NOT 0013 / PROOFFRAME

Damga worker'ı kapsıyordu, motoru değil

16 Eylül 2026Emir Hüseyin İnci7 dkEnglish

ProofFrame 0.7.2'yi yayınladım, sayfayı açtım, ve tarayıcı bana 0.7.1 çalıştırdığını söyledi. Bu cümledeki her şeyin doğru olduğu ortaya çıktı — inanmamın biraz zaman almasının sebebi de o.

Sayfada canlı bir demo var: motor WebAssembly'ye derlenmiş ve sekmenin içinde koşuyor, yani hiçbir şey yüklenmiyor. Bırakma alanının üstünde bir rozet duruyor, ve o rozet sabit bir metin basmıyor. Gerçekten yüklenmiş olan modülde engine_version çağırıyor. Yanındaki metin bunu açıkça söylüyor — aşağıda bildirdiği sürüm, çalıştırdığın yapının sürümüdür — ki bu, seni ele vermeye başlayana kadar verilmesi güzel bir söz.

proofframe 0.7.1 · wasm yazıyordu.

Kontrol ettiğim her şey doğruydu

Bariz şüpheliler önce gitti. Derlenmiş sayfa 0.7.2'yi yirmi beş yerde, 0.7.1'i hiç taşımıyordu. Crate yükseltilmişti. Modül yükseltilmiş kaynaktan yeniden derlenmişti — Compiling proofframe-wasm v0.7.2 build log'unda duruyor. Dosyayı yerel sunucudan çekip diskteki artifact'la bayt bayt karşılaştırdım:

curl .../proofframe_wasm_bg.wasm | cmp - assets/proofframe/wasm/proofframe_wasm_bg.wasm
# identical

Yani sunucu 0.7.2 gönderiyordu, sayfa onu istiyordu, ve motor 0.7.1 cevabı veriyordu. O boşlukta oturabilecek tek bir şey var.

Sayfanın zaten bildiği önbellek

Bu sayfa bu sorunla daha önce tanışmıştı. Motor uygulama paketinin içinde gelmiyor: kardeş bir klasörde iki dosya olarak duruyor — bir worker ve yanında worker'ın import ettiği WebAssembly modülü. İkisi de on dakikalık önbellekle statik dosya olarak sunuluyor ve hiçbir şey tarayıcıyı ikisini birlikte yeniden doğrulamaya zorlamıyor. Nitekim daha önceki bir sürüm bu arızanın gürültülü halini üretmişti: yenilenmiş bir sayfa, bayat bir worker'la konuşuyor, worker'ın hiç duymadığı bir operasyonu çağırıyor ve bu okuyucuya unknown operation olarak ulaşıyordu.

Onun çözümü bir içerik damgası oldu. Worker linter-worker.js?v=<worker'ın hash'i> olarak isteniyor, böylece yeni sayfa yeni worker istiyor, eski sayfa yazıldığı worker'da kalıyor. Worker değişip damga değişmezse düşen bir test de var. Sabitin üstündeki yorum bütün gerekçeyi anlatıyor, ve iyi bir yorum.

Damga c77cc6c9d3b7'ydi. Motoru değiştirmeden önce c77cc6c9d3b7'ydi, değiştirdikten sonra da c77cc6c9d3b7'ydi. Test de aynı fikirdeydi. Hiçbir şey değişmemişti, çünkü damganın izlediği hiçbir şey değişmemişti: motoru, worker'ın tek satırına dokunmadan yeniden derlemiştim.

Damga tam olarak yazıldığı işi yapıyordu. Yazıldığı iş, birlikte hareket etmesi gereken üç dosyadan birini kapsıyordu.

Göreli bir import damgayı neden düşürüyor

Worker motoru beklediğin şekilde import ediyordu:

import init, { engine_version, check_csv, … } from "./wasm/proofframe_wasm.js";

