Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
ea8ae6a
release: reconcile v0.4.0 and scope v0.5.0
Jul 21, 2026
aad49a4
verify: add atomic batch citation verification
Jul 21, 2026
32d7ef9
report: add deterministic HTML proof reports
Jul 21, 2026
2320f04
python: add evidence-handle context and hydration
Jul 21, 2026
662275a
bridge: project evidence states and generate types
Jul 21, 2026
cad0dc9
examples: add evidence-handle bridge walkthrough
Jul 21, 2026
96480d0
packaging: promote ethos-full release candidate
Jul 21, 2026
d83f0f4
packaging: bind ethos-full manifest inputs
Jul 21, 2026
68290a5
ci: add ethos-full target smoke helper
Jul 21, 2026
09ce2ab
test: cover batch verification equivalence
Jul 21, 2026
2e311b7
ci: build ethos-full candidates per target
Jul 21, 2026
887b9d2
release: activate v0.5 core candidate metadata
Jul 21, 2026
7383234
ci: verify ethos-full candidate archive bindings
Jul 21, 2026
1ee9b96
release: add v0.5 performance evidence runner
Jul 21, 2026
a3fc2ff
test: exercise v0.5 performance evidence runner
Jul 21, 2026
00b08c9
report: harden HTML crop link prefixes
Jul 21, 2026
b091c46
bridge: reject empty evidence handle contexts
Jul 21, 2026
5105d83
bridge: validate projection report schema
Jul 21, 2026
e584b77
release: validate repeated v0.5 batch evidence
Jul 21, 2026
1c1b660
release: verify performance records in make gate
Jul 21, 2026
db159e9
release: bind performance evidence fixture hashes
Jul 21, 2026
2d874d4
release: bind npm B activation to frozen core A evidence
Jul 21, 2026
0460b3f
ci: enforce npm B activation boundary
Jul 21, 2026
bb432e9
release: hold npm lockfile at published version
Jul 21, 2026
92bf393
release: bind npm evidence sizes
Jul 21, 2026
39e48d8
docs: add npm B freeze runbook
Jul 21, 2026
acaf1ab
fix: reconcile v0.5 release review findings
Jul 21, 2026
d2c176e
ci: mark v0.5 release boundary review
Jul 21, 2026
a29866f
ci: repair validation and Python API guards
Jul 21, 2026
557115a
ci: repin verification action to v0.4.0
Jul 21, 2026
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
148 changes: 148 additions & 0 deletions .github/scripts/smoke_ethos_full_candidate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
#!/usr/bin/env python3
"""Validate and smoke an ethos-full release-candidate archive on its target host."""
from __future__ import annotations

import argparse
import hashlib
import json
import os
import shutil
import subprocess
import tarfile
from pathlib import Path


INVENTORY_SCHEMA = "ethos.full_candidate_inventory.v1"
STATUS = "release_candidate_pending_target_smoke"
PUBLICATION = "not_publishable_pending_release_gates"


def run(command: list[str], env: dict[str, str]) -> subprocess.CompletedProcess[bytes]:
return subprocess.run(command, env=env, stdout=subprocess.PIPE, stderr=subprocess.PIPE)


def require(condition: bool, message: str) -> None:
if not condition:
raise SystemExit(f"smoke-ethos-full-candidate: error: {message}")


def required_regular(path: Path, label: str) -> None:
require(path.is_file() and not path.is_symlink(), f"missing regular {label}: {path}")


