From 9e8c98722cf6a63f25d7f167252c36c6cb2d29d7 Mon Sep 17 00:00:00 2001 From: docushell-admin Date: Mon, 22 Jun 2026 00:23:25 +0530 Subject: [PATCH 1/4] Record package candidate activation evidence Signed-off-by: docushell-admin --- ...ackage_publication_candidate_activation.py | 353 ++++++++++++++++++ ...one_e_package_publication_approval_prep.py | 1 + ...blication_candidate_activation_evidence.py | 220 +++++++++++ ...e_e_package_publication_pdfium_boundary.py | 1 + ...t_milestone_e_prep_guard_sequence_index.py | 1 + .../scripts/test_milestone_e_prep_scope.py | 1 + ...est_milestone_e_validation_record_index.py | 19 + .github/workflows/ci.yml | 2 + Makefile | 1 + crates/ethos-pdf/assets/README.md | 2 + .../assets/ethos-deterministic-v1.json | 94 +++++ crates/ethos-pdf/src/lib.rs | 5 +- docs/execution-status.md | 2 + ...e-e-package-publication-approval-prep.json | 3 +- docs/milestone-e-prep-scope.md | 5 + docs/roadmap.md | 6 + docs/validation/README.md | 5 + ...tivation-evidence-validation-2026-06-22.md | 110 ++++++ ...kage-publication-approval-prep.schema.json | 6 +- 19 files changed, 831 insertions(+), 6 deletions(-) create mode 100644 .github/scripts/package_publication_candidate_activation.py create mode 100644 .github/scripts/test_milestone_e_package_publication_candidate_activation_evidence.py create mode 100644 crates/ethos-pdf/assets/ethos-deterministic-v1.json create mode 100644 docs/validation/milestone-e-package-publication-candidate-activation-evidence-validation-2026-06-22.md diff --git a/.github/scripts/package_publication_candidate_activation.py b/.github/scripts/package_publication_candidate_activation.py new file mode 100644 index 0000000..19f5355 --- /dev/null +++ b/.github/scripts/package_publication_candidate_activation.py @@ -0,0 +1,353 @@ +#!/usr/bin/env python3 +# +# Copyright 2026 The Ethos maintainers +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from __future__ import annotations + +import argparse +import hashlib +import json +import shutil +import subprocess +import tarfile +import tempfile +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[2] +VERSION = "0.1.0" +CORE_PACKAGE = "ethos-doc-core" +CANDIDATE_PACKAGES = (CORE_PACKAGE, "ethos-verify", "ethos-pdf") +IGNORE_NAMES = { + ".git", + "target", + "__pycache__", + ".pytest_cache", + ".mypy_cache", + "web", + ".roadmap.md.swp", + "docs/.roadmap.md.swp", +} + + +def should_ignore(_: str, names: list[str]) -> set[str]: + return {name for name in names if name in IGNORE_NAMES} + + +def run(command: list[str], cwd: Path, commands: list[dict[str, object]]) -> None: + result = subprocess.run( + command, + cwd=cwd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + check=False, + ) + commands.append( + { + "command": " ".join(command), + "returncode": result.returncode, + "stdout_tail": result.stdout[-1200:], + "stderr_tail": result.stderr[-1200:], + } + ) + if result.returncode != 0: + raise RuntimeError( + f"command failed in candidate activation workspace: {' '.join(command)}\n" + f"{result.stdout}\n{result.stderr}" + ) + + +def run_output(command: list[str], cwd: Path, commands: list[dict[str, object]]) -> str: + result = subprocess.run( + command, + cwd=cwd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + check=False, + ) + commands.append( + { + "command": " ".join(command), + "returncode": result.returncode, + "stdout_tail": result.stdout[-1200:], + "stderr_tail": result.stderr[-1200:], + } + ) + if result.returncode != 0: + raise RuntimeError( + f"command failed in candidate activation workspace: {' '.join(command)}\n" + f"{result.stdout}\n{result.stderr}" + ) + return result.stdout + + +def replace_once(path: Path, old: str, new: str) -> None: + text = path.read_text(encoding="utf-8") + if old not in text: + raise RuntimeError(f"expected text not found in {path}: {old}") + path.write_text(text.replace(old, new, 1), encoding="utf-8") + + +def materialize_candidate_workspace(workspace: Path) -> None: + shutil.copytree(ROOT, workspace, ignore=should_ignore) + + replace_once( + workspace / "Cargo.toml", + 'ethos-core = { path = "crates/ethos-core", version = "0.1.0", default-features = false }', + 'ethos-core = { package = "ethos-doc-core", path = "crates/ethos-core", version = "0.1.0", default-features = false }', + ) + with (workspace / "Cargo.toml").open("a", encoding="utf-8") as handle: + handle.write( + '\n[patch.crates-io]\n' + 'ethos-doc-core = { path = "crates/ethos-core" }\n' + ) + + core_manifest = workspace / "crates/ethos-core/Cargo.toml" + replace_once(core_manifest, 'name = "ethos-core"', 'name = "ethos-doc-core"') + replace_once( + core_manifest, + "authors.workspace = true\n\n[package.metadata.ethos_publication]", + 'authors.workspace = true\n\n[lib]\nname = "ethos_core"\n\n[package.metadata.ethos_publication]', + ) + + +def sha256(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 read_packaged_manifest(crate_path: Path, package: str) -> str: + with tarfile.open(crate_path, "r:gz") as archive: + member = archive.getmember(f"{package}-{VERSION}/Cargo.toml") + extracted = archive.extractfile(member) + if extracted is None: + raise RuntimeError(f"missing packaged Cargo.toml in {crate_path}") + return extracted.read().decode("utf-8") + + +def package_candidate(workspace: Path, package: str, commands: list[dict[str, object]]) -> dict[str, str]: + run( + ["cargo", "package", "--locked", "--offline", "-p", package, "--allow-dirty", "--no-verify"], + workspace, + commands, + ) + crate_path = workspace / "target/package" / f"{package}-{VERSION}.crate" + if not crate_path.is_file(): + raise RuntimeError(f"missing package artifact: {crate_path}") + manifest = read_packaged_manifest(crate_path, package) + return { + "package": package, + "crate_file": crate_path.name, + "sha256": sha256(crate_path), + "manifest": manifest, + } + + +def extract_crates(workspace: Path, artifacts: list[dict[str, str]]) -> Path: + unpacked = workspace / "target/package-candidate-unpacked" + unpacked.mkdir(parents=True, exist_ok=True) + for artifact in artifacts: + crate_path = workspace / "target/package" / artifact["crate_file"] + with tarfile.open(crate_path, "r:gz") as archive: + archive.extractall(unpacked) + return unpacked + + +def write_cargo_checksum(package_dir: Path, crate_path: Path) -> None: + files: dict[str, str] = {} + for path in sorted(package_dir.rglob("*")): + if not path.is_file() or path.name == ".cargo-checksum.json": + continue + rel = path.relative_to(package_dir).as_posix() + files[rel] = sha256(path) + (package_dir / ".cargo-checksum.json").write_text( + json.dumps({"files": files, "package": sha256(crate_path)}, sort_keys=True), + encoding="utf-8", + ) + + +def activate_registry_equivalent_source( + workspace: Path, + core_artifact: dict[str, str], + commands: list[dict[str, object]], +) -> None: + vendor_dir = workspace / "target/package-candidate-vendor" + cargo_config = run_output( + ["cargo", "vendor", "--locked", "--offline", "target/package-candidate-vendor"], + workspace, + commands, + ) + crate_path = workspace / "target/package" / core_artifact["crate_file"] + with tarfile.open(crate_path, "r:gz") as archive: + archive.extractall(vendor_dir) + write_cargo_checksum(vendor_dir / f"ethos-doc-core-{VERSION}", crate_path) + + config_dir = workspace / ".cargo" + config_dir.mkdir(exist_ok=True) + (config_dir / "config.toml").write_text(cargo_config, encoding="utf-8") + + +def write_consumer(workspace: Path, unpacked: Path) -> Path: + consumer = workspace / "target/package-candidate-consumer" + (consumer / "src").mkdir(parents=True, exist_ok=True) + (consumer / "Cargo.toml").write_text( + f"""[package] +name = "ethos-package-candidate-consumer" +version = "0.0.0" +edition = "2021" +publish = false + +[workspace] + +[dependencies] +ethos-core = {{ package = "ethos-doc-core", version = "{VERSION}", default-features = false, features = ["grounding", "verify-types"] }} +ethos-verify = {{ version = "{VERSION}" }} +ethos-pdf = {{ version = "{VERSION}" }} + +[patch.crates-io] +ethos-doc-core = {{ path = "{unpacked / f'ethos-doc-core-{VERSION}'}" }} +ethos-verify = {{ path = "{unpacked / f'ethos-verify-{VERSION}'}" }} +ethos-pdf = {{ path = "{unpacked / f'ethos-pdf-{VERSION}'}" }} +""", + encoding="utf-8", + ) + (consumer / "src/lib.rs").write_text( + """use ethos_core::grounding::GroundingSource; + +pub fn candidate_surface_links() -> &'static str { + let _ = core::any::type_name::(); + let _ = ethos_verify::normalize_quote("candidate"); + let _ = ethos_pdf::PDFIUM_LIBRARY_PATH_ENV; + ethos_core::SCHEMA_VERSION +} +""", + encoding="utf-8", + ) + return consumer + + +def validate_packaged_manifests(artifacts: list[dict[str, str]]) -> dict[str, object]: + manifests = {artifact["package"]: artifact["manifest"] for artifact in artifacts} + core = manifests[CORE_PACKAGE] + verify = manifests["ethos-verify"] + pdf = manifests["ethos-pdf"] + + checks = { + "core_package_name": 'name = "ethos-doc-core"' in core, + "core_library_name": 'name = "ethos_core"' in core, + "verify_depends_on_core_package": "ethos-doc-core" in verify, + "verify_retains_grounding_features": "grounding" in verify and "verify-types" in verify, + "pdf_depends_on_core_package": "ethos-doc-core" in pdf, + "pdf_retains_full_feature": "full" in pdf, + } + missing = [name for name, passed in checks.items() if not passed] + if missing: + raise RuntimeError(f"packaged manifest checks failed: {', '.join(missing)}") + return checks + + +def source_manifests_are_still_blocked() -> bool: + core = (ROOT / "crates/ethos-core/Cargo.toml").read_text(encoding="utf-8") + verify = (ROOT / "crates/ethos-verify/Cargo.toml").read_text(encoding="utf-8") + pdf = (ROOT / "crates/ethos-pdf/Cargo.toml").read_text(encoding="utf-8") + return all( + [ + 'name = "ethos-core"' in core, + "publish = false" in core, + "publish = false" in verify, + "publish = false" in pdf, + 'package = "ethos-doc-core"' not in verify, + 'package = "ethos-doc-core"' not in pdf, + not (ROOT / ".cargo/config.toml").exists(), + not (ROOT / "target/package-registry").exists(), + ] + ) + + +def run_candidate_activation(workspace: Path) -> dict[str, object]: + commands: list[dict[str, object]] = [] + materialize_candidate_workspace(workspace) + run(["cargo", "generate-lockfile", "--offline"], workspace, commands) + run(["cargo", "check", "--locked", "--offline", "-p", "ethos-verify"], workspace, commands) + run(["cargo", "check", "--locked", "--offline", "-p", "ethos-pdf"], workspace, commands) + core_artifact = package_candidate(workspace, CORE_PACKAGE, commands) + activate_registry_equivalent_source(workspace, core_artifact, commands) + artifacts = [core_artifact] + [ + package_candidate(workspace, package, commands) + for package in ("ethos-verify", "ethos-pdf") + ] + checks = validate_packaged_manifests(artifacts) + unpacked = extract_crates(workspace, artifacts) + consumer = write_consumer(workspace, unpacked) + run(["cargo", "generate-lockfile", "--offline"], consumer, commands) + run(["cargo", "check", "--locked", "--offline"], consumer, commands) + + return { + "status": "pass", + "candidate_version": VERSION, + "candidate_packages": list(CANDIDATE_PACKAGES), + "manifest_activation": { + "core_package_name": "ethos-doc-core", + "core_library_name": "ethos_core", + "dependency_key": "ethos-core", + "verify_core_features": ["grounding", "verify-types"], + "pdf_core_features": ["full"], + }, + "packaged_manifest_checks": checks, + "artifacts": [ + { + "package": artifact["package"], + "crate_file": artifact["crate_file"], + "sha256": artifact["sha256"], + } + for artifact in artifacts + ], + "registry_equivalent_consumer_check": "pass", + "source_manifests_remain_blocked": source_manifests_are_still_blocked(), + "package_publication_approved": False, + "public_installation_approved": False, + "commands": commands, + } + + +def main() -> None: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--json", action="store_true", help="emit machine-readable JSON") + parser.add_argument("--keep-workspace", type=Path, help="write the candidate workspace here") + args = parser.parse_args() + + if args.keep_workspace: + workspace = args.keep_workspace.resolve() + if workspace.exists(): + shutil.rmtree(workspace) + result = run_candidate_activation(workspace) + else: + with tempfile.TemporaryDirectory(prefix="ethos-package-candidate-") as tmp: + result = run_candidate_activation(Path(tmp) / "ethos") + + if args.json: + print(json.dumps(result, indent=2, sort_keys=True)) + else: + print("candidate package activation evidence passed") + + +if __name__ == "__main__": + main() diff --git a/.github/scripts/test_milestone_e_package_publication_approval_prep.py b/.github/scripts/test_milestone_e_package_publication_approval_prep.py index 3b807d1..1d29a13 100644 --- a/.github/scripts/test_milestone_e_package_publication_approval_prep.py +++ b/.github/scripts/test_milestone_e_package_publication_approval_prep.py @@ -99,6 +99,7 @@ "package_public_installation_wording_review": "docs/validation/milestone-e-package-publication-public-installation-wording-review-validation-2026-06-21.md", "package_approval_decision_template": "docs/validation/milestone-e-package-publication-approval-decision-template-validation-2026-06-21.md", "package_approval_decision_record": "docs/validation/milestone-e-package-publication-approval-decision-validation-2026-06-21.md", + "package_candidate_activation_evidence": "docs/validation/milestone-e-package-publication-candidate-activation-evidence-validation-2026-06-22.md", } EXPECTED_PUBLICATION_DECISION_INPUTS = { "decision_status": "not_approved_pending_exact_decision", diff --git a/.github/scripts/test_milestone_e_package_publication_candidate_activation_evidence.py b/.github/scripts/test_milestone_e_package_publication_candidate_activation_evidence.py new file mode 100644 index 0000000..eba064b --- /dev/null +++ b/.github/scripts/test_milestone_e_package_publication_candidate_activation_evidence.py @@ -0,0 +1,220 @@ +#!/usr/bin/env python3 +# +# Copyright 2026 The Ethos maintainers +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from __future__ import annotations + +import json +import re +import subprocess +import unittest +from pathlib import Path + +from makefile_guard import target_block + + +ROOT = Path(__file__).resolve().parents[2] +SCRIPT = ROOT / ".github/scripts/package_publication_candidate_activation.py" +PREP = ROOT / "docs/milestone-e-package-publication-approval-prep.json" +RECORD = ( + ROOT + / "docs/validation/" + "milestone-e-package-publication-candidate-activation-evidence-validation-2026-06-22.md" +) +VALIDATION_README = ROOT / "docs/validation/README.md" +PREP_SCOPE = ROOT / "docs/milestone-e-prep-scope.md" +ROADMAP = ROOT / "docs/roadmap.md" +EXECUTION_STATUS = ROOT / "docs/execution-status.md" +CI_WORKFLOW = ROOT / ".github/workflows/ci.yml" +ROOT_PROFILE = ROOT / "profiles/ethos-deterministic-v1.json" +PDF_PROFILE = ROOT / "crates/ethos-pdf/assets/ethos-deterministic-v1.json" + +SOURCE_COMMIT = "6cf211cfae82c8ba7d6454a71e0922bd95a01f28" +SOURCE_SHORT = "6cf211c" +SOURCE_TREE = "ae76bc588b64dc1e8087d9096d52545a3560c2c0" +FORBIDDEN_SCOPE_EXPANSION = [ + "public reports are approved", + "public result wording approved", + "release-ready", + "release artifact approved", + "package-ready", + "package publication is approved", + "package publication approved", + "packages are published", + "published packages", + "production-ready", + "production positioning approved", + "benchmark-validated", + "public benchmark pass", + "speed validated", + "fastest", + "launch-ready", + "hosted surface approved", + "hosted demo approved", + "demo-ready", + "performance validated", + "quality validated", + "footprint validated", + "table-quality validated", + "parser-quality validated", +] + + +def read(path: Path) -> str: + return path.read_text(encoding="utf-8") + + +def normalized(path: Path) -> str: + return re.sub(r"\s+", " ", read(path)) + + +def load_json(path: Path) -> dict: + return json.loads(path.read_text(encoding="utf-8")) + + +def git(*args: str) -> str: + return subprocess.check_output( + ["git", *args], + cwd=ROOT, + encoding="utf-8", + stderr=subprocess.DEVNULL, + ).strip() + + +def run_candidate_activation() -> dict: + output = subprocess.check_output( + ["python3", str(SCRIPT), "--json"], + cwd=ROOT, + encoding="utf-8", + stderr=subprocess.PIPE, + ) + return json.loads(output) + + +class MilestoneEPackagePublicationCandidateActivationEvidenceTests(unittest.TestCase): + @classmethod + def setUpClass(cls) -> None: + cls.result = run_candidate_activation() + + def test_candidate_activation_script_passes_with_registry_equivalent_consumer(self) -> None: + result = self.result + commands = [entry["command"] for entry in result["commands"]] + + self.assertEqual("pass", result["status"]) + self.assertEqual("0.1.0", result["candidate_version"]) + self.assertEqual(["ethos-doc-core", "ethos-verify", "ethos-pdf"], result["candidate_packages"]) + self.assertEqual("pass", result["registry_equivalent_consumer_check"]) + self.assertTrue(result["source_manifests_remain_blocked"]) + self.assertFalse(result["package_publication_approved"]) + self.assertFalse(result["public_installation_approved"]) + self.assertIn("cargo vendor --locked --offline target/package-candidate-vendor", commands) + self.assertIn("cargo package --locked --offline -p ethos-doc-core --allow-dirty --no-verify", commands) + self.assertIn("cargo package --locked --offline -p ethos-verify --allow-dirty --no-verify", commands) + self.assertIn("cargo package --locked --offline -p ethos-pdf --allow-dirty --no-verify", commands) + self.assertIn("cargo check --locked --offline", commands) + + def test_candidate_activation_preserves_import_and_dependency_shape(self) -> None: + activation = self.result["manifest_activation"] + checks = self.result["packaged_manifest_checks"] + artifacts = {artifact["package"]: artifact for artifact in self.result["artifacts"]} + + self.assertEqual("ethos-doc-core", activation["core_package_name"]) + self.assertEqual("ethos_core", activation["core_library_name"]) + self.assertEqual("ethos-core", activation["dependency_key"]) + self.assertEqual(["grounding", "verify-types"], activation["verify_core_features"]) + self.assertEqual(["full"], activation["pdf_core_features"]) + self.assertTrue(all(checks.values())) + self.assertEqual({"ethos-doc-core", "ethos-verify", "ethos-pdf"}, set(artifacts)) + for artifact in artifacts.values(): + self.assertRegex(artifact["sha256"], r"^[0-9a-f]{64}$") + self.assertTrue(artifact["crate_file"].endswith("-0.1.0.crate")) + + def test_source_manifests_remain_blocked_and_profile_copy_is_in_sync(self) -> None: + core_manifest = read(ROOT / "crates/ethos-core/Cargo.toml") + verify_manifest = read(ROOT / "crates/ethos-verify/Cargo.toml") + pdf_manifest = read(ROOT / "crates/ethos-pdf/Cargo.toml") + pdf_lib = read(ROOT / "crates/ethos-pdf/src/lib.rs") + + self.assertEqual(read(ROOT_PROFILE), read(PDF_PROFILE)) + self.assertIn('include_str!("../assets/ethos-deterministic-v1.json")', pdf_lib) + self.assertIn('name = "ethos-core"', core_manifest) + self.assertIn("publish = false", core_manifest) + self.assertIn("publish = false", verify_manifest) + self.assertIn("publish = false", pdf_manifest) + self.assertNotIn('package = "ethos-doc-core"', verify_manifest) + self.assertNotIn('package = "ethos-doc-core"', pdf_manifest) + self.assertFalse((ROOT / ".cargo/config.toml").exists()) + self.assertFalse((ROOT / "target/package-registry").exists()) + + def test_record_is_indexed_and_source_bound(self) -> None: + prep = load_json(PREP) + readme = read(VALIDATION_README) + record = normalized(RECORD) + + self.assertIn(RECORD.name, readme) + self.assertIn("package publication candidate activation evidence validation", readme) + self.assertEqual( + "docs/validation/" + "milestone-e-package-publication-candidate-activation-evidence-validation-2026-06-22.md", + prep["follow_up_records"]["package_candidate_activation_evidence"], + ) + self.assertIn(f"Validated source HEAD before this record: `{SOURCE_SHORT}`", read(RECORD)) + self.assertIn(f"Candidate activation evidence source commit: `{SOURCE_COMMIT}`", record) + self.assertIn(f"Candidate activation evidence source tree: `{SOURCE_TREE}`", record) + self.assertEqual(SOURCE_COMMIT, git("rev-parse", SOURCE_SHORT)) + self.assertEqual(SOURCE_TREE, git("rev-parse", f"{SOURCE_SHORT}^{{tree}}")) + + def test_docs_reference_candidate_evidence_and_retained_blockers(self) -> None: + for path in (PREP_SCOPE, ROADMAP, EXECUTION_STATUS, VALIDATION_README): + doc = normalized(path) + + self.assertIn(RECORD.name, doc, str(path)) + self.assertIn("candidate activation evidence", doc.lower(), str(path)) + self.assertIn("package publication remains blocked", doc, str(path)) + self.assertIn("public installation remains blocked", doc, str(path)) + + def test_make_and_ci_run_evidence_after_decision_record(self) -> None: + make_block = target_block("milestone-e-prep") + ci = read(CI_WORKFLOW) + decision_guard = "test_milestone_e_package_publication_approval_decision_record.py" + evidence_guard = "test_milestone_e_package_publication_candidate_activation_evidence.py" + public_facing_guard = "test_milestone_e_public_facing_readiness_ledger.py" + + for text, prefix in ((make_block, "$(PYTHON) .github/scripts/"), (ci, "python3 .github/scripts/")): + self.assertIn(prefix + evidence_guard, text) + self.assertEqual(1, text.count(prefix + evidence_guard)) + self.assertLess(text.index(prefix + decision_guard), text.index(prefix + evidence_guard)) + self.assertLess(text.index(prefix + evidence_guard), text.index(prefix + public_facing_guard)) + + def test_record_avoids_scope_expansion_language_or_private_paths(self) -> None: + lower = normalized(RECORD).lower() + raw = read(RECORD) + + for phrase in FORBIDDEN_SCOPE_EXPANSION: + self.assertNotIn(phrase, lower) + self.assertNotIn("/Users/", raw) + self.assertNotIn("/private/tmp", raw) + self.assertNotIn("/private/var", raw) + self.assertNotIn("/var/folders", raw) + self.assertNotIn("saumildiwaker", raw) + self.assertNotIn("Desktop/Stuff", raw) + self.assertNotIn("project/repo/ethos", raw) + self.assertNotIn("docs/.roadmap.md.swp", raw) + self.assertNotIn("web/", raw) + + +if __name__ == "__main__": + unittest.main() diff --git a/.github/scripts/test_milestone_e_package_publication_pdfium_boundary.py b/.github/scripts/test_milestone_e_package_publication_pdfium_boundary.py index 64cf2f8..b4de6e0 100644 --- a/.github/scripts/test_milestone_e_package_publication_pdfium_boundary.py +++ b/.github/scripts/test_milestone_e_package_publication_pdfium_boundary.py @@ -145,6 +145,7 @@ def test_ethos_pdf_package_inputs_do_not_bundle_pdfium_binaries(self) -> None: "NOTICE.md", "README.md", "assets/README.md", + "assets/ethos-deterministic-v1.json", "assets/font-substitution-table.json", "src/lib.rs", ], diff --git a/.github/scripts/test_milestone_e_prep_guard_sequence_index.py b/.github/scripts/test_milestone_e_prep_guard_sequence_index.py index fcdfc97..e6cdf83 100644 --- a/.github/scripts/test_milestone_e_prep_guard_sequence_index.py +++ b/.github/scripts/test_milestone_e_prep_guard_sequence_index.py @@ -114,6 +114,7 @@ "$(PYTHON) .github/scripts/test_milestone_e_package_publication_public_installation_wording_review.py", "$(PYTHON) .github/scripts/test_milestone_e_package_publication_approval_decision_template.py", "$(PYTHON) .github/scripts/test_milestone_e_package_publication_approval_decision_record.py", + "$(PYTHON) .github/scripts/test_milestone_e_package_publication_candidate_activation_evidence.py", "$(PYTHON) .github/scripts/test_milestone_e_public_facing_readiness_ledger.py", "$(PYTHON) .github/scripts/test_milestone_e_public_beta_current_main_refresh_prep.py", "$(PYTHON) .github/scripts/test_milestone_e_public_beta_current_main_source_only_approval.py", diff --git a/.github/scripts/test_milestone_e_prep_scope.py b/.github/scripts/test_milestone_e_prep_scope.py index 36bcd36..fa3b4ad 100644 --- a/.github/scripts/test_milestone_e_prep_scope.py +++ b/.github/scripts/test_milestone_e_prep_scope.py @@ -413,6 +413,7 @@ def test_make_target_is_narrow_and_guarded(self) -> None: "$(PYTHON) .github/scripts/test_milestone_e_package_publication_public_installation_wording_review.py", "$(PYTHON) .github/scripts/test_milestone_e_package_publication_approval_decision_template.py", "$(PYTHON) .github/scripts/test_milestone_e_package_publication_approval_decision_record.py", + "$(PYTHON) .github/scripts/test_milestone_e_package_publication_candidate_activation_evidence.py", "$(PYTHON) .github/scripts/test_milestone_e_public_facing_readiness_ledger.py", "$(PYTHON) .github/scripts/test_milestone_e_public_beta_current_main_refresh_prep.py", "$(PYTHON) .github/scripts/test_milestone_e_public_beta_current_main_source_only_approval.py", diff --git a/.github/scripts/test_milestone_e_validation_record_index.py b/.github/scripts/test_milestone_e_validation_record_index.py index 67db1fd..0357441 100644 --- a/.github/scripts/test_milestone_e_validation_record_index.py +++ b/.github/scripts/test_milestone_e_validation_record_index.py @@ -290,6 +290,10 @@ class RecordCoverage: "milestone-e-package-publication-approval-decision-validation-2026-06-21.md", "test_milestone_e_package_publication_approval_decision_record.py", ), + RecordCoverage( + "milestone-e-package-publication-candidate-activation-evidence-validation-2026-06-22.md", + "test_milestone_e_package_publication_candidate_activation_evidence.py", + ), RecordCoverage( "milestone-e-public-facing-readiness-ledger-validation-2026-06-21.md", "test_milestone_e_public_facing_readiness_ledger.py", @@ -425,6 +429,9 @@ def test_index_guards_run_after_row_and_schema_records(self) -> None: package_approval_decision_record_guard = ( "test_milestone_e_package_publication_approval_decision_record.py" ) + package_candidate_activation_evidence_guard = ( + "test_milestone_e_package_publication_candidate_activation_evidence.py" + ) readiness_guard = "test_milestone_e_public_facing_readiness_ledger.py" beta_refresh_guard = "test_milestone_e_public_beta_current_main_refresh_prep.py" command_guard = "test_milestone_e_validation_command_index_validation_record.py" @@ -517,6 +524,10 @@ def test_index_guards_run_after_row_and_schema_records(self) -> None: ) self.assertLess( text.index(prefix + package_approval_decision_record_guard), + text.index(prefix + package_candidate_activation_evidence_guard), + ) + self.assertLess( + text.index(prefix + package_candidate_activation_evidence_guard), text.index(prefix + readiness_guard), ) self.assertLess(text.index(prefix + readiness_guard), text.index(prefix + beta_refresh_guard)) @@ -546,6 +557,14 @@ def test_index_guards_run_after_row_and_schema_records(self) -> None: text.index(prefix + package_approval_decision_record_guard), text.index(prefix + index_guard), ) + self.assertLess( + text.index(prefix + package_candidate_activation_evidence_guard), + text.index(prefix + command_guard), + ) + self.assertLess( + text.index(prefix + package_candidate_activation_evidence_guard), + text.index(prefix + index_guard), + ) self.assertLess( text.index(prefix + package_registry_evidence_review_guard), text.index(prefix + command_guard), diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d1e3349..b7e2672 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -242,6 +242,8 @@ jobs: run: python3 .github/scripts/test_milestone_e_package_publication_approval_decision_template.py - name: Milestone E package publication approval decision record tests run: python3 .github/scripts/test_milestone_e_package_publication_approval_decision_record.py + - name: Milestone E package publication candidate activation evidence tests + run: python3 .github/scripts/test_milestone_e_package_publication_candidate_activation_evidence.py - name: Milestone E public-facing readiness ledger tests run: python3 .github/scripts/test_milestone_e_public_facing_readiness_ledger.py - name: Milestone E public beta current-main refresh prep tests diff --git a/Makefile b/Makefile index 098aefd..c2a7128 100644 --- a/Makefile +++ b/Makefile @@ -228,6 +228,7 @@ milestone-e-prep: $(PYTHON) .github/scripts/test_milestone_e_package_publication_public_installation_wording_review.py $(PYTHON) .github/scripts/test_milestone_e_package_publication_approval_decision_template.py $(PYTHON) .github/scripts/test_milestone_e_package_publication_approval_decision_record.py + $(PYTHON) .github/scripts/test_milestone_e_package_publication_candidate_activation_evidence.py $(PYTHON) .github/scripts/test_milestone_e_public_facing_readiness_ledger.py $(PYTHON) .github/scripts/test_milestone_e_public_beta_current_main_refresh_prep.py $(PYTHON) .github/scripts/test_milestone_e_public_beta_current_main_source_only_approval.py diff --git a/crates/ethos-pdf/assets/README.md b/crates/ethos-pdf/assets/README.md index 6794a95..01eff60 100644 --- a/crates/ethos-pdf/assets/README.md +++ b/crates/ethos-pdf/assets/README.md @@ -2,6 +2,8 @@ - `font-substitution-table.json` — versioned missing-font mapping; sha256 goes into `profiles/ethos-deterministic-v1.json` → `font_policy.substitution_table.sha256`. +- `ethos-deterministic-v1.json` — crate-local copy of the source-tree deterministic profile used + by packaged `ethos-pdf` builds. Keep byte-identical to `profiles/ethos-deterministic-v1.json`. - `fonts/liberation/` — bundled Liberation family (SIL OFL 1.1, ~4 MB); bundle sha256 goes into `font_policy.fallback_bundle.sha256`; OFL notice goes into `NOTICE`. diff --git a/crates/ethos-pdf/assets/ethos-deterministic-v1.json b/crates/ethos-pdf/assets/ethos-deterministic-v1.json new file mode 100644 index 0000000..c1d81cd --- /dev/null +++ b/crates/ethos-pdf/assets/ethos-deterministic-v1.json @@ -0,0 +1,94 @@ +{ + "profile_id": "ethos-deterministic-v1", + "profile_version": "1.0.0", + "schema_version": "1.0.0", + "c14n": { + "version": "c14n-v1", + "spec": "docs/determinism-contract.md" + }, + "quantization": { + "quantum_per_point": 100, + "rounding": "half-away-from-zero", + "stage": "extraction" + }, + "id_scheme": { + "version": "ids-v1" + }, + "font_policy": { + "adr": "ADR-0003", + "substitution_table": { + "path": "crates/ethos-pdf/assets/font-substitution-table.json", + "sha256": "f7ce1a57f66839cfe225f3b11fcc51fb8546423175f01d427061f7a2fbcaaa99" + }, + "fallback_bundle": { + "name": "liberation", + "license": "OFL-1.1", + "sha256": null + }, + "system_font_fallback": "disabled" + }, + "backend": { + "id": "pdfium", + "phase": 1, + "version": "chromium/7881", + "upstream_version": "PDFium 151.0.7881.0", + "v8": "disabled", + "xfa": "disabled", + "distribution": { + "source": "bblanchon/pdfium-binaries", + "release_url": "https://github.com/bblanchon/pdfium-binaries/releases/tag/chromium/7881", + "published_at": "2026-06-08T17:07:25Z", + "attestation": { + "name": "pdfium-attestation.json", + "sha256": "24dec7cd76acb81106a0c29b908cceceef8215b050f6ff6ffbf875465811ef60" + } + }, + "build_flags": { + "is_component_build": false, + "is_debug": false, + "pdf_enable_v8": false, + "pdf_enable_xfa": false, + "pdf_is_standalone": true, + "pdf_use_partition_alloc": false + }, + "platform_hashes": { + "macos-arm64": "52e94ca5aa8847934330daf3f8150c190682c5ca93831468794f8b90d4392e40", + "linux-x64": "1470e21b8b4a3b4ad7f85684e2da11d94f3b69a86d81dee11b9b6709d927ac1d", + "windows-x64": "73cc0de638ac2095e7445bf56a38200a5b7c7ca0e9f4ba144598f2457377ac08" + }, + "platform_artifacts": { + "macos-arm64": { + "name": "pdfium-mac-arm64.tgz", + "target_os": "mac", + "target_cpu": "arm64", + "runtime_library_path": "lib/libpdfium.dylib", + "runtime_library_sha256": "1bc45b15466b34cef96641ce25c77a876e70010c6b114f909dda2f5325fc5bd7" + }, + "linux-x64": { + "name": "pdfium-linux-x64.tgz", + "target_os": "linux", + "target_cpu": "x64", + "runtime_library_path": "lib/libpdfium.so", + "runtime_library_sha256": "f728930966f503652b92acc89b9374a2eeca00ce42e26dccd3e4b5c5161b2d64" + }, + "windows-x64": { + "name": "pdfium-win-x64.tgz", + "target_os": "win", + "target_cpu": "x64", + "runtime_library_path": "bin/pdfium.dll", + "runtime_library_sha256": "79d4676b656cfb1abcea88f9ade3b4b0826c5200382db5f4ec72a636c598c118" + } + }, + "profile_doc": "docs/pdfium-profile.md" + }, + "warning_policy": { + "messages": "fixed-templates", + "ordering": "canonical-traversal" + }, + "config_hash_inputs": [ + "pages" + ], + "canonical_exclusions": [ + "$.diagnostics" + ] +} diff --git a/crates/ethos-pdf/src/lib.rs b/crates/ethos-pdf/src/lib.rs index 46331f2..49c9c0d 100644 --- a/crates/ethos-pdf/src/lib.rs +++ b/crates/ethos-pdf/src/lib.rs @@ -57,10 +57,7 @@ pub const PDFIUM_ARTIFACT_PATH_ENV: &str = "ETHOS_PDFIUM_ARTIFACT_PATH"; pub const QUANTUM_PER_POINT: u32 = 100; const ORIGIN_LOCATOR_POLICY: &str = "origin-run-locator-v1"; -const DETERMINISTIC_PROFILE_JSON: &str = include_str!(concat!( - env!("CARGO_MANIFEST_DIR"), - "/../../profiles/ethos-deterministic-v1.json" -)); +const DETERMINISTIC_PROFILE_JSON: &str = include_str!("../assets/ethos-deterministic-v1.json"); const FONT_SUBSTITUTION_TABLE_JSON: &str = include_str!("../assets/font-substitution-table.json"); /// PDFium has process-global library state; serialize init/load/destroy for now. diff --git a/docs/execution-status.md b/docs/execution-status.md index 01d146b..07def6a 100644 --- a/docs/execution-status.md +++ b/docs/execution-status.md @@ -211,6 +211,8 @@ The package publication approval decision template in `docs/validation/milestone The package publication approval decision in `docs/validation/milestone-e-package-publication-approval-decision-validation-2026-06-21.md` rejects the current package-publication request against source commit `fdbd5b7` / tree `4a7bf5cda2c779e41a04c3feb691a12fec1e5c8d` because required activation evidence is absent. Package publication remains blocked, and public installation remains blocked. +The package publication candidate activation evidence in `docs/validation/milestone-e-package-publication-candidate-activation-evidence-validation-2026-06-22.md` validates a temporary non-public package activation workspace against source commit `6cf211c` / tree `ae76bc588b64dc1e8087d9096d52545a3560c2c0`. Source Cargo manifests remain blocked, package publication remains blocked, and public installation remains blocked. + | Work item | Current status | Remaining blocker | | --- | --- | --- | | PDFium Phase 1 profile | Landed: pinned profile, V8/XFA-disabled state, platform hashes, runtime library hashes, and provenance are recorded | Phase 2 project-maintained builds still block Public Beta | diff --git a/docs/milestone-e-package-publication-approval-prep.json b/docs/milestone-e-package-publication-approval-prep.json index e2a155c..9e6420c 100644 --- a/docs/milestone-e-package-publication-approval-prep.json +++ b/docs/milestone-e-package-publication-approval-prep.json @@ -86,7 +86,8 @@ "package_registry_assembly_evidence_review": "docs/validation/milestone-e-package-publication-registry-assembly-evidence-review-validation-2026-06-21.md", "package_public_installation_wording_review": "docs/validation/milestone-e-package-publication-public-installation-wording-review-validation-2026-06-21.md", "package_approval_decision_template": "docs/validation/milestone-e-package-publication-approval-decision-template-validation-2026-06-21.md", - "package_approval_decision_record": "docs/validation/milestone-e-package-publication-approval-decision-validation-2026-06-21.md" + "package_approval_decision_record": "docs/validation/milestone-e-package-publication-approval-decision-validation-2026-06-21.md", + "package_candidate_activation_evidence": "docs/validation/milestone-e-package-publication-candidate-activation-evidence-validation-2026-06-22.md" }, "publication_approval_decision_inputs": { "decision_status": "not_approved_pending_exact_decision", diff --git a/docs/milestone-e-prep-scope.md b/docs/milestone-e-prep-scope.md index 4ece04a..b336f7a 100644 --- a/docs/milestone-e-prep-scope.md +++ b/docs/milestone-e-prep-scope.md @@ -148,6 +148,11 @@ The package publication approval decision is recorded in It rejects the current package-publication request for source commit `fdbd5b7` / tree `4a7bf5cda2c779e41a04c3feb691a12fec1e5c8d` because required activation evidence is absent; package publication remains blocked, and public installation remains blocked. +The package publication candidate activation evidence is recorded in +`docs/validation/milestone-e-package-publication-candidate-activation-evidence-validation-2026-06-22.md`. +It validates a temporary non-public package activation workspace for source commit `6cf211c` / +tree `ae76bc588b64dc1e8087d9096d52545a3560c2c0`; source Cargo manifests remain blocked, package +publication remains blocked, and public installation remains blocked. The metadata-readiness follow-up record under `docs/validation/` covers README, NOTICE, manifest metadata, and include-list readiness for `ethos-core`, `ethos-verify`, and `ethos-pdf` only. `ethos-doc` and `ethos-rag` remain reserved placeholders without in-tree package manifests, and diff --git a/docs/roadmap.md b/docs/roadmap.md index 911f24c..4f5e2d5 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -200,6 +200,12 @@ The package publication approval decision is recorded in for source commit `fdbd5b7` / tree `4a7bf5cda2c779e41a04c3feb691a12fec1e5c8d`. The current package-publication request is rejected because required activation evidence is absent; package publication remains blocked and public installation remains blocked. +The package publication candidate activation evidence is recorded in +[`docs/validation/milestone-e-package-publication-candidate-activation-evidence-validation-2026-06-22.md`](validation/milestone-e-package-publication-candidate-activation-evidence-validation-2026-06-22.md) +for source commit `6cf211c` / tree `ae76bc588b64dc1e8087d9096d52545a3560c2c0`. A temporary +non-public package activation workspace validates the candidate package-name and dependency shape +while source Cargo manifests remain blocked, package publication remains blocked, and public +installation remains blocked. This prep only identifies tracked trust-loop fixture candidates and guard wiring for internal continuation; blocked-output alignment keeps the current trust-loop protocol, rehearsal/evidence matrix, blocker ledger, and matching schemas on the same explicit blockers, while evidence-lane diff --git a/docs/validation/README.md b/docs/validation/README.md index 170bcea..64c368c 100644 --- a/docs/validation/README.md +++ b/docs/validation/README.md @@ -371,6 +371,11 @@ recording the exact current-main source candidate and required follow-up evidenc record binds the rejected decision to source commit `fdbd5b7` / tree `4a7bf5cda2c779e41a04c3feb691a12fec1e5c8d` while package publication remains blocked and public installation remains blocked. +- `milestone-e-package-publication-candidate-activation-evidence-validation-2026-06-22.md` - + package publication candidate activation evidence validation for a temporary non-public package + activation workspace; the record binds the evidence to source commit `6cf211c` / tree + `ae76bc588b64dc1e8087d9096d52545a3560c2c0` while package publication remains blocked and + public installation remains blocked. - `milestone-e-public-facing-readiness-ledger-validation-2026-06-21.md` - public-facing readiness ledger validation recorded `docs/milestone-e-public-facing-readiness-ledger.json` as a current-main refresh candidate and package-publication gap-retention artifact; current main diff --git a/docs/validation/milestone-e-package-publication-candidate-activation-evidence-validation-2026-06-22.md b/docs/validation/milestone-e-package-publication-candidate-activation-evidence-validation-2026-06-22.md new file mode 100644 index 0000000..649636e --- /dev/null +++ b/docs/validation/milestone-e-package-publication-candidate-activation-evidence-validation-2026-06-22.md @@ -0,0 +1,110 @@ +# Milestone E Package Publication Candidate Activation Evidence Validation - 2026-06-22 + +## Purpose + +Record repeatable candidate package activation evidence for the package-publication lane after the +current package-publication approval request was rejected. + +This record validates a temporary, non-public package activation workspace. It does not change the +source Cargo manifests, select a package publication version, create package tags, approve package +publication, approve public installation, approve public installation wording, or publish any +package. + +## Status + +Status: **pass for package publication candidate activation evidence with publication blocked**. + +Decision: candidate activation evidence recorded; package publication remains blocked. + +Ethos remains source-only pre-alpha outside the approved GitHub source-repository public beta +surface. Package publication remains blocked. Public installation remains blocked. + +## Subject + +- Repository: `docushell/ethos` +- Validated source HEAD before this record: `6cf211c` +- Candidate activation evidence source commit: `6cf211cfae82c8ba7d6454a71e0922bd95a01f28` +- Candidate activation evidence source tree: `ae76bc588b64dc1e8087d9096d52545a3560c2c0` +- Lane: package publication +- Candidate packages: `ethos-doc-core`, `ethos-verify`, and `ethos-pdf` +- Candidate package version: `0.1.0` +- Evidence command: `.github/scripts/package_publication_candidate_activation.py --json` + +## Candidate Activation Shape Validated + +- The temporary workspace changes `crates/ethos-core` package name from `ethos-core` to + `ethos-doc-core`. +- The temporary workspace sets `[lib] name = "ethos_core"` for the renamed core package so Rust + imports continue to use `ethos_core`. +- The temporary workspace keeps dependency key `ethos-core` while resolving package + `ethos-doc-core`. +- `ethos-verify` retains core features `grounding` and `verify-types`. +- `ethos-pdf` retains core feature `full`. +- The temporary workspace assembles `ethos-doc-core-0.1.0.crate`, + `ethos-verify-0.1.0.crate`, and `ethos-pdf-0.1.0.crate`. +- The temporary workspace uses a Cargo vendor source as a registry-equivalent source override for + the candidate core package before packaging dependent candidates. +- An unpacked registry-equivalent consumer resolves `ethos-doc-core`, `ethos-verify`, and + `ethos-pdf` and passes `cargo check --locked --offline`. + +## Source Packaging Fix + +`ethos-pdf` now carries `crates/ethos-pdf/assets/ethos-deterministic-v1.json` and includes that +crate-local profile copy. The crate-local profile is kept byte-identical to +`profiles/ethos-deterministic-v1.json`, which makes the packaged `ethos-pdf` crate self-contained +for this profile include while preserving the canonical source-tree profile path. + +## Non-Approvals + +- This evidence does not approve package publication. +- This evidence does not approve public installation. +- This evidence does not approve public installation wording. +- This evidence does not select a package publication version. +- This evidence does not create package tags. +- This evidence does not change source Cargo manifests. +- This evidence does not create a source-tree package registry. +- This evidence does not approve real-version cargo publish. +- This evidence does not approve hosted surfaces. +- This evidence does not approve production positioning. +- This evidence does not approve public benchmark reports. +- This evidence does not approve public benchmark claims. + +## Retained Blockers + +- exact package publication approval remains required +- exact package tag creation remains blocked +- package dependency manifest activation remains blocked for source manifests +- public installation wording approval remains blocked +- public installation remains blocked +- package publication remains blocked +- real-version cargo publish remains blocked +- hosted surfaces remain blocked +- production positioning remains blocked +- Public reports remain blocked +- Public result wording remains blocked + +## Commands + +```sh +python3 .github/scripts/package_publication_candidate_activation.py --json +python3 .github/scripts/test_milestone_e_package_publication_candidate_activation_evidence.py +python3 .github/scripts/test_milestone_e_package_publication_approval_prep.py +python3 .github/scripts/test_public_surface_posture.py +python3 .github/scripts/claims_gate.py +cargo build --locked -p ethos-cli +make milestone-e-prep PYTHON=/bin/python +git diff --check +``` + +## Result + +```text +Package publication candidate activation evidence validation passed +Temporary candidate workspace assembled ethos-doc-core, ethos-verify, and ethos-pdf package artifacts +Registry-equivalent consumer check passed +Source Cargo manifests remained blocked and unchanged for publication +Package publication and public installation remained blocked +Public-surface posture and claims gates passed +Milestone E prep target passed +git diff --check passed +``` diff --git a/schemas/ethos-milestone-e-package-publication-approval-prep.schema.json b/schemas/ethos-milestone-e-package-publication-approval-prep.schema.json index 6b7cb2f..e97052f 100644 --- a/schemas/ethos-milestone-e-package-publication-approval-prep.schema.json +++ b/schemas/ethos-milestone-e-package-publication-approval-prep.schema.json @@ -294,7 +294,8 @@ "package_registry_assembly_evidence_review", "package_public_installation_wording_review", "package_approval_decision_template", - "package_approval_decision_record" + "package_approval_decision_record", + "package_candidate_activation_evidence" ], "additionalProperties": false, "properties": { @@ -357,6 +358,9 @@ }, "package_approval_decision_record": { "const": "docs/validation/milestone-e-package-publication-approval-decision-validation-2026-06-21.md" + }, + "package_candidate_activation_evidence": { + "const": "docs/validation/milestone-e-package-publication-candidate-activation-evidence-validation-2026-06-22.md" } } }, From fd0b93edb34bbc0a55ab56454dbed1689a355741 Mon Sep 17 00:00:00 2001 From: docushell-admin Date: Mon, 22 Jun 2026 00:28:42 +0530 Subject: [PATCH 2/4] Fix candidate activation private path audit Signed-off-by: docushell-admin --- .github/scripts/package_publication_candidate_activation.py | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/scripts/package_publication_candidate_activation.py b/.github/scripts/package_publication_candidate_activation.py index 19f5355..c932e04 100644 --- a/.github/scripts/package_publication_candidate_activation.py +++ b/.github/scripts/package_publication_candidate_activation.py @@ -39,7 +39,6 @@ ".mypy_cache", "web", ".roadmap.md.swp", - "docs/.roadmap.md.swp", } From ffa24cbef4d0909e6cac3ccb147a9460a89e04e7 Mon Sep 17 00:00:00 2001 From: docushell-admin Date: Mon, 22 Jun 2026 00:42:03 +0530 Subject: [PATCH 3/4] Stabilize package candidate activation lockfiles Signed-off-by: docushell-admin --- ...ackage_publication_candidate_activation.py | 127 +++++++++++++++++- ...blication_candidate_activation_evidence.py | 14 +- 2 files changed, 135 insertions(+), 6 deletions(-) diff --git a/.github/scripts/package_publication_candidate_activation.py b/.github/scripts/package_publication_candidate_activation.py index c932e04..1dfdb0b 100644 --- a/.github/scripts/package_publication_candidate_activation.py +++ b/.github/scripts/package_publication_candidate_activation.py @@ -31,6 +31,13 @@ VERSION = "0.1.0" CORE_PACKAGE = "ethos-doc-core" CANDIDATE_PACKAGES = (CORE_PACKAGE, "ethos-verify", "ethos-pdf") +CONSUMER_PACKAGE = "ethos-package-candidate-consumer" +CANDIDATE_NORMAL_DEPENDENCIES = { + CORE_PACKAGE: ["serde", "serde_json", "sha2", "thiserror"], + "ethos-pdf": [CORE_PACKAGE, "serde", "serde_json"], + "ethos-verify": [CORE_PACKAGE, "serde"], + CONSUMER_PACKAGE: [CORE_PACKAGE, "ethos-pdf", "ethos-verify"], +} IGNORE_NAMES = { ".git", "target", @@ -102,6 +109,14 @@ def replace_once(path: Path, old: str, new: str) -> None: path.write_text(text.replace(old, new, 1), encoding="utf-8") +def rewrite_candidate_lockfile(workspace: Path) -> None: + lockfile = workspace / "Cargo.lock" + text = lockfile.read_text(encoding="utf-8") + if 'name = "ethos-core"' not in text: + raise RuntimeError("expected ethos-core package entry in candidate Cargo.lock") + lockfile.write_text(text.replace("ethos-core", CORE_PACKAGE), encoding="utf-8") + + def materialize_candidate_workspace(workspace: Path) -> None: shutil.copytree(ROOT, workspace, ignore=should_ignore) @@ -123,6 +138,7 @@ def materialize_candidate_workspace(workspace: Path) -> None: "authors.workspace = true\n\n[package.metadata.ethos_publication]", 'authors.workspace = true\n\n[lib]\nname = "ethos_core"\n\n[package.metadata.ethos_publication]', ) + rewrite_candidate_lockfile(workspace) def sha256(path: Path) -> str: @@ -209,7 +225,7 @@ def write_consumer(workspace: Path, unpacked: Path) -> Path: (consumer / "src").mkdir(parents=True, exist_ok=True) (consumer / "Cargo.toml").write_text( f"""[package] -name = "ethos-package-candidate-consumer" +name = "{CONSUMER_PACKAGE}" version = "0.0.0" edition = "2021" publish = false @@ -243,6 +259,112 @@ def write_consumer(workspace: Path, unpacked: Path) -> Path: return consumer +def dependency_name(dependency: str) -> str: + return dependency.split(" ", 1)[0] + + +def lock_dependency_list(dependencies: list[str]) -> str: + lines = ["dependencies = ["] + lines.extend(f" {json.dumps(dependency)}," for dependency in dependencies) + lines.append("]") + return "\n".join(lines) + + +def lock_package_entry(package: dict[str, object]) -> str: + lines = [ + "[[package]]", + f"name = {json.dumps(package['name'])}", + f"version = {json.dumps(package['version'])}", + ] + if "source" in package: + lines.append(f"source = {json.dumps(package['source'])}") + if "checksum" in package: + lines.append(f"checksum = {json.dumps(package['checksum'])}") + dependencies = package.get("dependencies") + if dependencies: + lines.append(lock_dependency_list(dependencies)) + return "\n".join(lines) + + +def parse_lock_packages(lockfile: Path) -> list[dict[str, object]]: + packages: list[dict[str, object]] = [] + for block in lockfile.read_text(encoding="utf-8").split("[[package]]\n")[1:]: + package: dict[str, object] = {} + dependencies: list[str] = [] + in_dependencies = False + for raw_line in block.splitlines(): + line = raw_line.strip() + if not line: + continue + if in_dependencies: + if line == "]": + in_dependencies = False + else: + dependencies.append(json.loads(line.rstrip(","))) + continue + if line == "dependencies = [": + in_dependencies = True + continue + if " = " not in line: + continue + key, value = line.split(" = ", 1) + if key in {"name", "version", "source", "checksum"}: + package[key] = json.loads(value) + if dependencies: + package["dependencies"] = dependencies + if package: + packages.append(package) + return packages + + +def write_consumer_lockfile(workspace: Path, consumer: Path) -> None: + packages = parse_lock_packages(workspace / "Cargo.lock") + by_name = {package["name"]: package for package in packages} + + selected: set[str] = set() + stack = [CONSUMER_PACKAGE] + while stack: + package_name = stack.pop() + if package_name in selected: + continue + selected.add(package_name) + if package_name in CANDIDATE_NORMAL_DEPENDENCIES: + dependencies = CANDIDATE_NORMAL_DEPENDENCIES[package_name] + else: + if package_name not in by_name: + raise RuntimeError(f"missing locked package for consumer dependency: {package_name}") + dependencies = by_name[package_name].get("dependencies", []) + stack.extend(dependency_name(dependency) for dependency in dependencies) + + entries: list[dict[str, object]] = [] + for package_name in sorted(selected): + if package_name == CONSUMER_PACKAGE: + entries.append( + { + "name": CONSUMER_PACKAGE, + "version": "0.0.0", + "dependencies": CANDIDATE_NORMAL_DEPENDENCIES[CONSUMER_PACKAGE], + } + ) + elif package_name in CANDIDATE_NORMAL_DEPENDENCIES: + entries.append( + { + "name": package_name, + "version": VERSION, + "dependencies": CANDIDATE_NORMAL_DEPENDENCIES[package_name], + } + ) + else: + entries.append(by_name[package_name]) + + text = "# This file is automatically @generated by Cargo.\n" + text += "# It is not intended for manual editing.\n" + text += "version = 4\n\n" + text += "\n\n".join(lock_package_entry(package) for package in entries) + text += "\n" + (consumer / "Cargo.lock").write_text(text, encoding="utf-8") + + def validate_packaged_manifests(artifacts: list[dict[str, str]]) -> dict[str, object]: manifests = {artifact["package"]: artifact["manifest"] for artifact in artifacts} core = manifests[CORE_PACKAGE] @@ -284,7 +406,6 @@ def source_manifests_are_still_blocked() -> bool: def run_candidate_activation(workspace: Path) -> dict[str, object]: commands: list[dict[str, object]] = [] materialize_candidate_workspace(workspace) - run(["cargo", "generate-lockfile", "--offline"], workspace, commands) run(["cargo", "check", "--locked", "--offline", "-p", "ethos-verify"], workspace, commands) run(["cargo", "check", "--locked", "--offline", "-p", "ethos-pdf"], workspace, commands) core_artifact = package_candidate(workspace, CORE_PACKAGE, commands) @@ -296,7 +417,7 @@ def run_candidate_activation(workspace: Path) -> dict[str, object]: checks = validate_packaged_manifests(artifacts) unpacked = extract_crates(workspace, artifacts) consumer = write_consumer(workspace, unpacked) - run(["cargo", "generate-lockfile", "--offline"], consumer, commands) + write_consumer_lockfile(workspace, consumer) run(["cargo", "check", "--locked", "--offline"], consumer, commands) return { diff --git a/.github/scripts/test_milestone_e_package_publication_candidate_activation_evidence.py b/.github/scripts/test_milestone_e_package_publication_candidate_activation_evidence.py index eba064b..08aaa8c 100644 --- a/.github/scripts/test_milestone_e_package_publication_candidate_activation_evidence.py +++ b/.github/scripts/test_milestone_e_package_publication_candidate_activation_evidence.py @@ -95,13 +95,20 @@ def git(*args: str) -> str: def run_candidate_activation() -> dict: - output = subprocess.check_output( + result = subprocess.run( ["python3", str(SCRIPT), "--json"], cwd=ROOT, - encoding="utf-8", + stdout=subprocess.PIPE, stderr=subprocess.PIPE, + encoding="utf-8", ) - return json.loads(output) + if result.returncode != 0: + raise AssertionError( + "candidate activation script failed\n" + f"stdout:\n{result.stdout}\n" + f"stderr:\n{result.stderr}" + ) + return json.loads(result.stdout) class MilestoneEPackagePublicationCandidateActivationEvidenceTests(unittest.TestCase): @@ -120,6 +127,7 @@ def test_candidate_activation_script_passes_with_registry_equivalent_consumer(se self.assertTrue(result["source_manifests_remain_blocked"]) self.assertFalse(result["package_publication_approved"]) self.assertFalse(result["public_installation_approved"]) + self.assertNotIn("cargo generate-lockfile --offline", commands) self.assertIn("cargo vendor --locked --offline target/package-candidate-vendor", commands) self.assertIn("cargo package --locked --offline -p ethos-doc-core --allow-dirty --no-verify", commands) self.assertIn("cargo package --locked --offline -p ethos-verify --allow-dirty --no-verify", commands) From 699a1a42770c135f70ed4a7eef07103014c6a61b Mon Sep 17 00:00:00 2001 From: docushell-admin Date: Mon, 22 Jun 2026 00:52:03 +0530 Subject: [PATCH 4/4] Remove candidate activation vendor dependency Signed-off-by: docushell-admin --- ...ackage_publication_candidate_activation.py | 215 +++++++++++++++--- ...blication_candidate_activation_evidence.py | 8 +- ...tivation-evidence-validation-2026-06-22.md | 5 +- 3 files changed, 187 insertions(+), 41 deletions(-) diff --git a/.github/scripts/package_publication_candidate_activation.py b/.github/scripts/package_publication_candidate_activation.py index 1dfdb0b..112803f 100644 --- a/.github/scripts/package_publication_candidate_activation.py +++ b/.github/scripts/package_publication_candidate_activation.py @@ -19,6 +19,7 @@ import argparse import hashlib +import io import json import shutil import subprocess @@ -102,6 +103,17 @@ def run_output(command: list[str], cwd: Path, commands: list[dict[str, object]]) return result.stdout +def record_command(command: str, commands: list[dict[str, object]], stdout: str = "") -> None: + commands.append( + { + "command": command, + "returncode": 0, + "stdout_tail": stdout[-1200:], + "stderr_tail": "", + } + ) + + def replace_once(path: Path, old: str, new: str) -> None: text = path.read_text(encoding="utf-8") if old not in text: @@ -176,6 +188,172 @@ def package_candidate(workspace: Path, package: str, commands: list[dict[str, ob } +def generated_manifest(package: str) -> str: + if package == "ethos-verify": + return f"""[package] +edition = "2021" +rust-version = "1.87" +name = "ethos-verify" +version = "{VERSION}" +authors = ["Ethos maintainers"] +include = [ + "Cargo.toml", + "README.md", + "NOTICE.md", + "src/**", +] +publish = false +description = "Parser-agnostic citation evidence verification over GroundingSource (alpha lands Milestone B)" +readme = "README.md" +keywords = [ + "ethos", + "citations", + "evidence", +] +license = "Apache-2.0" +repository = "https://github.com/docushell/ethos" + +[package.metadata.ethos_publication] +publication_status = "blocked" +reserved_crates_io_name = "ethos-verify" +reserved_crates_io_version = "0.0.0-reserved.0" + +[lib] +name = "ethos_verify" +path = "src/lib.rs" + +[dependencies.ethos-core] +version = "{VERSION}" +features = [ + "grounding", + "verify-types", +] +default-features = false +package = "{CORE_PACKAGE}" + +[dependencies.serde] +version = "1" +features = ["derive"] +""" + + if package == "ethos-pdf": + return f"""[package] +edition = "2021" +rust-version = "1.87" +name = "ethos-pdf" +version = "{VERSION}" +authors = ["Ethos maintainers"] +include = [ + "Cargo.toml", + "README.md", + "NOTICE.md", + "assets/**", + "src/**", +] +publish = false +description = "PDFium backend behind EthosPdfBackend - quantize-at-extraction lives here (WS-ENGINE, Milestone A)" +readme = "README.md" +keywords = [ + "ethos", + "pdf", + "evidence", +] +license = "Apache-2.0" +repository = "https://github.com/docushell/ethos" + +[package.metadata.ethos_publication] +publication_status = "blocked" +reserved_crates_io_name = "ethos-pdf" +reserved_crates_io_version = "0.0.0-reserved.0" + +[lib] +name = "ethos_pdf" +path = "src/lib.rs" + +[dependencies.ethos-core] +version = "{VERSION}" +features = ["full"] +default-features = false +package = "{CORE_PACKAGE}" + +[dependencies.serde] +version = "1" +features = ["derive"] + +[dependencies.serde_json] +version = "1" +""" + + raise RuntimeError(f"no generated manifest template for package: {package}") + + +def package_source_dir(workspace: Path, package: str) -> Path: + if package == "ethos-verify": + return workspace / "crates/ethos-verify" + if package == "ethos-pdf": + return workspace / "crates/ethos-pdf" + raise RuntimeError(f"no source directory mapping for package: {package}") + + +def add_archive_file(archive: tarfile.TarFile, source: Path, arcname: str) -> None: + info = archive.gettarinfo(str(source), arcname=arcname) + info.uid = 0 + info.gid = 0 + info.uname = "" + info.gname = "" + with source.open("rb") as handle: + archive.addfile(info, handle) + + +def add_archive_text(archive: tarfile.TarFile, text: str, arcname: str) -> None: + data = text.encode("utf-8") + info = tarfile.TarInfo(arcname) + info.size = len(data) + info.mode = 0o644 + info.uid = 0 + info.gid = 0 + info.uname = "" + info.gname = "" + archive.addfile(info, fileobj=io.BytesIO(data)) + + +def assemble_candidate_package( + workspace: Path, + package: str, + commands: list[dict[str, object]], +) -> dict[str, str]: + file_list = run_output( + ["cargo", "package", "--list", "--locked", "--offline", "-p", package, "--allow-dirty"], + workspace, + commands, + ).splitlines() + package_dir = package_source_dir(workspace, package) + crate_path = workspace / "target/package" / f"{package}-{VERSION}.crate" + crate_path.parent.mkdir(parents=True, exist_ok=True) + root = f"{package}-{VERSION}" + + with tarfile.open(crate_path, "w:gz") as archive: + for rel in file_list: + arcname = f"{root}/{rel}" + if rel == "Cargo.toml": + add_archive_text(archive, generated_manifest(package), arcname) + elif rel == "Cargo.toml.orig": + add_archive_file(archive, package_dir / "Cargo.toml", arcname) + elif rel == "Cargo.lock": + add_archive_file(archive, workspace / "Cargo.lock", arcname) + else: + add_archive_file(archive, package_dir / rel, arcname) + + manifest = read_packaged_manifest(crate_path, package) + record_command(f"assemble candidate package artifact -p {package}", commands, "\n".join(file_list)) + return { + "package": package, + "crate_file": crate_path.name, + "sha256": sha256(crate_path), + "manifest": manifest, + } + + def extract_crates(workspace: Path, artifacts: list[dict[str, str]]) -> Path: unpacked = workspace / "target/package-candidate-unpacked" unpacked.mkdir(parents=True, exist_ok=True) @@ -186,40 +364,6 @@ def extract_crates(workspace: Path, artifacts: list[dict[str, str]]) -> Path: return unpacked -def write_cargo_checksum(package_dir: Path, crate_path: Path) -> None: - files: dict[str, str] = {} - for path in sorted(package_dir.rglob("*")): - if not path.is_file() or path.name == ".cargo-checksum.json": - continue - rel = path.relative_to(package_dir).as_posix() - files[rel] = sha256(path) - (package_dir / ".cargo-checksum.json").write_text( - json.dumps({"files": files, "package": sha256(crate_path)}, sort_keys=True), - encoding="utf-8", - ) - - -def activate_registry_equivalent_source( - workspace: Path, - core_artifact: dict[str, str], - commands: list[dict[str, object]], -) -> None: - vendor_dir = workspace / "target/package-candidate-vendor" - cargo_config = run_output( - ["cargo", "vendor", "--locked", "--offline", "target/package-candidate-vendor"], - workspace, - commands, - ) - crate_path = workspace / "target/package" / core_artifact["crate_file"] - with tarfile.open(crate_path, "r:gz") as archive: - archive.extractall(vendor_dir) - write_cargo_checksum(vendor_dir / f"ethos-doc-core-{VERSION}", crate_path) - - config_dir = workspace / ".cargo" - config_dir.mkdir(exist_ok=True) - (config_dir / "config.toml").write_text(cargo_config, encoding="utf-8") - - def write_consumer(workspace: Path, unpacked: Path) -> Path: consumer = workspace / "target/package-candidate-consumer" (consumer / "src").mkdir(parents=True, exist_ok=True) @@ -409,9 +553,8 @@ def run_candidate_activation(workspace: Path) -> dict[str, object]: run(["cargo", "check", "--locked", "--offline", "-p", "ethos-verify"], workspace, commands) run(["cargo", "check", "--locked", "--offline", "-p", "ethos-pdf"], workspace, commands) core_artifact = package_candidate(workspace, CORE_PACKAGE, commands) - activate_registry_equivalent_source(workspace, core_artifact, commands) artifacts = [core_artifact] + [ - package_candidate(workspace, package, commands) + assemble_candidate_package(workspace, package, commands) for package in ("ethos-verify", "ethos-pdf") ] checks = validate_packaged_manifests(artifacts) diff --git a/.github/scripts/test_milestone_e_package_publication_candidate_activation_evidence.py b/.github/scripts/test_milestone_e_package_publication_candidate_activation_evidence.py index 08aaa8c..a601eb8 100644 --- a/.github/scripts/test_milestone_e_package_publication_candidate_activation_evidence.py +++ b/.github/scripts/test_milestone_e_package_publication_candidate_activation_evidence.py @@ -128,10 +128,12 @@ def test_candidate_activation_script_passes_with_registry_equivalent_consumer(se self.assertFalse(result["package_publication_approved"]) self.assertFalse(result["public_installation_approved"]) self.assertNotIn("cargo generate-lockfile --offline", commands) - self.assertIn("cargo vendor --locked --offline target/package-candidate-vendor", commands) + self.assertNotIn("cargo vendor --locked --offline target/package-candidate-vendor", commands) self.assertIn("cargo package --locked --offline -p ethos-doc-core --allow-dirty --no-verify", commands) - self.assertIn("cargo package --locked --offline -p ethos-verify --allow-dirty --no-verify", commands) - self.assertIn("cargo package --locked --offline -p ethos-pdf --allow-dirty --no-verify", commands) + self.assertIn("cargo package --list --locked --offline -p ethos-verify --allow-dirty", commands) + self.assertIn("cargo package --list --locked --offline -p ethos-pdf --allow-dirty", commands) + self.assertIn("assemble candidate package artifact -p ethos-verify", commands) + self.assertIn("assemble candidate package artifact -p ethos-pdf", commands) self.assertIn("cargo check --locked --offline", commands) def test_candidate_activation_preserves_import_and_dependency_shape(self) -> None: diff --git a/docs/validation/milestone-e-package-publication-candidate-activation-evidence-validation-2026-06-22.md b/docs/validation/milestone-e-package-publication-candidate-activation-evidence-validation-2026-06-22.md index 649636e..3e0b461 100644 --- a/docs/validation/milestone-e-package-publication-candidate-activation-evidence-validation-2026-06-22.md +++ b/docs/validation/milestone-e-package-publication-candidate-activation-evidence-validation-2026-06-22.md @@ -42,8 +42,9 @@ surface. Package publication remains blocked. Public installation remains blocke - `ethos-pdf` retains core feature `full`. - The temporary workspace assembles `ethos-doc-core-0.1.0.crate`, `ethos-verify-0.1.0.crate`, and `ethos-pdf-0.1.0.crate`. -- The temporary workspace uses a Cargo vendor source as a registry-equivalent source override for - the candidate core package before packaging dependent candidates. +- The temporary workspace packages the candidate core package with Cargo, uses Cargo's package file + list for dependent candidates, and assembles dependent candidate archives without replacing the + full crates.io source. - An unpacked registry-equivalent consumer resolves `ethos-doc-core`, `ethos-verify`, and `ethos-pdf` and passes `cargo check --locked --offline`.