# Delta 0.0.2-beta.1.fix.1 — clôture de la stabilisation runtime ## Base requise État `0.0.2-beta.1` committé et poussé après application du history : ```text history/0.0.2/beta.1.md = PARTIAL ``` La version devient : ```text 0.0.2-beta.1.fix.1 ``` ## Cause `beta.1` acquiert la réconciliation, la revue d'horizon, les audits Markdown/Rust, Cargo/Clippy/tests, la baseline Web et le manifest Vite. La gate générée n'exécute toutefois pas les trois smokes HTTP requis par le plan pendant la stabilisation. Cette omission appartient au contrat de validation livré, pas à une défaillance de l'opérateur. Le history `beta.1` reste donc `PARTIAL`. ## Objet du fix Le fix n'introduit aucune fonctionnalité. Il : - rend explicite chez `400-VALIDATION` qu'un smoke de stabilisation exigé par le plan ne peut pas être omis au motif qu'il n'existe aucun diff runtime ; - documente `beta.1 = PARTIAL` dans le plan et la matrice ; - rejoue la gate beta complète ; - ajoute les trois smokes HTTP production manquants avant d'autoriser la RC. ## Scope Fichiers remplacés : ```text VERSION Cargo.toml docs/rules/400-VALIDATION.md docs/plans/002-V0_0_2_GIT_SYNC_PLAN.md docs/validation/001-VALIDATION_MATRIX.md ``` Fichier ajouté : ```text deltas/0.0.2/beta.1.fix.1.md ``` Aucun fichier sous `crates/` ou `Web/` n'est remplacé. Aucun package, dépendance, contrat métier, comportement Git ou prochaine release n'est ajouté. ## Gate opérateur ```bash set -euo pipefail test "$(cat VERSION)" = '0.0.2-beta.1.fix.1' grep -F 'version = "0.0.2-beta.1.fix.1"' Cargo.toml grep -A2 '^## Statut$' history/0.0.2/beta.1.md | grep -Fx 'PARTIAL' git diff --check python3 -B scripts/audit_markdown_headers.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts deltas history python3 -B scripts/audit_markdown_links.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts deltas history python3 -B scripts/audit_markdown_tables.py README.md RULES.md ROADMAP.md CHANGELOG.md docs prompts deltas history cargo fmt --all cargo fmt --all -- --check python3 -B scripts/audit_rust_test_layout.py crates python3 -B scripts/audit_rust_blank_lines.py crates python3 -B scripts/audit_rust_item_spacing.py crates python3 -B scripts/audit_rust_module_order.py crates python3 -B scripts/audit_rust_trait_import_blocks.py crates python3 -B scripts/audit_rust_reexport_blocks.py crates python3 -B scripts/audit_rust_crate_root_facades.py crates python3 -B scripts/audit_rust_export_completeness.py --root . --summary-only cargo metadata --format-version 1 --no-deps > /tmp/sasedev-knowledge-0.0.2-beta.1.fix.1-metadata.json cargo check --workspace cargo clippy --workspace --all-targets --all-features -- -D warnings cargo test --workspace --all-targets --all-features ( cd Web/knowledge-template-lab npm ci npm run typecheck npm run build ) python3 - <<'PYGATE' import json from pathlib import Path metadata = json.loads(Path('/tmp/sasedev-knowledge-0.0.2-beta.1.fix.1-metadata.json').read_text(encoding='utf-8')) expected = { 'knowledge-app-lib', 'knowledge-domain-lib', 'knowledge-git-api', 'knowledge-git-lib', 'knowledge-sync-api', 'knowledge-web-lib', 'knowledge-pgr-server', 'knowledge-howtos-server', 'knowledge-snippets-server', } assert {package['name'] for package in metadata['packages']} == expected assert len(metadata['workspace_members']) == 9 manifest_path = Path('../builds/sasedev-knowledge/web/template-lab/.vite/manifest.json') assert manifest_path.is_file(), manifest_path manifest = json.loads(manifest_path.read_text(encoding='utf-8')) assert 'src/main.ts' in manifest, manifest.keys() study = Path('docs/studies/005-V0_0_2_GIT_SYNC_RECONCILIATION.md').read_text(encoding='utf-8') assert 'Aucun prompt `0.0.3` n'est créé' in study roadmap = Path('ROADMAP.md').read_text(encoding='utf-8') assert '0.0.3' not in roadmap assert not Path('prompts/003-V0_0_3_START_PROMPT.md').exists() assert not Path('history/0.0.2/beta.1.fix.1.md').exists() rule400 = Path('docs/rules/400-VALIDATION.md').read_text(encoding='utf-8') assert 'Lorsqu'un plan exige un smoke de stabilisation, cette preuve est obligatoire' in rule400 print('Knowledge 0.0.2-beta.1.fix.1 stabilization audit: clean') PYGATE python3 - <<'PYSMOKE' import json import os from pathlib import Path import signal import subprocess import time import urllib.request root = Path.cwd() build_root = (root / "../builds/sasedev-knowledge").resolve() manifest_path = build_root / "web/template-lab/.vite/manifest.json" manifest = json.loads(manifest_path.read_text(encoding="utf-8")) entry = manifest["src/main.ts"] asset_paths = ["/" + entry["file"].lstrip("/")] asset_paths.extend("/" + value.lstrip("/") for value in entry.get("css", [])) subprocess.run([ "cargo", "build", "-p", "knowledge-pgr-server", "-p", "knowledge-howtos-server", "-p", "knowledge-snippets-server", ], check=True) servers = ( ("knowledge-pgr-server", "KNOWLEDGE_PGR_BIND_ADDRESS", 18181), ("knowledge-howtos-server", "KNOWLEDGE_HOWTOS_BIND_ADDRESS", 18182), ("knowledge-snippets-server", "KNOWLEDGE_SNIPPETS_BIND_ADDRESS", 18183), ) for binary_name, bind_variable, port in servers: binary = build_root / "target/debug" / binary_name assert binary.is_file(), binary env = os.environ.copy() env.pop("KNOWLEDGE_VITE_DEV_ORIGIN", None) env["KNOWLEDGE_VITE_MANIFEST_PATH"] = str(manifest_path) env[bind_variable] = f"127.0.0.1:{port}" process = subprocess.Popen( [str(binary)], env=env, stdout=subprocess.PIPE, stderr=subprocess.PIPE, start_new_session=True, text=True, ) try: health_url = f"http://127.0.0.1:{port}/healthz" for attempt in range(50): try: with urllib.request.urlopen(health_url, timeout=1) as response: body = response.read().decode("utf-8") assert response.status == 200 assert body == "ok" break except Exception: if process.poll() is not None: stdout, stderr = process.communicate() raise AssertionError((binary_name, process.returncode, stdout, stderr)) if attempt == 49: raise time.sleep(0.1) with urllib.request.urlopen(f"http://127.0.0.1:{port}/", timeout=2) as response: html = response.read().decode("utf-8") assert response.status == 200 for asset_path in asset_paths: assert asset_path in html, (binary_name, asset_path) with urllib.request.urlopen(f"http://127.0.0.1:{port}{asset_path}", timeout=2) as response: assert response.status == 200, (binary_name, asset_path, response.status) print(f"{binary_name} smoke PASS") finally: if process.poll() is None: os.killpg(process.pid, signal.SIGTERM) try: process.wait(timeout=5) except subprocess.TimeoutExpired: os.killpg(process.pid, signal.SIGKILL) process.wait(timeout=5) print("Knowledge 0.0.2-beta.1.fix.1 HTTP smoke: clean") PYSMOKE python3 - <<'PYTEXT' from pathlib import Path roots = [Path('README.md'), Path('RULES.md'), Path('ROADMAP.md'), Path('CHANGELOG.md'), Path('docs'), Path('prompts'), Path('deltas'), Path('history')] paths = [] for root in roots: if root.is_file(): paths.append(root) elif root.is_dir(): paths.extend(path for path in root.rglob('*') if path.is_file()) for path in paths: data = path.read_bytes() assert b'\r' not in data, path assert data.endswith(b'\n'), path text = data.decode('utf-8') for number, line in enumerate(text.splitlines(), start=1): assert line == line.rstrip(' \t'), (path, number) print(f'Knowledge beta.1.fix.1 text hygiene: clean ({len(paths)} file(s))') PYTEXT test -f Cargo.lock git check-ignore -q Cargo.lock if git ls-files --error-unmatch Cargo.lock >/dev/null 2>&1; then echo 'Cargo.lock must remain untracked for Knowledge' >&2 exit 1 fi test -z "$(git diff --name-only -- crates Web)" test ! -e crates/knowledge-forge-gitea-lib test ! -e crates/knowledge-store-postgres-lib git add -A git diff --cached --check git status --short ``` ## Résultat attendu Le `PASS` doit notamment afficher : ```text Knowledge 0.0.2-beta.1.fix.1 stabilization audit: clean knowledge-pgr-server smoke PASS knowledge-howtos-server smoke PASS knowledge-snippets-server smoke PASS Knowledge 0.0.2-beta.1.fix.1 HTTP smoke: clean ``` Les warnings npm/Sass déjà observés restent non bloquants si les commandes terminent avec succès. ## Transition `PASS` autorise uniquement : ```text 0.0.2-rc.1 ``` Le history `beta.1.fix.1` est produit seulement après réception de la gate opérateur complète.