Eleven rows corrected, in both directions. Overstated: AR-011 (the derived dedup window reached scenes.json only), AR-017 (route was a literal), AR-019 (the local plurality tally was still deciding), AR-024 (its "static check" enforcement did not exist), DP-001 (scene_preview had forked and stopped compiling), VR-002 (the Python replay bindings have not compiled since the tracker redesign, and the fixtures it calls committed are gitignored registry artifacts). Understated: VR-010 was marked Planned while five VR-010 tags sat in the code implementing it. Rescoped: VR-007 now names the four AR-025 constants it was already being deferred to for, and which no sweep could reach until this pass. The Withdrawn note gets the longest correction, because it asserted a removal that had not happened and nothing could have caught that: the gate reads tags, and a withdrawn requirement has no tag to be orphaned. The general form is now written down there -- a status column is a claim, and the only claims this project checks automatically are the ones a test or a static check makes. Four of the rows above are the same pattern: recorded as done, and done in one place out of two. New: VR-016, a cadence study for cut_threshold. It is the one always-on signal with no recorded provenance, and its input rate depends on an unrelated flag -- with --scene-detect off, camera_pos compares frames a full second apart at the default sample_fps, and with it on, native-rate frames. Same constant, two meanings, and is_cut drives track_alpha to 0 and clears every expansion buffer. Also stops check_raw_cosine.py inflating its own metric: the extractor scans scripts/, so the tool's prose describing the exception tag was counted as four recorded exceptions. The count now reads 1, which is the number of real ones. TRACES: AR-011, AR-017, AR-019, AR-024, AR-025 | DP-001, DP-007 | IR-004 | VR-002, VR-007, VR-010, VR-016
226 lines
9.2 KiB
Python
Executable File
226 lines
9.2 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Enforce the AR-024 invariant: never a raw cosine, always the calibration.
|
|
|
|
TRACES: AR-024 | SR-002
|
|
|
|
docs/requirements.md gives AR-024's verification tier as "Static check -- no
|
|
bare cosine outside a tagged EXCEPTION | Grep-based; this is the invariant's
|
|
enforcement". This is that check. Until it existed the invariant was enforced
|
|
by reading, and reading missed a live violation: the identity matcher's
|
|
no-calibration fallback thresholded raw cosine distance and fed `max(0, cosine)`
|
|
into the Bayesian accumulation as though it were a posterior.
|
|
|
|
WHAT IT CHECKS, precisely, because a static check that overclaims its reach is
|
|
worse than one with a stated scope:
|
|
|
|
Every call to `cosine_similarity(...)` in C++ source must either
|
|
|
|
(a) have its result consumed immediately by a calibration -- the call is
|
|
textually wrapped in `cal_(...)`, `calibrate_(...)`, `.probability(...)`
|
|
or similar; or
|
|
(b) sit under an exception comment -- the token is `EXCEPTION:` followed by
|
|
`AR-024` and a reason -- within EXCEPTION_SCOPE_LINES above it.
|
|
|
|
Note that this file deliberately never spells that token out. The traceability
|
|
extractor scans scripts/ as source, so prose here describing the tag would be
|
|
counted as recorded exceptions; four of them were, until this was noticed. The
|
|
same trap the shared config warns about for the vendored parser tests.
|
|
|
|
Anything else is a defect, per CLAUDE.md: "treat any bare cosine comparison in
|
|
the code as a defect to be fixed".
|
|
|
|
WHAT IT DOES NOT CHECK, and why you should not read a pass as more than it is:
|
|
|
|
- It cannot follow a cosine through a variable across statements. A file that
|
|
stores `float s = cosine_similarity(a, b);` and compares `s` three lines
|
|
later is not caught. The codebase does not currently do this, and this check
|
|
exists partly to keep it that way, but it is a convention backed by review,
|
|
not by the tool.
|
|
- It says nothing about GEMM output. The similarity engine returns a whole
|
|
matrix of cosines and the matcher reads them directly; that path is correct
|
|
by inspection (every value goes through `cal_.probability`) and is not
|
|
verified here.
|
|
- A retired constant reintroduced under a new name is invisible to it.
|
|
|
|
Exit status is 0 when clean, 1 when a violation is found, 2 on a usage error.
|
|
"""
|
|
|
|
import argparse
|
|
import pathlib
|
|
import re
|
|
import sys
|
|
|
|
# How far above a use an exception tag may sit and still cover it.
|
|
# Generous, because the house style puts a paragraph of reasoning between the
|
|
# tag and the code -- but bounded, so a tag cannot silently cover a whole file.
|
|
EXCEPTION_SCOPE_LINES = 25
|
|
|
|
CPP_SUFFIXES = {".h", ".hpp", ".hxx", ".cc", ".cpp", ".cxx", ".cu", ".cuh"}
|
|
|
|
# src only, deliberately. The invariant governs what the PIPELINE decides --
|
|
# CLAUDE.md's rule is "tag the unit that decides" -- whereas a test legitimately
|
|
# asserts properties of the metric space itself (that a vector's cosine with
|
|
# itself is 1, that the annex ended up holding the spoke it should have). Those
|
|
# are measurements of the code under test, not decisions shipped to a user, and
|
|
# sweeping them in would produce a wall of blanket EXCEPTION tags that would
|
|
# devalue the tag everywhere else. Pass --source-root tests to scan them anyway.
|
|
DEFAULT_ROOTS = ["src"]
|
|
|
|
# Directories that are never this repo's code.
|
|
EXCLUDE_DIRS = {
|
|
"build", "build-ort", "external", "vendor", "__pycache__",
|
|
".git", "node_modules", "models",
|
|
}
|
|
|
|
COSINE_CALL = re.compile(r"\bcosine_similarity\s*\(")
|
|
|
|
# The result is immediately handed to a calibration. Matches the house shapes:
|
|
# cal_(cosine_similarity(a, b))
|
|
# calibrate_(cosine_similarity(a, b))
|
|
# same_person(cosine_similarity(a, b))
|
|
# cal_.probability(cosine_similarity(a, b))
|
|
CALIBRATED = re.compile(
|
|
r"(?:\b(?:cal_|cal|calibrate_|calibrate|same_person|same_person_probability)"
|
|
r"\s*(?:\.\s*probability\s*)?\(\s*|\.\s*probability\s*\(\s*)"
|
|
r"cosine_similarity\s*\("
|
|
)
|
|
|
|
EXCEPTION_TAG = re.compile(r"EXCEPT" + r"ION:\s*AR-" + r"024\b(.*)")
|
|
|
|
# The function's own definition is not a use of it.
|
|
DEFINITION = re.compile(r"^\s*(?:inline\s+|static\s+|constexpr\s+)*float\s+"
|
|
r"cosine_similarity\s*\(")
|
|
|
|
# The house style wraps long calls across lines:
|
|
# const float p = calibrate_(
|
|
# cosine_similarity(a, b));
|
|
# so the calibration and the call it guards are not always on one line. Joining
|
|
# a small window before testing is what makes this check usable on real code
|
|
# rather than a generator of false positives that trains people to ignore it.
|
|
JOIN_LOOKBEHIND = 2
|
|
|
|
|
|
def iter_sources(root: pathlib.Path, roots):
|
|
for rel in roots:
|
|
base = root / rel
|
|
if not base.exists():
|
|
continue
|
|
for p in sorted(base.rglob("*")):
|
|
if p.suffix.lower() not in CPP_SUFFIXES:
|
|
continue
|
|
if any(part in EXCLUDE_DIRS for part in p.relative_to(root).parts):
|
|
continue
|
|
yield p
|
|
|
|
|
|
def covering_exception(lines, idx):
|
|
"""Return the reason text of an exception tag covering line `idx`."""
|
|
lo = max(0, idx - EXCEPTION_SCOPE_LINES)
|
|
for j in range(idx, lo - 1, -1):
|
|
m = EXCEPTION_TAG.search(lines[j])
|
|
if m:
|
|
return m.group(1).strip(" -—*/") or "(no reason given)"
|
|
return None
|
|
|
|
|
|
def check_file(path: pathlib.Path, root: pathlib.Path):
|
|
violations, exceptions = [], []
|
|
try:
|
|
lines = path.read_text(encoding="utf-8", errors="replace").splitlines()
|
|
except OSError as e:
|
|
print(f"error: cannot read {path}: {e}", file=sys.stderr)
|
|
return violations, exceptions
|
|
|
|
rel = path.relative_to(root)
|
|
for i, line in enumerate(lines):
|
|
if not COSINE_CALL.search(line):
|
|
continue
|
|
# A comment mentioning the function is prose, not a use.
|
|
stripped = line.lstrip()
|
|
if stripped.startswith(("//", "///", "*", "/*")):
|
|
continue
|
|
if DEFINITION.match(line):
|
|
continue
|
|
# Join a small window so a call wrapped across lines is still seen as
|
|
# calibrated. Whitespace is collapsed so the join reads as one statement.
|
|
window = " ".join(
|
|
lines[max(0, i - JOIN_LOOKBEHIND):i + 1]
|
|
)
|
|
window = re.sub(r"\s+", " ", window)
|
|
if CALIBRATED.search(window):
|
|
continue
|
|
reason = covering_exception(lines, i)
|
|
if reason:
|
|
exceptions.append((rel, i + 1, line.strip(), reason))
|
|
else:
|
|
violations.append((rel, i + 1, line.strip()))
|
|
return violations, exceptions
|
|
|
|
|
|
def main():
|
|
ap = argparse.ArgumentParser(description=__doc__,
|
|
formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
ap.add_argument("--root", default=None,
|
|
help="repository root (default: the script's ../..)")
|
|
ap.add_argument("--source-root", action="append", default=None,
|
|
help="directory to scan; repeatable (default: src, tests)")
|
|
args = ap.parse_args()
|
|
|
|
root = pathlib.Path(args.root) if args.root \
|
|
else pathlib.Path(__file__).resolve().parents[2]
|
|
roots = args.source_root or DEFAULT_ROOTS
|
|
|
|
if not root.is_dir():
|
|
print(f"error: root {root} is not a directory", file=sys.stderr)
|
|
return 2
|
|
|
|
all_violations, all_exceptions, n_files = [], [], 0
|
|
for p in iter_sources(root, roots):
|
|
n_files += 1
|
|
v, e = check_file(p, root)
|
|
all_violations += v
|
|
all_exceptions += e
|
|
|
|
if n_files == 0:
|
|
# A scan that found nothing to read is a misconfiguration reporting a
|
|
# pass, which is the failure mode the traceability gate also guards.
|
|
print(f"error: scanned 0 source files under {root} ({', '.join(roots)})",
|
|
file=sys.stderr)
|
|
return 2
|
|
|
|
print("AR-024 — always the calibrated probability, never a raw cosine")
|
|
print("=" * 72)
|
|
print(f"Repo root : {root}")
|
|
print(f"Files scanned : {n_files} ({', '.join(roots)})")
|
|
print(f"Recorded excs. : {len(all_exceptions)}")
|
|
print(f"Violations : {len(all_violations)}")
|
|
|
|
if all_exceptions:
|
|
print("\nRecorded exceptions (allowed, and each one is a claim to re-read):")
|
|
for rel, ln, src, reason in all_exceptions:
|
|
print(f" {rel}:{ln} {reason}")
|
|
print(f" {src}")
|
|
|
|
if all_violations:
|
|
print("\nVIOLATIONS — a bare cosine with no recorded exception:")
|
|
for rel, ln, src in all_violations:
|
|
print(f" {rel}:{ln}")
|
|
print(f" {src}")
|
|
print("\nEvery similarity is converted through the sigmoid calibration")
|
|
print("before it is used, compared, or thresholded. A raw cosine means")
|
|
print("something different for every model, gallery and face size, and")
|
|
print("it cannot be combined with anything else.")
|
|
print("\nEither route it through the calibration, or, if the use is")
|
|
print("genuinely about the metric space rather than about a decision,")
|
|
print("record it:")
|
|
print(" // " + "EXCEPT" + "ION: AR-" + "024 <why this one is not a decision>")
|
|
print("and add a row to CLAUDE.md's agreed-exceptions table.")
|
|
return 1
|
|
|
|
print("\nOK: no bare cosine outside a recorded exception.")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|