search-with-proof: reproducible npm builds for old lockless projects (--before commit date)

A lockless npm project (e.g. glance, 2015) resolved an unpinned transitive range to the
latest major ([email protected] -> mime@4, which dropped .lookup()), so the built app crashed
serving files -- a build-reproducibility defect, not an app or agent fault. The executor
now, for the npm-install (no-lock) case only and only when .git is present, resolves
dependencies as of the checkout's HEAD commit date via `npm install --before <date>`,
giving era-appropriate, compatible versions. npm ci (lockfile present) is untouched, and
any git/date failure falls back to plain install. demo.py also recognizes
npm-shrinkwrap.json as a lock for the ci-vs-install choice.

Tests: executor + smoke 75 passed, demo 107 passed; ruff clean; mypy clean on executor.py.
Executor-side change -> image rebuild required.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
Marius Ciepluch
2026-10-07 13:37:23 +02:00
co-authored by Claude Opus 4.8
parent 8de0c2732b
commit c30dd9126a
3 changed files with 78 additions and 1 deletions
+62
View File
@@ -1,12 +1,74 @@
"""Offline executor boundary and mailbox protocol checks."""
import json
import subprocess
import pytest
from openultrasast.search.executor import MAX_RESULT_BYTES, Command, InProcessExecutor, ObjectStoreExecutor, clean_environment, serve
@pytest.mark.parametrize("lockfile", [None, "package-lock.json", "npm-shrinkwrap.json"])
@pytest.mark.parametrize("git_date", ["ok", "no_git", "missing", "failed", "empty", "timeout"])
def test_npm_preparation_commit_date(tmp_path, monkeypatch, lockfile, git_date):
from openultrasast.search import executor
# A nested project must still use the checkout's HEAD, with its own build cwd.
project = tmp_path / "app"
project.mkdir()
(project / "package.json").write_text("{}")
(project / "index.js").write_text('console.log("ready")')
if lockfile:
(project / lockfile).write_text("{}")
if git_date != "no_git":
(tmp_path / ".git").mkdir()
date = "2015-06-03T12:34:56+02:00"
git_calls, builds = [], []
def git(argv, **kwargs):
git_calls.append(argv)
assert argv == ["git", "-C", str(project), "show", "-s", "--format=%cI", "HEAD"]
assert kwargs["check"] and 0 < kwargs["timeout"] <= 5
assert kwargs["env"] == clean_environment()
if git_date == "missing":
raise FileNotFoundError("git")
if git_date == "failed":
raise subprocess.CalledProcessError(128, argv)
if git_date == "timeout":
raise subprocess.TimeoutExpired(argv, kwargs["timeout"])
return subprocess.CompletedProcess(argv, 0, " \n" if git_date == "empty" else date + "\n", "")
def build(argv, timeout, limits, *, cwd):
builds.append(argv)
assert cwd == project
return {"exit_code": 0, "timed_out": False, "stderr": ""}
app = InProcessExecutor(tmp_path, task_boundary=True)
app.prepared_put_url = "https://files.example/output"
monkeypatch.setattr(executor.subprocess, "run", git)
monkeypatch.setattr(app, "_task_run", build)
monkeypatch.setattr(executor.URLTransport, "put", lambda *args: None)
result = app.submit(
"prepare_verification",
{
"demo": {
"oracle": "path",
"build": {"recipe": "npm", "arguments": ["app/package.json"]},
"start": {"runtime": "node", "path": "app/index.js", "arguments": [], "mode": "cli"},
"steps": [{"type": "cli", "arguments": []}],
},
"family": "path",
"timeout_seconds": 2,
},
)
assert result["status"] == "ok", result
expected = ["npm", "ci" if lockfile else "install", "--ignore-scripts"]
if not lockfile and git_date == "ok":
expected += ["--before", date]
assert builds == [expected]
assert len(git_calls) == int(not lockfile and git_date != "no_git")
def test_order_retry_stop(tmp_path):
(tmp_path / "input").write_text("hello")
executor = InProcessExecutor(tmp_path)