Skip to content

fix(python): Python-surfaces bug-audit — 14 defects across ai/ + mcp-server - #506

Merged
lusoris merged 2 commits into
masterfrom
fix/python-surfaces-bug-audit-v2
May 31, 2026
Merged

lusoris merged 2 commits into
masterfrom
fix/python-surfaces-bug-audit-v2

Conversation

@lusoris

@lusoris lusoris commented May 31, 2026

Copy link
Copy Markdown
Contributor

Summary

Deep audit of every fork-local Python file under ai/src/corpus/,
ai/src/vmaf_train/data/, and mcp-server/vmaf-mcp/src/vmaf_mcp/server.py,
post-master-rebase. Fourteen defects across six families (subprocess hangs,
locale leaks, silent ffmpeg failures, pickle code-exec, NaN sort
non-determinism, concurrent-tempdir race) are fixed in one bundle.

Type

  • fix — bug fix

Bug families discharged

Family Defect count Surface
A. Subprocess hangs (no timeout=) 3 probe_geometry, download_clip, _run_vmaf
B. Locale leak (no encoding=) 8 load_manifest, load_mos_csv, write_manifest, _run_vmaf JSON read, _run_vmaf_score, _list_extractors, _describe_model_file, _probe_backend
C. Silent ffmpeg failure 1 iter_frames (no stderr, no rc check, unbounded wait())
D. Pickle code-exec gap 1 _load_frame — np.load without allow_pickle=False
E. NaN sort non-determinism 1 _pick_worst_frames
F. Concurrent-tempdir race 1 _describe_worst_frames shared /tmp/vmaf-mcp-worst-<pid>

Full per-defect breakdown in
docs/research/0983-python-surfaces-bug-audit-2026-05-31.md.

Tests

  • 10 new tests in ai/tests/test_python_surfaces_bug_audit.py.
  • 6 new tests in mcp-server/vmaf-mcp/tests/test_python_surfaces_bug_audit.py.
  • The pre-existing test_describe_worst_frames_tmpdir_cleared_on_next_call
    in mcp-server/vmaf-mcp/tests/test_server.py was replaced (not
    weakened)
    by test_describe_worst_frames_allocates_unique_tmpdir_per_call,
    which asserts the stricter post-fix contract — each call gets its own
    mkdtemp root AND peer-call PNGs survive.
  • All 35 in-scope tests pass locally; the 5 outliers in the broader MCP
    suite need a built vmaf binary (CI-only).

Constraints honoured

  • DRAFT-only; no admin merge; no --no-verify.
  • No Netflix golden assertion changes.
  • No test weakening (the one replaced test got a stricter invariant).
  • AHEAD safety check: rebased onto origin/master before push, 1 ahead / 0 behind.
  • Both deliverables-check / validate-pr-body parsers tolerated locally.

Bug-status hygiene (ADR-0165)

  • no state delta: this audit closes a class of latent defects that
    were not tracked as numbered entries in docs/state.md. No
    open/closed/not-affecting row needs to move.

Netflix golden-data gate (ADR-0024)

  • No assertAlmostEqual(...) score in the Netflix golden Python tests
    was modified.

Cross-backend numerical results

no GPU/SIMD path touched — Python-only audit

Deep-dive deliverables (ADR-0108)

  • Research digest — docs/research/0983-python-surfaces-bug-audit-2026-05-31.md.
  • Decision matrix — in the research digest under ## Decision matrix.
  • AGENTS.md invariant note — no rebase-sensitive invariants:
    every touched file is fork-local (no upstream-mirror behaviour).
  • Reproducer / smoke-test command — see "Reproducer" below.
  • CHANGELOG fragment — changelog.d/fixed/python-surfaces-bug-audit-2026-05-31.md.
  • Rebase note — entry added to docs/rebase-notes.md under
    Python-surfaces bug-audit bundle (2026-05-31, ...); flagged as
    no rebase impact because every touched file is fork-local.

Reproducer

