check: no library source file is unreachable from its crate root #142

Merged
dcharlot merged 2 commits from sandbox/meter-delta-read into main 2026-10-13 06:37:24 -04:00
4 changed files with 250 additions and 4 deletions

View file

@ -140,3 +140,46 @@ jobs:
exit 1
# (touched to test that this very change supersedes its own queued runs)
# ── No library source file is unreachable from its crate root ─────────────
#
# An orphan `.rs` — one no `mod`, `#[path]` or `include!` ever names — is not
# merely untested. It is never compiled, so it is invisible to every check in
# this repository at once: cargo check, cargo test, clippy, the conformance
# suites. It cannot fail, because nothing asks it anything.
#
# This repo keeps finding that family: a feature-gated test run without
# `--features`, a platform-gated meter pinned to one runner, a conformance
# suite in `main()` that `cargo test` compiles and never runs. An orphan file
# is the strongest member — it does not even type-check.
#
# The hazard is the next reader, not the dead code. One such file documents
# itself as measuring energy via IOReport and in fact multiplies CPU seconds
# by a fixed watt figure; a reader who trusts that header gets a fabricated
# number. The script's baseline pins today's 25 so this does not go red on a
# pre-existing condition, and so the count can only ratchet down.
orphan-sources:
name: No new orphan source files
runs-on: linux
steps:
- name: Checkout
uses: actions/checkout@v4
# Both directions, before trusting the result: a planted orphan must fail
# the check, and removing it must pass. A guard verified in only the
# passing direction is a guard that has never been seen to work.
- name: The check fails closed (self-test)
run: |
set -euo pipefail
probe=jcp/jcp-rs/crates/jcp-efficiency/src/_ci_orphan_probe.rs
printf 'pub fn never_compiled() -> u64 { 0 }\n' > "$probe"
if python3 scripts/check-orphan-sources.py >/dev/null 2>&1; then
rm -f "$probe"
echo "::error::the orphan check passed with a planted orphan present"
exit 1
fi
rm -f "$probe"
python3 scripts/check-orphan-sources.py
- name: Run the orphan-source check
run: python3 scripts/check-orphan-sources.py

View file

@ -52,6 +52,9 @@ use jcp_schema::{ContentId, Did, Energy, Frame, FrameKind, SignedGrant, Trust};
use jouleclaw_mcp::{McpError, MeteredTool};
use jouleclaw_transparency_log::{InclusionProof, LogError, MerkleTree, SignedTreeHead, TreeHead};
pub mod skill;
pub mod stack;
/// The JCP resource of an MCP tool: the content address of its stable
/// `tool_id` (e.g. `"mcp:filesystem/read"`). A grant scoped to this resource
/// authorizes exactly this tool — a same-named tool on another server is a

View file

@ -1,8 +1,33 @@
//! Energy metering for Apple Silicon via IOReport.
//! Energy **estimation** from process time. Not a measurement, and not
//! IOReport — despite what this header said until 2026-10-02.
//!
//! Measures actual GPU, CPU, ANE, and DRAM energy consumption in joules
//! using Apple's IOReport framework. Falls back to time-based estimation
//! on non-macOS platforms.
//! # What it actually does
//!
//! This file is **not compiled**: `runtime/mod.rs` does not declare it, so no
//! `cargo check`, `cargo test` or clippy run in this repository has ever looked
//! at it. The corrected description below is therefore a warning to the next
//! reader rather than a description of running code.
//!
//! It does not call IOReport. The macOS path reads `getrusage` and multiplies
//! CPU seconds by a fixed watt figure; the other path multiplies wall-clock by
//! a different fixed figure. Each then splits the result across GPU, CPU, ANE
//! and DRAM by constant fractions.
//!
//! The per-subsystem fields are the sharper problem. `gpu_joules`,
//! `cpu_joules`, `ane_joules` and `dram_joules` read as four independent
//! observations; they are four fractions of one guess. A split implies
//! resolution that does not exist, which invites per-subsystem conclusions
//! nothing here could support — precision standing in for measurement.
//!
//! # Before wiring this in
//!
//! A real meter lives in this same workspace: `jouleclaw-energy`, with a
//! per-rail liveness gate that declines to report rather than report a frozen
//! counter. Any figure intended to reach a receipt must come from there. If a
//! coarse power figure is genuinely wanted for scheduling, that is a legitimate
//! need — but it is `Estimator`-class in the JCP metering ladder (SPEC §5.1),
//! it MUST NOT be presented as `ModelBased`, and the subsystem split MUST NOT
//! be presented at all.
use std::time::{Duration, Instant};