Göreli bir specifier, import eden modülün URL'ine göre çözülür, ve çözüm yolu korur ama sorgu dizesini düşürür. Worker linter-worker.js?v=c77cc6c9d3b7 olarak çekiliyor; import ettiği modül ise çıplak halde, wasm/proofframe_wasm.js olarak. Damga ilk sıçramada duruyor.

Bir sıçrama daha aşağıda, wasm-bindgen'in ürettiği glue kodu binary'nin kendisini buluyor:

module_or_path = new URL('proofframe_wasm_bg.wasm', import.meta.url);

Aynı kurgu, aynı sonuç. Hemfikir olması gereken üç dosya, biri damgalı.

Bunu görmesi zor kılan şey arızanın sessiz olması. Önceki hatadaki bayat çift çöküyordu: worker'dan uygulamadığı bir şey isteniyordu ve birileri hata alıyordu. Bu ise çalışıyor. Modül yükleniyor, her operasyon çalışıyor, her cevap kendisini üreten kod için doğru. Tek belirti bir sürüm geride kalmış bir sürüm dizesi — ve sürüm dizesi tam olarak üzerinden okuyup geçtiğin türden bir şeydir, çünkü ne yazması gerektiğini bilirsin.

Çözüm

Damgayı elle taşımak, ki bu dinamik import demek, çünkü statik bir specifier çalışma zamanı değeri alamaz:

const STAMP = new URLSearchParams(self.location.search).get("v");
const query = STAMP ? `?v=${STAMP}` : "";

const { default: init, engine_version, … } =
  await import(`./wasm/proofframe_wasm.js${query}`);

const started = init({
  module_or_path: new URL(`./wasm/proofframe_wasm_bg.wasm${query}`, import.meta.url),
}).then(engine_version);

Ve damgayı, koruduğunu iddia ettiği kümeye genişletmek. Artık üç dosyanın hash'i, ve yazılmıyor, türetiliyor: küçük bir betik baytları okuyup sabiti yeniden yazıyor, ve build onu kendinden önce koşturuyor. İnsanın değiştirmeyi hatırlaması gereken bir sayı, eninde sonunda yanlış olan bir sayıdır — ve bu, hiçbir şey fark etmeden bir sürüm boyunca yanlış kalmıştı.

Test de onunla birlikte taşındı. Aynı üç dosyayı hash'liyor, ve ayrıca damgalama betiğinin o üçünü kapsadığını doğruluyor — yoksa ikisi bir sayıda anlaşırken o sayının ne anlama geldiği konusunda anlaşmazlığa düşer. Worker'da artık modül için çıplak specifier bulunmadığını da kontrol ediyor, çünkü düşen tam olarak o satırdı.

Bundan çıkardığım

Damganın üstündeki yorum doğruydu, spesifikti ve çözdüğü problem konusunda dürüsttü. Tehlikeyi onu üreten olay üzerinden tarif ediyordu — çökme — ve dolayısıyla gerekçelendirdiği koruma da o olayın şeklindeydi. Aynı tehlikenin daha sessiz biçimi hiç görüş alanına girmemişti.

Testin şekli de aynıydı. Worker'ı değiştirdikten sonra damgayı yükselt diyordu; bu doğru, ve değişmez değil. Değişmez şu: tek bir birim olarak çekilen her şey tek bir birim olarak yeniden doğrulanır. Böyle yazıldığında eksik dosya bariz; tek bir dosya hakkında kural olarak yazıldığında test geçiyor.

Bunun bir versiyonu çoğu önbellekleme kurulumunda var: damgaladığın şey, damgalamayı hatırladığın şeydir, ve o genellikle giriş noktasıdır, ve giriş noktası genellikle değişen kısım değildir. Kendi sürümünü bildiren bir demo bunu öğrenmenin ucuz yolu — seninle anlaşmadığında ona inanman şartıyla.

Rozet artık 0.7.2 diyor.