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
102 changes: 102 additions & 0 deletions .github/scripts/test_milestone_e_package_publication_approval_prep.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@
"package_real_version_selection_prep": "docs/validation/milestone-e-package-publication-real-version-selection-prep-validation-2026-06-21.md",
"package_tag_creation_prep": "docs/validation/milestone-e-package-publication-tag-creation-prep-validation-2026-06-21.md",
"package_decision_bundle_validation": "docs/validation/milestone-e-package-publication-decision-bundle-validation-2026-06-21.md",
"package_pre_approval_gap_ledger": "docs/validation/milestone-e-package-publication-pre-approval-gap-ledger-validation-2026-06-21.md",
}
EXPECTED_PUBLICATION_DECISION_INPUTS = {
"decision_status": "not_approved_pending_exact_decision",
Expand Down Expand Up @@ -272,6 +273,59 @@
"real-version cargo publish remains blocked",
],
}
EXPECTED_PACKAGE_PUBLICATION_PRE_APPROVAL_GAP_LEDGER = {
"ledger_state": "pre_approval_gaps_recorded_publication_blocked",
"gap_rows": [
"version map gap: no package publication version is selected; requires exact SemVer package version or per-crate version map",
"tag name gap: no package tag is created; requires exact package tag name",
"tag binding gap: no package_tag_source_commit or source tree is selected; requires exact source commit and tree binding",
"manifest activation gap: current Cargo manifests remain unchanged; requires exact package-name migration and dependency activation diff",
"registry assembly gap: no registry-backed dependent package assembly is activated; requires exact non-public assembly evidence",
"public installation wording gap: no public installation wording is approved; requires exact wording and exclusions",
"posture and claims gate gap: gates must rerun after exact public installation wording changes",
],
"blocked_actions": [
"selecting a package publication version remains blocked",
"creating a package tag remains blocked",
"changing Cargo manifests remains blocked",
"activating package dependency manifests remains blocked",
"creating a registry remains blocked",
"activating registry-backed dependent package assembly remains blocked",
"inviting public installation remains blocked",
"approving package publication remains blocked",
],
"required_resolution_inputs": [
"exact package publication approval decision record",
"exact candidate crate list",
"exact SemVer package version or per-crate version map",
"exact package tag name",
"exact package_tag_source_commit and package source tree",
"exact package-name migration diff for ethos-doc-core",
"exact dependency manifest activation diff for ethos-verify and ethos-pdf",
"exact registry-backed dependent package assembly evidence",
"exact public installation wording and explicit exclusions",
"posture and claims gates after exact public installation wording changes",
],
"non_approvals": [
"this ledger does not select a package publication version",
"this ledger does not create a package tag",
"this ledger does not change Cargo manifests",
"this ledger does not activate package dependency manifests",
"this ledger does not create a registry",
"this ledger does not activate registry-backed dependent package assembly",
"this ledger does not invite public installation",
"this ledger does not approve package publication",
],
"retained_blockers": [
"no package publication version is selected",
"no package tag is created",
"no package dependency manifest activation is approved",
"no registry-backed dependent package assembly activation is approved",
"public installation remains blocked",
"package publication remains blocked",
"real-version cargo publish remains blocked",
],
}