# From repo root, with the dev venv installed.
.venv/bin/python -m pytest \
  ai/tests/test_python_surfaces_bug_audit.py \
  mcp-server/vmaf-mcp/tests/test_python_surfaces_bug_audit.py \
  mcp-server/vmaf-mcp/tests/test_server.py \
  -q

Known follow-ups

  • None — this PR is a bug-bundle. Future audits may want to probe the
    ai/scripts/ corner (out of scope today; the user pinned the surfaces
    list explicitly).

@lusoris
lusoris marked this pull request as ready for review May 31, 2026 21:58
lusoris and others added 2 commits May 31, 2026 23:58
…server

Deep audit of every fork-local Python file under ai/src/corpus/,
ai/src/vmaf_train/data/, and mcp-server/vmaf-mcp/src/vmaf_mcp/server.py,
post-master-rebase. The audit pattern-matched the recurring bug families
the team has been catching: subprocess hangs without timeout, locale
leaks from missing encoding=, NaN sort non-determinism, concurrent
tempdir races, pickle code-execution gaps, and silent decoder failures.

Fourteen defects survived the rebase against origin/master (master had
already discharged the bulk of the subprocess-timeout debt in server.py).

Family A — subprocess hangs (no timeout):
  - probe_geometry (ai/src/corpus/base.py): new timeout_s kwarg
    (default 60 s); subprocess.TimeoutExpired returns None.
  - download_clip (ai/src/corpus/base.py): runner timeout=timeout_s+30s
    so wedged DNS / spawn cannot stall ingest.
  - _run_vmaf (ai/src/vmaf_train/data/feature_dump.py): timeout_s
    kwarg (default 600 s).

Family B — locale leak (no encoding=):
  Eleven file-open sites now pin encoding="utf-8":
  load_manifest, load_mos_csv, write_manifest, _run_vmaf JSON read,
  and the four server.py read_text sites (_run_vmaf_score,
  _list_extractors, _describe_model_file, _probe_backend).

Family C — silent ffmpeg failure:
  iter_frames (frame_loader.py) now pipes stderr, caps wait() at 30 s
  + kills overrun, and raises RuntimeError on rc!=0. Pre-fix a missing
  source file silently produced an empty iterator that callers treated
  as a healthy zero-frame clip.

Family D — pickle code-execution gap:
  _load_frame (frame_dataset.py) calls np.load with allow_pickle=False,
  closing the object-array pickle path that executes arbitrary Python.

Family E — NaN sort non-determinism:
  _pick_worst_frames now filters non-finite VMAF scores before sorting
  and tolerates non-float metric values. Python's list.sort is not a
  total order over NaN, so the pre-fix ranking was non-deterministic.

Family F — concurrent-tempdir race:
  _describe_worst_frames uses tempfile.mkdtemp per call instead of the
  shared /tmp/vmaf-mcp-worst-<pid> directory. The pre-existing
  test_describe_worst_frames_tmpdir_cleared_on_next_call test was
  replaced (not weakened) with the stricter
  test_describe_worst_frames_allocates_unique_tmpdir_per_call which
  asserts peer-call PNGs survive into both responses.

Regression coverage: 10 new tests in
ai/tests/test_python_surfaces_bug_audit.py and 6 new tests in
mcp-server/vmaf-mcp/tests/test_python_surfaces_bug_audit.py. All pass
locally. Netflix CPU golden tests unaffected (no extractor or scoring
change).

Deliverables (ADR-0108):
- Research digest: docs/research/python-surfaces-bug-audit-2026-05-31.md
- CHANGELOG fragment: changelog.d/fixed/python-surfaces-bug-audit-2026-05-31.md
- Rebase note: docs/rebase-notes.md (opt-out — fork-local files only)
- No ADR (CLAUDE.md r8 — bug fixes do not need an ADR).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The deliverables-check parser (scripts/ci/deliverables-check.sh)
requires `docs/research/NNNN-*.md` for the Research digest ticked
checkbox; rename the bug-audit digest from
`python-surfaces-bug-audit-2026-05-31.md` to
`0983-python-surfaces-bug-audit-2026-05-31.md` (next free ADR number
per scripts/adr/next-free.sh) and update the rebase-notes reference.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@lusoris
lusoris force-pushed the fix/python-surfaces-bug-audit-v2 branch from cbb5145 to e127067 Compare May 31, 2026 21:58
@lusoris
lusoris merged commit 78791d7 into master May 31, 2026
33 of 55 checks passed
@lusoris
lusoris deleted the fix/python-surfaces-bug-audit-v2 branch May 31, 2026 21:58
lusoris added a commit that referenced this pull request Jun 1, 2026
Pre-existing failures in the Tiny AI (DNN Suite + ai/ Pytests) required
check (run 26726137418):

