From 9dc1da47ccfbedb3a3d68e071fe9562e8d802593 Mon Sep 17 00:00:00 2001 From: sametbasbug Date: Fri, 18 Sep 2026 20:44:33 +0300 Subject: [PATCH] =?UTF-8?q?Undici=208'e=20ge=C3=A7=20ve=20PR=20kontrolleri?= =?UTF-8?q?ni=20paralelle=C5=9Ftir?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Undici 8'in Node global fetch dispatcher sözleşmesini davranışsal regresyonla kilitle. PR doğrulamasında production deploy'daki eksiksiz D1 bölünmesini paralel kullan ve foundation workflow'undaki yinelenen tam regresyonu kaldır; manuel foundation koşuları tam tanıyı korur. --- .github/workflows/pull-request.yml | 93 ++++++++++++++++++++--- .github/workflows/v6-foundation-check.yml | 26 +++---- docs/V6_PROJECT_LEDGER.md | 22 ++++++ package-lock.json | 10 +-- package.json | 4 +- scripts/orbit-actions-scope-tests.mjs | 18 +++++ scripts/orbit-test-ports-tests.ts | 23 ++++++ scripts/support/test-http.ts | 45 ++++++----- 8 files changed, 191 insertions(+), 50 deletions(-) diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index f3b71c8..7309e6d 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -13,11 +13,13 @@ concurrency: group: pull-request-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true +# Reuse the production deploy's exhaustive D1 partition instead of running the +# full D1 suite serially before every other check. The final verify job keeps +# the stable branch-protection check name and fails unless every slice passes. jobs: - verify: + frontend: runs-on: ubuntu-latest - timeout-minutes: 25 - + timeout-minutes: 12 steps: - name: Checkout uses: actions/checkout@v7 @@ -31,13 +33,86 @@ jobs: - name: Install locked dependencies run: npm ci - - name: Verify types, content, API, D1 and browser behavior + - name: Verify scope classifier and production frontend run: | npm run actions:scope:test - npm run check - npm run build + npm run verify:frontend:production + + backend: + runs-on: ubuntu-latest + timeout-minutes: 12 + steps: + - name: Checkout + uses: actions/checkout@v7 + + - name: Set up Node + uses: actions/setup-node@v7 + with: + node-version: 24 + cache: npm + + - name: Install locked dependencies + run: npm ci + + - name: Verify D1 core and reference clients + run: npm run test:d1:core && npm run orbit:test:clients + + backend-publication: + runs-on: ubuntu-latest + timeout-minutes: 12 + steps: + - name: Checkout + uses: actions/checkout@v7 + + - name: Set up Node + uses: actions/setup-node@v7 + with: + node-version: 24 + cache: npm + + - name: Install locked dependencies + run: npm ci + + - name: Verify publication, renderer and style behavior + run: npm run test:d1:publication - - name: Verify production configuration and Worker bundle + backend-platform: + runs-on: ubuntu-latest + timeout-minutes: 12 + steps: + - name: Checkout + uses: actions/checkout@v7 + + - name: Set up Node + uses: actions/setup-node@v7 + with: + node-version: 24 + cache: npm + + - name: Install locked dependencies + run: npm ci + + - name: Verify dashboard, media and platform behavior + run: npm run test:d1:platform + + verify: + needs: + - frontend + - backend + - backend-publication + - backend-platform + if: ${{ always() }} + runs-on: ubuntu-latest + timeout-minutes: 3 + steps: + - name: Require every parallel verification slice + env: + FRONTEND_RESULT: ${{ needs.frontend.result }} + BACKEND_RESULT: ${{ needs.backend.result }} + PUBLICATION_RESULT: ${{ needs.backend-publication.result }} + PLATFORM_RESULT: ${{ needs.backend-platform.result }} run: | - npm run production:config:check - npm run worker:build:production:live + test "$FRONTEND_RESULT" = success + test "$BACKEND_RESULT" = success + test "$PUBLICATION_RESULT" = success + test "$PLATFORM_RESULT" = success diff --git a/.github/workflows/v6-foundation-check.yml b/.github/workflows/v6-foundation-check.yml index 76e371a..8241789 100644 --- a/.github/workflows/v6-foundation-check.yml +++ b/.github/workflows/v6-foundation-check.yml @@ -1,10 +1,8 @@ name: Orbit V6 Foundation Check -# Bu iş akışı bir zamanlar `v6/server-platform` geliştirme dalına da push -# tetikleyicisiyle bağlıydı. O dal birleşip emekli olduğundan tetikleyici -# hiçbir şeyi çalıştırmıyordu; kaldırıldı. Altyapıya dokunan PR'lar ve elle -# tetikleme kalıyor — main'e push'ta üretim doğrulamasını deploy-production -# iş akışı yapar. +# Pull Request Check already validates the complete D1 split, source checks +# and production-live artifact. This workflow is the extra deployment-mode +# guard for foundation-sensitive PRs. Manual runs retain full diagnostics. on: pull_request: branches: @@ -49,17 +47,19 @@ jobs: - name: Install dependencies run: npm ci - - name: Verify D1 foundation and Worker build - run: npm run v6:check + - name: Verify generic Worker bundle + run: npm run worker:build - name: Verify isolated staging bundle run: npm run worker:build:staging - - name: Verify production config parity - run: npm run production:config:check - - - name: Verify production live bundle without deployment - run: npm run worker:build:production:live - - name: Verify production dark-launch rollback bundle without deployment run: npm run worker:build:production:dark-launch + + - name: Run standalone full foundation diagnostics + if: ${{ github.event_name == 'workflow_dispatch' }} + run: | + npm run test:d1 + npm run check + npm run production:config:check + npm run worker:build:production:live diff --git a/docs/V6_PROJECT_LEDGER.md b/docs/V6_PROJECT_LEDGER.md index 007e13b..2424096 100644 --- a/docs/V6_PROJECT_LEDGER.md +++ b/docs/V6_PROJECT_LEDGER.md @@ -2305,3 +2305,25 @@ Anything under `docs/archive/` describes an earlier state and is frozen. Read it `site.tokens_issued`, `site.grant_revoked`, then a fresh consent and issue — in that order. Worth knowing before anyone reads a live grant row as proof that access was never withdrawn. + +### 2026-09-18 — Undici 8 and PR CI de-duplication + +- Upgraded the test-only direct Undici dependency and override from `7.29.1` + to `8.10.2`. Orbit's deployed Worker still does not ship Undici; it remains + Node-side test/build tooling. +- Undici 8 moved the npm package's global dispatcher slot from the historical + `.1`/`.2` aliasing to `.2` while Node 26 still retained `.1`. The old guard + treated every visible historical slot as authoritative and would therefore + reject a valid upgrade. The guard now verifies the package dispatcher plus + at least one live global slot, and a MockAgent regression proves that Node's + built-in `fetch` actually honors the dispatcher installed by the npm package. +- PR validation previously ran the full 341-test D1 suite serially in + `pull-request.yml` while `v6-foundation-check.yml` repeated the same D1, + source and production-live work for package/foundation changes. The PR path + now reuses the production deploy's exhaustive core/publication/platform + partition in parallel with the production frontend verifier, then preserves + the stable `verify` check as a fail-closed aggregator. +- The foundation PR workflow now owns only the extra generic, staging and + dark-launch bundle checks. Manual foundation runs still execute the full + historical diagnostics. This removes duplicate CI work without deleting any + validation surface. diff --git a/package-lock.json b/package-lock.json index d2b6cc4..6e483b3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,7 +25,7 @@ "sharp": "^0.35.4", "tsx": "4.23.13", "typescript": "6.0.3", - "undici": "^7.29.1", + "undici": "^8.10.2", "wrangler": "4.135.0" } }, @@ -5499,12 +5499,12 @@ "license": "MIT" }, "node_modules/undici": { - "version": "7.29.1", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.29.1.tgz", - "integrity": "sha512-RYONW2MeafgYlkVOKYKkA/Ag7BmXqgIWCa8t1m0JcxrQg9pI9lEqRhAOruOBCbAohOa/gkCF+iPi9hrgvTzu6Q==", + "version": "8.10.2", + "resolved": "https://registry.npmjs.org/undici/-/undici-8.10.2.tgz", + "integrity": "sha512-/y4/bH9YNU5hi9NIrpOuvGXFcxrj3CMrV+/AYpowAYTpHn8gX/XPFjNy766FPoYY0miQhdW977JFWKGNhBdwyQ==", "license": "MIT", "engines": { - "node": ">=20.18.1" + "node": ">=22.19.0" } }, "node_modules/undici-types": { diff --git a/package.json b/package.json index 35361e9..473b71d 100644 --- a/package.json +++ b/package.json @@ -77,10 +77,10 @@ "sharp": "^0.35.4", "tsx": "4.23.13", "typescript": "6.0.3", - "undici": "^7.29.1", + "undici": "^8.10.2", "wrangler": "4.135.0" }, "overrides": { - "undici": "^7.29.1" + "undici": "^8.10.2" } } diff --git a/scripts/orbit-actions-scope-tests.mjs b/scripts/orbit-actions-scope-tests.mjs index 0d9d124..d75e25c 100644 --- a/scripts/orbit-actions-scope-tests.mjs +++ b/scripts/orbit-actions-scope-tests.mjs @@ -73,4 +73,22 @@ test('every D1 test file the full suite runs is also in exactly one split', asyn `${name} is not run by the production deploy workflow`, ); } + + /* PR doğrulaması da aynı eksiksiz bölünmeyi paralel çalıştırıyor. Deploy + * listesi doğru kalıp PR listesi sessizce eksilirse merge öncesi kapı + * zayıflar; bunu ayrı bir sözleşme olarak kilitle. */ + const pullRequestWorkflow = await readFile( + new URL('../.github/workflows/pull-request.yml', import.meta.url), + 'utf8', + ); + for (const name of splitNames) { + assert.ok( + pullRequestWorkflow.includes(`npm run ${name}`), + `${name} is not run by the pull-request workflow`, + ); + } + assert.ok( + pullRequestWorkflow.includes('npm run verify:frontend:production'), + 'production frontend verifier is not run by the pull-request workflow', + ); }); diff --git a/scripts/orbit-test-ports-tests.ts b/scripts/orbit-test-ports-tests.ts index 550dfc2..b733aaf 100644 --- a/scripts/orbit-test-ports-tests.ts +++ b/scripts/orbit-test-ports-tests.ts @@ -3,6 +3,7 @@ import { spawn } from 'node:child_process'; import { existsSync, readFileSync, writeFileSync } from 'node:fs'; import path from 'node:path'; import { after, describe, test } from 'node:test'; +import { getGlobalDispatcher, MockAgent, setGlobalDispatcher } from 'undici'; import { claimPort, releasePort, reserveWorkerPorts, RESERVATION_DIRECTORY } from './orbit-test-ports'; /* Bu dosyanın ölçtüğü şey bir gecelik regresyon hatası. @@ -58,6 +59,28 @@ async function holdPortsInAChildProcess(): Promise<{ return { ports, stop: () => { child.stdin.write('\n'); child.kill('SIGTERM'); } }; } +describe('test HTTP dispatcher compatibility', () => { + test('Node global fetch honors the installed undici dispatcher', async () => { + const previous = getGlobalDispatcher(); + const mock = new MockAgent(); + mock.disableNetConnect(); + mock + .get('http://orbit-undici-dispatcher.invalid') + .intercept({ path: '/probe', method: 'GET' }) + .reply(200, 'orbit-dispatcher-ok'); + + setGlobalDispatcher(mock); + try { + const response = await globalThis.fetch('http://orbit-undici-dispatcher.invalid/probe'); + assert.equal(response.status, 200); + assert.equal(await response.text(), 'orbit-dispatcher-ok'); + } finally { + setGlobalDispatcher(previous); + await mock.close(); + } + }); +}); + describe('test worker port reservation', () => { test('concurrent processes never receive the same port', async () => { /* Yarışın gerçek şekli bu: aynı anda başlayan süreçler. diff --git a/scripts/support/test-http.ts b/scripts/support/test-http.ts index eb77f17..fd13c6a 100644 --- a/scripts/support/test-http.ts +++ b/scripts/support/test-http.ts @@ -30,14 +30,16 @@ * kendi bağlantısını açar ve yarış ortadan kalkar. Yerelde birkaç düzine * istek için maliyeti ölçülemeyecek kadar küçük. */ -import { Agent, setGlobalDispatcher } from 'undici'; +import { Agent, getGlobalDispatcher, setGlobalDispatcher } from 'undici'; import type { Dispatcher } from 'undici'; let installed = false; -/* Node'un global `fetch`'i dispatcher'ı `Symbol.for('undici.globalDispatcher.1')` - * üzerinden okuyor; undici paketinin `setGlobalDispatcher`'ı aynı sembole - * yazdığı için yerleşik fetch de bu ayarı görüyor. */ +/* Node'un global `fetch`'i ile npm'den kurulu undici aynı dispatcher + * sözleşmesini paylaşır. Sembolün sürüm eki undici major'ları arasında + * değişebilir; bu yüzden aşağıdaki bekçi tek bir tarihsel sembole bağlanmaz. + * Bu sözleşmenin gerçekten Node `fetch`'ine ulaştığı ayrıca + * orbit-test-ports-tests.ts içindeki MockAgent regresyonuyla ölçülür. */ export function useFreshConnectionPerRequest(): void { if (installed) return; const agent = new Agent({ pipelining: 0 }); @@ -48,31 +50,32 @@ export function useFreshConnectionPerRequest(): void { /* Bekçi: ayarın gerçekten yerleşip yerleşmediğini kontrol eder. * - * `setGlobalDispatcher` global bir sembole yazıyor ve Node'un yerleşik - * `fetch`'i onu okuyor. Bugün (Node 26 / undici 7) iki sembol de — - * `undici.globalDispatcher.1` ve `.2` — aynı nesneyi gösteriyor. Yarın - * paket ile Node'un sembolleri ayrışırsa çağrı sessizce hiçbir şey yapmaz: - * keep-alive geri gelir, flake geri gelir ve hiçbir test kırılmaz, çünkü - * bu yalnızca ara sıra düşen bir yarışı geri açar. + * Undici 7 aynı dispatcher'ı hem `.1` hem `.2` sembolünde görüyordu; + * undici 8 ile eski `.1` slotu Node tarafından tutulurken yeni paket `.2` + * slotunu kullanıyor. Bütün tarihsel slotların aynı nesne olmasını istemek + * geçerli major yükseltmesini yanlış negatifle durdurur. * - * Sessiz bozulmayı gürültülü hataya çeviriyoruz: yerleşmediyse testler - * daha ilk satırda dursun. */ + * Bunun yerine paket API'sinin beklenen dispatcher'ı döndürmesini ve bilinen + * global slotlardan en az birinin aynı nesneyi taşımasını istiyoruz. Node'un + * yerleşik fetch'inin de bu dispatcher'ı kullandığı ayrı regresyon testinde + * davranışsal olarak doğrulanıyor. */ function assertDispatcherInstalled(expected: Dispatcher): void { - const seen = [ + const symbols = [ Symbol.for('undici.globalDispatcher.1'), Symbol.for('undici.globalDispatcher.2'), - ].filter((symbol) => symbol in globalThis); - - const missed = seen.filter( - (symbol) => (globalThis as Record)[symbol] !== expected, + Symbol.for('undici.globalDispatcher.3'), + ]; + const seen = symbols.filter((symbol) => symbol in globalThis); + const matching = seen.filter( + (symbol) => (globalThis as Record)[symbol] === expected, ); - if (seen.length === 0 || missed.length > 0) { + if (getGlobalDispatcher() !== expected || matching.length === 0) { throw new Error( 'Test fetch bağlantı politikası yerleşmedi: undici setGlobalDispatcher ' + - "Node'un yerleşik fetch'inin okuduğu sembole yazmıyor. Keep-alive " + - 'yeniden kullanımı geri döner ve UND_ERR_SOCKET flake\'i yeniden ' + - 'başlar. support/test-http.ts içindeki gerekçeye bak; undici ve Node ' + + "beklenen global dispatcher'ı kurmadı. Keep-alive yeniden kullanımı " + + "geri döner ve UND_ERR_SOCKET flake'i yeniden başlar. " + + 'support/test-http.ts içindeki gerekçeye bak; undici ve Node ' + 'sürümlerinin uyumunu kontrol et.', ); }