FORBIDDEN_PREP_WORDING = [
"public beta is approved",
Expand Down Expand Up @@ -643,6 +697,38 @@ def test_package_publication_approval_request_packet_keeps_all_actions_blocked(s
self.assertIn('name = "ethos-verify"', verify_manifest)
self.assertIn('name = "ethos-pdf"', pdf_manifest)

def test_package_publication_pre_approval_gap_ledger_keeps_resolution_inputs_explicit(self) -> None:
ledger = load_json(PREP)["package_publication_pre_approval_gap_ledger"]
cargo = read(ROOT / "Cargo.toml")
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")

self.assertEqual(EXPECTED_PACKAGE_PUBLICATION_PRE_APPROVAL_GAP_LEDGER, ledger)
self.assertEqual("pre_approval_gaps_recorded_publication_blocked", ledger["ledger_state"])
self.assertEqual(7, len(ledger["gap_rows"]))
self.assertEqual(8, len(ledger["blocked_actions"]))
self.assertIn("creating a package tag remains blocked", ledger["blocked_actions"])
self.assertIn("changing Cargo manifests remains blocked", ledger["blocked_actions"])
self.assertIn("inviting public installation remains blocked", ledger["blocked_actions"])
self.assertIn(
"exact package_tag_source_commit and package source tree",
ledger["required_resolution_inputs"],
)
self.assertIn(
"exact public installation wording and explicit exclusions",
ledger["required_resolution_inputs"],
)
self.assertIn("this ledger does not approve package publication", ledger["non_approvals"])
self.assertIn("real-version cargo publish remains blocked", ledger["retained_blockers"])
self.assertIn("publish = false", core_manifest)
self.assertIn("publish = false", verify_manifest)
self.assertIn("publish = false", pdf_manifest)
self.assertIn('name = "ethos-core"', core_manifest)
self.assertIn('name = "ethos-verify"', verify_manifest)
self.assertIn('name = "ethos-pdf"', pdf_manifest)
self.assertIn('version = "0.1.0"', cargo)

def test_pdfium_boundary_keeps_ethos_pdf_held_until_confirmed(self) -> None:
approved = load_json(PREP)["approved_package_publication_prep"]
pdfium_boundary = " ".join(approved["pdfium_boundary"])
Expand Down Expand Up @@ -708,6 +794,10 @@ def test_schema_validation_covers_package_publication_prep(self) -> None:
False,
schema["$defs"]["package_publication_approval_request_packet"]["additionalProperties"],
)
self.assertEqual(
False,
schema["$defs"]["package_publication_pre_approval_gap_ledger"]["additionalProperties"],
)
self.assertEqual(9, schema["properties"]["required_evidence"]["minItems"])
self.assertEqual(13, schema["properties"]["explicit_blockers"]["minItems"])
self.assertEqual(
Expand Down Expand Up @@ -746,6 +836,18 @@ def test_schema_validation_covers_package_publication_prep(self) -> None:
"explicit_exclusions"
]["minItems"],
)
self.assertEqual(
7,
schema["$defs"]["package_publication_pre_approval_gap_ledger"]["properties"][
"gap_rows"
]["minItems"],
)
self.assertEqual(
10,
schema["$defs"]["package_publication_pre_approval_gap_ledger"]["properties"][
"required_resolution_inputs"
]["minItems"],
)
self.assertIn("ethos-milestone-e-package-publication-approval-prep.schema.json", validate_examples)
self.assertIn("docs\" / \"milestone-e-package-publication-approval-prep.json", validate_examples)
self.assertIn("ethos-milestone-e-package-publication-approval-prep.schema.json", schemas_readme)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
#!/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 unittest
from pathlib import Path

from makefile_guard import target_block


ROOT = Path(__file__).resolve().parents[2]
PREP = ROOT / "docs/milestone-e-package-publication-approval-prep.json"
RECORD = (
ROOT
/ "docs/validation/"
"milestone-e-package-publication-pre-approval-gap-ledger-validation-2026-06-21.md"
)
VALIDATION_README = ROOT / "docs/validation/README.md"
CI_WORKFLOW = ROOT / ".github/workflows/ci.yml"

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"))


class MilestoneEPackagePublicationPreApprovalGapLedgerTests(unittest.TestCase):
def test_gap_ledger_record_is_indexed(self) -> None:
readme = read(VALIDATION_README)
normalized_readme = re.sub(r"\s+", " ", readme)

self.assertIn(RECORD.name, readme)
self.assertIn(
"package publication pre-approval gap-ledger validation",
normalized_readme,
)

def test_record_names_validation_commands(self) -> None:
text = read(RECORD)