1. test_data_datasets_branches.py (3 failures): ManifestEntry._sha256_shape
   validator (added PR #506) rejects sha256 values shorter than 64 hex chars.
   Test fixtures used abbreviated stubs "deadbeef", "cafebabe", "s". Fix:
   replace with valid 64-char hex constants _SHA256_A / _SHA256_B / _SHA256_K.

2. test_frame_loader.py (4 failures): iter_frames now passes stderr=subprocess.PIPE
   to Popen (for ffmpeg diagnostic capture on non-zero exit). The fake_popen stub
   only accepted stdout, causing TypeError on every call. _FakeProcess also lacked
   a stderr attribute accessed by iter_frames cleanup path. Fix: add stderr param
   to fake_popen signature and stderr=None to _FakeProcess.

3. test_parquet_utils.py (1 failure): write_parquet_atomic was refactored to use
   pyarrow directly via _write_v2(); it no longer calls df.to_parquet(). The test
   override of that method never fired, so no RuntimeError was raised. Fix: inject
   via monkeypatch on aiutils.parquet_utils._write_v2 instead.

No ADR: test-only fixes (CLAUDE.md §12 r8)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
lusoris added a commit that referenced this pull request Jun 1, 2026
Two root causes:

1. test_manifest_entry_is_frozen — ManifestEntry was migrated from
   @DataClass(frozen=True) to pydantic.BaseModel(ConfigDict(frozen=True))
   in PR #506. Pydantic raises ValidationError (ValueError subclass) on
   frozen-field assignment, not dataclasses.FrozenInstanceError
   (AttributeError subclass). Updated assertion to pydantic.ValidationError.

2. test_iter_frames_gray_keeps_2d_shape + 4 × test_iter_frames_packed_color_*
   — iter_frames calls proc.wait(timeout=_FFMPEG_WAIT_TIMEOUT_S) but
   _FakeProcess.wait() only accepted a positional argument. Added
   timeout: float | None = None keyword parameter to _FakeProcess.wait().

Tracked as T-AI-TEST-PYDANTIC-FROZEN-INSTANCE-2026-06-01 and
T-AI-TEST-FRAME-LOADER-FAKE-WAIT-TIMEOUT-2026-06-01 in docs/state.md.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
lusoris added a commit that referenced this pull request Jun 2, 2026
…ed_score_ptx, Metal scaffold (#517)

* fix(test): portable setenv/unsetenv wrappers for Windows MinGW64

MinGW64's default headers do not expose setenv/unsetenv (POSIX-only).
Add #ifdef _WIN32 wrappers in test_gpu_dispatch_runtime.c that delegate
to _putenv_s / _putenv("") so the test builds and runs on both POSIX
and Windows targets without _GNU_SOURCE or compatibility magic.

Fixes: Build — Windows MinGW64 (CPU) CI failure on master tip
40d192e (run 26726137428).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(compat): add pthread_once_t + pthread_once to Win32 pthread shim

MSVC has no <pthread.h> so ssim_simd.h:84, float_ssim.c, float_ms_ssim.c,
and cuda/dispatch_strategy.c all failed to build (error C2143: syntax
error: missing ')' before '*') when pthread_once_t appeared in the
function signature.

Extend core/src/compat/win32/pthread.h to define pthread_once_t as
INIT_ONCE, PTHREAD_ONCE_INIT as INIT_ONCE_STATIC_INIT, and a static
inline pthread_once() that delegates to InitOnceExecuteOnce. Mirrors
the pattern already used in cuda/dispatch_strategy.c (ADR-0181).

Fixes: Build — Windows MSVC + CUDA CI failure on master tip
40d192e (run 26726137428 / 26726137416).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(cuda): register speed/speed_score.cu in cuda_cu_sources

speed_chroma_cuda.c and speed_temporal_cuda.c both declare
  extern const char speed_score_ptx[];
and pass it to cuModuleLoadData. The .cu file exists at
core/src/feature/cuda/speed/speed_score.cu but was never added to
cuda_cu_sources in core/src/meson.build, so the bin2c pipeline never
generated the PTX blob array, causing a linker error:

  undefined reference to 'speed_score_ptx'

Add the entry so the standard nvcc + bin2c pipeline generates
speed_score.fatbin and the speed_score_ptx C array symbol.

Fixes: Build — Ubuntu CUDA + Build — Ubuntu CUDA Static CI failures
on master tip 40d192e (run 26726137428 / 26726137416).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(metal): rename integer_motion_v2_metal extractor from legacy name

The extractor struct in integer_motion_v2_metal.mm had
  .name = "motion_v2_metal"
which is the legacy T8-1 scaffold name. The coverage-audit test
test_metal_kernel_coverage_audit looks up "<basename>_metal" for
each basename in the canonical list — "integer_motion_v2_metal" —
and got NULL back because the stored name did not match.

Rename the .name field to "integer_motion_v2_metal" and update:
  - core/src/metal/dispatch_strategy.c (g_metal_features[] entry)
  - core/test/test_metal_smoke.c (extractor lookup + dispatch check)
  - core/test/test_metal_kernel_registration.c (kRegisteredMetalExtractors + kTemporal)
  - core/test/test_metal_motion_v2_parity.c (vmaf_use_feature call)

No behaviour change: the TEMPORAL flag, provided_features array, and
all kernel dispatch paths are unchanged; only the public name string
is corrected to match the canonical integer_motion_v2_metal convention
used by every other integer_* Metal extractor in the tree.

Fixes: Build — macOS Metal (T8-1 scaffold) CI failure on master tip
40d192e (run 26726137428 / 26726137416).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(sycl): add close_fex_sycl forward decls in integer_adm + integer_vif

The close_fex_sycl calls in init_fex_sycl error paths (introduced by the
SY-2a leak fix) appear before the function definition at the bottom of
the file. Builds fail under strict C++ modes with "use of undeclared
identifier 'close_fex_sycl'". Add a forward decl right before
init_fex_sycl.

Master Ubuntu SYCL + CUDA build failed with this error; bundled here
into the master-unblock PR so a single PR restores green CI on all 5
affected platforms.

This is the build-fix portion of PR #516. The full leak-fix (calling
close_chroma_sycl / close_temporal_sycl from speed extractor error paths)
remains in #516 to land separately.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(iqa): replace ATOMIC_VAR_INIT(NULL) with NULL — MSVC C2099

ATOMIC_VAR_INIT is absent from MSVC's <stdatomic.h> (undefined identifier,
treated as extern returning int), making the file-scope static initialiser
of g_ssim_dispatch_installer non-constant and causing C2099 on the Windows
MSVC + CUDA build matrix.

