Egy kolléga a Northstar tanulóprojektet adja át neked: a
lab/ mappát, benne a determinisztikus adatgenerátorral, a
kezdő elemző notebookkal, a src/northstar modullal és a
manifesttel. A te feladatod tiszta mappában rekonstruálni a környezetet,
legenerálni vagy ellenőrizni az adatot, lefuttatni a notebookot, és a
futást visszakereshető jegyzékben dokumentálni. A kolléga egyetlen
belépő parancsot kér: aki klónozza a repót, annak ne kelljen fejben
tartania a lépések sorrendjét.
A megfigyelési egység itt nem ügyfél és nem rendelés, hanem egy
ellenőrző futás egy adott gépen. A bemenet a projektfájl-készlet és a
gép környezete; a kimenet a futási jegyzék, az újra futtatott notebook
és a kontrollösszeg-eredmény. Az előzetes eredménybecslés: ha az átadott
repo konzisztens, akkor a manifestben rögzített hashvizsgálat
failures=0 értékkel zár, a quickcheck a szerződésben
rögzített sorszámokat adja vissza (300 ügyfél, 2400 rendelés, 4796
tételsor az alaps seed esetén), és a git-munkaállapot tiszta marad. A
tényleges hashértékeket nem találgathatjuk előre; azokat a labor
manifest.json fájlja tartalmazza.
A reprodukálható munkaasztal hat rétegből áll, és a belépő szkript feladata az, hogy ezeket sorban, hiba esetén korán álljon meg.
A projektgyökér az a mappa, ahol a pyproject.toml
található. Minden relatív útvonal innen értendő:
scripts/entrypoint.sh, src/northstar,
manifest.json. Ha más munkakönyvtárból indítod a parancsot,
akkor nem a szkript hibás, hanem a hívási kontextus.
A belépő szkript ezért az első soraiban explicit horgonyt ellenőriz:
if [[ ! -f pyproject.toml ]]; then
echo "Run this script from the lab project root." >&2
exit 2
fiA szabály: egy parancs akkor hordozható, ha a meghívás helyét a projektgyökér rögzíti, és a hívó nem támaszkodik rejtett, gépenként eltérő környezeti változóra.
A labor nem feltételez előre feloldott uv.lock fájlt. Az
első tiszta futtatáskor a felbontást el kell végezni, utána viszont a
lock már rögzített állapotot jelent. A uv projekt-sync
dokumentációjának szerepe szerint a --locked kapcsoló
azt ellenőrzi, hogy a lock és a pyproject.toml összhangban
van-e; a --frozen ennél szigorúbb, és aktualitásvizsgálatot
sem végez.
A belépő szkript logikája:
uv.lock, készítsd el;uv sync --locked;uv run-on keresztül
fusson, hogy a projekt .venv-je legyen az interpreter.A lokális python3 parancs nem azonos a projekt
interpreterével. Az átadás során mindig a projekt
.venv/bin/python (Windowson
.venv\Scripts\python.exe) az irányadó.
A 3.4 alfejezetben már láttuk, hogy a
Jupyter-kernel egy külön folyamat, amelynek argv[0] eleme a
Python-útvonal. A notebook akkor reprodukálható, ha ez az útvonal
pontosan a projekt .venv-ére mutat. A belépő szkript ezért
regisztrál egy kernelspecet, és az nbconvert végrehajtásnál
ezt a kernelnevet adja meg:
uv run python -m ipykernel install --user \
--name northstar-clean-handoff \
--display-name "Northstar clean handoff"A regisztráció önmagában nem futtat semmit; a notebook végrehajtása
csak ezután következhet. A kernelválasztás és a
Restart and Run All a JupyterLab felületén is ugyanezt a
kernelt kell, hogy használja.
A Git a forráskód, a notebook és a szkriptek verzióját őrzi. A
.venv/ és a run/ mappa a
.gitignore-ba tartozik, mert gépfüggő vagy újragenerálható
állapot. A belépő szkript a végén git status --porcelain=v1
kimenetet rögzít, hogy a fogadó gép naplójában látható legyen: a
futtatás nem hagyott-e váratlan módosítást a követett fájlokban.
A porcelain=v1 formátum azért hasznos, mert stabil,
scriptből elemezhető kimenetet ad. A naplóba kerülő Git-állapot egy
ellenőrző jel, nem a tudományos eredmény.
A futási jegyzék nem más, mint a parancsok és kimeneteik időbélyegzett naplója. A minimálisan érdemes mezők:
| Mező | Példa | Mit ellenőriz? |
|---|---|---|
run_id |
20261020T083011Z |
egyedi futtatás |
python_executable |
.../lab/.venv/bin/python |
projekt interpreter |
kernel_name |
northstar-clean-handoff |
notebook kernel |
hash_result |
checked=6 failures=0 |
fájl integritás |
A naplót a run/ mappába írjuk, a belépő szkript pedig
tee-vel egyszerre mutatja és rögzíti a kimenetet. A
jegyzéket akkor lehet hatékonyan használni, ha egy óra múlva
grep-pelhető, nem akkor, ha egy képernyőképen zöld pipa
látszik.
A kontrollösszeg itt SHA-256 üzenetkivonat (hash). Egy fájl bájtsorozatához rendelünk egy 64 hexadecimális jegyből álló szöveget:
ahol a fájl -edik bájtja, a fájl mérete bájtban, pedig a kimeneti kivonat. A kimenet 64 hexa jegy, tehát bájt, azaz bit. Egy hexa jegy négy bitet kódol: például , egy bájt pedig két hexa jegyből áll, -tól -ig.
A hash nem „összeg” a hétköznapi értelemben. Egy egyszerű bájtsorrend-összegnél az és a sorozat azonos eredményt adhat, így a sorrendhiba láthatatlan marad. A SHA-256 konstrukciójának célja éppen az, hogy egyetlen bájt megváltozása is előrejelezhetetlenül más kimenetet adjon. Ez nem abszolút matematikai garancia az ütközésmentességre; a mi kontrollünkben az integritásvizsgálat egy manifesthez rögzített referenciaértékhez hasonlít, és a manifest maga is megbízhatósági kérdés.
Az itteni futási napló minden idő- és verzióadata szemléltető, nem a te gépeden mért kimenet. A tényleges futtatást a tanulói labor végzi; a hashértékeket kizárólag a labor manifestje tartalmazza.
A labor gyökerében az alábbi szerkezetet feltételezzük a 4.3 átadásából:
lab/
├── pyproject.toml
├── src/northstar/metrics.py
├── notebooks/explore.ipynb
├── generate_data.py
├── quickcheck.py
├── manifest.json
├── scripts/
│ ├── entrypoint.sh
│ └── verify_hashes.py
└── run/
A verify_hashes.py egyetlen feladatot lát el: a
manifestben rögzített elvárt SHA-256 értékeket összehasonlítja a
tényleges fájlokkal.
"""Verify files against the SHA-256 hashes recorded in manifest.json.
An equal digest means the bytes are almost certainly identical, but the
statement is only as trustworthy as the manifest itself.
"""
from __future__ import annotations
import argparse
import hashlib
import json
from pathlib import Path
CHUNK = 1024 * 1024
def sha256(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as handle:
while chunk := handle.read(CHUNK):
digest.update(chunk)
return digest.hexdigest()
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--manifest", type=Path, default=Path("manifest.json"))
parser.add_argument("--root", type=Path, default=Path("."))
args = parser.parse_args()
payload = json.loads(args.manifest.read_text(encoding="utf-8"))
expected = payload.get("files")
if not isinstance(expected, dict):
raise ValueError("manifest requires a 'files' object")
failures = 0
checked = 0
for relative, expected_digest in expected.items():
target = (args.root / relative).resolve()
checked += 1
if not target.exists():
print(f"{relative}: MISSING")
failures += 1
continue
actual_digest = sha256(target)
if actual_digest != expected_digest:
print(f"{relative}: MISMATCH")
failures += 1
continue
print(f"{relative}: OK")
print(f"checked={checked} failures={failures}")
return 0 if failures == 0 else 1
if __name__ == "__main__":
raise SystemExit(main())A files kulcs egy lehetséges manifestszerződés; ha a
labor manifest.json szerkezete más kulcsneveket használ, a
hívó oldalon ahhoz kell igazítani. A hash-eredmény összehasonlítása
pontos egyezést kér: nincs numerikus tolerancia, mert bájtszintű
integritásról van szó.
A belépő szkript Linux/macOS bash környezetre:
#!/usr/bin/env bash
# One-command handoff check for the Northstar lab.
# Run from the project root: bash scripts/entrypoint.sh
set -euo pipefail
if [[ ! -f pyproject.toml ]]; then
echo "Run this script from the lab project root." >&2
exit 2
fi
RUN_DIR="run"
STAMP="$(date -u +%Y%m%dT%H%M%SZ)"
LOG="${RUN_DIR}/handoff-${STAMP}.log"
mkdir -p "${RUN_DIR}"
log() { echo "$(date -u +%H:%M:%SZ) $*" | tee -a "${LOG}"; }
log "python outside project: $(command -v python3 || echo missing)"
log "uv: $(command -v uv || echo missing)"
if [[ ! -f uv.lock ]]; then
uv lock | tee -a "${LOG}"
fi
uv sync --locked | tee -a "${LOG}"
uv run python -m ipykernel install --user \
--name northstar-clean-handoff \
--display-name "Northstar clean handoff" | tee -a "${LOG}"
uv run python generate_data.py | tee -a "${LOG}"
uv run python scripts/verify_hashes.py --manifest manifest.json | tee -a "${LOG}"
uv run python quickcheck.py | tee -a "${LOG}"
uv run python -m northstar.metrics --help | tee -a "${LOG}"
uv run jupyter nbconvert --to notebook --execute notebooks/explore.ipynb \
--output explore.executed.ipynb \
--output-dir run \
--ExecutePreprocessor.kernel_name=northstar-clean-handoff \
--ExecutePreprocessor.timeout=120 | tee -a "${LOG}"
uv run python - <<'PY' | tee -a "${LOG}"
import platform, sys
import numpy, pandas
print("python", sys.version.split()[0])
print("executable", sys.executable)
print("platform", platform.platform())
print("numpy", numpy.__version__)
print("pandas", pandas.__version__)
PY
if git rev-parse --is-inside-work-tree >/dev/null 2>&1; then
git status --porcelain=v1 >> "${LOG}"
fi
log "HANDOFF OK"A parancs a projektgyökérből:
bash scripts/entrypoint.shA várható belépési folyamat tehát egyetlen sor a fogadó oldalon. A
szkripten belül minden lépés külön fut, és a
set -euo pipefail miatt az első hibánál megáll. A
--ExecutePreprocessor.kernel_name explicit kernelválasztása
azért fontos, mert különben az nbconvert a notebook
metaadatában tárolt régi kernelt használhatná.
A sikeres futás után a napló szerkezete ilyen lehet; a konkrét idők, útvonalak és verziók a laborban futás közben derülnek ki:
2026-10-20T08:30:11Z python outside project: /usr/bin/python3
2026-10-20T08:30:11Z uv: /home/ada/.local/bin/uv
2026-10-20T08:31:02Z checked=6 failures=0
2026-10-20T08:31:14Z converted notebooks/explore.ipynb to run/explore.executed.ipynb
2026-10-20T08:31:20Z executable: /home/ada/work/lab/.venv/bin/python
2026-10-20T08:31:21Z HANDOFF OK
A failures=0 azt jelenti, hogy a hat ellenőrzött CSV
bájtjai egyeznek a manifestben rögzített elvárt hash-ekkel. Ez nem
jelenti automatikusan, hogy a notebook tartalma lefutott; a notebook
végrehajtását az nbconvert sora és a létrejött
run/explore.executed.ipynb igazolja. A
python outside project sorban a python3 lehet
a rendszerinterpreter, de a fontos sor a uv run által kiírt
executable: annak a .venv/bin/python útvonalra
kell mutatnia.
A napló utolsó git status --porcelain blokkja akkor jó,
ha üres. Ha a generálás módosítaná a követett CSV-ket, az azonnal
látszana; ebben az esetben vagy a generátor viselkedését, vagy a fájlok
követésének döntését kell felülvizsgálni. A hash-ellenőrzés és a
Git-állapot két különböző réteg: az egyik a bájtok, a másik a verziózott
források állapota.
Értékelési rubrika (20 pont). Környezet és interpreter azonosítása: 3 pont; forrásadat és hash/fájlméret rögzítése: 3 pont; megfigyelési egység és join-invariáns: 4 pont; friss kernelben teljes futás: 4 pont; értelmezhető hibajegyzék és javítás: 3 pont; átadható napló és README: 3 pont. A rubrika a készségre ad visszajelzést, nem csak a »lefutott« állapotra.
Kritikus kizáró hiba: előre nem ismert információ beemelése a predikcióba, többszörözött megfigyelési egység vagy az ellenőrzés hamis sikernek jelentése esetén a megoldás nem fogadható el teljesítettnek, még ha a részpontszám magas is. A javítás után új, tiszta futás szükséges.
Értelmezés. A társ a következő naplórészletet
küldi: checked=6 failures=0 és HANDOFF OK. Azt
állítja, hogy a notebook is biztosan sikerült, mert a hash-ellenőrzés
zöld. Miért hibás a következtetés? Nevezd meg, melyik naplósor vagy fájl
bizonyítaná a notebook végrehajtását, és mit nem bizonyít a
hash.
Megvalósítás. A labor
scripts/verify_hashes.py fájlja hiányzik. Írd meg a fenti
szerződés szerint, majd teszteld egy háromsoros mini manifesten:
sample.csv elvárt hash-ét a tényleges fájlból számold ki,
futtasd le, változtass egyetlen bájtot, és futtasd újra. Rögzítsd
mindkét kimenetet és a kilépési kódot.
Hibadiagnózis. A társ gépen a
uv run python -c "import northstar; print('ok')" parancs
sikeres, de a notebook első cellája
ModuleNotFoundError: No module named 'northstar' hibát ad.
A notebookban a import sys; print(sys.executable) sor
/usr/bin/python3 értéket ír ki. Add meg a hiba valószínű
okát, a kimutatásához szükséges parancsot, és a javítási utat a
kernelspec szintjén.
Megváltozott feltétel. A fogadó gép Windowson
fut, bash nem elérhető. Írd át a belépő munkamenet lényegi lépéseit
PowerShellre a projektgyökérből, beleértve a lock, a sync, a
hash-ellenőrzés és a Get-FileHash használatát. Nevezd meg,
milyen bájtszintű kockázatot jelenthet a sorelválasztó eltérése, ha a
generátor platformfüggő sortörést írna.
Megváltozott kontraktus. A
quickcheck.py egy új sémaszabályt ad hozzá: minden
order_items sorra igaznak kell lennie a
line_total_minor = quantity * unit_price_minor - discount_minor
egyenlőségnek. A hash-ellenőrzés failures=0, de a
quickcheck hibát jelez. Mi a diagnózis iránya, és miért nem mond ellent
a két eredmény?
A hash-ellenőrzés a CSV-fájlok bájtjait hasonlítja a manifesthez.
A notebook végrehajtását az nbconvert naplósora és a
generált run/explore.executed.ipynb igazolja. A
failures=0 tehát szükséges, de nem elégséges feltétele a
teljes átadási célnak; a környezet, a kernel és a kód futtatása önálló
bizonyítékot igényel.
A fenti verify_hashes.py kód közvetlenül
használható. A mini teszt menete Linuxon:
printf 'alpha\n' > sample.csv
sha256sum sample.csv
# a kapott 64 hexa karaktert írd a mini-manifest.json "files" objektumába
uv run python scripts/verify_hashes.py --manifest mini-manifest.json
# checked=1 failures=0
printf 'beta\n' > sample.csv
uv run python scripts/verify_hashes.py --manifest mini-manifest.json
# checked=1 failures=1, exit code 1Az ellenőrző invariáns: azonos bemeneti bájtokhoz azonos digest,
eltérő bájthoz a szkript failures=1 és nem nulla kilépési
kód. A hashnél tűrés nincs.
.venv. A diagnózis:uv run python -c "import sys; print(sys.executable)"Ha ez .../lab/.venv/bin/python, a notebookban viszont
/usr/bin/python3, akkor a kernelspec rossz. A javítás a
projektkörnyezetből regisztrált kernel:
uv run python -m ipykernel install --user \
--name northstar-lab \
--display-name "Northstar lab"Ezután a JupyterLabban a Kernel > Change Kernel
menüben a Northstar lab kernelt válaszd, majd futtasd a
Restart and Run All parancsot. Az ismételt
sys.executable kiírásnak a .venv/bin/python
útvonallal kell megegyeznie. A pontos útvonal gépfüggő, az invariáns a
két parancs kimenetének egyezése.
Set-Location C:\work\northstar-lab
if (-not (Test-Path uv.lock)) { uv lock }
uv sync --locked
uv run python scripts\verify_hashes.py --manifest manifest.json
Get-FileHash customers.csv -Algorithm SHA256
uv run python -m ipykernel install --user `
--name northstar-lab `
--display-name "Northstar lab"A projekt interpreter itt
C:\work\northstar-lab\.venv\Scripts\python.exe, tehát a
kernel bejegyzés argv[0] eleme más elérési útvonal, mint
Linuxon. A sorelválasztó kockázata: ha a generátor szöveges módban ír,
Windowson \r\n, Linuxon \n kerülhet a fájlba,
így a sorok logikailag azonosak, de a hash eltér, mert a hash a bájtokra
vonatkozik. A bitazonossághoz a generátornak platformfüggetlen
újsor-konvenciót kell írnia, vagy a manifestnek külön szerződnie kell a
sortörésről.
A leggyakoribb hiba a képernyőkép mint „bizonyíték”. A társ küld egy
felvételt a zöld pipáról, de utólag senki sem tudja megmondani, melyik
kernel futott, mi volt a Python-útvonal, vagy hány hash-ellenőrzés ment
át. Ha viszont a run/handoff-*.log szövegfájlt küldi, a
grep, a diff és a kolléga is pontosan követni tudja a
lépéseket.
Ugyanilyen félreértés a hash túlértékelése. A failures=0
nem jelenti, hogy a manifest helyes; ha valaki a manifestet hibásan
frissítette, a rossz elvárt értékhez is lehet tökéletes egyezés. Ezért a
hash-ellenőrzés mellé mindig kell legalább egy független sémakontroll,
például a quickcheck.py invariánsai. A hash az integritás
egy rétegét, nem a tudományos helyességet igazolja.
Futtatás után kérdezd végig a következőket:
pyproject.toml jelen volt?executable sora a .venv-re mutat,
és a notebook kernelneve azonos a regisztrált kernellel?checked=6 failures=0, a quickcheck a
rögzített sorszámokat adja?nbconvert révén valóban létrehozta a
kimeneti fájlt?Ha bármelyik kérdésre nemleges a válasz, a futási jegyzékből azonosítani kell a réteget, és csak azt javítani; a következő futtatást újraindított, tiszta kernelrel kell elvégezni.
A megismételhető kutatói munkaasztal nem egyetlen notebook, hanem hat
együttműködő réteg: projektgyökér, project interpreter, regisztrált
kernel, Git-állapot, futási jegyzék és kontrollösszeg-manifest. A belépő
szkript a lock hiányát első futtatáskor feloldja, --locked
módban szinkronizál, a notebookot a projekt .venv-hez
regisztrált kernellel futtatja, a hash-ellenőrzést és a sémakontrollt
elkülönítetten rögzíti. A failures=0 a fájlok integritását
igazolja, de nem helyettesíti a notebook-végrehajtás bizonyítékát. A
következő fejezetben NumPy-tömbökkel dolgozunk: shape, tengely és dtype
válik a numerikus munka új ellenőrző szerződésévé.