scripts.attribute_traces_runtime
Reference › scripts › attribute_traces_runtime
Attributes requirements to code by what actually ran: runtime execution evidence for the requirements traceability matrix. It reads a combined coverage database in which each executed line is tagged with the test that ran it. A human or the RTM pipeline runs it; it imports harvest_src_declarations from the generate_rtm_evidence script at run time.
Invocation: --coverage-db is required. Others are --specs (default specs), --src (default src/orchestrata_ultimate), --tests (default tests), --json-out (default docs/trace-attribution.json), --top-n and --ubiquity-cap.
verify_ctrace_core checks that the coverage data was recorded with the compiled tracer core and refuses with a CoverageCoreError (exit 1) if not. test_context_citations maps each test to the requirement ids it cites, executed_defs_by_context maps each test context to the public definitions it executed (public_defs, span), and build_attribution joins these with the @traces declarations in source. Two measures keep results meaningful: breadth (how many tests execute a definition) and spread, used with the ubiquity cap so code that every test touches does not claim every requirement. Each requirement gets a tier, main counts the tiers, and the result is written as JSON.
At a glance
| Source | scripts/attribute_traces_runtime.py |
| Lines of code | 406 |
| Classes | 1 |
| Functions | 6 |
| Imports (internal) | none |
| Imported by | none |
| Tests that import it | none |
Class diagram
Dependency diagram
No internal imports and no importers.
API
Classes
CoverageCoreError
Bases: RuntimeError · Line 96
Raised when a coverage db does not carry the ctrace re-recording signature this tool depends on (report §3.2's hard gate).
Functions
| Function | Signature | Summary |
|---|---|---|
verify_ctrace_core |
verify_ctrace_core(data: CoverageData) -> None |
Refuse coverage data with no evidence it was collected under COVERAGE_CORE=ctrace. |
public_defs |
public_defs(py_file: Path) -> list[tuple[str, int, int]] |
[(qualname, start_line, end_line)] for public module-level functions and public methods of public classes in py_file. |
test_context_citations |
test_context_citations(tests_dir: Path, reqs: dict[str, dict]) -> dict[str, set[str]] |
{nodeid (no '|run' suffix): {req ids cited by that test}}. |
executed_defs_by_context |
executed_defs_by_context(data: CoverageData, src_dir: Path) -> tuple[dict[str, set[str]], set[str]] |
(def_qualname -> {context nodeid, ...}, all |run nodeids). |
build_attribution |
build_attribution(reqs: dict[str, dict], decls: dict[str, list[dict]], test_reqs: dict[str, set[str]], executed_by: dict[str, set[str]], all_run_nodeids: set[str], top_n: int = _TOP_N, ubiquity_cap: float = _UBIQUITY_CAP) -> dict[str, dict] |
The scored join (report §5.2). |
main |
main() -> int |
Source
"""Runtime execution-evidence attribution for the RTM (BDD runtime-tracing
Phase 1, ``plans/bdd-runtime-tracing-plan.md``).
Joins per-test coverage attribution (which lines each test's ``|run`` phase
actually executed) against the citation harvest (which requirements each
test claims to verify, ``generate_rtm_evidence.harvest_test_citations``) to
produce deterministic, zero-LLM (requirement -> def) edges with provenance.
This is strictly execution evidence: no judgment, no keyword matching.
Evidence chain (report §5.1, ``reports/research-bdd-runtime-tracing-rtm.md``):
requirement R --cited by--> test T (docstring/comment or @traces-on-test
decorator; GHOST ids excluded)
test T --executed (|run phase)--> def M (coverage dynamic context,
line -> enclosing PUBLIC def via AST)
=> (M, R) candidate edge, provenance=runtime
HARD GATE -- ctrace core only (report §3.2): coverage.py does NOT persist
which core (ctrace/sysmon/pytrace) collected a ``.coverage`` db -- there is
no stored flag to read back. But sysmon's PEP 669 per-line event disabling
means it never re-records a line under a NEW dynamic context once ANY
context has recorded it, so any code shared by more than one test silently
loses attribution for every test after the first. That is a structural
fingerprint we CAN read back deterministically: if the db has >=2 '|run'
contexts but no line anywhere is ever recorded under more than one of them,
the db was not collected under ctrace (or is otherwise unusable) and this
tool refuses. Verified empirically this session (scratchpad covtest2/): a
helper called by 3 tests recorded 3 contexts under ctrace and exactly 1
(the first caller) under sysmon.
Scoring (report §5.2), for candidate edge (M, R):
support(M,R) = # distinct test contexts citing R whose |run phase
executed M
breadth(M) = # distinct reqs whose citing tests executed M (IDF)
spread(R) = # distinct defs executed by R's citing tests (IDF)
ubiquity(M) = fraction of ALL |run contexts (cited or not) executing M
score(M,R) = support / (log2(1+breadth) * log2(1+spread))
Hard filters (all from the report, validated against the pilot):
1. Public defs only -- private helpers/dunders inherit their class/module
trace (``plans/trace-remediation-plan.md`` W5 ruling).
2. Infra lane excluded -- reuse ``_INFRA_LANE`` from
``scripts/add_traces_from_evidence.py``.
3. Ubiquity cap: drop M when ubiquity(M) > 0.5 (kills conftest/fixture
plumbing every test touches).
4. Phase filter: ``|run`` contexts only.
5. Top-N=3 proposals per requirement (most-discriminative defs by score).
Output tiers, per edge:
CONFIRMED static @traces id on M ∩ a runtime edge (M, R).
RUNTIME-CANDIDATE runtime edge (M, R), no static id on M for R, score
surviving the filters -- a nomination, not a write.
STATIC-UNCONFIRMED R has a static @traces id (on some def D) and citing
tests exist in the coverage db, but none of them
executed D. NOTE: pass/fail is not an input to this
join (the coverage db alone doesn't carry it) --
combine with ``generate_rtm_evidence.py --verify``'s
FAILING grade to fully match report §5.3's
"citing tests all passed" wording. Flag-only, never
auto-stripped (D3 pending).
STATIC-UNTESTABLE R has a static @traces id but no citing tests at all
in this harvest -- not evidence of anything.
Usage:
python scripts/attribute_traces_runtime.py --coverage-db .coverage
python scripts/attribute_traces_runtime.py --coverage-db .coverage \\
--json-out docs/trace-attribution.json --top-n 3
"""
from __future__ import annotations
import argparse
import ast
import json
import math
import sys
from collections import defaultdict
from pathlib import Path
from coverage import CoverageData # noqa: E402 (third-party; declared in pyproject)
sys.path.insert(0, str(Path(__file__).parent))
from add_traces_from_evidence import _INFRA_LANE # noqa: E402
from generate_rtm_evidence import (
harvest_test_citations,
load_requirements,
) # noqa: E402
_UBIQUITY_CAP = 0.5
_TOP_N = 3
class CoverageCoreError(RuntimeError):
"""Raised when a coverage db does not carry the ctrace re-recording
signature this tool depends on (report §3.2's hard gate)."""
def verify_ctrace_core(data: CoverageData) -> None:
"""Refuse coverage data with no evidence it was collected under
``COVERAGE_CORE=ctrace``.
coverage.py stores no "which core" flag -- this checks the one thing
that structurally differs between cores: whether ANY line, anywhere in
the measured files, was ever recorded under more than one '|run'
context. ctrace re-records every context that executes a line; sysmon
(PEP 669) records only the first. A real suite with >=2 tests that
share any code path (helpers, base classes, fixtures) will show
overlap under ctrace; sysmon shows none, ever.
Raises:
CoverageCoreError: fewer than 2 '|run' contexts to compare, or no
line anywhere was ever shared by more than one '|run' context.
"""
run_contexts = {c for c in data.measured_contexts() if c.endswith("|run")}
if len(run_contexts) < 2:
raise CoverageCoreError(
"coverage db has fewer than 2 '|run' test contexts -- cannot "
"verify the ctrace re-recording signature (need >=2 contexts "
"touching shared code). Re-collect with --cov-context=test "
"over more than one test."
)
for filename in data.measured_files():
contexts_by_line = data.contexts_by_lineno(filename)
for contexts in contexts_by_line.values():
if sum(1 for c in contexts if c.endswith("|run")) > 1:
return # proof of cross-context re-recording: ctrace confirmed
raise CoverageCoreError(
"coverage db looks like it was collected under COVERAGE_CORE=sysmon "
"(or an equivalent non-re-recording core): no line anywhere in the "
"measured files was ever recorded under more than one '|run' "
"context. sysmon (PEP 669) silently drops per-test attribution for "
"any code exercised by more than one test (report §3.2). Re-collect "
"with COVERAGE_CORE=ctrace."
)
def public_defs(py_file: Path) -> list[tuple[str, int, int]]:
"""[(qualname, start_line, end_line)] for public module-level functions
and public methods of public classes in ``py_file``.
Public = name does not start with "_" (W5 ruling: private helpers and
dunders inherit their class/module trace, never attributed on their
own). ``start_line`` includes any decorator lines so an executed
decorator call still counts as "inside" the def.
"""
try:
tree = ast.parse(py_file.read_text(encoding="utf-8"))
except OSError, SyntaxError, UnicodeDecodeError:
return []
out: list[tuple[str, int, int]] = []
def span(node: ast.FunctionDef | ast.AsyncFunctionDef) -> tuple[int, int]:
start = min([node.lineno] + [d.lineno for d in node.decorator_list])
return start, node.end_lineno or node.lineno
for node in tree.body:
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
if node.name.startswith("_"):
continue
start, end = span(node)
out.append((node.name, start, end))
elif isinstance(node, ast.ClassDef) and not node.name.startswith("_"):
for item in node.body:
if isinstance(
item, (ast.FunctionDef, ast.AsyncFunctionDef)
) and not item.name.startswith("_"):
start, end = span(item)
out.append((f"{node.name}.{item.name}", start, end))
return out
def test_context_citations(
tests_dir: Path, reqs: dict[str, dict]
) -> dict[str, set[str]]:
"""{nodeid (no '|run' suffix): {req ids cited by that test}}.
Built from ``harvest_test_citations`` (docstring + decorator provenance,
already merged there) via the same enclosing-span logic
``add_traces_from_evidence.enclosing_test_functions`` uses, reused
rather than reimplemented. GHOST ids (cited but defined in no spec) are
dropped here -- they can never be adjudicated by this join.
"""
from add_traces_from_evidence import enclosing_test_functions
cites = harvest_test_citations(tests_dir)
lines_by_file: dict[str, dict[int, set[str]]] = defaultdict(
lambda: defaultdict(set)
)
for rid, occs in cites.items():
if rid not in reqs:
continue
for occ in occs:
lines_by_file[occ["file"]][occ["line"]].add(rid)
out: dict[str, set[str]] = {}
for test_file, line_map in lines_by_file.items():
for (tname, tcls), rids in enclosing_test_functions(
Path(test_file), line_map
).items():
qual = f"{test_file}::{tcls}::{tname}" if tcls else f"{test_file}::{tname}"
out[qual] = rids
return out
def executed_defs_by_context(
data: CoverageData, src_dir: Path
) -> tuple[dict[str, set[str]], set[str]]:
"""(def_qualname -> {context nodeid, ...}, all |run nodeids).
Returns ``executed_by[def] = {nodeid, ...}`` (which test contexts
executed that public def's lines) and the set of ALL distinct |run
nodeids in the db (for the ubiquity denominator). Infra-lane files
(``_INFRA_LANE``) are never indexed -- hard filter #2.
"""
executed_by: dict[str, set[str]] = defaultdict(set)
all_run_nodeids: set[str] = set()
src_root = src_dir.resolve()
cwd = Path.cwd()
for filename in sorted(data.measured_files()):
abspath = Path(filename).resolve()
try:
abspath.relative_to(src_root)
except ValueError:
continue # outside --src entirely, not a candidate
try:
rel_to_cwd = str(abspath.relative_to(cwd))
except ValueError:
rel_to_cwd = str(abspath)
# _INFRA_LANE prefixes are repo-root-relative (e.g.
# "src/orchestrata_ultimate/core/base/"), not --src-relative.
if any(rel_to_cwd.startswith(lane) for lane in _INFRA_LANE):
continue
defs = public_defs(abspath)
if not defs:
continue
contexts_by_line = data.contexts_by_lineno(filename)
for lineno, contexts in contexts_by_line.items():
run_ctxs = {c.rsplit("|", 1)[0] for c in contexts if c.endswith("|run")}
all_run_nodeids |= run_ctxs
if not run_ctxs:
continue
for qualname, start, end in defs:
if start <= lineno <= end:
key = f"{abspath}:{qualname}"
executed_by[key] |= run_ctxs
return dict(executed_by), all_run_nodeids
def build_attribution(
reqs: dict[str, dict],
decls: dict[str, list[dict]],
test_reqs: dict[str, set[str]],
executed_by: dict[str, set[str]],
all_run_nodeids: set[str],
top_n: int = _TOP_N,
ubiquity_cap: float = _UBIQUITY_CAP,
) -> dict[str, dict]:
"""The scored join (report §5.2). Deterministic: sorted inputs in,
sorted outputs out."""
total_contexts = len(all_run_nodeids) or 1
# citing_contexts[R] = {nodeid, ...} whose citation set contains R --
# restricted to nodeids that actually ran as PART OF THIS coverage db.
# A test that cites R but never executed here (e.g. this run measured
# only a package slice) gives no execution evidence either way for R;
# counting it as "cited" would produce false STATIC-UNCONFIRMED flags
# any time the coverage db doesn't happen to cover the whole suite.
citing_contexts: dict[str, set[str]] = defaultdict(set)
for nodeid, rids in test_reqs.items():
if nodeid not in all_run_nodeids:
continue
for rid in rids:
citing_contexts[rid].add(nodeid)
# ubiquity filter, applied once
survivors = {
d: ctxs
for d, ctxs in executed_by.items()
if len(ctxs) / total_contexts <= ubiquity_cap
}
def breadth(defname: str) -> int:
ctxs = survivors.get(defname, set())
return len({rid for c in ctxs for rid in test_reqs.get(c, ())})
def spread(rid: str) -> int:
ctxs = citing_contexts.get(rid, set())
return len({d for d, dctxs in survivors.items() if ctxs & dctxs})
# Normalize to the SAME resolved-absolute-path form ``executed_by`` keys
# use (harvest_src_declarations reports file paths relative to however
# --src was invoked; coverage records its own, possibly-absolute form --
# without resolving both, CONFIRMED would never match).
declared_defs: dict[str, set[str]] = defaultdict(set)
for rid, occs in decls.items():
for occ in occs:
declared_defs[rid].add(f"{Path(occ['file']).resolve()}:{occ['name']}")
result: dict[str, dict] = {}
for rid in sorted(reqs):
cited = citing_contexts.get(rid, set())
declared = declared_defs.get(rid, set())
if not cited and not declared:
continue
if not cited:
result[rid] = {"tier": "STATIC-UNTESTABLE", "edges": []}
continue
sp = spread(rid) or 1
candidates: list[dict] = []
for defname, ctxs in survivors.items():
support = len(cited & ctxs)
if support == 0:
continue
br = breadth(defname) or 1
score = support / (math.log2(1 + br) * math.log2(1 + sp))
candidates.append(
{
"def": defname,
"tests": sorted(cited & ctxs),
"support": support,
"breadth": br,
"spread": sp,
"score": round(score, 6),
}
)
candidates.sort(key=lambda e: (-e["score"], e["def"]))
top = candidates[:top_n]
confirmed = [e for e in top if e["def"] in declared]
if confirmed:
result[rid] = {"tier": "CONFIRMED", "edges": confirmed}
elif declared:
# static id exists, citing tests ran, none executed the
# declared def(s) -- the wrong-trace / STATIC-UNCONFIRMED flag.
result[rid] = {
"tier": "STATIC-UNCONFIRMED",
"edges": [{"def": d, "tests": sorted(cited)} for d in sorted(declared)],
}
elif top:
result[rid] = {"tier": "RUNTIME-CANDIDATE", "edges": top}
else:
result[rid] = {
"tier": "STATIC-UNTESTABLE" if declared else "UNLINKED",
"edges": [],
}
return result
def main() -> int:
ap = argparse.ArgumentParser(description=(__doc__ or "").splitlines()[0])
ap.add_argument("--coverage-db", required=True, help="Combined .coverage db path")
ap.add_argument("--specs", default="specs")
ap.add_argument("--src", default="src/orchestrata_ultimate")
ap.add_argument("--tests", default="tests")
ap.add_argument("--json-out", default="docs/trace-attribution.json")
ap.add_argument("--top-n", type=int, default=_TOP_N)
ap.add_argument("--ubiquity-cap", type=float, default=_UBIQUITY_CAP)
args = ap.parse_args()
data = CoverageData(basename=args.coverage_db)
data.read()
try:
verify_ctrace_core(data)
except CoverageCoreError as exc:
print(f"REFUSED: {exc}", file=sys.stderr)
return 1
from generate_rtm_evidence import harvest_src_declarations
reqs = load_requirements(Path(args.specs))
decls = harvest_src_declarations(Path(args.src))
test_reqs = test_context_citations(Path(args.tests), reqs)
executed_by, all_run_nodeids = executed_defs_by_context(data, Path(args.src))
attribution = build_attribution(
reqs,
decls,
test_reqs,
executed_by,
all_run_nodeids,
top_n=args.top_n,
ubiquity_cap=args.ubiquity_cap,
)
tiers: dict[str, int] = defaultdict(int)
for row in attribution.values():
tiers[row["tier"]] += 1
out = {
"coverage_db": args.coverage_db,
"run_contexts_total": len(all_run_nodeids),
"requirements": attribution,
"tier_counts": dict(sorted(tiers.items())),
}
Path(args.json_out).write_text(json.dumps(out, indent=1, sort_keys=True))
print(
f"{len(attribution)} requirements attributed -> "
+ ", ".join(f"{t}={n}" for t, n in sorted(tiers.items()))
)
print(f"wrote {args.json_out}")
return 0
if __name__ == "__main__":
sys.exit(main())