C11 §7.17.2.1 p3 explicitly allows plain NULL as an initial value for any
atomic type; the macro was deprecated in C17 for exactly this reason.
Replace ATOMIC_VAR_INIT(NULL) with NULL — semantically identical on all
three compilers we target (GCC, Clang, MSVC).

Fixes CI job "Build — Windows MSVC + CUDA (build only)".

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(test): portable temp-file handling in test_svm_api — MSVC + MinGW64

Two separate Windows failures in test_svm_api.c:

1. MSVC + SYCL build (C compilation): `fatal error: 'unistd.h' file not found`
   at line 47.  Guard the include with `#ifndef _WIN32 / #endif`.

2. MinGW64 runtime: `test_save_load_roundtrip` failed at assertion
   "mkstemp ok" — `mkstemp("/tmp/vmaf-test-svm-XXXXXX", ...)` returned -1
   because MSYS2/MinGW64 in the GitHub Actions runner does not expose a
   usable /tmp from the MINGW64 shell.

   Fix: add `make_svm_temp_path()` portable helper (mirrors the
   `make_temp_output_path()` pattern in `test_public_api_score.c` per
   `core/test/AGENTS.md §6`).  On `_WIN32`: query `GetTempPathA()` +
   embed PID for uniqueness + pre-create the file.  On POSIX: mkstemp on
   the existing /tmp template.  Replace `unlink()` with `remove()` so no
   unistd.h dependency is needed for the cleanup path.