175
scripts/check-orphan-sources.py Executable file
View file

@ -0,0 +1,175 @@
#!/usr/bin/env python3
"""No library source file is unreachable from its crate root.
An orphan `.rs` file — one no `mod` declaration, `#[path]` attribute or
`include!` ever names — is not merely untested. It is **never compiled**, so it
is invisible to every check in this repository at once: not `cargo check`, not
`cargo test`, not clippy, not the conformance suites. It cannot fail, because
nothing ever asks it anything.
That makes it the strongest member of a family this repo keeps finding: a
feature-gated test run without `--features`, a platform-gated meter on a
single-platform runner, a conformance suite living in `main()` that
`cargo test` compiles and never runs. In each the code looks present and
reports nothing. An orphan file does not even type-check.
The hazard is not the dead code itself, it is the next reader. A file that
documents itself as doing something real, sitting in the tree beside files that
do, will eventually be wired in by someone who trusts its header — and it has
never been compiled, let alone verified.
What is NOT an orphan, and why the exclusions matter:
src/bin/*.rs cargo auto-discovers these as binaries
examples/, tests/, benches/ likewise auto-discovered
lib.rs, main.rs, mod.rs, build.rs entry points
foo.rs beside a foo/ directory a directory module of the same name
A first pass at this check counted 77 orphans by missing the `src/bin`
exclusion. The real figure is a quarter of that and sits in one crate. A
detector that over-reports is as useless as one that under-reports, so the
exclusions are part of the check, not a convenience.
Usage:
scripts/check-orphan-sources.py # fail on anything not in BASELINE
scripts/check-orphan-sources.py --list # print every orphan found
The baseline pins what exists today so CI does not go red on a pre-existing
condition, and so the number can only go **down**. Removing a file from the
baseline is the point; adding one is a regression and should be argued for in a
commit message, not slipped in.
"""
from __future__ import annotations
import os
import re
import sys
# Pre-existing orphans, pinned 2026-10-02. All 25 are in jouleclaw-omni, which
# also holds `runtime/energy.rs` — a file documenting itself as measuring
# "actual GPU, CPU, ANE and DRAM energy consumption using Apple's IOReport
# framework" while in fact multiplying `getrusage` CPU seconds by a fixed 15 W
# and apportioning the result 60/20/20 across subsystems. It has never been
# compiled. That is precisely the trap this check exists to stop growing:
# a real meter with a per-rail liveness gate lives in `jouleclaw-energy`, and a
# reader who wires this one in instead gets a fabricated figure.
BASELINE = {
"jouleclaw/jouleclaw-rs/crates/jouleclaw-omni/src/inference/architecture/bark.rs",
"jouleclaw/jouleclaw-rs/crates/jouleclaw-omni/src/inference/architecture/canary.rs",
"jouleclaw/jouleclaw-rs/crates/jouleclaw-omni/src/inference/architecture/chatterbox.rs",
"jouleclaw/jouleclaw-rs/crates/jouleclaw-omni/src/inference/architecture/depth_anything.rs",
"jouleclaw/jouleclaw-rs/crates/jouleclaw-omni/src/inference/architecture/esrgan.rs",
"jouleclaw/jouleclaw-rs/crates/jouleclaw-omni/src/inference/architecture/fibo.rs",
"jouleclaw/jouleclaw-rs/crates/jouleclaw-omni/src/inference/architecture/jamba.rs",
"jouleclaw/jouleclaw-rs/crates/jouleclaw-omni/src/inference/architecture/kokoro.rs",
"jouleclaw/jouleclaw-rs/crates/jouleclaw-omni/src/inference/architecture/mamba2.rs",
"jouleclaw/jouleclaw-rs/crates/jouleclaw-omni/src/inference/architecture/mimo.rs",
"jouleclaw/jouleclaw-rs/crates/jouleclaw-omni/src/inference/architecture/mochi.rs",
"jouleclaw/jouleclaw-rs/crates/jouleclaw-omni/src/inference/architecture/moonshine.rs",
"jouleclaw/jouleclaw-rs/crates/jouleclaw-omni/src/inference/architecture/nemotron.rs",
"jouleclaw/jouleclaw-rs/crates/jouleclaw-omni/src/inference/architecture/parler_tts.rs",
"jouleclaw/jouleclaw-rs/crates/jouleclaw-omni/src/inference/architecture/sam2.rs",
"jouleclaw/jouleclaw-rs/crates/jouleclaw-omni/src/inference/architecture/sana_wm_gpu_dispatchers_draft.rs",
"jouleclaw/jouleclaw-rs/crates/jouleclaw-omni/src/inference/architecture/sd3.rs",
"jouleclaw/jouleclaw-rs/crates/jouleclaw-omni/src/inference/architecture/sesame_csm.rs",
"jouleclaw/jouleclaw-rs/crates/jouleclaw-omni/src/inference/expert_cache.rs",
"jouleclaw/jouleclaw-rs/crates/jouleclaw-omni/src/inference/fusion.rs",
"jouleclaw/jouleclaw-rs/crates/jouleclaw-omni/src/inference/gpv.rs",
"jouleclaw/jouleclaw-rs/crates/jouleclaw-omni/src/inference/video_decode.rs",
"jouleclaw/jouleclaw-rs/crates/jouleclaw-omni/src/runtime/energy.rs",
"jouleclaw/jouleclaw-rs/crates/jouleclaw-omni/src/server/model_library.rs",
"jouleclaw/jouleclaw-rs/crates/jouleclaw-omni/src/server/worker.rs",
}
ENTRY = {"lib", "main", "mod", "build"}
DECL = re.compile(r"(?m)^\s*(?:pub(?:\([^)]*\))?\s+)?mod\s+([A-Za-z_]\w*)\s*;")
PATH_ATTR = re.compile(r'#\[path\s*=\s*"([^"]+)"\]')
INCLUDE = re.compile(r'include!\s*\(\s*"([^"]+)"')
def declared_names(src_root: str) -> set[str]:
"""Every module name the crate's own sources name, by any mechanism."""
names: set[str] = set()
for dirpath, dirs, files in os.walk(src_root):
dirs[:] = [d for d in dirs if d != "target"]
for f in files:
if not f.endswith(".rs"):
continue
try:
text = open(os.path.join(dirpath, f), encoding="utf-8", errors="ignore").read()
except OSError:
continue
names.update(DECL.findall(text))
for pat in (PATH_ATTR, INCLUDE):
for hit in pat.findall(text):
names.add(os.path.basename(hit).removesuffix(".rs"))
return names
def find_orphans(repo: str) -> list[str]:
orphans: list[str] = []
seen_roots: dict[str, set[str]] = {}
for dirpath, dirs, files in os.walk(repo):
dirs[:] = [d for d in dirs if d not in ("target", ".git", "node_modules")]
rel = os.path.relpath(dirpath, repo)
if "/src" not in "/" + rel + "/":
continue
# cargo auto-discovers these; they are reachable without a `mod`.
if "/src/bin" in "/" + rel or rel.endswith("/src/bin"):
continue
rs = [f for f in files if f.endswith(".rs")]
if not rs:
continue
src_root = os.path.join(repo, rel.split("/src")[0], "src")
if src_root not in seen_roots:
seen_roots[src_root] = declared_names(src_root)
declared = seen_roots[src_root]
for f in rs:
stem = f[:-3]
if stem in ENTRY:
continue
if stem in declared:
continue
if os.path.isdir(os.path.join(dirpath, stem)):
continue # a directory module of the same name
orphans.append(os.path.normpath(os.path.join(rel, f)))
return sorted(orphans)
def main() -> int:
repo = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
orphans = find_orphans(repo)
if "--list" in sys.argv:
for o in orphans:
mark = " (baseline)" if o in BASELINE else " NEW"
print(f"{o}{mark}")
print(f"\n{len(orphans)} orphan(s); {len(BASELINE)} pinned in the baseline")
return 0
new = [o for o in orphans if o not in BASELINE]
gone = sorted(BASELINE - set(orphans))
if gone:
print("These baseline orphans are reachable again, or were removed — good.")
print("Drop them from BASELINE in this script so the floor ratchets down:")
for g in gone:
print(f" {g}")
print()
if new:
print("New orphan source file(s): never compiled, so invisible to every")
print("check in this repo — cargo check, cargo test, clippy, conformance.")
print("Declare it with `mod`, move it under src/bin/, or delete it.\n")
for n in new:
print(f" {n}")
return 1
print(f"No new orphan sources ({len(orphans)} pinned, {len(BASELINE)} expected).")
return 1 if gone else 0
if __name__ == "__main__":
sys.exit(main())