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
20 changes: 19 additions & 1 deletion .github/scripts/test_ethos_full_candidate.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def setUp(self) -> None:
self.temp = tempfile.TemporaryDirectory()
self.work = Path(self.temp.name)
self.binary = self.work / "ethos-bin"
self.binary.write_bytes(b"fixture ethos binary\n")
self.binary.write_bytes(b"#!/bin/sh\nprintf '%s\\n' \"$ETHOS_PDFIUM_LIBRARY_PATH\"\n")
self.binary.chmod(0o755)
self.runtime = b"fixture pdfium runtime\n"
self.pdfium_archive = self.work / "pdfium.tgz"
Expand Down Expand Up @@ -155,6 +155,24 @@ def test_double_run_is_byte_identical_and_complete(self) -> None:
self.assertEqual(sha256(self.binary.read_bytes()), manifest["input_sha256"]["ethos_binary"])
self.assertEqual(sha256(self.pdfium_archive.read_bytes()), manifest["input_sha256"]["pdfium_archive"])
self.assertIn('ETHOS_PDFIUM_LIBRARY_PATH="$root/lib/libpdfium.dylib"', launcher)
self.assertIn('while [ -L "$source" ]; do', launcher)
self.assertIn('source=$(readlink "$source")', launcher)
self.assertIn('hops=$((hops + 1))', launcher)

def test_launcher_resolves_external_symlink_to_archive_runtime(self) -> None:
output_dir = self.work / "output"
subprocess.run(self.command(output_dir), cwd=ROOT, check=True, capture_output=True, text=True)
archive_path = output_dir / "ethos-full-test-macos-arm64.tar.gz"
extracted = self.work / "extracted"
with tarfile.open(archive_path, "r:gz") as archive:
archive.extractall(extracted)
root = extracted / "ethos-full-test-macos-arm64"
link_dir = self.work / "bin"
link_dir.mkdir()
link = link_dir / "ethos"
link.symlink_to(root / "ethos")
result = subprocess.run([str(link)], check=True, capture_output=True, text=True)
self.assertEqual(str((root / "lib/libpdfium.dylib").resolve()), result.stdout.strip())

def test_hash_mismatch_fails_closed(self) -> None:
self.write_pdfium_archive(b"wrong runtime\n", include_pdfium_notice=True)
Expand Down
18 changes: 13 additions & 5 deletions .github/scripts/test_v0_5_0_version_activation.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,17 +44,25 @@ def test_draft_artifact_workflows_derive_the_activated_version(self) -> None:
def test_public_install_wording_is_not_advanced_to_the_candidate(self) -> None:
claims = read("docs/public-boundary-claims.json")
readme = read("README.md")
self.assertNotIn("0.5.0", readme)
active_readme = readme.split("### 60-second `ethos-full` install", 1)[0]
self.assertNotIn("0.5.0", active_readme)
self.assertNotIn("0.5.0", claims)

def test_npm_payload_remains_on_published_release_until_refreshed_from_core_a(self) -> None:
manifest = json.loads(read("packages/npm/ethos-pdf/vendor/manifest.json"))
package = json.loads(read("packages/npm/ethos-pdf/package.json"))
lock = json.loads(read("packages/npm/ethos-pdf/package-lock.json"))
self.assertEqual(PUBLISHED_NPM_PAYLOAD, manifest["cli_version"])
self.assertEqual(PUBLISHED_NPM_PAYLOAD, package["version"])
self.assertEqual(PUBLISHED_NPM_PAYLOAD, lock["version"])
self.assertEqual(PUBLISHED_NPM_PAYLOAD, lock["packages"][""].get("version"))
versions = {
manifest["cli_version"],
package["version"],
lock["version"],
lock["packages"][""].get("version"),
}
if versions == {VERSION}:
changelog = read("CHANGELOG.md")
self.assertIn("boundary-exception: refresh the v0.5.0 npm B payload from frozen core-A", changelog)
else:
self.assertEqual({PUBLISHED_NPM_PAYLOAD}, versions)