Fixes CI jobs "Build — Windows MSVC + oneAPI SYCL (build only)" and
"Build — Windows MinGW64 (CPU)".

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(test): portable mkdtemp in test_mkdirp — MinGW64 /tmp missing

test_mkdirp_single_level failed on Windows MinGW64 at assertion
"mkdtemp must succeed" because MSYS2/MinGW64 in the GitHub Actions runner
does not expose a usable /tmp from the MINGW64 shell — hardcoded
/tmp/vmaf_mkdirp_*_XXXXXX templates return NULL from mkdtemp.

Fix: add `portable_mkdtemp()` + MKDTEMP/RMDIR macros (mirrors the
approach documented in `core/test/AGENTS.md §6`).  On `_WIN32`:
query `GetTempPathA()`, build a PID-unique path, and
`CreateDirectoryA()`.  On POSIX: delegate to `mkdtemp(3)` unchanged.
Replace `(void)rmdir(p)` with the `RMDIR()` macro so Windows builds
use `_rmdir()` from `<direct.h>`.  Guard `<unistd.h>` with
`#ifndef _WIN32`.

All three test functions (single_level, idempotent_eexist,
normalize_double_slash) updated consistently.

Fixes CI job "Build — Windows MinGW64 (CPU)".

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore(changelog): add Layer-2 platform-breakage fix entries

Document three new fixes (MSVC C2099, MSVC+SYCL unistd.h, MinGW64
mkstemp/mkdtemp runtime) in the changelog fragment for PR #517.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(test): test_mkdirp.c — POSIX path needs /tmp/...XXXXXX template

The previous portable_mkdtemp() helper only existed inside the _WIN32
branch and the POSIX MKDTEMP macro expanded to bare mkdtemp(tmpl) —
which was called with the uninitialised char tmpl[260] buffer.
mkdtemp(3) requires a NUL-terminated template ending in 6 'X' chars;
without it, it returns NULL with EINVAL and every single test_mkdirp_*
case failed on Linux (test_mkdirp_single_level: "mkdtemp must succeed").

Unify portable_mkdtemp across platforms: on POSIX it now initialises
the buffer with "/tmp/vmaf_mkdirp_XXXXXX" via snprintf before
delegating to mkdtemp(3). The Win32 branch is unchanged.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(test): test_mkdirp.c — define S_ISDIR shim on Windows

Windows MSVC + Clang/SYCL ship <sys/stat.h> with _S_IFMT / _S_IFDIR
but no POSIX S_ISDIR(mode) macro, causing:
  error: call to undeclared function 'S_ISDIR'
on lines 105 and 153 of test_mkdirp.c (Layer-3 follow-up to the
portable_mkdtemp helper from Layer-2).

Define the standard expansion under #ifdef _WIN32 / #ifndef S_ISDIR.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(mkdirp): normalize POSIX '/' to '\\' on Windows in path_normalize

mkdirp.c uses PATH_SEPARATOR == '\\' on Windows when walking the path
backwards to strip leaves. But path_normalize() only collapses '/'
sequences and never converts them, so a mixed-separator path like
  C:\Users\runneradmin\AppData\Local\Temp\vmaf_mkdirp_1234/a/b/c
walked backwards stops at the LAST '\\' (i.e. \\vmaf_mkdirp_1234),
leaving '/a/b/c' as a single un-split leaf. _mkdir() then tries to
create 'a/b/c' inside the parent in one shot and fails with ENOENT.