def sha256_file(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as handle:
for chunk in iter(lambda: handle.read(1024 * 1024), b""):
digest.update(chunk)
return digest.hexdigest()


def validate_metadata(archive: Path, checksum: Path, inventory: Path) -> dict[str, object]:
for path, label in ((archive, "archive"), (checksum, "checksum"), (inventory, "inventory")):
required_regular(path, label)
digest = sha256_file(archive)
expected_checksum = f"{digest} {archive.name}\n"
require(checksum.read_text(encoding="utf-8") == expected_checksum, "checksum must bind the archive basename and sha256 canonically")
try:
record = json.loads(inventory.read_text(encoding="utf-8"))
except (OSError, json.JSONDecodeError) as error:
raise SystemExit(f"smoke-ethos-full-candidate: error: invalid inventory: {error}") from error
require(isinstance(record, dict), "inventory must be an object")
require(
set(record) == {"schema", "status", "publication", "artifact", "sha256", "size_bytes", "target"},
"inventory has an unexpected shape",
)
require(record["schema"] == INVENTORY_SCHEMA, "inventory schema is unsupported")
require(record["status"] == STATUS, "inventory status is not a release candidate")
require(record["publication"] == PUBLICATION, "inventory publication state is invalid")
require(record["artifact"] == archive.name, "inventory artifact does not match archive")
require(record["sha256"] == digest, "inventory sha256 does not match archive")
require(record["size_bytes"] == archive.stat().st_size, "inventory size does not match archive")
require(record["target"] in {"macos-arm64", "linux-x64"}, "inventory target is unsupported")
return record


def extract_candidate(archive: Path, extract_dir: Path) -> Path:
require(not extract_dir.exists(), f"extract directory already exists: {extract_dir}")
try:
with tarfile.open(archive, "r:gz") as bundle:
members = bundle.getmembers()
require(members, "archive has no members")
roots: set[str] = set()
for member in members:
path = Path(member.name)
require(not path.is_absolute() and all(part not in {"", ".", ".."} for part in path.parts), "archive contains an unsafe member path")
require(member.isfile(), f"archive member is not a regular file: {member.name}")
roots.add(path.parts[0])
require(len(roots) == 1, "archive must contain exactly one top-level directory")
root_name = next(iter(roots))
extract_dir.mkdir(parents=True)
root = extract_dir / root_name
root.mkdir()
for member in members:
destination = extract_dir / member.name
destination.parent.mkdir(parents=True, exist_ok=True)
source = bundle.extractfile(member)
require(source is not None, f"archive member is unreadable: {member.name}")
with destination.open("wb") as output:
shutil.copyfileobj(source, output)
destination.chmod(member.mode)
return root
except (OSError, tarfile.TarError) as error:
raise SystemExit(f"smoke-ethos-full-candidate: error: invalid archive: {error}") from error


def smoke(root: Path, expected_version: str, fixture: Path, record: dict[str, object], archive: Path) -> dict[str, object]:
required = ["ethos", "bin/ethos", "LICENSE", "NOTICE", "artifact-manifest.json"]
for name in required:
required_regular(root / name, f"payload {name}")
manifest = json.loads((root / "artifact-manifest.json").read_text(encoding="utf-8"))
require(manifest.get("target") == record["target"], "payload target does not match inventory")
runtime = manifest["pdfium"]["runtime_library_path"]
required_regular(root / runtime, f"PDFium runtime {runtime}")
env = dict(os.environ)
env["ETHOS_PDFIUM_LIBRARY_PATH"] = "/invalid/ambient/pdfium"
version = run([str(root / "ethos"), "--version"], env)
require(version.returncode == 0 and version.stdout.decode().strip() == expected_version, "unexpected ethos --version result")
doctor = run([str(root / "ethos"), "doctor", "--require-pdfium"], env)
require(doctor.returncode == 0, f"ethos doctor --require-pdfium failed: {doctor.stderr.decode()}")
evidence: dict[str, object] = {
"schema": "ethos.full_candidate_smoke.v1",
"artifact": archive.name,
"archive_sha256": record["sha256"],
"archive_size_bytes": record["size_bytes"],
"target": record["target"],
"version_stdout": expected_version,
"runtime_library_path": runtime,
"doctor_exit_code": doctor.returncode,
}
require(fixture.is_file(), f"missing parse fixture: {fixture}")
first = run([str(root / "ethos"), "doc", "parse", str(fixture), "--format", "json"], env)
second = run([str(root / "ethos"), "doc", "parse", str(fixture), "--format", "json"], env)
require(first.returncode == second.returncode == 0, "fixture parse failed")
require(first.stdout == second.stdout, "fixture parses were not byte-identical")
evidence["parse_stdout_sha256"] = hashlib.sha256(first.stdout).hexdigest()
return evidence


def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--archive", required=True, type=Path)
parser.add_argument("--checksum", required=True, type=Path)
parser.add_argument("--inventory", required=True, type=Path)
parser.add_argument("--extract-dir", required=True, type=Path)
parser.add_argument("--expected-version", required=True)
parser.add_argument("--fixture", required=True, type=Path)
parser.add_argument("--out", type=Path)
args = parser.parse_args()
record = validate_metadata(args.archive, args.checksum, args.inventory)
root = extract_candidate(args.archive, args.extract_dir)
evidence = smoke(root, args.expected_version, args.fixture, record, args.archive)
if args.out:
args.out.write_text(json.dumps(evidence, sort_keys=True, indent=2) + "\n", encoding="utf-8")
return 0


if __name__ == "__main__":
raise SystemExit(main())
1 change: 1 addition & 0 deletions .github/scripts/test_ci_workflow.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ def test_package_integrity_and_registry_surface_gates_run_in_pr_ci(self) -> None
"npm test --prefix packages/npm/ethos-pdf",
"python3 .github/scripts/test_package_registry_source_consistency.py",
"python3 .github/scripts/test_claims_gate_registry_surfaces.py",
"python3 .github/scripts/test_validate_npm_b_activation.py",
"python3 .github/scripts/claims_gate.py",
"python3 .github/scripts/public_boundary_claims_gate.py",
"python3 .github/scripts/check_release_boundary_paths.py",
Expand Down
43 changes: 40 additions & 3 deletions .github/scripts/test_ethos_full_candidate.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ def setUp(self) -> None:
self.profile = self.work / "profile.json"
self.write_profile(sha256(self.runtime))

def write_pdfium_archive(self, runtime: bytes, include_pdfium_notice: bool) -> None:
def write_pdfium_archive(self, runtime: bytes, include_pdfium_notice: bool, extras=None) -> None:
files = {
"LICENSE": b"PDFium package license\n",
"lib/libpdfium.dylib": runtime,
Expand All @@ -50,6 +50,8 @@ def write_pdfium_archive(self, runtime: bytes, include_pdfium_notice: bool) -> N
info.size = len(data)
info.mtime = 0
archive.addfile(info, io.BytesIO(data))
for info, data in extras or []:
archive.addfile(info, io.BytesIO(data) if data is not None else None)

def write_profile(self, runtime_hash: str) -> None:
self.profile.write_text(
Expand Down Expand Up @@ -120,6 +122,11 @@ def test_double_run_is_byte_identical_and_complete(self) -> None:
(first / "ethos-full-test-macos-arm64.inventory.json").read_bytes(),
(second / "ethos-full-test-macos-arm64.inventory.json").read_bytes(),
)
inventory = json.loads((first / "ethos-full-test-macos-arm64.inventory.json").read_text())
self.assertEqual(first_archive.name, inventory["artifact"])
self.assertEqual(sha256(first_archive.read_bytes()), inventory["sha256"])
self.assertEqual(first_archive.stat().st_size, inventory["size_bytes"])
self.assertEqual("macos-arm64", inventory["target"])

root = "ethos-full-test-macos-arm64"
with tarfile.open(first_archive, "r:gz") as archive:
Expand All @@ -137,8 +144,10 @@ def test_double_run_is_byte_identical_and_complete(self) -> None:
self.assertIn(required, names)
manifest = json.load(archive.extractfile(f"{root}/artifact-manifest.json"))
launcher = archive.extractfile(f"{root}/ethos").read().decode("utf-8")
self.assertEqual("proposal_evidence_not_release_ready", manifest["status"])
self.assertEqual("blocked_pending_adr_0015", manifest["publication"])
self.assertEqual("release_candidate_pending_target_smoke", manifest["status"])
self.assertEqual("not_publishable_pending_release_gates", manifest["publication"])
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)

def test_hash_mismatch_fails_closed(self) -> None:
Expand All @@ -159,6 +168,34 @@ def test_missing_notice_fails_closed(self) -> None:
self.assertNotEqual(0, result.returncode)
self.assertIn("must include licenses/pdfium.txt", result.stderr)

def test_link_and_special_members_fail_closed(self) -> None:
for name, member_type in (("licenses/link.txt", tarfile.SYMTYPE), ("licenses/device.txt", tarfile.CHRTYPE)):
with self.subTest(member_type=member_type):
info = tarfile.TarInfo(name)
info.type = member_type
info.linkname = "licenses/pdfium.txt"
self.write_pdfium_archive(self.runtime, True, [(info, None)])
self.write_profile(sha256(self.runtime))
result = subprocess.run(self.command(self.work / f"out-{member_type!r}"), cwd=ROOT, capture_output=True, text=True)
self.assertNotEqual(0, result.returncode)
self.assertIn("must be a regular file", result.stderr)

def test_unsafe_duplicate_and_unexpected_members_fail_closed(self) -> None:
cases = []
unsafe = tarfile.TarInfo("../escape.txt"); unsafe.size = 1
cases.append((unsafe, b"x", "unsafe PDFium archive entry"))
duplicate = tarfile.TarInfo("LICENSE"); duplicate.size = 1
cases.append((duplicate, b"x", "duplicate PDFium archive entry"))
unexpected = tarfile.TarInfo("payload.bin"); unexpected.size = 1
cases.append((unexpected, b"x", "unexpected PDFium archive file"))
for info, data, diagnostic in cases:
with self.subTest(diagnostic=diagnostic):
self.write_pdfium_archive(self.runtime, True, [(info, data)])
self.write_profile(sha256(self.runtime))
result = subprocess.run(self.command(self.work / f"out-{diagnostic[:6]}"), cwd=ROOT, capture_output=True, text=True)
self.assertNotEqual(0, result.returncode)
self.assertIn(diagnostic, result.stderr)


if __name__ == "__main__":
unittest.main()
16 changes: 8 additions & 8 deletions .github/scripts/test_execution_status.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,18 +34,18 @@ def test_status_is_scoped_to_internal_continuation(self) -> None:
text = status_text()

self.assertIn(
"Status: v0.3.0 Rust library crates `ethos-doc-core`, `ethos-verify`, and `ethos-pdf` "
"Status: v0.4.0 Rust library crates `ethos-doc-core`, `ethos-verify`, and `ethos-pdf` "
"are live on crates.io, and the Python `ethos-pdf` wheel is live on PyPI.",
text,
)
self.assertIn(
"The exact v0.3.0 public install wording packet is approved and closed out",
"The exact v0.4.0 public install wording packet is approved and closed out",
text,
)
self.assertIn("docs/validation/v0-3-0-public-install-wording-approval-decision-validation-2026-07-02.md", text)
self.assertIn("docs/validation/v0-3-0-public-install-wording-closeout-validation-2026-07-02.md", text)
self.assertIn("GitHub Release `v0.3.0`", text)
self.assertIn("npm `@docushell/ethos-pdf@0.3.0` is live on npm", text)
self.assertIn("npm `@docushell/ethos-pdf@0.4.0` is live on npm", text)
self.assertIn("v0.3.0 npm publication closeout", text)
self.assertIn("DocuShell integration remain blocked", text)
self.assertIn("Internal Milestone D source-only closeout remains complete", text)
Expand Down Expand Up @@ -106,15 +106,15 @@ def test_public_posture_boundary_remains_explicit(self) -> None:
text,
)
self.assertIn(
"v0.3.0 Rust library crates `ethos-doc-core`, `ethos-verify`, and `ethos-pdf` are live on crates.io",
"v0.4.0 Rust library crates `ethos-doc-core`, `ethos-verify`, and `ethos-pdf` are live on crates.io",
text,
)
self.assertIn("the Python `ethos-pdf` wheel is live on PyPI", text)
self.assertIn("npm `@docushell/ethos-pdf@0.3.0` is live on npm", text)
self.assertIn("The exact v0.3.0 public install wording packet is approved and closed out", text)
self.assertIn("GitHub Release `v0.3.0`", text)
self.assertIn("npm `@docushell/ethos-pdf@0.4.0` is live on npm", text)
self.assertIn("The exact v0.4.0 public install wording packet is approved and closed out", text)
self.assertIn("GitHub Release `v0.4.0`", text)
self.assertIn("macOS arm64/Linux x64 CLI artifacts", text)
self.assertIn("`@docushell/ethos-pdf@0.3.0`", text)
self.assertIn("`@docushell/ethos-pdf@0.4.0`", text)
self.assertIn("docs/validation/v0-3-0-publication-closeout-validation-2026-07-01.md", text)
self.assertIn("ethos-doc-core", text)
self.assertIn("ethos-verify", text)
Expand Down
6 changes: 4 additions & 2 deletions .github/scripts/test_npm_binary_package_scaffold.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@
"scripts/prepare-vendor.js",
"types/answer-release.d.ts",
"types/citation-emission.d.ts",
"types/citation-emission-v2.d.ts",
"types/evidence-handle-context.d.ts",
"types/index.d.ts",
"types/verification-report.d.ts",
"vendor/ethos-darwin-arm64",
Expand Down Expand Up @@ -146,8 +148,8 @@ def test_package_docs_keep_pdfium_and_publication_boundaries(self) -> None:
self.assertIn("does not bundle PDFium", text)
self.assertIn("ETHOS_PDFIUM_LIBRARY_PATH", text)
self.assertIn("QUICKSTART.md", text)
self.assertIn("current published npm package is `@docushell/ethos-pdf@0.3.0`", text)
self.assertIn("`ethos 0.3.0`", text)
self.assertIn("current published npm package is `@docushell/ethos-pdf@0.4.0`", text)
self.assertIn("`ethos 0.4.0`", text)
self.assertIn("release-archive and extracted-executable SHA256 values", text)
self.assertIn("does not include public benchmark reports or claims", normalized)

Expand Down
12 changes: 6 additions & 6 deletions .github/scripts/test_public_surface_posture.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,12 @@ def test_readme_status_matches_public_beta_evaluation_scope(self) -> None:
self.assertIn("Python `ethos-pdf` wheel", normalized)
self.assertIn("caller-provided PDFium", text)
self.assertIn("release-scope work", text)
self.assertIn("cargo add ethos-doc-core@0.3.0", text)
self.assertIn("cargo add ethos-verify@0.3.0", text)
self.assertIn("cargo add ethos-pdf@0.3.0", text)
self.assertIn("python3 -m pip install ethos-pdf==0.3.0", text)
self.assertIn("npm install -g @docushell/ethos-pdf@0.3.0", text)
self.assertIn("GitHub Release `v0.3.0`", text)
self.assertIn("cargo add ethos-doc-core@0.4.0", text)
self.assertIn("cargo add ethos-verify@0.4.0", text)
self.assertIn("cargo add ethos-pdf@0.4.0", text)
self.assertIn("python3 -m pip install ethos-pdf==0.4.0", text)
self.assertIn("npm install -g @docushell/ethos-pdf@0.4.0", text)
self.assertIn("GitHub Release `v0.4.0`", text)
self.assertNotIn("cargo add ethos-doc-core@0.2.0", text)
self.assertNotIn("python3 -m pip install ethos-pdf==0.2.0", text)
self.assertNotIn("npm install -g @docushell/ethos-pdf@0.2.1", text)
Expand Down
4 changes: 4 additions & 0 deletions .github/scripts/test_python_public_api_policy.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,17 @@
"anchor",
"app_answer_release_decision",
"build_citation_emission",
"build_evidence_citation_emission",
"build_evidence_handle_context",
"build_langchain_context",
"build_llamaindex_context",
"citation_json_bytes",
"crop_element",
"emit_langchain_citations",
"emit_llamaindex_citations",
"hydrate_citations",
"hydrate_evidence_citations",
"project_evidence_states",
"parse_pdf_json",
"parse_pdf_markdown",
"parse_pdf_text",
Expand Down
21 changes: 20 additions & 1 deletion .github/scripts/test_release_artifact_workflow_prep.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ def test_workflow_generates_draft_artifacts_without_publication(self) -> None:
self.assertNotIn('tar -C "$(dirname "$out")" -czf', text)
self.assertIn("test_build_release_cli_archive.py", text)
self.assertIn("smoke_release_cli_artifact.py", text)
self.assertIn('--expected-version "ethos 0.4.0"', text)
self.assertIn('--expected-version "ethos ${{ steps.version.outputs.value }}"', text)
self.assertIn("--target \"${{ matrix.artifact_target }}\"", text)
self.assertIn("*.smoke.json", text)
self.assertIn("validate_release_artifact_inventory.py", text)
Expand All @@ -74,9 +74,28 @@ def test_preflight_runs_release_scope_guards_before_artifacts(self) -> None:
"test_pdfium_manual_setup_contract.py",
"test_build_release_cli_archive.py",
"test_windows_verify_candidate.py",
"test_ethos_full_candidate.py",
"test_smoke_ethos_full_candidate.py",
):
self.assertIn(guard, text)

def test_ethos_full_candidate_job_is_target_limited_and_nonpublishing(self) -> None:
text = read(WORKFLOW)
start = text.index(" ethos-full-release-candidate:")
end = text.index(" windows-verify-draft-artifact:", start)
job = text[start:end]
self.assertIn("macos-arm64", job)
self.assertIn("linux-x64", job)
self.assertNotIn("windows", job)
self.assertIn("curl -fL --retry 3", job)
self.assertIn("profiles/ethos-deterministic-v1.json", job)
self.assertIn("build-ethos-full-candidate.py", job)
self.assertEqual(3, job.count("cmp target/release-artifacts/run1"))
self.assertIn("smoke_ethos_full_candidate.py", job)
self.assertIn("fixtures/synthetic/simple-text/document.pdf", job)
self.assertNotIn("gh release", job)
self.assertNotIn("publish", job)

def test_inventory_writer_and_validator_accept_draft_manifest(self) -> None:
with tempfile.TemporaryDirectory() as temp:
root = Path(temp)
Expand Down
Loading
Loading