10 KiB
Delta 0.0.2-rc.1 — release candidate
Base requise
État 0.0.2-beta.1.fix.2 committé et poussé après application du history :
history/0.0.2/beta.1.fix.2.md = PASS
La version devient :
0.0.2-rc.1
Objet
Geler le scope de 0.0.2 et exécuter sa gate complète de release candidate.
Aucune nouvelle fonctionnalité, dépendance, crate, route, comportement Git/sync ou décision d'horizon n'est introduite.
Scope
La RC remplace uniquement :
VERSION
Cargo.toml
README.md
docs/plans/002-V0_0_2_GIT_SYNC_PLAN.md
docs/validation/001-VALIDATION_MATRIX.md
et ajoute :
deltas/0.0.2/rc.1.md
Aucun fichier n'est supprimé. crates/, Web/, CHANGELOG.md, ROADMAP.md, les études, l'architecture et les owners locaux sont gelés.
Histories attendus avant RC
La gate doit retrouver exactement les treize histories antérieurs suivants :
alpha.1 FAIL
alpha.1.fix.1 PASS
alpha.2 FAIL
alpha.2.fix.1 FAIL
alpha.2.fix.2 PASS
alpha.3 FAIL
alpha.3.fix.1 PASS
alpha.4 FAIL
alpha.4.fix.1 PASS
alpha.5 PASS
beta.1 PARTIAL
beta.1.fix.1 FAIL
beta.1.fix.2 PASS
Pendant la gate, rc.1.md doit être le seul delta 0.0.2 sans history correspondant.
Présentation candidate
La RC conserve :
CHANGELOG.md -> ## 0.0.2 (candidate)
ROADMAP.md -> 0.0.2 encore engagé, aucune release suivante numérotée
aucun prompt -> 0.0.3
La présentation stable appartient à rel.1, pas à la RC.
Gate opérateur
Exécuter directement dans le shell interactif. Ne pas activer set -e, set -u ou set -o pipefail globalement.
test "$(cat VERSION)" = '0.0.2-rc.1'
grep -F 'version = "0.0.2-rc.1"' Cargo.toml
grep -A2 '^## Statut$' history/0.0.2/beta.1.fix.2.md | grep -Fx 'PASS'
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-rc.1-metadata.json
cargo check --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test -p knowledge-git-lib --test git_read_path
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-rc.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
statuses = {
'alpha.1': 'FAIL',
'alpha.1.fix.1': 'PASS',
'alpha.2': 'FAIL',
'alpha.2.fix.1': 'FAIL',
'alpha.2.fix.2': 'PASS',
'alpha.3': 'FAIL',
'alpha.3.fix.1': 'PASS',
'alpha.4': 'FAIL',
'alpha.4.fix.1': 'PASS',
'alpha.5': 'PASS',
'beta.1': 'PARTIAL',
'beta.1.fix.1': 'FAIL',
'beta.1.fix.2': 'PASS',
}
for tranche, status in statuses.items():
path = Path(f'history/0.0.2/{tranche}.md')
assert path.is_file(), path
text = path.read_text(encoding='utf-8')
assert f'## Statut\n\n{status}' in text, (tranche, status)
history_names = {path.stem for path in Path('history/0.0.2').glob('*.md')}
assert history_names == set(statuses), history_names
delta_names = {path.stem for path in Path('deltas/0.0.2').glob('*.md')}
assert delta_names == set(statuses) | {'rc.1'}, delta_names
assert not Path('history/0.0.2/rc.1.md').exists()
assert '## 0.0.2 (candidate)' in Path('CHANGELOG.md').read_text(encoding='utf-8')
roadmap = Path('ROADMAP.md').read_text(encoding='utf-8')
assert '( ) 0.0.2' in roadmap
assert '0.0.3' not in roadmap
assert not Path('prompts/003-V0_0_3_START_PROMPT.md').exists()
for forbidden in (
Path('crates/knowledge-forge-gitea-lib'),
Path('crates/knowledge-store-postgres-lib'),
):
assert not forbidden.exists(), forbidden
print('Knowledge 0.0.2-rc.1 release-candidate audit: clean')
PYGATE
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
assert not data.endswith(b'\n\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 0.0.2-rc.1 text hygiene: clean ({len(paths)} file(s))')
PYTEXT
python3 - <<'PYMANIFEST'
import json
from pathlib import Path
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()
entry = manifest['src/main.ts']
assert entry.get('file'), entry
assert entry.get('css'), entry
print('Knowledge 0.0.2-rc.1 Vite manifest audit: clean')
PYMANIFEST
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-rc.1 HTTP smoke: clean')
PYSMOKE
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 CHANGELOG.md ROADMAP.md docs/architecture docs/studies docs/rules prompts)"
test ! -e crates/knowledge-forge-gitea-lib
test ! -e crates/knowledge-store-postgres-lib
git add -A
git diff --cached --check
git status --short
Les warnings de dépendances frontend déjà connus ne constituent pas à eux seuls un FAIL si npm/Vite terminent avec succès.
Transition
Un PASS de rc.1 autorise uniquement rel.1.
Un défaut fonctionnel important découvert en RC impose un retour de phase ou un REPLAN. Un défaut limité à la gate, au packaging ou aux métadonnées de candidate peut produire rc.1.fix.1 selon le diagnostic réel.
Ne pas committer rc.1 avant production de son history à partir des résultats opérateur.