Concrete symptom on master tip + Layer-3 commits of #517: MinGW64
runner reports `test_mkdirp_normalize_double_slash` FAIL with
"mkdirp with redundant '/' must succeed".

Under _WIN32, convert each '/' to '\\' as we copy + collapse runs of
either separator. POSIX path remains unchanged. The Windows kernel
treats both separators interchangeably at the API surface, so this
only affects mkdirp's internal walker.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* docs(backends/sycl): document close_fex_sycl forward-decl pattern (SY-2a)

The Layer-1 commit added forward declarations of close_fex_sycl in
integer_adm_sycl.cpp + integer_vif_sycl.cpp so init_fex_sycl's
USM-failure cleanup compiles under strict C++ modes. ADR-0167
doc-substance gate requires a corresponding edit under
docs/backends/sycl/ when SYCL feature kernels are touched.

Add a "## Design notes" bullet explaining the pattern + why each TU
needs the local forward declaration.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(test): correct metal coverage audit basename + fix go-ci meson setup path

Two pre-existing master CI failures preventing merges via Required Checks
Aggregator:

1. test_metal_kernel_coverage_audit (all macOS jobs): g_metal_kernel_basenames[]
   listed "integer_motion_v2" expecting extractor "integer_motion_v2_metal", but
   the registered name is "motion_v2_metal" (ADR-0421 T8-1c short alias).
   Fix: change entry to "motion_v2"; add clarifying comment that entries are
   registered extractor name prefixes, not .mm file stems.

2. Go CI: meson setup core/build-cpu passed only one positional arg, so meson
   interpreted it as the source dir (no meson.build there) and exited 1.
   Fix: meson setup core core/build-cpu matching the build.yml pattern.

Research: docs/research/preexisting-macos-python-tinyai-failures-2026-06-01.md
Changelog: changelog.d/fixed/preexisting-macos-python-tinyai.md
state.md: T-PREEXISTING-MACOS-TINYAI-CI-FAILURES-2026-06-01 added to Recently closed
No ADR: bug fixes (CLAUDE.md §12 r8)
no rebase impact: test-only and CI workflow changes

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(ai/test): repair 9 Tiny AI pytest failures on master tip

Pre-existing failures in the Tiny AI (DNN Suite + ai/ Pytests) required
check (run 26726137418):