def test_mcp_prototype_remains_excluded(self) -> None:
cargo = read("Cargo.toml")
Expand Down
2 changes: 1 addition & 1 deletion .github/scripts/test_validate_npm_b_activation.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def fixture(self, version: str = "0.5.0") -> tuple[Path, Path]:
inventory = root / f"{target}.inventory.json"
inventory.write_text(json.dumps({"schema": "ethos.full_candidate_inventory.v1", "status": "release_candidate_pending_target_smoke", "publication": "not_publishable_pending_release_gates", "target": target, "sha256": digest, "size_bytes": archive.stat().st_size}), encoding="utf-8")
smoke = root / f"{target}.smoke.json"
smoke.write_text(json.dumps({"schema": "ethos.full_candidate_smoke.v1", "target": target, "archive_sha256": digest, "archive_size_bytes": archive.stat().st_size, "version_stdout": version}), encoding="utf-8")
smoke.write_text(json.dumps({"schema": "ethos.full_candidate_smoke.v1", "target": target, "archive_sha256": digest, "archive_size_bytes": archive.stat().st_size, "version_stdout": f"ethos {version}"}), encoding="utf-8")
targets[target] = {"archive": archive.name, "checksum": checksum.name, "inventory": inventory.name, "smoke": smoke.name}
evidence = root / "evidence.json"
evidence.write_text(json.dumps({"schema": "ethos.npm_b_activation_evidence.v1", "core_version": version, "core_commit": "a" * 40, "targets": targets}), encoding="utf-8")
Expand Down
2 changes: 1 addition & 1 deletion .github/scripts/validate_npm_b_activation.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ def validate(evidence: Path, package_root: Path, expected_version: str = "0.5.0"
or smoke.get("target") != target
or smoke.get("archive_sha256") != archive_hash
or smoke.get("archive_size_bytes") != paths["archive"].stat().st_size
or smoke.get("version_stdout") != expected_version
or smoke.get("version_stdout") != f"ethos {expected_version}"
):
fail(f"{target} smoke evidence does not bind the frozen candidate")
package = load(package_root / "package.json")
Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@
while continuing to reject unsafe and unrelated members.
- ci: allow the pinned PDFium archive's nested C++ header directory.
- ci: include deterministic base CLI archive evidence in the v0.5 candidate workflow.
- ci: bind npm B smoke validation to the canonical `ethos <version>` output.
- perf: emit compact one-request-per-line NDJSON for the v0.5 batch benchmark runner.
- boundary-exception: refresh the v0.5.0 npm B payload from frozen core-A target-smoke evidence;
no publication or public install wording change.
- docs: add a future-gated ethos-full quick install, symlink-safe launcher guidance, and macOS
Gatekeeper quarantine handling.
- test: execute the ethos-full launcher through an external symlink and bound symlink hops.
- Fix Evidence Handle Bridge projection for real verification reports, stale evidence, structured state fields, strict contexts, and inert model prose.
- Render complete HTML proof diagnostics with canonical schema labels and expanded variant coverage.
- Cover the full verify-batch request boundary, foreign-grounding, config, and crop-rejection matrix.
Expand Down
32 changes: 32 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,38 @@ Prerequisites:
- caller-provided local PDFium through `ETHOS_PDFIUM_LIBRARY_PATH` only for PDFium-backed paths
(`scripts/fetch-pdfium.sh` can fetch the exact pinned evaluation archive; see the quickstart)

### 60-second `ethos-full` install (when v0.5.0 is published)

The optional `ethos-full` archive is the shortest path to a local PDF-capable CLI. Download the
macOS arm64 or Linux x64 archive and its published SHA-256 file, verify the checksum, extract it,
and put its launcher on your `PATH`:

```bash
target=macos-arm64 # use linux-x64 on Linux
archive="ethos-full-0.5.0-${target}.tar.gz"
curl -LO "https://github.com/docushell/ethos/releases/download/v0.5.0/${archive}"
curl -LO "https://github.com/docushell/ethos/releases/download/v0.5.0/${archive}.sha256"
shasum -a 256 -c "${archive}.sha256"
tar -xzf "${archive}"
sudo ln -sf "$PWD/ethos-full-0.5.0-${target}/ethos" /usr/local/bin/ethos
ethos --version
```

The launcher resolves symlinks before locating its bundled runtime, so a `/usr/local/bin/ethos`
link works without adding the extracted archive directory to `PATH`. The archive includes the
caller-provided-PDFium runtime for this optional surface; the base CLI, Python, and npm surfaces
remain caller-PDFium as described below.

On macOS, Gatekeeper may add a quarantine attribute to browser-downloaded archives. If a trusted
archive is refused to launch, remove that attribute from the extracted directory before retrying:

```bash
xattr -dr com.apple.quarantine "ethos-full-0.5.0-${target}"
```

This is an unsigned-artifact workaround; signing and notarization are separate future release
work and are not implied by this instruction.

From a source checkout:

```bash
Expand Down
4 changes: 2 additions & 2 deletions packages/npm/ethos-pdf/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/npm/ethos-pdf/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@docushell/ethos-pdf",
"version": "0.4.0",
"version": "0.5.0",
"description": "Ethos PDF CLI binary package for document evidence workflows.",
"license": "Apache-2.0",
"repository": {
Expand Down
2 changes: 1 addition & 1 deletion packages/npm/ethos-pdf/test/platform-selection.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ assert.throws(
const manifest = readVendorManifest();
assert.strictEqual(manifest.version, 1);
assert.strictEqual(manifest.package, "@docushell/ethos-pdf");
assert.strictEqual(manifest.cli_version, "0.4.0");
assert.strictEqual(manifest.cli_version, "0.5.0");
validateVendorManifest(manifest);
for (const [key, binaryName] of SUPPORTED_TARGETS.entries()) {
assert.strictEqual(manifest.targets[key].binary, binaryName);
Expand Down
Binary file modified packages/npm/ethos-pdf/vendor/ethos-darwin-arm64
Binary file not shown.
Binary file modified packages/npm/ethos-pdf/vendor/ethos-linux-x64
Binary file not shown.
10 changes: 5 additions & 5 deletions packages/npm/ethos-pdf/vendor/manifest.json
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
{
"version": 1,
"package": "@docushell/ethos-pdf",
"cli_version": "0.4.0",
"cli_version": "0.5.0",
"targets": {
"darwin:arm64": {
"binary": "ethos-darwin-arm64",
"binary_sha256": "7d0fb5407d35acc29387c1573a362c54090410e11a8072a3cd2d541195b2a84b",
"binary_sha256": "df2d46efb96501b8071cd8665ca525ee5af4787804cd04d07262354199ead913",
"release_asset": "ethos-macos-arm64.tar.gz",
"release_asset_sha256": "719f092101bb1e4cfeebc9507155b15739642f0b891664bd0d9f8a05d9688ba5"
"release_asset_sha256": "30fa34afda745d168e1af39a134e2281f4a409d425765f3dc85c2e312fcbbcc2"
},
"linux:x64": {
"binary": "ethos-linux-x64",
"binary_sha256": "2136dcd349a7b3f73f8df83a1b1e35819f9832043eb264b3eaea341697b739ed",
"binary_sha256": "7b6b7cb03c1d16183b6cdd56f6d2ebe593a25ef257baa5b6553a0055c53e8f44",
"release_asset": "ethos-linux-x64.tar.gz",
"release_asset_sha256": "616be562306d64a293554ca4695f19deb6e135dd328e88598a80e76f6f8fb3cd"
"release_asset_sha256": "592b175c00d147625f2f2ccc8bc5c74fb8a00ee37f178c363757f2c72404876e"
}
}
}
14 changes: 13 additions & 1 deletion scripts/build-ethos-full-candidate.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,19 @@ def wrapper(runtime_path: str) -> bytes:
return (
"#!/bin/sh\n"
"set -eu\n"
'root=$(CDPATH= cd -P -- "$(dirname "$0")" && pwd)\n'
'source=$0\n'
'hops=0\n'
'while [ -L "$source" ]; do\n'
' hops=$((hops + 1))\n'
' [ "$hops" -le 40 ] || { echo "ethos launcher: symlink chain exceeds 40 hops" >&2; exit 1; }\n'
' source_dir=$(CDPATH= cd -P -- "$(dirname "$source")" && pwd)\n'
' source=$(readlink "$source")\n'
' case "$source" in\n'
' /*) ;;\n'
' *) source="$source_dir/$source" ;;\n'
' esac\n'
'done\n'
'root=$(CDPATH= cd -P -- "$(dirname "$source")" && pwd)\n'
f'export ETHOS_PDFIUM_LIBRARY_PATH="$root/{runtime_path}"\n'
'exec "$root/bin/ethos" "$@"\n'
).encode("utf-8")
Expand Down
7 changes: 6 additions & 1 deletion scripts/measure-v0-5-performance.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,12 @@ def main() -> int:

with tempfile.TemporaryDirectory() as temporary:
requests = Path(temporary) / "requests.ndjson"
requests.write_bytes((args.citations.read_bytes().rstrip(b"\n") + b"\n") * 32)
citation_request = json.dumps(
json.loads(args.citations.read_text(encoding="utf-8")),
separators=(",", ":"),
sort_keys=True,
).encode("utf-8")
requests.write_bytes((citation_request + b"\n") * 32)
batch_command = [str(args.candidate_bin), "verify-batch", str(args.source), "--citations-ndjson", str(requests)]
expected_batch = (expected.rstrip(b"\n") + b"\n") * 32
run_individual_32(candidate_command, expected) # non-recorded setup
Expand Down
1 change: 1 addition & 0 deletions scripts/test_measure_v0_5_performance.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ def test_writes_bound_passing_record(self):
b=root/v; b.write_text(FAKE); b.chmod(0o755); bins.append(b)
out=root/"record.json"; log=root/"calls.log"; env=dict(os.environ,ETHOS_PERF_TEST_LOG=str(log)); r=subprocess.run(["python3",str(RUNNER),"--baseline-bin",str(bins[0]),"--candidate-bin",str(bins[1]),"--source",str(source),"--citations",str(citations),"--out",str(out)],capture_output=True,text=True,env=env)
self.assertEqual(0,r.returncode,r.stderr); record=json.loads(out.read_text()); self.assertTrue(record["derived"]["passed"]); self.assertEqual(30,len(record["single_request_cold_ns"]["baseline"])); self.assertEqual(10,len(record["batch_32_ns"]["individual_processes"])); self.assertEqual(10,len(record["batch_32_ns"]["batch_process"])); self.assertEqual({"os","os_release","architecture","cpu"},set(record["environment"]))
self.assertIn("verify-batch", calls[-1])
calls=log.read_text().splitlines(); cold=calls[1:61]; self.assertEqual(["0.4.0:verify","0.5.0:verify","0.5.0:verify","0.4.0:verify"],cold[:4])
def test_rejects_wrong_version(self):
with tempfile.TemporaryDirectory() as d:
Expand Down
Loading