feat(devkit): add resumable workspace automation and UI review tooling
Verified with the coordinated workspace changes by devkit full run 2026-09-08T225814-186389-0000-3e3ed7cd (all seven phases passed). This shared UI pass does not mark the individual module reviews complete.
This commit is contained in:
Executable
+438
@@ -0,0 +1,438 @@
|
||||
"""Audit the selected fixture workspace, never a fuller neighboring checkout."""
|
||||
|
||||
from argparse import Namespace
|
||||
import importlib.util
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
from types import ModuleType, SimpleNamespace
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
META_ROOT = Path(__file__).resolve().parents[1]
|
||||
SCRIPT = META_ROOT / "tools/inventory/platform-interface-inventory.py"
|
||||
WEBUI_SCRIPT = META_ROOT / "tools/inventory/extract-webui-structure.mjs"
|
||||
SPEC = importlib.util.spec_from_file_location("audit_scope_inventory", SCRIPT)
|
||||
assert SPEC is not None and SPEC.loader is not None
|
||||
inventory = importlib.util.module_from_spec(SPEC)
|
||||
SPEC.loader.exec_module(inventory)
|
||||
|
||||
sys.path.insert(0, str(META_ROOT / "tools/devkit"))
|
||||
from govoplan_devkit import docs, issues, runner # noqa: E402
|
||||
from govoplan_devkit.workspace import Project, Repository # noqa: E402
|
||||
|
||||
|
||||
def write(path, content):
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_text(content, encoding="utf-8")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def workspaces(tmp_path):
|
||||
selected = tmp_path / "selected"
|
||||
default = tmp_path / "fuller-default"
|
||||
meta = tmp_path / "legacy-siblings/govoplan"
|
||||
selected.mkdir()
|
||||
meta.mkdir(parents=True)
|
||||
catalog = {
|
||||
"default_parent": str(default),
|
||||
"repositories": [
|
||||
{"name": "govoplan-core", "path": "govoplan-core"},
|
||||
{"name": "govoplan-example", "path": "nested/example"},
|
||||
{"name": "govoplan-optional", "path": "govoplan-optional"},
|
||||
],
|
||||
}
|
||||
write(meta / "repositories.json", json.dumps(catalog))
|
||||
write(
|
||||
selected / "nested/example/webui/src/Page.tsx",
|
||||
"export const page = <h1>SELECTED_ONLY</h1>;\n",
|
||||
)
|
||||
write(
|
||||
default / "nested/example/webui/src/Page.tsx",
|
||||
"export const page = <h1>DEFAULT_ONLY</h1>;\n",
|
||||
)
|
||||
write(
|
||||
default / "govoplan-optional/webui/src/Page.tsx",
|
||||
"export const page = <h1>DEFAULT_OPTIONAL</h1>;\n",
|
||||
)
|
||||
for item in catalog["repositories"]:
|
||||
(default / item["path"] / "src").mkdir(parents=True)
|
||||
return selected, default, meta, catalog
|
||||
|
||||
|
||||
def test_explicit_partial_root_beats_fuller_legacy_discovery(workspaces, monkeypatch):
|
||||
selected, default, meta, catalog = workspaces
|
||||
monkeypatch.setattr(inventory, "META_ROOT", meta)
|
||||
assert inventory._resolve_workspace_root(catalog) == default
|
||||
assert inventory._resolve_workspace_root(catalog, selected) == selected
|
||||
with pytest.raises(ValueError, match="existing directory"):
|
||||
inventory._resolve_workspace_root(catalog, selected / "missing")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("relative", ["../fuller-default", "/absolute/path"])
|
||||
def test_repository_paths_cannot_escape_explicit_root(workspaces, relative):
|
||||
selected, _, _, _ = workspaces
|
||||
with pytest.raises(ValueError, match="inside the selected workspace"):
|
||||
inventory._validate_repository_roots(
|
||||
{"repositories": [{"path": relative}]}, selected
|
||||
)
|
||||
|
||||
|
||||
def test_linked_repository_cannot_borrow_default_sources(workspaces):
|
||||
selected, default, _, catalog = workspaces
|
||||
(selected / "govoplan-core").symlink_to(
|
||||
default / "govoplan-core", target_is_directory=True
|
||||
)
|
||||
with pytest.raises(ValueError, match="escapes the selected workspace"):
|
||||
inventory._validate_repository_roots(catalog, selected)
|
||||
|
||||
|
||||
def test_python_forwards_selected_root_and_configured_node(workspaces, monkeypatch):
|
||||
selected, _, meta, _ = workspaces
|
||||
monkeypatch.setattr(inventory, "META_ROOT", meta)
|
||||
monkeypatch.setenv("NODE", "/selected/toolchain/node")
|
||||
calls = []
|
||||
|
||||
def run(argv, **kwargs):
|
||||
calls.append((argv, kwargs))
|
||||
return SimpleNamespace(stdout=json.dumps({"workspaceRoot": str(selected)}))
|
||||
|
||||
monkeypatch.setattr(inventory.subprocess, "run", run)
|
||||
assert inventory._extract_webui(selected)["workspaceRoot"] == str(selected)
|
||||
argv, options = calls[0]
|
||||
assert argv == [
|
||||
"/selected/toolchain/node",
|
||||
str(meta / "tools/inventory/extract-webui-structure.mjs"),
|
||||
str(meta),
|
||||
"--workspace-root",
|
||||
str(selected),
|
||||
]
|
||||
assert options == {"check": True, "capture_output": True, "text": True}
|
||||
|
||||
|
||||
def test_python_rejects_webui_result_from_another_root(workspaces, monkeypatch):
|
||||
selected, default, _, _ = workspaces
|
||||
monkeypatch.setattr(
|
||||
inventory.subprocess,
|
||||
"run",
|
||||
lambda *a, **k: SimpleNamespace(
|
||||
stdout=json.dumps({"workspaceRoot": str(default)})
|
||||
),
|
||||
)
|
||||
with pytest.raises(ValueError, match="did not confirm"):
|
||||
inventory._extract_webui(selected)
|
||||
|
||||
|
||||
def test_main_forwards_same_root_to_every_collector(workspaces, monkeypatch):
|
||||
selected, _, meta, catalog = workspaces
|
||||
monkeypatch.setattr(inventory, "META_ROOT", meta)
|
||||
roots = []
|
||||
monkeypatch.setattr(
|
||||
inventory, "_extract_webui", lambda root: roots.append(root) or {}
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
inventory,
|
||||
"_extract_backend_endpoints",
|
||||
lambda data, root: roots.append(root) or [],
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
inventory, "_extract_manifests", lambda data, root: roots.append(root) or []
|
||||
)
|
||||
monkeypatch.setattr(inventory, "_load_endpoint_declarations", lambda _: {})
|
||||
monkeypatch.setattr(inventory, "_load_high_risk_help_baseline", lambda _: {})
|
||||
monkeypatch.setattr(inventory, "_assemble_inventory", lambda **kwargs: {})
|
||||
monkeypatch.setattr(inventory, "_render_markdown", lambda data: "fixture\n")
|
||||
output = selected / "output"
|
||||
monkeypatch.setattr(
|
||||
sys,
|
||||
"argv",
|
||||
[str(SCRIPT), "--workspace-root", str(selected), "--output-dir", str(output)],
|
||||
)
|
||||
assert inventory.main() == 0
|
||||
assert roots == [selected, selected, selected]
|
||||
report = json.loads((output / "platform-interface-inventory.json").read_text())
|
||||
assert report["workspace_root"] == str(selected)
|
||||
assert report["workspace_selection"] == "explicit"
|
||||
|
||||
|
||||
def install_fixture_compiler(root):
|
||||
compiler = META_ROOT.parent / "govoplan-core/webui/node_modules/typescript"
|
||||
if not compiler.is_dir() or not shutil.which("node"):
|
||||
pytest.skip("Node and the installed Core TypeScript parser are required")
|
||||
target = root / "govoplan-core/webui/node_modules/typescript"
|
||||
target.parent.mkdir(parents=True, exist_ok=True)
|
||||
# Parser dependencies may be shared; audited source checkouts may not.
|
||||
target.symlink_to(compiler, target_is_directory=True)
|
||||
|
||||
|
||||
def collect_webui(meta, selected=None):
|
||||
argv = [shutil.which("node") or "node", str(WEBUI_SCRIPT), str(meta)]
|
||||
if selected is not None:
|
||||
argv += ["--workspace-root", str(selected)]
|
||||
return subprocess.run(argv, capture_output=True, text=True, timeout=30)
|
||||
|
||||
|
||||
def test_javascript_explicit_root_is_authoritative_and_legacy_cli_still_works(
|
||||
workspaces,
|
||||
):
|
||||
selected, default, meta, _ = workspaces
|
||||
install_fixture_compiler(selected)
|
||||
install_fixture_compiler(default)
|
||||
explicit = collect_webui(meta, selected)
|
||||
assert explicit.returncode == 0, explicit.stderr
|
||||
report = json.loads(explicit.stdout)
|
||||
assert report["workspaceRoot"] == str(selected)
|
||||
assert [item["value"] for item in report["visibleText"]] == ["SELECTED_ONLY"]
|
||||
assert report["visibleText"][0]["file"] == "webui/src/Page.tsx"
|
||||
legacy = collect_webui(meta)
|
||||
assert legacy.returncode == 0, legacy.stderr
|
||||
assert json.loads(legacy.stdout)["workspaceRoot"] == str(default)
|
||||
assert {item["value"] for item in json.loads(legacy.stdout)["visibleText"]} == {
|
||||
"DEFAULT_ONLY",
|
||||
"DEFAULT_OPTIONAL",
|
||||
}
|
||||
|
||||
|
||||
def test_javascript_does_not_borrow_missing_core_dependencies(workspaces):
|
||||
selected, default, meta, _ = workspaces
|
||||
install_fixture_compiler(default)
|
||||
result = collect_webui(meta, selected)
|
||||
assert result.returncode != 0
|
||||
assert (
|
||||
str(selected / "govoplan-core/webui/node_modules/typescript") in result.stderr
|
||||
)
|
||||
assert not result.stdout
|
||||
|
||||
|
||||
def test_javascript_rejects_source_root_linked_outside_workspace(workspaces):
|
||||
selected, default, meta, _ = workspaces
|
||||
install_fixture_compiler(selected)
|
||||
linked = selected / "govoplan-optional/webui/src"
|
||||
linked.parent.mkdir(parents=True)
|
||||
linked.symlink_to(default / "govoplan-optional/webui/src", target_is_directory=True)
|
||||
result = collect_webui(meta, selected)
|
||||
assert result.returncode != 0
|
||||
assert "source root escapes the selected workspace" in result.stderr
|
||||
|
||||
|
||||
def test_backend_does_not_borrow_optional_endpoints(workspaces):
|
||||
selected, default, _, catalog = workspaces
|
||||
source = "from fastapi import APIRouter\nrouter = APIRouter()\n@router.get('/selected')\ndef endpoint(): pass\n"
|
||||
write(selected / "nested/example/src/routes.py", source)
|
||||
write(
|
||||
default / "govoplan-optional/src/routes.py", source.replace("selected", "other")
|
||||
)
|
||||
endpoints = inventory._extract_backend_endpoints(catalog, selected)
|
||||
assert [item["path"] for item in endpoints] == ["/selected"]
|
||||
(selected / "nested/example/src/foreign.py").symlink_to(
|
||||
default / "govoplan-optional/src/routes.py"
|
||||
)
|
||||
with pytest.raises(ValueError, match="Backend source path escapes"):
|
||||
inventory._extract_backend_endpoints(catalog, selected)
|
||||
|
||||
|
||||
def test_manifests_require_selected_core_sources(workspaces, monkeypatch):
|
||||
selected, default, _, catalog = workspaces
|
||||
write(
|
||||
default / "govoplan-core/src/govoplan_core/core/platform_interfaces.py",
|
||||
"raise AssertionError('foreign source imported')",
|
||||
)
|
||||
monkeypatch.syspath_prepend(str(default / "govoplan-core/src"))
|
||||
with pytest.raises(ValueError, match="requires Core interface sources"):
|
||||
inventory._extract_manifests(catalog, selected)
|
||||
|
||||
|
||||
def test_cached_application_import_cannot_replace_missing_checkout(
|
||||
workspaces, monkeypatch
|
||||
):
|
||||
selected, default, _, _ = workspaces
|
||||
# Meta's own devkit/release packages may audit another workspace.
|
||||
tooling = ModuleType("govoplan_release")
|
||||
tooling.__file__ = str(default / "tools/release/govoplan_release/__init__.py")
|
||||
modules = {"govoplan_release": tooling, "govoplan_devkit.docs": docs}
|
||||
# Isolate the fixture from application packages collected by unrelated
|
||||
# suites, without removing or replacing those real cached imports.
|
||||
monkeypatch.setattr(inventory, "sys", SimpleNamespace(modules=modules))
|
||||
inventory._assert_workspace_imports(selected)
|
||||
application = ModuleType("govoplan_audit_fixture")
|
||||
application.__file__ = str(
|
||||
default / "govoplan-optional/src/govoplan_audit_fixture/__init__.py"
|
||||
)
|
||||
modules["govoplan_audit_fixture"] = application
|
||||
with pytest.raises(ValueError, match="outside the selected inventory workspace"):
|
||||
inventory._assert_workspace_imports(selected)
|
||||
|
||||
|
||||
def test_partial_manifest_collection_uses_selected_sources_in_fresh_process(workspaces):
|
||||
selected, default, _, catalog = workspaces
|
||||
core = selected / "govoplan-core/src/govoplan_core"
|
||||
write(core / "__init__.py", "")
|
||||
write(core / "core/__init__.py", "")
|
||||
write(
|
||||
core / "core/platform_interfaces.py",
|
||||
"def manifest_interface_catalog(manifest): return {'selected': True}\n",
|
||||
)
|
||||
write(
|
||||
default / "govoplan-core/src/govoplan_core/__init__.py",
|
||||
"raise AssertionError('foreign Core loaded')\n",
|
||||
)
|
||||
module = selected / "nested/example/src/govoplan_example"
|
||||
write(module / "__init__.py", "")
|
||||
write(module / "backend/__init__.py", "")
|
||||
manifest = (
|
||||
"from types import SimpleNamespace as S\n"
|
||||
"def get_manifest():\n"
|
||||
" return S(id='selected', name='Selected', version='1', dependencies=(), "
|
||||
"optional_dependencies=(), required_capabilities=(), provides_interfaces=(), "
|
||||
"capability_factories={}, permissions=(), documentation=(), architecture=None, "
|
||||
"information_governance=S(to_dict=lambda: {}), frontend=None)\n"
|
||||
)
|
||||
write(module / "backend/manifest.py", manifest)
|
||||
write(
|
||||
default / "govoplan-optional/src/govoplan_optional/backend/manifest.py",
|
||||
manifest.replace("selected", "foreign"),
|
||||
)
|
||||
code = (
|
||||
"import importlib.util, json; from pathlib import Path; "
|
||||
f"spec=importlib.util.spec_from_file_location('fixture', {str(SCRIPT)!r}); "
|
||||
"module=importlib.util.module_from_spec(spec); spec.loader.exec_module(module); "
|
||||
f"print(json.dumps(module._extract_manifests({catalog!r}, Path({str(selected)!r}))))"
|
||||
)
|
||||
env = {
|
||||
**os.environ,
|
||||
"PYTHONPATH": os.pathsep.join(
|
||||
str(default / item["path"] / "src") for item in catalog["repositories"]
|
||||
),
|
||||
}
|
||||
result = subprocess.run(
|
||||
[sys.executable, "-c", code],
|
||||
env=env,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=30,
|
||||
)
|
||||
assert result.returncode == 0, result.stderr
|
||||
manifests = json.loads(result.stdout)
|
||||
assert [manifest["id"] for manifest in manifests] == ["selected"]
|
||||
assert manifests[0]["interface_catalog"] == {"selected": True}
|
||||
|
||||
|
||||
def doc_plan(workspace, monkeypatch):
|
||||
repos = tuple(
|
||||
Repository(name, workspace / name)
|
||||
for name in ("govoplan", "govoplan-core", "govoplan-example")
|
||||
)
|
||||
project = Project("fixture", repos, {})
|
||||
monkeypatch.setattr(docs, "load_project", lambda *a: project)
|
||||
args = Namespace(
|
||||
workspace_root=workspace,
|
||||
project=None,
|
||||
state_dir=workspace.parent / "state",
|
||||
repo=["govoplan-example"],
|
||||
changed=False,
|
||||
)
|
||||
return docs.build_doc_stages(args)
|
||||
|
||||
|
||||
def test_docs_plan_forwards_root_and_persists_all_limitations(workspaces, monkeypatch):
|
||||
selected, _, _, _ = workspaces
|
||||
stages = doc_plan(selected, monkeypatch)
|
||||
by_id = {stage["id"]: stage for stage in stages}
|
||||
for stage_id in (
|
||||
"docs.manifests",
|
||||
"docs.interface-inventory",
|
||||
"docs.plain-display-labels",
|
||||
):
|
||||
argv = by_id[stage_id]["argv"]
|
||||
assert argv[argv.index("--workspace-root") + 1] == str(selected)
|
||||
assert by_id["docs.translation-structure"]["argv"][1] == str(
|
||||
selected / "govoplan-core/webui/scripts/audit-i18n-structural.mjs"
|
||||
)
|
||||
assert issues.coverage_notes(stages) == docs.LIMITATIONS
|
||||
assert by_id["docs.plain-display-labels"]["argv"][-2:] == [
|
||||
"--repo",
|
||||
"govoplan-example",
|
||||
]
|
||||
|
||||
|
||||
def test_docs_limitations_survive_real_receipt_and_issue_evidence(
|
||||
workspaces, monkeypatch, tmp_path
|
||||
):
|
||||
selected, _, _, _ = workspaces
|
||||
stages = doc_plan(selected, monkeypatch)
|
||||
repo = selected / "example"
|
||||
repo.mkdir()
|
||||
subprocess.run(["git", "init", "-q", str(repo)], check=True, timeout=10)
|
||||
write(repo / "source.txt", "fixture\n")
|
||||
subprocess.run(
|
||||
["git", "-C", str(repo), "add", "source.txt"], check=True, timeout=10
|
||||
)
|
||||
subprocess.run(
|
||||
[
|
||||
"git",
|
||||
"-C",
|
||||
str(repo),
|
||||
"-c",
|
||||
"user.name=Fixture",
|
||||
"-c",
|
||||
"user.email=fixture@example.invalid",
|
||||
"commit",
|
||||
"-qm",
|
||||
"fixture",
|
||||
],
|
||||
check=True,
|
||||
timeout=10,
|
||||
)
|
||||
project = tmp_path / "project.json"
|
||||
write(
|
||||
project,
|
||||
json.dumps(
|
||||
{
|
||||
"schema_version": 1,
|
||||
"name": "fixture",
|
||||
"repositories": [{"name": "example", "path": "example"}],
|
||||
"checks": [],
|
||||
"profiles": {},
|
||||
}
|
||||
),
|
||||
)
|
||||
args = Namespace(
|
||||
workspace_root=selected,
|
||||
project=project,
|
||||
state_dir=tmp_path / "state",
|
||||
dry_run=False,
|
||||
jobs=2,
|
||||
profile="docs",
|
||||
resume=None,
|
||||
)
|
||||
for stage in stages:
|
||||
stage.update(
|
||||
argv=[sys.executable, "-c", "print('fixture audit')"],
|
||||
cwd=str(repo),
|
||||
deps=[],
|
||||
resources=[],
|
||||
timeout_seconds=10,
|
||||
)
|
||||
monkeypatch.setattr(docs, "build_doc_stages", lambda _: stages)
|
||||
monkeypatch.setattr(
|
||||
runner, "environment_fingerprint", lambda *a, **k: "fixture-env"
|
||||
)
|
||||
result = docs.audit(args)
|
||||
assert result["status"] == "passed", result
|
||||
assert all(result["summary"].count(note) == 1 for note in docs.LIMITATIONS)
|
||||
receipt = runner.read_receipt(selected, args.state_dir, result["run_id"])
|
||||
assert issues.coverage_notes(receipt["stages"]) == docs.LIMITATIONS
|
||||
evidence = issues.evidence_record(result["run_id"], args)
|
||||
assert evidence["coverage_notes"] == docs.LIMITATIONS
|
||||
assert evidence["source_state"] == "matches-current"
|
||||
target = issues.NoteTarget(
|
||||
repo, "https://gitea.example.invalid", "fixture", "example", 1
|
||||
)
|
||||
_, body = issues.render_note(
|
||||
{"summary": [], "next": [], "body": ""}, evidence, target, "fixture"
|
||||
)
|
||||
assert all(note in body for note in docs.LIMITATIONS)
|
||||
Reference in New Issue
Block a user