self.assertIn("Validated source HEAD before this record: `c28704f`", text)
self.assertIn(
"python3 .github/scripts/test_milestone_e_package_publication_approval_prep.py",
text,
)
self.assertIn(
"python3 .github/scripts/test_milestone_e_package_publication_pre_approval_gap_ledger.py",
text,
)
self.assertIn("python3 .github/scripts/test_public_surface_posture.py", text)
self.assertIn("python3 .github/scripts/claims_gate.py", text)
self.assertIn("make milestone-e-prep PYTHON=<jsonschema-venv>/bin/python", text)
self.assertIn("git diff --check", text)

def test_record_matches_gap_ledger_without_approving_actions(self) -> None:
prep = load_json(PREP)
ledger = prep["package_publication_pre_approval_gap_ledger"]
record = normalized(RECORD)

self.assertEqual("pre_approval_gaps_recorded_publication_blocked", ledger["ledger_state"])
self.assertIn(ledger["ledger_state"], record)
for row in ledger["gap_rows"]:
self.assertIn(row, record)
for action in ledger["blocked_actions"]:
self.assertIn(action, record)
for required in ledger["required_resolution_inputs"]:
self.assertIn(required, record)
for non_approval in ledger["non_approvals"]:
self.assertIn(non_approval, record)
for blocker in ledger["retained_blockers"]:
self.assertIn(blocker, record)
self.assertIn("Package publication remains blocked", record)
self.assertIn("Public installation remains blocked", record)

def test_current_manifests_stay_non_publishable(self) -> None:
cargo = read(ROOT / "Cargo.toml")
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")

self.assertIn('"crates/ethos-core"', cargo)
self.assertIn('"crates/ethos-verify"', cargo)
self.assertIn('"crates/ethos-pdf"', cargo)
self.assertIn("publish = false", core_manifest)
self.assertIn("publish = false", verify_manifest)
self.assertIn("publish = false", pdf_manifest)
self.assertIn('reserved_crates_io_version = "0.0.0-reserved.0"', core_manifest)
self.assertIn('reserved_crates_io_version = "0.0.0-reserved.0"', verify_manifest)
self.assertIn('reserved_crates_io_version = "0.0.0-reserved.0"', pdf_manifest)

def test_make_and_ci_run_gap_ledger_after_decision_bundle(self) -> None:
make_block = target_block("milestone-e-prep")
ci = read(CI_WORKFLOW)
bundle_guard = "test_milestone_e_package_publication_decision_bundle_validation_record.py"
gap_guard = "test_milestone_e_package_publication_pre_approval_gap_ledger.py"
command_guard = "test_milestone_e_validation_command_index.py"

for text, prefix in ((make_block, "$(PYTHON) .github/scripts/"), (ci, "python3 .github/scripts/")):
self.assertIn(prefix + gap_guard, text)
self.assertEqual(1, text.count(prefix + gap_guard))
self.assertLess(text.index(prefix + bundle_guard), text.index(prefix + gap_guard))
self.assertLess(text.index(prefix + gap_guard), text.index(prefix + command_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()
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@
"$(PYTHON) .github/scripts/test_milestone_e_package_publication_manifest_activation_prep.py",
"$(PYTHON) .github/scripts/test_milestone_e_package_publication_registry_assembly_activation_prep.py",
"$(PYTHON) .github/scripts/test_milestone_e_package_publication_decision_bundle_validation_record.py",
"$(PYTHON) .github/scripts/test_milestone_e_package_publication_pre_approval_gap_ledger.py",
"$(PYTHON) .github/scripts/test_milestone_e_validation_command_index.py",
"$(PYTHON) .github/scripts/test_milestone_e_validation_command_index_validation_record.py",
"$(PYTHON) .github/scripts/test_milestone_e_validation_record_index.py",
Expand Down
1 change: 1 addition & 0 deletions .github/scripts/test_milestone_e_prep_scope.py
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,7 @@ def test_make_target_is_narrow_and_guarded(self) -> None:
"$(PYTHON) .github/scripts/test_milestone_e_package_publication_manifest_activation_prep.py",
"$(PYTHON) .github/scripts/test_milestone_e_package_publication_registry_assembly_activation_prep.py",
"$(PYTHON) .github/scripts/test_milestone_e_package_publication_decision_bundle_validation_record.py",
"$(PYTHON) .github/scripts/test_milestone_e_package_publication_pre_approval_gap_ledger.py",
"$(PYTHON) .github/scripts/test_milestone_e_validation_command_index.py",
"$(PYTHON) .github/scripts/test_milestone_e_validation_command_index_validation_record.py",
"$(PYTHON) .github/scripts/test_milestone_e_validation_record_index.py",
Expand Down
13 changes: 11 additions & 2 deletions .github/scripts/test_milestone_e_validation_record_index.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,10 @@ class RecordCoverage:
"milestone-e-package-publication-decision-bundle-validation-2026-06-21.md",
"test_milestone_e_package_publication_decision_bundle_validation_record.py",
),
RecordCoverage(
"milestone-e-package-publication-pre-approval-gap-ledger-validation-2026-06-21.md",
"test_milestone_e_package_publication_pre_approval_gap_ledger.py",
),
RecordCoverage(
"milestone-e-validation-command-index-validation-2026-06-20.md",
"test_milestone_e_validation_command_index_validation_record.py",
Expand Down Expand Up @@ -352,6 +356,7 @@ def test_index_guards_run_after_row_and_schema_records(self) -> None:
package_decision_bundle_guard = (
"test_milestone_e_package_publication_decision_bundle_validation_record.py"
)
package_gap_ledger_guard = "test_milestone_e_package_publication_pre_approval_gap_ledger.py"
command_guard = "test_milestone_e_validation_command_index_validation_record.py"
index_guard = "test_milestone_e_validation_record_index.py"
index_record_guard = "test_milestone_e_validation_record_index_validation_record.py"
Expand Down Expand Up @@ -404,8 +409,12 @@ def test_index_guards_run_after_row_and_schema_records(self) -> None:
text.index(prefix + package_registry_activation_guard),
text.index(prefix + package_decision_bundle_guard),
)
self.assertLess(text.index(prefix + package_decision_bundle_guard), text.index(prefix + command_guard))
self.assertLess(text.index(prefix + package_decision_bundle_guard), text.index(prefix + index_guard))
self.assertLess(
text.index(prefix + package_decision_bundle_guard),
text.index(prefix + package_gap_ledger_guard),
)
self.assertLess(text.index(prefix + package_gap_ledger_guard), text.index(prefix + command_guard))
self.assertLess(text.index(prefix + package_gap_ledger_guard), text.index(prefix + index_guard))
self.assertLess(text.index(prefix + package_manifest_activation_guard), text.index(prefix + command_guard))
self.assertLess(text.index(prefix + package_manifest_activation_guard), text.index(prefix + index_guard))
self.assertLess(text.index(prefix + package_tag_guard), text.index(prefix + command_guard))
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,8 @@ jobs:
run: python3 .github/scripts/test_milestone_e_package_publication_registry_assembly_activation_prep.py
- name: Milestone E package publication decision-bundle validation record tests
run: python3 .github/scripts/test_milestone_e_package_publication_decision_bundle_validation_record.py
- name: Milestone E package publication pre-approval gap ledger tests
run: python3 .github/scripts/test_milestone_e_package_publication_pre_approval_gap_ledger.py
- name: Milestone E validation-command index tests
run: python3 .github/scripts/test_milestone_e_validation_command_index.py
- name: Milestone E validation-command index validation record tests
Expand Down
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,7 @@ milestone-e-prep:
$(PYTHON) .github/scripts/test_milestone_e_package_publication_manifest_activation_prep.py
$(PYTHON) .github/scripts/test_milestone_e_package_publication_registry_assembly_activation_prep.py
$(PYTHON) .github/scripts/test_milestone_e_package_publication_decision_bundle_validation_record.py
$(PYTHON) .github/scripts/test_milestone_e_package_publication_pre_approval_gap_ledger.py
$(PYTHON) .github/scripts/test_milestone_e_validation_command_index.py
$(PYTHON) .github/scripts/test_milestone_e_validation_command_index_validation_record.py
$(PYTHON) .github/scripts/test_milestone_e_validation_record_index.py
Expand Down
Loading
Loading