بازگشت به مهندسی مدرن و کار با هوش مصنوعی · فصل ۵ — Shell، Workspace، Lockfile و Build Closure · 27
Exact Toolchain Closure و Offline Reproducibility
میتواند «Exact Toolchain Closure و Offline Reproducibility» را در یک workspace محدود طوری پیکربندی کند که dependency/toolchain state قابل تکرار و قابل بررسی باشد.
خلاصهٔ درس
میتواند «Exact Toolchain Closure و Offline Reproducibility» را در یک workspace محدود طوری پیکربندی کند که dependency/toolchain state قابل تکرار و قابل بررسی باشد.
خروجی این درس
میتواند «Exact Toolchain Closure و Offline Reproducibility» را در یک workspace محدود طوری پیکربندی کند که dependency/toolchain state قابل تکرار و قابل بررسی باشد.
سه lockfile سبز میتوانند یک workspace آفلاین خراب بسازند
یک repository محدود سه بخش دارد: workspace جاوااسکریپت با pnpm، پروژه Python با uv و workspace Rust با Cargo. هر سه lockfile commit شدهاند و روی لپتاپ توسعهدهنده commandهای روزمره سبز هستند. با این حال روی یک محیط تازه و بدون شبکه، reconstruction متوقف میشود: نسخه Node با انتظار تیم یکی نیست، یک wheel محلی حذف شده و مقدار یک متغیر محیطی مؤثر ثبت نشده است. lockfileها تغییری نکردهاند، اما closure کامل نبوده است.
خروجی این درس Exact Toolchain Closure Manifest v1 است. این artifact قرار نیست بگوید «همهچیز برای همیشه reproducible است». باید دقیقاً نشان دهد چه workspace/configهایی authority هستند، کدام tool/runtimeها واقعاً فعالاند، lock state هر ecosystem چیست، کدام artifactهای محلی برای reconstruction لازماند، کدام environment/system inputs داخل claim هستند، clean reconstruction چگونه اجرا شده و چه residualهایی هنوز بیرون مرز ماندهاند.
این درس exit درسهای ابزارمحور فصل است: pnpm، uv/Python و Cargo/Rust را دوباره از صفر آموزش نمیدهد؛ evidence آنها را در یک decision قابل review به هم متصل میکند.
سه مسیر، یک evidence bar
در Foundation ابتدا فقط شش لایه را روی یک زنجیره بنویس: Authority/Workspace → Tool & Runtime Identity → Locks → Local Artifacts → Clean Offline Reconstruction → Residual Boundary. سپس برای هر لایه «declared / observed / unknown» را جدا کن.
در Repair روی کوچکترین edge شکسته بمان. اگر lock سالم است ولی runtime drift کرده، lock را refresh نکن. اگر runtime درست است ولی artifact محلی مفقود است، toolchain را عوض نکن. اگر فقط متغیر نویزی تغییر کرده، closure را بیدلیل invalid نکن.
در Experienced/Fast Path میتوان walkthrough دستورها را کوتاه کرد، اما این موارد حذف نمیشوند: identity دقیق، hash/inventory، clean-state proof، per-tool semantic source، mutation sensitivity، target boundary و residual-risk statement. سرعت مسیر نباید evidence bar را پایین بیاورد.
مدل ذهنی: closure یک زنجیره است، نه یک فایل
یک lockfile فقط بخشی از state را میبندد. pnpm-lock.yaml، uv.lock و Cargo.lock هرکدام در ecosystem خودشان معنا دارند. آنها بهتنهایی هویت Node، Python، uv، rustc یا rustup را تثبیت نمیکنند؛ همچنین OS، linker، locale، clock، CPU یا serviceهای بیرونی را بهطور خودکار داخل claim نمیآورند.
مدل این درس شش edge دارد:
- Authority/Workspace: فایلهایی که graph و config مورد انتظار را تعریف میکنند.
- Tool & Runtime Identity: executable و نسخهای که واقعاً interpretation/build را انجام میدهد.
- Locks: state حل dependency در همان tool.
- Local Artifacts: store/wheel/crate/path inputهایی که بدون شبکه باید موجود باشند.
- Clean Offline Reconstruction: حذف state نصب/build قبلی و بازسازی فقط از boundary اعلامشده.
- Residual Boundary: ورودیهایی که کنترل یا اثبات نشدهاند و بنابراین بیرون claim میمانند.
اگر یک edge ناشناخته باشد، نتیجه درست UNKNOWN یا BLOCKED است؛ نه اینکه از سبز بودن edge دیگر نتیجه کلی بسازیم.
Exact Toolchain Closure Manifest v1 را چگونه پر کنیم
برای هر ecosystem یک row مستقل بساز و ستونهای مشترک را فقط بهعنوان evidence category استفاده کن، نه semantic یکسان. حداقل ستونها: layer، authority_file_or_source، hash_or_identity، tool_runtime_target، lock_state، local_artifact_inventory، relevant_env_system_assumption، clean_offline_check، mutation_result، evidence_locator، decision و residual_boundary.
هر مقدار باید یکی از سه حالت داشته باشد: OBSERVED یعنی مستقیماً از file/command/hash دیده شده؛ INFERRED یعنی از evidence موجود نتیجه محدود گرفته شده؛ UNKNOWN یعنی هنوز evidence نداریم. در یک manifest قابل دفاع، UNKNOWN پنهان نمیشود.
یک closure claim خوب target را نیز نام میبرد. مثلاً «reconstruct این fixture روی همین OS/architecture با toolهای ثبتشده و artifactهای local اعلامشده» بسیار دقیقتر از «پروژه کاملاً reproducible است» است.
semantics ابزارها را جدا نگه دار، evidence را کنار هم بگذار
در pnpm، pnpm install --offline فقط از packageهایی استفاده میکند که در store موجودند و اگر package محلی پیدا نشود install fail میشود. --frozen-lockfile اجازه ساخت/تغییر lockfile را نمیدهد و وقتی lock با manifest sync نیست یا lockfile لازم وجود ندارد، install fail میشود. این semantics متعلق به pnpm است.
در uv، project locking و syncing رفتار خودشان را دارند. --locked lockfile را چک میکند و در صورت stale بودن بهجای update کردن fail میشود؛ --frozen از نسخههای lockfile بهعنوان source of truth استفاده میکند و check up-to-date را انجام نمیدهد. --offline access شبکه را محدود میکند. پس نام frozen در uv را نباید با تعریف Cargo جایگزین کرد.
در Cargo، --locked ثابتماندن dependency resolution نسبت به Cargo.lock را assert میکند، --offline دسترسی شبکه Cargo را ممنوع میکند و --frozen مشخصاً ترکیب locked و offline است. Rust toolchain یک لایه جداست: rustup میتواند از command shorthand، RUSTUP_TOOLCHAIN، directory override، rust-toolchain.toml یا default toolchain انتخاب کند. بنابراین Cargo.lock هویت compiler را ثابت نمیکند.
بعد از ثبت semantics مستقل، میتوان یک closure manifest مشترک ساخت چون categoryهای evidence شبیهاند: authority، identity، lock، local availability، clean reconstruction و residual. اشتراک در category به معنی اشتراک semantics flag نیست.
سه مثال که closure ناقص را آشکار میکنند
Baseline محدود: hash فایلهای workspace/config و lockها را بگیر، identity ابزارها را ثبت کن، inventory artifactهای local را بساز، MAKNOW_PROFILE=lab را بهعنوان environment input مؤثر اعلام کن و noise را بیرون claim نگه دار. سپس node_modules، .venv و target را پاک کن و reconstruction آفلاین هر ecosystem را با command مخصوص خودش اجرا کن. اگر یک runtime اصلاً موجود نیست، همان ecosystem RUNTIME_EVIDENCE_INCOMPLETE است؛ این failure را سبز نمیکنیم.
Missing artifact با lock ثابت: wheel موردنیاز Python را حذف کن اما uv.lock را دست نزن. یک محیط گرم ممکن است هنوز import کند، اما clean offline reconstruction باید fail شود. نتیجه: lock state unchanged است ولی LOCAL_ARTIFACT_AVAILABILITY شکسته است.
Tool/runtime drift: lockها را ثابت نگه دار و expectation مربوط به Node/Python/Rust identity را تغییر بده یا یک runtime متفاوت مشاهده کن. manifest باید قبل از اینکه موفقیت dependency install را به closure کلی تبدیل کند، drift را نشان دهد.
Relevant در برابر irrelevant env: MAKNOW_PROFILE را تغییر بده؛ چون داخل boundary اعلام شده است، decision باید invalidate یا regenerate شود. سپس فقط MAKNOW_NOISE را تغییر بده؛ اگر هیچ claim یا execution path به آن وابسته نیست، invalid کردن closure overreaction است.
پنج shortcut که باید رد شوند
«سه lockfile داریم، پس closure کامل است.» خیر؛ identity، artifact inventory، environment و residual target هنوز میتوانند باز باشند.
«همه ابزارها flag آفلاین/frozen دارند، پس یک تعریف عمومی کافی است.» خیر؛ source هر tool جداست و همان واژه میتواند semantics متفاوت داشته باشد.
«روی سیستم من بدون اینترنت command سبز شد.» اگر از warm store، .venv یا target قدیمی استفاده کردهای، clean reconstruction اثبات نشده است.
«هر تغییر environment باید hash را خراب کند.» فقط inputهای semantically relevant باید closure را تغییر دهند. noise بیربط را وارد contract نکن مگر دلیل داشته باشی.
«offline success یعنی bit-for-bit reproducible.» offline فقط بخشی از availability/control را نشان میدهد. timestamps، native toolchains، OS، locale، hardware و serviceهای بیرونی میتوانند بیرون boundary باشند.
برای مهندس باتجربه: closure را به review artifact تبدیل کن
Manifest را artifact قابل diff بساز. تغییر pnpm-lock.yaml، uv.lock یا Cargo.lock باید به row همان ecosystem وصل شود؛ تغییر rust-toolchain.toml یا Node/Python identity باید به identity layer وصل شود؛ تغییر artifact inventory باید independent از lock diff دیده شود.
در CI، یک job «warm verification» با job «clean offline reconstruction» یکسان نیست. اگر هدف disconnected deployment یا hermetic-ish CI است، clean job باید state نصب/build را پاک کند و از boundary محلی مشخص استفاده کند. هر runtime unavailable یا artifact missing باید non-zero و fail-closed باشد.
همچنین version string بهتنهایی همیشه کافی نیست. وقتی claim مهم است executable path، platform/target و config selector را نیز نگه دار. اما اطلاعاتی که material نیستند را بیدلیل hash نکن؛ manifest باید reviewable بماند.
closure باریک اما صادقانه در برابر closure گسترده و پرهزینه
هرچه boundary را وسیعتر کنی، هزینه capture و maintenance بیشتر میشود. اضافهکردن OS image digest، compiler/linker، locale و hardware میتواند claim را قویتر کند، ولی فقط وقتی واقعاً control و verification آنها را اجرا میکنی. ثبت نام آنها بدون oracle ارزش زیادی ندارد.
Prepared local stores/wheelhouse/crate caches برای offline reconstruction مفیدند، اما provenance و update lifecycle خودشان را دارند. این درس integrity inventory را evidence میگیرد، نه اینکه upstream supply-chain trust یا signing را اثبات کند.
Container/Nix/Bazel ممکن است در مراحل بعد boundaryهای بیشتری را استاندارد کنند، اما در این درس replacement برای فهم layerها نیستند. حتی container image هم اگر tag شناور یا artifact بیرونی نامشخص داشته باشد، closure کامل نمیسازد.
Lab: یک fixture polyglot، چهار mutation
TOL006-LAB1 سه subfixture کوچک را کنار هم میگذارد: pnpm، uv/Python و Cargo/Rust. ابتدا python setup_fixture.py را اجرا کن. سپس MAKNOW_PROFILE=lab را تنظیم کن و python inspect_closure.py را برای تولید baseline inventory اجرا کن.
python run_clean_reconstruction.py baseline stateهای نصب/build را پاک میکند و برای هر ecosystem فقط command آفلاین/locked مخصوص همان ابزار را اجرا میکند. اگر pnpm، uv یا Cargo/Rust در دستگاه موجود نباشد، runner با RUNTIME_EVIDENCE_INCOMPLETE و exit code غیرصفر پایان مییابد؛ unavailable بودن evidence هرگز PASS نیست.
Mutationها را پس از reset جدا اجرا کن: remove-python-artifact، tool-expectation-drift و lock-drift. mutation محیطی با تغییر MAKNOW_PROFILE اجرا میشود و noise با MAKNOW_NOISE بررسی میشود. هدف حفظ sequence نیست؛ هدف این است که بگویی کدام layer invalid شده و کدام evidence هنوز معتبر است.
حداقل evidence قابل دفاع برای chapter exit
برای هر ecosystem: authority/config hashes، tool/runtime identities، lock hash/state، inventory artifactهای local، clean-state actions، command/result آفلاین و evidence locator را نگه دار. برای کل fixture: relevant environment، target/system assumptions، mutation results و residual boundary را اضافه کن.
Strongest green claim این درس محدود است: «در target ثبتشده، با tool/runtime identityهای ثبتشده و local closure boundary اعلامشده، هر ecosystem قابل اجرا از clean state بدون نیاز به outbound network reconstruct شد؛ mutationهای material manifest/verification را تغییر دادند و residualهای اعلامشده هنوز خارج claim هستند.» اگر یکی از runtimeها موجود نیست، این جمله مجاز نیست.
Decisionها فقط ACCEPT_BOUNDED، BLOCKED_MISSING_EVIDENCE یا REGENERATE_AFTER_DRIFT هستند. «probably reproducible» decision قابل audit نیست.
ارزیابی: diagnosis، decision، application و transfer
AS1 — Diagnosis: سه lock hash ثابتاند، اما Node version و rust-toolchain.toml با manifest قبلی متفاوتاند. کوچکترین layerهای شکسته را نام ببر و بگو چرا lock refresh راهحل verification نیست.
AS2 — Decision: تیم clean uv reconstruction دارد، اما pnpm فقط روی warm store اجرا شده و Cargo runtime identity ثبت نشده است. برای Exact Toolchain Closure Manifest v1 یکی از ACCEPT_BOUNDED، BLOCKED_MISSING_EVIDENCE یا REGENERATE_AFTER_DRIFT را انتخاب کن و residual risk را بنویس.
AS3 — Application: Lab را اجرا کن، matrix را کامل کن و نشان بده حذف wheel، tool expectation drift و relevant-env mutation چگونه evidence متفاوتی را invalid میکنند. noise بیربط نباید بدون دلیل closure را invalidate کند.
AS4 — Transfer: fixture دوم Node/Python/Rust identityهای جدید، یک artifact مفقود، یک env material و یک env نویزی دارد. بدون sequence hint یک manifest جدید یا BLOCKED state بساز و دقیقاً بگو چه چیزی هنوز اثبات نشده است.
مرز منبع و تازگی
Claimهای tool-specific در 2026-09-09 فقط با مستندات رسمی recheck شدهاند: pnpm Workspace و install options، uv locking/syncing و CLI، Cargo build options و rustup toolchain overrides. این منابع semantics componentها را پشتیبانی میکنند؛ Exact Toolchain Closure Manifest v1 طراحی MAKNOW است و به ابزارها نسبت داده نمیشود.
Review بعدی Bible 2026-10-17 است. اگر defaultها یا semantics flagها عوض شوند، source layer volatile refresh میشود؛ outcome درس به bit-identical system reproducibility، supply-chain trust یا correctness کامل گسترش پیدا نمیکند.
ارزیابی
پاسخنامهی این درس در بستهی آموزش وجود ندارد؛ خودت جواب بده و صادقانه علامت بزن. «نیاز به مرور» درس را به صف تکرار میفرستد.
AS1 diagnosis
Three lock hashes are unchanged, but Node identity and rust-toolchain.toml differ from the previous manifest. Diagnose the smallest broken closure layers and reject one tempting but invalid fix.
AS2 decision
Clean uv reconstruction exists; pnpm evidence is warm-store only; Cargo runtime identity is missing. Choose a bounded closure decision and state residual risk.
AS3 application
Run TOL006-LAB1, complete the closure matrix, and classify missing-artifact, tool-drift, and relevant-environment mutations with evidence locators.
AS4 transfer
Transfer the method to the changed fixture with new tool identities, a missing artifact, one material env input and one noise variable; produce a new bounded manifest or BLOCKED state.
ملاکهای ارزیابی (rubric)
- Boundary correctness — critical: identifies the smallest material closure layer and does not silently expand the claim.
- Evidence traceability — critical: every material conclusion resolves to a file/hash/command observation or is explicitly UNKNOWN.
- Mutation robustness — critical: the decision changes for material drift and stays stable for justified irrelevant noise.
- Transfer/residual risk: the method survives a changed fixture and residual OS/native/time/locale/hardware uncertainty is stated.
A green command without the surrounding closure evidence is insufficient.
منابع و تازگی
مطالعهٔ موارد (case studies)
فایلهای آزمایشگاه
python setup_fixture.pyرا اجرا کن.- متغیر
MAKNOW_PROFILE=labرا تنظیم کن.MAKNOW_NOISEعمداً خارج از closure claim است. python inspect_closure.pyرا اجرا کن. گزارش باید Python interpreter واقعی همین Lab، در صورت وجود Rust active-toolchain، و Rust path dependencyهای resolveشده از جمله crate غیرعضوlocal_deps/text_fmtرا ثبت کند.python run_clean_reconstruction.py baselineرا اجرا کن. نبود runtime لازم باید باRUNTIME_EVIDENCE_INCOMPLETEو کد غیرصفر بسته شود؛ اگر هر failure قطعی در ecosystem/tool/environment دیده شد، آن failure بر نبود runtimeهای دیگر اولویت دارد و نتیجهRECONSTRUCTION_FAILاست.- هر بار fixture را reset کن و یکی از mutationهای
remove-python-artifact،tool-expectation-driftیاlock-driftرا جداگانه اجرا کن. - برای environment probe،
MAKNOW_PROFILEرا بهعنوان ورودی material وMAKNOW_NOISEرا بهعنوان noise تغییر بده. - Exact Toolchain Closure Manifest v1 را کامل کن و residualهای OS/native/time/locale/hardware را صریح بنویس.
هیچ scriptای ابزار نصب نمیکند و عمداً به remote registry دسترسی نمیگیرد.
رسانه و اسلایدهای درس
فایلهای آموزشی (دانلود)