From d24d3f07ab90769455d60bcdcb3801e1d25d2b35 Mon Sep 17 00:00:00 2001 From: Codex Date: Tue, 8 Sep 2026 06:54:18 +0000 Subject: [PATCH 01/65] Route gonol skill to current geometry and EDCM scale authority --- .agents/skills/README.md | 26 +++++--- .agents/skills/gonol-build/SKILL.md | 44 +++++++++---- .../skills/gonol-build/check_edcm_boundary.py | 62 +++++++++++++++++++ .github/workflows/ci.yml | 4 +- 4 files changed, 111 insertions(+), 25 deletions(-) create mode 100644 .agents/skills/gonol-build/check_edcm_boundary.py diff --git a/.agents/skills/README.md b/.agents/skills/README.md index 17a55b2..4932114 100644 --- a/.agents/skills/README.md +++ b/.agents/skills/README.md @@ -3,19 +3,25 @@ This directory contains repo-local copies of canonical skills from `The-Interdependency/skill-lib`. -Source commit: `c14ee9d500579a4b5d6821f62c9d82ca96e73608` +Source commit: `537cc3954b9ce4f660d46694b1e704a3ae9a5684` Repo-local copies are not the source of truth. Edit `skill-lib` first, then propagate from the canonical source. -Installed skills: +Skills refreshed from the source commit above: -- `canon/` -- `domain-claims/` - `gonol-build/` -- `interdependent-work-graph/` -- `meta-module-build/` -- `msdmd/` -- `test-build/` -- `the-interdependency/` -- `ucns-option-selection/` + +Other installed skills (not refreshed by this propagation): + +Their existing files retain their own authority/provenance; the source +commit above does not assert that these copies were refreshed. + +- `canon/` [not refreshed; prior source: `c14ee9d500579a4b5d6821f62c9d82ca96e73608`] +- `domain-claims/` [not refreshed; prior source: `c14ee9d500579a4b5d6821f62c9d82ca96e73608`] +- `interdependent-work-graph/` [not refreshed; prior source: `c14ee9d500579a4b5d6821f62c9d82ca96e73608`] +- `meta-module-build/` [not refreshed; prior source: `c14ee9d500579a4b5d6821f62c9d82ca96e73608`] +- `msdmd/` [not refreshed; prior source: `c14ee9d500579a4b5d6821f62c9d82ca96e73608`] +- `test-build/` [not refreshed; prior source: `c14ee9d500579a4b5d6821f62c9d82ca96e73608`] +- `the-interdependency/` [not refreshed; prior source: `c14ee9d500579a4b5d6821f62c9d82ca96e73608`] +- `ucns-option-selection/` [not refreshed; prior source: `c14ee9d500579a4b5d6821f62c9d82ca96e73608`] diff --git a/.agents/skills/gonol-build/SKILL.md b/.agents/skills/gonol-build/SKILL.md index 86747c8..0654cc7 100644 --- a/.agents/skills/gonol-build/SKILL.md +++ b/.agents/skills/gonol-build/SKILL.md @@ -1,6 +1,6 @@ --- name: gonol-build -description: Construction, closure, and replay contract for gonols across UCNS and EDCM. Load this when building or reviewing UCNS geometry used by gonols, or building EDCM character, word, definition, or recursive-relation gonols. UCNS owns geometry; EDCM owns text construction. The required EDCM order is characters -> words -> definitions -> recursive gonol relations. Pronunciation is not required unless an explicitly declared later experiment makes it part of the construction. Do not load for unrelated geometry, ordinary prose editing, or measurement over already-closed gonols. +description: Construction, closure, and replay contract for gonols across UCNS and EDCM. Load this when building or reviewing UCNS geometry used by gonols, or building EDCM character, word, definition, or recursive-relation gonols. UCNS owns geometry; EDCM owns text construction and admissible scale options. Closed gonols participate atomically at any admissible consuming scale; no universal adjacent-scale ladder is required. Pronunciation is not required unless an explicitly declared later experiment makes it part of the construction. Do not load for unrelated geometry, ordinary prose editing, or measurement over already-closed gonols. --- # gonol-build @@ -11,8 +11,8 @@ Use this skill to keep gonol construction on the declared architecture and nothi 1. Resolve the current UCNS and EDCM authorities before building. 2. Before launching construction or replay whose completion materially depends on scarce resources, preflight the resources required to finish the declared scope. -3. Keep the EDCM order load-bearing: characters -> words -> definitions -> recursive gonol relations. -4. Close each completed gonol before it participates atomically at the next declared scale. +3. Resolve the EDCM constructor's declared scale option set and participant eligibility; do not impose a universal adjacent-scale ladder. +4. Close each completed gonol before it participates atomically at an admissible consuming scale. 5. If required UCNS geometry is unresolved, preserve that boundary as `hmmm`. 6. Replay the complete declared scope only where replay is required by the governing protocol. @@ -29,18 +29,25 @@ Resolve the current UCNS and EDCM authorities before building. Do not move text ## EDCM construction contract ```text -characters -> words -> definitions -> recursive gonol relations +declared scale option set + eligible already-closed participants -> construction -> closure ``` -This order is load-bearing. +EDCM owns the admissible scale options. Consult its exact current +`docs/GONOL_LANGUAGE_BOUNDARY.md` and `edcm/gonol.py` contract before selecting +participants. The sequence `characters -> words -> definitions -> recursive +gonol relations` may describe a particular construction, but is not a mandatory +ladder for all EDCM constructions. - Every admitted character is a gonol. -- Ordered character gonols close into a word gonol. -- A closed word gonol is atomic at the consuming scale while its constituent identities, order, multiplicity, source positions, and provenance remain recoverable. -- Definition gonols are constructed from the applicable closed word gonols and exact source definition evidence. +- Ordered character gonols may close into a word gonol under its declared constructor. +- Any closed gonol is atomic at an admissible consuming scale while its constituent identities, order, multiplicity, source positions, and provenance remain recoverable. +- Definition gonols use eligible closed participants and exact source definition evidence; do not require word intermediates when EDCM admits another scale option. - Recursive relations are constructed from already-closed gonols without reopening or erasing their internal structure. -Do not insert another required stage into this sequence unless the governing contract is explicitly changed. +Do not invent participant eligibility or another required stage. A permitted +character-to-definition construction must not be rejected solely because it +omits a word intermediate. Nor does this permit every scale combination: the +owning constructor's option set remains load-bearing. ## Pronunciation boundary @@ -57,7 +64,7 @@ ordered eligible gonols -> authorized UCNS geometric relation/application -> closure -> deterministic identity + provenance receipt --> atomic participation at the next declared scale +-> atomic participation at an admissible consuming scale ``` Preserve exact source identity, occurrence order, multiplicity, and provenance. Do not normalize, deduplicate, infer relations, or substitute tokens, embeddings, hashes, or another representation for gonol identity unless the active contract explicitly authorizes it. @@ -83,19 +90,30 @@ Replay establishes reproducibility of that construction only. It does not by its ## Usage guidance +The executable cross-source witness is `gonol-build/check_edcm_boundary.py`. +Run it against a clean EDCM checkout at the script's exact `EDCM_COMMIT`: + +```bash +python gonol-build/check_edcm_boundary.py /path/to/edcm +``` + +It checks direct character-to-definition construction, replay, candidate standing, +and refusal of an undeclared scale. This pinned witness is not a universal scale +registry; current construction must still resolve its owning EDCM option set. + For text construction, start in EDCM and consume current UCNS geometry. ```text UCNS: geometry -EDCM: characters -> words -> definitions -> recursive gonol relations +EDCM: declared scale options, participant eligibility, text construction ``` -When a word closes, use that word gonol atomically at the next scale. Ignore pronunciation unless a future explicit construction says otherwise. +When a gonol closes, use it atomically at any admissible consuming scale. Ignore pronunciation unless a future explicit construction says otherwise. ## Anti-patterns - Moving text semantics into UCNS or inventing geometry in EDCM. -- Inserting another required EDCM stage without an explicit contract change. +- Imposing an adjacent-scale ladder or inventing an eligible scale option without EDCM authority. - Letting pronunciation alter gonol identity, closure, ordering, or relations unless a later explicitly declared experiment makes phonology part of its construction. - Normalizing, deduplicating, inferring relations, or substituting tokens, embeddings, or hashes for gonol identity unless the active contract explicitly authorizes it. - Adding arbitrary wall-clock limits to a healthy admitted run. diff --git a/.agents/skills/gonol-build/check_edcm_boundary.py b/.agents/skills/gonol-build/check_edcm_boundary.py new file mode 100644 index 0000000..6a97be7 --- /dev/null +++ b/.agents/skills/gonol-build/check_edcm_boundary.py @@ -0,0 +1,62 @@ +# ratios: loc_comments=29:22 imports_exports=4:1 calls_definitions=12:1 +"""Executable cross-source scale witness; not a geometry or measurement proof. + +Usage: python gonol-build/check_edcm_boundary.py /exact/edcm/checkout +The supplied clean checkout must match EDCM_COMMIT. No package install or network +access is performed by this witness. Refreshing the pin is a reviewed boundary change. +""" + +# === MODULE_BUILD === +# id: gonol_edcm_scale_witness +# module_name: check_edcm_boundary +# module_kind: adapter +# summary: Exercise an exact EDCM constructor's non-adjacent scale and replay contract. +# owner: skill-lib maintainers +# public_surface: check +# internal_surface: none +# auth_boundary: none +# storage_boundary: none +# network_boundary: none +# user_data_boundary: none +# admin_only: false +# tests: explicit CLI against the declared EDCM commit +# rollout: cross-source CI gate +# rollback: retain pin and report boundary as hmmm if unavailable +# === END MODULE_BUILD === + +from pathlib import Path +import subprocess +import sys + +EDCM_COMMIT = "ddc89a97ebbcf0a5863dad6e633b01b520e9bccf" + + +def check(root: Path) -> None: + actual = subprocess.check_output(["git", "-C", str(root), "rev-parse", "HEAD"], text=True).strip() + if actual != EDCM_COMMIT: + raise ValueError(f"expected exact EDCM {EDCM_COMMIT}, got {actual}") + if subprocess.check_output(["git", "-C", str(root), "status", "--porcelain"]): + raise ValueError("EDCM witness requires a clean source checkout") + sys.path.insert(0, str(root.resolve())) + from edcm.gonol import construct_gonol, replay_gonol, GonolConstructionError + + character = construct_gonol(scale="character", source="x", source_id="skill-lib:character") + definition = construct_gonol(scale="definition", source="bounded evidence", + relation="skill-lib:defined-by", participants=(character.gonol,), + source_id="skill-lib:definition") + assert definition.gonol.participants == (character.gonol,) + assert replay_gonol(receipt=definition).receipt_digest == definition.receipt_digest + assert definition.standing == "implemented-candidate" + assert definition.selection_effect == "none" + try: + construct_gonol(scale="undeclared-scale", source="x", source_id="skill-lib:invalid") + except GonolConstructionError: + pass + else: + raise AssertionError("undeclared scale must not become eligible") + print(f"PASS EDCM {actual}: direct character-to-definition, replay, candidate non-transfer, rejected undeclared scale") + + +if __name__ == "__main__": + check(Path(sys.argv[1])) +# ratios: loc_comments=29:22 imports_exports=4:1 calls_definitions=12:1 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1b0bf8d..53e6fa8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,7 +21,7 @@ jobs: uses: actions/checkout@v7.0.1 with: repository: The-Interdependency/skill-lib - ref: c14ee9d500579a4b5d6821f62c9d82ca96e73608 + ref: 537cc3954b9ce4f660d46694b1e704a3ae9a5684 path: .skill-lib-canon sparse-checkout: | msdmd @@ -48,7 +48,7 @@ jobs: run: >- python .skill-lib-canon/tools/check_consumer_drift.py . --canon-root .skill-lib-canon - --sha c14ee9d500579a4b5d6821f62c9d82ca96e73608 + --sha 537cc3954b9ce4f660d46694b1e704a3ae9a5684 --strict-sha --require-vendored - name: Audit skill-lib contract graph From 0c8e9fc38edf3b3c2c00e6309c4064cb9b0e27fb Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 11 Sep 2026 00:42:11 +0000 Subject: [PATCH 02/65] Repair exact inputs, evidence gates, and source distribution replay --- .github/workflows/ci.yml | 21 ++-- MANIFEST.in | 7 ++ README.md | 36 +++++- src/ucns/gonal_boundary_trace.py | 20 +++- src/ucns/modular_orbit.py | 25 ++++- src/ucns/mpfr_interval.py | 27 +++++ tests/test_distributions.py | 66 +++++++++++ tests/test_gonal_boundary_trace.py | 16 +++ tests/test_modular_orbit.py | 19 ++++ tests/test_mpfr_input_boundaries.py | 57 ++++++++++ tests/test_skill_lib_boundary_runner.py | 100 ++++++++++++++++- tests/test_skill_lib_contracts.py | 48 ++++++++ tools/run_skill_lib_boundaries.py | 111 +++++++++++++++---- tools/verify_distributions.py | 140 ++++++++++++++++++++++++ tools/verify_skill_lib_contracts.py | 85 ++++++++------ uv.lock | 70 +----------- 16 files changed, 707 insertions(+), 141 deletions(-) create mode 100644 MANIFEST.in create mode 100644 tests/test_distributions.py create mode 100644 tests/test_mpfr_input_boundaries.py create mode 100644 tools/verify_distributions.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 53e6fa8..e83d83a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -40,10 +40,15 @@ jobs: - uses: actions/setup-python@v7.0.0 with: python-version: ${{ matrix.python-version }} - - name: Install verification dependencies - run: python -m pip install --upgrade pip pytest build twine - - name: Install package - run: python -m pip install -e ".[test]" + - name: Install verification prerequisites + run: | + sudo apt-get update + sudo apt-get install -y libmpfr6 + python -m pip install uv==0.11.18 + - name: Check lock and install verification dependencies + run: | + uv lock --check + uv sync --locked --python python --extra test --extra build - name: Check vendored skill-lib drift run: >- python .skill-lib-canon/tools/check_consumer_drift.py . @@ -54,8 +59,10 @@ jobs: - name: Audit skill-lib contract graph run: python tools/verify_skill_lib_contracts.py . - name: Run geometry tests - run: python -m pytest + run: .venv/bin/python -m pytest - name: Build package - run: python -m build + run: .venv/bin/python -m build - name: Check distributions - run: python -m twine check dist/* + run: | + .venv/bin/python -m twine check dist/* + python tools/verify_distributions.py . dist diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..46b0402 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,7 @@ +include AGENTS.md CANON.md CLAUDE.md uv.lock +recursive-include tools *.py +recursive-include tests *.py +recursive-include docs *.md *.json *.jsonl *.svg +recursive-include generated *.json +recursive-include .agents/skills *.md *.json *.py *.ts +global-exclude *.py[cod] diff --git a/README.md b/README.md index 784b445..114d6aa 100644 --- a/README.md +++ b/README.md @@ -144,11 +144,39 @@ assert s720 == s0 ## Build ```bash -python -m pip install -e ".[test,build]" -python -m pytest -q +python -m pip install uv==0.11.18 +uv lock --check +uv sync --locked --python python --extra test --extra build +.venv/bin/python -m pytest -q python tools/verify_skill_lib_contracts.py . -python -m build -python -m twine check dist/* +.venv/bin/python -m build +.venv/bin/python -m twine check dist/* +python tools/verify_distributions.py . dist ``` +The independent interval checks also require system MPFR (for example, +`libmpfr6` on Ubuntu). Verification dependencies are locked; the isolated build +backend still follows `[build-system].requires`. Use a clean `dist` directory. + +The wheel supplies the geometry package. Repository-context research replay and +the full tests require the source archive or checkout: run from its root, where +the archived preregistrations, generated evidence, and vendored parser live. +The distribution gate checks these inputs byte-for-byte; it does not recertify +their mathematical claims. + +Exact modular and trace records require immutable tuples with non-Boolean integer +residues; prefer the public builders. MPFR rational constructors accept only +integers or `Fraction`, and NaN cannot participate in interval ordering. + +For a source-bound, selected-check receipt: + +```bash +.venv/bin/python tools/run_skill_lib_boundaries.py . \ + --check check_boundary_runner_nonactivation --receipt /tmp/ucns-receipt.json +``` + +Receipt schema 2 rejects skipped/expected-failing evidence, absent test reports, +and source changes during execution. A `passed` receipt covers only its selected +checks; it does not select geometry, ratify candidates, or establish freshness. + `hmmm`: ratification of the modular-orbit / continuum-boundary-trace candidates, the complete higher-dimensional UCNS construction, the exact visible-circle wave-trace lift into the native Möbius carrier, any law selecting one continuum covering lift from a finite modular congruence class, and the exact geometric operation of every Public Gonol function position remain unresolved. Unresolved geometry stays unresolved; semantic machinery is not used to fill it. diff --git a/src/ucns/gonal_boundary_trace.py b/src/ucns/gonal_boundary_trace.py index 0f54fde..7d3134c 100644 --- a/src/ucns/gonal_boundary_trace.py +++ b/src/ucns/gonal_boundary_trace.py @@ -1,3 +1,4 @@ +# ratios: loc_comments=226:108 imports_exports=5:7 calls_definitions=76:18 # === MODULE_BUILD === # id: ucns_gonal_boundary_trace # module_name: gonal_boundary_trace @@ -211,6 +212,8 @@ def __post_init__(self) -> None: def phase_at(self, residue: int) -> Fraction: """Return exact spatial phase in normalized turns at one declared position.""" + if isinstance(residue, bool) or not isinstance(residue, int): + raise GonalBoundaryTraceError("residue must be a nonboolean integer") for sample in self.samples: if sample.residue == residue: return sample.phase_turn @@ -242,7 +245,11 @@ def as_dict(self) -> dict[str, object]: @dataclass(frozen=True, slots=True) class CircleWaveCoveringTrace: - """Exact trace-level witness of a continuum degree-d spacetime pullback.""" + """Exact trace-level witness of a continuum degree-d spacetime pullback. + + Direct restoration requires validated trace records and immutable integer + action pairs. Use ``pullback_circle_wave_trace`` to construct a witness. + """ source: CircleWaveModeTrace target: CircleWaveModeTrace @@ -251,6 +258,14 @@ class CircleWaveCoveringTrace: action: tuple[tuple[int, int], ...] def __post_init__(self) -> None: + if not isinstance(self.source, CircleWaveModeTrace) or not isinstance(self.target, CircleWaveModeTrace): + raise GonalBoundaryTraceError("source and target must be CircleWaveModeTrace records") + if not isinstance(self.action, tuple) or any( + not isinstance(pair, tuple) or len(pair) != 2 + or any(isinstance(value, bool) or not isinstance(value, int) for value in pair) + for pair in self.action + ): + raise GonalBoundaryTraceError("action must contain immutable nonboolean integer pairs") _validate_covering_degree(self.covering_degree) _validate_time_scale(self.time_scale) if self.time_scale != self.covering_degree: @@ -347,6 +362,8 @@ def pullback_circle_wave_trace( explicitly, and this function requires ``d % m == geometry.multiplier``. """ + if not isinstance(trace, CircleWaveModeTrace) or not isinstance(geometry, ModularOrbitGeometry): + raise GonalBoundaryTraceError("pullback requires validated trace and modular geometry records") if trace.modulus != geometry.modulus: raise GonalBoundaryTraceError("trace and modular geometry moduli must match") if trace.positions != geometry.positions: @@ -379,3 +396,4 @@ def pullback_circle_wave_trace( "build_circle_wave_mode_trace", "pullback_circle_wave_trace", ] +# ratios: loc_comments=226:108 imports_exports=5:7 calls_definitions=76:18 diff --git a/src/ucns/modular_orbit.py b/src/ucns/modular_orbit.py index 3a815db..0dba580 100644 --- a/src/ucns/modular_orbit.py +++ b/src/ucns/modular_orbit.py @@ -1,3 +1,4 @@ +# ratios: loc_comments=204:82 imports_exports=4:5 calls_definitions=92:10 # === MODULE_BUILD === # id: ucns_modular_orbit_geometry # module_name: modular_orbit @@ -99,7 +100,11 @@ def __post_init__(self) -> None: @dataclass(frozen=True, slots=True) class ModularOrbitGeometry: - """Immutable exact geometry of one finite modular multiplication action.""" + """Immutable exact geometry of one finite modular multiplication action. + + Restore records with nested tuples and nonboolean integers; numeric equality + to an integer is not sufficient admission. Use the builder for iterables. + """ modulus: int multiplier: int @@ -110,6 +115,19 @@ class ModularOrbitGeometry: embedding: tuple[CircularResiduePosition, ...] def __post_init__(self) -> None: + for name in ("positions", "action", "orbits", "periods", "embedding"): + if not isinstance(getattr(self, name), tuple): + raise ModularOrbitError(f"{name} must be an immutable tuple") + for name in ("action", "orbits"): + for row in getattr(self, name): + if not isinstance(row, tuple) or not row: + raise ModularOrbitError(f"{name} must contain nonempty immutable tuples") + if name == "action" and len(row) != 2: + raise ModularOrbitError("action entries must be source/target pairs") + if any(isinstance(value, bool) or not isinstance(value, int) for value in row): + raise ModularOrbitError(f"{name} must contain nonboolean integer residues") + if any(isinstance(value, bool) or not isinstance(value, int) for value in self.periods): + raise ModularOrbitError("periods must be nonboolean integers") if isinstance(self.modulus, bool) or not isinstance(self.modulus, int) or self.modulus <= 1: raise ModularOrbitError("modulus must be an integer greater than 1") if isinstance(self.multiplier, bool) or not isinstance(self.multiplier, int): @@ -170,6 +188,8 @@ def __post_init__(self) -> None: def target(self, residue: int) -> int: """Return the exact action target for a declared position.""" + if isinstance(residue, bool) or not isinstance(residue, int): + raise ModularOrbitError("residue must be a nonboolean integer") for source, target in self.action: if source == residue: return target @@ -178,6 +198,8 @@ def target(self, residue: int) -> int: def turn_of(self, residue: int) -> Fraction: """Return the exact normalized-circle turn for a declared position.""" + if isinstance(residue, bool) or not isinstance(residue, int): + raise ModularOrbitError("residue must be a nonboolean integer") for position in self.embedding: if position.residue == residue: return position.turn @@ -309,3 +331,4 @@ def build_modular_orbit_geometry( "ModularOrbitGeometry", "build_modular_orbit_geometry", ] +# ratios: loc_comments=204:82 imports_exports=4:5 calls_definitions=92:10 diff --git a/src/ucns/mpfr_interval.py b/src/ucns/mpfr_interval.py index af3da5e..088109c 100644 --- a/src/ucns/mpfr_interval.py +++ b/src/ucns/mpfr_interval.py @@ -1,3 +1,4 @@ +# ratios: loc_comments=374:65 imports_exports=7:9 calls_definitions=179:48 # === MODULE_BUILD === # id: ucns_mpfr_interval # module_name: mpfr_interval @@ -31,6 +32,16 @@ # then: both prime candidates retain lower endpoints above the declared centerline margin # class: evidence # since: 2026-08-11 +# +# id: mpfr_rejects_nan_ordering +# given: a NaN or uninitialized MPFR value reaches an interval, comparison, or sign query +# then: MPFRError is raised before unordered values can be used as certificate evidence +# class: safety +# +# id: mpfr_rational_inputs_are_exact +# given: a rational interval or MPFR rational number is requested +# then: only nonboolean integers and exact Fractions are accepted without implicit float or string conversion +# class: correctness # === END CONTRACTS === """Minimal outward-rounded interval arithmetic backed directly by system MPFR. @@ -38,6 +49,10 @@ This module deliberately avoids ``mpmath.iv``. It calls ``libmpfr`` through ``ctypes`` and passes an explicit directed rounding mode to every primitive operation used by the P7/P5 separation replay. + +Usage: ``MPInterval.rational(Fraction(1, 3))`` encloses an exact rational; +``MPInterval.decimal("0.1")`` encloses explicit decimal text. NaN is never +ordered evidence, including MPFR's initially unassigned number state. """ from __future__ import annotations @@ -101,6 +116,7 @@ def _bind(name: str, restype: object, *argtypes: object) -> object: _mpfr_atan2 = _bind("mpfr_atan2", ctypes.c_int, _PTR, _PTR, _PTR, ctypes.c_int) _mpfr_const_pi = _bind("mpfr_const_pi", ctypes.c_int, _PTR, ctypes.c_int) _mpfr_cmp = _bind("mpfr_cmp", ctypes.c_int, _PTR, _PTR) +_mpfr_nan_p = _bind("mpfr_nan_p", ctypes.c_int, _PTR) _mpfr_sgn = _bind("mpfr_sgn", ctypes.c_int, _PTR) _mpfr_get_d = _bind("mpfr_get_d", ctypes.c_double, _PTR, ctypes.c_int) _mpfr_get_str = _bind( @@ -168,6 +184,8 @@ def rational( precision: int = DEFAULT_PRECISION_BITS, rounding: int, ) -> "MPNumber": + if isinstance(value, bool) or not isinstance(value, (int, Fraction)): + raise MPFRError("rational input must be a nonboolean int or exact Fraction") item = value if isinstance(value, Fraction) else Fraction(value) numerator = cls.integer(item.numerator, precision=precision) denominator = cls.integer(item.denominator, precision=precision) @@ -186,6 +204,7 @@ def decimal_value( out = cls(precision) if _mpfr_set_str(out.ptr, value.encode("ascii"), 10, rounding) != 0: raise MPFRError(f"invalid MPFR decimal input: {value!r}") + out._require_ordered() return out @classmethod @@ -195,10 +214,17 @@ def pi(cls, *, precision: int = DEFAULT_PRECISION_BITS, rounding: int) -> "MPNum return out def compare(self, other: "MPNumber") -> int: + self._require_ordered() + other._require_ordered() return int(_mpfr_cmp(self.ptr, other.ptr)) + def _require_ordered(self) -> None: + if _mpfr_nan_p(self.ptr): + raise MPFRError("NaN cannot supply ordered interval evidence") + @property def sign(self) -> int: + self._require_ordered() return int(_mpfr_sgn(self.ptr)) def to_float(self, rounding: int = MPFR_RNDN) -> float: @@ -491,3 +517,4 @@ def atan2_interval(y: MPInterval, x: MPInterval) -> MPInterval: down.append(lower) up.append(upper) return MPInterval(_minimum(down), _maximum(up)) +# ratios: loc_comments=374:65 imports_exports=7:9 calls_definitions=179:48 diff --git a/tests/test_distributions.py b/tests/test_distributions.py new file mode 100644 index 0000000..aa054d6 --- /dev/null +++ b/tests/test_distributions.py @@ -0,0 +1,66 @@ +# ratios: loc_comments=45:10 imports_exports=6:1 calls_definitions=26:2 +# === CHECKS === +# id: check_distribution_replay_inputs +# proves: distributions_retain_exact_replay_inputs +# call: self::test_distribution_replay_inputs_fail_closed +# requires: python3, pytest +# timeout: 10 +# mutates: temporary_path +# cleanup: pytest temporary_path +# === END CHECKS === + +"""Build small archive fixtures; missing evidence must fail independently of Twine.""" + +import importlib.util +import io +from pathlib import Path +import tarfile +import zipfile + +import pytest + + +SPEC = importlib.util.spec_from_file_location("distribution_audit", Path(__file__).parents[1] / "tools/verify_distributions.py") +assert SPEC is not None and SPEC.loader is not None +audit = importlib.util.module_from_spec(SPEC) +SPEC.loader.exec_module(audit) + + +def _archives(root, sdist, wheel, *, omit="", altered="", extra=""): + with tarfile.open(sdist, "w:gz") as archive: + for name, data in audit.expected_files(root).items(): + if name == omit: + continue + if name == altered: + data += b"drift" + member = tarfile.TarInfo(f"ucns-0/{name}") + member.size = len(data) + archive.addfile(member, io.BytesIO(data)) + with zipfile.ZipFile(wheel, "w") as archive: + for name, data in audit.expected_files(root).items(): + if name.startswith("src/ucns/"): + archive.writestr(name.removeprefix("src/"), data) + if extra: + archive.writestr(extra, b"unexpected") + + +def test_distribution_replay_inputs_fail_closed(tmp_path: Path) -> None: + root = tmp_path / "repo" + root.mkdir() + for name in (*audit.ROOT_INPUTS, "src/ucns/__init__.py", "generated/receipt.json", "docs/preregistration.md", ".agents/skills/msdmd/parsers/universal.py"): + path = root / name + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text("fixture\n") + sdist, wheel = tmp_path / "ucns.tar.gz", tmp_path / "ucns.whl" + _archives(root, sdist, wheel) + assert audit.verify_distributions(root, sdist, wheel) == [] + for key, name, expected in (("omit", "generated/receipt.json", "missing"), ("altered", "docs/preregistration.md", "altered"), ("extra", "ucns/lexical.py", "unexpected")): + _archives(root, sdist, wheel, **{key: name}) + assert any(expected in problem for problem in audit.verify_distributions(root, sdist, wheel)) + _archives(root, sdist, wheel, extra="../escaped.py") + assert any("unsafe" in problem for problem in audit.verify_distributions(root, sdist, wheel)) + _archives(root, sdist, wheel) + with zipfile.ZipFile(wheel, "a") as archive, pytest.warns(UserWarning, match="Duplicate"): + archive.writestr("ucns/__init__.py", b"duplicate") + assert any("duplicate" in problem for problem in audit.verify_distributions(root, sdist, wheel)) +# ratios: loc_comments=45:10 imports_exports=6:1 calls_definitions=26:2 diff --git a/tests/test_gonal_boundary_trace.py b/tests/test_gonal_boundary_trace.py index a40c89f..95a5428 100644 --- a/tests/test_gonal_boundary_trace.py +++ b/tests/test_gonal_boundary_trace.py @@ -1,3 +1,4 @@ +# ratios: loc_comments=100:51 imports_exports=6:6 calls_definitions=55:7 # === CHECKS === # id: check_gonal_boundary_trace_samples_circle_wave_mode_exactly # proves: gonal_boundary_trace_samples_circle_wave_mode_exactly @@ -140,6 +141,20 @@ def test_trace_bridge_fails_closed() -> None: with pytest.raises(GonalBoundaryTraceError, match="time scale must be a positive integer"): replace(valid, time_scale=2.0) # type: ignore[arg-type] + identity = build_modular_orbit_geometry(3, 1) + trace = build_circle_wave_mode_trace(3, 1) + covering = pullback_circle_wave_trace(trace, identity, 1) + for action in (((False, False), (True, True), (2.0, 2.0)), None, [[0, 0], [1, 1], [2, 2]]): + with pytest.raises(GonalBoundaryTraceError): + replace(covering, action=action) + for query in (True, False, 1.0, Fraction(1), "1", []): + with pytest.raises(GonalBoundaryTraceError): + trace.phase_at(query) + with pytest.raises(GonalBoundaryTraceError): + replace(covering, source=None) + with pytest.raises(GonalBoundaryTraceError): + pullback_circle_wave_trace(trace, None, 1) + def test_trace_surface_is_geometry_only() -> None: geometry = build_modular_orbit_geometry(9, 7, range(1, 9)) @@ -163,3 +178,4 @@ def test_trace_is_candidate_scoped_in_canon() -> None: assert "candidate" in section.lower() assert "not ratified" in section.lower() +# ratios: loc_comments=100:51 imports_exports=6:6 calls_definitions=55:7 diff --git a/tests/test_modular_orbit.py b/tests/test_modular_orbit.py index 9f77665..04d67d5 100644 --- a/tests/test_modular_orbit.py +++ b/tests/test_modular_orbit.py @@ -1,3 +1,4 @@ +# ratios: loc_comments=84:43 imports_exports=5:5 calls_definitions=39:5 # === CHECKS === # id: check_modular_orbit_mod9_times_two # proves: modular_orbit_action_decomposes_exact_permutation, modular_orbit_circle_embedding_is_exact @@ -101,6 +102,23 @@ def test_invalid_carriers_fail_closed() -> None: with pytest.raises(ModularOrbitError, match="noncanonical residue"): replace(valid, positions=(0, [])) # type: ignore[arg-type] + identity = build_modular_orbit_geometry(3, 1) + for patch in ( + {"action": ((False, False), (True, True), (2.0, 2.0))}, + {"orbits": ((False,), (True,), (2.0,))}, + {"orbits": [list(orbit) for orbit in identity.orbits]}, + {"periods": (True, 1.0, 1)}, + {"orbits": None}, + {"orbits": ((0,), None, (2,))}, + ): + with pytest.raises(ModularOrbitError): + replace(identity, **patch) + for query in (True, False, 1.0, Fraction(1), "1", []): + with pytest.raises(ModularOrbitError): + identity.target(query) + with pytest.raises(ModularOrbitError): + identity.turn_of(query) + def test_serialized_surface_contains_geometry_only() -> None: payload = build_modular_orbit_geometry(9, 2, range(1, 9)).as_dict() @@ -129,3 +147,4 @@ def test_modular_orbit_is_candidate_scoped_in_canon() -> None: assert "candidate" in section.lower() assert "not ratified" in section.lower() assert "active cycle primitive" not in section.lower() +# ratios: loc_comments=84:43 imports_exports=5:5 calls_definitions=39:5 diff --git a/tests/test_mpfr_input_boundaries.py b/tests/test_mpfr_input_boundaries.py new file mode 100644 index 0000000..2c56cb3 --- /dev/null +++ b/tests/test_mpfr_input_boundaries.py @@ -0,0 +1,57 @@ +# ratios: loc_comments=29:18 imports_exports=4:2 calls_definitions=20:2 +# === CHECKS === +# id: check_mpfr_nan_is_not_ordered_evidence +# proves: mpfr_rejects_nan_ordering +# call: self::test_nan_cannot_be_an_interval_or_an_ordering_witness +# requires: python3, libmpfr +# timeout: 10 +# mutates: none +# cleanup: none +# +# id: check_mpfr_exact_rational_admission +# proves: mpfr_rational_inputs_are_exact +# call: self::test_rational_admission_preserves_exact_input_boundary +# requires: python3, libmpfr +# timeout: 10 +# mutates: none +# cleanup: none +# === END CHECKS === + +"""Run with pytest tests/test_mpfr_input_boundaries.py; requires system libmpfr.""" + +from fractions import Fraction +import math + +import pytest + +from ucns.mpfr_interval import MPFRError, MPInterval, MPNumber, MPFR_RNDD + + +def test_nan_cannot_be_an_interval_or_an_ordering_witness() -> None: + for value in ("nan", "NaN", "@NaN@"): + with pytest.raises(MPFRError): + MPInterval.decimal(value) + uninitialized = MPNumber() # MPFR initializes a number to NaN. + zero = MPNumber.integer(0) + with pytest.raises(MPFRError): + uninitialized.compare(zero) + with pytest.raises(MPFRError): + zero.compare(uninitialized) + with pytest.raises(MPFRError): + _ = uninitialized.sign + with pytest.raises(MPFRError): + MPInterval(uninitialized, zero) + assert MPInterval.rational(0).lo.sign == 0 + + +def test_rational_admission_preserves_exact_input_boundary() -> None: + for invalid in (True, False, 0.1, "1/3"): + with pytest.raises(MPFRError): + MPInterval.rational(invalid) + for value in (0, -3, Fraction(1, 3), Fraction(-7, 11)): + interval = MPInterval.rational(value) + assert Fraction.from_float(interval.lower_float()) <= value + assert Fraction.from_float(interval.upper_float()) >= value + assert math.isfinite(interval.lower_float()) + assert MPNumber.rational(Fraction(1, 2), rounding=MPFR_RNDD).to_float() == 0.5 +# ratios: loc_comments=29:18 imports_exports=4:2 calls_definitions=20:2 diff --git a/tests/test_skill_lib_boundary_runner.py b/tests/test_skill_lib_boundary_runner.py index 1b556a0..db412a7 100644 --- a/tests/test_skill_lib_boundary_runner.py +++ b/tests/test_skill_lib_boundary_runner.py @@ -1,3 +1,4 @@ +# ratios: loc_comments=94:169 imports_exports=6:13 calls_definitions=56:15 # === CHECKS === # id: check_boundary_runner_audit_gate # proves: boundary_runner_audits_before_execution @@ -48,6 +49,40 @@ # cleanup: pytest temporary_path # === END CHECKS === +# === CHECKS === +# id: check_boundary_runner_skips_are_not_passes +# proves: boundary_runner_classifies_and_continues +# call: self::test_skips_xfails_and_mixed_parameters_are_not_passes +# requires: python3, pytest +# timeout: 30 +# mutates: temporary_path +# cleanup: pytest temporary_path +# +# id: check_boundary_runner_invalid_declaration_continuation +# proves: boundary_runner_classifies_and_continues +# call: self::test_invalid_execution_metadata_does_not_abort_later_checks +# requires: python3, pytest +# timeout: 15 +# mutates: temporary_path +# cleanup: pytest temporary_path +# +# id: check_boundary_runner_source_identity +# proves: boundary_runner_receipt_is_bounded_and_bound +# call: self::test_source_mutation_prevents_acceptance +# requires: python3, pytest +# timeout: 15 +# mutates: temporary_path +# cleanup: pytest temporary_path +# +# id: check_boundary_runner_report_and_discovery_boundaries +# proves: boundary_runner_classifies_and_continues, boundary_runner_receipt_is_bounded_and_bound +# call: self::test_report_errors_suffix_discovery_and_launch_continuation +# requires: python3, pytest +# timeout: 20 +# mutates: temporary_path +# cleanup: pytest temporary_path +# === END CHECKS === + from __future__ import annotations from hashlib import sha256 @@ -155,7 +190,7 @@ def test_pass(): def test_fail(): assert False def test_error(): - raise RuntimeError("broken harness") + raise RuntimeError("AssertionError mentioned by a broken harness") """ checks = [ {"id": "check_pass", "function": "test_pass"}, @@ -167,10 +202,35 @@ def test_error(): "PASS", "FAIL", "ERROR", ] assert receipt["outcome_counts"] == { - "PASS": 1, "FAIL": 1, "ERROR": 1, "TIMEOUT": 0, + "PASS": 1, "FAIL": 1, "ERROR": 1, "TIMEOUT": 0, "SKIP": 0, } +def test_report_errors_suffix_discovery_and_launch_continuation(tmp_path: Path, monkeypatch) -> None: + report = tmp_path / "report.xml" + assert runner._junit_status(report, 0) == "ERROR" + for text in ("not xml", "", ''): + report.write_text(text) + assert runner._junit_status(report, 0) == "ERROR" + root = _repo(tmp_path, "def test_first():\n pass\ndef test_later():\n pass\n", [ + {"id": "check_first", "function": "test_first"}, + {"id": "check_later", "function": "test_later"}, + ]) + (root / "tests/test_feature.py").rename(root / "tests/feature_test.py") + original = runner._run_check + + def launch(repository, check): + if check.id == "check_first": + raise OSError("fixture process launch failed") + return original(repository, check) + + monkeypatch.setattr(runner, "_run_check", launch) + receipt = runner.run_boundaries(root) + assert [outcome["status"] for outcome in receipt["outcomes"]] == ["ERROR", "PASS"] + assert receipt["outcomes"][0]["diagnostic"].startswith("OSError:") + assert receipt["status"] == "not-passed" + + def test_receipt_binds_declarations_outputs_and_identity(tmp_path: Path) -> None: root = _repo(tmp_path, "def test_output():\n print('evidence')\n", [{ "id": "check_output", "function": "test_output", @@ -197,3 +257,39 @@ def test_passing_receipt_has_no_activation_or_selection_effect(tmp_path: Path) - assert receipt["selection_effect"] == "none" assert receipt["edcm_activation"] == "inactive" assert receipt["canon_status"] == "none" + + +def test_skips_xfails_and_mixed_parameters_are_not_passes(tmp_path: Path) -> None: + bodies = ( + "import pytest\ndef test_probe():\n pytest.skip('not observed')\n", + "import pytest\n@pytest.mark.xfail(reason='broken')\ndef test_probe():\n assert False\n", + "import pytest\n@pytest.mark.parametrize('x', [1, 2])\ndef test_probe(x):\n if x == 2: pytest.skip('partial')\n", + ) + for index, body in enumerate(bodies): + root = _repo(tmp_path / str(index), body, [{"id": "check_probe", "function": "test_probe"}]) + receipt = runner.run_boundaries(root) + assert receipt["status"] == "not-passed" + assert receipt["outcomes"][0]["status"] == "SKIP" + assert runner.run_boundaries(tmp_path / "absent")["status"] == "audit-gap" + + +def test_invalid_execution_metadata_does_not_abort_later_checks(tmp_path: Path) -> None: + root = _repo(tmp_path, "def test_bad():\n pass\ndef test_good():\n pass\n", [ + {"id": "check_bad", "function": "test_bad", "timeout": "not-an-integer"}, + {"id": "check_good", "function": "test_good"}, + ]) + receipt = runner.run_boundaries(root) + assert [outcome["status"] for outcome in receipt["outcomes"]] == ["ERROR", "PASS"] + assert receipt["status"] == "not-passed" + + +def test_source_mutation_prevents_acceptance(tmp_path: Path) -> None: + root = _repo(tmp_path, "from pathlib import Path\ndef test_probe():\n p=Path('src/pkg/feature.py')\n p.write_text(p.read_text()+'\\n# mutation\\n')\n", [ + {"id": "check_probe", "function": "test_probe"}, + ]) + receipt = runner.run_boundaries(root) + assert receipt["outcomes"][0]["status"] == "PASS" + assert receipt["status"] == "not-passed" + assert receipt["source_before_sha256"] != receipt["source_after_sha256"] + assert receipt["source_unchanged"] is False +# ratios: loc_comments=94:169 imports_exports=6:13 calls_definitions=56:15 diff --git a/tests/test_skill_lib_contracts.py b/tests/test_skill_lib_contracts.py index 56bef47..2a99b90 100644 --- a/tests/test_skill_lib_contracts.py +++ b/tests/test_skill_lib_contracts.py @@ -1,3 +1,4 @@ +# ratios: loc_comments=76:42 imports_exports=5:5 calls_definitions=38:5 # === CHECKS === # id: check_contract_audit_no_exec # proves: contract_audit_is_no_exec @@ -24,6 +25,24 @@ # cleanup: none # === END CHECKS === +# === CHECKS === +# id: check_contract_audit_empty_syntax_and_class_gaps +# proves: contract_audit_reports_graph_gaps +# call: self::test_empty_syntax_and_class_coverage_are_not_closed +# requires: python3 +# timeout: 10 +# mutates: filesystem +# cleanup: tempdir_teardown +# +# id: check_contract_audit_canonical_parser +# proves: contract_audit_reports_graph_gaps, contract_audit_is_no_exec +# call: self::test_nested_fences_cannot_hide_an_obligation +# requires: python3 +# timeout: 10 +# mutates: filesystem +# cleanup: tempdir_teardown +# === END CHECKS === + from pathlib import Path from tempfile import TemporaryDirectory @@ -86,3 +105,32 @@ def test_contract_audit_detects_gaps() -> None: def test_repository_contract_graph() -> None: ok, problems = audit_repository(ROOT) assert ok, "\n".join(problems) + + +def test_empty_syntax_and_class_coverage_are_not_closed(tmp_path: Path) -> None: + from test_skill_lib_boundary_runner import _repo + + ok, problems = audit_repository(tmp_path) + assert not ok and any("empty" in item for item in problems) + root = _repo(tmp_path, "def test_probe():\n pass\n", [{"id": "check_probe", "function": "test_probe"}]) + source = root / "src/pkg/feature.py" + original = source.read_text() + source.write_text(original + "\ndef syntax broken\n") + ok, problems = audit_repository(root) + assert not ok and any("syntax" in item.lower() for item in problems) + source.write_text(original) + (root / "tests/test_class.py").write_text("class TestUnregistered:\n def test_untracked(self):\n assert False\n") + ok, problems = audit_repository(root) + assert not ok and any("TestUnregistered::test_untracked" in item for item in problems) + + +def test_nested_fences_cannot_hide_an_obligation(tmp_path: Path) -> None: + from test_skill_lib_boundary_runner import _repo + + root = _repo(tmp_path, "def test_probe():\n pass\n", [{"id": "check_probe", "function": "test_probe"}]) + source = root / "src/pkg/feature.py" + original = source.read_text() + source.write_text(original.replace("# === CONTRACTS ===", "# === CONTRACTS ===\n# id: hidden\n# given: x\n# then: y\n# === CONTRACTS ===")) + ok, problems = audit_repository(root) + assert not ok and any("nested" in item for item in problems) +# ratios: loc_comments=76:42 imports_exports=5:5 calls_definitions=38:5 diff --git a/tools/run_skill_lib_boundaries.py b/tools/run_skill_lib_boundaries.py index 9471473..3610e41 100644 --- a/tools/run_skill_lib_boundaries.py +++ b/tools/run_skill_lib_boundaries.py @@ -1,3 +1,4 @@ +# ratios: loc_comments=261:61 imports_exports=18:4 calls_definitions=108:15 # === MODULE_BUILD === # id: skill_lib_boundary_runner # module_name: run_skill_lib_boundaries @@ -34,13 +35,13 @@ # # id: boundary_runner_classifies_and_continues # given: one declared check passes, fails an assertion, raises unexpectedly, or times out -# then: the runner records PASS, FAIL, ERROR, or TIMEOUT respectively and continues with remaining selected checks +# then: the runner records PASS, FAIL, ERROR, TIMEOUT, or SKIP from machine-readable outcomes and continues after per-check harness errors; absent or skipped evidence never passes # class: evidence # since: 2026-08-15 # # id: boundary_runner_receipt_is_bounded_and_bound # given: a boundary run completes -# then: its receipt binds declarations, commands, capabilities, outcomes, output digests, declared mutation and cleanup, bounded output excerpts, and an identity digest +# then: its receipt binds source/declaration digests before and after execution, commands, capabilities, outcomes, declared mutation and cleanup, and bounded output; source mutation prevents acceptance # class: evidence # since: 2026-08-15 # @@ -51,7 +52,13 @@ # since: 2026-08-15 # === END CONTRACTS === -"""Execute UCNS skill-lib ``CHECKS`` declarations as bounded processes.""" +"""Execute UCNS skill-lib ``CHECKS`` declarations as bounded processes. + +Usage: ``python tools/run_skill_lib_boundaries.py . --check CHECK_ID +--receipt /tmp/receipt.json``. Schema v2 requires actual, unskipped JUnit +evidence and unchanged source bytes. Receipts are not theorem or freshness +certificates. Timeouts retain the existing declared execution-safety boundary. +""" from __future__ import annotations @@ -69,6 +76,7 @@ import sys import tempfile import time +import xml.etree.ElementTree as ET from typing import Iterable, Sequence TOOLS_DIRECTORY = Path(__file__).resolve().parent @@ -79,7 +87,7 @@ SCHEMA_ID = "ucns.skill-lib-boundary-run-receipt" -SCHEMA_VERSION = "1.0.0" +SCHEMA_VERSION = "2.0.0" MAX_EXCERPT_BYTES = 16_384 ALLOWED_MUTATIONS = {"none", "filesystem", "temporary_path"} ALLOWED_CLEANUPS = {"none", "tempdir_teardown", "pytest temporary_path"} @@ -106,6 +114,7 @@ class CheckOutcome: stdout_excerpt: str stderr_excerpt: str missing_capabilities: tuple[str, ...] = () + diagnostic: str = "" def _sha(data: bytes) -> str: @@ -134,8 +143,9 @@ def _capability_available(name: str) -> bool: def _declared_checks(root: Path) -> tuple[Entry, ...]: checks: list[Entry] = [] - for path in sorted((root / "tests").rglob("test_*.py")): - checks.extend(entry for entry in parse_blocks(path) if entry.block == "CHECKS") + for path in sorted((root / "tests").rglob("*.py")): + if "__pycache__" not in path.parts and (path.name.startswith("test_") or path.name.endswith("_test.py")): + checks.extend(entry for entry in parse_blocks(path) if entry.block == "CHECKS") return tuple(checks) @@ -160,12 +170,62 @@ def _validate_check(check: Entry) -> tuple[tuple[str, ...], int, str, str]: def _excerpt(path: Path) -> tuple[str, int, str]: - data = path.read_bytes() - excerpt = data[:MAX_EXCERPT_BYTES] + digest = sha256() + size = 0 + excerpt = b"" + with path.open("rb") as stream: + for chunk in iter(lambda: stream.read(65_536), b""): + digest.update(chunk) + size += len(chunk) + excerpt += chunk[:max(0, MAX_EXCERPT_BYTES - len(excerpt))] text = excerpt.decode("utf-8", errors="replace") - if len(data) > len(excerpt): - text += f"\n[truncated {len(data) - len(excerpt)} bytes]" - return _sha(data), len(data), text + if size > len(excerpt): + text += f"\n[truncated {size - len(excerpt)} bytes]" + return digest.hexdigest(), size, text + + +def _junit_status(path: Path, returncode: int) -> str: + """Classify actual test cases, not console wording or exit zero alone.""" + try: + cases = tuple(ET.parse(path).getroot().iter("testcase")) + except (OSError, ET.ParseError): + return "ERROR" + if not cases or any(case.find("error") is not None for case in cases): + return "ERROR" + failures = [failure for case in cases for failure in case.findall("failure")] + if failures: + # pytest's JUnit failure message retains the actual exception type. + # A RuntimeError merely mentioning AssertionError is not a contract FAIL. + assertion_messages = ("assert ", "AssertionError", "Failed:") + return "FAIL" if all(f.get("message", "").startswith(assertion_messages) for f in failures) else "ERROR" + if any(case.find("skipped") is not None for case in cases): + return "SKIP" + return "PASS" if returncode == 0 else "ERROR" + + +def _source_snapshot(root: Path) -> tuple[dict[str, str], str]: + """Bind repository-owned execution inputs, excluding caches and secrets.""" + suffixes = {".py", ".md", ".json", ".jsonl", ".ts", ".svg", ".yml", ".yaml"} + paths = { + path for directory in ("src", "tools", "tests", "docs", "generated", ".agents/skills", ".github/workflows") + for path in (root / directory).rglob("*") + if path.is_file() and path.suffix in suffixes and "__pycache__" not in path.parts + } + paths.update(root / name for name in ("pyproject.toml", "uv.lock", "pytest.ini", "setup.cfg", "MANIFEST.in", "conftest.py", "CANON.md", "AGENTS.md") if (root / name).is_file()) + inventory = {path.relative_to(root).as_posix(): _sha(path.read_bytes()) for path in sorted(paths)} + return inventory, _sha(json.dumps(inventory, sort_keys=True, separators=(",", ":")).encode()) + + +def _error_outcome(root: Path, check: Entry, error: Exception) -> CheckOutcome: + empty = _sha(b"") + return CheckOutcome( + check.id, check.source.relative_to(root).as_posix(), + _split(check.fields.get("proves", "")), check.fields.get("call", ""), (), + _split(check.fields.get("requires", "")), 0, + check.fields.get("mutates", ""), check.fields.get("cleanup", ""), + "ERROR", None, 0.0, empty, empty, 0, 0, "", "", + diagnostic=f"{type(error).__name__}: {error}", + ) def _run_check(root: Path, check: Entry) -> CheckOutcome: @@ -188,6 +248,8 @@ def _run_check(root: Path, check: Entry) -> CheckOutcome: with tempfile.TemporaryDirectory(prefix="ucns-boundary-") as temporary: stdout_path = Path(temporary) / "stdout" stderr_path = Path(temporary) / "stderr" + junit_path = Path(temporary) / "outcomes.xml" + command = (*command, f"--junitxml={junit_path}", "-o", "xfail_strict=true") with stdout_path.open("wb") as stdout, stderr_path.open("wb") as stderr: process = subprocess.Popen( command, cwd=root, stdin=subprocess.DEVNULL, @@ -205,16 +267,9 @@ def _run_check(root: Path, check: Entry) -> CheckOutcome: returncode = process.wait() stdout_sha, stdout_bytes, stdout_excerpt = _excerpt(stdout_path) stderr_sha, stderr_bytes, stderr_excerpt = _excerpt(stderr_path) + status = "TIMEOUT" if timed_out else _junit_status(junit_path, returncode) duration = round(time.monotonic() - started, 6) - if timed_out: - status = "TIMEOUT" - elif returncode == 0: - status = "PASS" - elif returncode == 1 and "AssertionError" in (stdout_excerpt + stderr_excerpt): - status = "FAIL" - else: - status = "ERROR" return CheckOutcome( check.id, relative_source, _split(check.fields["proves"]), call, command, requires, timeout, mutates, cleanup, status, returncode, @@ -236,6 +291,7 @@ def run_boundaries( root: Path, *, selected_ids: Iterable[str] = (), ) -> dict[str, object]: root = root.resolve() + source_files, source_before = _source_snapshot(root) audit_ok, gaps = audit_repository(root) if not audit_ok: receipt: dict[str, object] = { @@ -256,16 +312,26 @@ def run_boundaries( selected = checks if not requested else tuple( check for check in checks if check.id in requested ) - outcomes = tuple(_run_check(root, check) for check in selected) + outcomes = [] + for check in selected: + try: + outcomes.append(_run_check(root, check)) + except (ValueError, OSError) as error: + outcomes.append(_error_outcome(root, check, error)) + _, source_after = _source_snapshot(root) statuses = {outcome.status for outcome in outcomes} receipt: dict[str, object] = { "schema_id": SCHEMA_ID, "schema_version": SCHEMA_VERSION, - "status": "passed" if statuses <= {"PASS"} else "not-passed", + "status": "passed" if outcomes and statuses == {"PASS"} and source_before == source_after else "not-passed", "audit_closed": True, "audit_gaps": [], + "source_files_sha256": source_files, + "source_before_sha256": source_before, + "source_after_sha256": source_after, + "source_unchanged": source_before == source_after, "selected_check_ids": [outcome.check_id for outcome in outcomes], "outcome_counts": { key: sum(outcome.status == key for outcome in outcomes) - for key in ("PASS", "FAIL", "ERROR", "TIMEOUT") + for key in ("PASS", "FAIL", "ERROR", "TIMEOUT", "SKIP") }, "outcomes": [asdict(outcome) for outcome in outcomes], "selection_effect": "none", "edcm_activation": "inactive", @@ -299,3 +365,4 @@ def main(argv: Sequence[str] | None = None) -> int: if __name__ == "__main__": raise SystemExit(main()) +# ratios: loc_comments=261:61 imports_exports=18:4 calls_definitions=108:15 diff --git a/tools/verify_distributions.py b/tools/verify_distributions.py new file mode 100644 index 0000000..0241128 --- /dev/null +++ b/tools/verify_distributions.py @@ -0,0 +1,140 @@ +# ratios: loc_comments=87:32 imports_exports=5:4 calls_definitions=49:5 +# === MODULE_BUILD === +# id: ucns_distribution_audit +# module_name: verify_distributions +# module_kind: instrument +# summary: checks built UCNS source and wheel archive bytes against repository-owned replay inputs +# owner: Erin Spencer +# public_surface: verify_distributions, command-line audit +# internal_surface: expected_files, read_archive +# auth_boundary: none +# storage_boundary: read-only repository and archives +# network_boundary: none +# user_data_boundary: none +# admin_only: false +# tests: tests/test_distributions.py +# rollout: CI artifact gate after build +# rollback: remove tool, test, and CI invocation together +# since: 2026-09-11 +# unresolved: semantic validity of archived certificates and independent wheel runtime replay +# === END MODULE_BUILD === + +# === CONTRACTS === +# id: distributions_retain_exact_replay_inputs +# given: a built sdist and wheel are compared to a UCNS source tree +# then: missing or altered required inputs, unsafe archive members, duplicate members, and unexpected wheel payloads fail without extraction or execution +# class: evidence +# since: 2026-09-11 +# === END CONTRACTS === + +"""Usage: ``python tools/verify_distributions.py . dist`` after a clean build. + +The wheel contains the package; the sdist also contains the tests, their +evidence, preregistrations, tools, and vendored parser. Matching archive bytes +is packaging evidence only, not certificate verification or ratification. +""" + +from __future__ import annotations + +import argparse +from pathlib import Path, PurePosixPath +import tarfile +import zipfile + + +ROOT_INPUTS = ("pyproject.toml", "README.md", "AGENTS.md", "CANON.md", "CLAUDE.md", "uv.lock", "MANIFEST.in") +TREE_INPUTS = { + "src/ucns": {".py"}, "tests": {".py"}, "tools": {".py"}, + "docs": {".md", ".json", ".jsonl", ".svg"}, "generated": {".json"}, + ".agents/skills": {".md", ".json", ".py", ".ts"}, +} + + +def expected_files(root: Path) -> dict[str, bytes]: + paths = {root / name for name in ROOT_INPUTS} + for directory, suffixes in TREE_INPUTS.items(): + paths.update(path for path in (root / directory).rglob("*") + if path.is_file() and path.suffix in suffixes and "__pycache__" not in path.parts) + if not any(path.is_relative_to(root / "src/ucns") for path in paths): + raise ValueError("missing UCNS package source") + return {path.relative_to(root).as_posix(): path.read_bytes() for path in sorted(paths)} + + +def read_archive(path: Path, *, wheel: bool) -> dict[str, bytes]: + """Read regular files only; reject ambiguous names rather than extracting.""" + files: dict[str, bytes] = {} + prefixes: set[str] = set() + + def record(name: str, data: bytes) -> None: + parts = PurePosixPath(name).parts + if not parts or name.startswith("/") or ".." in parts or "\\" in name: + raise ValueError(f"unsafe archive name: {name}") + if not wheel: + prefixes.add(parts[0]) + if len(parts) < 2 or len(prefixes) != 1: + raise ValueError("sdist must have one enclosing directory") + name = "/".join(parts[1:]) + if name in files: + raise ValueError(f"duplicate archive member: {name}") + files[name] = data + + if wheel: + with zipfile.ZipFile(path) as archive: + for member in archive.infolist(): + if not member.is_dir(): + if (member.external_attr >> 16) & 0o170000 == 0o120000: + raise ValueError(f"archive symlink: {member.filename}") + record(member.filename, archive.read(member)) + else: + with tarfile.open(path, "r:gz") as archive: + for member in archive: + if member.isdir(): + continue + if not member.isfile(): + raise ValueError(f"non-regular archive member: {member.name}") + stream = archive.extractfile(member) + if stream is None: + raise ValueError(f"unreadable archive member: {member.name}") + with stream: + record(member.name, stream.read()) + return files + + +def verify_distributions(root: Path, sdist: Path, wheel: Path) -> list[str]: + expected = expected_files(root.resolve()) + wheel_expected = {name.removeprefix("src/"): data for name, data in expected.items() if name.startswith("src/ucns/")} + problems = [] + for path, inputs, is_wheel in ((sdist, expected, False), (wheel, wheel_expected, True)): + try: + actual = read_archive(path, wheel=is_wheel) + except (OSError, ValueError, tarfile.TarError, zipfile.BadZipFile) as error: + problems.append(f"{path.name}: {error}") + continue + for name, data in inputs.items(): + if name not in actual: + problems.append(f"{path.name}: missing {name}") + elif actual[name] != data: + problems.append(f"{path.name}: altered {name}") + for name in actual.keys() - inputs.keys(): + metadata = name.split("/", 1)[0].endswith(".dist-info") + if (is_wheel and not metadata) or (not is_wheel and name.startswith("src/ucns/")): + problems.append(f"{path.name}: unexpected payload {name}") + return problems + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("root", type=Path) + parser.add_argument("dist", type=Path) + args = parser.parse_args() + sdists, wheels = sorted(args.dist.glob("*.tar.gz")), sorted(args.dist.glob("*.whl")) + if len(sdists) != 1 or len(wheels) != 1: + parser.error("expected exactly one sdist and one wheel; use a clean output directory") + problems = verify_distributions(args.root, sdists[0], wheels[0]) + print("\n".join(problems) if problems else "distribution replay inputs: exact") + return int(bool(problems)) + + +if __name__ == "__main__": + raise SystemExit(main()) +# ratios: loc_comments=87:32 imports_exports=5:4 calls_definitions=49:5 diff --git a/tools/verify_skill_lib_contracts.py b/tools/verify_skill_lib_contracts.py index bd3c47c..090932f 100644 --- a/tools/verify_skill_lib_contracts.py +++ b/tools/verify_skill_lib_contracts.py @@ -1,3 +1,4 @@ +# ratios: loc_comments=181:45 imports_exports=8:4 calls_definitions=72:8 # === MODULE_BUILD === # id: skill_lib_contract_audit # module_name: verify_skill_lib_contracts @@ -27,7 +28,7 @@ # # id: contract_audit_reports_graph_gaps # given: a contract, check target, or self call is missing or unknown -# then: the audit reports the gap and exits nonzero +# then: the audit reports the gap and exits nonzero, including empty input, malformed syntax/fences, and unsupported class-based tests # class: evidence # since: 2026-07-21 # @@ -38,15 +39,17 @@ # since: 2026-07-21 # === END CONTRACTS === -"""Minimal no-exec skill-lib contract graph audit. +"""No-exec contract reconciliation using the vendored canonical msdmd parser. -The parser is intentionally bounded to the line-oriented msdmd fields used by -this repository. It does not replace skill-lib's canonical universal parser. +Usage: ``python tools/verify_skill_lib_contracts.py .``. Product and test files +are parsed, never imported. Only the pinned parser shipped beside this tool is +loaded. Unsupported class-based test targets are visible gaps, not coverage. """ from __future__ import annotations import ast +import importlib.util import re import sys from dataclasses import dataclass @@ -55,7 +58,12 @@ BLOCK_RE = re.compile(r"^\s*#\s*===\s*(MODULE_BUILD|CONTRACTS|CHECKS)\s*===\s*$") END_RE = re.compile(r"^\s*#\s*===\s*END\s+(MODULE_BUILD|CONTRACTS|CHECKS)\s*===\s*$") -FIELD_RE = re.compile(r"^\s*#\s*(?P[A-Za-z_][\w-]*):\s*(?P.*)$") +PARSER_PATH = Path(__file__).resolve().parents[1] / ".agents/skills/msdmd/parsers/universal.py" +_SPEC = importlib.util.spec_from_file_location("_ucns_canonical_msdmd", PARSER_PATH) +if _SPEC is None or _SPEC.loader is None: + raise RuntimeError("canonical msdmd parser unavailable") +_PARSER = importlib.util.module_from_spec(_SPEC) +_SPEC.loader.exec_module(_PARSER) REQUIRED_MODULE_FIELDS = { "module_name", "module_kind", @@ -90,45 +98,32 @@ def id(self) -> str: def _source_files(root: Path) -> Iterable[Path]: for base in (root / "src", root / "tools", root / "tests"): if base.exists(): - yield from sorted(base.rglob("*.py")) + yield from (path for path in sorted(base.rglob("*.py")) if "__pycache__" not in path.parts) def parse_blocks(path: Path) -> List[Entry]: - entries: List[Entry] = [] + """Check fence integrity, then delegate entry grammar to canonical msdmd.""" + text = path.read_text(encoding="utf-8") active: str | None = None - current: Dict[str, str] | None = None - - for raw in path.read_text(encoding="utf-8").splitlines(): + for raw in text.splitlines(): start = BLOCK_RE.match(raw) if start: + if active is not None: + raise ValueError(f"{path}: nested {start.group(1)} inside {active}") active = start.group(1) - current = None continue end = END_RE.match(raw) if end: if active != end.group(1): raise ValueError(f"{path}: mismatched END {end.group(1)}") - if current: - entries.append(Entry(active, path, current)) active = None - current = None - continue - if active is None: - continue - field = FIELD_RE.match(raw) - if not field: - continue - key, value = field.group("key"), field.group("value").strip() - if key == "id": - if current: - entries.append(Entry(active, path, current)) - current = {"id": value} - elif current is not None: - current[key] = value - if active is not None: raise ValueError(f"{path}: unterminated {active} block") - return entries + return [ + Entry(block, path, fields) + for block in ("MODULE_BUILD", "CONTRACTS", "CHECKS") + for fields in _PARSER.parse_text(text, block) + ] def _defined_functions(path: Path) -> Set[str]: @@ -147,13 +142,19 @@ def _missing(fields: Dict[str, str], required: Set[str]) -> Set[str]: def audit_repository(root: Path) -> Tuple[bool, List[str]]: + root = root.resolve() entries: List[Entry] = [] problems: List[str] = [] - for path in _source_files(root): + paths = tuple(_source_files(root)) + trees: Dict[Path, ast.Module] = {} + if not paths: + problems.append(f"GAP empty source/test tree: {root}") + for path in paths: try: + trees[path] = ast.parse(path.read_text(encoding="utf-8"), filename=str(path)) path_entries = parse_blocks(path) - except (SyntaxError, ValueError) as exc: - problems.append(f"GAP parse {exc}") + except (OSError, UnicodeError, SyntaxError, ValueError) as exc: + problems.append(f"GAP parse {type(exc).__name__}: {exc}") continue entries.extend(path_entries) @@ -182,6 +183,8 @@ def audit_repository(root: Path) -> Tuple[bool, List[str]]: contracts = {entry.id: entry for entry in entries if entry.block == "CONTRACTS"} checks = [entry for entry in entries if entry.block == "CHECKS"] + if not contracts or not checks: + problems.append("GAP empty contract/check graph cannot establish evidence") proved: Set[str] = set() for check in checks: @@ -204,7 +207,7 @@ def audit_repository(root: Path) -> Tuple[bool, List[str]]: else: is_test_module = ( check.source.is_relative_to(root / "tests") - and check.source.name.startswith("test_") + and (check.source.name.startswith("test_") or check.source.name.endswith("_test.py")) ) if not is_test_module or not name.startswith("test_"): problems.append( @@ -216,15 +219,26 @@ def audit_repository(root: Path) -> Tuple[bool, List[str]]: for contract_id in sorted(set(contracts) - proved): problems.append(f"GAP {contract_id} has no CHECKS entry claiming to prove it") - for test_path in sorted((root / "tests").rglob("test_*.py")) if (root / "tests").exists() else (): + for test_path, tree in trees.items(): + if not test_path.is_relative_to(root / "tests") or not ( + test_path.name.startswith("test_") or test_path.name.endswith("_test.py") + ): + continue declared_calls = { entry.fields.get("call", "")[len("self::") :] for entry in checks if entry.source == test_path and entry.fields.get("call", "").startswith("self::") } - for function in _defined_functions(test_path): + for function in (node.name for node in tree.body if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef))): if function.startswith("test_") and function not in declared_calls: problems.append(f"GAP executable check {test_path}::{function} has no resolving CHECKS declaration") + for cls in (node for node in ast.walk(tree) if isinstance(node, ast.ClassDef)): + for method in cls.body: + if isinstance(method, (ast.FunctionDef, ast.AsyncFunctionDef)) and method.name.startswith("test_"): + problems.append( + f"GAP unsupported class check {test_path}::{cls.name}::{method.name}; " + "use a declared top-level self::test_fn witness" + ) return not problems, problems @@ -243,3 +257,4 @@ def main(argv: Sequence[str] | None = None) -> int: if __name__ == "__main__": raise SystemExit(main()) +# ratios: loc_comments=181:45 imports_exports=8:4 calls_definitions=72:8 diff --git a/uv.lock b/uv.lock index e1aaa89..989d2f2 100644 --- a/uv.lock +++ b/uv.lock @@ -805,70 +805,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/de/3d/8161f7711c017e01ac9f008dfddd9410dff3674334c233bde66e7ba65bbf/pywin32_ctypes-0.2.3-py3-none-any.whl", hash = "sha256:8a1513379d709975552d202d942d9837758905c8d01eb82b8bcc30918929e7b8", size = 30756, upload-time = "2024-08-14T10:15:33.187Z" }, ] -[[package]] -name = "pyyaml" -version = "6.0.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/05/8e/961c0007c59b8dd7729d542c61a4d537767a59645b82a0b521206e1e25c2/pyyaml-6.0.3.tar.gz", hash = "sha256:d76623373421df22fb4cf8817020cbb7ef15c725b9d5e45f17e189bfc384190f", size = 130960, upload-time = "2025-09-25T21:33:16.546Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f4/a0/39350dd17dd6d6c6507025c0e53aef67a9293a6d37d3511f23ea510d5800/pyyaml-6.0.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:214ed4befebe12df36bcc8bc2b64b396ca31be9304b8f59e25c11cf94a4c033b", size = 184227, upload-time = "2025-09-25T21:31:46.04Z" }, - { url = "https://files.pythonhosted.org/packages/05/14/52d505b5c59ce73244f59c7a50ecf47093ce4765f116cdb98286a71eeca2/pyyaml-6.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02ea2dfa234451bbb8772601d7b8e426c2bfa197136796224e50e35a78777956", size = 174019, upload-time = "2025-09-25T21:31:47.706Z" }, - { url = "https://files.pythonhosted.org/packages/43/f7/0e6a5ae5599c838c696adb4e6330a59f463265bfa1e116cfd1fbb0abaaae/pyyaml-6.0.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b30236e45cf30d2b8e7b3e85881719e98507abed1011bf463a8fa23e9c3e98a8", size = 740646, upload-time = "2025-09-25T21:31:49.21Z" }, - { url = "https://files.pythonhosted.org/packages/2f/3a/61b9db1d28f00f8fd0ae760459a5c4bf1b941baf714e207b6eb0657d2578/pyyaml-6.0.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:66291b10affd76d76f54fad28e22e51719ef9ba22b29e1d7d03d6777a9174198", size = 840793, upload-time = "2025-09-25T21:31:50.735Z" }, - { url = "https://files.pythonhosted.org/packages/7a/1e/7acc4f0e74c4b3d9531e24739e0ab832a5edf40e64fbae1a9c01941cabd7/pyyaml-6.0.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9c7708761fccb9397fe64bbc0395abcae8c4bf7b0eac081e12b809bf47700d0b", size = 770293, upload-time = "2025-09-25T21:31:51.828Z" }, - { url = "https://files.pythonhosted.org/packages/8b/ef/abd085f06853af0cd59fa5f913d61a8eab65d7639ff2a658d18a25d6a89d/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:418cf3f2111bc80e0933b2cd8cd04f286338bb88bdc7bc8e6dd775ebde60b5e0", size = 732872, upload-time = "2025-09-25T21:31:53.282Z" }, - { url = "https://files.pythonhosted.org/packages/1f/15/2bc9c8faf6450a8b3c9fc5448ed869c599c0a74ba2669772b1f3a0040180/pyyaml-6.0.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:5e0b74767e5f8c593e8c9b5912019159ed0533c70051e9cce3e8b6aa699fcd69", size = 758828, upload-time = "2025-09-25T21:31:54.807Z" }, - { url = "https://files.pythonhosted.org/packages/a3/00/531e92e88c00f4333ce359e50c19b8d1de9fe8d581b1534e35ccfbc5f393/pyyaml-6.0.3-cp310-cp310-win32.whl", hash = "sha256:28c8d926f98f432f88adc23edf2e6d4921ac26fb084b028c733d01868d19007e", size = 142415, upload-time = "2025-09-25T21:31:55.885Z" }, - { url = "https://files.pythonhosted.org/packages/2a/fa/926c003379b19fca39dd4634818b00dec6c62d87faf628d1394e137354d4/pyyaml-6.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:bdb2c67c6c1390b63c6ff89f210c8fd09d9a1217a465701eac7316313c915e4c", size = 158561, upload-time = "2025-09-25T21:31:57.406Z" }, - { url = "https://files.pythonhosted.org/packages/6d/16/a95b6757765b7b031c9374925bb718d55e0a9ba8a1b6a12d25962ea44347/pyyaml-6.0.3-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:44edc647873928551a01e7a563d7452ccdebee747728c1080d881d68af7b997e", size = 185826, upload-time = "2025-09-25T21:31:58.655Z" }, - { url = "https://files.pythonhosted.org/packages/16/19/13de8e4377ed53079ee996e1ab0a9c33ec2faf808a4647b7b4c0d46dd239/pyyaml-6.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:652cb6edd41e718550aad172851962662ff2681490a8a711af6a4d288dd96824", size = 175577, upload-time = "2025-09-25T21:32:00.088Z" }, - { url = "https://files.pythonhosted.org/packages/0c/62/d2eb46264d4b157dae1275b573017abec435397aa59cbcdab6fc978a8af4/pyyaml-6.0.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10892704fc220243f5305762e276552a0395f7beb4dbf9b14ec8fd43b57f126c", size = 775556, upload-time = "2025-09-25T21:32:01.31Z" }, - { url = "https://files.pythonhosted.org/packages/10/cb/16c3f2cf3266edd25aaa00d6c4350381c8b012ed6f5276675b9eba8d9ff4/pyyaml-6.0.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:850774a7879607d3a6f50d36d04f00ee69e7fc816450e5f7e58d7f17f1ae5c00", size = 882114, upload-time = "2025-09-25T21:32:03.376Z" }, - { url = "https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b8bb0864c5a28024fac8a632c443c87c5aa6f215c0b126c449ae1a150412f31d", size = 806638, upload-time = "2025-09-25T21:32:04.553Z" }, - { url = "https://files.pythonhosted.org/packages/dd/6f/529b0f316a9fd167281a6c3826b5583e6192dba792dd55e3203d3f8e655a/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1d37d57ad971609cf3c53ba6a7e365e40660e3be0e5175fa9f2365a379d6095a", size = 767463, upload-time = "2025-09-25T21:32:06.152Z" }, - { url = "https://files.pythonhosted.org/packages/f2/6a/b627b4e0c1dd03718543519ffb2f1deea4a1e6d42fbab8021936a4d22589/pyyaml-6.0.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:37503bfbfc9d2c40b344d06b2199cf0e96e97957ab1c1b546fd4f87e53e5d3e4", size = 794986, upload-time = "2025-09-25T21:32:07.367Z" }, - { url = "https://files.pythonhosted.org/packages/45/91/47a6e1c42d9ee337c4839208f30d9f09caa9f720ec7582917b264defc875/pyyaml-6.0.3-cp311-cp311-win32.whl", hash = "sha256:8098f252adfa6c80ab48096053f512f2321f0b998f98150cea9bd23d83e1467b", size = 142543, upload-time = "2025-09-25T21:32:08.95Z" }, - { url = "https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:9f3bfb4965eb874431221a3ff3fdcddc7e74e3b07799e0e84ca4a0f867d449bf", size = 158763, upload-time = "2025-09-25T21:32:09.96Z" }, - { url = "https://files.pythonhosted.org/packages/d1/33/422b98d2195232ca1826284a76852ad5a86fe23e31b009c9886b2d0fb8b2/pyyaml-6.0.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7f047e29dcae44602496db43be01ad42fc6f1cc0d8cd6c83d342306c32270196", size = 182063, upload-time = "2025-09-25T21:32:11.445Z" }, - { url = "https://files.pythonhosted.org/packages/89/a0/6cf41a19a1f2f3feab0e9c0b74134aa2ce6849093d5517a0c550fe37a648/pyyaml-6.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:fc09d0aa354569bc501d4e787133afc08552722d3ab34836a80547331bb5d4a0", size = 173973, upload-time = "2025-09-25T21:32:12.492Z" }, - { url = "https://files.pythonhosted.org/packages/ed/23/7a778b6bd0b9a8039df8b1b1d80e2e2ad78aa04171592c8a5c43a56a6af4/pyyaml-6.0.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9149cad251584d5fb4981be1ecde53a1ca46c891a79788c0df828d2f166bda28", size = 775116, upload-time = "2025-09-25T21:32:13.652Z" }, - { url = "https://files.pythonhosted.org/packages/65/30/d7353c338e12baef4ecc1b09e877c1970bd3382789c159b4f89d6a70dc09/pyyaml-6.0.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:5fdec68f91a0c6739b380c83b951e2c72ac0197ace422360e6d5a959d8d97b2c", size = 844011, upload-time = "2025-09-25T21:32:15.21Z" }, - { url = "https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc", size = 807870, upload-time = "2025-09-25T21:32:16.431Z" }, - { url = "https://files.pythonhosted.org/packages/05/c0/b3be26a015601b822b97d9149ff8cb5ead58c66f981e04fedf4e762f4bd4/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8dc52c23056b9ddd46818a57b78404882310fb473d63f17b07d5c40421e47f8e", size = 761089, upload-time = "2025-09-25T21:32:17.56Z" }, - { url = "https://files.pythonhosted.org/packages/be/8e/98435a21d1d4b46590d5459a22d88128103f8da4c2d4cb8f14f2a96504e1/pyyaml-6.0.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:41715c910c881bc081f1e8872880d3c650acf13dfa8214bad49ed4cede7c34ea", size = 790181, upload-time = "2025-09-25T21:32:18.834Z" }, - { url = "https://files.pythonhosted.org/packages/74/93/7baea19427dcfbe1e5a372d81473250b379f04b1bd3c4c5ff825e2327202/pyyaml-6.0.3-cp312-cp312-win32.whl", hash = "sha256:96b533f0e99f6579b3d4d4995707cf36df9100d67e0c8303a0c55b27b5f99bc5", size = 137658, upload-time = "2025-09-25T21:32:20.209Z" }, - { url = "https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:5fcd34e47f6e0b794d17de1b4ff496c00986e1c83f7ab2fb8fcfe9616ff7477b", size = 154003, upload-time = "2025-09-25T21:32:21.167Z" }, - { url = "https://files.pythonhosted.org/packages/1a/08/67bd04656199bbb51dbed1439b7f27601dfb576fb864099c7ef0c3e55531/pyyaml-6.0.3-cp312-cp312-win_arm64.whl", hash = "sha256:64386e5e707d03a7e172c0701abfb7e10f0fb753ee1d773128192742712a98fd", size = 140344, upload-time = "2025-09-25T21:32:22.617Z" }, - { url = "https://files.pythonhosted.org/packages/d1/11/0fd08f8192109f7169db964b5707a2f1e8b745d4e239b784a5a1dd80d1db/pyyaml-6.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8da9669d359f02c0b91ccc01cac4a67f16afec0dac22c2ad09f46bee0697eba8", size = 181669, upload-time = "2025-09-25T21:32:23.673Z" }, - { url = "https://files.pythonhosted.org/packages/b1/16/95309993f1d3748cd644e02e38b75d50cbc0d9561d21f390a76242ce073f/pyyaml-6.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2283a07e2c21a2aa78d9c4442724ec1eb15f5e42a723b99cb3d822d48f5f7ad1", size = 173252, upload-time = "2025-09-25T21:32:25.149Z" }, - { url = "https://files.pythonhosted.org/packages/50/31/b20f376d3f810b9b2371e72ef5adb33879b25edb7a6d072cb7ca0c486398/pyyaml-6.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee2922902c45ae8ccada2c5b501ab86c36525b883eff4255313a253a3160861c", size = 767081, upload-time = "2025-09-25T21:32:26.575Z" }, - { url = "https://files.pythonhosted.org/packages/49/1e/a55ca81e949270d5d4432fbbd19dfea5321eda7c41a849d443dc92fd1ff7/pyyaml-6.0.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a33284e20b78bd4a18c8c2282d549d10bc8408a2a7ff57653c0cf0b9be0afce5", size = 841159, upload-time = "2025-09-25T21:32:27.727Z" }, - { url = "https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0f29edc409a6392443abf94b9cf89ce99889a1dd5376d94316ae5145dfedd5d6", size = 801626, upload-time = "2025-09-25T21:32:28.878Z" }, - { url = "https://files.pythonhosted.org/packages/f9/11/ba845c23988798f40e52ba45f34849aa8a1f2d4af4b798588010792ebad6/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7057c9a337546edc7973c0d3ba84ddcdf0daa14533c2065749c9075001090e6", size = 753613, upload-time = "2025-09-25T21:32:30.178Z" }, - { url = "https://files.pythonhosted.org/packages/3d/e0/7966e1a7bfc0a45bf0a7fb6b98ea03fc9b8d84fa7f2229e9659680b69ee3/pyyaml-6.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:eda16858a3cab07b80edaf74336ece1f986ba330fdb8ee0d6c0d68fe82bc96be", size = 794115, upload-time = "2025-09-25T21:32:31.353Z" }, - { url = "https://files.pythonhosted.org/packages/de/94/980b50a6531b3019e45ddeada0626d45fa85cbe22300844a7983285bed3b/pyyaml-6.0.3-cp313-cp313-win32.whl", hash = "sha256:d0eae10f8159e8fdad514efdc92d74fd8d682c933a6dd088030f3834bc8e6b26", size = 137427, upload-time = "2025-09-25T21:32:32.58Z" }, - { url = "https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:79005a0d97d5ddabfeeea4cf676af11e647e41d81c9a7722a193022accdb6b7c", size = 154090, upload-time = "2025-09-25T21:32:33.659Z" }, - { url = "https://files.pythonhosted.org/packages/73/e8/2bdf3ca2090f68bb3d75b44da7bbc71843b19c9f2b9cb9b0f4ab7a5a4329/pyyaml-6.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:5498cd1645aa724a7c71c8f378eb29ebe23da2fc0d7a08071d89469bf1d2defb", size = 140246, upload-time = "2025-09-25T21:32:34.663Z" }, - { url = "https://files.pythonhosted.org/packages/9d/8c/f4bd7f6465179953d3ac9bc44ac1a8a3e6122cf8ada906b4f96c60172d43/pyyaml-6.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8d1fab6bb153a416f9aeb4b8763bc0f22a5586065f86f7664fc23339fc1c1fac", size = 181814, upload-time = "2025-09-25T21:32:35.712Z" }, - { url = "https://files.pythonhosted.org/packages/bd/9c/4d95bb87eb2063d20db7b60faa3840c1b18025517ae857371c4dd55a6b3a/pyyaml-6.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:34d5fcd24b8445fadc33f9cf348c1047101756fd760b4dacb5c3e99755703310", size = 173809, upload-time = "2025-09-25T21:32:36.789Z" }, - { url = "https://files.pythonhosted.org/packages/92/b5/47e807c2623074914e29dabd16cbbdd4bf5e9b2db9f8090fa64411fc5382/pyyaml-6.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:501a031947e3a9025ed4405a168e6ef5ae3126c59f90ce0cd6f2bfc477be31b7", size = 766454, upload-time = "2025-09-25T21:32:37.966Z" }, - { url = "https://files.pythonhosted.org/packages/02/9e/e5e9b168be58564121efb3de6859c452fccde0ab093d8438905899a3a483/pyyaml-6.0.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:b3bc83488de33889877a0f2543ade9f70c67d66d9ebb4ac959502e12de895788", size = 836355, upload-time = "2025-09-25T21:32:39.178Z" }, - { url = "https://files.pythonhosted.org/packages/88/f9/16491d7ed2a919954993e48aa941b200f38040928474c9e85ea9e64222c3/pyyaml-6.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c458b6d084f9b935061bc36216e8a69a7e293a2f1e68bf956dcd9e6cbcd143f5", size = 794175, upload-time = "2025-09-25T21:32:40.865Z" }, - { url = "https://files.pythonhosted.org/packages/dd/3f/5989debef34dc6397317802b527dbbafb2b4760878a53d4166579111411e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7c6610def4f163542a622a73fb39f534f8c101d690126992300bf3207eab9764", size = 755228, upload-time = "2025-09-25T21:32:42.084Z" }, - { url = "https://files.pythonhosted.org/packages/d7/ce/af88a49043cd2e265be63d083fc75b27b6ed062f5f9fd6cdc223ad62f03e/pyyaml-6.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5190d403f121660ce8d1d2c1bb2ef1bd05b5f68533fc5c2ea899bd15f4399b35", size = 789194, upload-time = "2025-09-25T21:32:43.362Z" }, - { url = "https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:4a2e8cebe2ff6ab7d1050ecd59c25d4c8bd7e6f400f5f82b96557ac0abafd0ac", size = 156429, upload-time = "2025-09-25T21:32:57.844Z" }, - { url = "https://files.pythonhosted.org/packages/f4/f4/a4541072bb9422c8a883ab55255f918fa378ecf083f5b85e87fc2b4eda1b/pyyaml-6.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:93dda82c9c22deb0a405ea4dc5f2d0cda384168e466364dec6255b293923b2f3", size = 143912, upload-time = "2025-09-25T21:32:59.247Z" }, - { url = "https://files.pythonhosted.org/packages/7c/f9/07dd09ae774e4616edf6cda684ee78f97777bdd15847253637a6f052a62f/pyyaml-6.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:02893d100e99e03eda1c8fd5c441d8c60103fd175728e23e431db1b589cf5ab3", size = 189108, upload-time = "2025-09-25T21:32:44.377Z" }, - { url = "https://files.pythonhosted.org/packages/4e/78/8d08c9fb7ce09ad8c38ad533c1191cf27f7ae1effe5bb9400a46d9437fcf/pyyaml-6.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c1ff362665ae507275af2853520967820d9124984e0f7466736aea23d8611fba", size = 183641, upload-time = "2025-09-25T21:32:45.407Z" }, - { url = "https://files.pythonhosted.org/packages/7b/5b/3babb19104a46945cf816d047db2788bcaf8c94527a805610b0289a01c6b/pyyaml-6.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6adc77889b628398debc7b65c073bcb99c4a0237b248cacaf3fe8a557563ef6c", size = 831901, upload-time = "2025-09-25T21:32:48.83Z" }, - { url = "https://files.pythonhosted.org/packages/8b/cc/dff0684d8dc44da4d22a13f35f073d558c268780ce3c6ba1b87055bb0b87/pyyaml-6.0.3-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:a80cb027f6b349846a3bf6d73b5e95e782175e52f22108cfa17876aaeff93702", size = 861132, upload-time = "2025-09-25T21:32:50.149Z" }, - { url = "https://files.pythonhosted.org/packages/b1/5e/f77dc6b9036943e285ba76b49e118d9ea929885becb0a29ba8a7c75e29fe/pyyaml-6.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:00c4bdeba853cc34e7dd471f16b4114f4162dc03e6b7afcc2128711f0eca823c", size = 839261, upload-time = "2025-09-25T21:32:51.808Z" }, - { url = "https://files.pythonhosted.org/packages/ce/88/a9db1376aa2a228197c58b37302f284b5617f56a5d959fd1763fb1675ce6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:66e1674c3ef6f541c35191caae2d429b967b99e02040f5ba928632d9a7f0f065", size = 805272, upload-time = "2025-09-25T21:32:52.941Z" }, - { url = "https://files.pythonhosted.org/packages/da/92/1446574745d74df0c92e6aa4a7b0b3130706a4142b2d1a5869f2eaa423c6/pyyaml-6.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:16249ee61e95f858e83976573de0f5b2893b3677ba71c9dd36b9cf8be9ac6d65", size = 829923, upload-time = "2025-09-25T21:32:54.537Z" }, - { url = "https://files.pythonhosted.org/packages/f0/7a/1c7270340330e575b92f397352af856a8c06f230aa3e76f86b39d01b416a/pyyaml-6.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4ad1906908f2f5ae4e5a8ddfce73c320c2a1429ec52eafd27138b7f1cbe341c9", size = 174062, upload-time = "2025-09-25T21:32:55.767Z" }, - { url = "https://files.pythonhosted.org/packages/f1/12/de94a39c2ef588c7e6455cfbe7343d3b2dc9d6b6b2f40c4c6565744c873d/pyyaml-6.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:ebc55a14a21cb14062aa4162f906cd962b28e2e9ea38f9b4391244cd8de4ae0b", size = 149341, upload-time = "2025-09-25T21:32:56.828Z" }, -] - [[package]] name = "readme-renderer" version = "45.0" @@ -1056,9 +992,6 @@ build = [ { name = "build" }, { name = "twine" }, ] -lexical = [ - { name = "pyyaml" }, -] research = [ { name = "mpmath" }, { name = "sympy" }, @@ -1077,12 +1010,11 @@ requires-dist = [ { name = "mpmath", marker = "extra == 'test'", specifier = ">=1.3,<2" }, { name = "numpy", specifier = ">=1.24" }, { name = "pytest", marker = "extra == 'test'", specifier = ">=8" }, - { name = "pyyaml", marker = "extra == 'lexical'", specifier = ">=6,<7" }, { name = "sympy", marker = "extra == 'research'", specifier = ">=1.12,<2" }, { name = "sympy", marker = "extra == 'test'", specifier = ">=1.12,<2" }, { name = "twine", marker = "extra == 'build'", specifier = ">=5" }, ] -provides-extras = ["test", "research", "lexical", "build"] +provides-extras = ["test", "research", "build"] [[package]] name = "urllib3" From 87765a65166e05635982b2daee0d25d21e1175b7 Mon Sep 17 00:00:00 2001 From: Codex Date: Fri, 11 Sep 2026 00:43:18 +0000 Subject: [PATCH 03/65] Refresh UCNS audit frontier and minimal-to-maximal next steps --- docs/work-graphs/repository-plan-report.json | 168 ++++++++++++++++++- 1 file changed, 167 insertions(+), 1 deletion(-) diff --git a/docs/work-graphs/repository-plan-report.json b/docs/work-graphs/repository-plan-report.json index 9a8163e..b9a9a46 100644 --- a/docs/work-graphs/repository-plan-report.json +++ b/docs/work-graphs/repository-plan-report.json @@ -1 +1,167 @@ -{"schema":"the-interdependency.repository-plan-report","version":"1.0.0","repository":"The-Interdependency/ucns","contract":{"repository":"The-Interdependency/skill-lib","path":"interdependent-work-graph/repository-plan-report.schema.json","version":"1.0.0","blob_sha":"9b347b2dff7692054b571602f30ee6d00c2e7265"},"source":{"branch":"main","commit":"c4b8d6225262168942a456ebc6f34f42de8dccf0","generated_at":"2026-09-06","note":"This report describes the exact repository state immediately beneath the report commit; the report commit is coordination metadata and does not acquire or transfer substantive authority."},"authority":{"owns":["geometry constructions and exact geometric carriers implemented in UCNS","UCNS-local mathematical representation candidates and their proof/status evidence","Public Gonol carrier geometry and native Möbius geometric state"],"does_not_own":["lexical, semantic, morphological, NLP, EDCM measurement, PTCNA runtime, PCEA security, or external-physics truth claims"],"non_transfer":["executability does not ratify a candidate geometry","downstream use does not transfer downstream semantic or empirical status into UCNS","finite modular traces do not select a privileged continuum lift or external physical meaning"]},"portfolio_role":{"summary":"Own the geometry and mathematical-representation layer consumed by other Interdependency projects without absorbing their semantics or empirical claims.","reports_to":{"repository":"The-Interdependency/skill-lib","skill":"interdependent-work-graph","relation":"repo-owned report consumed by the deterministic portfolio projection"}},"status":{"state":"active geometry repository with exact constructions and explicitly candidate-scoped continuum/modular relations","current_claim":"UCNS provides exact Public Gonol, Möbius, modular-orbit, continuum wave-trace, and related topological geometry while leaving unresolved geometry and candidate selection explicit."},"delivered":[{"surface":"Public Gonol carrier","status":"implemented exact 157-position carrier","boundary":"glyph function meaning is not inferred from Unicode or lexical semantics"},{"surface":"native Möbius geometry","status":"implemented exact framed 360° visible / 720° complete return","boundary":"display equivalence does not collapse complete-state identity"},{"surface":"modular orbit and continuum boundary trace","status":"implemented candidate exact relation","boundary":"candidate status; no privileged lift, modulus, carrier, or physical meaning is selected"}],"active_frontier":["ratify or reject modular-orbit / continuum-boundary-trace candidates under explicit falsifiers","derive the complete visible-circle wave trace into the native Möbius carrier","resolve exact geometric operations for unresolved Public Gonol positions"],"next_actions":[{"action":"add and maintain this repo-owned portfolio report","owner":"The-Interdependency/ucns","dependency":"skill-lib repository-plan-report v1 contract"},{"action":"keep unresolved geometric operations as hmmm rather than filling them semantically","owner":"The-Interdependency/ucns","dependency":"geometry evidence sufficient to select operations"}],"blocked":[],"cross_repository_relations":[{"repository":"The-Interdependency/skill-lib","relation":"consumer of canonical build/evidence and work-graph doctrine","authority_transfer":false},{"repository":"The-Interdependency/stack","relation":"canonical source for stack-pinned UCNS views and stack-local UCNS research","authority_transfer":false},{"repository":"The-Interdependency/edcm","relation":"geometry producer for downstream text-domain gonol construction; EDCM retains text/measurement authority","authority_transfer":false},{"repository":"The-Interdependency/pcea","relation":"forward arithmetic/geometric substrate may be consumed by PCEA without transferring security claims","authority_transfer":false},{"repository":"The-Interdependency/epac","relation":"geometry dependency for EPAC candidate constructions; external physics/chemistry standing does not transfer","authority_transfer":false}],"machine_entrypoints":{"repo_report":"docs/work-graphs/repository-plan-report.json","system_overview":"README.md","package_source":"src/ucns/","public_gonol":"src/ucns/public_gonol.py","continuum_trace":"src/ucns/gonal_boundary_trace.py","continuum_trace_docs":"docs/modular-orbit-wave-trace.md"},"hmmm":["ratification of modular-orbit / continuum-boundary-trace candidates remains unresolved","complete native Möbius lift for visible-circle wave traces remains unresolved","exact geometric operation of every Public Gonol position remains unresolved"]} +{ + "schema": "the-interdependency.repository-plan-report", + "version": "1.0.0", + "repository": "The-Interdependency/ucns", + "contract": { + "repository": "The-Interdependency/skill-lib", + "path": "interdependent-work-graph/repository-plan-report.schema.json", + "version": "1.0.0", + "blob_sha": "9b347b2dff7692054b571602f30ee6d00c2e7265" + }, + "source": { + "branch": "repair/ucns-audit-20260911", + "commit": "0c8e9fc38edf3b3c2c00e6309c4064cb9b0e27fb", + "generated_at": "2026-09-11", + "note": "Describes the exact local implementation commit below this coordination-only report refresh. Not published or merged by this audit; remote main was observed at 487d24e3a0ed7d1cff858b85faae00312d631905. Report standing does not confer mathematical, semantic, empirical, or deployment authority." + }, + "authority": { + "owns": [ + "geometry constructions and exact geometric carriers implemented in UCNS", + "UCNS-local mathematical representation candidates and their proof/status evidence", + "Public Gonol carrier geometry and native Möbius geometric state" + ], + "does_not_own": [ + "lexical, semantic, morphological, NLP, EDCM measurement, PTCNA runtime, PCEA security, or external-physics truth claims" + ], + "non_transfer": [ + "executability does not ratify a candidate geometry", + "downstream use does not transfer downstream semantic or empirical status into UCNS", + "finite modular traces do not select a privileged continuum lift or external physical meaning" + ] + }, + "portfolio_role": { + "summary": "Own the geometry and mathematical-representation layer consumed by other Interdependency projects without absorbing their semantics or empirical claims.", + "reports_to": { + "repository": "The-Interdependency/skill-lib", + "skill": "interdependent-work-graph", + "relation": "repo-owned report consumed by the deterministic portfolio projection" + } + }, + "status": { + "state": "active geometry repository; local maintenance repair awaiting publication and remote CI", + "current_claim": "UCNS retains exact geometric carriers and candidate modular/wave relations. Local repairs harden immutable/exact inputs, MPFR NaN ordering, no-exec contract auditing, selected-check receipts, and replayable distributions; no candidate is ratified by those repairs." + }, + "delivered": [ + { + "surface": "Public Gonol carrier", + "status": "implemented exact 157-position carrier", + "boundary": "glyph function meaning is not inferred from Unicode or lexical semantics" + }, + { + "surface": "native Möbius geometry", + "status": "implemented exact framed 360° visible / 720° complete return", + "boundary": "display equivalence does not collapse complete-state identity" + }, + { + "surface": "modular orbit and continuum boundary trace", + "status": "implemented candidate exact relation", + "boundary": "candidate status; no privileged lift, modulus, carrier, or physical meaning is selected" + }, + { + "surface": "Exact modular/trace and MPFR input boundaries", + "status": "implemented with negative regression checks", + "boundary": "non-Boolean integer and immutable record admission; NaN cannot supply ordered interval evidence; valid geometric laws unchanged" + }, + { + "surface": "Contract audit and selected-check receipt schema 2", + "status": "implemented fail-closed graph audit and source-bound JUnit classification", + "boundary": "skips, empty evidence, and source mutation do not pass; selected checks do not establish whole-repository, theorem, freshness, or canon standing" + }, + { + "surface": "Source distribution replay inputs and artifact gate", + "status": "implemented source manifest, archive-byte verification, and locked verification-dependency CI path", + "boundary": "archive presence and matching bytes are packaging evidence; archived certificates are not thereby recomputed" + } + ], + "active_frontier": [ + "publish and remotely verify the local maintenance repair after its exact skill-lib dependency is available", + "reconcile the intrinsic/extrinsic Structural Null representation before constructing an origin attachment", + "derive the complete visible-circle wave trace into the native frame-bearing Möbius carrier", + "ratify or reject modular-orbit / continuum-boundary-trace candidates under explicit falsifiers", + "resolve higher-dimensional recursive-scale laws and geometric operations for unresolved Public Gonol positions" + ], + "next_actions": [ + { + "action": "Review and publish skill-lib 537cc3954b9ce4f660d46694b1e704a3ae9a5684, then UCNS repair; run Python 3.10/3.12 remote tests, drift, build, Twine, lock and archive-content gates", + "owner": "The-Interdependency/skill-lib and The-Interdependency/ucns maintainers", + "dependency": "separate publication direction; current inherited CI dependency is local and unpublished" + }, + { + "action": "Correct stale stack research claims about PR #220 and reconcile its exact research base before consuming results", + "owner": "The-Interdependency/stack research maintainers", + "dependency": "PR #220 merged with candidate-only wording; preserve existing dirty research and no automatic repin" + }, + { + "action": "Reconcile Structural Null representation, then derive or refute an explicit ambient origin attachment", + "owner": "The-Interdependency/ucns geometry", + "dependency": "geometric evidence and falsifier; coordinate defaults do not supply attachment authority" + }, + { + "action": "After attachment, evaluate direction, rotation system, marked outgoing dart, and closure in the stack-local constructor experiment", + "owner": "The-Interdependency/ucns geometry and The-Interdependency/stack research", + "dependency": "exact accepted attachment; this dependency order is a research contract, not ratified UCNS geometry" + }, + { + "action": "Propose a complete native Möbius wave lift with 360/720-degree and continuum-degree counterexample checks", + "owner": "The-Interdependency/ucns geometry", + "dependency": "explicit frame-bearing construction and candidate selection protocol" + }, + { + "action": "Recover the hash-bound source attachment and independently replay expensive P7/P5 certificates before stronger claims", + "owner": "The-Interdependency/ucns evidence maintainers", + "dependency": "exact source bytes, preregistrations, adequate resources, and owner review" + } + ], + "blocked": [ + "New remote maintenance CI is unobserved and requires publication of the inherited exact skill-lib dependency.", + "Origin attachment and recursive traversal cannot be supplied by numerical defaults or downstream interpretations." + ], + "cross_repository_relations": [ + { + "repository": "The-Interdependency/skill-lib", + "relation": "consumer of canonical build/evidence and work-graph doctrine", + "authority_transfer": false + }, + { + "repository": "The-Interdependency/stack", + "relation": "canonical source for stack-pinned UCNS views and stack-local UCNS research", + "authority_transfer": false + }, + { + "repository": "The-Interdependency/edcm", + "relation": "geometry producer for downstream text-domain gonol construction; EDCM retains text/measurement authority", + "authority_transfer": false + }, + { + "repository": "The-Interdependency/pcea", + "relation": "forward arithmetic/geometric substrate may be consumed by PCEA without transferring security claims", + "authority_transfer": false + }, + { + "repository": "The-Interdependency/epac", + "relation": "geometry dependency for EPAC candidate constructions; external physics/chemistry standing does not transfer", + "authority_transfer": false + } + ], + "machine_entrypoints": { + "repo_report": "docs/work-graphs/repository-plan-report.json", + "system_overview": "README.md", + "package_source": "src/ucns/", + "public_gonol": "src/ucns/public_gonol.py", + "continuum_trace": "src/ucns/gonal_boundary_trace.py", + "continuum_trace_docs": "docs/modular-orbit-wave-trace.md", + "contract_audit": "tools/verify_skill_lib_contracts.py", + "selected_check_runner": "tools/run_skill_lib_boundaries.py", + "distribution_gate": "tools/verify_distributions.py", + "build_and_usage": "README.md#build" + }, + "hmmm": [ + "Structural Null executable representation and candidate documentation remain unreconciled.", + "Origin attachment, recursive-scale transitions, and unresolved Public Gonol operations lack selecting geometric laws.", + "Modular-orbit / continuum-boundary-trace ratification and complete native Möbius lift remain unresolved.", + "The exact cited Möbius Strips and Quantum Geometry.txt source attachment is absent from the inspected owning tree.", + "A passing default suite includes archived receipt checks, not a newly executed full independent Gröbner replay.", + "New remote CI, merge, release, cross-repository repinning, and proof-assistant verification are unobserved.", + "Pre-existing ratio coverage gaps and large-module decomposition remain incremental maintenance work." + ] +} From 2a844e30d2710e8e415ed10b961a141fb7fac376 Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Fri, 11 Sep 2026 05:19:14 -0700 Subject: [PATCH 04/65] fix(authority): remove EDCM executable from UCNS --- .../skills/gonol-build/check_edcm_boundary.py | 62 ------------------- 1 file changed, 62 deletions(-) delete mode 100644 .agents/skills/gonol-build/check_edcm_boundary.py diff --git a/.agents/skills/gonol-build/check_edcm_boundary.py b/.agents/skills/gonol-build/check_edcm_boundary.py deleted file mode 100644 index 6a97be7..0000000 --- a/.agents/skills/gonol-build/check_edcm_boundary.py +++ /dev/null @@ -1,62 +0,0 @@ -# ratios: loc_comments=29:22 imports_exports=4:1 calls_definitions=12:1 -"""Executable cross-source scale witness; not a geometry or measurement proof. - -Usage: python gonol-build/check_edcm_boundary.py /exact/edcm/checkout -The supplied clean checkout must match EDCM_COMMIT. No package install or network -access is performed by this witness. Refreshing the pin is a reviewed boundary change. -""" - -# === MODULE_BUILD === -# id: gonol_edcm_scale_witness -# module_name: check_edcm_boundary -# module_kind: adapter -# summary: Exercise an exact EDCM constructor's non-adjacent scale and replay contract. -# owner: skill-lib maintainers -# public_surface: check -# internal_surface: none -# auth_boundary: none -# storage_boundary: none -# network_boundary: none -# user_data_boundary: none -# admin_only: false -# tests: explicit CLI against the declared EDCM commit -# rollout: cross-source CI gate -# rollback: retain pin and report boundary as hmmm if unavailable -# === END MODULE_BUILD === - -from pathlib import Path -import subprocess -import sys - -EDCM_COMMIT = "ddc89a97ebbcf0a5863dad6e633b01b520e9bccf" - - -def check(root: Path) -> None: - actual = subprocess.check_output(["git", "-C", str(root), "rev-parse", "HEAD"], text=True).strip() - if actual != EDCM_COMMIT: - raise ValueError(f"expected exact EDCM {EDCM_COMMIT}, got {actual}") - if subprocess.check_output(["git", "-C", str(root), "status", "--porcelain"]): - raise ValueError("EDCM witness requires a clean source checkout") - sys.path.insert(0, str(root.resolve())) - from edcm.gonol import construct_gonol, replay_gonol, GonolConstructionError - - character = construct_gonol(scale="character", source="x", source_id="skill-lib:character") - definition = construct_gonol(scale="definition", source="bounded evidence", - relation="skill-lib:defined-by", participants=(character.gonol,), - source_id="skill-lib:definition") - assert definition.gonol.participants == (character.gonol,) - assert replay_gonol(receipt=definition).receipt_digest == definition.receipt_digest - assert definition.standing == "implemented-candidate" - assert definition.selection_effect == "none" - try: - construct_gonol(scale="undeclared-scale", source="x", source_id="skill-lib:invalid") - except GonolConstructionError: - pass - else: - raise AssertionError("undeclared scale must not become eligible") - print(f"PASS EDCM {actual}: direct character-to-definition, replay, candidate non-transfer, rejected undeclared scale") - - -if __name__ == "__main__": - check(Path(sys.argv[1])) -# ratios: loc_comments=29:22 imports_exports=4:1 calls_definitions=12:1 From 3adac43a92b57996da84c5fd02ed2c95d258aec1 Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Fri, 11 Sep 2026 05:39:54 -0700 Subject: [PATCH 05/65] chore: refresh gonol-build from canonical skill-lib --- .agents/skills/gonol-build/SKILL.md | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/.agents/skills/gonol-build/SKILL.md b/.agents/skills/gonol-build/SKILL.md index 0654cc7..6e0e97d 100644 --- a/.agents/skills/gonol-build/SKILL.md +++ b/.agents/skills/gonol-build/SKILL.md @@ -90,16 +90,19 @@ Replay establishes reproducibility of that construction only. It does not by its ## Usage guidance -The executable cross-source witness is `gonol-build/check_edcm_boundary.py`. -Run it against a clean EDCM checkout at the script's exact `EDCM_COMMIT`: +The executable cross-source witness is owned by skill-lib at +`tools/check_edcm_boundary.py`, outside the propagated skill directory. In a +skill-lib checkout, run it against a clean EDCM checkout at the script's exact +`EDCM_COMMIT`: ```bash -python gonol-build/check_edcm_boundary.py /path/to/edcm +python tools/check_edcm_boundary.py /path/to/edcm ``` It checks direct character-to-definition construction, replay, candidate standing, -and refusal of an undeclared scale. This pinned witness is not a universal scale -registry; current construction must still resolve its owning EDCM option set. +and refusal of an undeclared scale. It is deliberately not vendored into UCNS or +other geometry consumers. This pinned witness is not a universal scale registry; +current construction must still resolve its owning EDCM option set. For text construction, start in EDCM and consume current UCNS geometry. @@ -113,9 +116,10 @@ When a gonol closes, use it atomically at any admissible consuming scale. Ignore ## Anti-patterns - Moving text semantics into UCNS or inventing geometry in EDCM. +- Vendoring the EDCM executable witness into a UCNS geometry repository. - Imposing an adjacent-scale ladder or inventing an eligible scale option without EDCM authority. - Letting pronunciation alter gonol identity, closure, ordering, or relations unless a later explicitly declared experiment makes phonology part of its construction. -- Normalizing, deduplicating, inferring relations, or substituting tokens, embeddings, or hashes for gonol identity unless the active contract explicitly authorizes it. +- Normalizing, deduplicating, inferring relations, or substituting tokens, embeddings, hashes for gonol identity unless the active contract explicitly authorizes it. - Adding arbitrary wall-clock limits to a healthy admitted run. - Treating replay as semantic quality, measurement validity, cognition, or extra-scope canon. @@ -123,4 +127,4 @@ When a gonol closes, use it atomically at any admissible consuming scale. Ignore - exact UCNS geometric operations that remain unresolved in current implementation; - any future construction that explicitly adds phonology or another stage; -- any recursive relation whose governing source or geometry is not yet established. +- any recursive relation whose governing source or geometry is not yet established. \ No newline at end of file From d3ad3f9a50e0f44c369be50d3369282c004a58cd Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Fri, 11 Sep 2026 05:40:14 -0700 Subject: [PATCH 06/65] ci: pin merged skill-lib authority --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e83d83a..1a6c002 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,7 +21,7 @@ jobs: uses: actions/checkout@v7.0.1 with: repository: The-Interdependency/skill-lib - ref: 537cc3954b9ce4f660d46694b1e704a3ae9a5684 + ref: 8de4f12d0f31ff94f41e4a0196c447c0cbe20faf path: .skill-lib-canon sparse-checkout: | msdmd @@ -53,7 +53,7 @@ jobs: run: >- python .skill-lib-canon/tools/check_consumer_drift.py . --canon-root .skill-lib-canon - --sha 537cc3954b9ce4f660d46694b1e704a3ae9a5684 + --sha 8de4f12d0f31ff94f41e4a0196c447c0cbe20faf --strict-sha --require-vendored - name: Audit skill-lib contract graph From a42db1bcf8d4601aade7e97cb339f1e441d401ff Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Fri, 11 Sep 2026 05:40:27 -0700 Subject: [PATCH 07/65] docs: bind gonol skill to merged authority --- .agents/skills/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.agents/skills/README.md b/.agents/skills/README.md index 4932114..ecc3c86 100644 --- a/.agents/skills/README.md +++ b/.agents/skills/README.md @@ -3,7 +3,7 @@ This directory contains repo-local copies of canonical skills from `The-Interdependency/skill-lib`. -Source commit: `537cc3954b9ce4f660d46694b1e704a3ae9a5684` +Source commit: `8de4f12d0f31ff94f41e4a0196c447c0cbe20faf` Repo-local copies are not the source of truth. Edit `skill-lib` first, then propagate from the canonical source. From 4463234d8970a00abf3fa1035ee706a6dc146865 Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Fri, 11 Sep 2026 07:11:24 -0700 Subject: [PATCH 08/65] chore(skills): refresh canonical msdmd contract --- .agents/skills/msdmd/SKILL.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.agents/skills/msdmd/SKILL.md b/.agents/skills/msdmd/SKILL.md index ffa10c4..cfb17d3 100644 --- a/.agents/skills/msdmd/SKILL.md +++ b/.agents/skills/msdmd/SKILL.md @@ -63,7 +63,9 @@ claims to prove those obligations. See referenced from external tooling). - **Field lines**: indented one level beneath the id (two spaces of visible indent inside the comment). Field names are lowercase - snake_case followed by `:` and a value. + snake_case followed by `:` and a value. Digits are allowed after the first + character, so `evidence_sha256` is valid; the first character must be a + lowercase letter or underscore. - **Multiple blocks per file**: a module may declare more than one block, of the same or different types. The parser concatenates entries. @@ -282,8 +284,8 @@ individual SKILLs and documented in their own SKILL.md. one unless your block needs syntax the universal parser can't express. 4. **Implement the visibility report** — your runner must list - modules without your block type as gaps, and the gap list must - be visible in normal output (not buried behind a flag). + modules without your block type as gaps, and the gap list must be + visible in normal output (not buried behind a flag). 5. **Author a SKILL.md** in this lib with the convention spec, the executor's behavior, and at least one worked example. From 64bf7ba770096cef54132b130d5582063f03889d Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Fri, 11 Sep 2026 07:12:15 -0700 Subject: [PATCH 09/65] chore(skills): refresh canonical msdmd parser --- .agents/skills/msdmd/parsers/universal.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.agents/skills/msdmd/parsers/universal.py b/.agents/skills/msdmd/parsers/universal.py index 204f533..e6cb88a 100644 --- a/.agents/skills/msdmd/parsers/universal.py +++ b/.agents/skills/msdmd/parsers/universal.py @@ -100,7 +100,7 @@ def parse_text(text: str, block_name: str, marker: str = "#") -> list[dict]: block_re = _block_regex(block_name, marker) m = re.escape(marker) id_re = re.compile(rf"^\s*{m}\s*id:\s*(?P\S+)\s*$") - field_re = re.compile(rf"^\s*{m}\s+(?P[a-z_]+):\s*(?P.+?)\s*$") + field_re = re.compile(rf"^\s*{m}\s+(?P[a-z_][a-z0-9_]*):\s*(?P.+?)\s*$") entries: list[dict] = [] for block in block_re.finditer(text): @@ -189,7 +189,7 @@ def iter_source_files(path: Path) -> Iterable[Path]: # boundary to literal line 2: # ratios: loc_comments=N:M imports_exports=N:M calls_definitions=N:M RATIO_IDS = ("loc_comments", "imports_exports", "calls_definitions") -_RATIOS_TOKEN_RE = re.compile(r"(?P[a-z_]+)=(?P\S+)") +_RATIOS_TOKEN_RE = re.compile(r"(?P[a-z_][a-z0-9_]*)=(?P\S+)") def _ratios_line_re(marker: str) -> re.Pattern[str]: @@ -253,4 +253,4 @@ def ratios_placement(text: str, marker: str = "#") -> tuple[bool, bool]: last_ok = bool(line_re.match(raw.rstrip())) break return (opening_ok, last_ok) -# ratios: loc_comments=161:57 imports_exports=4:7 calls_definitions=55:10 +# ratios: loc_comments=161:57 imports_exports=4:7 calls_definitions=55:10 \ No newline at end of file From 653624e8aea7f2c565667cbc6ce8c2cd60a65c5b Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Fri, 11 Sep 2026 07:13:00 -0700 Subject: [PATCH 10/65] chore(skills): refresh canonical msdmd TypeScript parser --- .agents/skills/msdmd/parsers/universal.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.agents/skills/msdmd/parsers/universal.ts b/.agents/skills/msdmd/parsers/universal.ts index 141f46c..67b7b21 100644 --- a/.agents/skills/msdmd/parsers/universal.ts +++ b/.agents/skills/msdmd/parsers/universal.ts @@ -86,7 +86,7 @@ export function parseText( "gm", ); const idRe = new RegExp(`^\\s*${m}\\s*id:\\s*(\\S+)\\s*$`); - const fieldRe = new RegExp(`^\\s*${m}\\s+([a-z_]+):\\s*(.+?)\\s*$`); + const fieldRe = new RegExp(`^\\s*${m}\\s+([a-z_][a-z0-9_]*):\\s*(.+?)\\s*$`); const entries: Entry[] = []; let match: RegExpExecArray | null; @@ -185,7 +185,7 @@ function ratiosLineRe(marker: string): RegExp { export function parseRatios(text: string, marker: string = "#"): Entry[] { const lineRe = ratiosLineRe(marker); - const tokenRe = /([a-z_]+)=(\S+)/g; + const tokenRe = /([a-z_][a-z0-9_]*)=(\S+)/g; const out: Entry[] = []; for (const raw of text.split("\n")) { const lm = lineRe.exec(raw.replace(/\s+$/, "")); From c3b2d98fa882979a23028a260cb5c4ba2f749085 Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Fri, 11 Sep 2026 07:14:33 -0700 Subject: [PATCH 11/65] chore(release): license UCNS under MPL-2.0 --- LICENSE | 373 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 373 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..93bc3cb --- /dev/null +++ b/LICENSE @@ -0,0 +1,373 @@ +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at https://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. From 678e09cf628a4f54fe042390665a190c00cfc00c Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Fri, 11 Sep 2026 07:14:43 -0700 Subject: [PATCH 12/65] chore(release): declare MPL-2.0 package metadata --- pyproject.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 98c9a0a..5dbb840 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,11 +7,13 @@ name = "ucns" version = "0.0.0.dev0" description = "UCNS geometric and topological construction research" readme = "README.md" +license = {file = "LICENSE"} requires-python = ">=3.10" authors = [{name = "Erin Spencer"}] dependencies = ["mpmath>=1.3", "numpy>=1.24"] classifiers = [ "Development Status :: 2 - Pre-Alpha", + "License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", From 30a1c10583d775ed7e529c6461fb2b01287bb603 Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Fri, 11 Sep 2026 07:20:13 -0700 Subject: [PATCH 13/65] chore(skills): make msdmd vendored copy byte-exact --- .agents/skills/msdmd/SKILL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.agents/skills/msdmd/SKILL.md b/.agents/skills/msdmd/SKILL.md index cfb17d3..227219c 100644 --- a/.agents/skills/msdmd/SKILL.md +++ b/.agents/skills/msdmd/SKILL.md @@ -322,4 +322,4 @@ for additional applications over the same parser contract. - **Reserved field names** above are stable. New reserved names are additive only. - **Application SKILLs** version independently in their own SKILL.md - files. + files. \ No newline at end of file From 4f480d3f5578b445cf6591f58597429906c5db94 Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Fri, 11 Sep 2026 07:24:27 -0700 Subject: [PATCH 14/65] chore(skills): finish byte-exact msdmd propagation --- .agents/skills/msdmd/SKILL.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.agents/skills/msdmd/SKILL.md b/.agents/skills/msdmd/SKILL.md index 227219c..cfb17d3 100644 --- a/.agents/skills/msdmd/SKILL.md +++ b/.agents/skills/msdmd/SKILL.md @@ -322,4 +322,4 @@ for additional applications over the same parser contract. - **Reserved field names** above are stable. New reserved names are additive only. - **Application SKILLs** version independently in their own SKILL.md - files. \ No newline at end of file + files. From 93197668d1598e089975e0b19ca79ba5f4df0f83 Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Fri, 11 Sep 2026 07:34:18 -0700 Subject: [PATCH 15/65] chore(ci): diagnose exact vendored skill byte drift --- .github/workflows/ci.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1a6c002..c43ec0f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -49,6 +49,21 @@ jobs: run: | uv lock --check uv sync --locked --python python --extra test --extra build + - name: Diagnose vendored msdmd byte drift + run: | + python - <<'PY' + from pathlib import Path + a = Path('.skill-lib-canon/msdmd/SKILL.md').read_bytes() + b = Path('.agents/skills/msdmd/SKILL.md').read_bytes() + print('canon_len', len(a), 'vendored_len', len(b)) + diffs = [(i, x, y) for i, (x, y) in enumerate(zip(a, b)) if x != y] + print('diff_count', len(diffs)) + for i, x, y in diffs[:40]: + lo=max(0,i-24); hi=min(len(a),i+25) + print('offset', i, 'canon', x, 'vendored', y) + print('canon_ctx', repr(a[lo:hi])) + print('vendored_ctx', repr(b[lo:hi])) + PY - name: Check vendored skill-lib drift run: >- python .skill-lib-canon/tools/check_consumer_drift.py . From 072c4bc111ad6b121d0100eca4b47b830a9d157b Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Fri, 11 Sep 2026 07:37:37 -0700 Subject: [PATCH 16/65] chore(skills): make msdmd vendored copy byte-exact --- .agents/skills/msdmd/SKILL.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.agents/skills/msdmd/SKILL.md b/.agents/skills/msdmd/SKILL.md index cfb17d3..1a6f0be 100644 --- a/.agents/skills/msdmd/SKILL.md +++ b/.agents/skills/msdmd/SKILL.md @@ -284,8 +284,8 @@ individual SKILLs and documented in their own SKILL.md. one unless your block needs syntax the universal parser can't express. 4. **Implement the visibility report** — your runner must list - modules without your block type as gaps, and the gap list must be - visible in normal output (not buried behind a flag). + modules without your block type as gaps, and the gap list must + be visible in normal output (not buried behind a flag). 5. **Author a SKILL.md** in this lib with the convention spec, the executor's behavior, and at least one worked example. From 82b11879b5048d364c4114e7f00ad8f9acc7b7cf Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Fri, 11 Sep 2026 07:37:56 -0700 Subject: [PATCH 17/65] chore(ci): remove temporary byte-drift diagnostic --- .github/workflows/ci.yml | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c43ec0f..1a6c002 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -49,21 +49,6 @@ jobs: run: | uv lock --check uv sync --locked --python python --extra test --extra build - - name: Diagnose vendored msdmd byte drift - run: | - python - <<'PY' - from pathlib import Path - a = Path('.skill-lib-canon/msdmd/SKILL.md').read_bytes() - b = Path('.agents/skills/msdmd/SKILL.md').read_bytes() - print('canon_len', len(a), 'vendored_len', len(b)) - diffs = [(i, x, y) for i, (x, y) in enumerate(zip(a, b)) if x != y] - print('diff_count', len(diffs)) - for i, x, y in diffs[:40]: - lo=max(0,i-24); hi=min(len(a),i+25) - print('offset', i, 'canon', x, 'vendored', y) - print('canon_ctx', repr(a[lo:hi])) - print('vendored_ctx', repr(b[lo:hi])) - PY - name: Check vendored skill-lib drift run: >- python .skill-lib-canon/tools/check_consumer_drift.py . From 0580677b66fdd0eee85891ba529d8cd669daff3d Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Fri, 11 Sep 2026 07:46:27 -0700 Subject: [PATCH 18/65] fix(release): bind license and reject distribution extras --- tools/verify_distributions.py | 80 ++++++++++++++++++++++++++++++----- 1 file changed, 69 insertions(+), 11 deletions(-) diff --git a/tools/verify_distributions.py b/tools/verify_distributions.py index 0241128..f13736b 100644 --- a/tools/verify_distributions.py +++ b/tools/verify_distributions.py @@ -22,16 +22,17 @@ # === CONTRACTS === # id: distributions_retain_exact_replay_inputs # given: a built sdist and wheel are compared to a UCNS source tree -# then: missing or altered required inputs, unsafe archive members, duplicate members, and unexpected wheel payloads fail without extraction or execution +# then: missing or altered required inputs, unsafe archive members, duplicate members, unexpected executable payloads/metadata, and altered license material fail without extraction or execution # class: evidence # since: 2026-09-11 # === END CONTRACTS === """Usage: ``python tools/verify_distributions.py . dist`` after a clean build. -The wheel contains the package; the sdist also contains the tests, their -evidence, preregistrations, tools, and vendored parser. Matching archive bytes -is packaging evidence only, not certificate verification or ratification. +The wheel contains the package plus a narrowly admitted generated ``.dist-info`` +set; the sdist also contains the tests, their evidence, preregistrations, tools, +vendored parser, and exact license bytes. Matching archive bytes is packaging +evidence only, not certificate verification or ratification. """ from __future__ import annotations @@ -42,12 +43,25 @@ import zipfile -ROOT_INPUTS = ("pyproject.toml", "README.md", "AGENTS.md", "CANON.md", "CLAUDE.md", "uv.lock", "MANIFEST.in") +ROOT_INPUTS = ( + "pyproject.toml", "README.md", "AGENTS.md", "CANON.md", "CLAUDE.md", + "LICENSE", "uv.lock", "MANIFEST.in", +) TREE_INPUTS = { "src/ucns": {".py"}, "tests": {".py"}, "tools": {".py"}, "docs": {".md", ".json", ".jsonl", ".svg"}, "generated": {".json"}, ".agents/skills": {".md", ".json", ".py", ".ts"}, } +SDIST_GENERATED = { + "PKG-INFO", + "src/ucns.egg-info/PKG-INFO", + "src/ucns.egg-info/SOURCES.txt", + "src/ucns.egg-info/dependency_links.txt", + "src/ucns.egg-info/requires.txt", + "src/ucns.egg-info/top_level.txt", +} +WHEEL_DIST_INFO_FILES = {"METADATA", "WHEEL", "RECORD", "top_level.txt"} +WHEEL_LICENSE_PATH = "licenses/LICENSE" def expected_files(root: Path) -> dict[str, bytes]: @@ -55,6 +69,9 @@ def expected_files(root: Path) -> dict[str, bytes]: for directory, suffixes in TREE_INPUTS.items(): paths.update(path for path in (root / directory).rglob("*") if path.is_file() and path.suffix in suffixes and "__pycache__" not in path.parts) + missing_roots = [path.name for path in paths if path.parent == root and not path.is_file()] + if missing_roots: + raise ValueError(f"missing root distribution inputs: {', '.join(sorted(missing_roots))}") if not any(path.is_relative_to(root / "src/ucns") for path in paths): raise ValueError("missing UCNS package source") return {path.relative_to(root).as_posix(): path.read_bytes() for path in sorted(paths)} @@ -100,10 +117,39 @@ def record(name: str, data: bytes) -> None: return files +def _wheel_metadata_problems(actual: dict[str, bytes], license_bytes: bytes) -> list[str]: + problems: list[str] = [] + prefixes = { + name.split("/", 1)[0] + for name in actual + if "/" in name and name.split("/", 1)[0].endswith(".dist-info") + } + if len(prefixes) != 1: + return ["wheel must contain exactly one .dist-info directory"] + prefix = next(iter(prefixes)) + allowed = {f"{prefix}/{name}" for name in WHEEL_DIST_INFO_FILES} + allowed.add(f"{prefix}/{WHEEL_LICENSE_PATH}") + for name in sorted(actual): + if name.startswith(f"{prefix}/") and name not in allowed: + problems.append(f"unexpected wheel metadata {name}") + required = {f"{prefix}/{name}" for name in ("METADATA", "WHEEL", "RECORD")} + required.add(f"{prefix}/{WHEEL_LICENSE_PATH}") + for name in sorted(required - actual.keys()): + problems.append(f"missing wheel metadata {name}") + packaged_license = actual.get(f"{prefix}/{WHEEL_LICENSE_PATH}") + if packaged_license is not None and packaged_license != license_bytes: + problems.append(f"altered wheel license {prefix}/{WHEEL_LICENSE_PATH}") + return problems + + def verify_distributions(root: Path, sdist: Path, wheel: Path) -> list[str]: expected = expected_files(root.resolve()) - wheel_expected = {name.removeprefix("src/"): data for name, data in expected.items() if name.startswith("src/ucns/")} - problems = [] + wheel_expected = { + name.removeprefix("src/"): data + for name, data in expected.items() + if name.startswith("src/ucns/") + } + problems: list[str] = [] for path, inputs, is_wheel in ((sdist, expected, False), (wheel, wheel_expected, True)): try: actual = read_archive(path, wheel=is_wheel) @@ -115,10 +161,22 @@ def verify_distributions(root: Path, sdist: Path, wheel: Path) -> list[str]: problems.append(f"{path.name}: missing {name}") elif actual[name] != data: problems.append(f"{path.name}: altered {name}") - for name in actual.keys() - inputs.keys(): - metadata = name.split("/", 1)[0].endswith(".dist-info") - if (is_wheel and not metadata) or (not is_wheel and name.startswith("src/ucns/")): - problems.append(f"{path.name}: unexpected payload {name}") + if is_wheel: + metadata_problems = _wheel_metadata_problems(actual, expected["LICENSE"]) + problems.extend(f"{path.name}: {problem}" for problem in metadata_problems) + dist_prefixes = { + name.split("/", 1)[0] + for name in actual + if "/" in name and name.split("/", 1)[0].endswith(".dist-info") + } + metadata_prefix = next(iter(dist_prefixes), "") + for name in sorted(actual.keys() - inputs.keys()): + if not metadata_prefix or not name.startswith(f"{metadata_prefix}/"): + problems.append(f"{path.name}: unexpected payload {name}") + else: + for name in sorted(actual.keys() - inputs.keys()): + if name not in SDIST_GENERATED: + problems.append(f"{path.name}: unexpected payload {name}") return problems From 757664a70892ae1baf785457c594e4fe91a306e6 Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Sat, 12 Sep 2026 01:43:55 +0000 Subject: [PATCH 19/65] Close receipt, contract-audit, and distribution review boundaries --- .agents/skills/README.md | 2 +- README.md | 8 +- tests/test_distributions.py | 32 +++- tests/test_skill_lib_boundary_runner.py | 62 +++++++- tests/test_skill_lib_contracts.py | 17 +- tools/_boundary_pytest.py | 103 ++++++++++++ tools/run_skill_lib_boundaries.py | 200 ++++++++++++++++++------ tools/verify_skill_lib_contracts.py | 34 +++- 8 files changed, 392 insertions(+), 66 deletions(-) create mode 100644 tools/_boundary_pytest.py diff --git a/.agents/skills/README.md b/.agents/skills/README.md index ecc3c86..9dab385 100644 --- a/.agents/skills/README.md +++ b/.agents/skills/README.md @@ -11,6 +11,7 @@ then propagate from the canonical source. Skills refreshed from the source commit above: - `gonol-build/` +- `msdmd/` Other installed skills (not refreshed by this propagation): @@ -21,7 +22,6 @@ commit above does not assert that these copies were refreshed. - `domain-claims/` [not refreshed; prior source: `c14ee9d500579a4b5d6821f62c9d82ca96e73608`] - `interdependent-work-graph/` [not refreshed; prior source: `c14ee9d500579a4b5d6821f62c9d82ca96e73608`] - `meta-module-build/` [not refreshed; prior source: `c14ee9d500579a4b5d6821f62c9d82ca96e73608`] -- `msdmd/` [not refreshed; prior source: `c14ee9d500579a4b5d6821f62c9d82ca96e73608`] - `test-build/` [not refreshed; prior source: `c14ee9d500579a4b5d6821f62c9d82ca96e73608`] - `the-interdependency/` [not refreshed; prior source: `c14ee9d500579a4b5d6821f62c9d82ca96e73608`] - `ucns-option-selection/` [not refreshed; prior source: `c14ee9d500579a4b5d6821f62c9d82ca96e73608`] diff --git a/README.md b/README.md index 114d6aa..9b5048c 100644 --- a/README.md +++ b/README.md @@ -175,8 +175,12 @@ For a source-bound, selected-check receipt: --check check_boundary_runner_nonactivation --receipt /tmp/ucns-receipt.json ``` -Receipt schema 2 rejects skipped/expected-failing evidence, absent test reports, -and source changes during execution. A `passed` receipt covers only its selected +Receipt schema 2.1 rejects skips, expected failures, both forms of XPASS, absent +reports, and source changes during execution (including write-and-restore). +Receipt execution requires Linux inotify; an unavailable observer is an error. +Checks import the bound checkout with ambient PYTHONPATH and pytest plugins +excluded, and imported package paths are recorded and checked. The observer is +evidence instrumentation, not a sandbox for hostile test code. A `passed` receipt covers only its selected checks; it does not select geometry, ratify candidates, or establish freshness. `hmmm`: ratification of the modular-orbit / continuum-boundary-trace candidates, the complete higher-dimensional UCNS construction, the exact visible-circle wave-trace lift into the native Möbius carrier, any law selecting one continuum covering lift from a finite modular congruence class, and the exact geometric operation of every Public Gonol function position remain unresolved. Unresolved geometry stays unresolved; semantic machinery is not used to fill it. diff --git a/tests/test_distributions.py b/tests/test_distributions.py index aa054d6..6ea6302 100644 --- a/tests/test_distributions.py +++ b/tests/test_distributions.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=45:10 imports_exports=6:1 calls_definitions=26:2 +# ratios: loc_comments=71:10 imports_exports=6:1 calls_definitions=32:2 # === CHECKS === # id: check_distribution_replay_inputs # proves: distributions_retain_exact_replay_inputs @@ -26,7 +26,7 @@ SPEC.loader.exec_module(audit) -def _archives(root, sdist, wheel, *, omit="", altered="", extra=""): +def _archives(root, sdist, wheel, *, omit="", altered="", extra="", sdist_extra="", wheel_omit="", wheel_altered=""): with tarfile.open(sdist, "w:gz") as archive: for name, data in audit.expected_files(root).items(): if name == omit: @@ -36,10 +36,23 @@ def _archives(root, sdist, wheel, *, omit="", altered="", extra=""): member = tarfile.TarInfo(f"ucns-0/{name}") member.size = len(data) archive.addfile(member, io.BytesIO(data)) + if sdist_extra: + member = tarfile.TarInfo(f"ucns-0/{sdist_extra}") + member.size = 10 + archive.addfile(member, io.BytesIO(b"unexpected")) with zipfile.ZipFile(wheel, "w") as archive: for name, data in audit.expected_files(root).items(): if name.startswith("src/ucns/"): archive.writestr(name.removeprefix("src/"), data) + metadata = { + "METADATA": b"Metadata-Version: 2.4\nName: ucns\nVersion: 0\n", + "WHEEL": b"Wheel-Version: 1.0\nRoot-Is-Purelib: true\nTag: py3-none-any\n", + "RECORD": b"", + "licenses/LICENSE": (root / "LICENSE").read_bytes(), + } + for name, data in metadata.items(): + if name != wheel_omit: + archive.writestr(f"ucns-0.dist-info/{name}", data + (b"drift" if name == wheel_altered else b"")) if extra: archive.writestr(extra, b"unexpected") @@ -54,6 +67,19 @@ def test_distribution_replay_inputs_fail_closed(tmp_path: Path) -> None: sdist, wheel = tmp_path / "ucns.tar.gz", tmp_path / "ucns.whl" _archives(root, sdist, wheel) assert audit.verify_distributions(root, sdist, wheel) == [] + for options, message in ( + ({"omit": "LICENSE"}, "missing LICENSE"), + ({"altered": "LICENSE"}, "altered LICENSE"), + ({"wheel_omit": "licenses/LICENSE"}, "missing wheel metadata"), + ({"wheel_altered": "licenses/LICENSE"}, "altered wheel license"), + ({"wheel_omit": "METADATA"}, "missing wheel metadata"), + ({"wheel_omit": "WHEEL"}, "missing wheel metadata"), + ({"wheel_omit": "RECORD"}, "missing wheel metadata"), + ({"sdist_extra": "setup.py"}, "unexpected payload setup.py"), + ({"extra": "ucns-0.dist-info/entry_points.txt"}, "unexpected wheel metadata"), + ): + _archives(root, sdist, wheel, **options) + assert any(message in problem for problem in audit.verify_distributions(root, sdist, wheel)), options for key, name, expected in (("omit", "generated/receipt.json", "missing"), ("altered", "docs/preregistration.md", "altered"), ("extra", "ucns/lexical.py", "unexpected")): _archives(root, sdist, wheel, **{key: name}) assert any(expected in problem for problem in audit.verify_distributions(root, sdist, wheel)) @@ -63,4 +89,4 @@ def test_distribution_replay_inputs_fail_closed(tmp_path: Path) -> None: with zipfile.ZipFile(wheel, "a") as archive, pytest.warns(UserWarning, match="Duplicate"): archive.writestr("ucns/__init__.py", b"duplicate") assert any("duplicate" in problem for problem in audit.verify_distributions(root, sdist, wheel)) -# ratios: loc_comments=45:10 imports_exports=6:1 calls_definitions=26:2 +# ratios: loc_comments=71:10 imports_exports=6:1 calls_definitions=32:2 diff --git a/tests/test_skill_lib_boundary_runner.py b/tests/test_skill_lib_boundary_runner.py index db412a7..f3ce485 100644 --- a/tests/test_skill_lib_boundary_runner.py +++ b/tests/test_skill_lib_boundary_runner.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=94:169 imports_exports=6:13 calls_definitions=56:15 +# ratios: loc_comments=98:209 imports_exports=6:16 calls_definitions=74:18 # === CHECKS === # id: check_boundary_runner_audit_gate # proves: boundary_runner_audits_before_execution @@ -25,7 +25,7 @@ # cleanup: none # # id: check_boundary_runner_status_continuation -# proves: boundary_runner_classifies_and_continues +# proves: boundary_runner_classifies_and_continues, boundary_pytest_observes_actual_outcomes # call: self::test_runner_classifies_all_outcomes_and_continues # requires: python3 # timeout: 20 @@ -191,27 +191,32 @@ def test_fail(): assert False def test_error(): raise RuntimeError("AssertionError mentioned by a broken harness") +class ContractViolation(AssertionError): + pass +def test_subclass(): + raise ContractViolation("broken") """ checks = [ {"id": "check_pass", "function": "test_pass"}, {"id": "check_fail", "function": "test_fail"}, {"id": "check_error", "function": "test_error"}, + {"id": "check_subclass", "function": "test_subclass"}, ] receipt = runner.run_boundaries(_repo(tmp_path, functions, checks)) assert [item["status"] for item in receipt["outcomes"]] == [ - "PASS", "FAIL", "ERROR", + "PASS", "FAIL", "ERROR", "FAIL", ] assert receipt["outcome_counts"] == { - "PASS": 1, "FAIL": 1, "ERROR": 1, "TIMEOUT": 0, "SKIP": 0, + "PASS": 1, "FAIL": 2, "ERROR": 1, "TIMEOUT": 0, "SKIP": 0, } def test_report_errors_suffix_discovery_and_launch_continuation(tmp_path: Path, monkeypatch) -> None: report = tmp_path / "report.xml" - assert runner._junit_status(report, 0) == "ERROR" - for text in ("not xml", "", ''): + assert runner._pytest_outcome(report, 0)[0] == "ERROR" + for text in ("not json", "[]", '{"status": "PASS"}', '{"status": "PASS", "calls": [], "other": [], "origins": {}}'): report.write_text(text) - assert runner._junit_status(report, 0) == "ERROR" + assert runner._pytest_outcome(report, 0)[0] == "ERROR" root = _repo(tmp_path, "def test_first():\n pass\ndef test_later():\n pass\n", [ {"id": "check_first", "function": "test_first"}, {"id": "check_later", "function": "test_later"}, @@ -270,6 +275,12 @@ def test_skips_xfails_and_mixed_parameters_are_not_passes(tmp_path: Path) -> Non receipt = runner.run_boundaries(root) assert receipt["status"] == "not-passed" assert receipt["outcomes"][0]["status"] == "SKIP" + for index, decorator in enumerate(("@pytest.mark.xfail(strict=False)", "@pytest.mark.xfail(strict=True)")): + body = "import pytest\n" + decorator + "\ndef test_probe():\n pass\n" + root = _repo(tmp_path / f"xpass{index}", body, [{"id": "check_probe", "function": "test_probe"}]) + receipt = runner.run_boundaries(root) + assert receipt["status"] == "not-passed" + assert receipt["outcomes"][0]["status"] == "FAIL" assert runner.run_boundaries(tmp_path / "absent")["status"] == "audit-gap" @@ -292,4 +303,39 @@ def test_source_mutation_prevents_acceptance(tmp_path: Path) -> None: assert receipt["status"] == "not-passed" assert receipt["source_before_sha256"] != receipt["source_after_sha256"] assert receipt["source_unchanged"] is False -# ratios: loc_comments=94:169 imports_exports=6:13 calls_definitions=56:15 + root = _repo(tmp_path / "restored", "from pathlib import Path\ndef test_probe():\n p=Path('src/pkg/feature.py')\n original=p.read_bytes()\n p.write_bytes(original+b'\\n# transient\\n')\n p.write_bytes(original)\n", [ + {"id": "check_probe", "function": "test_probe"}, + ]) + receipt = runner.run_boundaries(root) + assert receipt["source_before_sha256"] == receipt["source_after_sha256"] + assert receipt["source_unchanged"] is False + assert receipt["status"] == "not-passed" + assert "src/pkg/feature.py" in receipt["outcomes"][0]["source_events"] +# === CHECKS === +# id: check_boundary_runner_import_origin +# proves: boundary_runner_receipt_is_bounded_and_bound, boundary_pytest_observes_actual_outcomes +# call: self::test_check_imports_bound_source_despite_ambient_pythonpath +# requires: python3, pytest +# timeout: 15 +# mutates: temporary_path +# cleanup: pytest temporary_path +# === END CHECKS === + + +def test_check_imports_bound_source_despite_ambient_pythonpath(tmp_path: Path, monkeypatch) -> None: + body = "import pkg.feature\ndef test_probe():\n assert pkg.feature.VALUE == 'bound'\n" + root = _repo(tmp_path, body, [{"id": "check_probe", "function": "test_probe"}]) + source = root / "src/pkg/feature.py" + source.write_text(source.read_text() + "VALUE = 'bound'\n") + # A regular package avoids unrelated namespace packages in the host. + (root / "src/pkg/__init__.py").write_text(source.read_text().replace("fixture_module", "fixture_init").replace("contract_0", "init_contract")) + (root / "tests/test_feature.py").write_text((root / "tests/test_feature.py").read_text().replace("# proves: contract_0", "# proves: contract_0, init_contract")) + alternate = tmp_path / "alternate" + (alternate / "pkg").mkdir(parents=True) + (alternate / "pkg/__init__.py").write_text("") + (alternate / "pkg/feature.py").write_text("VALUE = 'wrong'\n") + monkeypatch.setenv("PYTHONPATH", str(alternate)) + receipt = runner.run_boundaries(root) + assert receipt["status"] == "passed", receipt + assert receipt["outcomes"][0]["imported_sources"]["pkg.feature"] == [str(source)] +# ratios: loc_comments=98:209 imports_exports=6:16 calls_definitions=74:18 diff --git a/tests/test_skill_lib_contracts.py b/tests/test_skill_lib_contracts.py index 2a99b90..06568d1 100644 --- a/tests/test_skill_lib_contracts.py +++ b/tests/test_skill_lib_contracts.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=76:42 imports_exports=5:5 calls_definitions=38:5 +# ratios: loc_comments=88:42 imports_exports=5:5 calls_definitions=47:5 # === CHECKS === # id: check_contract_audit_no_exec # proves: contract_audit_is_no_exec @@ -133,4 +133,17 @@ def test_nested_fences_cannot_hide_an_obligation(tmp_path: Path) -> None: source.write_text(original.replace("# === CONTRACTS ===", "# === CONTRACTS ===\n# id: hidden\n# given: x\n# then: y\n# === CONTRACTS ===")) ok, problems = audit_repository(root) assert not ok and any("nested" in item for item in problems) -# ratios: loc_comments=76:42 imports_exports=5:5 calls_definitions=38:5 + + for malformed in ("# === CONTRACTS ==", "# == CONTRACTS ==="): + source.write_text(original + malformed + "\n# id: dropped\n# given: x\n# then: y\n# === END CONTRACTS ===\n") + ok, problems = audit_repository(root) + assert not ok and any("malformed declaration fence" in item for item in problems) + for malformed in ("# id:", "# id: two words", "# id missing_colon"): + source.write_text(original.replace("# === END CONTRACTS ===", malformed + "\n# given: x\n# then: y\n# === END CONTRACTS ===")) + ok, problems = audit_repository(root) + assert not ok and any("malformed id" in item for item in problems) + source.write_text(original) + (root / "tests/test_helpers.py").write_text("class Helper:\n def test_helper(self): pass\nclass TestDisabled:\n __test__ = False\n def test_disabled(self): pass\ndef helper():\n class TestNested:\n def test_nested(self): pass\n") + ok, problems = audit_repository(root) + assert ok, problems +# ratios: loc_comments=88:42 imports_exports=5:5 calls_definitions=47:5 diff --git a/tools/_boundary_pytest.py b/tools/_boundary_pytest.py new file mode 100644 index 0000000..77f1834 --- /dev/null +++ b/tools/_boundary_pytest.py @@ -0,0 +1,103 @@ +# ratios: loc_comments=63:27 imports_exports=5:2 calls_definitions=22:4 +# === MODULE_BUILD === +# id: boundary_pytest_observer +# module_name: _boundary_pytest +# module_kind: instrument +# summary: observes actual pytest exceptions, xfail outcomes, and imported source origins for UCNS receipts +# owner: Erin Spencer +# public_surface: none; launched by run_skill_lib_boundaries +# internal_surface: Observer, main +# auth_boundary: none +# storage_boundary: writes caller-selected machine outcome report +# network_boundary: none +# user_data_boundary: none +# admin_only: false +# tests: tests/test_skill_lib_boundary_runner.py +# rollout: boundary runner subprocess bootstrap +# rollback: remove with boundary runner bootstrap integration +# === END MODULE_BUILD === +# === CONTRACTS === +# id: boundary_pytest_observes_actual_outcomes +# given: a selected pytest boundary runs through this bootstrap +# then: assertion subclasses fail, unexpected exceptions error, XPASS cannot pass, and imported local package origins must match the bound tree +# class: evidence +# === END CONTRACTS === + +"""Internal bootstrap. Usage: launched only by tools/run_skill_lib_boundaries.py. + +Observations describe executed checks, not theorem standing or a hostile-code +sandbox. Ambient pytest plugins and PYTHONPATH are excluded by the parent. +""" +from __future__ import annotations + +import json +from pathlib import Path +import sys + +import pytest + + +class Observer: + def __init__(self, root: Path): + self.root = root + self.calls: list[str] = [] + self.other: list[str] = [] + + @pytest.hookimpl(hookwrapper=True, tryfirst=True) + def pytest_runtest_makereport(self, item, call): + report = (yield).get_result() + if report.skipped: + status = "SKIP" + elif hasattr(report, "wasxfail"): + status = "FAIL" # Includes explicit xfail(strict=False) XPASS. + elif call.excinfo is not None: + status = "FAIL" if call.when == "call" and ( + call.excinfo.errisinstance(AssertionError) + or call.excinfo.errisinstance(pytest.fail.Exception) + ) else "ERROR" + elif report.failed: + status = "FAIL" if call.when == "call" else "ERROR" + else: + status = "PASS" + if call.when == "call": + self.calls.append(status) + elif status != "PASS": + self.other.append(status) + + +def main() -> int: + root = Path(sys.argv[1]).resolve() + report_path = Path(sys.argv[2]) + sys.path[:0] = [str(root / "src"), str(root)] + observer = Observer(root) + exit_code = int(pytest.main(sys.argv[3:], plugins=[observer])) + source_root = root / "src" + local_names = {p.stem if p.is_file() else p.name for p in source_root.iterdir()} if source_root.exists() else set() + origins = {} + wrong_origins = [] + for name, module in tuple(sys.modules.items()): + if name.split(".", 1)[0] not in local_names: + continue + paths = [getattr(module, "__file__", None), *getattr(module, "__path__", ())] + paths = [str(Path(p).resolve()) for p in paths if p is not None] + origins[name] = paths + if not paths or any(not Path(p).is_relative_to(source_root) for p in paths): + wrong_origins.append(name) + statuses = observer.calls + observer.other + if wrong_origins or "ERROR" in statuses: + status = "ERROR" + elif "FAIL" in statuses: + status = "FAIL" + elif "SKIP" in statuses: + status = "SKIP" + elif observer.calls and exit_code == 0: + status = "PASS" + else: + status = "ERROR" + report_path.write_text(json.dumps({"status": status, "calls": observer.calls, "other": observer.other, "origins": origins, "wrong_origins": wrong_origins}), encoding="utf-8") + return exit_code + + +if __name__ == "__main__": + raise SystemExit(main()) +# ratios: loc_comments=63:27 imports_exports=5:2 calls_definitions=22:4 diff --git a/tools/run_skill_lib_boundaries.py b/tools/run_skill_lib_boundaries.py index 3610e41..9157e20 100644 --- a/tools/run_skill_lib_boundaries.py +++ b/tools/run_skill_lib_boundaries.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=261:61 imports_exports=18:4 calls_definitions=108:15 +# ratios: loc_comments=361:65 imports_exports=19:4 calls_definitions=151:18 # === MODULE_BUILD === # id: skill_lib_boundary_runner # module_name: run_skill_lib_boundaries @@ -55,16 +55,18 @@ """Execute UCNS skill-lib ``CHECKS`` declarations as bounded processes. Usage: ``python tools/run_skill_lib_boundaries.py . --check CHECK_ID ---receipt /tmp/receipt.json``. Schema v2 requires actual, unskipped JUnit -evidence and unchanged source bytes. Receipts are not theorem or freshness +--receipt /tmp/receipt.json``. Schema v2.1 requires observed pytest outcomes, +bound import origins, unchanged source snapshots, and no Linux inotify write +events. JUnit remains diagnostic only. Receipts are not theorem or freshness certificates. Timeouts retain the existing declared execution-safety boundary. """ from __future__ import annotations import argparse +import ctypes import ctypes.util -from dataclasses import asdict, dataclass +from dataclasses import asdict, dataclass, field, replace from hashlib import sha256 import importlib.util import json @@ -72,11 +74,11 @@ from pathlib import Path import shutil import signal +import struct import subprocess import sys import tempfile import time -import xml.etree.ElementTree as ET from typing import Iterable, Sequence TOOLS_DIRECTORY = Path(__file__).resolve().parent @@ -87,10 +89,80 @@ SCHEMA_ID = "ucns.skill-lib-boundary-run-receipt" -SCHEMA_VERSION = "2.0.0" +SCHEMA_VERSION = "2.1.0" MAX_EXCERPT_BYTES = 16_384 ALLOWED_MUTATIONS = {"none", "filesystem", "temporary_path"} ALLOWED_CLEANUPS = {"none", "tempdir_teardown", "pytest temporary_path"} +SOURCE_DIRECTORIES = ("src", "tools", "tests", "docs", "generated", ".agents/skills", ".github/workflows") +ROOT_INPUTS = ("pyproject.toml", "uv.lock", "pytest.ini", "setup.cfg", "MANIFEST.in", "conftest.py", "CANON.md", "AGENTS.md", "README.md", "CLAUDE.md", "LICENSE") +BOOTSTRAP = TOOLS_DIRECTORY / "_boundary_pytest.py" + + +class _SourceWatch: + """Observe Linux source write events, including a write followed by restoration. + + Receipt execution requires inotify; an unavailable observer fails closed. + This detects changed inputs, not malicious checks or a security sandbox. + """ + def __init__(self, root: Path): + self.root = root + self.libc = ctypes.CDLL(None, use_errno=True) + self.fd = self.libc.inotify_init1(os.O_NONBLOCK | os.O_CLOEXEC) + if self.fd < 0: + raise OSError(ctypes.get_errno(), "source observer unavailable") + self.watches = {} + try: + directories = {root} + for relative in SOURCE_DIRECTORIES: + base = root / relative + if base.exists(): + directories.add(base) + directories.update(p for p in base.rglob("*") if p.is_dir() and "__pycache__" not in p.parts) + parent = base.parent + while parent != root and parent.is_relative_to(root): + if parent.is_dir(): + directories.add(parent) + parent = parent.parent + # MODIFY, ATTRIB, MOVED_FROM/TO, CREATE, DELETE, DELETE_SELF, MOVE_SELF. + for directory in sorted(directories): + descriptor = self.libc.inotify_add_watch(self.fd, os.fsencode(directory), 0xFC6) + if descriptor < 0: + raise OSError(ctypes.get_errno(), "cannot watch source directory") + self.watches[descriptor] = directory + except BaseException: + os.close(self.fd) + raise + + def finish(self) -> tuple[str, ...]: + changed = set() + try: + while True: + try: + data = os.read(self.fd, 65536) + except BlockingIOError: + break + if not data: + break + offset = 0 + while offset < len(data): + descriptor, mask, _, length = struct.unpack_from("iIII", data, offset) + name = os.fsdecode(data[offset + 16:offset + 16 + length].split(b"\0", 1)[0]) + offset += 16 + length + if mask & 0x4000: + changed.add("hmmm: source event queue overflow") + continue + parent = self.watches.get(descriptor) + if parent is None: + changed.add("hmmm: unknown source watch") + continue + relative = (parent / name).relative_to(self.root).as_posix() + if "__pycache__" in Path(relative).parts: + continue + if relative in ROOT_INPUTS or any(relative == d or relative.startswith(d + "/") or d.startswith(relative + "/") for d in SOURCE_DIRECTORIES): + changed.add(relative) + finally: + os.close(self.fd) + return tuple(sorted(changed)) @dataclass(frozen=True) @@ -115,6 +187,10 @@ class CheckOutcome: stderr_excerpt: str missing_capabilities: tuple[str, ...] = () diagnostic: str = "" + imported_sources: dict[str, list[str]] = field(default_factory=dict) + source_events: tuple[str, ...] = () + source_before_sha256: str = "" + source_after_sha256: str = "" def _sha(data: bytes) -> str: @@ -184,34 +260,44 @@ def _excerpt(path: Path) -> tuple[str, int, str]: return digest.hexdigest(), size, text -def _junit_status(path: Path, returncode: int) -> str: - """Classify actual test cases, not console wording or exit zero alone.""" +def _pytest_outcome(path: Path, returncode: int) -> tuple[str, dict]: + """Validate the bootstrap's machine report; absent evidence is an error.""" try: - cases = tuple(ET.parse(path).getroot().iter("testcase")) - except (OSError, ET.ParseError): - return "ERROR" - if not cases or any(case.find("error") is not None for case in cases): - return "ERROR" - failures = [failure for case in cases for failure in case.findall("failure")] - if failures: - # pytest's JUnit failure message retains the actual exception type. - # A RuntimeError merely mentioning AssertionError is not a contract FAIL. - assertion_messages = ("assert ", "AssertionError", "Failed:") - return "FAIL" if all(f.get("message", "").startswith(assertion_messages) for f in failures) else "ERROR" - if any(case.find("skipped") is not None for case in cases): - return "SKIP" - return "PASS" if returncode == 0 else "ERROR" + observed = json.loads(path.read_text(encoding="utf-8")) + except (OSError, ValueError): + return "ERROR", {} + if not isinstance(observed, dict): + return "ERROR", {} + calls, other = observed.get("calls"), observed.get("other") + origins = observed.get("origins") + allowed = {"PASS", "FAIL", "ERROR", "SKIP"} + if not isinstance(calls, list) or not isinstance(other, list) or not isinstance(origins, dict): + return "ERROR", {} + if any(not isinstance(value, str) or value not in allowed for value in calls + other): + return "ERROR", {} + statuses = calls + other + if observed.get("wrong_origins") or "ERROR" in statuses: + status = "ERROR" + elif "FAIL" in statuses: + status = "FAIL" + elif "SKIP" in statuses: + status = "SKIP" + else: + status = "PASS" if calls and returncode == 0 else "ERROR" + if observed.get("status") != status: + return "ERROR", {} + return status, observed def _source_snapshot(root: Path) -> tuple[dict[str, str], str]: """Bind repository-owned execution inputs, excluding caches and secrets.""" suffixes = {".py", ".md", ".json", ".jsonl", ".ts", ".svg", ".yml", ".yaml"} paths = { - path for directory in ("src", "tools", "tests", "docs", "generated", ".agents/skills", ".github/workflows") + path for directory in SOURCE_DIRECTORIES for path in (root / directory).rglob("*") if path.is_file() and path.suffix in suffixes and "__pycache__" not in path.parts } - paths.update(root / name for name in ("pyproject.toml", "uv.lock", "pytest.ini", "setup.cfg", "MANIFEST.in", "conftest.py", "CANON.md", "AGENTS.md") if (root / name).is_file()) + paths.update(root / name for name in ROOT_INPUTS if (root / name).is_file()) inventory = {path.relative_to(root).as_posix(): _sha(path.read_bytes()) for path in sorted(paths)} return inventory, _sha(json.dumps(inventory, sort_keys=True, separators=(",", ":")).encode()) @@ -249,25 +335,37 @@ def _run_check(root: Path, check: Entry) -> CheckOutcome: stdout_path = Path(temporary) / "stdout" stderr_path = Path(temporary) / "stderr" junit_path = Path(temporary) / "outcomes.xml" - command = (*command, f"--junitxml={junit_path}", "-o", "xfail_strict=true") - with stdout_path.open("wb") as stdout, stderr_path.open("wb") as stderr: - process = subprocess.Popen( - command, cwd=root, stdin=subprocess.DEVNULL, - stdout=stdout, stderr=stderr, start_new_session=True, - ) - timed_out = False - try: - returncode = process.wait(timeout=timeout) - except subprocess.TimeoutExpired: - timed_out = True + report_path = Path(temporary) / "outcomes.json" + command = (sys.executable, str(BOOTSTRAP), str(root), str(report_path), + "-q", f"{relative_source}::{function}", f"--junitxml={junit_path}", "-o", "xfail_strict=true") + environment = dict(os.environ) + for name in ("PYTHONPATH", "PYTHONHOME", "PYTEST_ADDOPTS", "PYTEST_PLUGINS"): + environment.pop(name, None) + environment.update(PYTHONDONTWRITEBYTECODE="1", PYTEST_DISABLE_PLUGIN_AUTOLOAD="1") + watcher = _SourceWatch(root) + try: + with stdout_path.open("wb") as stdout, stderr_path.open("wb") as stderr: + process = subprocess.Popen( + command, cwd=root, stdin=subprocess.DEVNULL, + stdout=stdout, stderr=stderr, start_new_session=True, env=environment, + ) + timed_out = False try: - os.killpg(process.pid, signal.SIGKILL) - except ProcessLookupError: - pass - returncode = process.wait() + returncode = process.wait(timeout=timeout) + except subprocess.TimeoutExpired: + timed_out = True + try: + os.killpg(process.pid, signal.SIGKILL) + except ProcessLookupError: + pass + returncode = process.wait() + finally: + source_events = watcher.finish() stdout_sha, stdout_bytes, stdout_excerpt = _excerpt(stdout_path) stderr_sha, stderr_bytes, stderr_excerpt = _excerpt(stderr_path) - status = "TIMEOUT" if timed_out else _junit_status(junit_path, returncode) + status, observed = _pytest_outcome(report_path, returncode) + if timed_out: + status = "TIMEOUT" duration = round(time.monotonic() - started, 6) return CheckOutcome( @@ -275,6 +373,7 @@ def _run_check(root: Path, check: Entry) -> CheckOutcome: command, requires, timeout, mutates, cleanup, status, returncode, duration, stdout_sha, stderr_sha, stdout_bytes, stderr_bytes, stdout_excerpt, stderr_excerpt, + imported_sources=observed.get("origins", {}), source_events=source_events, ) @@ -314,20 +413,29 @@ def run_boundaries( ) outcomes = [] for check in selected: + _, check_before = _source_snapshot(root) try: - outcomes.append(_run_check(root, check)) - except (ValueError, OSError) as error: - outcomes.append(_error_outcome(root, check, error)) + outcome = _run_check(root, check) + except (ValueError, OSError, AttributeError) as error: + outcome = _error_outcome(root, check, error) + _, check_after = _source_snapshot(root) + outcomes.append(replace(outcome, source_before_sha256=check_before, source_after_sha256=check_after)) _, source_after = _source_snapshot(root) statuses = {outcome.status for outcome in outcomes} + unchanged = source_before == source_after and all( + not outcome.source_events and outcome.source_before_sha256 == outcome.source_after_sha256 + for outcome in outcomes + ) receipt: dict[str, object] = { "schema_id": SCHEMA_ID, "schema_version": SCHEMA_VERSION, - "status": "passed" if outcomes and statuses == {"PASS"} and source_before == source_after else "not-passed", + "status": "passed" if outcomes and statuses == {"PASS"} and unchanged else "not-passed", "audit_closed": True, "audit_gaps": [], "source_files_sha256": source_files, "source_before_sha256": source_before, "source_after_sha256": source_after, - "source_unchanged": source_before == source_after, + "source_unchanged": unchanged, + "bootstrap_sha256": _sha(BOOTSTRAP.read_bytes()), + "python_version": sys.version, "selected_check_ids": [outcome.check_id for outcome in outcomes], "outcome_counts": { key: sum(outcome.status == key for outcome in outcomes) @@ -365,4 +473,4 @@ def main(argv: Sequence[str] | None = None) -> int: if __name__ == "__main__": raise SystemExit(main()) -# ratios: loc_comments=261:61 imports_exports=18:4 calls_definitions=108:15 +# ratios: loc_comments=361:65 imports_exports=19:4 calls_definitions=151:18 diff --git a/tools/verify_skill_lib_contracts.py b/tools/verify_skill_lib_contracts.py index 090932f..64e286f 100644 --- a/tools/verify_skill_lib_contracts.py +++ b/tools/verify_skill_lib_contracts.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=181:45 imports_exports=8:4 calls_definitions=72:8 +# ratios: loc_comments=205:47 imports_exports=8:4 calls_definitions=88:8 # === MODULE_BUILD === # id: skill_lib_contract_audit # module_name: verify_skill_lib_contracts @@ -58,6 +58,9 @@ BLOCK_RE = re.compile(r"^\s*#\s*===\s*(MODULE_BUILD|CONTRACTS|CHECKS)\s*===\s*$") END_RE = re.compile(r"^\s*#\s*===\s*END\s+(MODULE_BUILD|CONTRACTS|CHECKS)\s*===\s*$") +DECLARATION_FENCE_RE = re.compile(r"^\s*#\s*=+\s*(?:END\s+)?(?:MODULE_BUILD|CONTRACTS|CHECKS)\b") +ID_LIKE_RE = re.compile(r"^\s*#\s*id\b") +ID_RE = re.compile(r"^#\s*id:\s*([a-z_][a-z0-9_]*)\s*$") PARSER_PATH = Path(__file__).resolve().parents[1] / ".agents/skills/msdmd/parsers/universal.py" _SPEC = importlib.util.spec_from_file_location("_ucns_canonical_msdmd", PARSER_PATH) if _SPEC is None or _SPEC.loader is None: @@ -105,6 +108,7 @@ def parse_blocks(path: Path) -> List[Entry]: """Check fence integrity, then delegate entry grammar to canonical msdmd.""" text = path.read_text(encoding="utf-8") active: str | None = None + declarations = 0 for raw in text.splitlines(): start = BLOCK_RE.match(raw) if start: @@ -117,13 +121,23 @@ def parse_blocks(path: Path) -> List[Entry]: if active != end.group(1): raise ValueError(f"{path}: mismatched END {end.group(1)}") active = None + continue + if DECLARATION_FENCE_RE.match(raw): + raise ValueError(f"{path}: malformed declaration fence: {raw.strip()}") + if active is not None and ID_LIKE_RE.match(raw): + if ID_RE.fullmatch(raw) is None: + raise ValueError(f"{path}: malformed id declaration: {raw.strip()}") + declarations += 1 if active is not None: raise ValueError(f"{path}: unterminated {active} block") - return [ + entries = [ Entry(block, path, fields) for block in ("MODULE_BUILD", "CONTRACTS", "CHECKS") for fields in _PARSER.parse_text(text, block) ] + if len(entries) != declarations: + raise ValueError(f"{path}: declarations lost by canonical parser") + return entries def _defined_functions(path: Path) -> Set[str]: @@ -232,7 +246,19 @@ def audit_repository(root: Path) -> Tuple[bool, List[str]]: for function in (node.name for node in tree.body if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef))): if function.startswith("test_") and function not in declared_calls: problems.append(f"GAP executable check {test_path}::{function} has no resolving CHECKS declaration") - for cls in (node for node in ast.walk(tree) if isinstance(node, ast.ClassDef)): + for cls in (node for node in tree.body if isinstance(node, ast.ClassDef)): + # Match default pytest class collection without importing tests. + # Helpers/nested classes and explicitly disabled classes are not checks. + disabled = any( + isinstance(node, ast.Assign) + and any(isinstance(target, ast.Name) and target.id == "__test__" for target in node.targets) + and isinstance(node.value, ast.Constant) and node.value.value is False + for node in cls.body + ) + if not cls.name.startswith("Test") or disabled: + continue + if any(isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)) and node.name in {"__init__", "__new__"} for node in cls.body): + continue for method in cls.body: if isinstance(method, (ast.FunctionDef, ast.AsyncFunctionDef)) and method.name.startswith("test_"): problems.append( @@ -257,4 +283,4 @@ def main(argv: Sequence[str] | None = None) -> int: if __name__ == "__main__": raise SystemExit(main()) -# ratios: loc_comments=181:45 imports_exports=8:4 calls_definitions=72:8 +# ratios: loc_comments=205:47 imports_exports=8:4 calls_definitions=88:8 From 8ec67074352ecdea186586510e1593bbe61c0331 Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Sat, 12 Sep 2026 01:45:14 +0000 Subject: [PATCH 20/65] Validate the exact generated sdist configuration bytes --- tests/test_distributions.py | 5 +++-- tools/verify_distributions.py | 8 ++++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/tests/test_distributions.py b/tests/test_distributions.py index 6ea6302..1358780 100644 --- a/tests/test_distributions.py +++ b/tests/test_distributions.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=71:10 imports_exports=6:1 calls_definitions=32:2 +# ratios: loc_comments=72:10 imports_exports=6:1 calls_definitions=32:2 # === CHECKS === # id: check_distribution_replay_inputs # proves: distributions_retain_exact_replay_inputs @@ -76,6 +76,7 @@ def test_distribution_replay_inputs_fail_closed(tmp_path: Path) -> None: ({"wheel_omit": "WHEEL"}, "missing wheel metadata"), ({"wheel_omit": "RECORD"}, "missing wheel metadata"), ({"sdist_extra": "setup.py"}, "unexpected payload setup.py"), + ({"sdist_extra": "setup.cfg"}, "altered generated setup.cfg"), ({"extra": "ucns-0.dist-info/entry_points.txt"}, "unexpected wheel metadata"), ): _archives(root, sdist, wheel, **options) @@ -89,4 +90,4 @@ def test_distribution_replay_inputs_fail_closed(tmp_path: Path) -> None: with zipfile.ZipFile(wheel, "a") as archive, pytest.warns(UserWarning, match="Duplicate"): archive.writestr("ucns/__init__.py", b"duplicate") assert any("duplicate" in problem for problem in audit.verify_distributions(root, sdist, wheel)) -# ratios: loc_comments=71:10 imports_exports=6:1 calls_definitions=32:2 +# ratios: loc_comments=72:10 imports_exports=6:1 calls_definitions=32:2 diff --git a/tools/verify_distributions.py b/tools/verify_distributions.py index f13736b..a95e28a 100644 --- a/tools/verify_distributions.py +++ b/tools/verify_distributions.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=87:32 imports_exports=5:4 calls_definitions=49:5 +# ratios: loc_comments=146:33 imports_exports=5:4 calls_definitions=73:6 # === MODULE_BUILD === # id: ucns_distribution_audit # module_name: verify_distributions @@ -53,6 +53,7 @@ ".agents/skills": {".md", ".json", ".py", ".ts"}, } SDIST_GENERATED = { + "setup.cfg", "PKG-INFO", "src/ucns.egg-info/PKG-INFO", "src/ucns.egg-info/SOURCES.txt", @@ -60,6 +61,7 @@ "src/ucns.egg-info/requires.txt", "src/ucns.egg-info/top_level.txt", } +GENERATED_SETUP_CFG = b"[egg_info]\ntag_build = \ntag_date = 0\n\n" WHEEL_DIST_INFO_FILES = {"METADATA", "WHEEL", "RECORD", "top_level.txt"} WHEEL_LICENSE_PATH = "licenses/LICENSE" @@ -174,6 +176,8 @@ def verify_distributions(root: Path, sdist: Path, wheel: Path) -> list[str]: if not metadata_prefix or not name.startswith(f"{metadata_prefix}/"): problems.append(f"{path.name}: unexpected payload {name}") else: + if "setup.cfg" in actual and actual["setup.cfg"] != GENERATED_SETUP_CFG: + problems.append(f"{path.name}: altered generated setup.cfg") for name in sorted(actual.keys() - inputs.keys()): if name not in SDIST_GENERATED: problems.append(f"{path.name}: unexpected payload {name}") @@ -195,4 +199,4 @@ def main() -> int: if __name__ == "__main__": raise SystemExit(main()) -# ratios: loc_comments=87:32 imports_exports=5:4 calls_definitions=49:5 +# ratios: loc_comments=146:33 imports_exports=5:4 calls_definitions=73:6 From c5cdef8ac0e766f56169fcde92536684f85690b5 Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Sat, 12 Sep 2026 01:45:50 +0000 Subject: [PATCH 21/65] Bind the UCNS frontier report to the repaired implementation --- docs/work-graphs/repository-plan-report.json | 40 ++++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/docs/work-graphs/repository-plan-report.json b/docs/work-graphs/repository-plan-report.json index b9a9a46..4863264 100644 --- a/docs/work-graphs/repository-plan-report.json +++ b/docs/work-graphs/repository-plan-report.json @@ -10,15 +10,15 @@ }, "source": { "branch": "repair/ucns-audit-20260911", - "commit": "0c8e9fc38edf3b3c2c00e6309c4064cb9b0e27fb", - "generated_at": "2026-09-11", - "note": "Describes the exact local implementation commit below this coordination-only report refresh. Not published or merged by this audit; remote main was observed at 487d24e3a0ed7d1cff858b85faae00312d631905. Report standing does not confer mathematical, semantic, empirical, or deployment authority." + "commit": "8ec67074352ecdea186586510e1593bbe61c0331", + "generated_at": "2026-09-12", + "note": "Describes the exact implementation commit preceding this report-only refresh. Maintenance PR #226 is published; its final remote checks and merge remain separate acceptance observations. Report standing does not confer mathematical, semantic, empirical, or deployment authority." }, "authority": { "owns": [ "geometry constructions and exact geometric carriers implemented in UCNS", "UCNS-local mathematical representation candidates and their proof/status evidence", - "Public Gonol carrier geometry and native Möbius geometric state" + "Public Gonol carrier geometry and native M\u00f6bius geometric state" ], "does_not_own": [ "lexical, semantic, morphological, NLP, EDCM measurement, PTCNA runtime, PCEA security, or external-physics truth claims" @@ -38,7 +38,7 @@ } }, "status": { - "state": "active geometry repository; local maintenance repair awaiting publication and remote CI", + "state": "active geometry repository; maintenance repair in exact-head review", "current_claim": "UCNS retains exact geometric carriers and candidate modular/wave relations. Local repairs harden immutable/exact inputs, MPFR NaN ordering, no-exec contract auditing, selected-check receipts, and replayable distributions; no candidate is ratified by those repairs." }, "delivered": [ @@ -48,8 +48,8 @@ "boundary": "glyph function meaning is not inferred from Unicode or lexical semantics" }, { - "surface": "native Möbius geometry", - "status": "implemented exact framed 360° visible / 720° complete return", + "surface": "native M\u00f6bius geometry", + "status": "implemented exact framed 360\u00b0 visible / 720\u00b0 complete return", "boundary": "display equivalence does not collapse complete-state identity" }, { @@ -63,8 +63,8 @@ "boundary": "non-Boolean integer and immutable record admission; NaN cannot supply ordered interval evidence; valid geometric laws unchanged" }, { - "surface": "Contract audit and selected-check receipt schema 2", - "status": "implemented fail-closed graph audit and source-bound JUnit classification", + "surface": "Contract audit and selected-check receipt schema 2.1", + "status": "implemented fail-closed declaration audit, actual pytest outcome classification, import-origin verification, per-check snapshots, and Linux source-write observation", "boundary": "skips, empty evidence, and source mutation do not pass; selected checks do not establish whole-repository, theorem, freshness, or canon standing" }, { @@ -74,17 +74,17 @@ } ], "active_frontier": [ - "publish and remotely verify the local maintenance repair after its exact skill-lib dependency is available", + "close exact-head maintenance review and then verify the source-distribution/clean-install replay chain across supported Python versions", "reconcile the intrinsic/extrinsic Structural Null representation before constructing an origin attachment", - "derive the complete visible-circle wave trace into the native frame-bearing Möbius carrier", + "derive the complete visible-circle wave trace into the native frame-bearing M\u00f6bius carrier", "ratify or reject modular-orbit / continuum-boundary-trace candidates under explicit falsifiers", "resolve higher-dimensional recursive-scale laws and geometric operations for unresolved Public Gonol positions" ], "next_actions": [ { - "action": "Review and publish skill-lib 537cc3954b9ce4f660d46694b1e704a3ae9a5684, then UCNS repair; run Python 3.10/3.12 remote tests, drift, build, Twine, lock and archive-content gates", - "owner": "The-Interdependency/skill-lib and The-Interdependency/ucns maintainers", - "dependency": "separate publication direction; current inherited CI dependency is local and unpublished" + "action": "Complete exact-head review and Python 3.10/3.12 CI for UCNS PR #226; merge after all live findings and gates close", + "owner": "The-Interdependency/ucns maintainers", + "dependency": "Canonical skill-lib 8de4f12d0f31ff94f41e4a0196c447c0cbe20faf is published; nine installed skills pass strict drift verification" }, { "action": "Correct stale stack research claims about PR #220 and reconcile its exact research base before consuming results", @@ -102,7 +102,7 @@ "dependency": "exact accepted attachment; this dependency order is a research contract, not ratified UCNS geometry" }, { - "action": "Propose a complete native Möbius wave lift with 360/720-degree and continuum-degree counterexample checks", + "action": "Propose a complete native M\u00f6bius wave lift with 360/720-degree and continuum-degree counterexample checks", "owner": "The-Interdependency/ucns geometry", "dependency": "explicit frame-bearing construction and candidate selection protocol" }, @@ -113,7 +113,7 @@ } ], "blocked": [ - "New remote maintenance CI is unobserved and requires publication of the inherited exact skill-lib dependency.", + "Final exact-head CI and merge remain unobserved at this report refresh.", "Origin attachment and recursive traversal cannot be supplied by numerical defaults or downstream interpretations." ], "cross_repository_relations": [ @@ -158,10 +158,10 @@ "hmmm": [ "Structural Null executable representation and candidate documentation remain unreconciled.", "Origin attachment, recursive-scale transitions, and unresolved Public Gonol operations lack selecting geometric laws.", - "Modular-orbit / continuum-boundary-trace ratification and complete native Möbius lift remain unresolved.", - "The exact cited Möbius Strips and Quantum Geometry.txt source attachment is absent from the inspected owning tree.", - "A passing default suite includes archived receipt checks, not a newly executed full independent Gröbner replay.", - "New remote CI, merge, release, cross-repository repinning, and proof-assistant verification are unobserved.", + "Modular-orbit / continuum-boundary-trace ratification and complete native M\u00f6bius lift remain unresolved.", + "The exact cited M\u00f6bius Strips and Quantum Geometry.txt source attachment is absent from the inspected owning tree.", + "A passing default suite includes archived receipt checks, not a newly executed full independent Gr\u00f6bner replay.", + "Final-head remote CI, merge, release, cross-repository repinning, and proof-assistant verification remain separate observations.", "Pre-existing ratio coverage gaps and large-module decomposition remain incremental maintenance work." ] } From 73ab41bf491aa8835508000552fb3ebeae6801e4 Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Sat, 12 Sep 2026 02:16:02 +0000 Subject: [PATCH 22/65] Bind wheel installer metadata and keep receipts outside source --- .github/workflows/ci.yml | 2 +- README.md | 8 +- pyproject.toml | 2 +- tests/test_distributions.py | 48 ++++++--- tests/test_skill_lib_boundary_runner.py | 19 +++- tests/test_skill_lib_contracts.py | 2 +- tools/run_skill_lib_boundaries.py | 22 ++-- tools/verify_distributions.py | 127 ++++++++++++++++++++++-- tools/verify_skill_lib_contracts.py | 10 +- uv.lock | 2 + 10 files changed, 205 insertions(+), 37 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1a6c002..6ce354b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -65,4 +65,4 @@ jobs: - name: Check distributions run: | .venv/bin/python -m twine check dist/* - python tools/verify_distributions.py . dist + .venv/bin/python tools/verify_distributions.py . dist diff --git a/README.md b/README.md index 9b5048c..5dbaeda 100644 --- a/README.md +++ b/README.md @@ -151,7 +151,7 @@ uv sync --locked --python python --extra test --extra build python tools/verify_skill_lib_contracts.py . .venv/bin/python -m build .venv/bin/python -m twine check dist/* -python tools/verify_distributions.py . dist +.venv/bin/python tools/verify_distributions.py . dist ``` The independent interval checks also require system MPFR (for example, @@ -162,7 +162,9 @@ The wheel supplies the geometry package. Repository-context research replay and the full tests require the source archive or checkout: run from its root, where the archived preregistrations, generated evidence, and vendored parser live. The distribution gate checks these inputs byte-for-byte; it does not recertify -their mathematical claims. +their mathematical claims. It also binds wheel dependencies and flags to +`pyproject.toml`, verifies every RECORD digest, and requires the exact generated +sdist configuration. Exact modular and trace records require immutable tuples with non-Boolean integer residues; prefer the public builders. MPFR rational constructors accept only @@ -178,6 +180,8 @@ For a source-bound, selected-check receipt: Receipt schema 2.1 rejects skips, expected failures, both forms of XPASS, absent reports, and source changes during execution (including write-and-restore). Receipt execution requires Linux inotify; an unavailable observer is an error. +Receipt output must be outside the bound source tree and is written by atomic +replacement so an output hardlink cannot modify a bound input. Checks import the bound checkout with ambient PYTHONPATH and pytest plugins excluded, and imported package paths are recorded and checked. The observer is evidence instrumentation, not a sandbox for hostile test code. A `passed` receipt covers only its selected diff --git a/pyproject.toml b/pyproject.toml index 5dbb840..e16ee1e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,7 +30,7 @@ research = [ "mpmath>=1.3,<2", "sympy>=1.12,<2", ] -build = ["build>=1.2", "twine>=5"] +build = ["build>=1.2", "twine>=5", "tomli>=2; python_version < '3.11'"] [tool.setuptools] package-dir = {"" = "src"} diff --git a/tests/test_distributions.py b/tests/test_distributions.py index 1358780..79a57a8 100644 --- a/tests/test_distributions.py +++ b/tests/test_distributions.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=72:10 imports_exports=6:1 calls_definitions=32:2 +# ratios: loc_comments=92:10 imports_exports=9:1 calls_definitions=39:2 # === CHECKS === # id: check_distribution_replay_inputs # proves: distributions_retain_exact_replay_inputs @@ -12,6 +12,9 @@ """Build small archive fixtures; missing evidence must fail independently of Twine.""" import importlib.util +import base64 +import csv +import hashlib import io from pathlib import Path import tarfile @@ -26,9 +29,12 @@ SPEC.loader.exec_module(audit) -def _archives(root, sdist, wheel, *, omit="", altered="", extra="", sdist_extra="", wheel_omit="", wheel_altered=""): +def _archives(root, sdist, wheel, *, omit="", altered="", extra="", sdist_extra="", wheel_omit="", wheel_altered="", metadata_extra="", wheel_flags="true", record_mode=""): with tarfile.open(sdist, "w:gz") as archive: - for name, data in audit.expected_files(root).items(): + files = {**audit.expected_files(root), "setup.cfg": audit.GENERATED_SETUP_CFG} + if sdist_extra: + files[sdist_extra] = b"unexpected" + for name, data in files.items(): if name == omit: continue if name == altered: @@ -36,25 +42,33 @@ def _archives(root, sdist, wheel, *, omit="", altered="", extra="", sdist_extra= member = tarfile.TarInfo(f"ucns-0/{name}") member.size = len(data) archive.addfile(member, io.BytesIO(data)) - if sdist_extra: - member = tarfile.TarInfo(f"ucns-0/{sdist_extra}") - member.size = 10 - archive.addfile(member, io.BytesIO(b"unexpected")) with zipfile.ZipFile(wheel, "w") as archive: + files = {} for name, data in audit.expected_files(root).items(): if name.startswith("src/ucns/"): - archive.writestr(name.removeprefix("src/"), data) + files[name.removeprefix("src/")] = data metadata = { - "METADATA": b"Metadata-Version: 2.4\nName: ucns\nVersion: 0\n", - "WHEEL": b"Wheel-Version: 1.0\nRoot-Is-Purelib: true\nTag: py3-none-any\n", - "RECORD": b"", + "METADATA": ("Metadata-Version: 2.4\nName: ucns\nVersion: 0\nSummary: Fixture\nAuthor: Test\nRequires-Python: >=3.10\nDescription-Content-Type: text/markdown\nLicense: fixture\nLicense-File: LICENSE\nDynamic: license-file\n" + metadata_extra + "\nfixture\n").encode(), + "WHEEL": f"Wheel-Version: 1.0\nGenerator: fixture\nRoot-Is-Purelib: {wheel_flags}\nTag: py3-none-any\n".encode(), "licenses/LICENSE": (root / "LICENSE").read_bytes(), } for name, data in metadata.items(): if name != wheel_omit: - archive.writestr(f"ucns-0.dist-info/{name}", data + (b"drift" if name == wheel_altered else b"")) + files[f"ucns-0.dist-info/{name}"] = data + (b"drift" if name == wheel_altered else b"") if extra: - archive.writestr(extra, b"unexpected") + files[extra] = b"unexpected" + record = io.StringIO() + writer = csv.writer(record) + for name, data in files.items(): + digest = base64.urlsafe_b64encode(hashlib.sha256(data).digest()).rstrip(b"=").decode() + writer.writerow((name, "sha256=" + digest, len(data))) + writer.writerow(("ucns-0.dist-info/RECORD", "", "")) + if wheel_omit != "RECORD": + files["ucns-0.dist-info/RECORD"] = b"" if record_mode == "empty" else record.getvalue().encode() + if record_mode == "wrong-hash": + files["ucns/__init__.py"] += b"changed after recording" + for name, data in files.items(): + archive.writestr(name, data) def test_distribution_replay_inputs_fail_closed(tmp_path: Path) -> None: @@ -64,6 +78,7 @@ def test_distribution_replay_inputs_fail_closed(tmp_path: Path) -> None: path = root / name path.parent.mkdir(parents=True, exist_ok=True) path.write_text("fixture\n") + (root / "pyproject.toml").write_text('[project]\nname="ucns"\nversion="0"\ndescription="Fixture"\nrequires-python=">=3.10"\nauthors=[{name="Test"}]\nreadme="README.md"\n') sdist, wheel = tmp_path / "ucns.tar.gz", tmp_path / "ucns.whl" _archives(root, sdist, wheel) assert audit.verify_distributions(root, sdist, wheel) == [] @@ -77,6 +92,11 @@ def test_distribution_replay_inputs_fail_closed(tmp_path: Path) -> None: ({"wheel_omit": "RECORD"}, "missing wheel metadata"), ({"sdist_extra": "setup.py"}, "unexpected payload setup.py"), ({"sdist_extra": "setup.cfg"}, "altered generated setup.cfg"), + ({"omit": "setup.cfg"}, "missing generated setup.cfg"), + ({"metadata_extra": "Requires-Dist: unexpected>=1\n"}, "Requires-Dist differs"), + ({"wheel_flags": "false"}, "Root-Is-Purelib differs"), + ({"record_mode": "empty"}, "RECORD missing path"), + ({"record_mode": "wrong-hash"}, "RECORD digest or size mismatch"), ({"extra": "ucns-0.dist-info/entry_points.txt"}, "unexpected wheel metadata"), ): _archives(root, sdist, wheel, **options) @@ -90,4 +110,4 @@ def test_distribution_replay_inputs_fail_closed(tmp_path: Path) -> None: with zipfile.ZipFile(wheel, "a") as archive, pytest.warns(UserWarning, match="Duplicate"): archive.writestr("ucns/__init__.py", b"duplicate") assert any("duplicate" in problem for problem in audit.verify_distributions(root, sdist, wheel)) -# ratios: loc_comments=72:10 imports_exports=6:1 calls_definitions=32:2 +# ratios: loc_comments=92:10 imports_exports=9:1 calls_definitions=39:2 diff --git a/tests/test_skill_lib_boundary_runner.py b/tests/test_skill_lib_boundary_runner.py index f3ce485..a7afd43 100644 --- a/tests/test_skill_lib_boundary_runner.py +++ b/tests/test_skill_lib_boundary_runner.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=98:209 imports_exports=6:16 calls_definitions=74:18 +# ratios: loc_comments=98:224 imports_exports=8:16 calls_definitions=83:18 # === CHECKS === # id: check_boundary_runner_audit_gate # proves: boundary_runner_audits_before_execution @@ -311,6 +311,21 @@ def test_source_mutation_prevents_acceptance(tmp_path: Path) -> None: assert receipt["source_unchanged"] is False assert receipt["status"] == "not-passed" assert "src/pkg/feature.py" in receipt["outcomes"][0]["source_events"] + import pytest + import subprocess + with pytest.raises(ValueError, match="outside the bound source tree"): + runner.write_receipt(receipt, root / "generated/receipt.json") + assert not (root / "generated/receipt.json").exists() + result = subprocess.run([sys.executable, str(RUNNER_PATH), str(root), "--receipt", str(root / "generated/receipt.json")], capture_output=True, text=True) + assert result.returncode == 2 + assert "outside the bound source tree" in result.stderr + external = tmp_path / "outside-receipt.json" + source = root / "src/pkg/feature.py" + original = source.read_bytes() + external.hardlink_to(source) + runner.write_receipt(receipt, external) + assert source.read_bytes() == original + assert json.loads(external.read_text())["receipt_sha256"] == receipt["receipt_sha256"] # === CHECKS === # id: check_boundary_runner_import_origin # proves: boundary_runner_receipt_is_bounded_and_bound, boundary_pytest_observes_actual_outcomes @@ -338,4 +353,4 @@ def test_check_imports_bound_source_despite_ambient_pythonpath(tmp_path: Path, m receipt = runner.run_boundaries(root) assert receipt["status"] == "passed", receipt assert receipt["outcomes"][0]["imported_sources"]["pkg.feature"] == [str(source)] -# ratios: loc_comments=98:209 imports_exports=6:16 calls_definitions=74:18 +# ratios: loc_comments=98:224 imports_exports=8:16 calls_definitions=83:18 diff --git a/tests/test_skill_lib_contracts.py b/tests/test_skill_lib_contracts.py index 06568d1..efd9006 100644 --- a/tests/test_skill_lib_contracts.py +++ b/tests/test_skill_lib_contracts.py @@ -143,7 +143,7 @@ def test_nested_fences_cannot_hide_an_obligation(tmp_path: Path) -> None: ok, problems = audit_repository(root) assert not ok and any("malformed id" in item for item in problems) source.write_text(original) - (root / "tests/test_helpers.py").write_text("class Helper:\n def test_helper(self): pass\nclass TestDisabled:\n __test__ = False\n def test_disabled(self): pass\ndef helper():\n class TestNested:\n def test_nested(self): pass\n") + (root / "tests/test_helpers.py").write_text("class Helper:\n def test_helper(self): pass\nclass TestDisabled:\n __test__ = False\n def test_disabled(self): pass\nclass TestTypedDisabled:\n __test__: bool = False\n def test_disabled(self): pass\ndef helper():\n class TestNested:\n def test_nested(self): pass\n") ok, problems = audit_repository(root) assert ok, problems # ratios: loc_comments=88:42 imports_exports=5:5 calls_definitions=47:5 diff --git a/tools/run_skill_lib_boundaries.py b/tools/run_skill_lib_boundaries.py index 9157e20..7c96391 100644 --- a/tools/run_skill_lib_boundaries.py +++ b/tools/run_skill_lib_boundaries.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=361:65 imports_exports=19:4 calls_definitions=151:18 +# ratios: loc_comments=370:66 imports_exports=19:4 calls_definitions=160:18 # === MODULE_BUILD === # id: skill_lib_boundary_runner # module_name: run_skill_lib_boundaries @@ -397,6 +397,7 @@ def run_boundaries( "schema_id": SCHEMA_ID, "schema_version": SCHEMA_VERSION, "status": "audit-gap", "audit_closed": False, "audit_gaps": gaps, "outcomes": [], "selection_effect": "none", + "bound_source_root": str(root), "edcm_activation": "inactive", "canon_status": "none", } receipt["receipt_sha256"] = _receipt_identity(receipt) @@ -428,6 +429,7 @@ def run_boundaries( ) receipt: dict[str, object] = { "schema_id": SCHEMA_ID, "schema_version": SCHEMA_VERSION, + "bound_source_root": str(root), "status": "passed" if outcomes and statuses == {"PASS"} and unchanged else "not-passed", "audit_closed": True, "audit_gaps": [], "source_files_sha256": source_files, @@ -451,11 +453,17 @@ def run_boundaries( def write_receipt(receipt: dict[str, object], path: Path) -> None: + bound_root = receipt.get("bound_source_root") + if not isinstance(bound_root, str) or not bound_root: + raise ValueError("receipt must identify its bound source tree") + if path.resolve().is_relative_to(Path(bound_root).resolve()): + raise ValueError("receipt output must be outside the bound source tree") path.parent.mkdir(parents=True, exist_ok=True) - path.write_text( - json.dumps(receipt, indent=2, sort_keys=True, ensure_ascii=False) + "\n", - encoding="utf-8", - ) + # Replacing the directory entry avoids writing through an external hardlink. + with tempfile.TemporaryDirectory(prefix=".ucns-receipt-", dir=path.parent) as temporary: + output = Path(temporary) / "receipt.json" + output.write_text(json.dumps(receipt, indent=2, sort_keys=True, ensure_ascii=False) + "\n", encoding="utf-8") + os.replace(output, path) def main(argv: Sequence[str] | None = None) -> int: @@ -464,6 +472,8 @@ def main(argv: Sequence[str] | None = None) -> int: parser.add_argument("--check", action="append", default=[], dest="checks") parser.add_argument("--receipt", type=Path) args = parser.parse_args(argv) + if args.receipt and args.receipt.resolve().is_relative_to(Path(args.root).resolve()): + parser.error("receipt output must be outside the bound source tree") receipt = run_boundaries(Path(args.root), selected_ids=args.checks) if args.receipt: write_receipt(receipt, args.receipt) @@ -473,4 +483,4 @@ def main(argv: Sequence[str] | None = None) -> int: if __name__ == "__main__": raise SystemExit(main()) -# ratios: loc_comments=361:65 imports_exports=19:4 calls_definitions=151:18 +# ratios: loc_comments=370:66 imports_exports=19:4 calls_definitions=160:18 diff --git a/tools/verify_distributions.py b/tools/verify_distributions.py index a95e28a..979ae43 100644 --- a/tools/verify_distributions.py +++ b/tools/verify_distributions.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=146:33 imports_exports=5:4 calls_definitions=73:6 +# ratios: loc_comments=252:34 imports_exports=15:4 calls_definitions=128:9 # === MODULE_BUILD === # id: ucns_distribution_audit # module_name: verify_distributions @@ -38,10 +38,24 @@ from __future__ import annotations import argparse +import base64 +from collections import Counter +import csv +from email.parser import BytesParser +import hashlib +import io from pathlib import Path, PurePosixPath import tarfile import zipfile +from packaging.requirements import Requirement +from packaging.utils import canonicalize_name + +try: + import tomllib +except ModuleNotFoundError: # Python 3.10; declared in the build extra. + import tomli as tomllib + ROOT_INPUTS = ( "pyproject.toml", "README.md", "AGENTS.md", "CANON.md", "CLAUDE.md", @@ -119,7 +133,84 @@ def record(name: str, data: bytes) -> None: return files -def _wheel_metadata_problems(actual: dict[str, bytes], license_bytes: bytes) -> list[str]: +def _requirement_key(value: str) -> tuple[str, ...]: + requirement = Requirement(value) + return (canonicalize_name(requirement.name), ",".join(sorted(requirement.extras)), + str(requirement.specifier), requirement.url or "", str(requirement.marker or "")) + + +def _metadata_content_problems(data: bytes, expected: dict[str, bytes]) -> list[str]: + """Bind installer-facing metadata to this project's static configuration.""" + project = tomllib.loads(expected["pyproject.toml"].decode("utf-8"))["project"] + metadata = BytesParser().parsebytes(data) + wanted = { + "Metadata-Version": ["2.4"], "Name": [project["name"]], + "Version": [project["version"]], "Summary": [project["description"]], + "Requires-Python": [project["requires-python"]], + "Author": [", ".join(author["name"] for author in project["authors"])], + "Classifier": project.get("classifiers", []), + "Description-Content-Type": ["text/markdown"], "License-File": ["LICENSE"], + "Dynamic": ["license-file"], + "Provides-Extra": list(project.get("optional-dependencies", {})), + } + problems = [] + if metadata.defects: + problems.append("malformed wheel METADATA") + allowed = {key.lower() for key in wanted} | {"license", "requires-dist"} + for name in metadata.keys(): + if name.lower() not in allowed: + problems.append(f"unexpected wheel METADATA field {name}") + for name, values in wanted.items(): + if Counter(metadata.get_all(name, [])) != Counter(values): + problems.append(f"wheel METADATA {name} differs from project configuration") + licenses = metadata.get_all("License", []) + if len(licenses) != 1 or " ".join(licenses[0].split()) != " ".join(expected["LICENSE"].decode().split()): + problems.append("wheel METADATA License differs from source license") + if metadata.get_payload(decode=True).rstrip() != expected[project["readme"]].rstrip(): + problems.append("wheel METADATA description differs from source README") + requirements = list(project.get("dependencies", [])) + for extra, dependencies in project.get("optional-dependencies", {}).items(): + for value in dependencies: + requirement = Requirement(value) + marker = f'({requirement.marker}) and extra == "{extra}"' if requirement.marker else f'extra == "{extra}"' + requirement.marker = None + requirements.append(f"{requirement}; {marker}") + try: + if Counter(map(_requirement_key, metadata.get_all("Requires-Dist", []))) != Counter(map(_requirement_key, requirements)): + problems.append("wheel METADATA Requires-Dist differs from project configuration") + except ValueError as error: + problems.append(f"invalid wheel METADATA Requires-Dist: {error}") + return problems + + +def _record_problems(actual: dict[str, bytes], record_path: str) -> list[str]: + problems = [] + seen = set() + try: + for row in csv.reader(io.StringIO(actual[record_path].decode("utf-8")), strict=True): + if len(row) != 3: + problems.append("wheel RECORD row must have three fields") + continue + name, digest, size = row + if name in seen or name not in actual: + problems.append(f"wheel RECORD duplicate or unknown path {name}") + continue + seen.add(name) + if name == record_path: + if digest or size: + problems.append("wheel RECORD self-entry must omit hash and size") + else: + expected_digest = base64.urlsafe_b64encode(hashlib.sha256(actual[name]).digest()).rstrip(b"=").decode() + if digest != "sha256=" + expected_digest or size != str(len(actual[name])): + problems.append(f"wheel RECORD digest or size mismatch {name}") + except (UnicodeError, csv.Error) as error: + problems.append(f"malformed wheel RECORD: {error}") + for name in sorted(actual.keys() - seen): + problems.append(f"wheel RECORD missing path {name}") + return problems + + +def _wheel_metadata_problems(actual: dict[str, bytes], expected: dict[str, bytes]) -> list[str]: problems: list[str] = [] prefixes = { name.split("/", 1)[0] @@ -129,6 +220,10 @@ def _wheel_metadata_problems(actual: dict[str, bytes], license_bytes: bytes) -> if len(prefixes) != 1: return ["wheel must contain exactly one .dist-info directory"] prefix = next(iter(prefixes)) + project = tomllib.loads(expected["pyproject.toml"].decode("utf-8"))["project"] + expected_prefix = f"{project['name'].replace('-', '_')}-{project['version']}.dist-info" + if prefix != expected_prefix: + problems.append("wheel .dist-info identity differs from project configuration") allowed = {f"{prefix}/{name}" for name in WHEEL_DIST_INFO_FILES} allowed.add(f"{prefix}/{WHEEL_LICENSE_PATH}") for name in sorted(actual): @@ -139,8 +234,26 @@ def _wheel_metadata_problems(actual: dict[str, bytes], license_bytes: bytes) -> for name in sorted(required - actual.keys()): problems.append(f"missing wheel metadata {name}") packaged_license = actual.get(f"{prefix}/{WHEEL_LICENSE_PATH}") - if packaged_license is not None and packaged_license != license_bytes: + if packaged_license is not None and packaged_license != expected["LICENSE"]: problems.append(f"altered wheel license {prefix}/{WHEEL_LICENSE_PATH}") + if f"{prefix}/METADATA" in actual: + problems.extend(_metadata_content_problems(actual[f"{prefix}/METADATA"], expected)) + if f"{prefix}/WHEEL" in actual: + metadata = BytesParser().parsebytes(actual[f"{prefix}/WHEEL"]) + wanted = {"Wheel-Version": ["1.0"], "Root-Is-Purelib": ["true"], "Tag": ["py3-none-any"]} + if metadata.defects or metadata.get_payload().strip(): + problems.append("malformed wheel WHEEL metadata") + for name, values in wanted.items(): + if metadata.get_all(name, []) != values: + problems.append(f"wheel WHEEL {name} differs from pure Python configuration") + if len(metadata.get_all("Generator", [])) != 1 or not metadata["Generator"].strip(): + problems.append("wheel WHEEL must identify its generator") + if any(name.lower() not in {"wheel-version", "root-is-purelib", "tag", "generator"} for name in metadata.keys()): + problems.append("unexpected wheel WHEEL field") + if f"{prefix}/RECORD" in actual: + problems.extend(_record_problems(actual, f"{prefix}/RECORD")) + if f"{prefix}/top_level.txt" in actual and actual[f"{prefix}/top_level.txt"] != b"ucns\n": + problems.append("altered wheel top_level.txt") return problems @@ -164,7 +277,7 @@ def verify_distributions(root: Path, sdist: Path, wheel: Path) -> list[str]: elif actual[name] != data: problems.append(f"{path.name}: altered {name}") if is_wheel: - metadata_problems = _wheel_metadata_problems(actual, expected["LICENSE"]) + metadata_problems = _wheel_metadata_problems(actual, expected) problems.extend(f"{path.name}: {problem}" for problem in metadata_problems) dist_prefixes = { name.split("/", 1)[0] @@ -176,7 +289,9 @@ def verify_distributions(root: Path, sdist: Path, wheel: Path) -> list[str]: if not metadata_prefix or not name.startswith(f"{metadata_prefix}/"): problems.append(f"{path.name}: unexpected payload {name}") else: - if "setup.cfg" in actual and actual["setup.cfg"] != GENERATED_SETUP_CFG: + if "setup.cfg" not in actual: + problems.append(f"{path.name}: missing generated setup.cfg") + elif actual["setup.cfg"] != GENERATED_SETUP_CFG: problems.append(f"{path.name}: altered generated setup.cfg") for name in sorted(actual.keys() - inputs.keys()): if name not in SDIST_GENERATED: @@ -199,4 +314,4 @@ def main() -> int: if __name__ == "__main__": raise SystemExit(main()) -# ratios: loc_comments=146:33 imports_exports=5:4 calls_definitions=73:6 +# ratios: loc_comments=252:34 imports_exports=15:4 calls_definitions=128:9 diff --git a/tools/verify_skill_lib_contracts.py b/tools/verify_skill_lib_contracts.py index 64e286f..bb436cf 100644 --- a/tools/verify_skill_lib_contracts.py +++ b/tools/verify_skill_lib_contracts.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=205:47 imports_exports=8:4 calls_definitions=88:8 +# ratios: loc_comments=207:47 imports_exports=8:4 calls_definitions=90:8 # === MODULE_BUILD === # id: skill_lib_contract_audit # module_name: verify_skill_lib_contracts @@ -250,8 +250,10 @@ def audit_repository(root: Path) -> Tuple[bool, List[str]]: # Match default pytest class collection without importing tests. # Helpers/nested classes and explicitly disabled classes are not checks. disabled = any( - isinstance(node, ast.Assign) - and any(isinstance(target, ast.Name) and target.id == "__test__" for target in node.targets) + ((isinstance(node, ast.Assign) + and any(isinstance(target, ast.Name) and target.id == "__test__" for target in node.targets)) + or (isinstance(node, ast.AnnAssign) + and isinstance(node.target, ast.Name) and node.target.id == "__test__")) and isinstance(node.value, ast.Constant) and node.value.value is False for node in cls.body ) @@ -283,4 +285,4 @@ def main(argv: Sequence[str] | None = None) -> int: if __name__ == "__main__": raise SystemExit(main()) -# ratios: loc_comments=205:47 imports_exports=8:4 calls_definitions=88:8 +# ratios: loc_comments=207:47 imports_exports=8:4 calls_definitions=90:8 diff --git a/uv.lock b/uv.lock index 989d2f2..0e61838 100644 --- a/uv.lock +++ b/uv.lock @@ -990,6 +990,7 @@ dependencies = [ [package.optional-dependencies] build = [ { name = "build" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, { name = "twine" }, ] research = [ @@ -1012,6 +1013,7 @@ requires-dist = [ { name = "pytest", marker = "extra == 'test'", specifier = ">=8" }, { name = "sympy", marker = "extra == 'research'", specifier = ">=1.12,<2" }, { name = "sympy", marker = "extra == 'test'", specifier = ">=1.12,<2" }, + { name = "tomli", marker = "python_full_version < '3.11' and extra == 'build'", specifier = ">=2" }, { name = "twine", marker = "extra == 'build'", specifier = ">=5" }, ] provides-extras = ["test", "research", "build"] From 523876424425b2fef7a24384b989baef3e661742 Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Sat, 12 Sep 2026 02:17:00 +0000 Subject: [PATCH 23/65] Refresh maintenance report to installer-metadata repair --- docs/work-graphs/repository-plan-report.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/work-graphs/repository-plan-report.json b/docs/work-graphs/repository-plan-report.json index 4863264..6ca0ee3 100644 --- a/docs/work-graphs/repository-plan-report.json +++ b/docs/work-graphs/repository-plan-report.json @@ -10,9 +10,9 @@ }, "source": { "branch": "repair/ucns-audit-20260911", - "commit": "8ec67074352ecdea186586510e1593bbe61c0331", + "commit": "73ab41bf491aa8835508000552fb3ebeae6801e4", "generated_at": "2026-09-12", - "note": "Describes the exact implementation commit preceding this report-only refresh. Maintenance PR #226 is published; its final remote checks and merge remain separate acceptance observations. Report standing does not confer mathematical, semantic, empirical, or deployment authority." + "note": "Describes the exact implementation commit preceding this report-only refresh. Maintenance PR #226 is in final exact-head review; merge remains a separate acceptance observation. No scientific or deployment authority is transferred." }, "authority": { "owns": [ From db804f4c04f81ddc3d9348ac66300134ba55318d Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Sat, 12 Sep 2026 02:39:44 +0000 Subject: [PATCH 24/65] Bind source metadata, inherited checks, and descendant input identity --- README.md | 11 ++-- tests/test_distributions.py | 28 ++++++++-- tests/test_skill_lib_boundary_runner.py | 15 ++++- tests/test_skill_lib_contracts.py | 7 ++- tools/run_skill_lib_boundaries.py | 10 +++- tools/verify_distributions.py | 73 +++++++++++++++++++++---- tools/verify_skill_lib_contracts.py | 9 ++- 7 files changed, 124 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index 5dbaeda..791c65e 100644 --- a/README.md +++ b/README.md @@ -162,9 +162,10 @@ The wheel supplies the geometry package. Repository-context research replay and the full tests require the source archive or checkout: run from its root, where the archived preregistrations, generated evidence, and vendored parser live. The distribution gate checks these inputs byte-for-byte; it does not recertify -their mathematical claims. It also binds wheel dependencies and flags to +their mathematical claims. It also binds wheel filename tags, dependencies, and flags to `pyproject.toml`, verifies every RECORD digest, and requires the exact generated -sdist configuration. +sdist configuration. Both source PKG-INFO records and generated egg-info +dependency and file lists must agree with that same source configuration. Exact modular and trace records require immutable tuples with non-Boolean integer residues; prefer the public builders. MPFR rational constructors accept only @@ -178,12 +179,14 @@ For a source-bound, selected-check receipt: ``` Receipt schema 2.1 rejects skips, expected failures, both forms of XPASS, absent -reports, and source changes during execution (including write-and-restore). +reports, and source changes during execution (including write-and-restore +through external hardlinks). Receipt execution requires Linux inotify; an unavailable observer is an error. Receipt output must be outside the bound source tree and is written by atomic replacement so an output hardlink cannot modify a bound input. Checks import the bound checkout with ambient PYTHONPATH and pytest plugins -excluded, and imported package paths are recorded and checked. The observer is +replaced with the bound source path, which ordinary Python descendants inherit. +Origin receipts observe the selected pytest process. The observer is evidence instrumentation, not a sandbox for hostile test code. A `passed` receipt covers only its selected checks; it does not select geometry, ratify candidates, or establish freshness. diff --git a/tests/test_distributions.py b/tests/test_distributions.py index 79a57a8..02b4719 100644 --- a/tests/test_distributions.py +++ b/tests/test_distributions.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=92:10 imports_exports=9:1 calls_definitions=39:2 +# ratios: loc_comments=110:10 imports_exports=9:1 calls_definitions=44:2 # === CHECKS === # id: check_distribution_replay_inputs # proves: distributions_retain_exact_replay_inputs @@ -30,10 +30,18 @@ def _archives(root, sdist, wheel, *, omit="", altered="", extra="", sdist_extra="", wheel_omit="", wheel_altered="", metadata_extra="", wheel_flags="true", record_mode=""): + core_metadata = "Metadata-Version: 2.4\nName: ucns\nVersion: 0\nSummary: Fixture\nAuthor: Test\nRequires-Python: >=3.10\nDescription-Content-Type: text/markdown\nLicense: fixture\nLicense-File: LICENSE\nDynamic: license-file\n" with tarfile.open(sdist, "w:gz") as archive: - files = {**audit.expected_files(root), "setup.cfg": audit.GENERATED_SETUP_CFG} + files = {**audit.expected_files(root), "setup.cfg": audit.GENERATED_SETUP_CFG, + "PKG-INFO": (core_metadata + "\nfixture\n").encode(), + "src/ucns.egg-info/PKG-INFO": (core_metadata + "\nfixture\n").encode(), + "src/ucns.egg-info/top_level.txt": b"ucns\n", + "src/ucns.egg-info/dependency_links.txt": b"\n", + "src/ucns.egg-info/requires.txt": b"", + "src/ucns.egg-info/SOURCES.txt": b""} if sdist_extra: files[sdist_extra] = b"unexpected" + files["src/ucns.egg-info/SOURCES.txt"] = ("\n".join(sorted(files.keys() - {"PKG-INFO", "setup.cfg"})) + "\n").encode() for name, data in files.items(): if name == omit: continue @@ -48,7 +56,7 @@ def _archives(root, sdist, wheel, *, omit="", altered="", extra="", sdist_extra= if name.startswith("src/ucns/"): files[name.removeprefix("src/")] = data metadata = { - "METADATA": ("Metadata-Version: 2.4\nName: ucns\nVersion: 0\nSummary: Fixture\nAuthor: Test\nRequires-Python: >=3.10\nDescription-Content-Type: text/markdown\nLicense: fixture\nLicense-File: LICENSE\nDynamic: license-file\n" + metadata_extra + "\nfixture\n").encode(), + "METADATA": (core_metadata + metadata_extra + "\nfixture\n").encode(), "WHEEL": f"Wheel-Version: 1.0\nGenerator: fixture\nRoot-Is-Purelib: {wheel_flags}\nTag: py3-none-any\n".encode(), "licenses/LICENSE": (root / "LICENSE").read_bytes(), } @@ -79,7 +87,7 @@ def test_distribution_replay_inputs_fail_closed(tmp_path: Path) -> None: path.parent.mkdir(parents=True, exist_ok=True) path.write_text("fixture\n") (root / "pyproject.toml").write_text('[project]\nname="ucns"\nversion="0"\ndescription="Fixture"\nrequires-python=">=3.10"\nauthors=[{name="Test"}]\nreadme="README.md"\n') - sdist, wheel = tmp_path / "ucns.tar.gz", tmp_path / "ucns.whl" + sdist, wheel = tmp_path / "ucns-0.tar.gz", tmp_path / "ucns-0-py3-none-any.whl" _archives(root, sdist, wheel) assert audit.verify_distributions(root, sdist, wheel) == [] for options, message in ( @@ -92,6 +100,12 @@ def test_distribution_replay_inputs_fail_closed(tmp_path: Path) -> None: ({"wheel_omit": "RECORD"}, "missing wheel metadata"), ({"sdist_extra": "setup.py"}, "unexpected payload setup.py"), ({"sdist_extra": "setup.cfg"}, "altered generated setup.cfg"), + ({"sdist_extra": "PKG-INFO"}, "sdist PKG-INFO Name differs"), + ({"sdist_extra": "src/ucns.egg-info/PKG-INFO"}, "sdist src/ucns.egg-info/PKG-INFO Name differs"), + ({"omit": "PKG-INFO"}, "missing sdist metadata PKG-INFO"), + ({"omit": "src/ucns.egg-info/PKG-INFO"}, "missing sdist metadata src/ucns.egg-info/PKG-INFO"), + ({"sdist_extra": "src/ucns.egg-info/requires.txt"}, "requires.txt differs"), + ({"altered": "src/ucns.egg-info/SOURCES.txt"}, "SOURCES.txt differs"), ({"omit": "setup.cfg"}, "missing generated setup.cfg"), ({"metadata_extra": "Requires-Dist: unexpected>=1\n"}, "Requires-Dist differs"), ({"wheel_flags": "false"}, "Root-Is-Purelib differs"), @@ -107,7 +121,11 @@ def test_distribution_replay_inputs_fail_closed(tmp_path: Path) -> None: _archives(root, sdist, wheel, extra="../escaped.py") assert any("unsafe" in problem for problem in audit.verify_distributions(root, sdist, wheel)) _archives(root, sdist, wheel) + renamed = wheel.with_name("ucns-0-cp310-cp310-manylinux_2_17_x86_64.whl") + wheel.rename(renamed) + assert any("filename identity or tags" in problem for problem in audit.verify_distributions(root, sdist, renamed)) + renamed.rename(wheel) with zipfile.ZipFile(wheel, "a") as archive, pytest.warns(UserWarning, match="Duplicate"): archive.writestr("ucns/__init__.py", b"duplicate") assert any("duplicate" in problem for problem in audit.verify_distributions(root, sdist, wheel)) -# ratios: loc_comments=92:10 imports_exports=9:1 calls_definitions=39:2 +# ratios: loc_comments=110:10 imports_exports=9:1 calls_definitions=44:2 diff --git a/tests/test_skill_lib_boundary_runner.py b/tests/test_skill_lib_boundary_runner.py index a7afd43..f948a50 100644 --- a/tests/test_skill_lib_boundary_runner.py +++ b/tests/test_skill_lib_boundary_runner.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=98:224 imports_exports=8:16 calls_definitions=83:18 +# ratios: loc_comments=98:233 imports_exports=8:16 calls_definitions=87:18 # === CHECKS === # id: check_boundary_runner_audit_gate # proves: boundary_runner_audits_before_execution @@ -326,6 +326,15 @@ def test_source_mutation_prevents_acceptance(tmp_path: Path) -> None: runner.write_receipt(receipt, external) assert source.read_bytes() == original assert json.loads(external.read_text())["receipt_sha256"] == receipt["receipt_sha256"] + alias = tmp_path / "source-alias.py" + body = f"from pathlib import Path\ndef test_probe():\n p=Path({str(alias)!r})\n original=p.read_bytes()\n p.write_bytes(original+b'# transient\\n')\n p.write_bytes(original)\n" + root = _repo(tmp_path / "hardlinked", body, [{"id": "check_probe", "function": "test_probe"}]) + alias.hardlink_to(root / "src/pkg/feature.py") + receipt = runner.run_boundaries(root) + assert receipt["outcomes"][0]["status"] == "PASS" + assert receipt["source_before_sha256"] == receipt["source_after_sha256"] + assert receipt["status"] == "not-passed" + assert "src/pkg/feature.py" in receipt["outcomes"][0]["source_events"] # === CHECKS === # id: check_boundary_runner_import_origin # proves: boundary_runner_receipt_is_bounded_and_bound, boundary_pytest_observes_actual_outcomes @@ -338,7 +347,7 @@ def test_source_mutation_prevents_acceptance(tmp_path: Path) -> None: def test_check_imports_bound_source_despite_ambient_pythonpath(tmp_path: Path, monkeypatch) -> None: - body = "import pkg.feature\ndef test_probe():\n assert pkg.feature.VALUE == 'bound'\n" + body = "import pkg.feature\nimport subprocess, sys\ndef test_probe():\n assert pkg.feature.VALUE == 'bound'\n child = subprocess.check_output([sys.executable, '-c', 'import pkg.feature; print(pkg.feature.VALUE)'], text=True)\n assert child.strip() == 'bound'\n" root = _repo(tmp_path, body, [{"id": "check_probe", "function": "test_probe"}]) source = root / "src/pkg/feature.py" source.write_text(source.read_text() + "VALUE = 'bound'\n") @@ -353,4 +362,4 @@ def test_check_imports_bound_source_despite_ambient_pythonpath(tmp_path: Path, m receipt = runner.run_boundaries(root) assert receipt["status"] == "passed", receipt assert receipt["outcomes"][0]["imported_sources"]["pkg.feature"] == [str(source)] -# ratios: loc_comments=98:224 imports_exports=8:16 calls_definitions=83:18 +# ratios: loc_comments=98:233 imports_exports=8:16 calls_definitions=87:18 diff --git a/tests/test_skill_lib_contracts.py b/tests/test_skill_lib_contracts.py index efd9006..346b8e5 100644 --- a/tests/test_skill_lib_contracts.py +++ b/tests/test_skill_lib_contracts.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=88:42 imports_exports=5:5 calls_definitions=47:5 +# ratios: loc_comments=91:42 imports_exports=5:5 calls_definitions=50:5 # === CHECKS === # id: check_contract_audit_no_exec # proves: contract_audit_is_no_exec @@ -146,4 +146,7 @@ def test_nested_fences_cannot_hide_an_obligation(tmp_path: Path) -> None: (root / "tests/test_helpers.py").write_text("class Helper:\n def test_helper(self): pass\nclass TestDisabled:\n __test__ = False\n def test_disabled(self): pass\nclass TestTypedDisabled:\n __test__: bool = False\n def test_disabled(self): pass\ndef helper():\n class TestNested:\n def test_nested(self): pass\n") ok, problems = audit_repository(root) assert ok, problems -# ratios: loc_comments=88:42 imports_exports=5:5 calls_definitions=47:5 + (root / "tests/test_inherited.py").write_text("class Base:\n def test_inherited(self): assert False\nclass TestChild(Base):\n pass\n") + ok, problems = audit_repository(root) + assert not ok and any("inherited class check" in item for item in problems) +# ratios: loc_comments=91:42 imports_exports=5:5 calls_definitions=50:5 diff --git a/tools/run_skill_lib_boundaries.py b/tools/run_skill_lib_boundaries.py index 7c96391..87b6c0e 100644 --- a/tools/run_skill_lib_boundaries.py +++ b/tools/run_skill_lib_boundaries.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=370:66 imports_exports=19:4 calls_definitions=160:18 +# ratios: loc_comments=373:67 imports_exports=19:4 calls_definitions=162:18 # === MODULE_BUILD === # id: skill_lib_boundary_runner # module_name: run_skill_lib_boundaries @@ -124,7 +124,10 @@ def __init__(self, root: Path): directories.add(parent) parent = parent.parent # MODIFY, ATTRIB, MOVED_FROM/TO, CREATE, DELETE, DELETE_SELF, MOVE_SELF. - for directory in sorted(directories): + # File watches follow the inode, including writes via external hardlinks. + files, _ = _source_snapshot(root) + watched = directories | {root / name for name in files} + for directory in sorted(watched): descriptor = self.libc.inotify_add_watch(self.fd, os.fsencode(directory), 0xFC6) if descriptor < 0: raise OSError(ctypes.get_errno(), "cannot watch source directory") @@ -342,6 +345,7 @@ def _run_check(root: Path, check: Entry) -> CheckOutcome: for name in ("PYTHONPATH", "PYTHONHOME", "PYTEST_ADDOPTS", "PYTEST_PLUGINS"): environment.pop(name, None) environment.update(PYTHONDONTWRITEBYTECODE="1", PYTEST_DISABLE_PLUGIN_AUTOLOAD="1") + environment["PYTHONPATH"] = os.pathsep.join((str(root / "src"), str(root))) watcher = _SourceWatch(root) try: with stdout_path.open("wb") as stdout, stderr_path.open("wb") as stderr: @@ -483,4 +487,4 @@ def main(argv: Sequence[str] | None = None) -> int: if __name__ == "__main__": raise SystemExit(main()) -# ratios: loc_comments=370:66 imports_exports=19:4 calls_definitions=160:18 +# ratios: loc_comments=373:67 imports_exports=19:4 calls_definitions=162:18 diff --git a/tools/verify_distributions.py b/tools/verify_distributions.py index 979ae43..55c7851 100644 --- a/tools/verify_distributions.py +++ b/tools/verify_distributions.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=252:34 imports_exports=15:4 calls_definitions=128:9 +# ratios: loc_comments=301:34 imports_exports=17:4 calls_definitions=155:11 # === MODULE_BUILD === # id: ucns_distribution_audit # module_name: verify_distributions @@ -49,7 +49,9 @@ import zipfile from packaging.requirements import Requirement -from packaging.utils import canonicalize_name +from packaging.utils import canonicalize_name, parse_wheel_filename, parse_sdist_filename +from packaging.tags import Tag +from packaging.version import Version try: import tomllib @@ -139,6 +141,17 @@ def _requirement_key(value: str) -> tuple[str, ...]: str(requirement.specifier), requirement.url or "", str(requirement.marker or "")) +def _project_requirements(project: dict) -> list[str]: + requirements = list(project.get("dependencies", [])) + for extra, dependencies in project.get("optional-dependencies", {}).items(): + for value in dependencies: + requirement = Requirement(value) + marker = f'({requirement.marker}) and extra == "{extra}"' if requirement.marker else f'extra == "{extra}"' + requirement.marker = None + requirements.append(f"{requirement}; {marker}") + return requirements + + def _metadata_content_problems(data: bytes, expected: dict[str, bytes]) -> list[str]: """Bind installer-facing metadata to this project's static configuration.""" project = tomllib.loads(expected["pyproject.toml"].decode("utf-8"))["project"] @@ -168,13 +181,7 @@ def _metadata_content_problems(data: bytes, expected: dict[str, bytes]) -> list[ problems.append("wheel METADATA License differs from source license") if metadata.get_payload(decode=True).rstrip() != expected[project["readme"]].rstrip(): problems.append("wheel METADATA description differs from source README") - requirements = list(project.get("dependencies", [])) - for extra, dependencies in project.get("optional-dependencies", {}).items(): - for value in dependencies: - requirement = Requirement(value) - marker = f'({requirement.marker}) and extra == "{extra}"' if requirement.marker else f'extra == "{extra}"' - requirement.marker = None - requirements.append(f"{requirement}; {marker}") + requirements = _project_requirements(project) try: if Counter(map(_requirement_key, metadata.get_all("Requires-Dist", []))) != Counter(map(_requirement_key, requirements)): problems.append("wheel METADATA Requires-Dist differs from project configuration") @@ -183,6 +190,41 @@ def _metadata_content_problems(data: bytes, expected: dict[str, bytes]) -> list[ return problems +def _sdist_metadata_problems(actual: dict[str, bytes], expected: dict[str, bytes]) -> list[str]: + problems = [] + for name in ("PKG-INFO", "src/ucns.egg-info/PKG-INFO"): + if name not in actual: + problems.append(f"missing sdist metadata {name}") + else: + problems.extend(problem.replace("wheel METADATA", f"sdist {name}") + for problem in _metadata_content_problems(actual[name], expected)) + for name, value in (("top_level.txt", b"ucns\n"), ("dependency_links.txt", b"\n")): + if actual.get("src/ucns.egg-info/" + name) != value: + problems.append(f"altered or missing sdist {name}") + sources = actual.get("src/ucns.egg-info/SOURCES.txt", b"").decode("utf-8").splitlines() + if Counter(sources) != Counter(actual.keys() - {"PKG-INFO", "setup.cfg"}): + problems.append("sdist SOURCES.txt differs from archive members") + project = tomllib.loads(expected["pyproject.toml"].decode("utf-8"))["project"] + requirements = [] + marker = "" + try: + for line in actual.get("src/ucns.egg-info/requires.txt", b"").decode("utf-8").splitlines(): + if not line.strip(): + continue + if line.startswith("[") and line.endswith("]"): + extra, _, condition = line[1:-1].partition(":") + marker = condition + if extra: + marker = f'({marker}) and extra == "{extra}"' if marker else f'extra == "{extra}"' + else: + requirements.append(_requirement_key(line + ("; " + marker if marker else ""))) + if Counter(requirements) != Counter(map(_requirement_key, _project_requirements(project))): + problems.append("sdist requires.txt differs from project dependencies") + except (ValueError, UnicodeError): + problems.append("invalid sdist requires.txt") + return problems + + def _record_problems(actual: dict[str, bytes], record_path: str) -> list[str]: problems = [] seen = set() @@ -259,12 +301,22 @@ def _wheel_metadata_problems(actual: dict[str, bytes], expected: dict[str, bytes def verify_distributions(root: Path, sdist: Path, wheel: Path) -> list[str]: expected = expected_files(root.resolve()) + project = tomllib.loads(expected["pyproject.toml"].decode("utf-8"))["project"] wheel_expected = { name.removeprefix("src/"): data for name, data in expected.items() if name.startswith("src/ucns/") } problems: list[str] = [] + try: + name, version, build, tags = parse_wheel_filename(wheel.name) + if name != canonicalize_name(project["name"]) or version != Version(project["version"]) or build or tags != {Tag("py3", "none", "any")}: + problems.append("wheel filename identity or tags differ from project/WHEEL metadata") + name, version = parse_sdist_filename(sdist.name) + if name != canonicalize_name(project["name"]) or version != Version(project["version"]): + problems.append("sdist filename identity differs from project metadata") + except ValueError as error: + problems.append(f"invalid distribution filename: {error}") for path, inputs, is_wheel in ((sdist, expected, False), (wheel, wheel_expected, True)): try: actual = read_archive(path, wheel=is_wheel) @@ -289,6 +341,7 @@ def verify_distributions(root: Path, sdist: Path, wheel: Path) -> list[str]: if not metadata_prefix or not name.startswith(f"{metadata_prefix}/"): problems.append(f"{path.name}: unexpected payload {name}") else: + problems.extend(f"{path.name}: {problem}" for problem in _sdist_metadata_problems(actual, expected)) if "setup.cfg" not in actual: problems.append(f"{path.name}: missing generated setup.cfg") elif actual["setup.cfg"] != GENERATED_SETUP_CFG: @@ -314,4 +367,4 @@ def main() -> int: if __name__ == "__main__": raise SystemExit(main()) -# ratios: loc_comments=252:34 imports_exports=15:4 calls_definitions=128:9 +# ratios: loc_comments=301:34 imports_exports=17:4 calls_definitions=155:11 diff --git a/tools/verify_skill_lib_contracts.py b/tools/verify_skill_lib_contracts.py index bb436cf..40ab61e 100644 --- a/tools/verify_skill_lib_contracts.py +++ b/tools/verify_skill_lib_contracts.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=207:47 imports_exports=8:4 calls_definitions=90:8 +# ratios: loc_comments=212:47 imports_exports=8:4 calls_definitions=92:8 # === MODULE_BUILD === # id: skill_lib_contract_audit # module_name: verify_skill_lib_contracts @@ -261,6 +261,11 @@ def audit_repository(root: Path) -> Tuple[bool, List[str]]: continue if any(isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)) and node.name in {"__init__", "__new__"} for node in cls.body): continue + if any(not (isinstance(base, ast.Name) and base.id == "object") for base in cls.bases): + problems.append( + f"GAP inherited class check {test_path}::{cls.name}; " + "base-class executable surfaces require a declared top-level witness" + ) for method in cls.body: if isinstance(method, (ast.FunctionDef, ast.AsyncFunctionDef)) and method.name.startswith("test_"): problems.append( @@ -285,4 +290,4 @@ def main(argv: Sequence[str] | None = None) -> int: if __name__ == "__main__": raise SystemExit(main()) -# ratios: loc_comments=207:47 imports_exports=8:4 calls_definitions=90:8 +# ratios: loc_comments=212:47 imports_exports=8:4 calls_definitions=92:8 From 493bf71b05e624da403cdb03f1b0542a0ecd1642 Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Sat, 12 Sep 2026 02:40:15 +0000 Subject: [PATCH 25/65] Bind report to complete source and runtime evidence repairs --- docs/work-graphs/repository-plan-report.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/work-graphs/repository-plan-report.json b/docs/work-graphs/repository-plan-report.json index 6ca0ee3..2c1913d 100644 --- a/docs/work-graphs/repository-plan-report.json +++ b/docs/work-graphs/repository-plan-report.json @@ -10,7 +10,7 @@ }, "source": { "branch": "repair/ucns-audit-20260911", - "commit": "73ab41bf491aa8835508000552fb3ebeae6801e4", + "commit": "db804f4c04f81ddc3d9348ac66300134ba55318d", "generated_at": "2026-09-12", "note": "Describes the exact implementation commit preceding this report-only refresh. Maintenance PR #226 is in final exact-head review; merge remains a separate acceptance observation. No scientific or deployment authority is transferred." }, From d59738db2c624a164914351241766faa89a0c3cc Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Sat, 12 Sep 2026 03:29:42 +0000 Subject: [PATCH 26/65] Bind descendant lifetime and imports and close archive audit gaps --- README.md | 17 ++- pyproject.toml | 2 +- tests/test_distributions.py | 16 ++- tests/test_skill_lib_boundary_runner.py | 42 +++++- tests/test_skill_lib_contracts.py | 26 +++- tools/_boundary_pytest.py | 53 +++++++- tools/_boundary_site/sitecustomize.py | 66 ++++++++++ tools/run_skill_lib_boundaries.py | 24 +++- tools/verify_distributions.py | 33 +++-- tools/verify_skill_lib_contracts.py | 167 +++++++++++++++++++----- 10 files changed, 374 insertions(+), 72 deletions(-) create mode 100644 tools/_boundary_site/sitecustomize.py diff --git a/README.md b/README.md index 791c65e..1251d1a 100644 --- a/README.md +++ b/README.md @@ -156,7 +156,7 @@ python tools/verify_skill_lib_contracts.py . The independent interval checks also require system MPFR (for example, `libmpfr6` on Ubuntu). Verification dependencies are locked; the isolated build -backend still follows `[build-system].requires`. Use a clean `dist` directory. +backend is pinned in `[build-system].requires`. Use a clean `dist` directory. The wheel supplies the geometry package. Repository-context research replay and the full tests require the source archive or checkout: run from its root, where @@ -165,7 +165,8 @@ The distribution gate checks these inputs byte-for-byte; it does not recertify their mathematical claims. It also binds wheel filename tags, dependencies, and flags to `pyproject.toml`, verifies every RECORD digest, and requires the exact generated sdist configuration. Both source PKG-INFO records and generated egg-info -dependency and file lists must agree with that same source configuration. +dependency and file lists must agree with that same source configuration. Directory +entries are validated too; duplicate names and file/directory collisions fail. Exact modular and trace records require immutable tuples with non-Boolean integer residues; prefer the public builders. MPFR rational constructors accept only @@ -184,10 +185,14 @@ through external hardlinks). Receipt execution requires Linux inotify; an unavailable observer is an error. Receipt output must be outside the bound source tree and is written by atomic replacement so an output hardlink cannot modify a bound input. -Checks import the bound checkout with ambient PYTHONPATH and pytest plugins -replaced with the bound source path, which ordinary Python descendants inherit. -Origin receipts observe the selected pytest process. The observer is -evidence instrumentation, not a sandbox for hostile test code. A `passed` receipt covers only its selected +Checks import the bound checkout with ambient pytest plugins disabled and +PYTHONPATH replaced. An inherited startup hook makes ordinary Python descendants +prefer bound packages over their working directory. The bootstrap uses Linux +child subreaping to terminate remaining descendants before observation ends; +leaked background work prevents acceptance. Origin receipts observe the selected +pytest process. Explicit isolated/no-site interpreters and replaced child +environments do not inherit the import protocol. The observer instruments trusted +checks; it is not a sandbox for hostile test code. A `passed` receipt covers only its selected checks; it does not select geometry, ratify candidates, or establish freshness. `hmmm`: ratification of the modular-orbit / continuum-boundary-trace candidates, the complete higher-dimensional UCNS construction, the exact visible-circle wave-trace lift into the native Möbius carrier, any law selecting one continuum covering lift from a finite modular congruence class, and the exact geometric operation of every Public Gonol function position remain unresolved. Unresolved geometry stays unresolved; semantic machinery is not used to fill it. diff --git a/pyproject.toml b/pyproject.toml index e16ee1e..2052459 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [build-system] -requires = ["setuptools>=68", "wheel"] +requires = ["setuptools==84.0.0", "wheel==0.48.0"] build-backend = "setuptools.build_meta" [project] diff --git a/tests/test_distributions.py b/tests/test_distributions.py index 02b4719..cd95148 100644 --- a/tests/test_distributions.py +++ b/tests/test_distributions.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=110:10 imports_exports=9:1 calls_definitions=44:2 +# ratios: loc_comments=120:10 imports_exports=9:1 calls_definitions=47:2 # === CHECKS === # id: check_distribution_replay_inputs # proves: distributions_retain_exact_replay_inputs @@ -29,7 +29,7 @@ SPEC.loader.exec_module(audit) -def _archives(root, sdist, wheel, *, omit="", altered="", extra="", sdist_extra="", wheel_omit="", wheel_altered="", metadata_extra="", wheel_flags="true", record_mode=""): +def _archives(root, sdist, wheel, *, omit="", altered="", extra="", sdist_extra="", wheel_omit="", wheel_altered="", metadata_extra="", wheel_flags="true", record_mode="", sdist_directory="", wheel_directory=""): core_metadata = "Metadata-Version: 2.4\nName: ucns\nVersion: 0\nSummary: Fixture\nAuthor: Test\nRequires-Python: >=3.10\nDescription-Content-Type: text/markdown\nLicense: fixture\nLicense-File: LICENSE\nDynamic: license-file\n" with tarfile.open(sdist, "w:gz") as archive: files = {**audit.expected_files(root), "setup.cfg": audit.GENERATED_SETUP_CFG, @@ -50,6 +50,10 @@ def _archives(root, sdist, wheel, *, omit="", altered="", extra="", sdist_extra= member = tarfile.TarInfo(f"ucns-0/{name}") member.size = len(data) archive.addfile(member, io.BytesIO(data)) + if sdist_directory: + member = tarfile.TarInfo(f"ucns-0/{sdist_directory}") + member.type = tarfile.DIRTYPE + archive.addfile(member) with zipfile.ZipFile(wheel, "w") as archive: files = {} for name, data in audit.expected_files(root).items(): @@ -77,6 +81,8 @@ def _archives(root, sdist, wheel, *, omit="", altered="", extra="", sdist_extra= files["ucns/__init__.py"] += b"changed after recording" for name, data in files.items(): archive.writestr(name, data) + if wheel_directory: + archive.writestr(wheel_directory.rstrip("/") + "/", b"") def test_distribution_replay_inputs_fail_closed(tmp_path: Path) -> None: @@ -111,6 +117,10 @@ def test_distribution_replay_inputs_fail_closed(tmp_path: Path) -> None: ({"wheel_flags": "false"}, "Root-Is-Purelib differs"), ({"record_mode": "empty"}, "RECORD missing path"), ({"record_mode": "wrong-hash"}, "RECORD digest or size mismatch"), + ({"sdist_directory": "pyproject.toml"}, "duplicate archive member"), + ({"wheel_directory": "ucns/__init__.py"}, "duplicate archive member"), + ({"sdist_directory": "../escaped"}, "unsafe archive name"), + ({"extra": "ucns/__init__.py/inside.py"}, "file/directory archive collision"), ({"extra": "ucns-0.dist-info/entry_points.txt"}, "unexpected wheel metadata"), ): _archives(root, sdist, wheel, **options) @@ -128,4 +138,4 @@ def test_distribution_replay_inputs_fail_closed(tmp_path: Path) -> None: with zipfile.ZipFile(wheel, "a") as archive, pytest.warns(UserWarning, match="Duplicate"): archive.writestr("ucns/__init__.py", b"duplicate") assert any("duplicate" in problem for problem in audit.verify_distributions(root, sdist, wheel)) -# ratios: loc_comments=110:10 imports_exports=9:1 calls_definitions=44:2 +# ratios: loc_comments=120:10 imports_exports=9:1 calls_definitions=47:2 diff --git a/tests/test_skill_lib_boundary_runner.py b/tests/test_skill_lib_boundary_runner.py index f948a50..ebe9616 100644 --- a/tests/test_skill_lib_boundary_runner.py +++ b/tests/test_skill_lib_boundary_runner.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=98:233 imports_exports=8:16 calls_definitions=87:18 +# ratios: loc_comments=98:266 imports_exports=10:17 calls_definitions=103:19 # === CHECKS === # id: check_boundary_runner_audit_gate # proves: boundary_runner_audits_before_execution @@ -337,7 +337,7 @@ def test_source_mutation_prevents_acceptance(tmp_path: Path) -> None: assert "src/pkg/feature.py" in receipt["outcomes"][0]["source_events"] # === CHECKS === # id: check_boundary_runner_import_origin -# proves: boundary_runner_receipt_is_bounded_and_bound, boundary_pytest_observes_actual_outcomes +# proves: boundary_runner_receipt_is_bounded_and_bound, boundary_pytest_observes_actual_outcomes, boundary_descendants_import_bound_source # call: self::test_check_imports_bound_source_despite_ambient_pythonpath # requires: python3, pytest # timeout: 15 @@ -359,7 +359,43 @@ def test_check_imports_bound_source_despite_ambient_pythonpath(tmp_path: Path, m (alternate / "pkg/__init__.py").write_text("") (alternate / "pkg/feature.py").write_text("VALUE = 'wrong'\n") monkeypatch.setenv("PYTHONPATH", str(alternate)) + test_source = root / "tests/test_feature.py" + test_source.write_text(test_source.read_text().replace("text=True)", f"text=True, cwd={str(alternate)!r})")) receipt = runner.run_boundaries(root) assert receipt["status"] == "passed", receipt assert receipt["outcomes"][0]["imported_sources"]["pkg.feature"] == [str(source)] -# ratios: loc_comments=98:233 imports_exports=8:16 calls_definitions=87:18 + +# === CHECKS === +# id: check_boundary_background_descendants +# proves: boundary_runner_receipt_is_bounded_and_bound, boundary_pytest_observes_actual_outcomes +# call: self::test_background_descendants_block_acceptance +# requires: python3, pytest +# timeout: 30 +# mutates: temporary_path +# cleanup: pytest temporary_path +# === END CHECKS === + + +def test_background_descendants_block_acceptance(tmp_path: Path) -> None: + import os + import pytest + child = "import time; from pathlib import Path; time.sleep(30); Path('src/pkg/feature.py').write_text('late mutation')" + body = f"import subprocess, sys\nfrom pathlib import Path\ndef test_probe():\n p=subprocess.Popen([sys.executable, '-c', {child!r}], start_new_session=True)\n Path('child.pid').write_text(str(p.pid))\n" + root = _repo(tmp_path, body, [{"id": "check_probe", "function": "test_probe"}]) + before = (root / "src/pkg/feature.py").read_bytes() + receipt = runner.run_boundaries(root) + assert receipt["status"] == "not-passed", receipt + assert receipt["outcomes"][0]["status"] == "ERROR" + assert receipt["outcomes"][0]["descendants_reaped"] >= 1 + pid = int((root / "child.pid").read_text()) + with pytest.raises(ProcessLookupError): + os.kill(pid, 0) + assert (root / "src/pkg/feature.py").read_bytes() == before + body += " import time; time.sleep(30)\n" + timeout_root = _repo(tmp_path / "timeout-child", body, [{"id": "check_probe", "function": "test_probe", "timeout": "5"}]) + receipt = runner.run_boundaries(timeout_root) + assert receipt["outcomes"][0]["status"] == "TIMEOUT" + pid = int((timeout_root / "child.pid").read_text()) + with pytest.raises(ProcessLookupError): + os.kill(pid, 0) +# ratios: loc_comments=98:266 imports_exports=10:17 calls_definitions=103:19 diff --git a/tests/test_skill_lib_contracts.py b/tests/test_skill_lib_contracts.py index 346b8e5..863d97c 100644 --- a/tests/test_skill_lib_contracts.py +++ b/tests/test_skill_lib_contracts.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=91:42 imports_exports=5:5 calls_definitions=50:5 +# ratios: loc_comments=113:42 imports_exports=5:5 calls_definitions=65:5 # === CHECKS === # id: check_contract_audit_no_exec # proves: contract_audit_is_no_exec @@ -149,4 +149,26 @@ def test_nested_fences_cannot_hide_an_obligation(tmp_path: Path) -> None: (root / "tests/test_inherited.py").write_text("class Base:\n def test_inherited(self): assert False\nclass TestChild(Base):\n pass\n") ok, problems = audit_repository(root) assert not ok and any("inherited class check" in item for item in problems) -# ratios: loc_comments=91:42 imports_exports=5:5 calls_definitions=50:5 + inherited = root / "tests/test_inherited.py" + for declaration in ("__test__ = False", "__test__: bool = False"): + inherited.write_text(f"class Base:\n {declaration}\n def test_hidden(self): assert False\nclass TestChild(Base):\n pass\n") + ok, problems = audit_repository(root) + assert ok, problems + inherited.write_text("class A:\n __test__ = False\n def test_hidden(self): pass\nclass B(A): pass\nclass C(A):\n __test__ = True\nclass TestChild(B, C): pass\n") + ok, problems = audit_repository(root) + assert not ok and any("TestChild::test_hidden" in item for item in problems) + for code, label in ( + ("def hidden(): assert False\ntest_aliased = hidden\n", "unresolved executable alias"), + ("class Helper:\n __test__ = True\n def test_explicit(self): pass\n", "Helper::test_explicit"), + ("class TestAlias:\n def hidden(self): pass\n test_aliased = hidden\n", "TestAlias::test_aliased"), + ): + inherited.write_text(code) + ok, problems = audit_repository(root) + assert not ok and any(label in item for item in problems), problems + inherited.write_text("test_data = [1, 2]\n") + ok, problems = audit_repository(root) + assert ok, problems + inherited.write_text("__test__ = False\ndef test_disabled(): assert False\n") + ok, problems = audit_repository(root) + assert ok, problems +# ratios: loc_comments=113:42 imports_exports=5:5 calls_definitions=65:5 diff --git a/tools/_boundary_pytest.py b/tools/_boundary_pytest.py index 77f1834..db5ee55 100644 --- a/tools/_boundary_pytest.py +++ b/tools/_boundary_pytest.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=63:27 imports_exports=5:2 calls_definitions=22:4 +# ratios: loc_comments=101:28 imports_exports=8:2 calls_definitions=39:7 # === MODULE_BUILD === # id: boundary_pytest_observer # module_name: _boundary_pytest @@ -19,7 +19,7 @@ # === CONTRACTS === # id: boundary_pytest_observes_actual_outcomes # given: a selected pytest boundary runs through this bootstrap -# then: assertion subclasses fail, unexpected exceptions error, XPASS cannot pass, and imported local package origins must match the bound tree +# then: assertion subclasses fail, unexpected exceptions error, XPASS cannot pass, imported local package origins must match the bound tree, and leaked descendants prevent acceptance # class: evidence # === END CONTRACTS === @@ -31,7 +31,10 @@ from __future__ import annotations import json +import ctypes +import os from pathlib import Path +import signal import sys import pytest @@ -65,12 +68,52 @@ def pytest_runtest_makereport(self, item, call): self.other.append(status) +def _enable_descendant_reaping() -> None: + libc = ctypes.CDLL(None, use_errno=True) + if libc.prctl(36, 1, 0, 0, 0) != 0: # Linux PR_SET_CHILD_SUBREAPER. + raise OSError(ctypes.get_errno(), "cannot bind check descendant lifetime") + + +def _reap_descendants() -> int: + """Terminate and reap only children owned/adopted by this bootstrap.""" + observed = 0 + children_path = Path(f"/proc/self/task/{os.getpid()}/children") + while True: + children = [int(pid) for pid in children_path.read_text().split()] + if not children: + return observed + observed += len(children) + for pid in children: + try: + os.kill(pid, signal.SIGKILL) + except ProcessLookupError: + pass + for pid in children: + try: + os.waitpid(pid, 0) + except ChildProcessError: + pass + + def main() -> int: root = Path(sys.argv[1]).resolve() report_path = Path(sys.argv[2]) sys.path[:0] = [str(root / "src"), str(root)] observer = Observer(root) - exit_code = int(pytest.main(sys.argv[3:], plugins=[observer])) + _enable_descendant_reaping() + def terminate_check(signum, frame): + _reap_descendants() + raise SystemExit(124) + signal.signal(signal.SIGTERM, terminate_check) + hook = sys.modules.get("sitecustomize") + if getattr(hook, "BOUND_ROOT", None) != str(root) or getattr(hook, "FINDER", None) not in sys.meta_path: + raise RuntimeError("bound descendant import hook is unavailable") + try: + exit_code = int(pytest.main(sys.argv[3:], plugins=[observer])) + finally: + descendants = _reap_descendants() + if descendants: + observer.other.append("ERROR") source_root = root / "src" local_names = {p.stem if p.is_file() else p.name for p in source_root.iterdir()} if source_root.exists() else set() origins = {} @@ -94,10 +137,10 @@ def main() -> int: status = "PASS" else: status = "ERROR" - report_path.write_text(json.dumps({"status": status, "calls": observer.calls, "other": observer.other, "origins": origins, "wrong_origins": wrong_origins}), encoding="utf-8") + report_path.write_text(json.dumps({"status": status, "calls": observer.calls, "other": observer.other, "origins": origins, "wrong_origins": wrong_origins, "descendants_reaped": descendants}), encoding="utf-8") return exit_code if __name__ == "__main__": raise SystemExit(main()) -# ratios: loc_comments=63:27 imports_exports=5:2 calls_definitions=22:4 +# ratios: loc_comments=101:28 imports_exports=8:2 calls_definitions=39:7 diff --git a/tools/_boundary_site/sitecustomize.py b/tools/_boundary_site/sitecustomize.py new file mode 100644 index 0000000..6a8d7b7 --- /dev/null +++ b/tools/_boundary_site/sitecustomize.py @@ -0,0 +1,66 @@ +# ratios: loc_comments=28:27 imports_exports=5:1 calls_definitions=13:3 +# === MODULE_BUILD === +# id: boundary_descendant_import_binding +# module_name: sitecustomize +# module_kind: instrument +# summary: keeps bound package imports ahead of child working directories during declared checks +# owner: Erin Spencer +# public_surface: none; installed on the boundary runner's sanitized PYTHONPATH +# internal_surface: BoundSourceFinder +# auth_boundary: none +# storage_boundary: reads source package locations +# network_boundary: none +# user_data_boundary: none +# admin_only: false +# tests: tests/test_skill_lib_boundary_runner.py +# rollout: inherited by ordinary Python check descendants +# rollback: remove together with the runner's startup-hook contract +# === END MODULE_BUILD === +# === CONTRACTS === +# id: boundary_descendants_import_bound_source +# given: an ordinary Python check subprocess inherits the boundary environment +# then: bound source packages resolve before same-named packages in its working directory +# class: evidence +# === END CONTRACTS === +"""Internal startup hook; the runner sets UCNS_BOUND_SOURCE_ROOT and PYTHONPATH. + +Explicit isolated/no-site interpreters and replaced environments do not inherit +this protocol. This is trusted-check evidence instrumentation, not a sandbox. +""" +from __future__ import annotations + +from importlib.machinery import PathFinder +import os +from pathlib import Path +import sys + + +BOUND_ROOT = os.environ.get("UCNS_BOUND_SOURCE_ROOT", "") +FINDER = None + + +class BoundSourceFinder: + def __init__(self, root: Path): + self.source = root / "src" + self.names = {p.stem if p.is_file() else p.name for p in self.source.iterdir() + if p.is_dir() or p.suffix == ".py"} if self.source.is_dir() else set() + + def find_spec(self, fullname, path=None, target=None): + if fullname.split(".", 1)[0] not in self.names: + return None + search = [str(self.source)] if "." not in fullname else path + spec = PathFinder.find_spec(fullname, search, target) + if spec is None: + raise ModuleNotFoundError(f"module absent from bound source: {fullname}") + locations = list(spec.submodule_search_locations or ()) + if spec.origin is not None: + locations.append(spec.origin) + if not locations or any(not Path(location).resolve().is_relative_to(self.source) for location in locations): + raise ImportError(f"module outside bound source: {fullname}") + return spec + + +if BOUND_ROOT: + FINDER = BoundSourceFinder(Path(BOUND_ROOT).resolve()) + sys.meta_path.insert(0, FINDER) +# ratios: loc_comments=28:27 imports_exports=5:1 calls_definitions=13:3 diff --git a/tools/run_skill_lib_boundaries.py b/tools/run_skill_lib_boundaries.py index 87b6c0e..9050451 100644 --- a/tools/run_skill_lib_boundaries.py +++ b/tools/run_skill_lib_boundaries.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=373:67 imports_exports=19:4 calls_definitions=162:18 +# ratios: loc_comments=385:67 imports_exports=19:4 calls_definitions=168:18 # === MODULE_BUILD === # id: skill_lib_boundary_runner # module_name: run_skill_lib_boundaries @@ -96,6 +96,7 @@ SOURCE_DIRECTORIES = ("src", "tools", "tests", "docs", "generated", ".agents/skills", ".github/workflows") ROOT_INPUTS = ("pyproject.toml", "uv.lock", "pytest.ini", "setup.cfg", "MANIFEST.in", "conftest.py", "CANON.md", "AGENTS.md", "README.md", "CLAUDE.md", "LICENSE") BOOTSTRAP = TOOLS_DIRECTORY / "_boundary_pytest.py" +STARTUP_DIRECTORY = TOOLS_DIRECTORY / "_boundary_site" class _SourceWatch: @@ -190,6 +191,7 @@ class CheckOutcome: stderr_excerpt: str missing_capabilities: tuple[str, ...] = () diagnostic: str = "" + descendants_reaped: int = 0 imported_sources: dict[str, list[str]] = field(default_factory=dict) source_events: tuple[str, ...] = () source_before_sha256: str = "" @@ -345,7 +347,8 @@ def _run_check(root: Path, check: Entry) -> CheckOutcome: for name in ("PYTHONPATH", "PYTHONHOME", "PYTEST_ADDOPTS", "PYTEST_PLUGINS"): environment.pop(name, None) environment.update(PYTHONDONTWRITEBYTECODE="1", PYTEST_DISABLE_PLUGIN_AUTOLOAD="1") - environment["PYTHONPATH"] = os.pathsep.join((str(root / "src"), str(root))) + environment["UCNS_BOUND_SOURCE_ROOT"] = str(root) + environment["PYTHONPATH"] = os.pathsep.join((str(STARTUP_DIRECTORY), str(root / "src"), str(root))) watcher = _SourceWatch(root) try: with stdout_path.open("wb") as stdout, stderr_path.open("wb") as stderr: @@ -359,10 +362,16 @@ def _run_check(root: Path, check: Entry) -> CheckOutcome: except subprocess.TimeoutExpired: timed_out = True try: - os.killpg(process.pid, signal.SIGKILL) + process.terminate() # Bootstrap reaps detached descendants too. + returncode = process.wait(timeout=3) + except subprocess.TimeoutExpired: + try: + os.killpg(process.pid, signal.SIGKILL) + except ProcessLookupError: + pass + returncode = process.wait() except ProcessLookupError: - pass - returncode = process.wait() + returncode = process.wait() finally: source_events = watcher.finish() stdout_sha, stdout_bytes, stdout_excerpt = _excerpt(stdout_path) @@ -377,6 +386,8 @@ def _run_check(root: Path, check: Entry) -> CheckOutcome: command, requires, timeout, mutates, cleanup, status, returncode, duration, stdout_sha, stderr_sha, stdout_bytes, stderr_bytes, stdout_excerpt, stderr_excerpt, + diagnostic="background descendants outlived the check" if observed.get("descendants_reaped") else "", + descendants_reaped=observed.get("descendants_reaped", 0), imported_sources=observed.get("origins", {}), source_events=source_events, ) @@ -441,6 +452,7 @@ def run_boundaries( "source_after_sha256": source_after, "source_unchanged": unchanged, "bootstrap_sha256": _sha(BOOTSTRAP.read_bytes()), + "startup_hook_sha256": _sha((STARTUP_DIRECTORY / "sitecustomize.py").read_bytes()), "python_version": sys.version, "selected_check_ids": [outcome.check_id for outcome in outcomes], "outcome_counts": { @@ -487,4 +499,4 @@ def main(argv: Sequence[str] | None = None) -> int: if __name__ == "__main__": raise SystemExit(main()) -# ratios: loc_comments=373:67 imports_exports=19:4 calls_definitions=162:18 +# ratios: loc_comments=385:67 imports_exports=19:4 calls_definitions=168:18 diff --git a/tools/verify_distributions.py b/tools/verify_distributions.py index 55c7851..07142e7 100644 --- a/tools/verify_distributions.py +++ b/tools/verify_distributions.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=301:34 imports_exports=17:4 calls_definitions=155:11 +# ratios: loc_comments=310:34 imports_exports=17:4 calls_definitions=159:11 # === MODULE_BUILD === # id: ucns_distribution_audit # module_name: verify_distributions @@ -99,31 +99,40 @@ def read_archive(path: Path, *, wheel: bool) -> dict[str, bytes]: """Read regular files only; reject ambiguous names rather than extracting.""" files: dict[str, bytes] = {} prefixes: set[str] = set() + kinds: dict[str, bool] = {} - def record(name: str, data: bytes) -> None: + def record(name: str, data: bytes | None) -> None: parts = PurePosixPath(name).parts - if not parts or name.startswith("/") or ".." in parts or "\\" in name: + directory = data is None + if not parts or name.startswith("/") or ".." in parts or "\\" in name or "\0" in name: raise ValueError(f"unsafe archive name: {name}") if not wheel: prefixes.add(parts[0]) - if len(parts) < 2 or len(prefixes) != 1: + if (len(parts) < 2 and not directory) or len(prefixes) != 1: raise ValueError("sdist must have one enclosing directory") - name = "/".join(parts[1:]) - if name in files: + parts = parts[1:] + name = "/".join(parts) + if name in kinds: raise ValueError(f"duplicate archive member: {name}") - files[name] = data + if any(kinds.get("/".join(parts[:index])) is False for index in range(1, len(parts))): + raise ValueError(f"file/directory archive collision: {name}") + if not directory and any(existing.startswith(name + "/") for existing in kinds): + raise ValueError(f"file/directory archive collision: {name}") + kinds[name] = directory + if not directory: + files[name] = data if wheel: with zipfile.ZipFile(path) as archive: for member in archive.infolist(): - if not member.is_dir(): - if (member.external_attr >> 16) & 0o170000 == 0o120000: - raise ValueError(f"archive symlink: {member.filename}") - record(member.filename, archive.read(member)) + if (member.external_attr >> 16) & 0o170000 == 0o120000: + raise ValueError(f"archive symlink: {member.filename}") + record(member.filename, None if member.is_dir() else archive.read(member)) else: with tarfile.open(path, "r:gz") as archive: for member in archive: if member.isdir(): + record(member.name, None) continue if not member.isfile(): raise ValueError(f"non-regular archive member: {member.name}") @@ -367,4 +376,4 @@ def main() -> int: if __name__ == "__main__": raise SystemExit(main()) -# ratios: loc_comments=301:34 imports_exports=17:4 calls_definitions=155:11 +# ratios: loc_comments=310:34 imports_exports=17:4 calls_definitions=159:11 diff --git a/tools/verify_skill_lib_contracts.py b/tools/verify_skill_lib_contracts.py index 40ab61e..87d63be 100644 --- a/tools/verify_skill_lib_contracts.py +++ b/tools/verify_skill_lib_contracts.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=212:47 imports_exports=8:4 calls_definitions=92:8 +# ratios: loc_comments=303:47 imports_exports=8:4 calls_definitions=135:12 # === MODULE_BUILD === # id: skill_lib_contract_audit # module_name: verify_skill_lib_contracts @@ -85,6 +85,7 @@ } REQUIRED_CONTRACT_FIELDS = {"given", "then"} REQUIRED_CHECK_FIELDS = {"proves", "call", "timeout", "mutates", "cleanup"} +UNKNOWN_TEST_SETTING = object() @dataclass(frozen=True) @@ -140,15 +141,96 @@ def parse_blocks(path: Path) -> List[Entry]: return entries +def _target_names(target: ast.AST) -> list[str]: + if isinstance(target, ast.Name): + return [target.id] + if isinstance(target, (ast.Tuple, ast.List)): + return [name for item in target.elts for name in _target_names(item)] + return [] + + +def _bindings(body: list[ast.stmt]) -> dict[str, str]: + """Track direct namespace bindings; callable aliases remain unsupported.""" + bindings = {} + for node in body: + if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)): + bindings[node.name] = "class" if isinstance(node, ast.ClassDef) else "function" + elif isinstance(node, (ast.Assign, ast.AnnAssign)) and node.value is not None: + targets = node.targets if isinstance(node, ast.Assign) else [node.target] + kind = "literal" if isinstance(node.value, (ast.Constant, ast.List, ast.Tuple, ast.Set, ast.Dict)) else "unknown" + for target in targets: + for name in _target_names(target): + bindings[name] = kind + elif isinstance(node, (ast.Import, ast.ImportFrom)): + for alias in node.names: + bindings[alias.asname or alias.name.split(".", 1)[0]] = "literal" if isinstance(node, ast.Import) else "unknown" + elif isinstance(node, ast.AugAssign): + for name in _target_names(node.target): + bindings[name] = "unknown" + elif isinstance(node, ast.Delete): + for target in node.targets: + for name in _target_names(target): + bindings.pop(name, None) + return bindings + + +def _class_mro(name: str, classes: dict[str, ast.ClassDef], active=()) -> list[str] | None: + """Compute local C3 order; unresolved bases and inconsistent orders are gaps.""" + if name == "object": + return [name] + if name not in classes or name in active: + return None + cls = classes[name] + if any(not isinstance(base, ast.Name) for base in cls.bases): + return None + bases = [base.id for base in cls.bases] or ["object"] + if len(set(bases)) != len(bases): + return None + parents = [_class_mro(base, classes, (*active, name)) for base in bases] + if any(parent is None for parent in parents): + return None + sequences = [list(parent) for parent in parents] + [list(bases)] + result = [name] + while any(sequences): + candidates = [sequence[0] for sequence in sequences if sequence] + candidate = next((head for head in candidates if all(head not in sequence[1:] for sequence in sequences)), None) + if candidate is None: + return None + result.append(candidate) + for sequence in sequences: + if sequence and sequence[0] == candidate: + sequence.pop(0) + return result + + +def _test_setting(cls: ast.ClassDef | ast.Module) -> tuple[bool, object]: + found, value = False, None + for node in cls.body: + if isinstance(node, (ast.Assign, ast.AnnAssign)) and node.value is not None: + targets = node.targets if isinstance(node, ast.Assign) else [node.target] + if any("__test__" in _target_names(target) for target in targets): + found = True + try: + value = ast.literal_eval(node.value) + except (ValueError, TypeError, SyntaxError): + value = UNKNOWN_TEST_SETTING + elif isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)) and node.name == "__test__": + found, value = True, UNKNOWN_TEST_SETTING + elif isinstance(node, ast.AugAssign) and "__test__" in _target_names(node.target): + found, value = True, UNKNOWN_TEST_SETTING + elif isinstance(node, ast.Delete) and any("__test__" in _target_names(target) for target in node.targets): + found, value = False, None + return found, value + + def _defined_functions(path: Path) -> Set[str]: """Return top-level functions without importing or executing the module.""" tree = ast.parse(path.read_text(encoding="utf-8"), filename=str(path)) - return { - node.name - for node in tree.body - if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)) - } + found, setting = _test_setting(tree) + if found and not setting: + return set() + return {name for name, kind in _bindings(tree.body).items() if kind == "function"} def _missing(fields: Dict[str, str], required: Set[str]) -> Set[str]: @@ -243,35 +325,52 @@ def audit_repository(root: Path) -> Tuple[bool, List[str]]: for entry in checks if entry.source == test_path and entry.fields.get("call", "").startswith("self::") } - for function in (node.name for node in tree.body if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef))): - if function.startswith("test_") and function not in declared_calls: - problems.append(f"GAP executable check {test_path}::{function} has no resolving CHECKS declaration") - for cls in (node for node in tree.body if isinstance(node, ast.ClassDef)): - # Match default pytest class collection without importing tests. - # Helpers/nested classes and explicitly disabled classes are not checks. - disabled = any( - ((isinstance(node, ast.Assign) - and any(isinstance(target, ast.Name) and target.id == "__test__" for target in node.targets)) - or (isinstance(node, ast.AnnAssign) - and isinstance(node.target, ast.Name) and node.target.id == "__test__")) - and isinstance(node.value, ast.Constant) and node.value.value is False - for node in cls.body - ) - if not cls.name.startswith("Test") or disabled: + found, setting = _test_setting(tree) + if found and not setting: + continue + if setting is UNKNOWN_TEST_SETTING: + problems.append(f"GAP dynamic test-module opt-out {test_path}") + bindings = _bindings(tree.body) + for name, kind in bindings.items(): + if name == "*": + problems.append(f"GAP unresolved wildcard test-module import {test_path}") + elif name.startswith("test_"): + if kind == "unknown": + problems.append(f"GAP unresolved executable alias {test_path}::{name}") + elif kind == "function" and name not in declared_calls: + problems.append(f"GAP executable check {test_path}::{name} has no resolving CHECKS declaration") + elif name.startswith("Test") and kind == "unknown": + problems.append(f"GAP unresolved class alias {test_path}::{name}") + classes = {node.name: node for node in tree.body if isinstance(node, ast.ClassDef) and bindings.get(node.name) == "class"} + for cls in classes.values(): + found, setting = _test_setting(cls) + order = _class_mro(cls.name, classes) + if not found and order is not None: + for ancestor in order[1:]: + if ancestor in classes: + found, setting = _test_setting(classes[ancestor]) + if found: + break + if found and not setting: continue - if any(isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)) and node.name in {"__init__", "__new__"} for node in cls.body): + if setting is UNKNOWN_TEST_SETTING: + problems.append(f"GAP dynamic class opt-out {test_path}::{cls.name}") continue - if any(not (isinstance(base, ast.Name) and base.id == "object") for base in cls.bases): - problems.append( - f"GAP inherited class check {test_path}::{cls.name}; " - "base-class executable surfaces require a declared top-level witness" - ) - for method in cls.body: - if isinstance(method, (ast.FunctionDef, ast.AsyncFunctionDef)) and method.name.startswith("test_"): - problems.append( - f"GAP unsupported class check {test_path}::{cls.name}::{method.name}; " - "use a declared top-level self::test_fn witness" - ) + if not cls.name.startswith("Test") and setting is not True: + continue + if order is None: + problems.append(f"GAP inherited class check {test_path}::{cls.name}; unresolved base surface") + continue + inherited = [classes[name] for name in order if name in classes] + if any(isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)) and node.name in {"__init__", "__new__"} for ancestor in inherited for node in ancestor.body): + continue + methods = {} + for ancestor in reversed(inherited): + methods.update(_bindings(ancestor.body)) + for name, kind in methods.items(): + if name.startswith("test_") and kind in {"function", "unknown"}: + label = "inherited class check" if cls.bases else "unsupported class check" + problems.append(f"GAP {label} {test_path}::{cls.name}::{name}; use a declared top-level self::test_fn witness") return not problems, problems @@ -290,4 +389,4 @@ def main(argv: Sequence[str] | None = None) -> int: if __name__ == "__main__": raise SystemExit(main()) -# ratios: loc_comments=212:47 imports_exports=8:4 calls_definitions=92:8 +# ratios: loc_comments=303:47 imports_exports=8:4 calls_definitions=135:12 From dd9566f92d7107965d96770aec2a2e9a7c60d7d3 Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Sat, 12 Sep 2026 03:29:42 +0000 Subject: [PATCH 27/65] Bind maintenance report to reviewed implementation identity --- docs/work-graphs/repository-plan-report.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/work-graphs/repository-plan-report.json b/docs/work-graphs/repository-plan-report.json index 2c1913d..24a7578 100644 --- a/docs/work-graphs/repository-plan-report.json +++ b/docs/work-graphs/repository-plan-report.json @@ -10,7 +10,7 @@ }, "source": { "branch": "repair/ucns-audit-20260911", - "commit": "db804f4c04f81ddc3d9348ac66300134ba55318d", + "commit": "d59738db2c624a164914351241766faa89a0c3cc", "generated_at": "2026-09-12", "note": "Describes the exact implementation commit preceding this report-only refresh. Maintenance PR #226 is in final exact-head review; merge remains a separate acceptance observation. No scientific or deployment authority is transferred." }, @@ -64,12 +64,12 @@ }, { "surface": "Contract audit and selected-check receipt schema 2.1", - "status": "implemented fail-closed declaration audit, actual pytest outcome classification, import-origin verification, per-check snapshots, and Linux source-write observation", + "status": "implemented fail-closed declaration bindings, actual pytest outcomes, bound descendant imports and lifetime, per-check snapshots, and Linux source-write observation", "boundary": "skips, empty evidence, and source mutation do not pass; selected checks do not establish whole-repository, theorem, freshness, or canon standing" }, { "surface": "Source distribution replay inputs and artifact gate", - "status": "implemented source manifest, archive-byte verification, and locked verification-dependency CI path", + "status": "implemented exact wheel/sdist metadata and directory validation with pinned build backend and source replay inputs", "boundary": "archive presence and matching bytes are packaging evidence; archived certificates are not thereby recomputed" } ], From 30600fb0642afe28917835dec362afaebca3e682 Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Sat, 12 Sep 2026 04:08:52 +0000 Subject: [PATCH 28/65] Supervise check lifetimes and reconcile the canonical parser contract --- .agents/skills/README.md | 22 ++--- .agents/skills/msdmd/SKILL.md | 7 ++ .agents/skills/msdmd/parsers/universal.py | 27 ++++++- .github/workflows/ci.yml | 4 +- README.md | 9 ++- tests/test_distributions.py | 11 +-- tests/test_skill_lib_boundary_runner.py | 6 +- tests/test_skill_lib_contracts.py | 24 +++++- tools/_boundary_pytest.py | 53 ++---------- tools/_boundary_site/sitecustomize.py | 7 +- tools/_boundary_supervisor.py | 99 +++++++++++++++++++++++ tools/run_skill_lib_boundaries.py | 34 +++----- tools/verify_distributions.py | 13 +-- tools/verify_skill_lib_contracts.py | 14 +++- 14 files changed, 221 insertions(+), 109 deletions(-) create mode 100644 tools/_boundary_supervisor.py diff --git a/.agents/skills/README.md b/.agents/skills/README.md index 9dab385..a284405 100644 --- a/.agents/skills/README.md +++ b/.agents/skills/README.md @@ -3,25 +3,19 @@ This directory contains repo-local copies of canonical skills from `The-Interdependency/skill-lib`. -Source commit: `8de4f12d0f31ff94f41e4a0196c447c0cbe20faf` +Source commit: `0f0346ad619bd891b80cc9875617532f8b26d8a0` Repo-local copies are not the source of truth. Edit `skill-lib` first, then propagate from the canonical source. Skills refreshed from the source commit above: +- `canon/` +- `domain-claims/` - `gonol-build/` +- `interdependent-work-graph/` +- `meta-module-build/` - `msdmd/` - -Other installed skills (not refreshed by this propagation): - -Their existing files retain their own authority/provenance; the source -commit above does not assert that these copies were refreshed. - -- `canon/` [not refreshed; prior source: `c14ee9d500579a4b5d6821f62c9d82ca96e73608`] -- `domain-claims/` [not refreshed; prior source: `c14ee9d500579a4b5d6821f62c9d82ca96e73608`] -- `interdependent-work-graph/` [not refreshed; prior source: `c14ee9d500579a4b5d6821f62c9d82ca96e73608`] -- `meta-module-build/` [not refreshed; prior source: `c14ee9d500579a4b5d6821f62c9d82ca96e73608`] -- `test-build/` [not refreshed; prior source: `c14ee9d500579a4b5d6821f62c9d82ca96e73608`] -- `the-interdependency/` [not refreshed; prior source: `c14ee9d500579a4b5d6821f62c9d82ca96e73608`] -- `ucns-option-selection/` [not refreshed; prior source: `c14ee9d500579a4b5d6821f62c9d82ca96e73608`] +- `test-build/` +- `the-interdependency/` +- `ucns-option-selection/` diff --git a/.agents/skills/msdmd/SKILL.md b/.agents/skills/msdmd/SKILL.md index 1a6f0be..a891cc1 100644 --- a/.agents/skills/msdmd/SKILL.md +++ b/.agents/skills/msdmd/SKILL.md @@ -146,6 +146,13 @@ A reference implementation in pure stdlib Python lives at Both commit to zero non-stdlib dependencies so you can copy them into any project. +The Python reference helper owns its `MODULE_BUILD` and field-preservation +`CONTRACTS` beside the implementation. A consumer that executes this vendored +helper in its contract audit should reconcile those exact dependency declarations +and provide a local resolving `CHECKS` witness. The dependency's canonical owner +and exact source identity remain explicit; local execution does not transfer +parser ownership to the consumer. + Extension detection refuses ambiguous suffixes rather than sniffing content. For example, `.m` can mean Objective-C or MATLAB/Octave and therefore has no automatic marker. A caller that already knows the language may still call diff --git a/.agents/skills/msdmd/parsers/universal.py b/.agents/skills/msdmd/parsers/universal.py index e6cb88a..5475a7f 100644 --- a/.agents/skills/msdmd/parsers/universal.py +++ b/.agents/skills/msdmd/parsers/universal.py @@ -1,4 +1,27 @@ -# ratios: loc_comments=161:57 imports_exports=4:7 calls_definitions=55:10 +# ratios: loc_comments=161:80 imports_exports=4:7 calls_definitions=55:10 +# === MODULE_BUILD === +# id: msdmd_python_reference_parser +# module_name: universal +# module_kind: instrument +# summary: parses canonical line-comment metadata without executing inspected source +# owner: The Interdependency skill-lib +# public_surface: COMMENT_MARKERS, marker_for, parse_text, parse_file, walk_tree, parse_ratios, parse_ratios_file, ratios_placement +# internal_surface: marker and block matching helpers +# auth_boundary: none +# storage_boundary: read +# network_boundary: none +# user_data_boundary: read +# admin_only: false +# tests: tests/test_universal_parser.py +# rollout: exact-pinned reference parser propagation +# rollback: restore a previously accepted exact parser identity +# === END MODULE_BUILD === +# === CONTRACTS === +# id: msdmd_python_parser_preserves_field_names +# given: a valid metadata entry uses lowercase snake-case field names containing digits +# then: parsed entries retain those field names and string values without executing the inspected source +# class: evidence +# === END CONTRACTS === """Universal msdmd parser — pure stdlib. Implements the parser contract from ``msdmd/SKILL.md``: extracts every @@ -253,4 +276,4 @@ def ratios_placement(text: str, marker: str = "#") -> tuple[bool, bool]: last_ok = bool(line_re.match(raw.rstrip())) break return (opening_ok, last_ok) -# ratios: loc_comments=161:57 imports_exports=4:7 calls_definitions=55:10 \ No newline at end of file +# ratios: loc_comments=161:80 imports_exports=4:7 calls_definitions=55:10 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6ce354b..df0423e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,7 +21,7 @@ jobs: uses: actions/checkout@v7.0.1 with: repository: The-Interdependency/skill-lib - ref: 8de4f12d0f31ff94f41e4a0196c447c0cbe20faf + ref: 0f0346ad619bd891b80cc9875617532f8b26d8a0 path: .skill-lib-canon sparse-checkout: | msdmd @@ -53,7 +53,7 @@ jobs: run: >- python .skill-lib-canon/tools/check_consumer_drift.py . --canon-root .skill-lib-canon - --sha 8de4f12d0f31ff94f41e4a0196c447c0cbe20faf + --sha 0f0346ad619bd891b80cc9875617532f8b26d8a0 --strict-sha --require-vendored - name: Audit skill-lib contract graph diff --git a/README.md b/README.md index 1251d1a..efc30bc 100644 --- a/README.md +++ b/README.md @@ -172,6 +172,10 @@ Exact modular and trace records require immutable tuples with non-Boolean intege residues; prefer the public builders. MPFR rational constructors accept only integers or `Fraction`, and NaN cannot participate in interval ordering. +The no-exec graph also reconciles the exact vendored reference parser that it +executes, including a local numeric-field witness. Parser ownership remains in +skill-lib. Unused skill helper tools remain canonical dependency material. + For a source-bound, selected-check receipt: ```bash @@ -187,9 +191,10 @@ Receipt output must be outside the bound source tree and is written by atomic replacement so an output hardlink cannot modify a bound input. Checks import the bound checkout with ambient pytest plugins disabled and PYTHONPATH replaced. An inherited startup hook makes ordinary Python descendants -prefer bound packages over their working directory. The bootstrap uses Linux +prefer bound packages over their working directory. A separate supervisor uses Linux child subreaping to terminate remaining descendants before observation ends; -leaked background work prevents acceptance. Origin receipts observe the selected +leaked background work prevents acceptance. Timeout cleanup is outside the +pytest process, so replacing its signal handler cannot bypass descendant cleanup. Origin receipts observe the selected pytest process. Explicit isolated/no-site interpreters and replaced child environments do not inherit the import protocol. The observer instruments trusted checks; it is not a sandbox for hostile test code. A `passed` receipt covers only its selected diff --git a/tests/test_distributions.py b/tests/test_distributions.py index cd95148..41124aa 100644 --- a/tests/test_distributions.py +++ b/tests/test_distributions.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=120:10 imports_exports=9:1 calls_definitions=47:2 +# ratios: loc_comments=121:10 imports_exports=9:1 calls_definitions=47:2 # === CHECKS === # id: check_distribution_replay_inputs # proves: distributions_retain_exact_replay_inputs @@ -29,7 +29,7 @@ SPEC.loader.exec_module(audit) -def _archives(root, sdist, wheel, *, omit="", altered="", extra="", sdist_extra="", wheel_omit="", wheel_altered="", metadata_extra="", wheel_flags="true", record_mode="", sdist_directory="", wheel_directory=""): +def _archives(root, sdist, wheel, *, omit="", altered="", extra="", sdist_extra="", wheel_omit="", wheel_altered="", metadata_extra="", wheel_flags="true", record_mode="", sdist_directory="", wheel_directory="", sdist_root="ucns-0"): core_metadata = "Metadata-Version: 2.4\nName: ucns\nVersion: 0\nSummary: Fixture\nAuthor: Test\nRequires-Python: >=3.10\nDescription-Content-Type: text/markdown\nLicense: fixture\nLicense-File: LICENSE\nDynamic: license-file\n" with tarfile.open(sdist, "w:gz") as archive: files = {**audit.expected_files(root), "setup.cfg": audit.GENERATED_SETUP_CFG, @@ -47,11 +47,11 @@ def _archives(root, sdist, wheel, *, omit="", altered="", extra="", sdist_extra= continue if name == altered: data += b"drift" - member = tarfile.TarInfo(f"ucns-0/{name}") + member = tarfile.TarInfo(f"{sdist_root}/{name}") member.size = len(data) archive.addfile(member, io.BytesIO(data)) if sdist_directory: - member = tarfile.TarInfo(f"ucns-0/{sdist_directory}") + member = tarfile.TarInfo(f"{sdist_root}/{sdist_directory}") member.type = tarfile.DIRTYPE archive.addfile(member) with zipfile.ZipFile(wheel, "w") as archive: @@ -117,6 +117,7 @@ def test_distribution_replay_inputs_fail_closed(tmp_path: Path) -> None: ({"wheel_flags": "false"}, "Root-Is-Purelib differs"), ({"record_mode": "empty"}, "RECORD missing path"), ({"record_mode": "wrong-hash"}, "RECORD digest or size mismatch"), + ({"sdist_root": "other-99"}, "sdist root identity"), ({"sdist_directory": "pyproject.toml"}, "duplicate archive member"), ({"wheel_directory": "ucns/__init__.py"}, "duplicate archive member"), ({"sdist_directory": "../escaped"}, "unsafe archive name"), @@ -138,4 +139,4 @@ def test_distribution_replay_inputs_fail_closed(tmp_path: Path) -> None: with zipfile.ZipFile(wheel, "a") as archive, pytest.warns(UserWarning, match="Duplicate"): archive.writestr("ucns/__init__.py", b"duplicate") assert any("duplicate" in problem for problem in audit.verify_distributions(root, sdist, wheel)) -# ratios: loc_comments=120:10 imports_exports=9:1 calls_definitions=47:2 +# ratios: loc_comments=121:10 imports_exports=9:1 calls_definitions=47:2 diff --git a/tests/test_skill_lib_boundary_runner.py b/tests/test_skill_lib_boundary_runner.py index ebe9616..e35bb40 100644 --- a/tests/test_skill_lib_boundary_runner.py +++ b/tests/test_skill_lib_boundary_runner.py @@ -192,7 +192,7 @@ def test_fail(): def test_error(): raise RuntimeError("AssertionError mentioned by a broken harness") class ContractViolation(AssertionError): - pass + __test__ = False def test_subclass(): raise ContractViolation("broken") """ @@ -367,7 +367,7 @@ def test_check_imports_bound_source_despite_ambient_pythonpath(tmp_path: Path, m # === CHECKS === # id: check_boundary_background_descendants -# proves: boundary_runner_receipt_is_bounded_and_bound, boundary_pytest_observes_actual_outcomes +# proves: boundary_runner_receipt_is_bounded_and_bound, boundary_pytest_observes_actual_outcomes, boundary_supervisor_ends_descendants # call: self::test_background_descendants_block_acceptance # requires: python3, pytest # timeout: 30 @@ -391,7 +391,7 @@ def test_background_descendants_block_acceptance(tmp_path: Path) -> None: with pytest.raises(ProcessLookupError): os.kill(pid, 0) assert (root / "src/pkg/feature.py").read_bytes() == before - body += " import time; time.sleep(30)\n" + body += " import signal, time; signal.signal(signal.SIGTERM, signal.SIG_IGN); time.sleep(30)\n" timeout_root = _repo(tmp_path / "timeout-child", body, [{"id": "check_probe", "function": "test_probe", "timeout": "5"}]) receipt = runner.run_boundaries(timeout_root) assert receipt["outcomes"][0]["status"] == "TIMEOUT" diff --git a/tests/test_skill_lib_contracts.py b/tests/test_skill_lib_contracts.py index 863d97c..257c7a1 100644 --- a/tests/test_skill_lib_contracts.py +++ b/tests/test_skill_lib_contracts.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=113:42 imports_exports=5:5 calls_definitions=65:5 +# ratios: loc_comments=122:51 imports_exports=6:6 calls_definitions=72:6 # === CHECKS === # id: check_contract_audit_no_exec # proves: contract_audit_is_no_exec @@ -159,6 +159,8 @@ def test_nested_fences_cannot_hide_an_obligation(tmp_path: Path) -> None: assert not ok and any("TestChild::test_hidden" in item for item in problems) for code, label in ( ("def hidden(): assert False\ntest_aliased = hidden\n", "unresolved executable alias"), + ("import unittest\nclass HiddenName(unittest.TestCase):\n def test_hidden(self): assert False\n", "HiddenName"), + ("from unittest import TestCase as Case\nclass HiddenName(Case):\n def test_hidden(self): assert False\n", "HiddenName"), ("class Helper:\n __test__ = True\n def test_explicit(self): pass\n", "Helper::test_explicit"), ("class TestAlias:\n def hidden(self): pass\n test_aliased = hidden\n", "TestAlias::test_aliased"), ): @@ -171,4 +173,22 @@ def test_nested_fences_cannot_hide_an_obligation(tmp_path: Path) -> None: inherited.write_text("__test__ = False\ndef test_disabled(): assert False\n") ok, problems = audit_repository(root) assert ok, problems -# ratios: loc_comments=113:42 imports_exports=5:5 calls_definitions=65:5 +# === CHECKS === +# id: check_vendored_msdmd_field_preservation +# proves: msdmd_python_parser_preserves_field_names +# call: self::test_vendored_parser_retains_numeric_field_names +# requires: python3 +# timeout: 10 +# mutates: none +# cleanup: none +# === END CHECKS === + + +def test_vendored_parser_retains_numeric_field_names() -> None: + from tools.verify_skill_lib_contracts import _PARSER, PARSER_PATH, parse_blocks + declarations = parse_blocks(PARSER_PATH) + assert any(entry.block == "MODULE_BUILD" and entry.id == "msdmd_python_reference_parser" for entry in declarations) + assert any(entry.block == "CONTRACTS" and entry.id == "msdmd_python_parser_preserves_field_names" for entry in declarations) + text = "# === NARRATIVE ===\n# id: sample\n# evidence_sha256: abc123\n# === END NARRATIVE ===\nraise RuntimeError('not executable input')\n" + assert _PARSER.parse_text(text, "NARRATIVE") == [{"id": "sample", "evidence_sha256": "abc123"}] +# ratios: loc_comments=122:51 imports_exports=6:6 calls_definitions=72:6 diff --git a/tools/_boundary_pytest.py b/tools/_boundary_pytest.py index db5ee55..1ea5586 100644 --- a/tools/_boundary_pytest.py +++ b/tools/_boundary_pytest.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=101:28 imports_exports=8:2 calls_definitions=39:7 +# ratios: loc_comments=66:28 imports_exports=5:2 calls_definitions=25:4 # === MODULE_BUILD === # id: boundary_pytest_observer # module_name: _boundary_pytest @@ -8,7 +8,8 @@ # public_surface: none; launched by run_skill_lib_boundaries # internal_surface: Observer, main # auth_boundary: none -# storage_boundary: writes caller-selected machine outcome report +# storage_boundary: write +# storage_notes: writes caller-selected machine outcome report # network_boundary: none # user_data_boundary: none # admin_only: false @@ -19,7 +20,7 @@ # === CONTRACTS === # id: boundary_pytest_observes_actual_outcomes # given: a selected pytest boundary runs through this bootstrap -# then: assertion subclasses fail, unexpected exceptions error, XPASS cannot pass, imported local package origins must match the bound tree, and leaked descendants prevent acceptance +# then: assertion subclasses fail, unexpected exceptions error, XPASS cannot pass, imported local package origins must match the bound tree # class: evidence # === END CONTRACTS === @@ -31,10 +32,7 @@ from __future__ import annotations import json -import ctypes -import os from pathlib import Path -import signal import sys import pytest @@ -68,52 +66,15 @@ def pytest_runtest_makereport(self, item, call): self.other.append(status) -def _enable_descendant_reaping() -> None: - libc = ctypes.CDLL(None, use_errno=True) - if libc.prctl(36, 1, 0, 0, 0) != 0: # Linux PR_SET_CHILD_SUBREAPER. - raise OSError(ctypes.get_errno(), "cannot bind check descendant lifetime") - - -def _reap_descendants() -> int: - """Terminate and reap only children owned/adopted by this bootstrap.""" - observed = 0 - children_path = Path(f"/proc/self/task/{os.getpid()}/children") - while True: - children = [int(pid) for pid in children_path.read_text().split()] - if not children: - return observed - observed += len(children) - for pid in children: - try: - os.kill(pid, signal.SIGKILL) - except ProcessLookupError: - pass - for pid in children: - try: - os.waitpid(pid, 0) - except ChildProcessError: - pass - - def main() -> int: root = Path(sys.argv[1]).resolve() report_path = Path(sys.argv[2]) sys.path[:0] = [str(root / "src"), str(root)] observer = Observer(root) - _enable_descendant_reaping() - def terminate_check(signum, frame): - _reap_descendants() - raise SystemExit(124) - signal.signal(signal.SIGTERM, terminate_check) hook = sys.modules.get("sitecustomize") if getattr(hook, "BOUND_ROOT", None) != str(root) or getattr(hook, "FINDER", None) not in sys.meta_path: raise RuntimeError("bound descendant import hook is unavailable") - try: - exit_code = int(pytest.main(sys.argv[3:], plugins=[observer])) - finally: - descendants = _reap_descendants() - if descendants: - observer.other.append("ERROR") + exit_code = int(pytest.main(sys.argv[3:], plugins=[observer])) source_root = root / "src" local_names = {p.stem if p.is_file() else p.name for p in source_root.iterdir()} if source_root.exists() else set() origins = {} @@ -137,10 +98,10 @@ def terminate_check(signum, frame): status = "PASS" else: status = "ERROR" - report_path.write_text(json.dumps({"status": status, "calls": observer.calls, "other": observer.other, "origins": origins, "wrong_origins": wrong_origins, "descendants_reaped": descendants}), encoding="utf-8") + report_path.write_text(json.dumps({"status": status, "calls": observer.calls, "other": observer.other, "origins": origins, "wrong_origins": wrong_origins, "descendants_reaped": 0}), encoding="utf-8") return exit_code if __name__ == "__main__": raise SystemExit(main()) -# ratios: loc_comments=101:28 imports_exports=8:2 calls_definitions=39:7 +# ratios: loc_comments=66:28 imports_exports=5:2 calls_definitions=25:4 diff --git a/tools/_boundary_site/sitecustomize.py b/tools/_boundary_site/sitecustomize.py index 6a8d7b7..9e1ac6a 100644 --- a/tools/_boundary_site/sitecustomize.py +++ b/tools/_boundary_site/sitecustomize.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=28:27 imports_exports=5:1 calls_definitions=13:3 +# ratios: loc_comments=28:28 imports_exports=5:1 calls_definitions=13:3 # === MODULE_BUILD === # id: boundary_descendant_import_binding # module_name: sitecustomize @@ -8,7 +8,8 @@ # public_surface: none; installed on the boundary runner's sanitized PYTHONPATH # internal_surface: BoundSourceFinder # auth_boundary: none -# storage_boundary: reads source package locations +# storage_boundary: read +# storage_notes: reads source package locations # network_boundary: none # user_data_boundary: none # admin_only: false @@ -63,4 +64,4 @@ def find_spec(self, fullname, path=None, target=None): if BOUND_ROOT: FINDER = BoundSourceFinder(Path(BOUND_ROOT).resolve()) sys.meta_path.insert(0, FINDER) -# ratios: loc_comments=28:27 imports_exports=5:1 calls_definitions=13:3 +# ratios: loc_comments=28:28 imports_exports=5:1 calls_definitions=13:3 diff --git a/tools/_boundary_supervisor.py b/tools/_boundary_supervisor.py new file mode 100644 index 0000000..b35966c --- /dev/null +++ b/tools/_boundary_supervisor.py @@ -0,0 +1,99 @@ +# ratios: loc_comments=57:30 imports_exports=8:1 calls_definitions=23:3 +# === MODULE_BUILD === +# id: boundary_process_supervisor +# module_name: _boundary_supervisor +# module_kind: instrument +# summary: owns check descendant lifetime outside the pytest process and its signal handlers +# owner: Erin Spencer +# public_surface: none; launched by run_skill_lib_boundaries +# internal_surface: main, child subreaping +# auth_boundary: none +# storage_boundary: write +# storage_notes: updates machine outcome report after child cleanup +# network_boundary: none +# user_data_boundary: none +# admin_only: false +# tests: tests/test_skill_lib_boundary_runner.py +# rollout: process supervisor for every selected check +# rollback: remove with boundary-runner integration +# === END MODULE_BUILD === +# === CONTRACTS === +# id: boundary_supervisor_ends_descendants +# given: a selected check exits or exceeds its timeout, including after replacing its signal handlers +# then: a separate Linux subreaper kills and reaps remaining check descendants before accepting or returning its outcome +# class: evidence +# === END CONTRACTS === +"""Internal usage: python _boundary_supervisor.py TIMEOUT BOOTSTRAP ROOT REPORT PYTEST_ARGS. + +Only the child runs pytest or test code. Descendant adoption therefore survives +child signal-handler changes, crashes, and detached sessions. This is trusted +check instrumentation, not containment against hostile code targeting ancestors. +""" +from __future__ import annotations + +import ctypes +import json +import os +from pathlib import Path +import signal +import subprocess +import sys + + +def _enable_descendant_reaping() -> None: + libc = ctypes.CDLL(None, use_errno=True) + if libc.prctl(36, 1, 0, 0, 0) != 0: # Linux PR_SET_CHILD_SUBREAPER. + raise OSError(ctypes.get_errno(), "cannot bind check descendant lifetime") + + +def _reap_descendants() -> int: + """Terminate and reap only children owned/adopted by this bootstrap.""" + observed = 0 + children_path = Path(f"/proc/self/task/{os.getpid()}/children") + while True: + children = [int(pid) for pid in children_path.read_text().split()] + if not children: + return observed + observed += len(children) + for pid in children: + try: + os.kill(pid, signal.SIGKILL) + except ProcessLookupError: + pass + for pid in children: + try: + os.waitpid(pid, 0) + except ChildProcessError: + pass + + +def main() -> int: + timeout = int(sys.argv[1]) + report = Path(sys.argv[4]) + _enable_descendant_reaping() + process = subprocess.Popen([sys.executable, *sys.argv[2:]], start_new_session=True) + timed_out = False + try: + returncode = process.wait(timeout=timeout) + except subprocess.TimeoutExpired: + timed_out = True + process.kill() + returncode = process.wait() + descendants = _reap_descendants() + try: + observed = json.loads(report.read_text()) + if not isinstance(observed, dict): + raise ValueError("invalid bootstrap report") + except (OSError, ValueError): + observed = {"status": "ERROR", "calls": [], "other": ["ERROR"], "origins": {}, "wrong_origins": []} + if timed_out or descendants: + observed["status"] = "ERROR" + observed.setdefault("other", []).append("ERROR") + observed.update(descendants_reaped=descendants, timed_out=timed_out) + report.write_text(json.dumps(observed), encoding="utf-8") + return 124 if timed_out else returncode + + +if __name__ == "__main__": + raise SystemExit(main()) +# ratios: loc_comments=57:30 imports_exports=8:1 calls_definitions=23:3 diff --git a/tools/run_skill_lib_boundaries.py b/tools/run_skill_lib_boundaries.py index 9050451..00d033d 100644 --- a/tools/run_skill_lib_boundaries.py +++ b/tools/run_skill_lib_boundaries.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=385:67 imports_exports=19:4 calls_definitions=168:18 +# ratios: loc_comments=373:71 imports_exports=19:4 calls_definitions=165:18 # === MODULE_BUILD === # id: skill_lib_boundary_runner # module_name: run_skill_lib_boundaries @@ -8,9 +8,11 @@ # public_surface: command-line boundary runner, run_boundaries, write_receipt # internal_surface: capability resolution, subprocess classification, receipt hashing # auth_boundary: none -# storage_boundary: optional caller-selected JSON receipt path +# storage_boundary: write +# storage_notes: optional caller-selected JSON receipt path # network_boundary: none -# user_data_boundary: captured test output is bounded and retained only in the caller-selected receipt +# user_data_boundary: write +# user_data_notes: captured test output is bounded and retained only in the caller-selected receipt # admin_only: false # tests: tests/test_skill_lib_boundary_runner.py # rollout: explicit local and CI evidence runner; no product, EDCM, or canon activation @@ -96,6 +98,7 @@ SOURCE_DIRECTORIES = ("src", "tools", "tests", "docs", "generated", ".agents/skills", ".github/workflows") ROOT_INPUTS = ("pyproject.toml", "uv.lock", "pytest.ini", "setup.cfg", "MANIFEST.in", "conftest.py", "CANON.md", "AGENTS.md", "README.md", "CLAUDE.md", "LICENSE") BOOTSTRAP = TOOLS_DIRECTORY / "_boundary_pytest.py" +SUPERVISOR = TOOLS_DIRECTORY / "_boundary_supervisor.py" STARTUP_DIRECTORY = TOOLS_DIRECTORY / "_boundary_site" @@ -343,6 +346,7 @@ def _run_check(root: Path, check: Entry) -> CheckOutcome: report_path = Path(temporary) / "outcomes.json" command = (sys.executable, str(BOOTSTRAP), str(root), str(report_path), "-q", f"{relative_source}::{function}", f"--junitxml={junit_path}", "-o", "xfail_strict=true") + command = (sys.executable, str(SUPERVISOR), str(timeout), *command[1:]) environment = dict(os.environ) for name in ("PYTHONPATH", "PYTHONHOME", "PYTEST_ADDOPTS", "PYTEST_PLUGINS"): environment.pop(name, None) @@ -356,28 +360,15 @@ def _run_check(root: Path, check: Entry) -> CheckOutcome: command, cwd=root, stdin=subprocess.DEVNULL, stdout=stdout, stderr=stderr, start_new_session=True, env=environment, ) - timed_out = False - try: - returncode = process.wait(timeout=timeout) - except subprocess.TimeoutExpired: - timed_out = True - try: - process.terminate() # Bootstrap reaps detached descendants too. - returncode = process.wait(timeout=3) - except subprocess.TimeoutExpired: - try: - os.killpg(process.pid, signal.SIGKILL) - except ProcessLookupError: - pass - returncode = process.wait() - except ProcessLookupError: - returncode = process.wait() + # The separate supervisor owns the timeout and descendant reaping; + # pytest signal handlers never run in that process. + returncode = process.wait() finally: source_events = watcher.finish() stdout_sha, stdout_bytes, stdout_excerpt = _excerpt(stdout_path) stderr_sha, stderr_bytes, stderr_excerpt = _excerpt(stderr_path) status, observed = _pytest_outcome(report_path, returncode) - if timed_out: + if observed.get("timed_out"): status = "TIMEOUT" duration = round(time.monotonic() - started, 6) @@ -452,6 +443,7 @@ def run_boundaries( "source_after_sha256": source_after, "source_unchanged": unchanged, "bootstrap_sha256": _sha(BOOTSTRAP.read_bytes()), + "supervisor_sha256": _sha(SUPERVISOR.read_bytes()), "startup_hook_sha256": _sha((STARTUP_DIRECTORY / "sitecustomize.py").read_bytes()), "python_version": sys.version, "selected_check_ids": [outcome.check_id for outcome in outcomes], @@ -499,4 +491,4 @@ def main(argv: Sequence[str] | None = None) -> int: if __name__ == "__main__": raise SystemExit(main()) -# ratios: loc_comments=385:67 imports_exports=19:4 calls_definitions=168:18 +# ratios: loc_comments=373:71 imports_exports=19:4 calls_definitions=165:18 diff --git a/tools/verify_distributions.py b/tools/verify_distributions.py index 07142e7..50187a7 100644 --- a/tools/verify_distributions.py +++ b/tools/verify_distributions.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=310:34 imports_exports=17:4 calls_definitions=159:11 +# ratios: loc_comments=312:35 imports_exports=17:4 calls_definitions=160:11 # === MODULE_BUILD === # id: ucns_distribution_audit # module_name: verify_distributions @@ -8,7 +8,8 @@ # public_surface: verify_distributions, command-line audit # internal_surface: expected_files, read_archive # auth_boundary: none -# storage_boundary: read-only repository and archives +# storage_boundary: read +# storage_notes: read-only repository and archives # network_boundary: none # user_data_boundary: none # admin_only: false @@ -95,7 +96,7 @@ def expected_files(root: Path) -> dict[str, bytes]: return {path.relative_to(root).as_posix(): path.read_bytes() for path in sorted(paths)} -def read_archive(path: Path, *, wheel: bool) -> dict[str, bytes]: +def read_archive(path: Path, *, wheel: bool, expected_prefix: str | None = None) -> dict[str, bytes]: """Read regular files only; reject ambiguous names rather than extracting.""" files: dict[str, bytes] = {} prefixes: set[str] = set() @@ -107,6 +108,8 @@ def record(name: str, data: bytes | None) -> None: if not parts or name.startswith("/") or ".." in parts or "\\" in name or "\0" in name: raise ValueError(f"unsafe archive name: {name}") if not wheel: + if expected_prefix is not None and parts[0] != expected_prefix: + raise ValueError("sdist root identity differs from project/filename") prefixes.add(parts[0]) if (len(parts) < 2 and not directory) or len(prefixes) != 1: raise ValueError("sdist must have one enclosing directory") @@ -328,7 +331,7 @@ def verify_distributions(root: Path, sdist: Path, wheel: Path) -> list[str]: problems.append(f"invalid distribution filename: {error}") for path, inputs, is_wheel in ((sdist, expected, False), (wheel, wheel_expected, True)): try: - actual = read_archive(path, wheel=is_wheel) + actual = read_archive(path, wheel=is_wheel, expected_prefix=None if is_wheel else sdist.name.removesuffix(".tar.gz")) except (OSError, ValueError, tarfile.TarError, zipfile.BadZipFile) as error: problems.append(f"{path.name}: {error}") continue @@ -376,4 +379,4 @@ def main() -> int: if __name__ == "__main__": raise SystemExit(main()) -# ratios: loc_comments=310:34 imports_exports=17:4 calls_definitions=159:11 +# ratios: loc_comments=312:35 imports_exports=17:4 calls_definitions=160:11 diff --git a/tools/verify_skill_lib_contracts.py b/tools/verify_skill_lib_contracts.py index 87d63be..c8fb54a 100644 --- a/tools/verify_skill_lib_contracts.py +++ b/tools/verify_skill_lib_contracts.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=303:47 imports_exports=8:4 calls_definitions=135:12 +# ratios: loc_comments=306:50 imports_exports=8:4 calls_definitions=136:12 # === MODULE_BUILD === # id: skill_lib_contract_audit # module_name: verify_skill_lib_contracts @@ -100,6 +100,10 @@ def id(self) -> str: def _source_files(root: Path) -> Iterable[Path]: + # This vendored helper executes as part of the audit instrument itself. + parser = root / ".agents/skills/msdmd/parsers/universal.py" + if parser.is_file(): + yield parser for base in (root / "src", root / "tools", root / "tests"): if base.exists(): yield from (path for path in sorted(base.rglob("*.py")) if "__pycache__" not in path.parts) @@ -356,11 +360,13 @@ def audit_repository(root: Path) -> Tuple[bool, List[str]]: if setting is UNKNOWN_TEST_SETTING: problems.append(f"GAP dynamic class opt-out {test_path}::{cls.name}") continue - if not cls.name.startswith("Test") and setting is not True: - continue if order is None: + # unittest.TestCase collection does not require a Test prefix. + # Unknown external bases can carry executable tests under any name. problems.append(f"GAP inherited class check {test_path}::{cls.name}; unresolved base surface") continue + if not cls.name.startswith("Test") and setting is not True: + continue inherited = [classes[name] for name in order if name in classes] if any(isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)) and node.name in {"__init__", "__new__"} for ancestor in inherited for node in ancestor.body): continue @@ -389,4 +395,4 @@ def main(argv: Sequence[str] | None = None) -> int: if __name__ == "__main__": raise SystemExit(main()) -# ratios: loc_comments=303:47 imports_exports=8:4 calls_definitions=135:12 +# ratios: loc_comments=306:50 imports_exports=8:4 calls_definitions=136:12 From 7aab3219847063480b614efeed48beccbed3b42a Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Sat, 12 Sep 2026 04:08:52 +0000 Subject: [PATCH 29/65] Refresh the maintenance implementation and dependency identities --- docs/work-graphs/repository-plan-report.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/work-graphs/repository-plan-report.json b/docs/work-graphs/repository-plan-report.json index 24a7578..0904313 100644 --- a/docs/work-graphs/repository-plan-report.json +++ b/docs/work-graphs/repository-plan-report.json @@ -10,7 +10,7 @@ }, "source": { "branch": "repair/ucns-audit-20260911", - "commit": "d59738db2c624a164914351241766faa89a0c3cc", + "commit": "30600fb0642afe28917835dec362afaebca3e682", "generated_at": "2026-09-12", "note": "Describes the exact implementation commit preceding this report-only refresh. Maintenance PR #226 is in final exact-head review; merge remains a separate acceptance observation. No scientific or deployment authority is transferred." }, @@ -84,7 +84,7 @@ { "action": "Complete exact-head review and Python 3.10/3.12 CI for UCNS PR #226; merge after all live findings and gates close", "owner": "The-Interdependency/ucns maintainers", - "dependency": "Canonical skill-lib 8de4f12d0f31ff94f41e4a0196c447c0cbe20faf is published; nine installed skills pass strict drift verification" + "dependency": "Exact canonical skill-lib 0f0346ad619bd891b80cc9875617532f8b26d8a0 binds the declared parser; nine installed skills pass strict drift verification" }, { "action": "Correct stale stack research claims about PR #220 and reconcile its exact research base before consuming results", From ef3816f0513c24ce12e6c20cd5f15b788e16155f Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Sat, 12 Sep 2026 04:17:35 +0000 Subject: [PATCH 30/65] Pin the complete canonical parser public declaration --- .agents/skills/README.md | 2 +- .agents/skills/msdmd/parsers/universal.py | 2 +- .github/workflows/ci.yml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.agents/skills/README.md b/.agents/skills/README.md index a284405..ef1a960 100644 --- a/.agents/skills/README.md +++ b/.agents/skills/README.md @@ -3,7 +3,7 @@ This directory contains repo-local copies of canonical skills from `The-Interdependency/skill-lib`. -Source commit: `0f0346ad619bd891b80cc9875617532f8b26d8a0` +Source commit: `05c52aa0999ce102d9cb369434da214e91c5f421` Repo-local copies are not the source of truth. Edit `skill-lib` first, then propagate from the canonical source. diff --git a/.agents/skills/msdmd/parsers/universal.py b/.agents/skills/msdmd/parsers/universal.py index 5475a7f..e25feee 100644 --- a/.agents/skills/msdmd/parsers/universal.py +++ b/.agents/skills/msdmd/parsers/universal.py @@ -5,7 +5,7 @@ # module_kind: instrument # summary: parses canonical line-comment metadata without executing inspected source # owner: The Interdependency skill-lib -# public_surface: COMMENT_MARKERS, marker_for, parse_text, parse_file, walk_tree, parse_ratios, parse_ratios_file, ratios_placement +# public_surface: COMMENT_MARKERS, RATIO_IDS, marker_for, parse_text, parse_file, walk_tree, parse_ratios, parse_ratios_file, ratios_placement # internal_surface: marker and block matching helpers # auth_boundary: none # storage_boundary: read diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index df0423e..e52a894 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,7 +21,7 @@ jobs: uses: actions/checkout@v7.0.1 with: repository: The-Interdependency/skill-lib - ref: 0f0346ad619bd891b80cc9875617532f8b26d8a0 + ref: 05c52aa0999ce102d9cb369434da214e91c5f421 path: .skill-lib-canon sparse-checkout: | msdmd @@ -53,7 +53,7 @@ jobs: run: >- python .skill-lib-canon/tools/check_consumer_drift.py . --canon-root .skill-lib-canon - --sha 0f0346ad619bd891b80cc9875617532f8b26d8a0 + --sha 05c52aa0999ce102d9cb369434da214e91c5f421 --strict-sha --require-vendored - name: Audit skill-lib contract graph From 275e266cc21994618f0da424e5c5f8d365c23ddf Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Sat, 12 Sep 2026 04:17:35 +0000 Subject: [PATCH 31/65] Bind the report to the accepted parser declaration candidate --- docs/work-graphs/repository-plan-report.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/work-graphs/repository-plan-report.json b/docs/work-graphs/repository-plan-report.json index 0904313..db83131 100644 --- a/docs/work-graphs/repository-plan-report.json +++ b/docs/work-graphs/repository-plan-report.json @@ -10,7 +10,7 @@ }, "source": { "branch": "repair/ucns-audit-20260911", - "commit": "30600fb0642afe28917835dec362afaebca3e682", + "commit": "ef3816f0513c24ce12e6c20cd5f15b788e16155f", "generated_at": "2026-09-12", "note": "Describes the exact implementation commit preceding this report-only refresh. Maintenance PR #226 is in final exact-head review; merge remains a separate acceptance observation. No scientific or deployment authority is transferred." }, @@ -84,7 +84,7 @@ { "action": "Complete exact-head review and Python 3.10/3.12 CI for UCNS PR #226; merge after all live findings and gates close", "owner": "The-Interdependency/ucns maintainers", - "dependency": "Exact canonical skill-lib 0f0346ad619bd891b80cc9875617532f8b26d8a0 binds the declared parser; nine installed skills pass strict drift verification" + "dependency": "Exact canonical skill-lib 05c52aa0999ce102d9cb369434da214e91c5f421 binds the declared parser; nine installed skills pass strict drift verification" }, { "action": "Correct stale stack research claims about PR #220 and reconcile its exact research base before consuming results", From 6b232e908ea83a6c97c27e6f4f797bb6f63ee776 Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Sat, 12 Sep 2026 04:30:59 +0000 Subject: [PATCH 32/65] Reconcile and execute both canonical parser implementations --- .agents/skills/README.md | 2 +- .agents/skills/msdmd/SKILL.md | 8 ++--- .agents/skills/msdmd/parsers/universal.ts | 23 ++++++++++++++ .github/workflows/ci.yml | 7 +++-- README.md | 8 +++-- tests/test_skill_lib_contracts.py | 37 +++++++++++++++++++++-- tools/verify_skill_lib_contracts.py | 27 +++++++++++------ 7 files changed, 90 insertions(+), 22 deletions(-) diff --git a/.agents/skills/README.md b/.agents/skills/README.md index ef1a960..a623f8f 100644 --- a/.agents/skills/README.md +++ b/.agents/skills/README.md @@ -3,7 +3,7 @@ This directory contains repo-local copies of canonical skills from `The-Interdependency/skill-lib`. -Source commit: `05c52aa0999ce102d9cb369434da214e91c5f421` +Source commit: `5e7239b52dc5403d8d01fbc63341fe397719ec3f` Repo-local copies are not the source of truth. Edit `skill-lib` first, then propagate from the canonical source. diff --git a/.agents/skills/msdmd/SKILL.md b/.agents/skills/msdmd/SKILL.md index a891cc1..9a6a036 100644 --- a/.agents/skills/msdmd/SKILL.md +++ b/.agents/skills/msdmd/SKILL.md @@ -146,10 +146,10 @@ A reference implementation in pure stdlib Python lives at Both commit to zero non-stdlib dependencies so you can copy them into any project. -The Python reference helper owns its `MODULE_BUILD` and field-preservation -`CONTRACTS` beside the implementation. A consumer that executes this vendored -helper in its contract audit should reconcile those exact dependency declarations -and provide a local resolving `CHECKS` witness. The dependency's canonical owner +The Python and TypeScript reference helpers each own `MODULE_BUILD` and +field-preservation `CONTRACTS` beside their implementations. A consumer that +executes or updates a vendored helper should reconcile its exact dependency +declarations and provide a local resolving `CHECKS` witness. The dependency's canonical owner and exact source identity remain explicit; local execution does not transfer parser ownership to the consumer. diff --git a/.agents/skills/msdmd/parsers/universal.ts b/.agents/skills/msdmd/parsers/universal.ts index 67b7b21..6b4dc72 100644 --- a/.agents/skills/msdmd/parsers/universal.ts +++ b/.agents/skills/msdmd/parsers/universal.ts @@ -1,4 +1,27 @@ // ratios: loc_comments=hmmm imports_exports=hmmm calls_definitions=hmmm +// === MODULE_BUILD === +// id: msdmd_typescript_reference_parser +// module_name: universal +// module_kind: instrument +// summary: parses canonical line-comment metadata without executing inspected source +// owner: The Interdependency skill-lib +// public_surface: Entry, WalkOptions, COMMENT_MARKERS, RATIO_IDS, markerFor, parseText, parseFile, walkTree, parseRatios, parseRatiosFile, ratiosPlacement +// internal_surface: marker and block matching helpers +// auth_boundary: none +// storage_boundary: read +// network_boundary: none +// user_data_boundary: read +// admin_only: false +// tests: tests/test_universal_parser.py::test_typescript_parser_field_contract +// rollout: exact-pinned reference parser propagation +// rollback: restore a previously accepted exact parser identity +// === END MODULE_BUILD === +// === CONTRACTS === +// id: msdmd_typescript_parser_preserves_field_names +// given: a valid metadata entry uses lowercase snake-case field names containing digits +// then: parsed entries retain those field names and string values without executing the inspected source +// class: evidence +// === END CONTRACTS === /** * Universal msdmd parser — pure Node stdlib (fs, path). * diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e52a894..a9cca2c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,7 +21,7 @@ jobs: uses: actions/checkout@v7.0.1 with: repository: The-Interdependency/skill-lib - ref: 05c52aa0999ce102d9cb369434da214e91c5f421 + ref: 5e7239b52dc5403d8d01fbc63341fe397719ec3f path: .skill-lib-canon sparse-checkout: | msdmd @@ -40,6 +40,9 @@ jobs: - uses: actions/setup-python@v7.0.0 with: python-version: ${{ matrix.python-version }} + - uses: actions/setup-node@v6 + with: + node-version: "24.15.0" - name: Install verification prerequisites run: | sudo apt-get update @@ -53,7 +56,7 @@ jobs: run: >- python .skill-lib-canon/tools/check_consumer_drift.py . --canon-root .skill-lib-canon - --sha 05c52aa0999ce102d9cb369434da214e91c5f421 + --sha 5e7239b52dc5403d8d01fbc63341fe397719ec3f --strict-sha --require-vendored - name: Audit skill-lib contract graph diff --git a/README.md b/README.md index efc30bc..ca156b8 100644 --- a/README.md +++ b/README.md @@ -172,9 +172,11 @@ Exact modular and trace records require immutable tuples with non-Boolean intege residues; prefer the public builders. MPFR rational constructors accept only integers or `Fraction`, and NaN cannot participate in interval ordering. -The no-exec graph also reconciles the exact vendored reference parser that it -executes, including a local numeric-field witness. Parser ownership remains in -skill-lib. Unused skill helper tools remain canonical dependency material. +The no-exec graph also reconciles both exact vendored reference parsers, with +local Python and TypeScript numeric-field/no-execution witnesses. The complete +repository evidence suite requires Node 24.15.0 for that TypeScript check; UCNS +library use remains Python-only. Parser ownership remains in skill-lib. +Unused skill helper tools remain canonical dependency material. For a source-bound, selected-check receipt: diff --git a/tests/test_skill_lib_contracts.py b/tests/test_skill_lib_contracts.py index 257c7a1..b065339 100644 --- a/tests/test_skill_lib_contracts.py +++ b/tests/test_skill_lib_contracts.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=122:51 imports_exports=6:6 calls_definitions=72:6 +# ratios: loc_comments=144:60 imports_exports=9:7 calls_definitions=84:7 # === CHECKS === # id: check_contract_audit_no_exec # proves: contract_audit_is_no_exec @@ -191,4 +191,37 @@ def test_vendored_parser_retains_numeric_field_names() -> None: assert any(entry.block == "CONTRACTS" and entry.id == "msdmd_python_parser_preserves_field_names" for entry in declarations) text = "# === NARRATIVE ===\n# id: sample\n# evidence_sha256: abc123\n# === END NARRATIVE ===\nraise RuntimeError('not executable input')\n" assert _PARSER.parse_text(text, "NARRATIVE") == [{"id": "sample", "evidence_sha256": "abc123"}] -# ratios: loc_comments=122:51 imports_exports=6:6 calls_definitions=72:6 +# === CHECKS === +# id: check_vendored_typescript_field_preservation +# proves: msdmd_typescript_parser_preserves_field_names, contract_audit_reports_graph_gaps +# call: self::test_vendored_typescript_parser_retains_numeric_field_names +# requires: python3, node24 +# timeout: 10 +# mutates: filesystem +# cleanup: tempdir_teardown +# === END CHECKS === + + +def test_vendored_typescript_parser_retains_numeric_field_names(tmp_path: Path) -> None: + import json + import subprocess + from tools.verify_skill_lib_contracts import parse_blocks + helper = ROOT / ".agents/skills/msdmd/parsers/universal.ts" + declarations = parse_blocks(helper) + assert any(entry.block == "MODULE_BUILD" and entry.id == "msdmd_typescript_reference_parser" for entry in declarations) + assert any(entry.block == "CONTRACTS" and entry.id == "msdmd_typescript_parser_preserves_field_names" for entry in declarations) + marker = tmp_path / "executed" + source = tmp_path / "inspected.ts" + text = "// === NARRATIVE ===\n// id: sample\n// evidence_sha256: abc123\n// === END NARRATIVE ===\n" + source.write_text(text + 'import {writeFileSync} from "node:fs";\n' + f'writeFileSync({json.dumps(str(marker))}, "executed");\n') + script = f"import {{parseText, parseFile}} from {json.dumps(helper.as_uri())};" + f"process.stdout.write(JSON.stringify([parseText({json.dumps(text)}, 'NARRATIVE', '//'),parseFile({json.dumps(str(source))}, 'NARRATIVE')]));" + result = subprocess.run(["node", "--input-type=module", "--eval", script], check=True, capture_output=True, text=True) + expected = [{"id": "sample", "evidence_sha256": "abc123"}] + assert json.loads(result.stdout) == [expected, expected] + assert not marker.exists(), "TypeScript parsing executed inspected source" + broken = tmp_path / "repo/.agents/skills/msdmd/parsers/universal.ts" + broken.parent.mkdir(parents=True) + broken.write_text("// no declaration\nthrow new Error('must not execute');\n") + ok, problems = audit_repository(tmp_path / "repo") + assert not ok and any("universal.ts missing MODULE_BUILD" in item for item in problems), problems +# ratios: loc_comments=144:60 imports_exports=9:7 calls_definitions=84:7 diff --git a/tools/verify_skill_lib_contracts.py b/tools/verify_skill_lib_contracts.py index c8fb54a..13910ce 100644 --- a/tools/verify_skill_lib_contracts.py +++ b/tools/verify_skill_lib_contracts.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=306:50 imports_exports=8:4 calls_definitions=136:12 +# ratios: loc_comments=313:50 imports_exports=8:4 calls_definitions=140:12 # === MODULE_BUILD === # id: skill_lib_contract_audit # module_name: verify_skill_lib_contracts @@ -100,10 +100,11 @@ def id(self) -> str: def _source_files(root: Path) -> Iterable[Path]: - # This vendored helper executes as part of the audit instrument itself. - parser = root / ".agents/skills/msdmd/parsers/universal.py" - if parser.is_file(): - yield parser + # Reconcile both updated canonical parser implementations beside their owner. + for name in ("universal.py", "universal.ts"): + parser = root / ".agents/skills/msdmd/parsers" / name + if parser.is_file(): + yield parser for base in (root / "src", root / "tools", root / "tests"): if base.exists(): yield from (path for path in sorted(base.rglob("*.py")) if "__pycache__" not in path.parts) @@ -112,9 +113,13 @@ def _source_files(root: Path) -> Iterable[Path]: def parse_blocks(path: Path) -> List[Entry]: """Check fence integrity, then delegate entry grammar to canonical msdmd.""" text = path.read_text(encoding="utf-8") + marker = _PARSER.marker_for(path) + if marker not in {"#", "//"}: + raise ValueError(f"unsupported declaration source: {path}") active: str | None = None declarations = 0 - for raw in text.splitlines(): + for line in text.splitlines(): + raw = "#" + line[len(marker):] if line.startswith(marker) else line start = BLOCK_RE.match(raw) if start: if active is not None: @@ -138,7 +143,7 @@ def parse_blocks(path: Path) -> List[Entry]: entries = [ Entry(block, path, fields) for block in ("MODULE_BUILD", "CONTRACTS", "CHECKS") - for fields in _PARSER.parse_text(text, block) + for fields in _PARSER.parse_text(text, block, marker=marker) ] if len(entries) != declarations: raise ValueError(f"{path}: declarations lost by canonical parser") @@ -251,14 +256,16 @@ def audit_repository(root: Path) -> Tuple[bool, List[str]]: problems.append(f"GAP empty source/test tree: {root}") for path in paths: try: - trees[path] = ast.parse(path.read_text(encoding="utf-8"), filename=str(path)) + if path.suffix == ".py": + trees[path] = ast.parse(path.read_text(encoding="utf-8"), filename=str(path)) path_entries = parse_blocks(path) except (OSError, UnicodeError, SyntaxError, ValueError) as exc: problems.append(f"GAP parse {type(exc).__name__}: {exc}") continue entries.extend(path_entries) - if path.is_relative_to(root / "src") or path.is_relative_to(root / "tools"): + if (path.is_relative_to(root / "src") or path.is_relative_to(root / "tools") + or path.is_relative_to(root / ".agents/skills/msdmd/parsers")): declared = {entry.block for entry in path_entries} for required_block in ("MODULE_BUILD", "CONTRACTS"): if required_block not in declared: @@ -395,4 +402,4 @@ def main(argv: Sequence[str] | None = None) -> int: if __name__ == "__main__": raise SystemExit(main()) -# ratios: loc_comments=306:50 imports_exports=8:4 calls_definitions=136:12 +# ratios: loc_comments=313:50 imports_exports=8:4 calls_definitions=140:12 From 033c2e8d3223cdf3c5218ffe876729baa0d9f027 Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Sat, 12 Sep 2026 04:30:59 +0000 Subject: [PATCH 33/65] Refresh exact parser and implementation provenance --- docs/work-graphs/repository-plan-report.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/work-graphs/repository-plan-report.json b/docs/work-graphs/repository-plan-report.json index db83131..f458395 100644 --- a/docs/work-graphs/repository-plan-report.json +++ b/docs/work-graphs/repository-plan-report.json @@ -10,7 +10,7 @@ }, "source": { "branch": "repair/ucns-audit-20260911", - "commit": "ef3816f0513c24ce12e6c20cd5f15b788e16155f", + "commit": "6b232e908ea83a6c97c27e6f4f797bb6f63ee776", "generated_at": "2026-09-12", "note": "Describes the exact implementation commit preceding this report-only refresh. Maintenance PR #226 is in final exact-head review; merge remains a separate acceptance observation. No scientific or deployment authority is transferred." }, @@ -84,7 +84,7 @@ { "action": "Complete exact-head review and Python 3.10/3.12 CI for UCNS PR #226; merge after all live findings and gates close", "owner": "The-Interdependency/ucns maintainers", - "dependency": "Exact canonical skill-lib 05c52aa0999ce102d9cb369434da214e91c5f421 binds the declared parser; nine installed skills pass strict drift verification" + "dependency": "Exact canonical skill-lib 5e7239b52dc5403d8d01fbc63341fe397719ec3f binds the declared parser; nine installed skills pass strict drift verification" }, { "action": "Correct stale stack research claims about PR #220 and reconcile its exact research base before consuming results", From e408e1781ff143f772a02cd72d407deaca00cde4 Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Sat, 12 Sep 2026 04:45:20 +0000 Subject: [PATCH 34/65] Resolve Node 24 capability through its versioned node executable --- tests/test_skill_lib_boundary_runner.py | 27 +++++++++++++++++++++++-- tools/run_skill_lib_boundaries.py | 14 +++++++++++-- 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/tests/test_skill_lib_boundary_runner.py b/tests/test_skill_lib_boundary_runner.py index e35bb40..d2c3b17 100644 --- a/tests/test_skill_lib_boundary_runner.py +++ b/tests/test_skill_lib_boundary_runner.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=98:266 imports_exports=10:17 calls_definitions=103:19 +# ratios: loc_comments=98:287 imports_exports=12:18 calls_definitions=110:20 # === CHECKS === # id: check_boundary_runner_audit_gate # proves: boundary_runner_audits_before_execution @@ -398,4 +398,27 @@ def test_background_descendants_block_acceptance(tmp_path: Path) -> None: pid = int((timeout_root / "child.pid").read_text()) with pytest.raises(ProcessLookupError): os.kill(pid, 0) -# ratios: loc_comments=98:266 imports_exports=10:17 calls_definitions=103:19 +# === CHECKS === +# id: check_node24_capability_runs_typescript_witness +# proves: boundary_runner_consumes_capabilities_and_timeouts +# call: self::test_node24_capability_runs_typescript_witness +# requires: python3, node24 +# timeout: 20 +# mutates: filesystem +# cleanup: tempdir_teardown +# === END CHECKS === + + +def test_node24_capability_runs_typescript_witness() -> None: + from unittest.mock import patch + from subprocess import CompletedProcess + for version, expected in (("v24.15.0\n", True), ("v22.23.2\n", False), ("not-a-version", False)): + with patch.object(runner.shutil, "which", return_value="/fake/node"): + with patch.object(runner.subprocess, "run", return_value=CompletedProcess([], 0, version, "")): + assert runner._capability_available("node24") is expected + with patch.object(runner.shutil, "which", return_value=None): + assert not runner._capability_available("node24") + receipt = runner.run_boundaries(RUNNER_PATH.resolve().parents[1], selected_ids=("check_vendored_typescript_field_preservation",)) + assert receipt["status"] == "passed", receipt + assert len(receipt["outcomes"]) == 1 and receipt["outcomes"][0]["status"] == "PASS", receipt +# ratios: loc_comments=98:287 imports_exports=12:18 calls_definitions=110:20 diff --git a/tools/run_skill_lib_boundaries.py b/tools/run_skill_lib_boundaries.py index 00d033d..ba7579f 100644 --- a/tools/run_skill_lib_boundaries.py +++ b/tools/run_skill_lib_boundaries.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=373:71 imports_exports=19:4 calls_definitions=165:18 +# ratios: loc_comments=383:71 imports_exports=20:4 calls_definitions=168:18 # === MODULE_BUILD === # id: skill_lib_boundary_runner # module_name: run_skill_lib_boundaries @@ -73,6 +73,7 @@ import importlib.util import json import os +import re from pathlib import Path import shutil import signal @@ -212,6 +213,15 @@ def _split(value: str) -> tuple[str, ...]: def _capability_available(name: str) -> bool: if name == "python3": return True + if name == "node24": + executable = shutil.which("node") + if executable is None: + return False + try: + result = subprocess.run([executable, "--version"], capture_output=True, text=True, timeout=5) + except (OSError, UnicodeError, subprocess.TimeoutExpired): + return False + return result.returncode == 0 and re.fullmatch(r"v24\.\d+\.\d+", result.stdout.strip()) is not None if name == "posix_shell": return os.name == "posix" and shutil.which("sh") is not None if name == "posix_resource": @@ -491,4 +501,4 @@ def main(argv: Sequence[str] | None = None) -> int: if __name__ == "__main__": raise SystemExit(main()) -# ratios: loc_comments=373:71 imports_exports=19:4 calls_definitions=165:18 +# ratios: loc_comments=383:71 imports_exports=20:4 calls_definitions=168:18 From 6239d630d370ad36deba9d112f314da366d1a089 Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Sat, 12 Sep 2026 04:45:20 +0000 Subject: [PATCH 35/65] Bind maintenance report to the capability repair --- docs/work-graphs/repository-plan-report.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/work-graphs/repository-plan-report.json b/docs/work-graphs/repository-plan-report.json index f458395..0714200 100644 --- a/docs/work-graphs/repository-plan-report.json +++ b/docs/work-graphs/repository-plan-report.json @@ -10,7 +10,7 @@ }, "source": { "branch": "repair/ucns-audit-20260911", - "commit": "6b232e908ea83a6c97c27e6f4f797bb6f63ee776", + "commit": "e408e1781ff143f772a02cd72d407deaca00cde4", "generated_at": "2026-09-12", "note": "Describes the exact implementation commit preceding this report-only refresh. Maintenance PR #226 is in final exact-head review; merge remains a separate acceptance observation. No scientific or deployment authority is transferred." }, From a805763e7dbf2b20be492a476182a1497b05f0af Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Sat, 12 Sep 2026 05:10:17 +0000 Subject: [PATCH 36/65] Close conditional-test and artifact receipt boundary gaps --- README.md | 6 ++- tests/test_distributions.py | 5 ++- tests/test_skill_lib_boundary_runner.py | 31 ++++++++++++++- tests/test_skill_lib_contracts.py | 30 ++++++++++++++- tools/_boundary_supervisor.py | 27 +++++++++++-- tools/run_skill_lib_boundaries.py | 15 ++++++-- tools/verify_distributions.py | 19 ++++++---- tools/verify_skill_lib_contracts.py | 50 +++++++++++++++++++++++-- 8 files changed, 156 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index ca156b8..28711eb 100644 --- a/README.md +++ b/README.md @@ -188,7 +188,11 @@ For a source-bound, selected-check receipt: Receipt schema 2.1 rejects skips, expected failures, both forms of XPASS, absent reports, and source changes during execution (including write-and-restore through external hardlinks). -Receipt execution requires Linux inotify; an unavailable observer is an error. +Receipt execution requires Linux inotify and readable procfs process identities; +unavailable instrumentation is an error before check execution. Descendant discovery +uses the task-children interface when available and otherwise reads procfs parent IDs. +Potential test definitions inside module or class control flow fail the declaration +audit closed; executable witnesses use direct module-level definitions. Receipt output must be outside the bound source tree and is written by atomic replacement so an output hardlink cannot modify a bound input. Checks import the bound checkout with ambient pytest plugins disabled and diff --git a/tests/test_distributions.py b/tests/test_distributions.py index 41124aa..02f6d44 100644 --- a/tests/test_distributions.py +++ b/tests/test_distributions.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=121:10 imports_exports=9:1 calls_definitions=47:2 +# ratios: loc_comments=122:10 imports_exports=9:1 calls_definitions=47:2 # === CHECKS === # id: check_distribution_replay_inputs # proves: distributions_retain_exact_replay_inputs @@ -97,6 +97,7 @@ def test_distribution_replay_inputs_fail_closed(tmp_path: Path) -> None: _archives(root, sdist, wheel) assert audit.verify_distributions(root, sdist, wheel) == [] for options, message in ( + ({"wheel_directory": "evil-1.dist-info"}, "exactly one .dist-info directory"), ({"omit": "LICENSE"}, "missing LICENSE"), ({"altered": "LICENSE"}, "altered LICENSE"), ({"wheel_omit": "licenses/LICENSE"}, "missing wheel metadata"), @@ -139,4 +140,4 @@ def test_distribution_replay_inputs_fail_closed(tmp_path: Path) -> None: with zipfile.ZipFile(wheel, "a") as archive, pytest.warns(UserWarning, match="Duplicate"): archive.writestr("ucns/__init__.py", b"duplicate") assert any("duplicate" in problem for problem in audit.verify_distributions(root, sdist, wheel)) -# ratios: loc_comments=121:10 imports_exports=9:1 calls_definitions=47:2 +# ratios: loc_comments=122:10 imports_exports=9:1 calls_definitions=47:2 diff --git a/tests/test_skill_lib_boundary_runner.py b/tests/test_skill_lib_boundary_runner.py index d2c3b17..588f07a 100644 --- a/tests/test_skill_lib_boundary_runner.py +++ b/tests/test_skill_lib_boundary_runner.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=98:287 imports_exports=12:18 calls_definitions=110:20 +# ratios: loc_comments=98:314 imports_exports=13:18 calls_definitions=131:20 # === CHECKS === # id: check_boundary_runner_audit_gate # proves: boundary_runner_audits_before_execution @@ -326,6 +326,20 @@ def test_source_mutation_prevents_acceptance(tmp_path: Path) -> None: runner.write_receipt(receipt, external) assert source.read_bytes() == original assert json.loads(external.read_text())["receipt_sha256"] == receipt["receipt_sha256"] + internal_link = root / "receipt-link.json" + internal_link.symlink_to(external) + external_before = external.read_bytes() + with pytest.raises(ValueError, match="outside the bound source tree"): + runner.write_receipt(receipt, internal_link) + result = subprocess.run([sys.executable, str(RUNNER_PATH), str(root), "--receipt", str(internal_link)], capture_output=True, text=True) + assert result.returncode == 2 + assert "outside the bound source tree" in result.stderr + assert internal_link.is_symlink() and external.read_bytes() == external_before + root_alias = tmp_path / "root-alias" + root_alias.symlink_to(root, target_is_directory=True) + with pytest.raises(ValueError, match="outside the bound source tree"): + runner.write_receipt(receipt, root_alias / internal_link.name) + assert internal_link.is_symlink() and external.read_bytes() == external_before alias = tmp_path / "source-alias.py" body = f"from pathlib import Path\ndef test_probe():\n p=Path({str(alias)!r})\n original=p.read_bytes()\n p.write_bytes(original+b'# transient\\n')\n p.write_bytes(original)\n" root = _repo(tmp_path / "hardlinked", body, [{"id": "check_probe", "function": "test_probe"}]) @@ -379,6 +393,19 @@ def test_check_imports_bound_source_despite_ambient_pythonpath(tmp_path: Path, m def test_background_descendants_block_acceptance(tmp_path: Path) -> None: import os import pytest + from tools._boundary_supervisor import _owned_children + proc = tmp_path / "proc" + proc.mkdir() + for pid, parent in ((101, os.getpid()), (102, 1)): + process = proc / str(pid) + process.mkdir() + (process / "stat").write_text(f"{pid} (name with ) spaces) S {parent} 0 0 0\n") + (proc / "103").mkdir() # Exited between directory enumeration and stat read. + assert _owned_children(proc) == [101] + task = proc / f"self/task/{os.getpid()}" + task.mkdir(parents=True) + (task / "children").write_text("101 104\n") + assert _owned_children(proc) == [101, 104] child = "import time; from pathlib import Path; time.sleep(30); Path('src/pkg/feature.py').write_text('late mutation')" body = f"import subprocess, sys\nfrom pathlib import Path\ndef test_probe():\n p=subprocess.Popen([sys.executable, '-c', {child!r}], start_new_session=True)\n Path('child.pid').write_text(str(p.pid))\n" root = _repo(tmp_path, body, [{"id": "check_probe", "function": "test_probe"}]) @@ -421,4 +448,4 @@ def test_node24_capability_runs_typescript_witness() -> None: receipt = runner.run_boundaries(RUNNER_PATH.resolve().parents[1], selected_ids=("check_vendored_typescript_field_preservation",)) assert receipt["status"] == "passed", receipt assert len(receipt["outcomes"]) == 1 and receipt["outcomes"][0]["status"] == "PASS", receipt -# ratios: loc_comments=98:287 imports_exports=12:18 calls_definitions=110:20 +# ratios: loc_comments=98:314 imports_exports=13:18 calls_definitions=131:20 diff --git a/tests/test_skill_lib_contracts.py b/tests/test_skill_lib_contracts.py index b065339..c268e7b 100644 --- a/tests/test_skill_lib_contracts.py +++ b/tests/test_skill_lib_contracts.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=144:60 imports_exports=9:7 calls_definitions=84:7 +# ratios: loc_comments=170:60 imports_exports=9:7 calls_definitions=89:7 # === CHECKS === # id: check_contract_audit_no_exec # proves: contract_audit_is_no_exec @@ -122,6 +122,32 @@ def test_empty_syntax_and_class_coverage_are_not_closed(tmp_path: Path) -> None: (root / "tests/test_class.py").write_text("class TestUnregistered:\n def test_untracked(self):\n assert False\n") ok, problems = audit_repository(root) assert not ok and any("TestUnregistered::test_untracked" in item for item in problems) + branch = root / "tests/test_class.py" + for code in ( + "if True:\n def test_hidden(): assert False\n", + "if False:\n pass\nelse:\n def test_hidden(): assert False\n", + "if UNKNOWN:\n test_hidden = helper\n", + "for item in [1]:\n def test_hidden(): assert False\n", + "while UNKNOWN:\n def test_hidden(): assert False\n", + "try:\n pass\nfinally:\n def test_hidden(): assert False\n", + "with manager():\n def test_hidden(): assert False\n", + "match value:\n case 1:\n def test_hidden(): assert False\n", + "class TestConditional:\n if True:\n def test_hidden(self): assert False\n", + "if True:\n class TestHidden:\n def test_hidden(self): assert False\n", + "__test__ = False\nif True:\n __test__ = True\n def test_hidden(): assert False\n", + "class TestConditional:\n __test__ = False\n if True:\n __test__ = True\n def test_hidden(self): assert False\n", + ): + branch.write_text(code) + ok, problems = audit_repository(root) + assert not ok and any("conditional" in item for item in problems), (code, problems) + for code in ( + "if False:\n def test_inactive(): assert False\n", + "def helper():\n if True:\n def test_local(): assert False\n", + "__test__ = False\nif True:\n def test_disabled(): assert False\n", + ): + branch.write_text(code) + ok, problems = audit_repository(root) + assert ok, (code, problems) def test_nested_fences_cannot_hide_an_obligation(tmp_path: Path) -> None: @@ -224,4 +250,4 @@ def test_vendored_typescript_parser_retains_numeric_field_names(tmp_path: Path) broken.write_text("// no declaration\nthrow new Error('must not execute');\n") ok, problems = audit_repository(tmp_path / "repo") assert not ok and any("universal.ts missing MODULE_BUILD" in item for item in problems), problems -# ratios: loc_comments=144:60 imports_exports=9:7 calls_definitions=84:7 +# ratios: loc_comments=170:60 imports_exports=9:7 calls_definitions=89:7 diff --git a/tools/_boundary_supervisor.py b/tools/_boundary_supervisor.py index b35966c..039162f 100644 --- a/tools/_boundary_supervisor.py +++ b/tools/_boundary_supervisor.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=57:30 imports_exports=8:1 calls_definitions=23:3 +# ratios: loc_comments=73:31 imports_exports=8:1 calls_definitions=30:4 # === MODULE_BUILD === # id: boundary_process_supervisor # module_name: _boundary_supervisor @@ -46,12 +46,30 @@ def _enable_descendant_reaping() -> None: raise OSError(ctypes.get_errno(), "cannot bind check descendant lifetime") +def _owned_children(proc_root: Path = Path("/proc")) -> list[int]: + """Use the optional task list, falling back to ordinary procfs parent IDs.""" + parent = os.getpid() + try: + return [int(pid) for pid in (proc_root / f"self/task/{parent}/children").read_text().split()] + except FileNotFoundError: + children = [] + for process in proc_root.iterdir(): + if not process.name.isdecimal(): + continue + try: + state = (process / "stat").read_text().rpartition(")")[2].split() + if int(state[1]) == parent: + children.append(int(process.name)) + except (FileNotFoundError, ProcessLookupError, PermissionError): + continue # Exited processes or unrelated users hidden by procfs. + return children + + def _reap_descendants() -> int: """Terminate and reap only children owned/adopted by this bootstrap.""" observed = 0 - children_path = Path(f"/proc/self/task/{os.getpid()}/children") while True: - children = [int(pid) for pid in children_path.read_text().split()] + children = _owned_children() if not children: return observed observed += len(children) @@ -71,6 +89,7 @@ def main() -> int: timeout = int(sys.argv[1]) report = Path(sys.argv[4]) _enable_descendant_reaping() + _owned_children() # Verify procfs support before any check code is launched. process = subprocess.Popen([sys.executable, *sys.argv[2:]], start_new_session=True) timed_out = False try: @@ -96,4 +115,4 @@ def main() -> int: if __name__ == "__main__": raise SystemExit(main()) -# ratios: loc_comments=57:30 imports_exports=8:1 calls_definitions=23:3 +# ratios: loc_comments=73:31 imports_exports=8:1 calls_definitions=30:4 diff --git a/tools/run_skill_lib_boundaries.py b/tools/run_skill_lib_boundaries.py index ba7579f..579c819 100644 --- a/tools/run_skill_lib_boundaries.py +++ b/tools/run_skill_lib_boundaries.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=383:71 imports_exports=20:4 calls_definitions=168:18 +# ratios: loc_comments=388:71 imports_exports=20:4 calls_definitions=172:19 # === MODULE_BUILD === # id: skill_lib_boundary_runner # module_name: run_skill_lib_boundaries @@ -470,11 +470,18 @@ def run_boundaries( return receipt +def _receipt_inside_source(path: Path, root: Path) -> bool: + lexical = Path(os.path.abspath(path)) + destination = path.parent.resolve() / path.name + bound = root.resolve() + return any(candidate.is_relative_to(bound) for candidate in (lexical, destination, path.resolve())) + + def write_receipt(receipt: dict[str, object], path: Path) -> None: bound_root = receipt.get("bound_source_root") if not isinstance(bound_root, str) or not bound_root: raise ValueError("receipt must identify its bound source tree") - if path.resolve().is_relative_to(Path(bound_root).resolve()): + if _receipt_inside_source(path, Path(bound_root)): raise ValueError("receipt output must be outside the bound source tree") path.parent.mkdir(parents=True, exist_ok=True) # Replacing the directory entry avoids writing through an external hardlink. @@ -490,7 +497,7 @@ def main(argv: Sequence[str] | None = None) -> int: parser.add_argument("--check", action="append", default=[], dest="checks") parser.add_argument("--receipt", type=Path) args = parser.parse_args(argv) - if args.receipt and args.receipt.resolve().is_relative_to(Path(args.root).resolve()): + if args.receipt and _receipt_inside_source(args.receipt, Path(args.root)): parser.error("receipt output must be outside the bound source tree") receipt = run_boundaries(Path(args.root), selected_ids=args.checks) if args.receipt: @@ -501,4 +508,4 @@ def main(argv: Sequence[str] | None = None) -> int: if __name__ == "__main__": raise SystemExit(main()) -# ratios: loc_comments=383:71 imports_exports=20:4 calls_definitions=168:18 +# ratios: loc_comments=388:71 imports_exports=20:4 calls_definitions=172:19 diff --git a/tools/verify_distributions.py b/tools/verify_distributions.py index 50187a7..cae3dfd 100644 --- a/tools/verify_distributions.py +++ b/tools/verify_distributions.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=312:35 imports_exports=17:4 calls_definitions=160:11 +# ratios: loc_comments=315:35 imports_exports=17:4 calls_definitions=163:11 # === MODULE_BUILD === # id: ucns_distribution_audit # module_name: verify_distributions @@ -96,7 +96,7 @@ def expected_files(root: Path) -> dict[str, bytes]: return {path.relative_to(root).as_posix(): path.read_bytes() for path in sorted(paths)} -def read_archive(path: Path, *, wheel: bool, expected_prefix: str | None = None) -> dict[str, bytes]: +def read_archive(path: Path, *, wheel: bool, expected_prefix: str | None = None, directories: set[str] | None = None) -> dict[str, bytes]: """Read regular files only; reject ambiguous names rather than extracting.""" files: dict[str, bytes] = {} prefixes: set[str] = set() @@ -122,6 +122,8 @@ def record(name: str, data: bytes | None) -> None: if not directory and any(existing.startswith(name + "/") for existing in kinds): raise ValueError(f"file/directory archive collision: {name}") kinds[name] = directory + if directory and directories is not None: + directories.add(name) if not directory: files[name] = data @@ -264,12 +266,12 @@ def _record_problems(actual: dict[str, bytes], record_path: str) -> list[str]: return problems -def _wheel_metadata_problems(actual: dict[str, bytes], expected: dict[str, bytes]) -> list[str]: +def _wheel_metadata_problems(actual: dict[str, bytes], expected: dict[str, bytes], directories: set[str]) -> list[str]: problems: list[str] = [] prefixes = { name.split("/", 1)[0] - for name in actual - if "/" in name and name.split("/", 1)[0].endswith(".dist-info") + for name in actual.keys() | directories + if name.split("/", 1)[0].endswith(".dist-info") } if len(prefixes) != 1: return ["wheel must contain exactly one .dist-info directory"] @@ -331,7 +333,8 @@ def verify_distributions(root: Path, sdist: Path, wheel: Path) -> list[str]: problems.append(f"invalid distribution filename: {error}") for path, inputs, is_wheel in ((sdist, expected, False), (wheel, wheel_expected, True)): try: - actual = read_archive(path, wheel=is_wheel, expected_prefix=None if is_wheel else sdist.name.removesuffix(".tar.gz")) + directories: set[str] = set() + actual = read_archive(path, wheel=is_wheel, expected_prefix=None if is_wheel else sdist.name.removesuffix(".tar.gz"), directories=directories) except (OSError, ValueError, tarfile.TarError, zipfile.BadZipFile) as error: problems.append(f"{path.name}: {error}") continue @@ -341,7 +344,7 @@ def verify_distributions(root: Path, sdist: Path, wheel: Path) -> list[str]: elif actual[name] != data: problems.append(f"{path.name}: altered {name}") if is_wheel: - metadata_problems = _wheel_metadata_problems(actual, expected) + metadata_problems = _wheel_metadata_problems(actual, expected, directories) problems.extend(f"{path.name}: {problem}" for problem in metadata_problems) dist_prefixes = { name.split("/", 1)[0] @@ -379,4 +382,4 @@ def main() -> int: if __name__ == "__main__": raise SystemExit(main()) -# ratios: loc_comments=312:35 imports_exports=17:4 calls_definitions=160:11 +# ratios: loc_comments=315:35 imports_exports=17:4 calls_definitions=163:11 diff --git a/tools/verify_skill_lib_contracts.py b/tools/verify_skill_lib_contracts.py index 13910ce..b79d034 100644 --- a/tools/verify_skill_lib_contracts.py +++ b/tools/verify_skill_lib_contracts.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=313:50 imports_exports=8:4 calls_definitions=140:12 +# ratios: loc_comments=348:51 imports_exports=8:4 calls_definitions=167:14 # === MODULE_BUILD === # id: skill_lib_contract_audit # module_name: verify_skill_lib_contracts @@ -183,6 +183,39 @@ def _bindings(body: list[ast.stmt]) -> dict[str, str]: return bindings +_COMPOUND_STATEMENTS = (ast.If, ast.For, ast.AsyncFor, ast.While, ast.Try, ast.With, ast.AsyncWith, ast.Match, getattr(ast, "TryStar", ast.Try)) + + +def _conditional_test_names(body: list[ast.stmt]) -> set[str]: + """Find possible module/class test bindings without entering function bodies.""" + names: set[str] = set() + for node in body: + if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)): + if node.name.startswith("test_"): + names.add(node.name) + elif isinstance(node, ast.ClassDef): + found, setting = _test_setting(node) + if not (found and setting is False) and (node.name.startswith("Test") or node.bases or setting is True): + names.add(node.name) + elif isinstance(node, ast.If) and isinstance(node.test, ast.Constant): + names.update(_conditional_test_names(node.body if node.test.value else node.orelse)) + elif isinstance(node, _COMPOUND_STATEMENTS): + groups = [getattr(node, name, []) for name in ("body", "orelse", "finalbody")] + groups.extend(handler.body for handler in getattr(node, "handlers", [])) + groups.extend(case.body for case in getattr(node, "cases", [])) + for group in groups: + names.update(_conditional_test_names(group)) + else: + for name, kind in _bindings([node]).items(): + if name in {"*", "__test__"} or name.startswith("test_") and kind != "literal" or name.startswith("Test") and kind == "unknown": + names.add(name) + return names + + +def _conditional_surface(body: list[ast.stmt]) -> set[str]: + return _conditional_test_names([node for node in body if isinstance(node, _COMPOUND_STATEMENTS)]) + + def _class_mro(name: str, classes: dict[str, ast.ClassDef], active=()) -> list[str] | None: """Compute local C3 order; unresolved bases and inconsistent orders are gaps.""" if name == "object": @@ -337,8 +370,11 @@ def audit_repository(root: Path) -> Tuple[bool, List[str]]: if entry.source == test_path and entry.fields.get("call", "").startswith("self::") } found, setting = _test_setting(tree) - if found and not setting: + conditional = _conditional_surface(tree.body) + if found and not setting and "__test__" not in conditional: continue + for name in sorted(conditional): + problems.append(f"GAP conditional test binding {test_path}::{name}; use direct module-level test definitions") if setting is UNKNOWN_TEST_SETTING: problems.append(f"GAP dynamic test-module opt-out {test_path}") bindings = _bindings(tree.body) @@ -356,13 +392,16 @@ def audit_repository(root: Path) -> Tuple[bool, List[str]]: for cls in classes.values(): found, setting = _test_setting(cls) order = _class_mro(cls.name, classes) + conditional_opt_out = any("__test__" in _conditional_surface(classes[name].body) for name in (order or [cls.name]) if name in classes) + if conditional_opt_out: + problems.append(f"GAP conditional class opt-out {test_path}::{cls.name}") if not found and order is not None: for ancestor in order[1:]: if ancestor in classes: found, setting = _test_setting(classes[ancestor]) if found: break - if found and not setting: + if found and not setting and not conditional_opt_out: continue if setting is UNKNOWN_TEST_SETTING: problems.append(f"GAP dynamic class opt-out {test_path}::{cls.name}") @@ -375,6 +414,9 @@ def audit_repository(root: Path) -> Tuple[bool, List[str]]: if not cls.name.startswith("Test") and setting is not True: continue inherited = [classes[name] for name in order if name in classes] + for ancestor in inherited: + for name in sorted(_conditional_surface(ancestor.body)): + problems.append(f"GAP conditional class check {test_path}::{cls.name}::{name}") if any(isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)) and node.name in {"__init__", "__new__"} for ancestor in inherited for node in ancestor.body): continue methods = {} @@ -402,4 +444,4 @@ def main(argv: Sequence[str] | None = None) -> int: if __name__ == "__main__": raise SystemExit(main()) -# ratios: loc_comments=313:50 imports_exports=8:4 calls_definitions=140:12 +# ratios: loc_comments=348:51 imports_exports=8:4 calls_definitions=167:14 From 1ae724bbf829f7534c53a4e53c295f8e4c1bcd3e Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Sat, 12 Sep 2026 05:10:17 +0000 Subject: [PATCH 37/65] Bind maintenance report to final boundary repairs --- docs/work-graphs/repository-plan-report.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/work-graphs/repository-plan-report.json b/docs/work-graphs/repository-plan-report.json index 0714200..59573ea 100644 --- a/docs/work-graphs/repository-plan-report.json +++ b/docs/work-graphs/repository-plan-report.json @@ -10,7 +10,7 @@ }, "source": { "branch": "repair/ucns-audit-20260911", - "commit": "e408e1781ff143f772a02cd72d407deaca00cde4", + "commit": "a805763e7dbf2b20be492a476182a1497b05f0af", "generated_at": "2026-09-12", "note": "Describes the exact implementation commit preceding this report-only refresh. Maintenance PR #226 is in final exact-head review; merge remains a separate acceptance observation. No scientific or deployment authority is transferred." }, From 7a2a532a17ed4ca071e0f800fb28ef4a7f9f7414 Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Sat, 12 Sep 2026 05:24:48 +0000 Subject: [PATCH 38/65] Close pytest collection configuration and header binding gaps --- .github/workflows/ci.yml | 2 +- README.md | 4 ++ pyproject.toml | 1 + tests/test_skill_lib_contracts.py | 37 +++++++++++- tools/verify_skill_lib_contracts.py | 87 ++++++++++++++++++++++++++--- uv.lock | 2 + 6 files changed, 121 insertions(+), 12 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a9cca2c..e1e25c8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -60,7 +60,7 @@ jobs: --strict-sha --require-vendored - name: Audit skill-lib contract graph - run: python tools/verify_skill_lib_contracts.py . + run: .venv/bin/python tools/verify_skill_lib_contracts.py . - name: Run geometry tests run: .venv/bin/python -m pytest - name: Build package diff --git a/README.md b/README.md index 28711eb..a254797 100644 --- a/README.md +++ b/README.md @@ -193,6 +193,10 @@ unavailable instrumentation is an error before check execution. Descendant disco uses the task-children interface when available and otherwise reads procfs parent IDs. Potential test definitions inside module or class control flow fail the declaration audit closed; executable witnesses use direct module-level definitions. +The graph supports default pytest file/class/function naming and `tests` as the +collection root. Changed collection settings, alternative pytest configuration +files, and collection-changing `addopts` fail closed. Root `pyproject.toml` is +parsed without executing tests; Python 3.10 uses the declared `tomli` test dependency. Receipt output must be outside the bound source tree and is written by atomic replacement so an output hardlink cannot modify a bound input. Checks import the bound checkout with ambient pytest plugins disabled and diff --git a/pyproject.toml b/pyproject.toml index 2052459..3c908e6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,6 +23,7 @@ classifiers = [ [project.optional-dependencies] test = [ "pytest>=8", + "tomli>=2; python_version < '3.11'", "mpmath>=1.3,<2", "sympy>=1.12,<2", ] diff --git a/tests/test_skill_lib_contracts.py b/tests/test_skill_lib_contracts.py index c268e7b..1aa3c24 100644 --- a/tests/test_skill_lib_contracts.py +++ b/tests/test_skill_lib_contracts.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=170:60 imports_exports=9:7 calls_definitions=89:7 +# ratios: loc_comments=203:60 imports_exports=9:7 calls_definitions=102:7 # === CHECKS === # id: check_contract_audit_no_exec # proves: contract_audit_is_no_exec @@ -136,6 +136,15 @@ def test_empty_syntax_and_class_coverage_are_not_closed(tmp_path: Path) -> None: "if True:\n class TestHidden:\n def test_hidden(self): assert False\n", "__test__ = False\nif True:\n __test__ = True\n def test_hidden(): assert False\n", "class TestConditional:\n __test__ = False\n if True:\n __test__ = True\n def test_hidden(self): assert False\n", + "def helper(): assert False\nfor test_hidden in [helper]: pass\n", + "def helper(): assert False\nfor _, *test_hidden in [(1, helper)]: pass\n", + "with manager() as test_hidden: pass\n", + "if (test_hidden := helper): pass\n", + "match helper:\n case test_hidden: pass\n", + "match mapping:\n case {'x': value, **TestHidden}: pass\n", + "class TestConditional:\n for test_hidden in [helper]: pass\n", + "def helper(): assert False\n(test_hidden := helper)\n", + "def helper(): assert False\nvalues = [(test_hidden := helper) for item in [1]]\n", ): branch.write_text(code) ok, problems = audit_repository(root) @@ -148,6 +157,30 @@ def test_empty_syntax_and_class_coverage_are_not_closed(tmp_path: Path) -> None: branch.write_text(code) ok, problems = audit_repository(root) assert ok, (code, problems) + branch.write_text("def testhidden(): assert False\n") + ok, problems = audit_repository(root) + assert not ok and any("testhidden" in item for item in problems), problems + branch.unlink() + config = root / "pyproject.toml" + for settings in ( + 'python_files = ["spec_*.py"]', + 'python_classes = ["Spec"]', + 'python_functions = ["spec_"]', + 'testpaths = ["integration"]', + 'addopts = "-o python_files=spec_*.py"', + ): + config.write_text('[tool.pytest.ini_options]\n' + settings + '\n') + ok, problems = audit_repository(root) + assert not ok and any("pytest" in item for item in problems), (settings, problems) + config.write_text('[tool.pytest.ini_options]\npython_files = ["test_*.py", "*_test.py"]\npython_classes = ["Test"]\npython_functions = ["test"]\ntestpaths = ["tests"]\naddopts = "-q"\n') + ok, problems = audit_repository(root) + assert ok, problems + for name in ("pytest.ini", ".pytest.ini", "pytest.toml", ".pytest.toml", "tox.ini", "setup.cfg"): + alternate = root / name + alternate.write_text("") + ok, problems = audit_repository(root) + assert not ok and any("collection configuration" in item for item in problems), (name, problems) + alternate.unlink() def test_nested_fences_cannot_hide_an_obligation(tmp_path: Path) -> None: @@ -250,4 +283,4 @@ def test_vendored_typescript_parser_retains_numeric_field_names(tmp_path: Path) broken.write_text("// no declaration\nthrow new Error('must not execute');\n") ok, problems = audit_repository(tmp_path / "repo") assert not ok and any("universal.ts missing MODULE_BUILD" in item for item in problems), problems -# ratios: loc_comments=170:60 imports_exports=9:7 calls_definitions=89:7 +# ratios: loc_comments=203:60 imports_exports=9:7 calls_definitions=102:7 diff --git a/tools/verify_skill_lib_contracts.py b/tools/verify_skill_lib_contracts.py index b79d034..df1ad96 100644 --- a/tools/verify_skill_lib_contracts.py +++ b/tools/verify_skill_lib_contracts.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=348:51 imports_exports=8:4 calls_definitions=167:14 +# ratios: loc_comments=410:53 imports_exports=11:4 calls_definitions=204:16 # === MODULE_BUILD === # id: skill_lib_contract_audit # module_name: verify_skill_lib_contracts @@ -51,11 +51,17 @@ import ast import importlib.util import re +import shlex import sys from dataclasses import dataclass from pathlib import Path from typing import Dict, Iterable, List, Sequence, Set, Tuple +try: + import tomllib +except ModuleNotFoundError: # Python 3.10 verification dependency. + import tomli as tomllib + BLOCK_RE = re.compile(r"^\s*#\s*===\s*(MODULE_BUILD|CONTRACTS|CHECKS)\s*===\s*$") END_RE = re.compile(r"^\s*#\s*===\s*END\s+(MODULE_BUILD|CONTRACTS|CHECKS)\s*===\s*$") DECLARATION_FENCE_RE = re.compile(r"^\s*#\s*=+\s*(?:END\s+)?(?:MODULE_BUILD|CONTRACTS|CHECKS)\b") @@ -155,6 +161,8 @@ def _target_names(target: ast.AST) -> list[str]: return [target.id] if isinstance(target, (ast.Tuple, ast.List)): return [name for item in target.elts for name in _target_names(item)] + if isinstance(target, ast.Starred): + return _target_names(target.value) return [] @@ -186,12 +194,33 @@ def _bindings(body: list[ast.stmt]) -> dict[str, str]: _COMPOUND_STATEMENTS = (ast.If, ast.For, ast.AsyncFor, ast.While, ast.Try, ast.With, ast.AsyncWith, ast.Match, getattr(ast, "TryStar", ast.Try)) +def _header_bindings(node: ast.AST, *, named_only: bool = False) -> set[str]: + """Include assignment targets in statement headers, without executing them.""" + names: set[str] = set() + if isinstance(node, ast.NamedExpr): + names.update(_target_names(node.target)) + for field, value in ast.iter_fields(node): + if field in {"body", "orelse", "finalbody"}: + continue + values = value if isinstance(value, list) else [value] + for item in values: + if isinstance(item, ast.AST): + if not named_only and isinstance(item, ast.Name) and isinstance(item.ctx, ast.Store): + names.add(item.id) + if not named_only and isinstance(item, (ast.MatchAs, ast.MatchStar, ast.ExceptHandler)) and item.name: + names.add(item.name) + if not named_only and isinstance(item, ast.MatchMapping) and item.rest: + names.add(item.rest) + names.update(_header_bindings(item, named_only=named_only)) + return names + + def _conditional_test_names(body: list[ast.stmt]) -> set[str]: """Find possible module/class test bindings without entering function bodies.""" names: set[str] = set() for node in body: if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)): - if node.name.startswith("test_"): + if node.name.startswith("test"): names.add(node.name) elif isinstance(node, ast.ClassDef): found, setting = _test_setting(node) @@ -207,13 +236,18 @@ def _conditional_test_names(body: list[ast.stmt]) -> set[str]: names.update(_conditional_test_names(group)) else: for name, kind in _bindings([node]).items(): - if name in {"*", "__test__"} or name.startswith("test_") and kind != "literal" or name.startswith("Test") and kind == "unknown": + if name in {"*", "__test__"} or name.startswith("test") and kind != "literal" or name.startswith("Test") and kind == "unknown": names.add(name) + if isinstance(node, _COMPOUND_STATEMENTS): + names.update(name for name in _header_bindings(node) if name == "__test__" or name.startswith(("test", "Test"))) return names def _conditional_surface(body: list[ast.stmt]) -> set[str]: - return _conditional_test_names([node for node in body if isinstance(node, _COMPOUND_STATEMENTS)]) + names = _conditional_test_names([node for node in body if isinstance(node, _COMPOUND_STATEMENTS)]) + for node in body: + names.update(name for name in _header_bindings(node, named_only=True) if name == "__test__" or name.startswith(("test", "Test"))) + return names def _class_mro(name: str, classes: dict[str, ast.ClassDef], active=()) -> list[str] | None: @@ -279,10 +313,45 @@ def _missing(fields: Dict[str, str], required: Set[str]) -> Set[str]: return {name for name in required if not fields.get(name)} +def _collection_config_problems(root: Path) -> list[str]: + """The no-exec graph supports the repository's bounded default collection.""" + problems = [] + alternatives = {"pytest.toml", ".pytest.toml", "pytest.ini", ".pytest.ini", "tox.ini", "setup.cfg"} + for base in (root, root / "tests"): + paths = base.iterdir() if base == root else base.rglob("*") + if not base.exists(): + continue + for path in paths: + if path.is_file() and path.name in alternatives: + problems.append(f"GAP unsupported pytest collection configuration: {path}; use root pyproject.toml with default collection") + path = root / "pyproject.toml" + if not path.exists(): + return problems + try: + document = tomllib.loads(path.read_text(encoding="utf-8")) + section = document.get("tool", {}).get("pytest", {}) + if set(section) - {"ini_options"}: + problems.append(f"GAP unsupported native pytest collection configuration: {path}") + config = section.get("ini_options", {}) + defaults = {"python_files": ["test_*.py", "*_test.py"], "python_classes": ["Test"], "python_functions": ["test"], "testpaths": ["tests"]} + for name, expected in defaults.items(): + if name in config: + actual = config[name].split() if isinstance(config[name], str) else config[name] + if actual != expected: + problems.append(f"GAP unsupported pytest collection setting {name}: {actual!r}") + options = config.get("addopts", []) + options = shlex.split(options) if isinstance(options, str) else options + if not isinstance(options, list) or any(option not in {"-q", "-v", "-vv", "-ra", "--strict-markers", "--strict-config"} for option in options): + problems.append("GAP unsupported pytest addopts; collection-changing arguments are outside the audited boundary") + except (OSError, UnicodeError, ValueError, AttributeError, TypeError) as error: + problems.append(f"GAP invalid pytest collection configuration: {error}") + return problems + + def audit_repository(root: Path) -> Tuple[bool, List[str]]: root = root.resolve() entries: List[Entry] = [] - problems: List[str] = [] + problems: List[str] = _collection_config_problems(root) paths = tuple(_source_files(root)) trees: Dict[Path, ast.Module] = {} if not paths: @@ -349,7 +418,7 @@ def audit_repository(root: Path) -> Tuple[bool, List[str]]: check.source.is_relative_to(root / "tests") and (check.source.name.startswith("test_") or check.source.name.endswith("_test.py")) ) - if not is_test_module or not name.startswith("test_"): + if not is_test_module or not name.startswith("test"): problems.append( f"GAP {check.id} call does not target an executable pytest test: {call}" ) @@ -381,7 +450,7 @@ def audit_repository(root: Path) -> Tuple[bool, List[str]]: for name, kind in bindings.items(): if name == "*": problems.append(f"GAP unresolved wildcard test-module import {test_path}") - elif name.startswith("test_"): + elif name.startswith("test"): if kind == "unknown": problems.append(f"GAP unresolved executable alias {test_path}::{name}") elif kind == "function" and name not in declared_calls: @@ -423,7 +492,7 @@ def audit_repository(root: Path) -> Tuple[bool, List[str]]: for ancestor in reversed(inherited): methods.update(_bindings(ancestor.body)) for name, kind in methods.items(): - if name.startswith("test_") and kind in {"function", "unknown"}: + if name.startswith("test") and kind in {"function", "unknown"}: label = "inherited class check" if cls.bases else "unsupported class check" problems.append(f"GAP {label} {test_path}::{cls.name}::{name}; use a declared top-level self::test_fn witness") @@ -444,4 +513,4 @@ def main(argv: Sequence[str] | None = None) -> int: if __name__ == "__main__": raise SystemExit(main()) -# ratios: loc_comments=348:51 imports_exports=8:4 calls_definitions=167:14 +# ratios: loc_comments=410:53 imports_exports=11:4 calls_definitions=204:16 diff --git a/uv.lock b/uv.lock index 0e61838..a06c1f4 100644 --- a/uv.lock +++ b/uv.lock @@ -1001,6 +1001,7 @@ test = [ { name = "mpmath" }, { name = "pytest" }, { name = "sympy" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, ] [package.metadata] @@ -1014,6 +1015,7 @@ requires-dist = [ { name = "sympy", marker = "extra == 'research'", specifier = ">=1.12,<2" }, { name = "sympy", marker = "extra == 'test'", specifier = ">=1.12,<2" }, { name = "tomli", marker = "python_full_version < '3.11' and extra == 'build'", specifier = ">=2" }, + { name = "tomli", marker = "python_full_version < '3.11' and extra == 'test'", specifier = ">=2" }, { name = "twine", marker = "extra == 'build'", specifier = ">=5" }, ] provides-extras = ["test", "research", "build"] From 3f3d9625890d65a9b085b3bdd89500aaa580cc97 Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Sat, 12 Sep 2026 05:24:48 +0000 Subject: [PATCH 39/65] Bind report to bounded collection contract --- docs/work-graphs/repository-plan-report.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/work-graphs/repository-plan-report.json b/docs/work-graphs/repository-plan-report.json index 59573ea..a065790 100644 --- a/docs/work-graphs/repository-plan-report.json +++ b/docs/work-graphs/repository-plan-report.json @@ -10,7 +10,7 @@ }, "source": { "branch": "repair/ucns-audit-20260911", - "commit": "a805763e7dbf2b20be492a476182a1497b05f0af", + "commit": "7a2a532a17ed4ca071e0f800fb28ef4a7f9f7414", "generated_at": "2026-09-12", "note": "Describes the exact implementation commit preceding this report-only refresh. Maintenance PR #226 is in final exact-head review; merge remains a separate acceptance observation. No scientific or deployment authority is transferred." }, From b131ef1e2d6b89280592f516869c9c606e640836 Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Sat, 12 Sep 2026 05:25:55 +0000 Subject: [PATCH 40/65] Allow generated setup metadata without pytest overrides --- tests/test_skill_lib_contracts.py | 9 ++++++--- tools/verify_skill_lib_contracts.py | 15 +++++++++++++-- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/tests/test_skill_lib_contracts.py b/tests/test_skill_lib_contracts.py index 1aa3c24..d30ca40 100644 --- a/tests/test_skill_lib_contracts.py +++ b/tests/test_skill_lib_contracts.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=203:60 imports_exports=9:7 calls_definitions=102:7 +# ratios: loc_comments=206:60 imports_exports=9:7 calls_definitions=104:7 # === CHECKS === # id: check_contract_audit_no_exec # proves: contract_audit_is_no_exec @@ -177,10 +177,13 @@ def test_empty_syntax_and_class_coverage_are_not_closed(tmp_path: Path) -> None: assert ok, problems for name in ("pytest.ini", ".pytest.ini", "pytest.toml", ".pytest.toml", "tox.ini", "setup.cfg"): alternate = root / name - alternate.write_text("") + alternate.write_text("[tool:pytest]\npython_files=spec_*.py\n" if name == "setup.cfg" else "[pytest]\npython_files=spec_*.py\n" if name == "tox.ini" else "") ok, problems = audit_repository(root) assert not ok and any("collection configuration" in item for item in problems), (name, problems) alternate.unlink() + (root / "setup.cfg").write_text("[egg_info]\ntag_build =\ntag_date = 0\n") + ok, problems = audit_repository(root) + assert ok, problems def test_nested_fences_cannot_hide_an_obligation(tmp_path: Path) -> None: @@ -283,4 +286,4 @@ def test_vendored_typescript_parser_retains_numeric_field_names(tmp_path: Path) broken.write_text("// no declaration\nthrow new Error('must not execute');\n") ok, problems = audit_repository(tmp_path / "repo") assert not ok and any("universal.ts missing MODULE_BUILD" in item for item in problems), problems -# ratios: loc_comments=203:60 imports_exports=9:7 calls_definitions=102:7 +# ratios: loc_comments=206:60 imports_exports=9:7 calls_definitions=104:7 diff --git a/tools/verify_skill_lib_contracts.py b/tools/verify_skill_lib_contracts.py index df1ad96..30b95df 100644 --- a/tools/verify_skill_lib_contracts.py +++ b/tools/verify_skill_lib_contracts.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=410:53 imports_exports=11:4 calls_definitions=204:16 +# ratios: loc_comments=421:53 imports_exports=12:4 calls_definitions=208:16 # === MODULE_BUILD === # id: skill_lib_contract_audit # module_name: verify_skill_lib_contracts @@ -49,6 +49,7 @@ from __future__ import annotations import ast +import configparser import importlib.util import re import shlex @@ -323,6 +324,16 @@ def _collection_config_problems(root: Path) -> list[str]: continue for path in paths: if path.is_file() and path.name in alternatives: + if path.name in {"setup.cfg", "tox.ini"}: + try: + parser = configparser.ConfigParser(interpolation=None) + parser.read_string(path.read_text(encoding="utf-8")) + section = "tool:pytest" if path.name == "setup.cfg" else "pytest" + if not parser.has_section(section): + continue # Setuptools emits setup.cfg with only egg_info. + except (OSError, UnicodeError, configparser.Error) as error: + problems.append(f"GAP invalid pytest collection configuration: {path}: {error}") + continue problems.append(f"GAP unsupported pytest collection configuration: {path}; use root pyproject.toml with default collection") path = root / "pyproject.toml" if not path.exists(): @@ -513,4 +524,4 @@ def main(argv: Sequence[str] | None = None) -> int: if __name__ == "__main__": raise SystemExit(main()) -# ratios: loc_comments=410:53 imports_exports=11:4 calls_definitions=204:16 +# ratios: loc_comments=421:53 imports_exports=12:4 calls_definitions=208:16 From 163d18b463049a577a275d1b83ff8b646f2c4730 Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Sat, 12 Sep 2026 05:25:55 +0000 Subject: [PATCH 41/65] Refresh collection and package metadata repair identity --- docs/work-graphs/repository-plan-report.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/work-graphs/repository-plan-report.json b/docs/work-graphs/repository-plan-report.json index a065790..518371a 100644 --- a/docs/work-graphs/repository-plan-report.json +++ b/docs/work-graphs/repository-plan-report.json @@ -10,7 +10,7 @@ }, "source": { "branch": "repair/ucns-audit-20260911", - "commit": "7a2a532a17ed4ca071e0f800fb28ef4a7f9f7414", + "commit": "b131ef1e2d6b89280592f516869c9c606e640836", "generated_at": "2026-09-12", "note": "Describes the exact implementation commit preceding this report-only refresh. Maintenance PR #226 is in final exact-head review; merge remains a separate acceptance observation. No scientific or deployment authority is transferred." }, From 8409ec8b4d79b7b175273d1eb1f57b5bed942ed1 Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Sat, 12 Sep 2026 05:45:07 +0000 Subject: [PATCH 42/65] Bind collection, capability probes, and archive permissions to evidence --- README.md | 9 ++++-- tests/test_distributions.py | 13 +++++++-- tests/test_skill_lib_boundary_runner.py | 36 ++++++++++++++++++++--- tests/test_skill_lib_contracts.py | 22 ++++++++++++-- tools/_boundary_supervisor.py | 24 +++++++++++----- tools/run_skill_lib_boundaries.py | 38 ++++++++++++++----------- tools/verify_distributions.py | 7 +++-- tools/verify_skill_lib_contracts.py | 17 ++++++++--- 8 files changed, 126 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index a254797..788c3a2 100644 --- a/README.md +++ b/README.md @@ -193,10 +193,15 @@ unavailable instrumentation is an error before check execution. Descendant disco uses the task-children interface when available and otherwise reads procfs parent IDs. Potential test definitions inside module or class control flow fail the declaration audit closed; executable witnesses use direct module-level definitions. -The graph supports default pytest file/class/function naming and `tests` as the -collection root. Changed collection settings, alternative pytest configuration +The graph requires explicit `testpaths = ["tests"]` and supports default pytest +file/class/function naming. Conftest files and local plugin registration are +outside this bounded collection contract. Changed settings, alternative pytest configuration files, and collection-changing `addopts` fail closed. Root `pyproject.toml` is parsed without executing tests; Python 3.10 uses the declared `tomli` test dependency. +Source observation starts before capability probing. Executable Node version +probes use the same descendant supervisor as checks; a timeout or leaked child +cannot satisfy the capability. Source archives require owner-readable/writable +files and owner-readable/writable/searchable directories, without special mode bits. Receipt output must be outside the bound source tree and is written by atomic replacement so an output hardlink cannot modify a bound input. Checks import the bound checkout with ambient pytest plugins disabled and diff --git a/tests/test_distributions.py b/tests/test_distributions.py index 02f6d44..cd5bae8 100644 --- a/tests/test_distributions.py +++ b/tests/test_distributions.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=122:10 imports_exports=9:1 calls_definitions=47:2 +# ratios: loc_comments=129:10 imports_exports=9:1 calls_definitions=47:2 # === CHECKS === # id: check_distribution_replay_inputs # proves: distributions_retain_exact_replay_inputs @@ -29,7 +29,7 @@ SPEC.loader.exec_module(audit) -def _archives(root, sdist, wheel, *, omit="", altered="", extra="", sdist_extra="", wheel_omit="", wheel_altered="", metadata_extra="", wheel_flags="true", record_mode="", sdist_directory="", wheel_directory="", sdist_root="ucns-0"): +def _archives(root, sdist, wheel, *, omit="", altered="", extra="", sdist_extra="", wheel_omit="", wheel_altered="", metadata_extra="", wheel_flags="true", record_mode="", sdist_directory="", wheel_directory="", sdist_root="ucns-0", sdist_mode=None, directory_mode=0o755): core_metadata = "Metadata-Version: 2.4\nName: ucns\nVersion: 0\nSummary: Fixture\nAuthor: Test\nRequires-Python: >=3.10\nDescription-Content-Type: text/markdown\nLicense: fixture\nLicense-File: LICENSE\nDynamic: license-file\n" with tarfile.open(sdist, "w:gz") as archive: files = {**audit.expected_files(root), "setup.cfg": audit.GENERATED_SETUP_CFG, @@ -49,10 +49,13 @@ def _archives(root, sdist, wheel, *, omit="", altered="", extra="", sdist_extra= data += b"drift" member = tarfile.TarInfo(f"{sdist_root}/{name}") member.size = len(data) + if name == "pyproject.toml" and sdist_mode is not None: + member.mode = sdist_mode archive.addfile(member, io.BytesIO(data)) if sdist_directory: member = tarfile.TarInfo(f"{sdist_root}/{sdist_directory}") member.type = tarfile.DIRTYPE + member.mode = directory_mode archive.addfile(member) with zipfile.ZipFile(wheel, "w") as archive: files = {} @@ -97,6 +100,10 @@ def test_distribution_replay_inputs_fail_closed(tmp_path: Path) -> None: _archives(root, sdist, wheel) assert audit.verify_distributions(root, sdist, wheel) == [] for options, message in ( + ({"sdist_mode": 0}, "unusable sdist permissions"), + ({"sdist_mode": 0o400}, "unusable sdist permissions"), + ({"sdist_mode": 0o4644}, "unusable sdist permissions"), + ({"sdist_directory": "extra", "directory_mode": 0o644}, "unusable sdist permissions"), ({"wheel_directory": "evil-1.dist-info"}, "exactly one .dist-info directory"), ({"omit": "LICENSE"}, "missing LICENSE"), ({"altered": "LICENSE"}, "altered LICENSE"), @@ -140,4 +147,4 @@ def test_distribution_replay_inputs_fail_closed(tmp_path: Path) -> None: with zipfile.ZipFile(wheel, "a") as archive, pytest.warns(UserWarning, match="Duplicate"): archive.writestr("ucns/__init__.py", b"duplicate") assert any("duplicate" in problem for problem in audit.verify_distributions(root, sdist, wheel)) -# ratios: loc_comments=122:10 imports_exports=9:1 calls_definitions=47:2 +# ratios: loc_comments=129:10 imports_exports=9:1 calls_definitions=47:2 diff --git a/tests/test_skill_lib_boundary_runner.py b/tests/test_skill_lib_boundary_runner.py index 588f07a..b29cc3c 100644 --- a/tests/test_skill_lib_boundary_runner.py +++ b/tests/test_skill_lib_boundary_runner.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=98:314 imports_exports=13:18 calls_definitions=131:20 +# ratios: loc_comments=99:341 imports_exports=15:18 calls_definitions=147:20 # === CHECKS === # id: check_boundary_runner_audit_gate # proves: boundary_runner_audits_before_execution @@ -105,6 +105,7 @@ def _repo(tmp_path: Path, functions: str, checks: list[dict[str, str]]) -> Path: (root / "src" / "pkg").mkdir(parents=True) (root / "tools").mkdir() (root / "tests").mkdir() + (root / "pyproject.toml").write_text('[tool.pytest.ini_options]\ntestpaths = ["tests"]\n') contracts: list[str] = [] check_lines: list[str] = [] for index, check in enumerate(checks): @@ -427,7 +428,7 @@ def test_background_descendants_block_acceptance(tmp_path: Path) -> None: os.kill(pid, 0) # === CHECKS === # id: check_node24_capability_runs_typescript_witness -# proves: boundary_runner_consumes_capabilities_and_timeouts +# proves: boundary_runner_consumes_capabilities_and_timeouts, boundary_runner_receipt_is_bounded_and_bound, boundary_supervisor_ends_descendants # call: self::test_node24_capability_runs_typescript_witness # requires: python3, node24 # timeout: 20 @@ -436,7 +437,9 @@ def test_background_descendants_block_acceptance(tmp_path: Path) -> None: # === END CHECKS === -def test_node24_capability_runs_typescript_witness() -> None: +def test_node24_capability_runs_typescript_witness(tmp_path: Path) -> None: + import os + import pytest from unittest.mock import patch from subprocess import CompletedProcess for version, expected in (("v24.15.0\n", True), ("v22.23.2\n", False), ("not-a-version", False)): @@ -448,4 +451,29 @@ def test_node24_capability_runs_typescript_witness() -> None: receipt = runner.run_boundaries(RUNNER_PATH.resolve().parents[1], selected_ids=("check_vendored_typescript_field_preservation",)) assert receipt["status"] == "passed", receipt assert len(receipt["outcomes"]) == 1 and receipt["outcomes"][0]["status"] == "PASS", receipt -# ratios: loc_comments=98:314 imports_exports=13:18 calls_definitions=131:20 + root = _repo(tmp_path, "def test_probe(): pass\n", [{"id": "check_probe", "function": "test_probe", "requires": "node24"}]) + source = root / "src/pkg/feature.py" + node = tmp_path / "node" + node.write_text(f"#!{sys.executable}\nfrom pathlib import Path\np=Path({str(source)!r})\noriginal=p.read_bytes()\np.write_bytes(original+b'# transient\\n')\np.write_bytes(original)\nprint('v24.15.0')\n") + node.chmod(0o755) + with patch.dict(os.environ, {"PATH": str(tmp_path) + os.pathsep + os.environ["PATH"]}): + receipt = runner.run_boundaries(root) + assert receipt["outcomes"][0]["status"] == "PASS", receipt + assert receipt["source_before_sha256"] == receipt["source_after_sha256"] + assert receipt["status"] == "not-passed" and not receipt["source_unchanged"] + assert "src/pkg/feature.py" in receipt["outcomes"][0]["source_events"] + pid_path = tmp_path / "probe-child.pid" + node.write_text(f"#!{sys.executable}\nimport subprocess, sys\nfrom pathlib import Path\np=subprocess.Popen([sys.executable, '-c', 'import time; time.sleep(30)'], start_new_session=True)\nPath({str(pid_path)!r}).write_text(str(p.pid))\nprint('v24.15.0')\n") + with patch.dict(os.environ, {"PATH": str(tmp_path) + os.pathsep + os.environ["PATH"]}): + receipt = runner.run_boundaries(root) + assert receipt["status"] == "not-passed" and receipt["outcomes"][0]["status"] == "ERROR", receipt + assert receipt["outcomes"][0]["missing_capabilities"] == ("node24",) + with pytest.raises(ProcessLookupError): + os.kill(int(pid_path.read_text()), 0) + node.write_text(f"#!{sys.executable}\nimport os, time\nfrom pathlib import Path\nPath({str(pid_path)!r}).write_text(str(os.getpid()))\nprint('v24.15.0', flush=True)\ntime.sleep(30)\n") + with patch.dict(os.environ, {"PATH": str(tmp_path) + os.pathsep + os.environ["PATH"]}): + receipt = runner.run_boundaries(root) + assert receipt["outcomes"][0]["status"] == "ERROR", receipt + with pytest.raises(ProcessLookupError): + os.kill(int(pid_path.read_text()), 0) +# ratios: loc_comments=99:341 imports_exports=15:18 calls_definitions=147:20 diff --git a/tests/test_skill_lib_contracts.py b/tests/test_skill_lib_contracts.py index d30ca40..50e7bf2 100644 --- a/tests/test_skill_lib_contracts.py +++ b/tests/test_skill_lib_contracts.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=206:60 imports_exports=9:7 calls_definitions=104:7 +# ratios: loc_comments=224:60 imports_exports=9:7 calls_definitions=118:7 # === CHECKS === # id: check_contract_audit_no_exec # proves: contract_audit_is_no_exec @@ -145,6 +145,7 @@ def test_empty_syntax_and_class_coverage_are_not_closed(tmp_path: Path) -> None: "class TestConditional:\n for test_hidden in [helper]: pass\n", "def helper(): assert False\n(test_hidden := helper)\n", "def helper(): assert False\nvalues = [(test_hidden := helper) for item in [1]]\n", + "def helper(): assert False\nif True:\n (test_hidden := helper)\n", ): branch.write_text(code) ok, problems = audit_repository(root) @@ -184,6 +185,23 @@ def test_empty_syntax_and_class_coverage_are_not_closed(tmp_path: Path) -> None: (root / "setup.cfg").write_text("[egg_info]\ntag_build =\ntag_date = 0\n") ok, problems = audit_repository(root) assert ok, problems + for name in ("conftest.py", "tests/conftest.py"): + plugin = root / name + plugin.write_text("def pytest_pycollect_makeitem(collector, name, obj):\n return []\n") + ok, problems = audit_repository(root) + assert not ok and any("conftest collection/plugin" in item for item in problems), problems + plugin.unlink() + plugin = root / "tests/test_plugins.py" + plugin.write_text("pytest_plugins = ['custom_collector']\n") + ok, problems = audit_repository(root) + assert not ok and any("pytest plugin collection" in item for item in problems), problems + plugin.unlink() + config.write_text('[tool.pytest.ini_options]\naddopts = "-q"\n') + ok, problems = audit_repository(root) + assert not ok and any("explicit testpaths" in item for item in problems), problems + config.unlink() + ok, problems = audit_repository(root) + assert not ok and any("explicit testpaths" in item for item in problems), problems def test_nested_fences_cannot_hide_an_obligation(tmp_path: Path) -> None: @@ -286,4 +304,4 @@ def test_vendored_typescript_parser_retains_numeric_field_names(tmp_path: Path) broken.write_text("// no declaration\nthrow new Error('must not execute');\n") ok, problems = audit_repository(tmp_path / "repo") assert not ok and any("universal.ts missing MODULE_BUILD" in item for item in problems), problems -# ratios: loc_comments=206:60 imports_exports=9:7 calls_definitions=104:7 +# ratios: loc_comments=224:60 imports_exports=9:7 calls_definitions=118:7 diff --git a/tools/_boundary_supervisor.py b/tools/_boundary_supervisor.py index 039162f..8ee9380 100644 --- a/tools/_boundary_supervisor.py +++ b/tools/_boundary_supervisor.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=73:31 imports_exports=8:1 calls_definitions=30:4 +# ratios: loc_comments=79:32 imports_exports=8:1 calls_definitions=32:5 # === MODULE_BUILD === # id: boundary_process_supervisor # module_name: _boundary_supervisor @@ -19,12 +19,14 @@ # === END MODULE_BUILD === # === CONTRACTS === # id: boundary_supervisor_ends_descendants -# given: a selected check exits or exceeds its timeout, including after replacing its signal handlers +# given: a selected check or executable capability probe exits or exceeds its timeout, including after replacing its signal handlers # then: a separate Linux subreaper kills and reaps remaining check descendants before accepting or returning its outcome # class: evidence # === END CONTRACTS === """Internal usage: python _boundary_supervisor.py TIMEOUT BOOTSTRAP ROOT REPORT PYTEST_ARGS. +For a capability executable: python _boundary_supervisor.py --probe TIMEOUT COMMAND ARGS. + Only the child runs pytest or test code. Descendant adoption therefore survives child signal-handler changes, crashes, and detached sessions. This is trusted check instrumentation, not containment against hostile code targeting ancestors. @@ -85,12 +87,10 @@ def _reap_descendants() -> int: pass -def main() -> int: - timeout = int(sys.argv[1]) - report = Path(sys.argv[4]) +def _supervise(command: list[str], timeout: int) -> tuple[int, bool, int]: _enable_descendant_reaping() _owned_children() # Verify procfs support before any check code is launched. - process = subprocess.Popen([sys.executable, *sys.argv[2:]], start_new_session=True) + process = subprocess.Popen(command, start_new_session=True) timed_out = False try: returncode = process.wait(timeout=timeout) @@ -99,6 +99,16 @@ def main() -> int: process.kill() returncode = process.wait() descendants = _reap_descendants() + return returncode, timed_out, descendants + + +def main() -> int: + if sys.argv[1] == "--probe": + returncode, timed_out, descendants = _supervise(sys.argv[3:], int(sys.argv[2])) + return 124 if timed_out else 1 if descendants else returncode + timeout = int(sys.argv[1]) + report = Path(sys.argv[4]) + returncode, timed_out, descendants = _supervise([sys.executable, *sys.argv[2:]], timeout) try: observed = json.loads(report.read_text()) if not isinstance(observed, dict): @@ -115,4 +125,4 @@ def main() -> int: if __name__ == "__main__": raise SystemExit(main()) -# ratios: loc_comments=73:31 imports_exports=8:1 calls_definitions=30:4 +# ratios: loc_comments=79:32 imports_exports=8:1 calls_definitions=32:5 diff --git a/tools/run_skill_lib_boundaries.py b/tools/run_skill_lib_boundaries.py index 579c819..7bfe5be 100644 --- a/tools/run_skill_lib_boundaries.py +++ b/tools/run_skill_lib_boundaries.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=388:71 imports_exports=20:4 calls_definitions=172:19 +# ratios: loc_comments=392:71 imports_exports=20:4 calls_definitions=175:20 # === MODULE_BUILD === # id: skill_lib_boundary_runner # module_name: run_skill_lib_boundaries @@ -218,7 +218,7 @@ def _capability_available(name: str) -> bool: if executable is None: return False try: - result = subprocess.run([executable, "--version"], capture_output=True, text=True, timeout=5) + result = subprocess.run([sys.executable, str(SUPERVISOR), "--probe", "5", executable, "--version"], capture_output=True, text=True) except (OSError, UnicodeError, subprocess.TimeoutExpired): return False return result.returncode == 0 and re.fullmatch(r"v24\.\d+\.\d+", result.stdout.strip()) is not None @@ -333,6 +333,16 @@ def _error_outcome(root: Path, check: Entry, error: Exception) -> CheckOutcome: def _run_check(root: Path, check: Entry) -> CheckOutcome: + started = time.monotonic() + watcher = _SourceWatch(root) + try: + outcome = _execute_check(root, check) + finally: + source_events = watcher.finish() + return replace(outcome, source_events=source_events, duration_seconds=round(time.monotonic() - started, 6)) + + +def _execute_check(root: Path, check: Entry) -> CheckOutcome: requires, timeout, mutates, cleanup = _validate_check(check) missing = tuple(name for name in requires if not _capability_available(name)) call = check.fields["call"] @@ -363,18 +373,14 @@ def _run_check(root: Path, check: Entry) -> CheckOutcome: environment.update(PYTHONDONTWRITEBYTECODE="1", PYTEST_DISABLE_PLUGIN_AUTOLOAD="1") environment["UCNS_BOUND_SOURCE_ROOT"] = str(root) environment["PYTHONPATH"] = os.pathsep.join((str(STARTUP_DIRECTORY), str(root / "src"), str(root))) - watcher = _SourceWatch(root) - try: - with stdout_path.open("wb") as stdout, stderr_path.open("wb") as stderr: - process = subprocess.Popen( - command, cwd=root, stdin=subprocess.DEVNULL, - stdout=stdout, stderr=stderr, start_new_session=True, env=environment, - ) - # The separate supervisor owns the timeout and descendant reaping; - # pytest signal handlers never run in that process. - returncode = process.wait() - finally: - source_events = watcher.finish() + with stdout_path.open("wb") as stdout, stderr_path.open("wb") as stderr: + process = subprocess.Popen( + command, cwd=root, stdin=subprocess.DEVNULL, + stdout=stdout, stderr=stderr, start_new_session=True, env=environment, + ) + # The separate supervisor owns the timeout and descendant reaping; + # pytest signal handlers never run in that process. + returncode = process.wait() stdout_sha, stdout_bytes, stdout_excerpt = _excerpt(stdout_path) stderr_sha, stderr_bytes, stderr_excerpt = _excerpt(stderr_path) status, observed = _pytest_outcome(report_path, returncode) @@ -389,7 +395,7 @@ def _run_check(root: Path, check: Entry) -> CheckOutcome: stdout_excerpt, stderr_excerpt, diagnostic="background descendants outlived the check" if observed.get("descendants_reaped") else "", descendants_reaped=observed.get("descendants_reaped", 0), - imported_sources=observed.get("origins", {}), source_events=source_events, + imported_sources=observed.get("origins", {}), ) @@ -508,4 +514,4 @@ def main(argv: Sequence[str] | None = None) -> int: if __name__ == "__main__": raise SystemExit(main()) -# ratios: loc_comments=388:71 imports_exports=20:4 calls_definitions=172:19 +# ratios: loc_comments=392:71 imports_exports=20:4 calls_definitions=175:20 diff --git a/tools/verify_distributions.py b/tools/verify_distributions.py index cae3dfd..ee77082 100644 --- a/tools/verify_distributions.py +++ b/tools/verify_distributions.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=315:35 imports_exports=17:4 calls_definitions=163:11 +# ratios: loc_comments=318:35 imports_exports=17:4 calls_definitions=165:11 # === MODULE_BUILD === # id: ucns_distribution_audit # module_name: verify_distributions @@ -136,6 +136,9 @@ def record(name: str, data: bytes | None) -> None: else: with tarfile.open(path, "r:gz") as archive: for member in archive: + required_mode = 0o700 if member.isdir() else 0o600 + if member.mode & required_mode != required_mode or member.mode & 0o7000: + raise ValueError(f"unusable sdist permissions: {member.name}: {oct(member.mode)}") if member.isdir(): record(member.name, None) continue @@ -382,4 +385,4 @@ def main() -> int: if __name__ == "__main__": raise SystemExit(main()) -# ratios: loc_comments=315:35 imports_exports=17:4 calls_definitions=163:11 +# ratios: loc_comments=318:35 imports_exports=17:4 calls_definitions=165:11 diff --git a/tools/verify_skill_lib_contracts.py b/tools/verify_skill_lib_contracts.py index 30b95df..6c7fb24 100644 --- a/tools/verify_skill_lib_contracts.py +++ b/tools/verify_skill_lib_contracts.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=421:53 imports_exports=12:4 calls_definitions=208:16 +# ratios: loc_comments=430:53 imports_exports=12:4 calls_definitions=216:16 # === MODULE_BUILD === # id: skill_lib_contract_audit # module_name: verify_skill_lib_contracts @@ -239,8 +239,8 @@ def _conditional_test_names(body: list[ast.stmt]) -> set[str]: for name, kind in _bindings([node]).items(): if name in {"*", "__test__"} or name.startswith("test") and kind != "literal" or name.startswith("Test") and kind == "unknown": names.add(name) - if isinstance(node, _COMPOUND_STATEMENTS): - names.update(name for name in _header_bindings(node) if name == "__test__" or name.startswith(("test", "Test"))) + header = _header_bindings(node, named_only=not isinstance(node, _COMPOUND_STATEMENTS)) + names.update(name for name in header if name == "__test__" or name.startswith(("test", "Test"))) return names @@ -323,6 +323,8 @@ def _collection_config_problems(root: Path) -> list[str]: if not base.exists(): continue for path in paths: + if path.is_file() and path.name == "conftest.py": + problems.append(f"GAP unsupported conftest collection/plugin surface: {path}") if path.is_file() and path.name in alternatives: if path.name in {"setup.cfg", "tox.ini"}: try: @@ -337,6 +339,7 @@ def _collection_config_problems(root: Path) -> list[str]: problems.append(f"GAP unsupported pytest collection configuration: {path}; use root pyproject.toml with default collection") path = root / "pyproject.toml" if not path.exists(): + problems.append("GAP pytest collection requires explicit testpaths = ['tests'] in root pyproject.toml") return problems try: document = tomllib.loads(path.read_text(encoding="utf-8")) @@ -344,6 +347,10 @@ def _collection_config_problems(root: Path) -> list[str]: if set(section) - {"ini_options"}: problems.append(f"GAP unsupported native pytest collection configuration: {path}") config = section.get("ini_options", {}) + if "testpaths" not in config: + problems.append("GAP pytest collection requires explicit testpaths = ['tests']") + if config.get("required_plugins"): + problems.append("GAP unsupported pytest plugin collection configuration") defaults = {"python_files": ["test_*.py", "*_test.py"], "python_classes": ["Test"], "python_functions": ["test"], "testpaths": ["tests"]} for name, expected in defaults.items(): if name in config: @@ -440,6 +447,8 @@ def audit_repository(root: Path) -> Tuple[bool, List[str]]: problems.append(f"GAP {contract_id} has no CHECKS entry claiming to prove it") for test_path, tree in trees.items(): + if test_path.is_relative_to(root / "tests") and any(isinstance(node, ast.Name) and node.id == "pytest_plugins" for node in ast.walk(tree)): + problems.append(f"GAP unsupported pytest plugin collection surface: {test_path}") if not test_path.is_relative_to(root / "tests") or not ( test_path.name.startswith("test_") or test_path.name.endswith("_test.py") ): @@ -524,4 +533,4 @@ def main(argv: Sequence[str] | None = None) -> int: if __name__ == "__main__": raise SystemExit(main()) -# ratios: loc_comments=421:53 imports_exports=12:4 calls_definitions=208:16 +# ratios: loc_comments=430:53 imports_exports=12:4 calls_definitions=216:16 From 52e617bf3fc10889187cfb4b350fe942f1e53484 Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Sat, 12 Sep 2026 05:45:07 +0000 Subject: [PATCH 43/65] Bind report to collection and probe lifecycle repairs --- docs/work-graphs/repository-plan-report.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/work-graphs/repository-plan-report.json b/docs/work-graphs/repository-plan-report.json index 518371a..7c3f392 100644 --- a/docs/work-graphs/repository-plan-report.json +++ b/docs/work-graphs/repository-plan-report.json @@ -10,7 +10,7 @@ }, "source": { "branch": "repair/ucns-audit-20260911", - "commit": "b131ef1e2d6b89280592f516869c9c606e640836", + "commit": "8409ec8b4d79b7b175273d1eb1f57b5bed942ed1", "generated_at": "2026-09-12", "note": "Describes the exact implementation commit preceding this report-only refresh. Maintenance PR #226 is in final exact-head review; merge remains a separate acceptance observation. No scientific or deployment authority is transferred." }, From b2d5dfa81064363af3877d3f82dc6ad26f88b0a8 Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Sat, 12 Sep 2026 06:09:52 +0000 Subject: [PATCH 44/65] Close collection namespace and nested configuration escapes --- README.md | 6 +++ tests/test_distributions.py | 12 ++--- tests/test_skill_lib_boundary_runner.py | 24 ++++++--- tests/test_skill_lib_contracts.py | 26 +++++++++- tools/run_skill_lib_boundaries.py | 2 +- tools/verify_distributions.py | 9 +++- tools/verify_skill_lib_contracts.py | 65 +++++++++++++++++++++++-- 7 files changed, 121 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 788c3a2..c9139ef 100644 --- a/README.md +++ b/README.md @@ -198,10 +198,16 @@ file/class/function naming. Conftest files and local plugin registration are outside this bounded collection contract. Changed settings, alternative pytest configuration files, and collection-changing `addopts` fail closed. Root `pyproject.toml` is parsed without executing tests; Python 3.10 uses the declared `tomli` test dependency. +Nested pytest configuration is rejected, and selected execution explicitly uses +the audited root config with conftest loading disabled. Collection-time execution +is limited to declarations, pytest decorators, and read-only `Path(__file__)` +source constants. Indirect namespace mutation and unresolved calls, decorators, +or namespace protocols fail closed; setup work belongs in fixtures or checks. Source observation starts before capability probing. Executable Node version probes use the same descendant supervisor as checks; a timeout or leaked child cannot satisfy the capability. Source archives require owner-readable/writable files and owner-readable/writable/searchable directories, without special mode bits. +Explicit archive directories must belong to the hierarchy of allowed files. Receipt output must be outside the bound source tree and is written by atomic replacement so an output hardlink cannot modify a bound input. Checks import the bound checkout with ambient pytest plugins disabled and diff --git a/tests/test_distributions.py b/tests/test_distributions.py index cd5bae8..556eb66 100644 --- a/tests/test_distributions.py +++ b/tests/test_distributions.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=129:10 imports_exports=9:1 calls_definitions=47:2 +# ratios: loc_comments=127:10 imports_exports=9:1 calls_definitions=44:2 # === CHECKS === # id: check_distribution_replay_inputs # proves: distributions_retain_exact_replay_inputs @@ -11,7 +11,6 @@ """Build small archive fixtures; missing evidence must fail independently of Twine.""" -import importlib.util import base64 import csv import hashlib @@ -21,12 +20,9 @@ import zipfile import pytest +from tools import verify_distributions as audit -SPEC = importlib.util.spec_from_file_location("distribution_audit", Path(__file__).parents[1] / "tools/verify_distributions.py") -assert SPEC is not None and SPEC.loader is not None -audit = importlib.util.module_from_spec(SPEC) -SPEC.loader.exec_module(audit) def _archives(root, sdist, wheel, *, omit="", altered="", extra="", sdist_extra="", wheel_omit="", wheel_altered="", metadata_extra="", wheel_flags="true", record_mode="", sdist_directory="", wheel_directory="", sdist_root="ucns-0", sdist_mode=None, directory_mode=0o755): @@ -100,6 +96,8 @@ def test_distribution_replay_inputs_fail_closed(tmp_path: Path) -> None: _archives(root, sdist, wheel) assert audit.verify_distributions(root, sdist, wheel) == [] for options, message in ( + ({"wheel_directory": "unexpected"}, "unexpected directory"), + ({"sdist_directory": "unexpected"}, "unexpected directory"), ({"sdist_mode": 0}, "unusable sdist permissions"), ({"sdist_mode": 0o400}, "unusable sdist permissions"), ({"sdist_mode": 0o4644}, "unusable sdist permissions"), @@ -147,4 +145,4 @@ def test_distribution_replay_inputs_fail_closed(tmp_path: Path) -> None: with zipfile.ZipFile(wheel, "a") as archive, pytest.warns(UserWarning, match="Duplicate"): archive.writestr("ucns/__init__.py", b"duplicate") assert any("duplicate" in problem for problem in audit.verify_distributions(root, sdist, wheel)) -# ratios: loc_comments=129:10 imports_exports=9:1 calls_definitions=47:2 +# ratios: loc_comments=127:10 imports_exports=9:1 calls_definitions=44:2 diff --git a/tests/test_skill_lib_boundary_runner.py b/tests/test_skill_lib_boundary_runner.py index b29cc3c..df32655 100644 --- a/tests/test_skill_lib_boundary_runner.py +++ b/tests/test_skill_lib_boundary_runner.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=99:341 imports_exports=15:18 calls_definitions=147:20 +# ratios: loc_comments=108:342 imports_exports=16:18 calls_definitions=153:20 # === CHECKS === # id: check_boundary_runner_audit_gate # proves: boundary_runner_audits_before_execution @@ -90,14 +90,10 @@ import json from pathlib import Path import sys +from tools import run_skill_lib_boundaries as runner RUNNER_PATH = Path(__file__).parents[1] / "tools" / "run_skill_lib_boundaries.py" -SPEC = importlib.util.spec_from_file_location("run_skill_lib_boundaries", RUNNER_PATH) -assert SPEC is not None and SPEC.loader is not None -runner = importlib.util.module_from_spec(SPEC) -sys.modules[SPEC.name] = runner -SPEC.loader.exec_module(runner) def _repo(tmp_path: Path, functions: str, checks: list[dict[str, str]]) -> Path: @@ -152,6 +148,20 @@ def test_audit_gap_prevents_execution(tmp_path: Path) -> None: assert receipt["status"] == "audit-gap" assert receipt["outcomes"] == [] assert len(receipt["receipt_sha256"]) == 64 + root = _repo(tmp_path / "nested", "def test_fails(): assert False\n", [{"id": "check_fails", "function": "test_fails"}]) + nested = root / "tests/sub" + nested.mkdir() + (root / "tests/test_feature.py").rename(nested / "test_feature.py") + (nested / "pyproject.toml").write_text('[tool.pytest.ini_options]\naddopts = "-p custom_plugin"\n') + marker = tmp_path / "plugin-executed" + (root / "custom_plugin.py").write_text(f"from pathlib import Path\nPath({str(marker)!r}).write_text('executed')\ndef pytest_runtest_setup(item):\n item.obj = lambda: None\n") + receipt = runner.run_boundaries(root) + assert receipt["status"] == "audit-gap" and not receipt["outcomes"], receipt + assert not marker.exists() + # Even below the audit gate, execution must select the audited root config. + outcome = runner._run_check(root, runner._declared_checks(root)[0]) + assert outcome.status == "FAIL", outcome + assert not marker.exists() def test_missing_capability_and_timeout_are_enforced(tmp_path: Path) -> None: @@ -476,4 +486,4 @@ def test_node24_capability_runs_typescript_witness(tmp_path: Path) -> None: assert receipt["outcomes"][0]["status"] == "ERROR", receipt with pytest.raises(ProcessLookupError): os.kill(int(pid_path.read_text()), 0) -# ratios: loc_comments=99:341 imports_exports=15:18 calls_definitions=147:20 +# ratios: loc_comments=108:342 imports_exports=16:18 calls_definitions=153:20 diff --git a/tests/test_skill_lib_contracts.py b/tests/test_skill_lib_contracts.py index 50e7bf2..ebd3b5a 100644 --- a/tests/test_skill_lib_contracts.py +++ b/tests/test_skill_lib_contracts.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=224:60 imports_exports=9:7 calls_definitions=118:7 +# ratios: loc_comments=246:60 imports_exports=9:7 calls_definitions=130:7 # === CHECKS === # id: check_contract_audit_no_exec # proves: contract_audit_is_no_exec @@ -202,6 +202,28 @@ def test_empty_syntax_and_class_coverage_are_not_closed(tmp_path: Path) -> None: config.unlink() ok, problems = audit_repository(root) assert not ok and any("explicit testpaths" in item for item in problems), problems + config.write_text('[tool.pytest.ini_options]\ntestpaths = ["tests"]\n') + nested = root / "tests/sub/pyproject.toml" + nested.parent.mkdir() + nested.write_text('[tool.pytest.ini_options]\naddopts = "-p custom_plugin"\n') + ok, problems = audit_repository(root) + assert not ok and any("nested pytest configuration" in item for item in problems), problems + nested.unlink() + dynamic = root / "tests/test_dynamic.py" + for code in ( + 'def helper(): pass\nglobals()["test_hidden"] = helper\n', + 'def helper(): pass\nglobals().update(test_hidden=helper)\n', + 'def helper(): pass\nhelper.__test__ = True\n', + 'def install(): globals()["test_hidden"] = helper\nvalue = install()\n', + 'def install(function): return function\n@install\ndef helper(): pass\n', + 'def __getattr__(name): return ["custom_plugin"]\n', + ): + dynamic.write_text(code) + ok, problems = audit_repository(root) + assert not ok and any("namespace" in item or "collection-time" in item for item in problems), (code, problems) + dynamic.unlink() + ok, problems = audit_repository(root / "absent") + assert not ok and any("empty" in item for item in problems), problems def test_nested_fences_cannot_hide_an_obligation(tmp_path: Path) -> None: @@ -304,4 +326,4 @@ def test_vendored_typescript_parser_retains_numeric_field_names(tmp_path: Path) broken.write_text("// no declaration\nthrow new Error('must not execute');\n") ok, problems = audit_repository(tmp_path / "repo") assert not ok and any("universal.ts missing MODULE_BUILD" in item for item in problems), problems -# ratios: loc_comments=224:60 imports_exports=9:7 calls_definitions=118:7 +# ratios: loc_comments=246:60 imports_exports=9:7 calls_definitions=130:7 diff --git a/tools/run_skill_lib_boundaries.py b/tools/run_skill_lib_boundaries.py index 7bfe5be..84b8f44 100644 --- a/tools/run_skill_lib_boundaries.py +++ b/tools/run_skill_lib_boundaries.py @@ -365,7 +365,7 @@ def _execute_check(root: Path, check: Entry) -> CheckOutcome: junit_path = Path(temporary) / "outcomes.xml" report_path = Path(temporary) / "outcomes.json" command = (sys.executable, str(BOOTSTRAP), str(root), str(report_path), - "-q", f"{relative_source}::{function}", f"--junitxml={junit_path}", "-o", "xfail_strict=true") + "-q", "-c", str(root / "pyproject.toml"), "--noconftest", f"{relative_source}::{function}", f"--junitxml={junit_path}", "-o", "xfail_strict=true") command = (sys.executable, str(SUPERVISOR), str(timeout), *command[1:]) environment = dict(os.environ) for name in ("PYTHONPATH", "PYTHONHOME", "PYTEST_ADDOPTS", "PYTEST_PLUGINS"): diff --git a/tools/verify_distributions.py b/tools/verify_distributions.py index ee77082..f86221d 100644 --- a/tools/verify_distributions.py +++ b/tools/verify_distributions.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=318:35 imports_exports=17:4 calls_definitions=165:11 +# ratios: loc_comments=323:35 imports_exports=17:4 calls_definitions=169:11 # === MODULE_BUILD === # id: ucns_distribution_audit # module_name: verify_distributions @@ -341,6 +341,11 @@ def verify_distributions(root: Path, sdist: Path, wheel: Path) -> list[str]: except (OSError, ValueError, tarfile.TarError, zipfile.BadZipFile) as error: problems.append(f"{path.name}: {error}") continue + implied_directories = {parent.as_posix() for name in actual for parent in PurePosixPath(name).parents if parent.as_posix() != "."} + if not is_wheel: + implied_directories.add("") # The single enclosing sdist root. + for name in sorted(directories - implied_directories): + problems.append(f"{path.name}: unexpected directory {name}") for name, data in inputs.items(): if name not in actual: problems.append(f"{path.name}: missing {name}") @@ -385,4 +390,4 @@ def main() -> int: if __name__ == "__main__": raise SystemExit(main()) -# ratios: loc_comments=318:35 imports_exports=17:4 calls_definitions=165:11 +# ratios: loc_comments=323:35 imports_exports=17:4 calls_definitions=169:11 diff --git a/tools/verify_skill_lib_contracts.py b/tools/verify_skill_lib_contracts.py index 6c7fb24..b98dac4 100644 --- a/tools/verify_skill_lib_contracts.py +++ b/tools/verify_skill_lib_contracts.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=430:53 imports_exports=12:4 calls_definitions=216:16 +# ratios: loc_comments=484:54 imports_exports=12:4 calls_definitions=251:20 # === MODULE_BUILD === # id: skill_lib_contract_audit # module_name: verify_skill_lib_contracts @@ -319,10 +319,12 @@ def _collection_config_problems(root: Path) -> list[str]: problems = [] alternatives = {"pytest.toml", ".pytest.toml", "pytest.ini", ".pytest.ini", "tox.ini", "setup.cfg"} for base in (root, root / "tests"): - paths = base.iterdir() if base == root else base.rglob("*") - if not base.exists(): + if not base.is_dir(): continue + paths = base.iterdir() if base == root else base.rglob("*") for path in paths: + if path.is_file() and path.name == "pyproject.toml" and path.parent != root: + problems.append(f"GAP nested pytest configuration: {path}") if path.is_file() and path.name == "conftest.py": problems.append(f"GAP unsupported conftest collection/plugin surface: {path}") if path.is_file() and path.name in alternatives: @@ -366,6 +368,60 @@ def _collection_config_problems(root: Path) -> list[str]: return problems +def _collection_surface_problems(tree: ast.Module, path: Path) -> list[str]: + """Reject collection metaprogramming instead of guessing its effects.""" + def surface(node): + yield node + for field, value in ast.iter_fields(node): + if field == "body" and isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.Lambda)): + continue + for child in value if isinstance(value, list) else [value]: + if isinstance(child, ast.AST): + yield from surface(child) + nodes = list(surface(tree)) + imports = {} + for node in tree.body: + if isinstance(node, (ast.Import, ast.ImportFrom)): + for alias in node.names: + imports[alias.asname or alias.name] = (node.module, alias.name) if isinstance(node, ast.ImportFrom) else (alias.name, None) + rebound = {node.id for node in nodes if isinstance(node, ast.Name) and isinstance(node.ctx, ast.Store)} + rebound.update(node.name for node in nodes if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef))) + path_bound = imports.get("Path") == ("pathlib", "Path") and "Path" not in rebound + pytest_bound = imports.get("pytest") == ("pytest", None) and "pytest" not in rebound + def pytest_decorator(reference): + if not pytest_bound or not isinstance(reference, ast.Attribute): + return False + if isinstance(reference.value, ast.Name): + return reference.value.id == "pytest" and reference.attr == "fixture" + parent = reference.value + return isinstance(parent, ast.Attribute) and parent.attr == "mark" and isinstance(parent.value, ast.Name) and parent.value.id == "pytest" and not reference.attr.startswith("_") + def safe_call(node): + spelling = ast.unparse(node.func) + if path_bound and spelling == "Path": + return len(node.args) == 1 and isinstance(node.args[0], ast.Name) and node.args[0].id == "__file__" and not node.keywords + if path_bound and spelling == "Path(__file__).resolve": + return not node.args and not node.keywords + return pytest_decorator(node.func) + problems = [] + for node in nodes: + if isinstance(node, (ast.Assign, ast.AnnAssign, ast.AugAssign, ast.Delete)): + targets = node.targets if isinstance(node, (ast.Assign, ast.Delete)) else [node.target] + if any(isinstance(item, (ast.Subscript, ast.Attribute)) for target in targets for item in ast.walk(target)): + problems.append(f"GAP indirect test-namespace mutation: {path}:{node.lineno}") + if isinstance(node, ast.Call) and not safe_call(node): + problems.append(f"GAP unsupported collection-time call: {path}:{node.lineno}; move execution into fixtures or checks") + if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)): + for decorator in node.decorator_list: + reference = decorator.func if isinstance(decorator, ast.Call) else decorator + if not pytest_decorator(reference): + problems.append(f"GAP unsupported collection-time decorator: {path}:{decorator.lineno}") + if isinstance(node, ast.ClassDef) and node.keywords: + problems.append(f"GAP unsupported class construction keywords: {path}:{node.lineno}") + if node.name in {"__getattr__", "__getattribute__", "__dir__", "__init_subclass__", "__set_name__"}: + problems.append(f"GAP unsupported collection-time namespace protocol: {path}:{node.lineno}") + return problems + + def audit_repository(root: Path) -> Tuple[bool, List[str]]: root = root.resolve() entries: List[Entry] = [] @@ -453,6 +509,7 @@ def audit_repository(root: Path) -> Tuple[bool, List[str]]: test_path.name.startswith("test_") or test_path.name.endswith("_test.py") ): continue + problems.extend(_collection_surface_problems(tree, test_path)) declared_calls = { entry.fields.get("call", "")[len("self::") :] for entry in checks @@ -533,4 +590,4 @@ def main(argv: Sequence[str] | None = None) -> int: if __name__ == "__main__": raise SystemExit(main()) -# ratios: loc_comments=430:53 imports_exports=12:4 calls_definitions=216:16 +# ratios: loc_comments=484:54 imports_exports=12:4 calls_definitions=251:20 From 5a455dd0ec87329f0ba5a65502ca691569a9a740 Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Sat, 12 Sep 2026 06:09:52 +0000 Subject: [PATCH 45/65] Bind maintenance report to isolated collection inputs --- docs/work-graphs/repository-plan-report.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/work-graphs/repository-plan-report.json b/docs/work-graphs/repository-plan-report.json index 7c3f392..b6705fd 100644 --- a/docs/work-graphs/repository-plan-report.json +++ b/docs/work-graphs/repository-plan-report.json @@ -10,7 +10,7 @@ }, "source": { "branch": "repair/ucns-audit-20260911", - "commit": "8409ec8b4d79b7b175273d1eb1f57b5bed942ed1", + "commit": "b2d5dfa81064363af3877d3f82dc6ad26f88b0a8", "generated_at": "2026-09-12", "note": "Describes the exact implementation commit preceding this report-only refresh. Maintenance PR #226 is in final exact-head review; merge remains a separate acceptance observation. No scientific or deployment authority is transferred." }, From b9384752a386b582aa5994ae42af32fbb5903a43 Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Sat, 12 Sep 2026 06:29:54 +0000 Subject: [PATCH 46/65] Reject implicit collection hooks and descriptor-bound tests --- README.md | 4 +++ tests/test_skill_lib_boundary_runner.py | 17 +++++++++++-- tests/test_skill_lib_contracts.py | 23 +++++++++++++++-- tools/verify_skill_lib_contracts.py | 33 ++++++++++++++++++++++--- 4 files changed, 70 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index c9139ef..eaa6d8d 100644 --- a/README.md +++ b/README.md @@ -203,6 +203,10 @@ the audited root config with conftest loading disabled. Collection-time executio is limited to declarations, pytest decorators, and read-only `Path(__file__)` source constants. Indirect namespace mutation and unresolved calls, decorators, or namespace protocols fail closed; setup work belongs in fixtures or checks. +Module/class pytest and xunit hooks are unsupported implicit execution. Class +data bindings must be literal values; imported descriptors and compound class +namespace construction fail closed. Fixture helpers remain supported, including +test-prefixed helper names, but fixture-decorated functions cannot resolve CHECKS. Source observation starts before capability probing. Executable Node version probes use the same descendant supervisor as checks; a timeout or leaked child cannot satisfy the capability. Source archives require owner-readable/writable diff --git a/tests/test_skill_lib_boundary_runner.py b/tests/test_skill_lib_boundary_runner.py index df32655..d5723ae 100644 --- a/tests/test_skill_lib_boundary_runner.py +++ b/tests/test_skill_lib_boundary_runner.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=108:342 imports_exports=16:18 calls_definitions=153:20 +# ratios: loc_comments=121:342 imports_exports=16:18 calls_definitions=163:20 # === CHECKS === # id: check_boundary_runner_audit_gate # proves: boundary_runner_audits_before_execution @@ -162,6 +162,19 @@ def test_audit_gap_prevents_execution(tmp_path: Path) -> None: outcome = runner._run_check(root, runner._declared_checks(root)[0]) assert outcome.status == "FAIL", outcome assert not marker.exists() + hook_body = f"from pathlib import Path\ndef pytest_generate_tests(metafunc):\n Path({str(marker)!r}).write_text('hook ran')\n metafunc.function.__code__ = (lambda: None).__code__\ndef test_fails(): assert False\n" + root = _repo(tmp_path / "module-hook", hook_body, [{"id": "check_fails", "function": "test_fails"}]) + receipt = runner.run_boundaries(root) + assert receipt["status"] == "audit-gap" and not receipt["outcomes"], receipt + assert not marker.exists() + root = _repo(tmp_path / "descriptor", "from descriptor_helper import descriptor\nclass TestInjected:\n injected = descriptor\ndef test_probe(): pass\n", [{"id": "check_probe", "function": "test_probe"}]) + (root / "descriptor_helper.py").write_text(f"from pathlib import Path\nclass Inject:\n def __set_name__(self, owner, name):\n Path({str(marker)!r}).write_text('descriptor ran')\n owner.test_hidden = lambda self: 1 / 0\ndescriptor = Inject()\n") + receipt = runner.run_boundaries(root) + assert receipt["status"] == "audit-gap" and not receipt["outcomes"], receipt + assert not marker.exists() + root = _repo(tmp_path / "fixture-helper", "import pytest\n@pytest.fixture\ndef test_data(): return 1\ndef test_probe(test_data): assert test_data == 1\n", [{"id": "check_probe", "function": "test_probe"}]) + receipt = runner.run_boundaries(root) + assert receipt["status"] == "passed", receipt def test_missing_capability_and_timeout_are_enforced(tmp_path: Path) -> None: @@ -486,4 +499,4 @@ def test_node24_capability_runs_typescript_witness(tmp_path: Path) -> None: assert receipt["outcomes"][0]["status"] == "ERROR", receipt with pytest.raises(ProcessLookupError): os.kill(int(pid_path.read_text()), 0) -# ratios: loc_comments=108:342 imports_exports=16:18 calls_definitions=153:20 +# ratios: loc_comments=121:342 imports_exports=16:18 calls_definitions=163:20 diff --git a/tests/test_skill_lib_contracts.py b/tests/test_skill_lib_contracts.py index ebd3b5a..55ca546 100644 --- a/tests/test_skill_lib_contracts.py +++ b/tests/test_skill_lib_contracts.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=246:60 imports_exports=9:7 calls_definitions=130:7 +# ratios: loc_comments=265:60 imports_exports=9:7 calls_definitions=138:7 # === CHECKS === # id: check_contract_audit_no_exec # proves: contract_audit_is_no_exec @@ -222,6 +222,25 @@ def test_empty_syntax_and_class_coverage_are_not_closed(tmp_path: Path) -> None: ok, problems = audit_repository(root) assert not ok and any("namespace" in item or "collection-time" in item for item in problems), (code, problems) dynamic.unlink() + for code in ( + 'from descriptor_helper import descriptor\nclass TestInjected:\n injected = descriptor\n', + 'class TestInjected:\n from descriptor_helper import descriptor\n', + 'from descriptor_helper import descriptor\nclass Helper:\n injected: object = descriptor\nclass TestInherited(Helper): pass\n', + 'from descriptor_helper import descriptor\nclass TestInjected:\n for injected in [descriptor]: pass\n', + 'def pytest_generate_tests(metafunc): pass\n', + 'def helper(metafunc): pass\npytest_generate_tests = helper\n', + 'from hook_helper import pytest_generate_tests\n', + 'def setup_function(function): pass\n', + 'class TestHooks:\n def pytest_generate_tests(self, metafunc): pass\n', + ): + dynamic.write_text(code) + ok, problems = audit_repository(root) + assert not ok and any("class namespace" in item or "implicit pytest hook" in item for item in problems), (code, problems) + dynamic.unlink() + for index, decorator in enumerate(("@pytest.fixture", "@pytest.fixture()", "@pytest.fixture(name='other')")): + fixture_root = _repo(tmp_path / f"fixture-{index}", "import pytest\n" + decorator + "\ndef test_probe(): assert False\n", [{"id": "check_probe", "function": "test_probe"}]) + ok, problems = audit_repository(fixture_root) + assert not ok and any("check_probe call does not resolve" in item for item in problems), problems ok, problems = audit_repository(root / "absent") assert not ok and any("empty" in item for item in problems), problems @@ -326,4 +345,4 @@ def test_vendored_typescript_parser_retains_numeric_field_names(tmp_path: Path) broken.write_text("// no declaration\nthrow new Error('must not execute');\n") ok, problems = audit_repository(tmp_path / "repo") assert not ok and any("universal.ts missing MODULE_BUILD" in item for item in problems), problems -# ratios: loc_comments=246:60 imports_exports=9:7 calls_definitions=130:7 +# ratios: loc_comments=265:60 imports_exports=9:7 calls_definitions=138:7 diff --git a/tools/verify_skill_lib_contracts.py b/tools/verify_skill_lib_contracts.py index b98dac4..da8de8c 100644 --- a/tools/verify_skill_lib_contracts.py +++ b/tools/verify_skill_lib_contracts.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=484:54 imports_exports=12:4 calls_definitions=251:20 +# ratios: loc_comments=509:54 imports_exports=12:4 calls_definitions=265:21 # === MODULE_BUILD === # id: skill_lib_contract_audit # module_name: verify_skill_lib_contracts @@ -167,12 +167,21 @@ def _target_names(target: ast.AST) -> list[str]: return [] +def _is_fixture(node: ast.FunctionDef | ast.AsyncFunctionDef) -> bool: + for decorator in node.decorator_list: + reference = decorator.func if isinstance(decorator, ast.Call) else decorator + if (isinstance(reference, ast.Attribute) and reference.attr == "fixture" + and isinstance(reference.value, ast.Name) and reference.value.id == "pytest"): + return True + return False + + def _bindings(body: list[ast.stmt]) -> dict[str, str]: """Track direct namespace bindings; callable aliases remain unsupported.""" bindings = {} for node in body: if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)): - bindings[node.name] = "class" if isinstance(node, ast.ClassDef) else "function" + bindings[node.name] = "class" if isinstance(node, ast.ClassDef) else "fixture" if _is_fixture(node) else "function" elif isinstance(node, (ast.Assign, ast.AnnAssign)) and node.value is not None: targets = node.targets if isinstance(node, ast.Assign) else [node.target] kind = "literal" if isinstance(node.value, (ast.Constant, ast.List, ast.Tuple, ast.Set, ast.Dict)) else "unknown" @@ -403,7 +412,14 @@ def safe_call(node): return not node.args and not node.keywords return pytest_decorator(node.func) problems = [] + implicit_hooks = {"setup_module", "teardown_module", "setup_function", "teardown_function", + "setup_class", "teardown_class", "setup_method", "teardown_method", + "setUpModule", "tearDownModule"} for node in nodes: + names = set(_bindings([node])) if isinstance(node, ast.stmt) else set() + names.update(_header_bindings(node, named_only=not isinstance(node, _COMPOUND_STATEMENTS))) + if any(name.startswith("pytest_") or name in implicit_hooks for name in names): + problems.append(f"GAP unsupported implicit pytest hook: {path}:{node.lineno}") if isinstance(node, (ast.Assign, ast.AnnAssign, ast.AugAssign, ast.Delete)): targets = node.targets if isinstance(node, (ast.Assign, ast.Delete)) else [node.target] if any(isinstance(item, (ast.Subscript, ast.Attribute)) for target in targets for item in ast.walk(target)): @@ -417,6 +433,17 @@ def safe_call(node): problems.append(f"GAP unsupported collection-time decorator: {path}:{decorator.lineno}") if isinstance(node, ast.ClassDef) and node.keywords: problems.append(f"GAP unsupported class construction keywords: {path}:{node.lineno}") + if isinstance(node, ast.ClassDef): + for statement in node.body: + if isinstance(statement, (ast.Assign, ast.AnnAssign)): + if statement.value is not None: + try: + ast.literal_eval(statement.value) + except (ValueError, TypeError, SyntaxError): + problems.append(f"GAP unresolved class namespace value (possible descriptor): {path}:{statement.lineno}") + elif not isinstance(statement, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef, ast.Pass)): + if not (isinstance(statement, ast.Expr) and isinstance(statement.value, ast.Constant) and isinstance(statement.value.value, str)): + problems.append(f"GAP unsupported class namespace statement: {path}:{statement.lineno}") if node.name in {"__getattr__", "__getattribute__", "__dir__", "__init_subclass__", "__set_name__"}: problems.append(f"GAP unsupported collection-time namespace protocol: {path}:{node.lineno}") return problems @@ -590,4 +617,4 @@ def main(argv: Sequence[str] | None = None) -> int: if __name__ == "__main__": raise SystemExit(main()) -# ratios: loc_comments=484:54 imports_exports=12:4 calls_definitions=251:20 +# ratios: loc_comments=509:54 imports_exports=12:4 calls_definitions=265:21 From 2fa1954af5713ca503eb6a5d71cc499362d717d8 Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Sat, 12 Sep 2026 06:29:54 +0000 Subject: [PATCH 47/65] Bind maintenance report to explicit check collection --- docs/work-graphs/repository-plan-report.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/work-graphs/repository-plan-report.json b/docs/work-graphs/repository-plan-report.json index b6705fd..4336d6b 100644 --- a/docs/work-graphs/repository-plan-report.json +++ b/docs/work-graphs/repository-plan-report.json @@ -10,7 +10,7 @@ }, "source": { "branch": "repair/ucns-audit-20260911", - "commit": "b2d5dfa81064363af3877d3f82dc6ad26f88b0a8", + "commit": "b9384752a386b582aa5994ae42af32fbb5903a43", "generated_at": "2026-09-12", "note": "Describes the exact implementation commit preceding this report-only refresh. Maintenance PR #226 is in final exact-head review; merge remains a separate acceptance observation. No scientific or deployment authority is transferred." }, From 65ab9b1dee670616c67dc58bfea75608c308cd77 Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Sat, 12 Sep 2026 06:56:28 +0000 Subject: [PATCH 48/65] Bound collection declarations and require complete suite outcomes --- .github/workflows/ci.yml | 10 +++- README.md | 18 ++++-- pyproject.toml | 3 +- tests/test_gonal_boundary_trace.py | 6 +- tests/test_skill_lib_boundary_runner.py | 51 ++++++++++++++-- tests/test_skill_lib_contracts.py | 43 ++++++++++--- tools/_boundary_pytest.py | 30 ++++++++-- tools/verify_skill_lib_contracts.py | 80 ++++++++++++++++++++++--- uv.lock | 2 +- 9 files changed, 207 insertions(+), 36 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e1e25c8..e54b5f9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -62,7 +62,15 @@ jobs: - name: Audit skill-lib contract graph run: .venv/bin/python tools/verify_skill_lib_contracts.py . - name: Run geometry tests - run: .venv/bin/python -m pytest + env: + PYTEST_DISABLE_PLUGIN_AUTOLOAD: '1' + PYTHONDONTWRITEBYTECODE: '1' + run: | + .venv/bin/python - <<'PY' + from pathlib import Path + from tools._boundary_pytest import run_suite + raise SystemExit(run_suite(["tests", "-c", "pyproject.toml", "--noconftest", "--strict-config"], Path.cwd())) + PY - name: Build package run: .venv/bin/python -m build - name: Check distributions diff --git a/README.md b/README.md index eaa6d8d..2936e14 100644 --- a/README.md +++ b/README.md @@ -193,20 +193,26 @@ unavailable instrumentation is an error before check execution. Descendant disco uses the task-children interface when available and otherwise reads procfs parent IDs. Potential test definitions inside module or class control flow fail the declaration audit closed; executable witnesses use direct module-level definitions. -The graph requires explicit `testpaths = ["tests"]` and supports default pytest -file/class/function naming. Conftest files and local plugin registration are +The graph requires explicit `testpaths = ["tests"]` and +`collect_imported_tests = false`, with pytest 9.1 or later, and supports default pytest +file/class/function naming. Unrecognized configuration keys fail closed. +Conftest files and local plugin registration are outside this bounded collection contract. Changed settings, alternative pytest configuration files, and collection-changing `addopts` fail closed. Root `pyproject.toml` is parsed without executing tests; Python 3.10 uses the declared `tomli` test dependency. Nested pytest configuration is rejected, and selected execution explicitly uses the audited root config with conftest loading disabled. Collection-time execution -is limited to declarations, pytest decorators, and read-only `Path(__file__)` -source constants. Indirect namespace mutation and unresolved calls, decorators, +is limited to declarations, literal data, pytest decorators with literal arguments, +and read-only `Path(__file__)` source constants. Callable decorator options and +string skip/xfail conditions are unsupported. Compound annotations require +`from __future__ import annotations`. Indirect namespace mutation and unresolved calls, decorators, or namespace protocols fail closed; setup work belongs in fixtures or checks. -Module/class pytest and xunit hooks are unsupported implicit execution. Class -data bindings must be literal values; imported descriptors and compound class +Module/class pytest marks and pytest/xunit hooks are unsupported implicit execution. Class +data bindings must be literal values; imported descriptors, unresolved bases, nested classes, and compound class namespace construction fail closed. Fixture helpers remain supported, including test-prefixed helper names, but fixture-decorated functions cannot resolve CHECKS. +CI runs the complete suite through the outcome observer: collection skips/errors, +runtime skips, xfail, XPASS, and empty execution fail the suite gate. Source observation starts before capability probing. Executable Node version probes use the same descendant supervisor as checks; a timeout or leaked child cannot satisfy the capability. Source archives require owner-readable/writable diff --git a/pyproject.toml b/pyproject.toml index 3c908e6..d68221d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,7 +22,7 @@ classifiers = [ [project.optional-dependencies] test = [ - "pytest>=8", + "pytest>=9.1", "tomli>=2; python_version < '3.11'", "mpmath>=1.3,<2", "sympy>=1.12,<2", @@ -41,4 +41,5 @@ where = ["src"] [tool.pytest.ini_options] testpaths = ["tests"] +collect_imported_tests = false addopts = "-q" diff --git a/tests/test_gonal_boundary_trace.py b/tests/test_gonal_boundary_trace.py index 95a5428..ee1be47 100644 --- a/tests/test_gonal_boundary_trace.py +++ b/tests/test_gonal_boundary_trace.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=100:51 imports_exports=6:6 calls_definitions=55:7 +# ratios: loc_comments=101:51 imports_exports=7:6 calls_definitions=55:7 # === CHECKS === # id: check_gonal_boundary_trace_samples_circle_wave_mode_exactly # proves: gonal_boundary_trace_samples_circle_wave_mode_exactly @@ -49,6 +49,8 @@ # cleanup: none # === END CHECKS === +from __future__ import annotations + from dataclasses import replace from fractions import Fraction from pathlib import Path @@ -178,4 +180,4 @@ def test_trace_is_candidate_scoped_in_canon() -> None: assert "candidate" in section.lower() assert "not ratified" in section.lower() -# ratios: loc_comments=100:51 imports_exports=6:6 calls_definitions=55:7 +# ratios: loc_comments=101:51 imports_exports=7:6 calls_definitions=55:7 diff --git a/tests/test_skill_lib_boundary_runner.py b/tests/test_skill_lib_boundary_runner.py index d5723ae..c7d554c 100644 --- a/tests/test_skill_lib_boundary_runner.py +++ b/tests/test_skill_lib_boundary_runner.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=121:342 imports_exports=16:18 calls_definitions=163:20 +# ratios: loc_comments=149:351 imports_exports=19:18 calls_definitions=181:20 # === CHECKS === # id: check_boundary_runner_audit_gate # proves: boundary_runner_audits_before_execution @@ -101,7 +101,7 @@ def _repo(tmp_path: Path, functions: str, checks: list[dict[str, str]]) -> Path: (root / "src" / "pkg").mkdir(parents=True) (root / "tools").mkdir() (root / "tests").mkdir() - (root / "pyproject.toml").write_text('[tool.pytest.ini_options]\ntestpaths = ["tests"]\n') + (root / "pyproject.toml").write_text('[tool.pytest.ini_options]\ncollect_imported_tests = false\ntestpaths = ["tests"]\n') contracts: list[str] = [] check_lines: list[str] = [] for index, check in enumerate(checks): @@ -152,7 +152,7 @@ def test_audit_gap_prevents_execution(tmp_path: Path) -> None: nested = root / "tests/sub" nested.mkdir() (root / "tests/test_feature.py").rename(nested / "test_feature.py") - (nested / "pyproject.toml").write_text('[tool.pytest.ini_options]\naddopts = "-p custom_plugin"\n') + (nested / "pyproject.toml").write_text('[tool.pytest.ini_options]\ncollect_imported_tests = false\naddopts = "-p custom_plugin"\n') marker = tmp_path / "plugin-executed" (root / "custom_plugin.py").write_text(f"from pathlib import Path\nPath({str(marker)!r}).write_text('executed')\ndef pytest_runtest_setup(item):\n item.obj = lambda: None\n") receipt = runner.run_boundaries(root) @@ -175,6 +175,45 @@ def test_audit_gap_prevents_execution(tmp_path: Path) -> None: root = _repo(tmp_path / "fixture-helper", "import pytest\n@pytest.fixture\ndef test_data(): return 1\ndef test_probe(test_data): assert test_data == 1\n", [{"id": "check_probe", "function": "test_probe"}]) receipt = runner.run_boundaries(root) assert receipt["status"] == "passed", receipt + callback_body = f"import pytest\nfrom pathlib import Path\ndef alter(value):\n Path({str(marker)!r}).write_text('callback ran')\n test_fails.__code__ = (lambda sample: None).__code__\n return str(value)\n@pytest.fixture(params=[1], ids=alter)\ndef sample(request): return request.param\ndef test_fails(sample): assert False\n" + root = _repo(tmp_path / "decorator-callback", callback_body, [{"id": "check_fails", "function": "test_fails"}]) + receipt = runner.run_boundaries(root) + assert receipt["status"] == "audit-gap" and not receipt["outcomes"], receipt + assert not marker.exists() + + +# === CHECKS === +# id: check_geometry_suite_nonempty_pass +# proves: geometry_suite_requires_nonempty_pass +# call: self::test_geometry_suite_rejects_nonpasses +# requires: python3 +# timeout: 30 +# mutates: filesystem +# cleanup: tempdir_teardown +# === END CHECKS === +def test_geometry_suite_rejects_nonpasses(tmp_path: Path) -> None: + import os + import subprocess + import sys + cases = ( + ("pass", "def test_probe(): pass\n", True), + ("skip", "import pytest\ndef test_probe(): pytest.skip('unobserved')\n", False), + ("xfail", "import pytest\n@pytest.mark.xfail\ndef test_probe(): assert False\n", False), + ("xpass", "import pytest\n@pytest.mark.xfail(strict=False)\ndef test_probe(): pass\n", False), + ("empty", "# no executable checks\n", False), + ("collection-skip", "import pytest\npytest.skip('unobserved', allow_module_level=True)\n", False), + ("collection-error", "raise RuntimeError('broken collection')\n", False), + ("module-mark", "import pytest\npytestmark = pytest.mark.skip\ndef test_probe(): assert False\n", False), + ) + script = "import os,sys; from pathlib import Path; sys.path.insert(0,sys.argv[1]); from tools._boundary_pytest import run_suite; root=Path(sys.argv[2]); os.chdir(root); raise SystemExit(run_suite(['tests','-c','pyproject.toml','--noconftest','--strict-config'],root))" + environment = {key: value for key, value in os.environ.items() if key not in {"PYTHONPATH", "PYTHONHOME", "PYTEST_ADDOPTS", "PYTEST_PLUGINS"}} + environment["PYTEST_DISABLE_PLUGIN_AUTOLOAD"] = "1" + for label, body, expected_pass in cases: + root = _repo(tmp_path / label, body, []) + if label.startswith("collection-"): + (root / "tests/test_other.py").write_text("def test_other(): pass\n") + result = subprocess.run([sys.executable, "-c", script, str(RUNNER_PATH.parents[1]), str(root)], env=environment, capture_output=True, text=True) + assert (result.returncode == 0) is expected_pass, (label, result.stdout, result.stderr) def test_missing_capability_and_timeout_are_enforced(tmp_path: Path) -> None: @@ -215,9 +254,9 @@ def test_fail(): assert False def test_error(): raise RuntimeError("AssertionError mentioned by a broken harness") -class ContractViolation(AssertionError): - __test__ = False def test_subclass(): + class ContractViolation(AssertionError): + pass raise ContractViolation("broken") """ checks = [ @@ -499,4 +538,4 @@ def test_node24_capability_runs_typescript_witness(tmp_path: Path) -> None: assert receipt["outcomes"][0]["status"] == "ERROR", receipt with pytest.raises(ProcessLookupError): os.kill(int(pid_path.read_text()), 0) -# ratios: loc_comments=121:342 imports_exports=16:18 calls_definitions=163:20 +# ratios: loc_comments=149:351 imports_exports=19:18 calls_definitions=181:20 diff --git a/tests/test_skill_lib_contracts.py b/tests/test_skill_lib_contracts.py index 55ca546..010e79c 100644 --- a/tests/test_skill_lib_contracts.py +++ b/tests/test_skill_lib_contracts.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=265:60 imports_exports=9:7 calls_definitions=138:7 +# ratios: loc_comments=292:60 imports_exports=9:7 calls_definitions=150:7 # === CHECKS === # id: check_contract_audit_no_exec # proves: contract_audit_is_no_exec @@ -170,10 +170,10 @@ def test_empty_syntax_and_class_coverage_are_not_closed(tmp_path: Path) -> None: 'testpaths = ["integration"]', 'addopts = "-o python_files=spec_*.py"', ): - config.write_text('[tool.pytest.ini_options]\n' + settings + '\n') + config.write_text('[tool.pytest.ini_options]\ncollect_imported_tests = false\n' + settings + '\n') ok, problems = audit_repository(root) assert not ok and any("pytest" in item for item in problems), (settings, problems) - config.write_text('[tool.pytest.ini_options]\npython_files = ["test_*.py", "*_test.py"]\npython_classes = ["Test"]\npython_functions = ["test"]\ntestpaths = ["tests"]\naddopts = "-q"\n') + config.write_text('[tool.pytest.ini_options]\ncollect_imported_tests = false\npython_files = ["test_*.py", "*_test.py"]\npython_classes = ["Test"]\npython_functions = ["test"]\ntestpaths = ["tests"]\naddopts = "-q"\n') ok, problems = audit_repository(root) assert ok, problems for name in ("pytest.ini", ".pytest.ini", "pytest.toml", ".pytest.toml", "tox.ini", "setup.cfg"): @@ -196,16 +196,16 @@ def test_empty_syntax_and_class_coverage_are_not_closed(tmp_path: Path) -> None: ok, problems = audit_repository(root) assert not ok and any("pytest plugin collection" in item for item in problems), problems plugin.unlink() - config.write_text('[tool.pytest.ini_options]\naddopts = "-q"\n') + config.write_text('[tool.pytest.ini_options]\ncollect_imported_tests = false\naddopts = "-q"\n') ok, problems = audit_repository(root) assert not ok and any("explicit testpaths" in item for item in problems), problems config.unlink() ok, problems = audit_repository(root) assert not ok and any("explicit testpaths" in item for item in problems), problems - config.write_text('[tool.pytest.ini_options]\ntestpaths = ["tests"]\n') + config.write_text('[tool.pytest.ini_options]\ncollect_imported_tests = false\ntestpaths = ["tests"]\n') nested = root / "tests/sub/pyproject.toml" nested.parent.mkdir() - nested.write_text('[tool.pytest.ini_options]\naddopts = "-p custom_plugin"\n') + nested.write_text('[tool.pytest.ini_options]\ncollect_imported_tests = false\naddopts = "-p custom_plugin"\n') ok, problems = audit_repository(root) assert not ok and any("nested pytest configuration" in item for item in problems), problems nested.unlink() @@ -232,11 +232,38 @@ def test_empty_syntax_and_class_coverage_are_not_closed(tmp_path: Path) -> None: 'from hook_helper import pytest_generate_tests\n', 'def setup_function(function): pass\n', 'class TestHooks:\n def pytest_generate_tests(self, metafunc): pass\n', + 'from descriptor_helper import Base\nclass TestInjected:\n class Nested(Base): pass\n', + 'from descriptor_helper import Base\nclass Helper(Base):\n __test__ = False\n', + 'from descriptor_helper import Base as object\nclass TestInjected(object): pass\n', ): dynamic.write_text(code) ok, problems = audit_repository(root) - assert not ok and any("class namespace" in item or "implicit pytest hook" in item for item in problems), (code, problems) + assert not ok and any("class namespace" in item or "implicit pytest hook" in item or "class base" in item for item in problems), (code, problems) dynamic.unlink() + for code in ( + 'import pytest\npytestmark = pytest.mark.skip\n', + 'import pytest\npytestmark = [pytest.mark.xfail]\n', + 'from marker_helper import pytestmark\n', + 'import pytest\ndef alter(value): return value\n@pytest.fixture(params=[1], ids=alter)\ndef helper(request): return request.param\n', + 'import pytest\n@pytest.mark.parametrize("value", [1], ids=lambda value: str(value))\ndef helper(value): pass\n', + 'import pytest\n@pytest.mark.skipif("execute_a_condition()")\ndef helper(): pass\n', + 'from descriptor_helper import descriptor\nvalue = descriptor.attribute\n', + 'from descriptor_helper import descriptor\nassert descriptor.attribute\n', + ): + dynamic.write_text(code) + ok, problems = audit_repository(root) + assert not ok and any("collection-time" in item or "implicit pytest hook" in item for item in problems), (code, problems) + dynamic.unlink() + baseline = config.read_text() + for replacement in ('collect_imported_tests = true\n', 'collect_imported_tests = 0\n', ''): + config.write_text(baseline.replace('collect_imported_tests = false\n', replacement)) + ok, problems = audit_repository(root) + assert not ok and any("explicit collect_imported_tests" in item for item in problems), problems + for option in ('pythonpath = ["outside"]', 'future_collection_option = true'): + config.write_text(baseline + option + '\n') + ok, problems = audit_repository(root) + assert not ok and any("unsupported pytest collection settings" in item for item in problems), problems + config.write_text(baseline) for index, decorator in enumerate(("@pytest.fixture", "@pytest.fixture()", "@pytest.fixture(name='other')")): fixture_root = _repo(tmp_path / f"fixture-{index}", "import pytest\n" + decorator + "\ndef test_probe(): assert False\n", [{"id": "check_probe", "function": "test_probe"}]) ok, problems = audit_repository(fixture_root) @@ -345,4 +372,4 @@ def test_vendored_typescript_parser_retains_numeric_field_names(tmp_path: Path) broken.write_text("// no declaration\nthrow new Error('must not execute');\n") ok, problems = audit_repository(tmp_path / "repo") assert not ok and any("universal.ts missing MODULE_BUILD" in item for item in problems), problems -# ratios: loc_comments=265:60 imports_exports=9:7 calls_definitions=138:7 +# ratios: loc_comments=292:60 imports_exports=9:7 calls_definitions=150:7 diff --git a/tools/_boundary_pytest.py b/tools/_boundary_pytest.py index 1ea5586..57b6018 100644 --- a/tools/_boundary_pytest.py +++ b/tools/_boundary_pytest.py @@ -1,11 +1,11 @@ -# ratios: loc_comments=66:28 imports_exports=5:2 calls_definitions=25:4 +# ratios: loc_comments=77:35 imports_exports=5:3 calls_definitions=31:6 # === MODULE_BUILD === # id: boundary_pytest_observer # module_name: _boundary_pytest # module_kind: instrument # summary: observes actual pytest exceptions, xfail outcomes, and imported source origins for UCNS receipts # owner: Erin Spencer -# public_surface: none; launched by run_skill_lib_boundaries +# public_surface: run_suite; selected-check bootstrap launched by run_skill_lib_boundaries # internal_surface: Observer, main # auth_boundary: none # storage_boundary: write @@ -22,9 +22,17 @@ # given: a selected pytest boundary runs through this bootstrap # then: assertion subclasses fail, unexpected exceptions error, XPASS cannot pass, imported local package origins must match the bound tree # class: evidence +# +# id: geometry_suite_requires_nonempty_pass +# given: the full geometry suite runs through run_suite +# then: empty, skipped, xfailed, XPASS, failed, or collection-error evidence cannot produce exit status zero +# class: evidence # === END CONTRACTS === -"""Internal bootstrap. Usage: launched only by tools/run_skill_lib_boundaries.py. +"""Pytest evidence observer and selected-check bootstrap. + +CI usage: ``run_suite(["tests", "-c", "pyproject.toml", "--noconftest"], Path.cwd())``. +Selected-check CLI usage remains owned by tools/run_skill_lib_boundaries.py. Observations describe executed checks, not theorem standing or a hostile-code sandbox. Ambient pytest plugins and PYTHONPATH are excluded by the parent. @@ -44,6 +52,12 @@ def __init__(self, root: Path): self.calls: list[str] = [] self.other: list[str] = [] + def pytest_collectreport(self, report): + if report.skipped: + self.other.append("SKIP") + elif report.failed: + self.other.append("ERROR") + @pytest.hookimpl(hookwrapper=True, tryfirst=True) def pytest_runtest_makereport(self, item, call): report = (yield).get_result() @@ -66,6 +80,14 @@ def pytest_runtest_makereport(self, item, call): self.other.append(status) +def run_suite(arguments: list[str], root: Path) -> int: + observer = Observer(root) + result = int(pytest.main(arguments, plugins=[observer])) + if result: + return result + return 0 if observer.calls and set(observer.calls) == {"PASS"} and not observer.other else 1 + + def main() -> int: root = Path(sys.argv[1]).resolve() report_path = Path(sys.argv[2]) @@ -104,4 +126,4 @@ def main() -> int: if __name__ == "__main__": raise SystemExit(main()) -# ratios: loc_comments=66:28 imports_exports=5:2 calls_definitions=25:4 +# ratios: loc_comments=77:35 imports_exports=5:3 calls_definitions=31:6 diff --git a/tools/verify_skill_lib_contracts.py b/tools/verify_skill_lib_contracts.py index da8de8c..5ffdbf0 100644 --- a/tools/verify_skill_lib_contracts.py +++ b/tools/verify_skill_lib_contracts.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=509:54 imports_exports=12:4 calls_definitions=265:21 +# ratios: loc_comments=575:54 imports_exports=12:4 calls_definitions=309:23 # === MODULE_BUILD === # id: skill_lib_contract_audit # module_name: verify_skill_lib_contracts @@ -363,6 +363,11 @@ def _collection_config_problems(root: Path) -> list[str]: if config.get("required_plugins"): problems.append("GAP unsupported pytest plugin collection configuration") defaults = {"python_files": ["test_*.py", "*_test.py"], "python_classes": ["Test"], "python_functions": ["test"], "testpaths": ["tests"]} + unknown = set(config) - set(defaults) - {"addopts", "collect_imported_tests"} + if unknown: + problems.append(f"GAP unsupported pytest collection settings: {', '.join(sorted(unknown))}") + if config.get("collect_imported_tests") is not False: + problems.append("GAP pytest collection requires explicit collect_imported_tests = false") for name, expected in defaults.items(): if name in config: actual = config[name].split() if isinstance(config[name], str) else config[name] @@ -395,8 +400,13 @@ def surface(node): imports[alias.asname or alias.name] = (node.module, alias.name) if isinstance(node, ast.ImportFrom) else (alias.name, None) rebound = {node.id for node in nodes if isinstance(node, ast.Name) and isinstance(node.ctx, ast.Store)} rebound.update(node.name for node in nodes if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef))) - path_bound = imports.get("Path") == ("pathlib", "Path") and "Path" not in rebound - pytest_bound = imports.get("pytest") == ("pytest", None) and "pytest" not in rebound + binding_counts = {} + for node in nodes: + if isinstance(node, ast.stmt): + for name in _bindings([node]): + binding_counts[name] = binding_counts.get(name, 0) + 1 + path_bound = imports.get("Path") == ("pathlib", "Path") and "Path" not in rebound and binding_counts.get("Path") == 1 and "__file__" not in binding_counts + pytest_bound = imports.get("pytest") == ("pytest", None) and "pytest" not in rebound and binding_counts.get("pytest") == 1 def pytest_decorator(reference): if not pytest_bound or not isinstance(reference, ast.Attribute): return False @@ -410,23 +420,77 @@ def safe_call(node): return len(node.args) == 1 and isinstance(node.args[0], ast.Name) and node.args[0].id == "__file__" and not node.keywords if path_bound and spelling == "Path(__file__).resolve": return not node.args and not node.keywords - return pytest_decorator(node.func) + if not pytest_decorator(node.func): + return False + try: + arguments = [ast.literal_eval(value) for value in node.args] + keywords = {item.arg: ast.literal_eval(item.value) for item in node.keywords} + except (ValueError, TypeError, SyntaxError): + return False + if None in keywords: + return False + if node.func.attr in {"skipif", "xfail"}: + conditions = [*arguments, *([keywords["condition"]] if "condition" in keywords else [])] + if any(type(value) is not bool for value in conditions): + return False # String conditions are executable expressions. + return True + def literal(node): + try: + ast.literal_eval(node) + return True + except (ValueError, TypeError, SyntaxError): + return False + path_constants = set() + def source_path(node): + if isinstance(node, ast.Name): + return node.id in path_constants + if isinstance(node, ast.Call): + return path_bound and ast.unparse(node.func) in {"Path", "Path(__file__).resolve"} and safe_call(node) + if isinstance(node, ast.Attribute): + return node.attr == "parent" and source_path(node.value) + if isinstance(node, ast.Subscript) and isinstance(node.value, ast.Attribute) and node.value.attr == "parents": + return source_path(node.value.value) and isinstance(node.slice, ast.Constant) and type(node.slice.value) is int and node.slice.value >= 0 + return (isinstance(node, ast.BinOp) and isinstance(node.op, ast.Div) + and source_path(node.left) and isinstance(node.right, ast.Constant) and isinstance(node.right.value, str)) problems = [] + for statement in tree.body: + if isinstance(statement, (ast.Assign, ast.AnnAssign)) and statement.value is not None: + targets = statement.targets if isinstance(statement, ast.Assign) else [statement.target] + if source_path(statement.value): + path_constants.update(name for target in targets for name in _target_names(target) if binding_counts.get(name) == 1) + classes = {node.name: node for node in tree.body if isinstance(node, ast.ClassDef)} + postponed_annotations = any(isinstance(node, ast.ImportFrom) and node.module == "__future__" and any(alias.name == "annotations" for alias in node.names) for node in tree.body) implicit_hooks = {"setup_module", "teardown_module", "setup_function", "teardown_function", "setup_class", "teardown_class", "setup_method", "teardown_method", "setUpModule", "tearDownModule"} for node in nodes: + if isinstance(node, ast.stmt) and not isinstance(node, (ast.Import, ast.ImportFrom, ast.Assign, ast.AnnAssign, ast.Expr, ast.Pass, ast.If, ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)): + problems.append(f"GAP unsupported collection-time statement: {path}:{node.lineno}") + if isinstance(node, ast.AnnAssign) and not postponed_annotations and not isinstance(node.annotation, (ast.Name, ast.Constant)): + problems.append(f"GAP deferred annotations required for compound type expressions: {path}:{node.lineno}") names = set(_bindings([node])) if isinstance(node, ast.stmt) else set() names.update(_header_bindings(node, named_only=not isinstance(node, _COMPOUND_STATEMENTS))) - if any(name.startswith("pytest_") or name in implicit_hooks for name in names): + if any(name.startswith("pytest_") or name == "pytestmark" or name in implicit_hooks for name in names): problems.append(f"GAP unsupported implicit pytest hook: {path}:{node.lineno}") if isinstance(node, (ast.Assign, ast.AnnAssign, ast.AugAssign, ast.Delete)): targets = node.targets if isinstance(node, (ast.Assign, ast.Delete)) else [node.target] if any(isinstance(item, (ast.Subscript, ast.Attribute)) for target in targets for item in ast.walk(target)): problems.append(f"GAP indirect test-namespace mutation: {path}:{node.lineno}") + if isinstance(node, (ast.Assign, ast.AnnAssign)) and node.value is not None and not (literal(node.value) or source_path(node.value)): + problems.append(f"GAP unresolved collection-time value: {path}:{node.lineno}; use literal data or source-path constants") + if isinstance(node, ast.Expr) and not literal(node.value): + problems.append(f"GAP unsupported collection-time expression: {path}:{node.lineno}") + if isinstance(node, _COMPOUND_STATEMENTS) and not (isinstance(node, ast.If) and isinstance(node.test, ast.Constant) and type(node.test.value) is bool): + problems.append(f"GAP unsupported collection-time control flow: {path}:{node.lineno}") if isinstance(node, ast.Call) and not safe_call(node): problems.append(f"GAP unsupported collection-time call: {path}:{node.lineno}; move execution into fixtures or checks") if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)): + if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)): + if any(not (literal(value) or source_path(value)) for value in [*node.args.defaults, *(value for value in node.args.kw_defaults if value is not None)]): + problems.append(f"GAP unresolved collection-time default: {path}:{node.lineno}") + annotations = [node.returns, *(argument.annotation for argument in [*node.args.posonlyargs, *node.args.args, *node.args.kwonlyargs, *([node.args.vararg] if node.args.vararg else []), *([node.args.kwarg] if node.args.kwarg else [])])] + if not postponed_annotations and any(value is not None and not isinstance(value, (ast.Name, ast.Constant)) for value in annotations): + problems.append(f"GAP deferred annotations required for compound type expressions: {path}:{node.lineno}") for decorator in node.decorator_list: reference = decorator.func if isinstance(decorator, ast.Call) else decorator if not pytest_decorator(reference): @@ -434,6 +498,8 @@ def safe_call(node): if isinstance(node, ast.ClassDef) and node.keywords: problems.append(f"GAP unsupported class construction keywords: {path}:{node.lineno}") if isinstance(node, ast.ClassDef): + if _class_mro(node.name, classes) is None or any(isinstance(base, ast.Name) and base.id == "object" and "object" in binding_counts for base in node.bases): + problems.append(f"GAP unresolved collection-time class base: {path}:{node.lineno}") for statement in node.body: if isinstance(statement, (ast.Assign, ast.AnnAssign)): if statement.value is not None: @@ -441,7 +507,7 @@ def safe_call(node): ast.literal_eval(statement.value) except (ValueError, TypeError, SyntaxError): problems.append(f"GAP unresolved class namespace value (possible descriptor): {path}:{statement.lineno}") - elif not isinstance(statement, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef, ast.Pass)): + elif not isinstance(statement, (ast.FunctionDef, ast.AsyncFunctionDef, ast.Pass)): if not (isinstance(statement, ast.Expr) and isinstance(statement.value, ast.Constant) and isinstance(statement.value.value, str)): problems.append(f"GAP unsupported class namespace statement: {path}:{statement.lineno}") if node.name in {"__getattr__", "__getattribute__", "__dir__", "__init_subclass__", "__set_name__"}: @@ -617,4 +683,4 @@ def main(argv: Sequence[str] | None = None) -> int: if __name__ == "__main__": raise SystemExit(main()) -# ratios: loc_comments=509:54 imports_exports=12:4 calls_definitions=265:21 +# ratios: loc_comments=575:54 imports_exports=12:4 calls_definitions=309:23 diff --git a/uv.lock b/uv.lock index a06c1f4..bb30689 100644 --- a/uv.lock +++ b/uv.lock @@ -1011,7 +1011,7 @@ requires-dist = [ { name = "mpmath", marker = "extra == 'research'", specifier = ">=1.3,<2" }, { name = "mpmath", marker = "extra == 'test'", specifier = ">=1.3,<2" }, { name = "numpy", specifier = ">=1.24" }, - { name = "pytest", marker = "extra == 'test'", specifier = ">=8" }, + { name = "pytest", marker = "extra == 'test'", specifier = ">=9.1" }, { name = "sympy", marker = "extra == 'research'", specifier = ">=1.12,<2" }, { name = "sympy", marker = "extra == 'test'", specifier = ">=1.12,<2" }, { name = "tomli", marker = "python_full_version < '3.11' and extra == 'build'", specifier = ">=2" }, From f8909bb902d2db25919704a58d1e4a155f81ff95 Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Sat, 12 Sep 2026 06:56:28 +0000 Subject: [PATCH 49/65] Bind maintenance report to complete suite evidence --- docs/work-graphs/repository-plan-report.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/work-graphs/repository-plan-report.json b/docs/work-graphs/repository-plan-report.json index 4336d6b..ede3f02 100644 --- a/docs/work-graphs/repository-plan-report.json +++ b/docs/work-graphs/repository-plan-report.json @@ -10,7 +10,7 @@ }, "source": { "branch": "repair/ucns-audit-20260911", - "commit": "b9384752a386b582aa5994ae42af32fbb5903a43", + "commit": "65ab9b1dee670616c67dc58bfea75608c308cd77", "generated_at": "2026-09-12", "note": "Describes the exact implementation commit preceding this report-only refresh. Maintenance PR #226 is in final exact-head review; merge remains a separate acceptance observation. No scientific or deployment authority is transferred." }, From c9450d9e1d705cd482460c43aee67f1e62318fbf Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Sat, 12 Sep 2026 07:12:59 +0000 Subject: [PATCH 50/65] Reject imported collection opt-outs for declared checks --- README.md | 1 + tests/test_skill_lib_boundary_runner.py | 10 ++++++++-- tests/test_skill_lib_contracts.py | 8 ++++++-- tools/verify_skill_lib_contracts.py | 8 +++++--- 4 files changed, 20 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 2936e14..86f4b90 100644 --- a/README.md +++ b/README.md @@ -211,6 +211,7 @@ Module/class pytest marks and pytest/xunit hooks are unsupported implicit execut data bindings must be literal values; imported descriptors, unresolved bases, nested classes, and compound class namespace construction fail closed. Fixture helpers remain supported, including test-prefixed helper names, but fixture-decorated functions cannot resolve CHECKS. +Imported `__test__` bindings are unresolved collection opt-outs and fail closed. CI runs the complete suite through the outcome observer: collection skips/errors, runtime skips, xfail, XPASS, and empty execution fail the suite gate. Source observation starts before capability probing. Executable Node version diff --git a/tests/test_skill_lib_boundary_runner.py b/tests/test_skill_lib_boundary_runner.py index c7d554c..1ca5785 100644 --- a/tests/test_skill_lib_boundary_runner.py +++ b/tests/test_skill_lib_boundary_runner.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=149:351 imports_exports=19:18 calls_definitions=181:20 +# ratios: loc_comments=155:351 imports_exports=19:18 calls_definitions=186:20 # === CHECKS === # id: check_boundary_runner_audit_gate # proves: boundary_runner_audits_before_execution @@ -180,6 +180,12 @@ def test_audit_gap_prevents_execution(tmp_path: Path) -> None: receipt = runner.run_boundaries(root) assert receipt["status"] == "audit-gap" and not receipt["outcomes"], receipt assert not marker.exists() + root = _repo(tmp_path / "import-optout", "from helper import __test__\ndef test_fails(): assert False\n", [{"id": "check_fails", "function": "test_fails"}]) + (root / "helper.py").write_text(f"from pathlib import Path\nPath({str(marker)!r}).write_text('import ran')\n__test__ = False\n") + (root / "tests/test_other.py").write_text("# === CHECKS ===\n# id: check_other\n# proves: contract_0\n# call: self::test_other\n# timeout: 5\n# mutates: none\n# cleanup: none\n# === END CHECKS ===\ndef test_other(): pass\n") + receipt = runner.run_boundaries(root) + assert receipt["status"] == "audit-gap" and not receipt["outcomes"], receipt + assert not marker.exists() # === CHECKS === @@ -538,4 +544,4 @@ def test_node24_capability_runs_typescript_witness(tmp_path: Path) -> None: assert receipt["outcomes"][0]["status"] == "ERROR", receipt with pytest.raises(ProcessLookupError): os.kill(int(pid_path.read_text()), 0) -# ratios: loc_comments=149:351 imports_exports=19:18 calls_definitions=181:20 +# ratios: loc_comments=155:351 imports_exports=19:18 calls_definitions=186:20 diff --git a/tests/test_skill_lib_contracts.py b/tests/test_skill_lib_contracts.py index 010e79c..2e53c26 100644 --- a/tests/test_skill_lib_contracts.py +++ b/tests/test_skill_lib_contracts.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=292:60 imports_exports=9:7 calls_definitions=150:7 +# ratios: loc_comments=296:60 imports_exports=9:7 calls_definitions=154:7 # === CHECKS === # id: check_contract_audit_no_exec # proves: contract_audit_is_no_exec @@ -268,6 +268,10 @@ def test_empty_syntax_and_class_coverage_are_not_closed(tmp_path: Path) -> None: fixture_root = _repo(tmp_path / f"fixture-{index}", "import pytest\n" + decorator + "\ndef test_probe(): assert False\n", [{"id": "check_probe", "function": "test_probe"}]) ok, problems = audit_repository(fixture_root) assert not ok and any("check_probe call does not resolve" in item for item in problems), problems + for index, declaration in enumerate(("from helper import __test__", "from helper import disabled as __test__", "import helper as __test__")): + import_root = _repo(tmp_path / f"import-optout-{index}", declaration + "\ndef test_probe(): assert False\n", [{"id": "check_probe", "function": "test_probe"}]) + ok, problems = audit_repository(import_root) + assert not ok and any("dynamic test-module opt-out" in item for item in problems), problems ok, problems = audit_repository(root / "absent") assert not ok and any("empty" in item for item in problems), problems @@ -372,4 +376,4 @@ def test_vendored_typescript_parser_retains_numeric_field_names(tmp_path: Path) broken.write_text("// no declaration\nthrow new Error('must not execute');\n") ok, problems = audit_repository(tmp_path / "repo") assert not ok and any("universal.ts missing MODULE_BUILD" in item for item in problems), problems -# ratios: loc_comments=292:60 imports_exports=9:7 calls_definitions=150:7 +# ratios: loc_comments=296:60 imports_exports=9:7 calls_definitions=154:7 diff --git a/tools/verify_skill_lib_contracts.py b/tools/verify_skill_lib_contracts.py index 5ffdbf0..0bf53a7 100644 --- a/tools/verify_skill_lib_contracts.py +++ b/tools/verify_skill_lib_contracts.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=575:54 imports_exports=12:4 calls_definitions=309:23 +# ratios: loc_comments=577:54 imports_exports=12:4 calls_definitions=310:23 # === MODULE_BUILD === # id: skill_lib_contract_audit # module_name: verify_skill_lib_contracts @@ -302,6 +302,8 @@ def _test_setting(cls: ast.ClassDef | ast.Module) -> tuple[bool, object]: value = UNKNOWN_TEST_SETTING elif isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)) and node.name == "__test__": found, value = True, UNKNOWN_TEST_SETTING + elif isinstance(node, (ast.Import, ast.ImportFrom)) and "__test__" in _bindings([node]): + found, value = True, UNKNOWN_TEST_SETTING elif isinstance(node, ast.AugAssign) and "__test__" in _target_names(node.target): found, value = True, UNKNOWN_TEST_SETTING elif isinstance(node, ast.Delete) and any("__test__" in _target_names(target) for target in node.targets): @@ -314,7 +316,7 @@ def _defined_functions(path: Path) -> Set[str]: tree = ast.parse(path.read_text(encoding="utf-8"), filename=str(path)) found, setting = _test_setting(tree) - if found and not setting: + if setting is UNKNOWN_TEST_SETTING or found and not setting: return set() return {name for name, kind in _bindings(tree.body).items() if kind == "function"} @@ -683,4 +685,4 @@ def main(argv: Sequence[str] | None = None) -> int: if __name__ == "__main__": raise SystemExit(main()) -# ratios: loc_comments=575:54 imports_exports=12:4 calls_definitions=309:23 +# ratios: loc_comments=577:54 imports_exports=12:4 calls_definitions=310:23 From e0d0647ae064b8f8296646e5319cedf21ec5045c Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Sat, 12 Sep 2026 07:13:00 +0000 Subject: [PATCH 51/65] Bind maintenance report to collection opt-out checks --- docs/work-graphs/repository-plan-report.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/work-graphs/repository-plan-report.json b/docs/work-graphs/repository-plan-report.json index ede3f02..b2ebdcf 100644 --- a/docs/work-graphs/repository-plan-report.json +++ b/docs/work-graphs/repository-plan-report.json @@ -10,7 +10,7 @@ }, "source": { "branch": "repair/ucns-audit-20260911", - "commit": "65ab9b1dee670616c67dc58bfea75608c308cd77", + "commit": "c9450d9e1d705cd482460c43aee67f1e62318fbf", "generated_at": "2026-09-12", "note": "Describes the exact implementation commit preceding this report-only refresh. Maintenance PR #226 is in final exact-head review; merge remains a separate acceptance observation. No scientific or deployment authority is transferred." }, From 974ee0c358fcdf98081e063ec519357f6995f026 Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Sat, 12 Sep 2026 07:30:59 +0000 Subject: [PATCH 52/65] Resolve collection opt-outs and class binding ambiguity --- AGENTS.md | 2 +- README.md | 13 ++++++++++--- tests/test_skill_lib_contracts.py | 10 ++++++++-- tools/verify_skill_lib_contracts.py | 12 +++++++++--- 4 files changed, 28 insertions(+), 9 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 1b8cc5e..26b2f2f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -12,4 +12,4 @@ - Preserve exact arithmetic where exactness is load-bearing; lossy renderings remain explicitly secondary. - Use `hmmm` for unresolved geometric constraints. Never fill a missing geometric law with semantic machinery or implementation convenience. - Every behavior-bearing source module owns skill-lib `MODULE_BUILD` and `CONTRACTS`; executable tests own resolving `CHECKS`. -- Before claiming test-backed status run the complete geometry test suite, `python tools/verify_skill_lib_contracts.py .`, build, and Twine checks. +- Before claiming test-backed status run the complete geometry suite through the outcome gate documented in README, `.venv/bin/python tools/verify_skill_lib_contracts.py .`, build, and Twine checks. Use the project interpreter populated by `uv sync --locked --extra test --extra build` for these checks. diff --git a/README.md b/README.md index 86f4b90..0107eb8 100644 --- a/README.md +++ b/README.md @@ -147,8 +147,13 @@ assert s720 == s0 python -m pip install uv==0.11.18 uv lock --check uv sync --locked --python python --extra test --extra build -.venv/bin/python -m pytest -q -python tools/verify_skill_lib_contracts.py . +.venv/bin/python tools/verify_skill_lib_contracts.py . +env -u PYTHONPATH -u PYTHONHOME -u PYTEST_ADDOPTS -u PYTEST_PLUGINS \ + PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 .venv/bin/python - <<'PY' +from pathlib import Path +from tools._boundary_pytest import run_suite +raise SystemExit(run_suite(["tests", "-c", "pyproject.toml", "--noconftest", "--strict-config"], Path.cwd())) +PY .venv/bin/python -m build .venv/bin/python -m twine check dist/* .venv/bin/python tools/verify_distributions.py . dist @@ -211,7 +216,9 @@ Module/class pytest marks and pytest/xunit hooks are unsupported implicit execut data bindings must be literal values; imported descriptors, unresolved bases, nested classes, and compound class namespace construction fail closed. Fixture helpers remain supported, including test-prefixed helper names, but fixture-decorated functions cannot resolve CHECKS. -Imported `__test__` bindings are unresolved collection opt-outs and fail closed. +Imported or destructured `__test__` bindings are unresolved collection opt-outs and fail closed. +Class and base names must have unambiguous bindings; later rebinding cannot +stand in for the class used during construction. CI runs the complete suite through the outcome observer: collection skips/errors, runtime skips, xfail, XPASS, and empty execution fail the suite gate. Source observation starts before capability probing. Executable Node version diff --git a/tests/test_skill_lib_contracts.py b/tests/test_skill_lib_contracts.py index 2e53c26..b1a9488 100644 --- a/tests/test_skill_lib_contracts.py +++ b/tests/test_skill_lib_contracts.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=296:60 imports_exports=9:7 calls_definitions=154:7 +# ratios: loc_comments=302:60 imports_exports=9:7 calls_definitions=158:7 # === CHECKS === # id: check_contract_audit_no_exec # proves: contract_audit_is_no_exec @@ -235,6 +235,8 @@ def test_empty_syntax_and_class_coverage_are_not_closed(tmp_path: Path) -> None: 'from descriptor_helper import Base\nclass TestInjected:\n class Nested(Base): pass\n', 'from descriptor_helper import Base\nclass Helper(Base):\n __test__ = False\n', 'from descriptor_helper import Base as object\nclass TestInjected(object): pass\n', + 'from descriptor_helper import Base\nclass TestInjected(Base): pass\nclass Base: pass\n', + 'from descriptor_helper import Base\nclass TestInjected(Base): pass\nclass TestInjected: pass\n', ): dynamic.write_text(code) ok, problems = audit_repository(root) @@ -272,6 +274,10 @@ def test_empty_syntax_and_class_coverage_are_not_closed(tmp_path: Path) -> None: import_root = _repo(tmp_path / f"import-optout-{index}", declaration + "\ndef test_probe(): assert False\n", [{"id": "check_probe", "function": "test_probe"}]) ok, problems = audit_repository(import_root) assert not ok and any("dynamic test-module opt-out" in item for item in problems), problems + for index, declaration in enumerate(("(__test__,) = (False,)", "[__test__] = [False]", "value, (__test__,) = (1, (False,))", "*__test__, value = (1,)", "__test__ = (__test__,) = (False,)")): + unpack_root = _repo(tmp_path / f"unpacked-optout-{index}", declaration + "\ndef test_probe(): assert False\n", [{"id": "check_probe", "function": "test_probe"}]) + ok, problems = audit_repository(unpack_root) + assert not ok and any("destructured collection opt-out" in item for item in problems), problems ok, problems = audit_repository(root / "absent") assert not ok and any("empty" in item for item in problems), problems @@ -376,4 +382,4 @@ def test_vendored_typescript_parser_retains_numeric_field_names(tmp_path: Path) broken.write_text("// no declaration\nthrow new Error('must not execute');\n") ok, problems = audit_repository(tmp_path / "repo") assert not ok and any("universal.ts missing MODULE_BUILD" in item for item in problems), problems -# ratios: loc_comments=296:60 imports_exports=9:7 calls_definitions=154:7 +# ratios: loc_comments=302:60 imports_exports=9:7 calls_definitions=158:7 diff --git a/tools/verify_skill_lib_contracts.py b/tools/verify_skill_lib_contracts.py index 0bf53a7..596f909 100644 --- a/tools/verify_skill_lib_contracts.py +++ b/tools/verify_skill_lib_contracts.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=577:54 imports_exports=12:4 calls_definitions=310:23 +# ratios: loc_comments=583:54 imports_exports=12:4 calls_definitions=314:23 # === MODULE_BUILD === # id: skill_lib_contract_audit # module_name: verify_skill_lib_contracts @@ -296,6 +296,9 @@ def _test_setting(cls: ast.ClassDef | ast.Module) -> tuple[bool, object]: targets = node.targets if isinstance(node, ast.Assign) else [node.target] if any("__test__" in _target_names(target) for target in targets): found = True + if any("__test__" in _target_names(target) and not isinstance(target, ast.Name) for target in targets): + value = UNKNOWN_TEST_SETTING + continue try: value = ast.literal_eval(node.value) except (ValueError, TypeError, SyntaxError): @@ -476,6 +479,8 @@ def source_path(node): problems.append(f"GAP unsupported implicit pytest hook: {path}:{node.lineno}") if isinstance(node, (ast.Assign, ast.AnnAssign, ast.AugAssign, ast.Delete)): targets = node.targets if isinstance(node, (ast.Assign, ast.Delete)) else [node.target] + if any("__test__" in _target_names(target) and not isinstance(target, ast.Name) for target in targets): + problems.append(f"GAP destructured collection opt-out: {path}:{node.lineno}") if any(isinstance(item, (ast.Subscript, ast.Attribute)) for target in targets for item in ast.walk(target)): problems.append(f"GAP indirect test-namespace mutation: {path}:{node.lineno}") if isinstance(node, (ast.Assign, ast.AnnAssign)) and node.value is not None and not (literal(node.value) or source_path(node.value)): @@ -500,7 +505,8 @@ def source_path(node): if isinstance(node, ast.ClassDef) and node.keywords: problems.append(f"GAP unsupported class construction keywords: {path}:{node.lineno}") if isinstance(node, ast.ClassDef): - if _class_mro(node.name, classes) is None or any(isinstance(base, ast.Name) and base.id == "object" and "object" in binding_counts for base in node.bases): + ambiguous = binding_counts.get(node.name) != 1 or any(isinstance(base, ast.Name) and binding_counts.get(base.id, 0) > 1 for base in node.bases) + if ambiguous or _class_mro(node.name, classes) is None or any(isinstance(base, ast.Name) and base.id == "object" and "object" in binding_counts for base in node.bases): problems.append(f"GAP unresolved collection-time class base: {path}:{node.lineno}") for statement in node.body: if isinstance(statement, (ast.Assign, ast.AnnAssign)): @@ -685,4 +691,4 @@ def main(argv: Sequence[str] | None = None) -> int: if __name__ == "__main__": raise SystemExit(main()) -# ratios: loc_comments=577:54 imports_exports=12:4 calls_definitions=310:23 +# ratios: loc_comments=583:54 imports_exports=12:4 calls_definitions=314:23 From 949495bb62f39e17bc9a9a31f7338d8d4dd122d4 Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Sat, 12 Sep 2026 07:30:59 +0000 Subject: [PATCH 53/65] Bind maintenance report to unambiguous declarations --- docs/work-graphs/repository-plan-report.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/work-graphs/repository-plan-report.json b/docs/work-graphs/repository-plan-report.json index b2ebdcf..9d2a9fc 100644 --- a/docs/work-graphs/repository-plan-report.json +++ b/docs/work-graphs/repository-plan-report.json @@ -10,7 +10,7 @@ }, "source": { "branch": "repair/ucns-audit-20260911", - "commit": "c9450d9e1d705cd482460c43aee67f1e62318fbf", + "commit": "974ee0c358fcdf98081e063ec519357f6995f026", "generated_at": "2026-09-12", "note": "Describes the exact implementation commit preceding this report-only refresh. Maintenance PR #226 is in final exact-head review; merge remains a separate acceptance observation. No scientific or deployment authority is transferred." }, From 0ff4fb72c2fed47d7cb75ea7990f72407ec0da9d Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Sat, 12 Sep 2026 07:54:57 +0000 Subject: [PATCH 54/65] Bind every source fixture and reject replaced check functions --- MANIFEST.in | 12 +++++++----- README.md | 8 ++++++++ tests/test_distributions.py | 4 ++-- tests/test_skill_lib_boundary_runner.py | 25 +++++++++++++++++++++++-- tests/test_skill_lib_contracts.py | 8 ++++++-- tools/run_skill_lib_boundaries.py | 7 +++---- tools/verify_distributions.py | 17 ++++++++--------- tools/verify_skill_lib_contracts.py | 10 +++++++--- 8 files changed, 64 insertions(+), 27 deletions(-) diff --git a/MANIFEST.in b/MANIFEST.in index 46b0402..226f2a9 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,7 +1,9 @@ include AGENTS.md CANON.md CLAUDE.md uv.lock -recursive-include tools *.py -recursive-include tests *.py -recursive-include docs *.md *.json *.jsonl *.svg -recursive-include generated *.json -recursive-include .agents/skills *.md *.json *.py *.ts +recursive-include src/ucns * +recursive-include tools * +recursive-include tests * +recursive-include docs * +recursive-include generated * +recursive-include .agents/skills * +recursive-include .github/workflows * global-exclude *.py[cod] diff --git a/README.md b/README.md index 0107eb8..b5aa694 100644 --- a/README.md +++ b/README.md @@ -183,6 +183,14 @@ repository evidence suite requires Node 24.15.0 for that TypeScript check; UCNS library use remains Python-only. Parser ownership remains in skill-lib. Unused skill helper tools remain canonical dependency material. +Receipt inventories hash every file under the bound `src`, `tools`, `tests`, +`docs`, `generated`, `.agents/skills`, and `.github/workflows` directories, +excluding `__pycache__`, plus the runner's declared root inputs. Text, binary, +and extensionless fixtures are included. Source archives retain the complete +package, test, tool, documentation, generated-evidence, skill, and CI trees; +the distribution gate rejects missing or altered inputs. Declared test-function +bindings must be unique, so a replacement cannot stand in for an earlier witness. + For a source-bound, selected-check receipt: ```bash diff --git a/tests/test_distributions.py b/tests/test_distributions.py index 556eb66..8e59e5b 100644 --- a/tests/test_distributions.py +++ b/tests/test_distributions.py @@ -87,7 +87,7 @@ def _archives(root, sdist, wheel, *, omit="", altered="", extra="", sdist_extra= def test_distribution_replay_inputs_fail_closed(tmp_path: Path) -> None: root = tmp_path / "repo" root.mkdir() - for name in (*audit.ROOT_INPUTS, "src/ucns/__init__.py", "generated/receipt.json", "docs/preregistration.md", ".agents/skills/msdmd/parsers/universal.py"): + for name in (*audit.ROOT_INPUTS, "src/ucns/__init__.py", "generated/receipt.json", "docs/preregistration.md", "tests/witness.txt", "tools/helper.sh", ".github/workflows/ci.yml", "docs/extensionless", ".agents/skills/msdmd/parsers/universal.py"): path = root / name path.parent.mkdir(parents=True, exist_ok=True) path.write_text("fixture\n") @@ -132,7 +132,7 @@ def test_distribution_replay_inputs_fail_closed(tmp_path: Path) -> None: ): _archives(root, sdist, wheel, **options) assert any(message in problem for problem in audit.verify_distributions(root, sdist, wheel)), options - for key, name, expected in (("omit", "generated/receipt.json", "missing"), ("altered", "docs/preregistration.md", "altered"), ("extra", "ucns/lexical.py", "unexpected")): + for key, name, expected in (("omit", "tests/witness.txt", "missing"), ("altered", "docs/extensionless", "altered"), ("omit", ".github/workflows/ci.yml", "missing"), ("omit", "generated/receipt.json", "missing"), ("altered", "docs/preregistration.md", "altered"), ("extra", "ucns/lexical.py", "unexpected")): _archives(root, sdist, wheel, **{key: name}) assert any(expected in problem for problem in audit.verify_distributions(root, sdist, wheel)) _archives(root, sdist, wheel, extra="../escaped.py") diff --git a/tests/test_skill_lib_boundary_runner.py b/tests/test_skill_lib_boundary_runner.py index 1ca5785..a0fb0d0 100644 --- a/tests/test_skill_lib_boundary_runner.py +++ b/tests/test_skill_lib_boundary_runner.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=155:351 imports_exports=19:18 calls_definitions=186:20 +# ratios: loc_comments=158:367 imports_exports=19:18 calls_definitions=197:20 # === CHECKS === # id: check_boundary_runner_audit_gate # proves: boundary_runner_audits_before_execution @@ -148,6 +148,9 @@ def test_audit_gap_prevents_execution(tmp_path: Path) -> None: assert receipt["status"] == "audit-gap" assert receipt["outcomes"] == [] assert len(receipt["receipt_sha256"]) == 64 + root = _repo(tmp_path / "rebound-function", "def test_probe(): assert False\ndef test_probe(): pass\n", [{"id": "check_probe", "function": "test_probe"}]) + receipt = runner.run_boundaries(root) + assert receipt["status"] == "audit-gap" and not receipt["outcomes"], receipt root = _repo(tmp_path / "nested", "def test_fails(): assert False\n", [{"id": "check_fails", "function": "test_fails"}]) nested = root / "tests/sub" nested.mkdir() @@ -322,6 +325,24 @@ def test_receipt_binds_declarations_outputs_and_identity(tmp_path: Path) -> None assert identity == sha256(encoded).hexdigest() + root = _repo(tmp_path / "text-input", "from pathlib import Path\ndef test_probe():\n assert Path(__file__).with_name('witness.txt').read_text() in {'one', 'two'}\n", [{"id": "check_probe", "function": "test_probe"}]) + witness = root / "tests/witness.txt" + snapshots = [] + for text in ("one", "two"): + witness.write_text(text) + observed = runner.run_boundaries(root) + assert observed["status"] == "passed" and observed["source_unchanged"], observed + assert observed["source_files_sha256"]["tests/witness.txt"] == sha256(text.encode()).hexdigest() + snapshots.append(observed["source_before_sha256"]) + assert snapshots[0] != snapshots[1] + for directory in runner.SOURCE_DIRECTORIES: + path = root / directory / "fixture-without-suffix" + path.parent.mkdir(parents=True, exist_ok=True) + path.write_bytes(b"input") + inventory, _ = runner._source_snapshot(root) + assert inventory[path.relative_to(root).as_posix()] == sha256(b"input").hexdigest() + + def test_passing_receipt_has_no_activation_or_selection_effect(tmp_path: Path) -> None: receipt = runner.run_boundaries(_repo( tmp_path, "def test_pass():\n pass\n", @@ -544,4 +565,4 @@ def test_node24_capability_runs_typescript_witness(tmp_path: Path) -> None: assert receipt["outcomes"][0]["status"] == "ERROR", receipt with pytest.raises(ProcessLookupError): os.kill(int(pid_path.read_text()), 0) -# ratios: loc_comments=155:351 imports_exports=19:18 calls_definitions=186:20 +# ratios: loc_comments=158:367 imports_exports=19:18 calls_definitions=197:20 diff --git a/tests/test_skill_lib_contracts.py b/tests/test_skill_lib_contracts.py index b1a9488..1aa9f2e 100644 --- a/tests/test_skill_lib_contracts.py +++ b/tests/test_skill_lib_contracts.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=302:60 imports_exports=9:7 calls_definitions=158:7 +# ratios: loc_comments=306:60 imports_exports=9:7 calls_definitions=162:7 # === CHECKS === # id: check_contract_audit_no_exec # proves: contract_audit_is_no_exec @@ -278,6 +278,10 @@ def test_empty_syntax_and_class_coverage_are_not_closed(tmp_path: Path) -> None: unpack_root = _repo(tmp_path / f"unpacked-optout-{index}", declaration + "\ndef test_probe(): assert False\n", [{"id": "check_probe", "function": "test_probe"}]) ok, problems = audit_repository(unpack_root) assert not ok and any("destructured collection opt-out" in item for item in problems), problems + for index, prefix in enumerate(("def test_probe(): assert False\n", "test_probe = 1\n", "from helper import test_probe\n")): + rebound_root = _repo(tmp_path / f"rebound-function-{index}", prefix + "def test_probe(): pass\n", [{"id": "check_probe", "function": "test_probe"}]) + ok, problems = audit_repository(rebound_root) + assert not ok and any("check_probe call does not resolve" in item for item in problems), problems ok, problems = audit_repository(root / "absent") assert not ok and any("empty" in item for item in problems), problems @@ -382,4 +386,4 @@ def test_vendored_typescript_parser_retains_numeric_field_names(tmp_path: Path) broken.write_text("// no declaration\nthrow new Error('must not execute');\n") ok, problems = audit_repository(tmp_path / "repo") assert not ok and any("universal.ts missing MODULE_BUILD" in item for item in problems), problems -# ratios: loc_comments=302:60 imports_exports=9:7 calls_definitions=158:7 +# ratios: loc_comments=306:60 imports_exports=9:7 calls_definitions=162:7 diff --git a/tools/run_skill_lib_boundaries.py b/tools/run_skill_lib_boundaries.py index 84b8f44..a9b30fe 100644 --- a/tools/run_skill_lib_boundaries.py +++ b/tools/run_skill_lib_boundaries.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=392:71 imports_exports=20:4 calls_definitions=175:20 +# ratios: loc_comments=391:71 imports_exports=20:4 calls_definitions=175:20 # === MODULE_BUILD === # id: skill_lib_boundary_runner # module_name: run_skill_lib_boundaries @@ -309,11 +309,10 @@ def _pytest_outcome(path: Path, returncode: int) -> tuple[str, dict]: def _source_snapshot(root: Path) -> tuple[dict[str, str], str]: """Bind repository-owned execution inputs, excluding caches and secrets.""" - suffixes = {".py", ".md", ".json", ".jsonl", ".ts", ".svg", ".yml", ".yaml"} paths = { path for directory in SOURCE_DIRECTORIES for path in (root / directory).rglob("*") - if path.is_file() and path.suffix in suffixes and "__pycache__" not in path.parts + if path.is_file() and "__pycache__" not in path.parts } paths.update(root / name for name in ROOT_INPUTS if (root / name).is_file()) inventory = {path.relative_to(root).as_posix(): _sha(path.read_bytes()) for path in sorted(paths)} @@ -514,4 +513,4 @@ def main(argv: Sequence[str] | None = None) -> int: if __name__ == "__main__": raise SystemExit(main()) -# ratios: loc_comments=392:71 imports_exports=20:4 calls_definitions=175:20 +# ratios: loc_comments=391:71 imports_exports=20:4 calls_definitions=175:20 diff --git a/tools/verify_distributions.py b/tools/verify_distributions.py index f86221d..6a3a7cf 100644 --- a/tools/verify_distributions.py +++ b/tools/verify_distributions.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=323:35 imports_exports=17:4 calls_definitions=169:11 +# ratios: loc_comments=321:35 imports_exports=17:4 calls_definitions=168:11 # === MODULE_BUILD === # id: ucns_distribution_audit # module_name: verify_distributions @@ -64,11 +64,10 @@ "pyproject.toml", "README.md", "AGENTS.md", "CANON.md", "CLAUDE.md", "LICENSE", "uv.lock", "MANIFEST.in", ) -TREE_INPUTS = { - "src/ucns": {".py"}, "tests": {".py"}, "tools": {".py"}, - "docs": {".md", ".json", ".jsonl", ".svg"}, "generated": {".json"}, - ".agents/skills": {".md", ".json", ".py", ".ts"}, -} +TREE_INPUTS = ( + "src/ucns", "tests", "tools", "docs", "generated", ".agents/skills", ".github/workflows", +) + SDIST_GENERATED = { "setup.cfg", "PKG-INFO", @@ -85,9 +84,9 @@ def expected_files(root: Path) -> dict[str, bytes]: paths = {root / name for name in ROOT_INPUTS} - for directory, suffixes in TREE_INPUTS.items(): + for directory in TREE_INPUTS: paths.update(path for path in (root / directory).rglob("*") - if path.is_file() and path.suffix in suffixes and "__pycache__" not in path.parts) + if path.is_file() and "__pycache__" not in path.parts) missing_roots = [path.name for path in paths if path.parent == root and not path.is_file()] if missing_roots: raise ValueError(f"missing root distribution inputs: {', '.join(sorted(missing_roots))}") @@ -390,4 +389,4 @@ def main() -> int: if __name__ == "__main__": raise SystemExit(main()) -# ratios: loc_comments=323:35 imports_exports=17:4 calls_definitions=169:11 +# ratios: loc_comments=321:35 imports_exports=17:4 calls_definitions=168:11 diff --git a/tools/verify_skill_lib_contracts.py b/tools/verify_skill_lib_contracts.py index 596f909..6d8bd67 100644 --- a/tools/verify_skill_lib_contracts.py +++ b/tools/verify_skill_lib_contracts.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=583:54 imports_exports=12:4 calls_definitions=314:23 +# ratios: loc_comments=587:54 imports_exports=12:4 calls_definitions=316:23 # === MODULE_BUILD === # id: skill_lib_contract_audit # module_name: verify_skill_lib_contracts @@ -321,7 +321,11 @@ def _defined_functions(path: Path) -> Set[str]: found, setting = _test_setting(tree) if setting is UNKNOWN_TEST_SETTING or found and not setting: return set() - return {name for name, kind in _bindings(tree.body).items() if kind == "function"} + counts = {} + for statement in tree.body: + for name in _bindings([statement]): + counts[name] = counts.get(name, 0) + 1 + return {name for name, kind in _bindings(tree.body).items() if kind == "function" and counts[name] == 1} def _missing(fields: Dict[str, str], required: Set[str]) -> Set[str]: @@ -691,4 +695,4 @@ def main(argv: Sequence[str] | None = None) -> int: if __name__ == "__main__": raise SystemExit(main()) -# ratios: loc_comments=583:54 imports_exports=12:4 calls_definitions=314:23 +# ratios: loc_comments=587:54 imports_exports=12:4 calls_definitions=316:23 From 7b8abe16cbc760becffafa4e1d5062039e8f660d Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Sat, 12 Sep 2026 07:54:57 +0000 Subject: [PATCH 55/65] Bind maintenance report to complete fixture inventories --- docs/work-graphs/repository-plan-report.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/work-graphs/repository-plan-report.json b/docs/work-graphs/repository-plan-report.json index 9d2a9fc..3308cd8 100644 --- a/docs/work-graphs/repository-plan-report.json +++ b/docs/work-graphs/repository-plan-report.json @@ -10,7 +10,7 @@ }, "source": { "branch": "repair/ucns-audit-20260911", - "commit": "974ee0c358fcdf98081e063ec519357f6995f026", + "commit": "0ff4fb72c2fed47d7cb75ea7990f72407ec0da9d", "generated_at": "2026-09-12", "note": "Describes the exact implementation commit preceding this report-only refresh. Maintenance PR #226 is in final exact-head review; merge remains a separate acceptance observation. No scientific or deployment authority is transferred." }, From 3f7219d9da66db8f2bd5f245df312d6d4b9d3454 Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Sat, 12 Sep 2026 08:17:04 +0000 Subject: [PATCH 56/65] Bind executed test code and reject hidden source and archive inputs --- README.md | 8 +++-- tests/test_distributions.py | 20 +++++++++---- tests/test_skill_lib_boundary_runner.py | 30 +++++++++++++++++-- tools/_boundary_pytest.py | 40 +++++++++++++++++++++++-- tools/run_skill_lib_boundaries.py | 24 ++++++++++----- tools/verify_distributions.py | 23 +++++++++++--- tools/verify_skill_lib_contracts.py | 7 +++-- 7 files changed, 125 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index b5aa694..e1533e0 100644 --- a/README.md +++ b/README.md @@ -186,10 +186,14 @@ Unused skill helper tools remain canonical dependency material. Receipt inventories hash every file under the bound `src`, `tools`, `tests`, `docs`, `generated`, `.agents/skills`, and `.github/workflows` directories, excluding `__pycache__`, plus the runner's declared root inputs. Text, binary, -and extensionless fixtures are included. Source archives retain the complete +and extensionless fixtures are included. Symlinks in bound inputs are unsupported +and fail before execution. Source archives retain the complete package, test, tool, documentation, generated-evidence, skill, and CI trees; the distribution gate rejects missing or altered inputs. Declared test-function -bindings must be unique, so a replacement cannot stand in for an earlier witness. +bindings must be unique. Test-helper modules receive the same collection-surface +audit, and the observer compares the actual test function code with its declared +source before and after the call. An imported replacement cannot stand in for +the original witness. For a source-bound, selected-check receipt: diff --git a/tests/test_distributions.py b/tests/test_distributions.py index 8e59e5b..db5ea1c 100644 --- a/tests/test_distributions.py +++ b/tests/test_distributions.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=127:10 imports_exports=9:1 calls_definitions=44:2 +# ratios: loc_comments=137:10 imports_exports=9:1 calls_definitions=50:2 # === CHECKS === # id: check_distribution_replay_inputs # proves: distributions_retain_exact_replay_inputs @@ -25,7 +25,7 @@ -def _archives(root, sdist, wheel, *, omit="", altered="", extra="", sdist_extra="", wheel_omit="", wheel_altered="", metadata_extra="", wheel_flags="true", record_mode="", sdist_directory="", wheel_directory="", sdist_root="ucns-0", sdist_mode=None, directory_mode=0o755): +def _archives(root, sdist, wheel, *, omit="", altered="", extra="", sdist_extra="", wheel_omit="", wheel_altered="", metadata_extra="", wheel_flags="true", record_mode="", sdist_directory="", wheel_directory="", sdist_root="ucns-0", sdist_mode=None, directory_mode=0o755, directory_payload=b""): core_metadata = "Metadata-Version: 2.4\nName: ucns\nVersion: 0\nSummary: Fixture\nAuthor: Test\nRequires-Python: >=3.10\nDescription-Content-Type: text/markdown\nLicense: fixture\nLicense-File: LICENSE\nDynamic: license-file\n" with tarfile.open(sdist, "w:gz") as archive: files = {**audit.expected_files(root), "setup.cfg": audit.GENERATED_SETUP_CFG, @@ -52,7 +52,8 @@ def _archives(root, sdist, wheel, *, omit="", altered="", extra="", sdist_extra= member = tarfile.TarInfo(f"{sdist_root}/{sdist_directory}") member.type = tarfile.DIRTYPE member.mode = directory_mode - archive.addfile(member) + member.size = len(directory_payload) + archive.addfile(member, io.BytesIO(directory_payload)) with zipfile.ZipFile(wheel, "w") as archive: files = {} for name, data in audit.expected_files(root).items(): @@ -81,7 +82,7 @@ def _archives(root, sdist, wheel, *, omit="", altered="", extra="", sdist_extra= for name, data in files.items(): archive.writestr(name, data) if wheel_directory: - archive.writestr(wheel_directory.rstrip("/") + "/", b"") + archive.writestr(wheel_directory.rstrip("/") + "/", directory_payload) def test_distribution_replay_inputs_fail_closed(tmp_path: Path) -> None: @@ -92,10 +93,19 @@ def test_distribution_replay_inputs_fail_closed(tmp_path: Path) -> None: path.parent.mkdir(parents=True, exist_ok=True) path.write_text("fixture\n") (root / "pyproject.toml").write_text('[project]\nname="ucns"\nversion="0"\ndescription="Fixture"\nrequires-python=">=3.10"\nauthors=[{name="Test"}]\nreadme="README.md"\n') + outside = tmp_path / "outside" + outside.mkdir() + link = root / "tests/linked-fixture" + link.symlink_to(outside) + with pytest.raises(ValueError, match="unsupported source symlink"): + audit.expected_files(root) + link.unlink() sdist, wheel = tmp_path / "ucns-0.tar.gz", tmp_path / "ucns-0-py3-none-any.whl" _archives(root, sdist, wheel) assert audit.verify_distributions(root, sdist, wheel) == [] for options, message in ( + ({"wheel_directory": "ucns", "directory_payload": b"unaccounted"}, "nonempty archive directory"), + ({"sdist_directory": "tests", "directory_payload": b"unaccounted"}, "nonempty archive directory"), ({"wheel_directory": "unexpected"}, "unexpected directory"), ({"sdist_directory": "unexpected"}, "unexpected directory"), ({"sdist_mode": 0}, "unusable sdist permissions"), @@ -145,4 +155,4 @@ def test_distribution_replay_inputs_fail_closed(tmp_path: Path) -> None: with zipfile.ZipFile(wheel, "a") as archive, pytest.warns(UserWarning, match="Duplicate"): archive.writestr("ucns/__init__.py", b"duplicate") assert any("duplicate" in problem for problem in audit.verify_distributions(root, sdist, wheel)) -# ratios: loc_comments=127:10 imports_exports=9:1 calls_definitions=44:2 +# ratios: loc_comments=137:10 imports_exports=9:1 calls_definitions=50:2 diff --git a/tests/test_skill_lib_boundary_runner.py b/tests/test_skill_lib_boundary_runner.py index a0fb0d0..b3e3eba 100644 --- a/tests/test_skill_lib_boundary_runner.py +++ b/tests/test_skill_lib_boundary_runner.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=158:367 imports_exports=19:18 calls_definitions=197:20 +# ratios: loc_comments=168:379 imports_exports=20:18 calls_definitions=210:20 # === CHECKS === # id: check_boundary_runner_audit_gate # proves: boundary_runner_audits_before_execution @@ -191,6 +191,20 @@ def test_audit_gap_prevents_execution(tmp_path: Path) -> None: assert not marker.exists() + helper_source = "import inspect\ndef passing(): pass\nfor frame in inspect.stack():\n namespace = frame.frame.f_globals\n if 'test_probe' in namespace:\n passing.__name__ = 'test_probe'\n passing.__module__ = namespace['__name__']\n namespace['test_probe'] = passing\n" + root = _repo(tmp_path / "helper-replacement", "def test_probe(): assert False\nimport replacing_helper\n", [{"id": "check_probe", "function": "test_probe"}]) + helper = root / "tests/replacing_helper.py" + helper.write_text(helper_source) + receipt = runner.run_boundaries(root) + assert receipt["status"] == "audit-gap" and not receipt["outcomes"], receipt + # Root helpers lie outside the test-tree surface audit. Runtime witness binding + # must still reject an imported replacement with matching name and module. + helper.rename(root / "replacing_helper.py") + receipt = runner.run_boundaries(root) + assert receipt["status"] == "not-passed" and receipt["outcomes"][0]["status"] == "FAIL", receipt + assert "witness code differs" in receipt["outcomes"][0]["stdout_excerpt"], receipt + + # === CHECKS === # id: check_geometry_suite_nonempty_pass # proves: geometry_suite_requires_nonempty_pass @@ -343,6 +357,18 @@ def test_receipt_binds_declarations_outputs_and_identity(tmp_path: Path) -> None assert inventory[path.relative_to(root).as_posix()] == sha256(b"input").hexdigest() + import pytest + outside = tmp_path / "outside-inputs" + outside.mkdir() + (outside / "witness").write_text("one") + for index, name in enumerate(("tests/fixtures", "tests/witness-link", "README.md", ".agents")): + linked = _repo(tmp_path / f"symlink-{index}", "def test_probe(): pass\n", [{"id": "check_probe", "function": "test_probe"}]) + path = linked / name + path.symlink_to(outside / "witness" if name == "tests/witness-link" or name == "README.md" else outside) + with pytest.raises(ValueError, match="unsupported source symlink"): + runner.run_boundaries(linked) + + def test_passing_receipt_has_no_activation_or_selection_effect(tmp_path: Path) -> None: receipt = runner.run_boundaries(_repo( tmp_path, "def test_pass():\n pass\n", @@ -565,4 +591,4 @@ def test_node24_capability_runs_typescript_witness(tmp_path: Path) -> None: assert receipt["outcomes"][0]["status"] == "ERROR", receipt with pytest.raises(ProcessLookupError): os.kill(int(pid_path.read_text()), 0) -# ratios: loc_comments=158:367 imports_exports=19:18 calls_definitions=197:20 +# ratios: loc_comments=168:379 imports_exports=20:18 calls_definitions=210:20 diff --git a/tools/_boundary_pytest.py b/tools/_boundary_pytest.py index 57b6018..d30f795 100644 --- a/tools/_boundary_pytest.py +++ b/tools/_boundary_pytest.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=77:35 imports_exports=5:3 calls_definitions=31:6 +# ratios: loc_comments=108:35 imports_exports=7:3 calls_definitions=47:8 # === MODULE_BUILD === # id: boundary_pytest_observer # module_name: _boundary_pytest @@ -20,7 +20,7 @@ # === CONTRACTS === # id: boundary_pytest_observes_actual_outcomes # given: a selected pytest boundary runs through this bootstrap -# then: assertion subclasses fail, unexpected exceptions error, XPASS cannot pass, imported local package origins must match the bound tree +# then: assertion subclasses fail, unexpected exceptions error, XPASS cannot pass, actual test function code must match the declared source, imported local package origins must match the bound tree # class: evidence # # id: geometry_suite_requires_nonempty_pass @@ -42,6 +42,9 @@ import json from pathlib import Path import sys +from types import CodeType, FunctionType, MethodType + +from _pytest.assertion.rewrite import _rewrite_test import pytest @@ -51,6 +54,37 @@ def __init__(self, root: Path): self.root = root self.calls: list[str] = [] self.other: list[str] = [] + self.expected_code: dict[Path, CodeType] = {} + + def _witness_matches_source(self, item) -> bool: + path = Path(item.path).resolve() + if not path.is_relative_to(self.root.resolve()): + return False + if path not in self.expected_code: + if item.config.getoption("assertmode") == "plain": + code = compile(path.read_bytes(), str(path), "exec", dont_inherit=True) + else: + _, code = _rewrite_test(path, item.config) + self.expected_code[path] = code + expected = self.expected_code[path] + for name in item.nodeid.split("::")[1:]: + name = name.split("[", 1)[0] + candidates = [value for value in expected.co_consts if isinstance(value, CodeType) and value.co_name == name] + if len(candidates) != 1: + return False + expected = candidates[0] + actual = item.obj + if isinstance(actual, MethodType): + actual = actual.__func__ + return isinstance(actual, FunctionType) and actual.__code__ == expected + + @pytest.hookimpl(hookwrapper=True, tryfirst=True) + def pytest_pyfunc_call(self, pyfuncitem): + if not self._witness_matches_source(pyfuncitem): + pytest.fail("test witness code differs from its declared source", pytrace=False) + yield + if not self._witness_matches_source(pyfuncitem): + pytest.fail("test witness code changed during execution", pytrace=False) def pytest_collectreport(self, report): if report.skipped: @@ -126,4 +160,4 @@ def main() -> int: if __name__ == "__main__": raise SystemExit(main()) -# ratios: loc_comments=77:35 imports_exports=5:3 calls_definitions=31:6 +# ratios: loc_comments=108:35 imports_exports=7:3 calls_definitions=47:8 diff --git a/tools/run_skill_lib_boundaries.py b/tools/run_skill_lib_boundaries.py index a9b30fe..8005fa1 100644 --- a/tools/run_skill_lib_boundaries.py +++ b/tools/run_skill_lib_boundaries.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=391:71 imports_exports=20:4 calls_definitions=175:20 +# ratios: loc_comments=399:71 imports_exports=20:4 calls_definitions=179:20 # === MODULE_BUILD === # id: skill_lib_boundary_runner # module_name: run_skill_lib_boundaries @@ -309,12 +309,20 @@ def _pytest_outcome(path: Path, returncode: int) -> tuple[str, dict]: def _source_snapshot(root: Path) -> tuple[dict[str, str], str]: """Bind repository-owned execution inputs, excluding caches and secrets.""" - paths = { - path for directory in SOURCE_DIRECTORIES - for path in (root / directory).rglob("*") - if path.is_file() and "__pycache__" not in path.parts - } - paths.update(root / name for name in ROOT_INPUTS if (root / name).is_file()) + entries = {root / name for name in ROOT_INPUTS} + for directory in SOURCE_DIRECTORIES: + base = root / directory + parent = base + while parent != root: + if parent.is_symlink(): + raise ValueError("unsupported source symlink: " + parent.relative_to(root).as_posix()) + entries.add(parent) + parent = parent.parent + entries.update(base.rglob("*")) + links = sorted(path.relative_to(root).as_posix() for path in entries if path.is_symlink()) + if links: + raise ValueError("unsupported source symlink: " + ", ".join(links)) + paths = {path for path in entries if path.is_file() and "__pycache__" not in path.parts} inventory = {path.relative_to(root).as_posix(): _sha(path.read_bytes()) for path in sorted(paths)} return inventory, _sha(json.dumps(inventory, sort_keys=True, separators=(",", ":")).encode()) @@ -513,4 +521,4 @@ def main(argv: Sequence[str] | None = None) -> int: if __name__ == "__main__": raise SystemExit(main()) -# ratios: loc_comments=391:71 imports_exports=20:4 calls_definitions=175:20 +# ratios: loc_comments=399:71 imports_exports=20:4 calls_definitions=179:20 diff --git a/tools/verify_distributions.py b/tools/verify_distributions.py index 6a3a7cf..867dcb3 100644 --- a/tools/verify_distributions.py +++ b/tools/verify_distributions.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=321:35 imports_exports=17:4 calls_definitions=168:11 +# ratios: loc_comments=336:35 imports_exports=17:4 calls_definitions=178:11 # === MODULE_BUILD === # id: ucns_distribution_audit # module_name: verify_distributions @@ -84,9 +84,20 @@ def expected_files(root: Path) -> dict[str, bytes]: paths = {root / name for name in ROOT_INPUTS} + if any(path.is_symlink() for path in paths): + raise ValueError("unsupported source symlink in root distribution inputs") for directory in TREE_INPUTS: - paths.update(path for path in (root / directory).rglob("*") - if path.is_file() and "__pycache__" not in path.parts) + base = root / directory + parent = base + while parent != root: + if parent.is_symlink(): + raise ValueError(f"unsupported source symlink: {parent}") + parent = parent.parent + for path in base.rglob("*"): + if path.is_symlink(): + raise ValueError(f"unsupported source symlink: {path}") + if path.is_file() and "__pycache__" not in path.parts: + paths.add(path) missing_roots = [path.name for path in paths if path.parent == root and not path.is_file()] if missing_roots: raise ValueError(f"missing root distribution inputs: {', '.join(sorted(missing_roots))}") @@ -131,6 +142,8 @@ def record(name: str, data: bytes | None) -> None: for member in archive.infolist(): if (member.external_attr >> 16) & 0o170000 == 0o120000: raise ValueError(f"archive symlink: {member.filename}") + if member.is_dir() and member.file_size: + raise ValueError(f"nonempty archive directory: {member.filename}") record(member.filename, None if member.is_dir() else archive.read(member)) else: with tarfile.open(path, "r:gz") as archive: @@ -139,6 +152,8 @@ def record(name: str, data: bytes | None) -> None: if member.mode & required_mode != required_mode or member.mode & 0o7000: raise ValueError(f"unusable sdist permissions: {member.name}: {oct(member.mode)}") if member.isdir(): + if member.size: + raise ValueError(f"nonempty archive directory: {member.name}") record(member.name, None) continue if not member.isfile(): @@ -389,4 +404,4 @@ def main() -> int: if __name__ == "__main__": raise SystemExit(main()) -# ratios: loc_comments=321:35 imports_exports=17:4 calls_definitions=168:11 +# ratios: loc_comments=336:35 imports_exports=17:4 calls_definitions=178:11 diff --git a/tools/verify_skill_lib_contracts.py b/tools/verify_skill_lib_contracts.py index 6d8bd67..f97b0bd 100644 --- a/tools/verify_skill_lib_contracts.py +++ b/tools/verify_skill_lib_contracts.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=587:54 imports_exports=12:4 calls_definitions=316:23 +# ratios: loc_comments=588:54 imports_exports=12:4 calls_definitions=317:23 # === MODULE_BUILD === # id: skill_lib_contract_audit # module_name: verify_skill_lib_contracts @@ -610,11 +610,12 @@ def audit_repository(root: Path) -> Tuple[bool, List[str]]: for test_path, tree in trees.items(): if test_path.is_relative_to(root / "tests") and any(isinstance(node, ast.Name) and node.id == "pytest_plugins" for node in ast.walk(tree)): problems.append(f"GAP unsupported pytest plugin collection surface: {test_path}") + if test_path.is_relative_to(root / "tests"): + problems.extend(_collection_surface_problems(tree, test_path)) if not test_path.is_relative_to(root / "tests") or not ( test_path.name.startswith("test_") or test_path.name.endswith("_test.py") ): continue - problems.extend(_collection_surface_problems(tree, test_path)) declared_calls = { entry.fields.get("call", "")[len("self::") :] for entry in checks @@ -695,4 +696,4 @@ def main(argv: Sequence[str] | None = None) -> int: if __name__ == "__main__": raise SystemExit(main()) -# ratios: loc_comments=587:54 imports_exports=12:4 calls_definitions=316:23 +# ratios: loc_comments=588:54 imports_exports=12:4 calls_definitions=317:23 From 4b08f33130ef3f3a29712e53c256acece80e338f Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Sat, 12 Sep 2026 08:17:04 +0000 Subject: [PATCH 57/65] Bind maintenance report to executed witness identity --- docs/work-graphs/repository-plan-report.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/work-graphs/repository-plan-report.json b/docs/work-graphs/repository-plan-report.json index 3308cd8..6406cb0 100644 --- a/docs/work-graphs/repository-plan-report.json +++ b/docs/work-graphs/repository-plan-report.json @@ -10,7 +10,7 @@ }, "source": { "branch": "repair/ucns-audit-20260911", - "commit": "0ff4fb72c2fed47d7cb75ea7990f72407ec0da9d", + "commit": "3f7219d9da66db8f2bd5f245df312d6d4b9d3454", "generated_at": "2026-09-12", "note": "Describes the exact implementation commit preceding this report-only refresh. Maintenance PR #226 is in final exact-head review; merge remains a separate acceptance observation. No scientific or deployment authority is transferred." }, From 5d0f2f34aa43021b2a094dc7296e7dc7b915d736 Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Sat, 12 Sep 2026 08:47:00 +0000 Subject: [PATCH 58/65] Bind execution to source bytes and retain failed snapshot receipts --- README.md | 5 ++- tests/test_skill_lib_boundary_runner.py | 43 ++++++++++++++++++++++--- tools/_boundary_pytest.py | 21 +++++++++--- tools/_boundary_site/sitecustomize.py | 20 +++++++++--- tools/run_skill_lib_boundaries.py | 40 +++++++++++++++++------ tools/verify_skill_lib_contracts.py | 2 +- 6 files changed, 106 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index e1533e0..b2d6154 100644 --- a/README.md +++ b/README.md @@ -193,7 +193,10 @@ the distribution gate rejects missing or altered inputs. Declared test-function bindings must be unique. Test-helper modules receive the same collection-surface audit, and the observer compares the actual test function code with its declared source before and after the call. An imported replacement cannot stand in for -the original witness. +the original witness. Bound Python imports compile the inventoried source, and +pytest runs use fresh bytecode-cache locations; pre-existing caches cannot replace +those source imports. Snapshot failures during execution become explicit ERROR +outcomes, retain mutation evidence, and produce a not-passed receipt. For a source-bound, selected-check receipt: diff --git a/tests/test_skill_lib_boundary_runner.py b/tests/test_skill_lib_boundary_runner.py index b3e3eba..2b08eae 100644 --- a/tests/test_skill_lib_boundary_runner.py +++ b/tests/test_skill_lib_boundary_runner.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=168:379 imports_exports=20:18 calls_definitions=210:20 +# ratios: loc_comments=168:410 imports_exports=23:18 calls_definitions=228:20 # === CHECKS === # id: check_boundary_runner_audit_gate # proves: boundary_runner_audits_before_execution @@ -365,8 +365,9 @@ def test_receipt_binds_declarations_outputs_and_identity(tmp_path: Path) -> None linked = _repo(tmp_path / f"symlink-{index}", "def test_probe(): pass\n", [{"id": "check_probe", "function": "test_probe"}]) path = linked / name path.symlink_to(outside / "witness" if name == "tests/witness-link" or name == "README.md" else outside) - with pytest.raises(ValueError, match="unsupported source symlink"): - runner.run_boundaries(linked) + invalid = runner.run_boundaries(linked) + assert invalid["status"] == "audit-gap" and not invalid["outcomes"] + assert any("unsupported source symlink" in gap for gap in invalid["audit_gaps"]) def test_passing_receipt_has_no_activation_or_selection_effect(tmp_path: Path) -> None: @@ -378,6 +379,9 @@ def test_passing_receipt_has_no_activation_or_selection_effect(tmp_path: Path) - assert receipt["selection_effect"] == "none" assert receipt["edcm_activation"] == "inactive" assert receipt["canon_status"] == "none" + parametrized = _repo(tmp_path / "parameter-ids", "import pytest\n@pytest.mark.parametrize('value', [1, 2], ids=['a::b', 'nested[x]::y'])\ndef test_probe(value): assert value > 0\n", [{"id": "check_probe", "function": "test_probe"}]) + observed = runner.run_boundaries(parametrized) + assert observed["status"] == "passed", observed def test_skips_xfails_and_mixed_parameters_are_not_passes(tmp_path: Path) -> None: @@ -476,6 +480,15 @@ def test_source_mutation_prevents_acceptance(tmp_path: Path) -> None: # === END CHECKS === + root = _repo(tmp_path / "new-symlink", "from pathlib import Path\ndef test_mutate():\n Path(__file__).with_name('new-link').symlink_to('test_feature.py')\ndef test_later(): pass\n", [{"id": "check_mutate", "function": "test_mutate"}, {"id": "check_later", "function": "test_later"}]) + receipt = runner.run_boundaries(root) + assert receipt["status"] == "not-passed" and not receipt["source_unchanged"] + assert [outcome["check_id"] for outcome in receipt["outcomes"]] == ["check_mutate", "check_later"] + assert all(outcome["status"] == "ERROR" for outcome in receipt["outcomes"]) + assert receipt["snapshot_errors"] and receipt["source_after_sha256"] == "" + assert receipt["outcomes"][0]["source_events"] + + def test_check_imports_bound_source_despite_ambient_pythonpath(tmp_path: Path, monkeypatch) -> None: body = "import pkg.feature\nimport subprocess, sys\ndef test_probe():\n assert pkg.feature.VALUE == 'bound'\n child = subprocess.check_output([sys.executable, '-c', 'import pkg.feature; print(pkg.feature.VALUE)'], text=True)\n assert child.strip() == 'bound'\n" root = _repo(tmp_path, body, [{"id": "check_probe", "function": "test_probe"}]) @@ -506,6 +519,28 @@ def test_check_imports_bound_source_despite_ambient_pythonpath(tmp_path: Path, m # === END CHECKS === + # A timestamp-valid cache must not replace inventoried source bytes. + import os + import py_compile + import subprocess + poisoned = _repo(tmp_path / "poisoned-cache", "def test_probe():\n from pkg.feature import VALUE\n assert VALUE == 1\n import subprocess, sys\n child = subprocess.run([sys.executable, '-c', 'from pkg.feature import VALUE; print(VALUE)'], check=True, capture_output=True, text=True)\n assert child.stdout.strip() == '1'\n", [{"id": "check_probe", "function": "test_probe"}]) + module = poisoned / "src/pkg/feature.py" + declarations = module.read_text() + module.write_text(declarations + "\nVALUE = 2\n") + stamp = module.stat() + cache = module.parent / "__pycache__" / f"{module.stem}.{sys.implementation.cache_tag}.pyc" + py_compile.compile(str(module), cfile=str(cache), doraise=True) + module.write_text(declarations + "\nVALUE = 1\n") + os.utime(module, ns=(stamp.st_atime_ns, stamp.st_mtime_ns)) + ordinary = dict(os.environ, PYTHONPATH=str(poisoned / "src"), PYTHONDONTWRITEBYTECODE="1") + ordinary.pop("PYTHONPYCACHEPREFIX", None) + old = subprocess.run([sys.executable, "-c", "from pkg.feature import VALUE; print(VALUE)"], env=ordinary, capture_output=True, text=True, check=True) + assert old.stdout.strip() == "2", "fixture must contain an executable stale cache" + observed = runner.run_boundaries(poisoned) + assert observed["status"] == "passed" and observed["source_unchanged"], observed + assert not any("__pycache__" in name for name in observed["source_files_sha256"]) + + def test_background_descendants_block_acceptance(tmp_path: Path) -> None: import os import pytest @@ -591,4 +626,4 @@ def test_node24_capability_runs_typescript_witness(tmp_path: Path) -> None: assert receipt["outcomes"][0]["status"] == "ERROR", receipt with pytest.raises(ProcessLookupError): os.kill(int(pid_path.read_text()), 0) -# ratios: loc_comments=168:379 imports_exports=20:18 calls_definitions=210:20 +# ratios: loc_comments=168:410 imports_exports=23:18 calls_definitions=228:20 diff --git a/tools/_boundary_pytest.py b/tools/_boundary_pytest.py index d30f795..70ec696 100644 --- a/tools/_boundary_pytest.py +++ b/tools/_boundary_pytest.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=108:35 imports_exports=7:3 calls_definitions=47:8 +# ratios: loc_comments=119:35 imports_exports=8:3 calls_definitions=49:8 # === MODULE_BUILD === # id: boundary_pytest_observer # module_name: _boundary_pytest @@ -42,6 +42,7 @@ import json from pathlib import Path import sys +import tempfile from types import CodeType, FunctionType, MethodType from _pytest.assertion.rewrite import _rewrite_test @@ -67,8 +68,12 @@ def _witness_matches_source(self, item) -> bool: _, code = _rewrite_test(path, item.config) self.expected_code[path] = code expected = self.expected_code[path] - for name in item.nodeid.split("::")[1:]: - name = name.split("[", 1)[0] + names = [item.originalname or item.name.split("[", 1)[0]] + parent = item.parent + while isinstance(parent, pytest.Class): + names.insert(0, parent.name) + parent = parent.parent + for name in names: candidates = [value for value in expected.co_consts if isinstance(value, CodeType) and value.co_name == name] if len(candidates) != 1: return False @@ -116,7 +121,13 @@ def pytest_runtest_makereport(self, item, call): def run_suite(arguments: list[str], root: Path) -> int: observer = Observer(root) - result = int(pytest.main(arguments, plugins=[observer])) + previous_prefix = sys.pycache_prefix + try: + with tempfile.TemporaryDirectory(prefix="ucns-suite-bytecode-") as cache: + sys.pycache_prefix = cache + result = int(pytest.main(arguments, plugins=[observer])) + finally: + sys.pycache_prefix = previous_prefix if result: return result return 0 if observer.calls and set(observer.calls) == {"PASS"} and not observer.other else 1 @@ -160,4 +171,4 @@ def main() -> int: if __name__ == "__main__": raise SystemExit(main()) -# ratios: loc_comments=108:35 imports_exports=7:3 calls_definitions=47:8 +# ratios: loc_comments=119:35 imports_exports=8:3 calls_definitions=49:8 diff --git a/tools/_boundary_site/sitecustomize.py b/tools/_boundary_site/sitecustomize.py index 9e1ac6a..845adae 100644 --- a/tools/_boundary_site/sitecustomize.py +++ b/tools/_boundary_site/sitecustomize.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=28:28 imports_exports=5:1 calls_definitions=13:3 +# ratios: loc_comments=36:28 imports_exports=5:2 calls_definitions=19:5 # === MODULE_BUILD === # id: boundary_descendant_import_binding # module_name: sitecustomize @@ -6,7 +6,7 @@ # summary: keeps bound package imports ahead of child working directories during declared checks # owner: Erin Spencer # public_surface: none; installed on the boundary runner's sanitized PYTHONPATH -# internal_surface: BoundSourceFinder +# internal_surface: BoundSourceFinder, BoundSourceLoader # auth_boundary: none # storage_boundary: read # storage_notes: reads source package locations @@ -20,7 +20,7 @@ # === CONTRACTS === # id: boundary_descendants_import_bound_source # given: an ordinary Python check subprocess inherits the boundary environment -# then: bound source packages resolve before same-named packages in its working directory +# then: bound source packages compile inventoried source bytes before same-named packages in the working directory # class: evidence # === END CONTRACTS === """Internal startup hook; the runner sets UCNS_BOUND_SOURCE_ROOT and PYTHONPATH. @@ -30,7 +30,7 @@ """ from __future__ import annotations -from importlib.machinery import PathFinder +from importlib.machinery import PathFinder, SourceFileLoader, SourcelessFileLoader import os from pathlib import Path import sys @@ -40,6 +40,12 @@ FINDER = None +class BoundSourceLoader(SourceFileLoader): + def get_code(self, fullname): + filename = self.get_filename(fullname) + return self.source_to_code(self.get_data(filename), filename) + + class BoundSourceFinder: def __init__(self, root: Path): self.source = root / "src" @@ -58,10 +64,14 @@ def find_spec(self, fullname, path=None, target=None): locations.append(spec.origin) if not locations or any(not Path(location).resolve().is_relative_to(self.source) for location in locations): raise ImportError(f"module outside bound source: {fullname}") + if isinstance(spec.loader, SourceFileLoader): + spec.loader = BoundSourceLoader(fullname, spec.origin) + elif isinstance(spec.loader, SourcelessFileLoader): + raise ImportError(f"sourceless bytecode outside the source contract: {fullname}") return spec if BOUND_ROOT: FINDER = BoundSourceFinder(Path(BOUND_ROOT).resolve()) sys.meta_path.insert(0, FINDER) -# ratios: loc_comments=28:28 imports_exports=5:1 calls_definitions=13:3 +# ratios: loc_comments=36:28 imports_exports=5:2 calls_definitions=19:5 diff --git a/tools/run_skill_lib_boundaries.py b/tools/run_skill_lib_boundaries.py index 8005fa1..6f564a3 100644 --- a/tools/run_skill_lib_boundaries.py +++ b/tools/run_skill_lib_boundaries.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=399:71 imports_exports=20:4 calls_definitions=179:20 +# ratios: loc_comments=421:71 imports_exports=20:4 calls_definitions=186:20 # === MODULE_BUILD === # id: skill_lib_boundary_runner # module_name: run_skill_lib_boundaries @@ -308,7 +308,7 @@ def _pytest_outcome(path: Path, returncode: int) -> tuple[str, dict]: def _source_snapshot(root: Path) -> tuple[dict[str, str], str]: - """Bind repository-owned execution inputs, excluding caches and secrets.""" + """Bind declared execution inputs, excluding bytecode caches.""" entries = {root / name for name in ROOT_INPUTS} for directory in SOURCE_DIRECTORIES: base = root / directory @@ -378,6 +378,7 @@ def _execute_check(root: Path, check: Entry) -> CheckOutcome: for name in ("PYTHONPATH", "PYTHONHOME", "PYTEST_ADDOPTS", "PYTEST_PLUGINS"): environment.pop(name, None) environment.update(PYTHONDONTWRITEBYTECODE="1", PYTEST_DISABLE_PLUGIN_AUTOLOAD="1") + environment["PYTHONPYCACHEPREFIX"] = str(Path(temporary) / "bytecode") environment["UCNS_BOUND_SOURCE_ROOT"] = str(root) environment["PYTHONPATH"] = os.pathsep.join((str(STARTUP_DIRECTORY), str(root / "src"), str(root))) with stdout_path.open("wb") as stdout, stderr_path.open("wb") as stderr: @@ -419,8 +420,13 @@ def run_boundaries( root: Path, *, selected_ids: Iterable[str] = (), ) -> dict[str, object]: root = root.resolve() - source_files, source_before = _source_snapshot(root) - audit_ok, gaps = audit_repository(root) + try: + source_files, source_before = _source_snapshot(root) + except (ValueError, OSError) as error: + source_files, source_before = {}, "" + audit_ok, gaps = False, [f"GAP source snapshot {type(error).__name__}: {error}"] + else: + audit_ok, gaps = audit_repository(root) if not audit_ok: receipt: dict[str, object] = { "schema_id": SCHEMA_ID, "schema_version": SCHEMA_VERSION, @@ -442,17 +448,32 @@ def run_boundaries( check for check in checks if check.id in requested ) outcomes = [] + snapshot_errors = [] for check in selected: - _, check_before = _source_snapshot(root) + check_before = check_after = "" + try: + _, check_before = _source_snapshot(root) + except (ValueError, OSError) as error: + snapshot_errors.append({"check_id": check.id, "phase": "before", "error": str(error)}) + outcomes.append(_error_outcome(root, check, error)) + continue try: outcome = _run_check(root, check) except (ValueError, OSError, AttributeError) as error: outcome = _error_outcome(root, check, error) - _, check_after = _source_snapshot(root) + try: + _, check_after = _source_snapshot(root) + except (ValueError, OSError) as error: + snapshot_errors.append({"check_id": check.id, "phase": "after", "error": str(error)}) + outcome = replace(outcome, status="ERROR", diagnostic=f"source snapshot {type(error).__name__}: {error}") outcomes.append(replace(outcome, source_before_sha256=check_before, source_after_sha256=check_after)) - _, source_after = _source_snapshot(root) + try: + _, source_after = _source_snapshot(root) + except (ValueError, OSError) as error: + source_after = "" + snapshot_errors.append({"phase": "final", "error": str(error)}) statuses = {outcome.status for outcome in outcomes} - unchanged = source_before == source_after and all( + unchanged = not snapshot_errors and source_before == source_after and all( not outcome.source_events and outcome.source_before_sha256 == outcome.source_after_sha256 for outcome in outcomes ) @@ -465,6 +486,7 @@ def run_boundaries( "source_before_sha256": source_before, "source_after_sha256": source_after, "source_unchanged": unchanged, + "snapshot_errors": snapshot_errors, "bootstrap_sha256": _sha(BOOTSTRAP.read_bytes()), "supervisor_sha256": _sha(SUPERVISOR.read_bytes()), "startup_hook_sha256": _sha((STARTUP_DIRECTORY / "sitecustomize.py").read_bytes()), @@ -521,4 +543,4 @@ def main(argv: Sequence[str] | None = None) -> int: if __name__ == "__main__": raise SystemExit(main()) -# ratios: loc_comments=399:71 imports_exports=20:4 calls_definitions=179:20 +# ratios: loc_comments=421:71 imports_exports=20:4 calls_definitions=186:20 diff --git a/tools/verify_skill_lib_contracts.py b/tools/verify_skill_lib_contracts.py index f97b0bd..d512785 100644 --- a/tools/verify_skill_lib_contracts.py +++ b/tools/verify_skill_lib_contracts.py @@ -73,7 +73,7 @@ if _SPEC is None or _SPEC.loader is None: raise RuntimeError("canonical msdmd parser unavailable") _PARSER = importlib.util.module_from_spec(_SPEC) -_SPEC.loader.exec_module(_PARSER) +exec(compile(PARSER_PATH.read_bytes(), str(PARSER_PATH), "exec", dont_inherit=True), _PARSER.__dict__) REQUIRED_MODULE_FIELDS = { "module_name", "module_kind", From 08ee40b6d43f646be6cb1e8902f2643877b74963 Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Sat, 12 Sep 2026 08:47:01 +0000 Subject: [PATCH 59/65] Bind maintenance report to source execution repairs --- docs/work-graphs/repository-plan-report.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/work-graphs/repository-plan-report.json b/docs/work-graphs/repository-plan-report.json index 6406cb0..21639e5 100644 --- a/docs/work-graphs/repository-plan-report.json +++ b/docs/work-graphs/repository-plan-report.json @@ -10,7 +10,7 @@ }, "source": { "branch": "repair/ucns-audit-20260911", - "commit": "3f7219d9da66db8f2bd5f245df312d6d4b9d3454", + "commit": "5d0f2f34aa43021b2a094dc7296e7dc7b915d736", "generated_at": "2026-09-12", "note": "Describes the exact implementation commit preceding this report-only refresh. Maintenance PR #226 is in final exact-head review; merge remains a separate acceptance observation. No scientific or deployment authority is transferred." }, From 76d4eadaa85e74d03f7c9c45db08fdf1ba367a05 Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Sat, 12 Sep 2026 08:53:56 +0000 Subject: [PATCH 60/65] Budget nested nonactivation witness for cold source compilation --- tests/test_skill_lib_boundary_runner.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_skill_lib_boundary_runner.py b/tests/test_skill_lib_boundary_runner.py index 2b08eae..2145724 100644 --- a/tests/test_skill_lib_boundary_runner.py +++ b/tests/test_skill_lib_boundary_runner.py @@ -44,7 +44,7 @@ # proves: boundary_runner_has_no_activation_effect # call: self::test_passing_receipt_has_no_activation_or_selection_effect # requires: python3 -# timeout: 10 +# timeout: 30 # mutates: temporary_path # cleanup: pytest temporary_path # === END CHECKS === From d0c13d5eb4f189167b6f2be9e722f3dd5cba499b Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Sat, 12 Sep 2026 08:53:56 +0000 Subject: [PATCH 61/65] Bind report to cold-cache witness budget --- docs/work-graphs/repository-plan-report.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/work-graphs/repository-plan-report.json b/docs/work-graphs/repository-plan-report.json index 21639e5..8fca8bb 100644 --- a/docs/work-graphs/repository-plan-report.json +++ b/docs/work-graphs/repository-plan-report.json @@ -10,7 +10,7 @@ }, "source": { "branch": "repair/ucns-audit-20260911", - "commit": "5d0f2f34aa43021b2a094dc7296e7dc7b915d736", + "commit": "76d4eadaa85e74d03f7c9c45db08fdf1ba367a05", "generated_at": "2026-09-12", "note": "Describes the exact implementation commit preceding this report-only refresh. Maintenance PR #226 is in final exact-head review; merge remains a separate acceptance observation. No scientific or deployment authority is transferred." }, From 15e37de2d0a9c4984137bb14956242b15a995378 Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Sat, 12 Sep 2026 09:14:25 +0000 Subject: [PATCH 62/65] Reject root helper imports and require every declared test to execute --- README.md | 3 +++ tests/test_skill_lib_boundary_runner.py | 30 ++++++++++++++++++++----- tools/_boundary_pytest.py | 17 ++++++++++---- tools/verify_skill_lib_contracts.py | 11 +++++++-- 4 files changed, 49 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index b2d6154..6d36284 100644 --- a/README.md +++ b/README.md @@ -216,6 +216,9 @@ audit closed; executable witnesses use direct module-level definitions. The graph requires explicit `testpaths = ["tests"]` and `collect_imported_tests = false`, with pytest 9.1 or later, and supports default pytest file/class/function naming. Unrecognized configuration keys fail closed. +Repository-root helper imports outside the declared source layout fail the audit. +The complete-suite gate also requires every statically declared top-level test to +execute, so hiding a failing witness during collection cannot leave a passing gate. Conftest files and local plugin registration are outside this bounded collection contract. Changed settings, alternative pytest configuration files, and collection-changing `addopts` fail closed. Root `pyproject.toml` is diff --git a/tests/test_skill_lib_boundary_runner.py b/tests/test_skill_lib_boundary_runner.py index 2145724..079854a 100644 --- a/tests/test_skill_lib_boundary_runner.py +++ b/tests/test_skill_lib_boundary_runner.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=168:410 imports_exports=23:18 calls_definitions=228:20 +# ratios: loc_comments=186:410 imports_exports=23:18 calls_definitions=241:20 # === CHECKS === # id: check_boundary_runner_audit_gate # proves: boundary_runner_audits_before_execution @@ -197,12 +197,26 @@ def test_audit_gap_prevents_execution(tmp_path: Path) -> None: helper.write_text(helper_source) receipt = runner.run_boundaries(root) assert receipt["status"] == "audit-gap" and not receipt["outcomes"], receipt - # Root helpers lie outside the test-tree surface audit. Runtime witness binding - # must still reject an imported replacement with matching name and module. + # Root helpers are outside the declared source layout and must fail the audit. helper.rename(root / "replacing_helper.py") receipt = runner.run_boundaries(root) - assert receipt["status"] == "not-passed" and receipt["outcomes"][0]["status"] == "FAIL", receipt - assert "witness code differs" in receipt["outcomes"][0]["stdout_excerpt"], receipt + assert receipt["status"] == "audit-gap" and not receipt["outcomes"], receipt + assert any("root helper" in gap for gap in receipt["audit_gaps"]) + # Below the audit gate, the runtime still rejects a replaced witness. + outcome = runner._run_check(root, runner._declared_checks(root)[0]) + assert outcome.status == "FAIL" and "witness code differs" in outcome.stdout_excerpt, outcome + hiding = "import inspect\nfor frame in inspect.stack():\n witness = frame.frame.f_globals.get('test_fails')\n if witness is not None:\n witness.__test__ = False\n" + root = _repo(tmp_path / "hidden-witness", "def test_fails(): assert False\nimport hide_witness\ndef test_passes(): pass\n", [{"id": "check_fails", "function": "test_fails"}, {"id": "check_passes", "function": "test_passes"}]) + (root / "hide_witness.py").write_text(hiding) + receipt = runner.run_boundaries(root) + assert receipt["status"] == "audit-gap" and not receipt["outcomes"], receipt + namespace = root / "hide_package" + namespace.mkdir() + (root / "hide_witness.py").rename(namespace / "effects.py") + source = root / "tests/test_feature.py" + source.write_text(source.read_text().replace("import hide_witness", "import hide_package.effects")) + receipt = runner.run_boundaries(root) + assert receipt["status"] == "audit-gap" and not receipt["outcomes"], receipt # === CHECKS === @@ -227,6 +241,8 @@ def test_geometry_suite_rejects_nonpasses(tmp_path: Path) -> None: ("collection-skip", "import pytest\npytest.skip('unobserved', allow_module_level=True)\n", False), ("collection-error", "raise RuntimeError('broken collection')\n", False), ("module-mark", "import pytest\npytestmark = pytest.mark.skip\ndef test_probe(): assert False\n", False), + ("hidden-witness", "def test_fails(): assert False\nimport hide_witness\ndef test_passes(): pass\n", False), + ("removed-witness", "def test_first(request): request.session.items[:] = [request.node]\ndef test_fails(): assert False\n", False), ) script = "import os,sys; from pathlib import Path; sys.path.insert(0,sys.argv[1]); from tools._boundary_pytest import run_suite; root=Path(sys.argv[2]); os.chdir(root); raise SystemExit(run_suite(['tests','-c','pyproject.toml','--noconftest','--strict-config'],root))" environment = {key: value for key, value in os.environ.items() if key not in {"PYTHONPATH", "PYTHONHOME", "PYTEST_ADDOPTS", "PYTEST_PLUGINS"}} @@ -235,6 +251,8 @@ def test_geometry_suite_rejects_nonpasses(tmp_path: Path) -> None: root = _repo(tmp_path / label, body, []) if label.startswith("collection-"): (root / "tests/test_other.py").write_text("def test_other(): pass\n") + if label == "hidden-witness": + (root / "hide_witness.py").write_text("import inspect\nfor frame in inspect.stack():\n witness = frame.frame.f_globals.get('test_fails')\n if witness is not None:\n witness.__test__ = False\n") result = subprocess.run([sys.executable, "-c", script, str(RUNNER_PATH.parents[1]), str(root)], env=environment, capture_output=True, text=True) assert (result.returncode == 0) is expected_pass, (label, result.stdout, result.stderr) @@ -626,4 +644,4 @@ def test_node24_capability_runs_typescript_witness(tmp_path: Path) -> None: assert receipt["outcomes"][0]["status"] == "ERROR", receipt with pytest.raises(ProcessLookupError): os.kill(int(pid_path.read_text()), 0) -# ratios: loc_comments=168:410 imports_exports=23:18 calls_definitions=228:20 +# ratios: loc_comments=186:410 imports_exports=23:18 calls_definitions=241:20 diff --git a/tools/_boundary_pytest.py b/tools/_boundary_pytest.py index 70ec696..6491c68 100644 --- a/tools/_boundary_pytest.py +++ b/tools/_boundary_pytest.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=119:35 imports_exports=8:3 calls_definitions=49:8 +# ratios: loc_comments=128:35 imports_exports=9:3 calls_definitions=55:8 # === MODULE_BUILD === # id: boundary_pytest_observer # module_name: _boundary_pytest @@ -25,7 +25,7 @@ # # id: geometry_suite_requires_nonempty_pass # given: the full geometry suite runs through run_suite -# then: empty, skipped, xfailed, XPASS, failed, or collection-error evidence cannot produce exit status zero +# then: empty, unexecuted declared witnesses, skipped, xfailed, XPASS, failed, or collection-error evidence cannot produce exit status zero # class: evidence # === END CONTRACTS === @@ -55,6 +55,7 @@ def __init__(self, root: Path): self.root = root self.calls: list[str] = [] self.other: list[str] = [] + self.executed_witnesses: set[tuple[Path, str]] = set() self.expected_code: dict[Path, CodeType] = {} def _witness_matches_source(self, item) -> bool: @@ -115,11 +116,16 @@ def pytest_runtest_makereport(self, item, call): status = "PASS" if call.when == "call": self.calls.append(status) + self.executed_witnesses.add((Path(item.path).resolve(), item.originalname or item.name.split("[", 1)[0])) elif status != "PASS": self.other.append(status) def run_suite(arguments: list[str], root: Path) -> int: + from tools.verify_skill_lib_contracts import _defined_functions + expected = {(path.resolve(), name) for path in (root / "tests").rglob("*.py") + if path.name.startswith("test_") or path.name.endswith("_test.py") + for name in _defined_functions(path) if name.startswith("test")} observer = Observer(root) previous_prefix = sys.pycache_prefix try: @@ -130,7 +136,10 @@ def run_suite(arguments: list[str], root: Path) -> int: sys.pycache_prefix = previous_prefix if result: return result - return 0 if observer.calls and set(observer.calls) == {"PASS"} and not observer.other else 1 + missing = expected - observer.executed_witnesses + if missing: + print("Declared witnesses did not execute:", sorted(str(path) + "::" + name for path, name in missing)) + return 0 if observer.calls and set(observer.calls) == {"PASS"} and not observer.other and not missing else 1 def main() -> int: @@ -171,4 +180,4 @@ def main() -> int: if __name__ == "__main__": raise SystemExit(main()) -# ratios: loc_comments=119:35 imports_exports=8:3 calls_definitions=49:8 +# ratios: loc_comments=128:35 imports_exports=9:3 calls_definitions=55:8 diff --git a/tools/verify_skill_lib_contracts.py b/tools/verify_skill_lib_contracts.py index d512785..9d71e46 100644 --- a/tools/verify_skill_lib_contracts.py +++ b/tools/verify_skill_lib_contracts.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=588:54 imports_exports=12:4 calls_definitions=317:23 +# ratios: loc_comments=595:54 imports_exports=12:4 calls_definitions=323:23 # === MODULE_BUILD === # id: skill_lib_contract_audit # module_name: verify_skill_lib_contracts @@ -611,6 +611,13 @@ def audit_repository(root: Path) -> Tuple[bool, List[str]]: if test_path.is_relative_to(root / "tests") and any(isinstance(node, ast.Name) and node.id == "pytest_plugins" for node in ast.walk(tree)): problems.append(f"GAP unsupported pytest plugin collection surface: {test_path}") if test_path.is_relative_to(root / "tests"): + for node in ast.walk(tree): + names = ([node.module] if isinstance(node, ast.ImportFrom) and node.module and not node.level + else [alias.name for alias in node.names] if isinstance(node, ast.Import) else []) + for name in names: + top = name.split(".", 1)[0] + if top not in {"src", "tools", "tests"} and ((root / (top + ".py")).exists() or (root / top).is_dir()): + problems.append(f"GAP imported root helper outside audited source layout: {name} in {test_path}") problems.extend(_collection_surface_problems(tree, test_path)) if not test_path.is_relative_to(root / "tests") or not ( test_path.name.startswith("test_") or test_path.name.endswith("_test.py") @@ -696,4 +703,4 @@ def main(argv: Sequence[str] | None = None) -> int: if __name__ == "__main__": raise SystemExit(main()) -# ratios: loc_comments=588:54 imports_exports=12:4 calls_definitions=317:23 +# ratios: loc_comments=595:54 imports_exports=12:4 calls_definitions=323:23 From 5c01f810943061019179949f8f2976b1782c4a9f Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Sat, 12 Sep 2026 09:14:25 +0000 Subject: [PATCH 63/65] Bind report to complete declared-witness execution --- docs/work-graphs/repository-plan-report.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/work-graphs/repository-plan-report.json b/docs/work-graphs/repository-plan-report.json index 8fca8bb..53c0dbd 100644 --- a/docs/work-graphs/repository-plan-report.json +++ b/docs/work-graphs/repository-plan-report.json @@ -10,7 +10,7 @@ }, "source": { "branch": "repair/ucns-audit-20260911", - "commit": "76d4eadaa85e74d03f7c9c45db08fdf1ba367a05", + "commit": "15e37de2d0a9c4984137bb14956242b15a995378", "generated_at": "2026-09-12", "note": "Describes the exact implementation commit preceding this report-only refresh. Maintenance PR #226 is in final exact-head review; merge remains a separate acceptance observation. No scientific or deployment authority is transferred." }, From af02fa61c225b2ada4cb36d1c8016fc81f94f1c0 Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Sat, 12 Sep 2026 09:35:07 +0000 Subject: [PATCH 64/65] Reconcile every collected parameter item with its call outcome --- README.md | 2 ++ tests/test_skill_lib_boundary_runner.py | 27 ++++++++++++++++--------- tools/_boundary_pytest.py | 24 ++++++++++++++++------ tools/run_skill_lib_boundaries.py | 9 +++++---- 4 files changed, 43 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 6d36284..dca6b4b 100644 --- a/README.md +++ b/README.md @@ -219,6 +219,8 @@ file/class/function naming. Unrecognized configuration keys fail closed. Repository-root helper imports outside the declared source layout fail the audit. The complete-suite gate also requires every statically declared top-level test to execute, so hiding a failing witness during collection cannot leave a passing gate. +Both full and selected execution also reconcile every collected pytest item, +including separate parameter invocations, against its actual call outcome. Conftest files and local plugin registration are outside this bounded collection contract. Changed settings, alternative pytest configuration files, and collection-changing `addopts` fail closed. Root `pyproject.toml` is diff --git a/tests/test_skill_lib_boundary_runner.py b/tests/test_skill_lib_boundary_runner.py index 079854a..201639c 100644 --- a/tests/test_skill_lib_boundary_runner.py +++ b/tests/test_skill_lib_boundary_runner.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=186:410 imports_exports=23:18 calls_definitions=241:20 +# ratios: loc_comments=195:410 imports_exports=23:18 calls_definitions=248:20 # === CHECKS === # id: check_boundary_runner_audit_gate # proves: boundary_runner_audits_before_execution @@ -224,7 +224,7 @@ def test_audit_gap_prevents_execution(tmp_path: Path) -> None: # proves: geometry_suite_requires_nonempty_pass # call: self::test_geometry_suite_rejects_nonpasses # requires: python3 -# timeout: 30 +# timeout: 90 # mutates: filesystem # cleanup: tempdir_teardown # === END CHECKS === @@ -235,6 +235,7 @@ def test_geometry_suite_rejects_nonpasses(tmp_path: Path) -> None: cases = ( ("pass", "def test_probe(): pass\n", True), ("skip", "import pytest\ndef test_probe(): pytest.skip('unobserved')\n", False), + ("marked-skip", "import pytest\n@pytest.mark.skip(reason='unobserved')\ndef test_probe(): pass\n", False), ("xfail", "import pytest\n@pytest.mark.xfail\ndef test_probe(): assert False\n", False), ("xpass", "import pytest\n@pytest.mark.xfail(strict=False)\ndef test_probe(): pass\n", False), ("empty", "# no executable checks\n", False), @@ -243,18 +244,26 @@ def test_geometry_suite_rejects_nonpasses(tmp_path: Path) -> None: ("module-mark", "import pytest\npytestmark = pytest.mark.skip\ndef test_probe(): assert False\n", False), ("hidden-witness", "def test_fails(): assert False\nimport hide_witness\ndef test_passes(): pass\n", False), ("removed-witness", "def test_first(request): request.session.items[:] = [request.node]\ndef test_fails(): assert False\n", False), + ("removed-parameter", "import pytest\n@pytest.mark.parametrize('value', [0, 1], ids=['a::b', 'failing'])\ndef test_probe(value, request):\n if value == 0: request.session.items[:] = [request.node]\n assert value == 0\n", False), ) script = "import os,sys; from pathlib import Path; sys.path.insert(0,sys.argv[1]); from tools._boundary_pytest import run_suite; root=Path(sys.argv[2]); os.chdir(root); raise SystemExit(run_suite(['tests','-c','pyproject.toml','--noconftest','--strict-config'],root))" environment = {key: value for key, value in os.environ.items() if key not in {"PYTHONPATH", "PYTHONHOME", "PYTEST_ADDOPTS", "PYTEST_PLUGINS"}} environment["PYTEST_DISABLE_PLUGIN_AUTOLOAD"] = "1" for label, body, expected_pass in cases: - root = _repo(tmp_path / label, body, []) + checks = [{"id": "check_probe", "function": "test_probe", "timeout": "15"}] if label in {"removed-parameter", "marked-skip"} else [] + root = _repo(tmp_path / label, body, checks) if label.startswith("collection-"): (root / "tests/test_other.py").write_text("def test_other(): pass\n") if label == "hidden-witness": (root / "hide_witness.py").write_text("import inspect\nfor frame in inspect.stack():\n witness = frame.frame.f_globals.get('test_fails')\n if witness is not None:\n witness.__test__ = False\n") result = subprocess.run([sys.executable, "-c", script, str(RUNNER_PATH.parents[1]), str(root)], env=environment, capture_output=True, text=True) assert (result.returncode == 0) is expected_pass, (label, result.stdout, result.stderr) + if label in {"removed-parameter", "marked-skip"}: + receipt = runner.run_boundaries(root) + assert receipt["audit_closed"] and receipt["status"] == "not-passed", json.dumps(receipt, indent=2) + assert receipt["outcomes"][0]["status"] == ("ERROR" if label == "removed-parameter" else "SKIP"), json.dumps(receipt, indent=2) + if label == "removed-parameter": + assert "did not all execute" in receipt["outcomes"][0]["diagnostic"] def test_missing_capability_and_timeout_are_enforced(tmp_path: Path) -> None: @@ -492,7 +501,7 @@ def test_source_mutation_prevents_acceptance(tmp_path: Path) -> None: # proves: boundary_runner_receipt_is_bounded_and_bound, boundary_pytest_observes_actual_outcomes, boundary_descendants_import_bound_source # call: self::test_check_imports_bound_source_despite_ambient_pythonpath # requires: python3, pytest -# timeout: 15 +# timeout: 30 # mutates: temporary_path # cleanup: pytest temporary_path # === END CHECKS === @@ -509,7 +518,7 @@ def test_source_mutation_prevents_acceptance(tmp_path: Path) -> None: def test_check_imports_bound_source_despite_ambient_pythonpath(tmp_path: Path, monkeypatch) -> None: body = "import pkg.feature\nimport subprocess, sys\ndef test_probe():\n assert pkg.feature.VALUE == 'bound'\n child = subprocess.check_output([sys.executable, '-c', 'import pkg.feature; print(pkg.feature.VALUE)'], text=True)\n assert child.strip() == 'bound'\n" - root = _repo(tmp_path, body, [{"id": "check_probe", "function": "test_probe"}]) + root = _repo(tmp_path, body, [{"id": "check_probe", "function": "test_probe", "timeout": "15"}]) source = root / "src/pkg/feature.py" source.write_text(source.read_text() + "VALUE = 'bound'\n") # A regular package avoids unrelated namespace packages in the host. @@ -523,7 +532,7 @@ def test_check_imports_bound_source_despite_ambient_pythonpath(tmp_path: Path, m test_source = root / "tests/test_feature.py" test_source.write_text(test_source.read_text().replace("text=True)", f"text=True, cwd={str(alternate)!r})")) receipt = runner.run_boundaries(root) - assert receipt["status"] == "passed", receipt + assert receipt["status"] == "passed", json.dumps(receipt, indent=2) assert receipt["outcomes"][0]["imported_sources"]["pkg.feature"] == [str(source)] # === CHECKS === @@ -541,7 +550,7 @@ def test_check_imports_bound_source_despite_ambient_pythonpath(tmp_path: Path, m import os import py_compile import subprocess - poisoned = _repo(tmp_path / "poisoned-cache", "def test_probe():\n from pkg.feature import VALUE\n assert VALUE == 1\n import subprocess, sys\n child = subprocess.run([sys.executable, '-c', 'from pkg.feature import VALUE; print(VALUE)'], check=True, capture_output=True, text=True)\n assert child.stdout.strip() == '1'\n", [{"id": "check_probe", "function": "test_probe"}]) + poisoned = _repo(tmp_path / "poisoned-cache", "def test_probe():\n from pkg.feature import VALUE\n assert VALUE == 1\n import subprocess, sys\n child = subprocess.run([sys.executable, '-c', 'from pkg.feature import VALUE; print(VALUE)'], check=True, capture_output=True, text=True)\n assert child.stdout.strip() == '1'\n", [{"id": "check_probe", "function": "test_probe", "timeout": "15"}]) module = poisoned / "src/pkg/feature.py" declarations = module.read_text() module.write_text(declarations + "\nVALUE = 2\n") @@ -555,7 +564,7 @@ def test_check_imports_bound_source_despite_ambient_pythonpath(tmp_path: Path, m old = subprocess.run([sys.executable, "-c", "from pkg.feature import VALUE; print(VALUE)"], env=ordinary, capture_output=True, text=True, check=True) assert old.stdout.strip() == "2", "fixture must contain an executable stale cache" observed = runner.run_boundaries(poisoned) - assert observed["status"] == "passed" and observed["source_unchanged"], observed + assert observed["status"] == "passed" and observed["source_unchanged"], json.dumps(observed, indent=2) assert not any("__pycache__" in name for name in observed["source_files_sha256"]) @@ -644,4 +653,4 @@ def test_node24_capability_runs_typescript_witness(tmp_path: Path) -> None: assert receipt["outcomes"][0]["status"] == "ERROR", receipt with pytest.raises(ProcessLookupError): os.kill(int(pid_path.read_text()), 0) -# ratios: loc_comments=186:410 imports_exports=23:18 calls_definitions=241:20 +# ratios: loc_comments=195:410 imports_exports=23:18 calls_definitions=248:20 diff --git a/tools/_boundary_pytest.py b/tools/_boundary_pytest.py index 6491c68..23d1dd5 100644 --- a/tools/_boundary_pytest.py +++ b/tools/_boundary_pytest.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=128:35 imports_exports=9:3 calls_definitions=55:8 +# ratios: loc_comments=138:35 imports_exports=10:3 calls_definitions=62:10 # === MODULE_BUILD === # id: boundary_pytest_observer # module_name: _boundary_pytest @@ -25,7 +25,7 @@ # # id: geometry_suite_requires_nonempty_pass # given: the full geometry suite runs through run_suite -# then: empty, unexecuted declared witnesses, skipped, xfailed, XPASS, failed, or collection-error evidence cannot produce exit status zero +# then: empty, unexecuted declared witnesses or collected parameter items, skipped, xfailed, XPASS, failed, or collection-error evidence cannot produce exit status zero # class: evidence # === END CONTRACTS === @@ -39,6 +39,7 @@ """ from __future__ import annotations +from collections import Counter import json from pathlib import Path import sys @@ -56,8 +57,16 @@ def __init__(self, root: Path): self.calls: list[str] = [] self.other: list[str] = [] self.executed_witnesses: set[tuple[Path, str]] = set() + self.collected_items: tuple[object, ...] = () + self.item_calls: Counter[int] = Counter() self.expected_code: dict[Path, CodeType] = {} + def pytest_collection_finish(self, session): + self.collected_items = tuple(session.items) + + def item_coverage_closed(self) -> bool: + return Counter(id(item) for item in self.collected_items) == self.item_calls + def _witness_matches_source(self, item) -> bool: path = Path(item.path).resolve() if not path.is_relative_to(self.root.resolve()): @@ -116,6 +125,7 @@ def pytest_runtest_makereport(self, item, call): status = "PASS" if call.when == "call": self.calls.append(status) + self.item_calls[id(item)] += 1 self.executed_witnesses.add((Path(item.path).resolve(), item.originalname or item.name.split("[", 1)[0])) elif status != "PASS": self.other.append(status) @@ -139,7 +149,9 @@ def run_suite(arguments: list[str], root: Path) -> int: missing = expected - observer.executed_witnesses if missing: print("Declared witnesses did not execute:", sorted(str(path) + "::" + name for path, name in missing)) - return 0 if observer.calls and set(observer.calls) == {"PASS"} and not observer.other and not missing else 1 + if not observer.item_coverage_closed(): + print("Collected test item execution differs:", [(item.nodeid, observer.item_calls[id(item)]) for item in observer.collected_items]) + return 0 if observer.calls and set(observer.calls) == {"PASS"} and not observer.other and not missing and observer.item_coverage_closed() else 1 def main() -> int: @@ -170,14 +182,14 @@ def main() -> int: status = "FAIL" elif "SKIP" in statuses: status = "SKIP" - elif observer.calls and exit_code == 0: + elif observer.calls and exit_code == 0 and observer.item_coverage_closed(): status = "PASS" else: status = "ERROR" - report_path.write_text(json.dumps({"status": status, "calls": observer.calls, "other": observer.other, "origins": origins, "wrong_origins": wrong_origins, "descendants_reaped": 0}), encoding="utf-8") + report_path.write_text(json.dumps({"status": status, "calls": observer.calls, "other": observer.other, "origins": origins, "wrong_origins": wrong_origins, "descendants_reaped": 0, "item_coverage_closed": observer.item_coverage_closed()}), encoding="utf-8") return exit_code if __name__ == "__main__": raise SystemExit(main()) -# ratios: loc_comments=128:35 imports_exports=9:3 calls_definitions=55:8 +# ratios: loc_comments=138:35 imports_exports=10:3 calls_definitions=62:10 diff --git a/tools/run_skill_lib_boundaries.py b/tools/run_skill_lib_boundaries.py index 6f564a3..0069ac4 100644 --- a/tools/run_skill_lib_boundaries.py +++ b/tools/run_skill_lib_boundaries.py @@ -1,4 +1,4 @@ -# ratios: loc_comments=421:71 imports_exports=20:4 calls_definitions=186:20 +# ratios: loc_comments=422:71 imports_exports=20:4 calls_definitions=187:20 # === MODULE_BUILD === # id: skill_lib_boundary_runner # module_name: run_skill_lib_boundaries @@ -301,7 +301,7 @@ def _pytest_outcome(path: Path, returncode: int) -> tuple[str, dict]: elif "SKIP" in statuses: status = "SKIP" else: - status = "PASS" if calls and returncode == 0 else "ERROR" + status = "PASS" if calls and returncode == 0 and observed.get("item_coverage_closed") is True else "ERROR" if observed.get("status") != status: return "ERROR", {} return status, observed @@ -401,7 +401,8 @@ def _execute_check(root: Path, check: Entry) -> CheckOutcome: command, requires, timeout, mutates, cleanup, status, returncode, duration, stdout_sha, stderr_sha, stdout_bytes, stderr_bytes, stdout_excerpt, stderr_excerpt, - diagnostic="background descendants outlived the check" if observed.get("descendants_reaped") else "", + diagnostic=("background descendants outlived the check" if observed.get("descendants_reaped") else + "collected pytest items did not all execute" if status == "ERROR" and observed.get("item_coverage_closed") is False else ""), descendants_reaped=observed.get("descendants_reaped", 0), imported_sources=observed.get("origins", {}), ) @@ -543,4 +544,4 @@ def main(argv: Sequence[str] | None = None) -> int: if __name__ == "__main__": raise SystemExit(main()) -# ratios: loc_comments=421:71 imports_exports=20:4 calls_definitions=186:20 +# ratios: loc_comments=422:71 imports_exports=20:4 calls_definitions=187:20 From 13c212e93e2868c41814695e28c2572f4c90c55f Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Sat, 12 Sep 2026 09:35:07 +0000 Subject: [PATCH 65/65] Bind report to per-item execution reconciliation --- docs/work-graphs/repository-plan-report.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/work-graphs/repository-plan-report.json b/docs/work-graphs/repository-plan-report.json index 53c0dbd..772552c 100644 --- a/docs/work-graphs/repository-plan-report.json +++ b/docs/work-graphs/repository-plan-report.json @@ -10,7 +10,7 @@ }, "source": { "branch": "repair/ucns-audit-20260911", - "commit": "15e37de2d0a9c4984137bb14956242b15a995378", + "commit": "af02fa61c225b2ada4cb36d1c8016fc81f94f1c0", "generated_at": "2026-09-12", "note": "Describes the exact implementation commit preceding this report-only refresh. Maintenance PR #226 is in final exact-head review; merge remains a separate acceptance observation. No scientific or deployment authority is transferred." },