Repository navigation
Bridge YAMLs: fail CI when MATLAB has moved a file since matlab_last_sync_hash #191
Description
Activity
stevevanhooser commented
on Sep 7, 2026 ContributorAuthorMore actionsIt bit, and here is the sweep number
This issue parks itself on "has not bitten in practice — if a MATLAB sync goes badly for lack of it, that is the signal to prioritise." That happened on #203.
Merging
maininto that branch conflicted in two bridge YAMLs, and the only thing that decided the conflict was which side'smatlab_last_sync_hashwas current. Both sides looked equally plausible; resolving it meant checking each hash against NDI-matlab by hand. Three were stale in ways nothing would have caught:entry recorded actual GeneIngest9e956f76eae346afromGEF6ae50870a178e46tilePathe217f367b9972abThe check this issue proposes would have answered that in one command instead of a manual sweep.
The initial-sweep number
This issue notes "somebody has to run the check to know." I ran it — exactly the proposed
git log <hash>..HEAD -- <path>, againstmain(12dfe24) and NDI-matlabmain(114e034f8):entries with matlab_path AND matlab_last_sync_hash: 375 current: 286 (76%) STALE (MATLAB moved since): 84 (22%) unresolvable hash: 4 path no longer in NDI-matlab: 1Stale entries cluster, so the sweep is less daunting than 84 suggests — five files hold two thirds of them:
15 src/ndi/fun/ndi_matlab_python_bridge.yaml 13 src/ndi/ndi_matlab_python_bridge_database_fun.yaml 12 src/ndi/cloud/ndi_matlab_python_bridge.yaml 8 src/ndi/cloud/api/ndi_matlab_python_bridge.yaml 7 src/ndi/cloud/sync/ndi_matlab_python_bridge.yamlWorst by commits behind:
url(11),profile(8),extract_doc_files(6),dir(6),downloadGenericFiles(5),session(5).The "wrong kind of hash" prediction is confirmed
The side-benefit section is right. Four entries carry
234c356, which is not a commit in NDI-matlab at all —git cat-filereports "Not a valid object name":epoch (docTable),treatment (docTable),readtablechar,writetablechar. Exactly the PR #61 failure mode, and the commits-behind check catches them for free.Two things the check as scoped would still miss
Both surfaced while running the sweep, and both are cheap to fold in:
1. 143 entries have a
matlab_pathbut nomatlab_last_sync_hashat all. That is more than the 84 stale ones. An entry with no hash cannot be stale and cannot be checked — it is permanently invisible to a freshness guard, which is a weaker claim than "current" but reads the same to a reviewer. Worth deciding whether a missing hash should fail, warn, or be recorded deliberately.2. The root-level
src/ndi/ndi_matlab_python_bridge_database*.yamlfiles are outside the completeness guard entirely.read_bridge_indexrglobs from eachBridgedPackage.python_dir, and those three files sit directly insrc/ndi/, under no package. Consequence:copy_session_to_datasetstill carries an entry and a hash for+ndi/+database/+fun/copy_session_to_dataset.m, which no longer exists in NDI-matlab — it was superseded byconvertLinkedSessionToIngested.test_every_recorded_matlab_path_points_at_a_real_filedoes not catch it because it never walks that file. That is a live stale path today, not a hypothetical.Related
#208 adds the sibling guard on the same file — one MATLAB file gets one entry, because eleven were recorded twice and four of the pairs disagreed with each other about whether the thing was ported. Same failure shape as this issue: the bridge answering one question two ways, with nothing checking. A freshness check and a duplicate check are the two halves of "an entry can be trusted."
Happy to do the sweep and the check if it is wanted — the script above is throwaway but the numbers are reproducible.
Generated by Claude Code
stevevanhooser commented
on Sep 7, 2026 ContributorAuthorMore actionsCorrection to the numbers above, and a way to fix most of it exactly
My "84 stale / 4 unresolvable" split was wrong.
git log <blob>..HEAD -- <path>does not error on a blob hash — it silently returns commits — so entries carrying a blob hash were counted as stale.git diffdoes reject them, which is how it surfaced.Typing every hash with
git cat-file -tgives the real breakdown:count entries with a matlab_pathand a hash375 → commit hashes 336 — 286 current, 50 stale → blob hashes (PR #61 bug) 35 → resolve to nothing 4 (all 234c356)So 39 of 375 (10%) are not commit hashes at all, not 4. The "wrong kind of hash" side-benefit in the issue body is the larger half of the problem, not an incidental one.
The 35 blob hashes are exactly recoverable
A blob hash is the file's content at the moment somebody examined it, so the commit it came from can be recovered rather than guessed — walk the file's history and compare
git rev-parse <commit>:<path>to the blob:makePyramid 8fff9ff6 -> 261816e8d "Give a pyramid the subject it was measured from" readTileFile 6636493c -> f5d4baaec extract_doc_files 9dc96e22 -> fe64a9f53 ... recovered 35/35, unrecoverable 0All 35 recovered, and 24 of them turn out to be current — the port was fine, only the field was the wrong kind of hash. That matters for sequencing: converting them is mechanical and verifiable (each result is checked by construction against the recorded content), it needs no porting judgement, and it shrinks the problem rather than papering over it. It also preserves the real examined-at point instead of bumping to HEAD, which would assert an examination nobody did.
The 4
234c356entries (epoch (docTable),treatment (docTable),readtablechar,writetablechar) are not recoverable this way — they resolve to no object at all, so somebody has to say what they were meant to be.Corrected staleness, and what it needs
Once the blobs are converted, genuinely stale is 61 (50 + the 11 recovered-but-behind). Triaging those by what actually changed in the MATLAB file:
what changed count what it needs signature / argumentsblock16 port review — the recorded input_arguments/output_argumentsmay now be wrongbody 43 read the diff; mostly bump-with- examined_atcomments/docs only 2 safe bump The 16 signature-changed are all
status: ported, so there is live Python behind each:profile(166 changed code lines),session(69),stimulator(63),dir(58),sessiontable(48),dataset(39),dir(28),updateFileInfoForLocalFiles(22),ndiquery/ndiqueryAll(20 each),advanceSession,startSession,setup.lab,setup.rayolab,clocktype,RayoLabStims.A blanket bump of all 61 would be worse than doing nothing — it would convert "unknown" into a false claim of "examined and current," which is exactly the signal this issue exists to create. Only 2 of the 61 are safe to bump unread.
Suggested order: convert the 35 blobs (mechanical, exact) → land the check so it cannot regress → work the 16 signature-changed as real port review → sweep the remaining 45.
Generated by Claude Code
stevevanhooser commented
on Sep 8, 2026 ContributorAuthorMore actionsThis is done — every point in the issue, checked against the code rather than assumed
Built as
tests/test_matlab_bridge_hashes.pyunder #211, which took this issue's proposal cross-repo. Point by point:The check itself.
has_drifted()runsgit log <matlab_last_sync_hash>..HEAD -- <matlab_path>in the NDI-matlab checkout — path-filtered, exactly as proposed — andTestNothingDriftsFromMatlab::test_no_entry_has_driftedfails on a non-empty result, listing the commits.Commits-behind, not days-behind. As argued here. Never a timer.
Gated on
NDI_BRIDGE_CHECK_STRICT. And the CI job names the file explicitly (ci.yml:108), against afetch-depth: 0checkout — a shallow clone collapses history and makesgit log <hash>..HEADlie.The "wrong kind of hash" side benefit. Caught, and better than incidentally:
TestEverySyncHashIsACommit::test_no_bridge_entry_records_a_blobchecks the object type directly, andTestTheGuardWouldActuallyCatchOneis a positive-control class proving the guard discriminates — a blob is reported, an unresolvable hash is reported, a real commit passes. Those exist because a check that silently stops checking is the failure mode this whole family is about (#77).The rollout. Done as a ratchet rather than a big bang, per #211's decision 3:
when the gate went up now drifted 60 1 matlab_pathwith no hash147 0 The one remaining line is
downloadNdiDocuments, held open deliberately for #215 (no file-series support in the cloud path, so the drift is pointing at a real missing port).TestTheAllowlistOnlyShrinksfails if a listed entry stops violating, so the list cannot rot.The companion rule.
test_every_matlab_path_entry_carries_a_hash— an entry with a path and no hash can never show drift, so it claimed to be current forever. That was 147 entries; it is now zero.Two things beyond what this issue asked for
TestAHashChangeIsJustified, ported from Require a written reason when a hash moves without a port VH-Lab/NDR-python#24. NDR-python#23 observed that merely telling an agent "the hash does not match" tempts it to bump the hash rather than fix the thing. So if a hash changes and none of that entry'spython_pathfiles change in the same diff, thedecision_logmust change too and name the new commit.- The sweep found real defects, not just staleness. Roughly one entry in three:
startSessionmissing a now-requiredorganizationId,setup.labduplicating every DAQ system on a second run,waitForAllBulkUploadsported and never called, a transfer URL never checked forhttps. The issue predicted bookkeeping; it was more than that.
One deviation worth recording
This issue proposed the convention
examined_at: <newhash>; deferred: <reason>for a bump where nothing needed porting. What shipped uses the existingdecision_logfield instead, and #211 decision 4 settled that a regular port needs no prose at all — the bumped hash is itself the record of "examined as of this commit". Substantively the same rule, one fewer field.Closable as far as I can tell — every acceptance point is implemented and enforced. Leaving the close to you since you filed it.
Generated by Claude Code
Follow-up to #131 / #154. Those closed the completeness half of the porting-log gap: every MATLAB function now has a bridge entry (or an exclusion with a reason), enforced by
tests/test_matlab_bridge_completeness.py. What remains is the freshness half: an entry can carry amatlab_last_sync_hashvalue from months ago and the guard does not complain, so an out-of-date port looks the same as a current one.What the check would do
For every bridge entry with a
matlab_pathand amatlab_last_sync_hash, run — in the NDI-matlab checkout the completeness guard already sets up:If empty, the file has not moved since somebody looked at it — pass. If non-empty, the file has moved — fail, and list the commits so the reviewer can decide whether the changes affect the port. Two possible resolutions per stale entry:
matlab_last_sync_hashto the new HEAD.examined_at: <newhash>; deferred: <reason>(PR NDI-python 2026-08 catch-up — hardening bundle + MATLAB parity + CI safety #61's convention).Why commits-behind, not days-behind
Days-behind (
fail if timestamp is older than N days) measures the wrong thing. A MATLAB file untouched in two years does not need re-examination just because 90 days passed; a file MATLAB rewrote yesterday should not wait for the timer. Wall clock is disconnected from what actually matters — has MATLAB moved.Commits-behind is the direct measurement. It also needs no index: git already indexes commits, and the check runs where git can see the NDI-matlab tree.
Side benefit — catches the "wrong kind of hash" bug
PR #61's sweep found that 24 of 37 contracts had a hash that did not resolve as expected — some were branch tips (which move under you), some were blob hashes from
git hash-object(whichgit logcannot walk from). The commits-behind check incidentally enforces "must be a resolvable commit hash", becausegit log <hash>..HEADerrors on both. One test catches both bugs.Rollout
tests/test_matlab_bridge_completeness.py(or a siblingtest_matlab_bridge_freshness.py), gated on the sameNDI_BRIDGE_CHECK_STRICTenv var so it skips locally without NDI-matlab checked out and fails in CI.mainreports the current stale entries — that is the initial sweep. Fix them (bump-with-examined-at where nothing has really changed; port-and-bump where the MATLAB changes matter).Cost
Priority
Not urgent. The completeness guard catches the higher-value problem (silent gaps); this catches the second-order problem (an entry claiming to be current when it isn't). Worth doing, but has not bitten in practice — fits the "pick up when it starts biting" posture. If a MATLAB sync goes badly for lack of it, that is the signal to prioritise.
Prior context
Originally noted as an unfiled residual in #90 (2026-09-01 audit of PR #61). Filed now so #90 can close.