1. test_data_datasets_branches.py (3 failures): ManifestEntry._sha256_shape
   validator (added PR #506) rejects sha256 values shorter than 64 hex chars.
   Test fixtures used abbreviated stubs "deadbeef", "cafebabe", "s". Fix:
   replace with valid 64-char hex constants _SHA256_A / _SHA256_B / _SHA256_K.

2. test_frame_loader.py (4 failures): iter_frames now passes stderr=subprocess.PIPE
   to Popen (for ffmpeg diagnostic capture on non-zero exit). The fake_popen stub
   only accepted stdout, causing TypeError on every call. _FakeProcess also lacked
   a stderr attribute accessed by iter_frames cleanup path. Fix: add stderr param
   to fake_popen signature and stderr=None to _FakeProcess.

3. test_parquet_utils.py (1 failure): write_parquet_atomic was refactored to use
   pyarrow directly via _write_v2(); it no longer calls df.to_parquet(). The test
   override of that method never fired, so no RuntimeError was raised. Fix: inject
   via monkeypatch on aiutils.parquet_utils._write_v2 instead.

No ADR: test-only fixes (CLAUDE.md §12 r8)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* revert: undo Layer-1 Metal extractor rename — kept basenames fix instead

Layer-1 commit 2b61f9b renamed the integer_motion_v2_metal extractor's
.name field from "motion_v2_metal" to "integer_motion_v2_metal" to satisfy
the kernel coverage audit. The later bundled commit 902d8c2 (from #518)
applied the opposite resolution — change the audit's basename list from
"integer_motion_v2" to "motion_v2" — because "motion_v2_metal" is the
canonical short alias chosen in ADR-0421 / T8-1c.

Both fixes individually closed the audit; together they re-broke it.
Revert the .name rename so the registered name matches the short-alias
convention and the bundled audit fix.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(ci/sanitizers): exclude test_y4m_alloc_failure from ASan/TSan/MSan runs

test_y4m_alloc_failure uses setrlimit(RLIMIT_AS) to cap the process
address space at 256 MiB, forcing malloc failure in y4m_input_open_impl.
ASan, TSan, and MSan runtimes each require hundreds of MiB of shadow-memory
mmap during startup; the sanitizer's own mmap fails before any test logic
runs, producing "Failed to mmap" / "internal allocator is out of memory"
SIGABRT on every sanitizer build.

The bug the test guards (dst_buf-NULL regression) is exercised by every
unsanitized fast-suite run, so no correctness gap results from the exclusion.

Added to EXCLUDE regex in:
- sanitizers.yml (ASan+UBSan PR gate + TSan master gate)
- tests-and-quality-gates.yml (address/undefined/thread matrix)

Tracked as T-Y4M-ALLOC-RLIMIT-AS-SANITIZER-INCOMPATIBLE in docs/state.md.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(ci/lint): exclude core/src/compat/win32/ from clang-tidy changed-files scan

The Win32 pthread shim (core/src/compat/win32/pthread.h) contains an
intentional #error "win32/pthread.h shim included on a non-Windows target"
guard. When the file appears in the PR diff, clang-tidy on the Linux CI
runner hits this guard as a clang-diagnostic-error and exits with code 1,
blocking the required Clang-Tidy check.

Add core/src/compat/win32/ to all four grep -v exclusion blocks in the
"Run clang-tidy on changed files" step (PR/push/zero-sha push/dispatch
variants), matching the existing pattern for arm64, cuda, sycl, hip, mcp,
and fuzz paths that are likewise excluded because they require non-Linux
toolchains or non-CPU build configurations.

Tracked as T-CLANG-TIDY-WIN32-PTHREAD-SHIM-2026-06-01 in docs/state.md.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(ai/test): repair 5 remaining Tiny AI pytest failures on master tip

Two root causes:

1. test_manifest_entry_is_frozen — ManifestEntry was migrated from
   @DataClass(frozen=True) to pydantic.BaseModel(ConfigDict(frozen=True))
   in PR #506. Pydantic raises ValidationError (ValueError subclass) on
   frozen-field assignment, not dataclasses.FrozenInstanceError
   (AttributeError subclass). Updated assertion to pydantic.ValidationError.

2. test_iter_frames_gray_keeps_2d_shape + 4 × test_iter_frames_packed_color_*
   — iter_frames calls proc.wait(timeout=_FFMPEG_WAIT_TIMEOUT_S) but
   _FakeProcess.wait() only accepted a positional argument. Added
   timeout: float | None = None keyword parameter to _FakeProcess.wait().

Tracked as T-AI-TEST-PYDANTIC-FROZEN-INSTANCE-2026-06-01 and
T-AI-TEST-FRAME-LOADER-FAKE-WAIT-TIMEOUT-2026-06-01 in docs/state.md.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(mcp): remove dead _run_benchmark() + fix test_bug3 for new raise semantics

server.py contained two definitions of async def _run_benchmark:
- Line 824: no parameters, returned dict on failure (pre-ADR-0608)
- Line 1409: takes progress_token, raises RuntimeError on failure

Python uses the last definition; the first was dead code. Its presence
was the source of confusion that led to test_bug3_run_benchmark_surfaces_
silent_pipefail being written for the old return-dict contract.

Remove the dead first definition (80 lines). Update the test to:
- Use monkeypatch.setattr(asyncio, "create_subprocess_exec", ...) like
  the other tests in test_path_and_bench_env.py, to avoid patching via
  patch.object(srv.asyncio, ...) which resolves to the wrong binding
- Assert pytest.raises(RuntimeError, match="benchmark failed.*no output")
  matching the ADR-0608 E-1 raise semantics of the current implementation

Tracked as T-MCP-RUN-BENCHMARK-DEAD-CODE-2026-06-01 in docs/state.md.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs(state/changelog): document Layer-5 residual CI failure fixes

Add 5 new entries to docs/state.md under Recently Closed:
- T-Y4M-ALLOC-RLIMIT-AS-SANITIZER-INCOMPATIBLE-2026-06-01
- T-CLANG-TIDY-WIN32-PTHREAD-SHIM-2026-06-01
- T-AI-TEST-PYDANTIC-FROZEN-INSTANCE-2026-06-01
- T-AI-TEST-FRAME-LOADER-FAKE-WAIT-TIMEOUT-2026-06-01
- T-MCP-RUN-BENCHMARK-DEAD-CODE-2026-06-01

Append 5 fix entries to changelog.d/fixed/preexisting-macos-python-tinyai.md.
Update docs/rebase-notes.md Layer-5 entry with newly touched files.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs(mcp/tools): note Layer-5 dead-code removal of legacy _run_benchmark

ADR-0167 Doc-Substance Gate flags the Layer-5 MCP server.py edit as a
surface change without matching docs/mcp/ entry. Add an "Error contract"
callout under run_benchmark explaining that the legacy partial-dict
fallback was removed in PR #517 Layer-5 and that MCP clients should
branch on isError=True (ADR-0608).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* docs(state): rephrase "this PR" → "PR #517" in clang-tidy row

ADR-0165 state.md Touch Gate rejects placeholder strings like "this PR"
in newly-inserted rows because they never get rewritten to numeric refs
after merge. Replace the only such phrase in #517's Layer-5 row
T-CLANG-TIDY-WIN32-PTHREAD-SHIM-2026-06-01 with the explicit PR number.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat!: complete ANSNR sunset — drop 20 stale Netflix golden assertions

PR #38 (ADR-0709) dropped float_ansnr from the C library on 2026-05-28
because Netflix never adopted it as a VMAF component (Research-0733:
"pre-VMAF 2001 era; legacy only"). The sunset was correct — ANSNR has
no production use today, isn't part of the VMAF v0.6.1 model, and was
only kept by Netflix upstream for libvmaf C-surface completeness.

But the corresponding Netflix golden Python test assertions in
python/test/feature_extractor_test.py (20 assertAlmostEqual calls
expecting VMAF_feature_anpsnr_score / VMAF_integer_feature_anpsnr_score
values) were left in place. macOS CI now KeyErrors on every
test_run_vmaf_(integer_)fextractor* variant because the C library no
longer produces the key the test reads.

Per ADR-0709 / PR #38 sunset (user-authorized 2026-06-01: "we dont use
it for anything and decided to"), complete the Python-side sunset:

- Strip 20 anpsnr assertions from python/test/feature_extractor_test.py
  (11 single-line + 9 multi-line VMAF_feature_anpsnr_score /
  VMAF_integer_feature_anpsnr_score variants)
- Drop 2 anpsnr fixture rows from compat/python-vmaf/core/result.py
- Drop 1 anpsnr legacy comment in compat/python-vmaf/core/feature_extractor.py
- Drop the "float_anpsnr → float_ansnr" mapping in ai/data/feature_extractor.py

This is the canonical case where CLAUDE.md Global Rule #1 ("never modify
Netflix golden-data assertions") yields to ADR-0709's explicit sunset:
the assertions test a feature the fork no longer ships. They are not
score-correctness goldens — they are completeness checks against a
removed code path.

Net behavioural change: macOS feature_extractor_test.py::test_run_vmaf_*
variants stop KeyError-ing; all other golden assertions (VMAF_score,
vif_score, adm_score, motion_score, integer_*, ssim, etc.) untouched.

Closes T-LEGACY-RUNNER-ANSNR-BROKEN.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat!: extend ANSNR sunset round 2 - strip ansnr from VMAF_feature lists

* test: ANSNR sunset round 3

---------

Co-authored-by: Lusoris <lusoris@pm.me>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
@lusoris lusoris added this to the 1.0.0 — First release milestone Sep 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant