Three workstreams, full suite 2482 passed, experimental paths default-OFF. #000055 — Windows quickstart without make tasks.py (pure-stdlib runner) + make.bat shim + .gitattributes; README Windows section rewritten. Quickstart needs only Python 3.10+ (no make/bzip2/curl/bash). Mirrors the Makefile quickstart subset; drift-pinned by tests/test_tasks_runner.py. #000001 §7 Phase 0 — deterministic cross-language guard arborist/qa/crosslang.py: non-English signal (¿/¡/non-ASCII) + an es function-word stoppack. Fail-closed to UNGROUNDED before retrieval/LLM (mirrors the quantifier reject-DAG) when no content token survives, else strips es stopwords from the retrieval query only. English path byte-identical by construction. Default OFF (crosslang_guard_enabled). Measured: the anarcocapitalismo field case 10.4s -> 1.6s. #000056 — Operation Sandwich (cross-language grounding) arborist/qa/mt/: opus-mt es/fr/ru<->en, lazy per-pair memoised singleton (fixes the 88%-engine-error concurrency defect), manifest-pinned, [mt] extra; entity_mask wrapper. Sandwich = translate query in (retrieval + LLM prompt) -> English answer -> UNTOUCHED verifier grounds English-vs-English -> translate the verified answer out as display-only (banner-labelled, zero grounding). question_hash + verifier_policy_hash invariant; MT engine identity binds into RetrievalPlan, not governance. CLI --crosslang-translate / make XLANG_MT=1. Default OFF; entity_mask default OFF (measured net-negative at bench scale). Fan-out bench (bench/*.py): Spanish ~0% -> 71% grounded vs the real no-support baseline; the round-trip predictor was tried and refuted; the entity-mask lever failed at scale (corpus-title anchoring untried). CLAUDE.md: cross-language bright-line convention + module map. Pre-existing modified diagram files are intentionally excluded.
172 lines
5.6 KiB
Python
172 lines
5.6 KiB
Python
"""Drift + behaviour guard for the Windows/no-make quickstart runner.
|
|
|
|
``tasks.py`` + ``make.bat`` exist so the README quickstart runs on
|
|
native Windows without GNU make (ticket #000055). Two thin entry
|
|
points are a known DRY cost; this test pins them:
|
|
|
|
- the supported-target set is frozen here, so adding/removing a
|
|
quickstart target without updating this contract fails loudly;
|
|
- venv-layout + Python-launcher detection is verified for *both*
|
|
``os.name`` values (the host only exercises one at runtime);
|
|
- the make-style ``KEY=VALUE`` parser is verified for every shell
|
|
quoting form (POSIX strips quotes; cmd/PowerShell do not).
|
|
|
|
No network, no subprocess, no venv — pure import + function checks.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import importlib.util
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
REPO = Path(__file__).resolve().parents[1]
|
|
|
|
|
|
def _load():
|
|
"""Fresh import of tasks.py with cleared module-level CLI state."""
|
|
spec = importlib.util.spec_from_file_location("arborist_tasks", REPO / "tasks.py")
|
|
mod = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(mod)
|
|
mod._CLI_VARS.clear()
|
|
mod._POSITIONALS.clear()
|
|
return mod
|
|
|
|
|
|
# The frozen contract. Mirrors the README Quickstart + "After the answer"
|
|
# + Setup blocks and the Makefile quickstart subset. Changing the runner's
|
|
# surface MUST change this set in the same commit.
|
|
EXPECTED_TARGETS = {
|
|
"bootstrap",
|
|
"fetch-cur",
|
|
"ingest-cur-attached",
|
|
"distill-shards-parallel",
|
|
"distill-shards-tfidf-parallel",
|
|
"query",
|
|
"query-dry",
|
|
"inspect",
|
|
"falsify",
|
|
"burn",
|
|
"burn-kindergarten",
|
|
"bootstrap-crawler",
|
|
"crawl-ingest",
|
|
"recrawl-check",
|
|
"stats",
|
|
"stats-shards",
|
|
"verify",
|
|
"search",
|
|
"test",
|
|
"clean",
|
|
"clean-db",
|
|
"clean-data",
|
|
}
|
|
|
|
|
|
def test_supported_target_set_is_frozen():
|
|
mod = _load()
|
|
assert set(mod.TARGETS) == EXPECTED_TARGETS
|
|
# Every target is callable and self-documents (help relies on __doc__).
|
|
for name, fn in mod.TARGETS.items():
|
|
assert callable(fn), name
|
|
assert (fn.__doc__ or "").strip(), f"{name} has no help docstring"
|
|
|
|
|
|
def test_help_lists_every_target(capsys):
|
|
mod = _load()
|
|
mod.main(["tasks.py", "help"])
|
|
out = capsys.readouterr().out
|
|
for name in EXPECTED_TARGETS | {"help"}:
|
|
assert f" {name}" in out, name
|
|
|
|
|
|
def test_unknown_target_exits_2(capsys):
|
|
mod = _load()
|
|
with pytest.raises(SystemExit) as exc:
|
|
mod.main(["tasks.py", "definitely-not-a-target"])
|
|
assert exc.value.code == 2
|
|
captured = capsys.readouterr()
|
|
assert "unknown target" in captured.err # diagnostics on stderr
|
|
assert "show this help" in captured.out # help still printed
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"is_windows,bin_name,arborist_name,py_name",
|
|
[
|
|
(False, "bin", "arborist", "python"),
|
|
(True, "Scripts", "arborist.exe", "python.exe"),
|
|
],
|
|
)
|
|
def test_venv_layout_per_platform(monkeypatch, is_windows, bin_name, arborist_name, py_name):
|
|
mod = _load()
|
|
monkeypatch.setattr(mod, "IS_WINDOWS", is_windows)
|
|
assert mod.venv_bin_dir().name == bin_name
|
|
assert mod.arborist_exe().name == arborist_name
|
|
assert mod.venv_python().name == py_name
|
|
assert mod.venv_exe("pytest").name == ("pytest.exe" if is_windows else "pytest")
|
|
|
|
|
|
def test_windows_prefers_py_launcher(monkeypatch):
|
|
mod = _load()
|
|
monkeypatch.setattr(mod, "IS_WINDOWS", True)
|
|
monkeypatch.delenv("ARBORIST_PYTHON", raising=False)
|
|
monkeypatch.setattr(mod.shutil, "which", lambda name: f"/x/{name}" if name == "py" else None)
|
|
assert mod.base_python_cmd() == ["py", "-3"]
|
|
|
|
|
|
def test_posix_uses_python3(monkeypatch):
|
|
mod = _load()
|
|
monkeypatch.setattr(mod, "IS_WINDOWS", False)
|
|
monkeypatch.delenv("ARBORIST_PYTHON", raising=False)
|
|
monkeypatch.setattr(mod.shutil, "which", lambda name: f"/usr/bin/{name}")
|
|
assert mod.base_python_cmd() == ["python3"]
|
|
|
|
|
|
def test_arborist_python_override(monkeypatch):
|
|
mod = _load()
|
|
monkeypatch.setenv("ARBORIST_PYTHON", "py -3.12")
|
|
assert mod.base_python_cmd() == ["py", "-3.12"]
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"argv,expect_q",
|
|
[
|
|
# POSIX: the shell already stripped the quotes.
|
|
(["query", "Q=What is X?"], "What is X?"),
|
|
# cmd: KEY="value with spaces" — quotes forwarded literally.
|
|
(["query", 'Q="What is X?"'], "What is X?"),
|
|
# cmd-friendly whole-token form.
|
|
(["query", '"Q=What is X?"'], "What is X?"),
|
|
],
|
|
)
|
|
def test_make_style_value_parsing(argv, expect_q):
|
|
mod = _load()
|
|
# Stop before executing the target — just parse args.
|
|
mod.TARGETS["query"] = lambda: None
|
|
mod.main(["tasks.py", *argv])
|
|
assert mod._CLI_VARS["Q"] == expect_q
|
|
|
|
|
|
def test_positional_question_and_flags():
|
|
mod = _load()
|
|
mod.TARGETS["query"] = lambda: None
|
|
mod.main(["tasks.py", "query", "What is X?", "JSON=1", "ANSWER_MODE=quote"])
|
|
assert mod._POSITIONALS == ["What is X?"]
|
|
assert mod._CLI_VARS["JSON"] == "1" and mod.truthy("JSON")
|
|
assert mod._CLI_VARS["ANSWER_MODE"] == "quote"
|
|
assert not mod.truthy("BURN") # unset → falsey, like make
|
|
|
|
|
|
def test_crawl_shard_name_matches_makefile_sed():
|
|
mod = _load()
|
|
mod._CLI_VARS["URL"] = "https://russell.ballestrini.net/blog/x?y=1"
|
|
# Makefile: sed 's,^https?://([^/]+).*,\1,' | tr '.' '_'
|
|
assert mod._crawl_shard_path().name == "crawl_russell_ballestrini_net.db"
|
|
|
|
|
|
def test_shim_and_attributes_present():
|
|
assert (REPO / "make.bat").is_file()
|
|
text = (REPO / "make.bat").read_text()
|
|
assert "tasks.py" in text and "py -3" in text
|
|
ga = (REPO / ".gitattributes").read_text()
|
|
assert "*.bat text eol=crlf" in ga
|