Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,11 @@ jobs:
- name: Run the full unit suite
if: env.HAS_SPLIT_TOKEN == 'true'
run: python -m pytest tests/unit -q
env:
# Components were just staged, so an incomplete set is a provisioning failure, not
# a reason to quietly cover less. Without this the suite ran with ZERO renderers
# loaded and still reported green (#22).
INTENTUMDIFF_REQUIRE_ALL_COMPONENTS: "1"

- name: Run the reduced suite (no token - components unavailable)
# Dependabot and fork PRs get no secrets, so parser components cannot be
Expand Down
61 changes: 61 additions & 0 deletions scripts/provision_build_inputs.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,67 @@ def get(url: str, accept: str = "application/vnd.github+json") -> bytes:
staged += 1
except urllib.error.HTTPError as exc:
missing.append((name, f"HTTP {exc.code} at {stage} ({exc.reason})"))
# ---- renderers -------------------------------------------------------------------
# Renderers are NOT parser repos - they are crates in intentumdiff-core, published as
# the `renderer-components` artifact since core#26. Nothing fetched them before, so the
# suite ran with ZERO renderers loaded and still reported green: every test touching
# --format html|patch|llm|terminal was skipping, hitting a Python fallback, or asserting
# on degraded output, with nothing distinguishing those from real coverage (#22).
stage = "renderers"
try:
runs = _json.loads(
get(f"{api}/repos/{org}/intentumdiff-core/actions/runs"
f"?status=success&per_page=20")
Comment on lines +233 to +235

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Pin renderer artifacts to the staged core revision

When the newest successful core run belongs to another branch or commit, this unfiltered query selects its renderer artifact even though stage_core() builds the native host from CORE_REF; the publish workflow also invokes this mode when constructing PyPI wheels. This can therefore package renderer components from an incompatible revision, and any unpinned renderers will not be rejected by the checksum check. Filter the workflow runs using the exact revision staged in CORE_DEST, as the parser artifact flow already does.

Useful? React with 👍 / 👎.

).get("workflow_runs", [])
art = None
for run in runs:
arts = _json.loads(get(run["artifacts_url"]))
art = next((a for a in arts.get("artifacts", [])
if a["name"] == "renderer-components" and not a.get("expired")), None)
if art:
break
if art is None:
missing.append(("intentumdiff-core", "no renderer-components artifact"))
else:
blob = get(art["archive_download_url"])
found = 0
with zipfile.ZipFile(io.BytesIO(blob)) as z:
for member in z.namelist():
if not member.endswith(".wasm"):
continue
out = Path(member).name
for _prefix in ("intentumdiff_", "intentdiff_"):
if out.startswith(_prefix):
out = out[len(_prefix):]
break
payload = z.read(member)
digest = _hashlib.sha256(payload).hexdigest()
pinned = pins.get(out)
# Renderers are not all pinned in the registry today. Where a pin EXISTS
# it is enforced exactly as for parsers; where it does not, the component
# is staged and the fact is printed rather than silently accepted, so an
# unpinned component is visible instead of indistinguishable from a
# verified one.
if pinned is not None and pinned != digest:
missing.append(("intentumdiff-core",
f"{out} CHECKSUM MISMATCH (registry pins "
f"{pinned[:16]}..., artifact is {digest[:16]}...)"))
continue
if pinned is None:
print(f" note: {out} is not pinned in the registry (staged unverified)")
(WASM_DEST / out).write_bytes(payload)
staged += 1
found += 1
# Fail closed on a partial set. A consumer that stages 3 of 4 renderers loses one
# silently and reports green - the exact failure this exists to end.
_EXPECTED_RENDERERS = 4
if found < _EXPECTED_RENDERERS:
missing.append(("intentumdiff-core",
f"only {found}/{_EXPECTED_RENDERERS} renderer components in "
f"the artifact"))
except urllib.error.HTTPError as exc:
missing.append(("intentumdiff-core", f"HTTP {exc.code} at {stage} ({exc.reason})"))

print(f"staged {staged} components into {WASM_DEST}")
if missing:
print(f"MISSING ({len(missing)}):")
Expand Down
20 changes: 19 additions & 1 deletion src/intentumdiff/plugins/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@

import importlib.metadata
import logging
import os
import re
import threading
import time
Expand All @@ -44,7 +45,7 @@
ParserAdapter,
RendererAdapter,
)
from intentumdiff.plugins.exceptions import PluginFuelExhausted, PluginNotFoundError
from intentumdiff.plugins.exceptions import PluginFuelExhausted, PluginNotFoundError, PluginLoadError
from intentumdiff.plugins.language_metadata import fallback_language_info
from intentumdiff.plugins.loader import LoadedPlugin, load_plugin

Expand Down Expand Up @@ -1046,6 +1047,7 @@ def _load_parsers(
def _load_renderers(fuel: int = 10_000_000) -> list[RendererAdapter]:
"""Discover and instantiate all registered renderer plugins."""
adapters: list[RendererAdapter] = []
failed: list[str] = []
for ep in importlib.metadata.entry_points(group=_RENDERER_GROUP):
try:
wasm_path = _wasm_path_from_ep(ep)
Expand All @@ -1056,6 +1058,22 @@ def _load_renderers(fuel: int = 10_000_000) -> list[RendererAdapter]:
logger.debug("Loaded renderer plugin: %s (%s)", ep.name, wasm_path)
except Exception as exc:
logger.warning("Failed to load renderer plugin %r: %s", ep.name, exc)
failed.append(ep.name)
# A renderer that fails to load used to be a warning and nothing else: the adapter was
# simply omitted and every caller carried on with a smaller set. CI ran the whole suite
# with ZERO renderers and reported green - each test touching --format html|patch|llm|
# terminal was skipping, hitting a Python fallback, or asserting on degraded output, and
# nothing distinguished those from real coverage (#22).
#
# Opt-in rather than always-on, because a user with a partial install should still get a
# working diff - degrading is right for THEM and wrong for a gate. CI sets this so an
# incomplete component set stops the run where the cause is legible.
if failed and os.environ.get("INTENTUMDIFF_REQUIRE_ALL_COMPONENTS") == "1":
raise PluginLoadError(
f"{len(failed)} renderer plugin(s) failed to load: {', '.join(sorted(failed))}. "
"INTENTUMDIFF_REQUIRE_ALL_COMPONENTS=1 is set, so an incomplete component set is "
"an error rather than a silent downgrade - stage the components, or unset it."
Comment on lines +1072 to +1075

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Pass both arguments required by PluginLoadError

Whenever a renderer fails to load while INTENTUMDIFF_REQUIRE_ALL_COMPONENTS=1 is set, this call raises TypeError instead of the intended PluginLoadError, because PluginLoadError.__init__ requires separate wasm_path and detail arguments. Thus the newly enabled CI failure path masks the aggregate renderer diagnostic and violates the exception contract expected by callers.

Useful? React with 👍 / 👎.

)
return adapters


Expand Down
Loading