From c84acfbe4551f3b40479c867c4e4f5b17895942d Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Sat, 12 Sep 2026 10:17:51 -0400 Subject: [PATCH 1/2] fix(pagingsweepcheck): the whereis (G) pair compared two reads of a ref namespace every session writes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Arm (G) demands byte-identity between two cold runs of `--whereis=rankGraph --limit=3 --offset=3`. --whereis's input is not only the corpus's files: it is every refs/heads of the repository ENCLOSING the crawl root. On $ROOT that is the checkout's shared .git, which every worktree and every session on the machine writes, and which pargates' tree tripwire cannot see (a ref write is not a `git status` line). A full pargates run on 2026-09-12 failed only this arm; run alone, the gate passed 335/335. In five hand-run cold pairs one differed: refs_scanned= "302" vs "303", blobs 7335 -> 7336, hits 93115 -> 93526, the rows otherwise identical. The verb answered correctly both times. The row now runs on the gate's own paging fixture (already built for --mentions and --stray-content), whose refs nothing but the gate writes, as --whereis=renderWidget. That keeps every branch the arm took over rankGraph: 90 hits exceed the 60-hit default cap, so (E) still checks the CUT posture (M2), and the rows (C) and (G) page through are still HEAD's index-labelled rows. The pair still compares every byte; stripping refs_scanned=/blobs=/hits= instead would have kept it green by comparing less. A guard asserts that (G)'s exact page scans refs_scanned="8", the fixture's 8 stray branches. A control creates a branch in the fixture, requires refs_scanned="9" on that same page, and requires that deleting it restores every byte. It branches at stray1, not HEAD: a ref whose tip is HEAD's commit is not scanned at all (probed), so a control there would change nothing. The same shape in the same file is removed rather than left (CONTRIBUTING §2 rule 5): section (I)'s differential against RIPWIRE_PREBIN compared --whereis and --stray-content across two reads of $ROOT's refs, and both now run on the fixture. GIT_DIR/GIT_WORK_TREE/GIT_INDEX_FILE are unset (inherited from a hook, they would aim the fixture's git calls and the ref scans at the caller's repository), and the fixture sets core.hooksPath=/dev/null. Red/green. A shim stood in for BIN and, after chosen runs, created a branch in a --shared scratch clone the gate ran from, never in the real .git. Firing after each cold --whereis run: the stock gate exits 1 with exactly one FAIL, "whereis: paged page NOT deterministic" (334 PASS); the fixed gate exits 0, 336 PASS. Firing after EVERY binary call, with branches carrying a divergent commit and RIPWIRE_PREBIN pointed at the shim so (I) runs: the stock gate exits 1 with exactly three FAILs of 357 arms, the (G) whereis pair and (I)'s --whereis and --stray-content (297 branches created); the fixed gate exits 0, 358 PASS (300 created). Mutations, in place in a third clone: (G)'s page aimed back at $ROOT reds the guard with refs_scanned="0"; the control branched at HEAD reds the control. In this checkout the gate exits 0, 336 PASS. Gate count unchanged at 606; manifestcheck passes; quality-delta is clean. Co-Authored-By: Claude Opus 5 --- test/pagingsweepcheck.sh | 90 +++++++++++++++++++++++++++++++++++----- 1 file changed, 79 insertions(+), 11 deletions(-) diff --git a/test/pagingsweepcheck.sh b/test/pagingsweepcheck.sh index ef151f04..75325bb8 100755 --- a/test/pagingsweepcheck.sh +++ b/test/pagingsweepcheck.sh @@ -104,6 +104,10 @@ ok(){ printf ' PASS %s\n' "$*" || { fail=1; printf ' FAIL could not write th no(){ printf ' FAIL %s\n' "$*"; fail=1; } [ -x "$BIN" ] || { echo "no ripwire binary at $BIN — build first"; exit 2; } +# Inherited from a hook running the suite, these would aim every git and ripwire call below at the caller's +# repository: the paging fixture's own init/commit/branch calls, and the ref scans of the --whereis and +# --stray-content rows, which must read ONLY the refs that fixture holds (see mkPagingFixture). +unset GIT_DIR GIT_WORK_TREE GIT_INDEX_FILE cd "$ROOT" echo "pagingsweepcheck: BIN=$BIN PREBIN=${PREBIN:-}" @@ -111,8 +115,9 @@ echo "pagingsweepcheck: BIN=$BIN PREBIN=${PREBIN:-}" # PAGE_CORPUS lets one arm point at a corpus other than this checkout. The paging contract # (limit windows, offset advances, has_more terminates) is a property of the CODE, not of the corpus, so # an arm whose row supply this repo cannot guarantee is re-anchored onto a fixture built below rather -# than left to red on whichever clone happens to be short of rows. Defaults to $ROOT: every other arm is -# untouched. +# than left to red on whichever clone happens to be short of rows — and so is an arm whose INPUT this +# checkout cannot hold still between two runs (--whereis: the ref namespace). Defaults to $ROOT: every +# other arm is untouched. # §R-J: --grep's block (queries/*.scm-class hits) is a SEPARATE population from the indexed # hits= this whole file's paging CONTRACT is about. Stripped here, at the one seam every page_verb capture # runs through, so a pattern that happens to also live in an unsupported-ext file cannot inflate a generic @@ -138,6 +143,10 @@ cold(){ "$BIN" "${PAGE_CORPUS:-$ROOT}" "$@" --no-cache 2>/dev/null | stripUninde # TWO places therefore stay COLD on purpose, and say so at their own site: page_verb's (G) determinism # pair (a pair of runs restoring ONE cache file cannot observe a re-crawl+re-rank ordering defect), and # section (I)'s differential against a SECOND binary, which must never read a cache this one wrote. +# Cold rules out the cache, not motion in the input: each run of a cold pair re-reads everything it answers +# from, so all of it must hold still between the two. --whereis also answers from the enclosing repository's +# refs, which on $ROOT every session on the machine writes, so its (G) pair runs on the in-gate fixture built +# below — and (I) runs it, and --stray-content, there too. # cacheFor() keys on the corpus and primes on first use, so the in-gate fixture built below — and any # PAGE_CORPUS override — gets its OWN file instead of silently reading $ROOT's. cacheFor(){ # $1 = corpus dir → that corpus's cache path, primed once on first use @@ -149,8 +158,9 @@ cacheFor(){ # $1 = corpus dir → that corpus's cache path, primed once on fir ROOTCACHE="$( cacheFor "$ROOT" )" # the main corpus, primed up front — section (K) invokes $BIN directly # ── the in-gate paging fixture ──────────────────────────────────────────────────────────────────────── -# TWO of these arms — --mentions and --stray-content — need a corpus-SHAPE this repo does not supply on -# a fresh clone, which is every clone but the author's and therefore every CI leg: +# THREE of these arms run here instead of on $ROOT. Two — --mentions and --stray-content — need a +# corpus-SHAPE this repo does not supply on a fresh clone, which is every clone but the author's and +# therefore every CI leg: # # --mentions=main needs >= 6 doc rows for page_verb's (C) seam check (page[0:3]+[3:6] == [0:6]) # and >= 4 for has_more="1" on --limit=3. The published repo has exactly 3 docs @@ -162,10 +172,24 @@ ROOTCACHE="$( cacheFor "$ROOT" )" # the main corpus, primed up front — secti # unanalysable ("v=unknown ... the fix is to deepen the clone") — so even a repo # with real stray branches could not assert here. # -# So both are re-anchored onto one throwaway fixture built from scratch here: 8 markdown docs naming one -# symbol, and 8 branches each authoring lines HEAD does not have. It carries its own full history, so it -# asserts identically on a fresh clone, a shallow CI checkout, and the author's machine. Every other arm -# still runs against $ROOT. Both verbs also still meet the live corpus in section (K)'s honoring-set loop. +# The third, --whereis, has rows to spare on $ROOT. What it lacks there is an input that holds still: +# +# --whereis scans every refs/heads of the repository ENCLOSING the crawl root. On $ROOT that +# is the checkout's .git, which every worktree and every session on the machine +# writes, and which pargates' tree tripwire cannot see (a ref write is not a `git +# status` line). (G)'s cold pair compares whole documents, so one branch created +# between its two runs moved refs_scanned="302" -> "303", blobs=, hits=/total= and +# , and a full pargates run (2026-09-12) reported "paged page NOT +# deterministic" about a verb that answered correctly both times. Stripping those +# attributes would have kept (G) green by comparing less; here the refs are the +# gate's own, so (G) still compares every byte. +# +# So all three are re-anchored onto one throwaway fixture built from scratch here: 8 markdown docs naming +# one symbol, and 8 branches each authoring lines HEAD does not have. It carries its own full history, so +# it asserts identically on a fresh clone, a shallow CI checkout, and the author's machine, and nothing but +# this gate writes a ref in it. Every other arm still runs against $ROOT. All three verbs also still meet +# the live corpus in section (K)'s honoring-set loop, and --whereis in (H), (L) and the §A10.1 legend arm +# too — each of those reads the ref namespace once per assertion, so ref motion cannot split a comparison. PAGEFIX="$TMP/pagefix" mkPagingFixture(){ mkdir -p "$PAGEFIX/src" "$PAGEFIX/docs" || return 1 @@ -180,6 +204,7 @@ mkPagingFixture(){ git config user.email paging@example.invalid git config user.name paging-fixture git config commit.gpgsign false + git config core.hooksPath /dev/null # no host hook runs in (or adds a ref to) a repo whose refs arms count git add -A && git commit -qm "fixture base" || exit 1 local home; home="$( git rev-parse --abbrev-ref HEAD )" # init.defaultBranch varies by host local b @@ -275,7 +300,6 @@ page_verb "clones" ']*>' | head -c 160 ))" +else + git -C "$PAGEFIX" branch -q wherecontrol stray1 >/dev/null 2>&1 + WH1="$( wherePageG )" + git -C "$PAGEFIX" branch -q -D wherecontrol >/dev/null 2>&1 + WH2="$( wherePageG )" + if ! printf '%s' "$WH1" | grep -q ' refs_scanned="9" '; then + no "whereis fixture refs (control): a branch created in the paging fixture did not reach (G)'s page (no refs_scanned=\"9\") — ref motion cannot reach the pair there, so the isolation proves nothing" + elif [ "$WH2" != "$WH0" ]; then + no "whereis fixture refs (control): deleting the control branch did not restore (G)'s page byte for byte" + else + ok "whereis fixture refs: (G)'s page scans only the fixture's 8 branches; a branch created there reaches it (refs_scanned=9) and deleting it restores every byte" + fi +fi +PAGE_CORPUS="$PAGEFIX" page_verb "whereis" ' element, so a bare ']*-->//g' \ -e 's/]*\/>//' -e 's/instab="[0-9.]+"/instab="X"/g'; } - for v in "--clones" "--communities" "--doc-drift" "--grep=NodeId" "--hotspots" "--cochange" "--whereis=rankGraph" "--owners" \ + for v in "--clones" "--communities" "--doc-drift" "--grep=NodeId" "--hotspots" "--cochange" "--owners" \ "--callers=escapeXml" "--callees=runUses" "--tree" "--deps" "--impact=escapeXml" "--uses=escapeXml"; do "$BIN" "$ROOT" $v --no-cache 2>/dev/null | strip > "$TMP/new" "$PREBIN" "$ROOT" $v --no-cache 2>/dev/null | strip > "$TMP/old" @@ -515,6 +568,15 @@ if [ -n "$PREBIN" ] && [ -x "$PREBIN" ]; then diff "$TMP/old" "$TMP/new" | head -4 | cut -c1-200 fi done + # --whereis separately, on the paging fixture: it reads the enclosing repository's REFS, and on $ROOT every + # session on the machine writes those, so a branch created between the two sides would red this differential + # about ref motion rather than about either binary — (G)'s defect, see mkPagingFixture(). --stray-content, + # below, moves for the same reason. + "$BIN" "$PAGEFIX" --whereis=renderWidget --no-cache 2>/dev/null | strip > "$TMP/new" + "$PREBIN" "$PAGEFIX" --whereis=renderWidget --no-cache 2>/dev/null | strip > "$TMP/old" + diff -q "$TMP/old" "$TMP/new" >/dev/null \ + && ok "--whereis (paging fixture): un-paginated data byte-identical modulo root disclosure attrs" \ + || no "--whereis (paging fixture): un-paginated output CHANGED beyond the root disclosure attrs" # --match separately: its query carries a space, so it cannot ride the unquoted `for v` expansion. # This ONE arm stays (string_literal) — a non-nesting kind — while the paging arms above use # (call_expression): the PRE-change binary's tie order over a nesting kind was NONDETERMINISTIC (the @@ -528,7 +590,7 @@ if [ -n "$PREBIN" ] && [ -x "$PREBIN" ]; then # §P15/§P16's seven: none of them changed their un-paginated byte shape at all (see the extended table # above), so no strip() normalization is needed — a bare diff must hold. - for v in "--seams" "--zoom" "--external-surface" "--dead-code" "--mentions=main" "--stray-content"; do + for v in "--seams" "--zoom" "--external-surface" "--dead-code" "--mentions=main"; do "$BIN" "$ROOT" $v --no-cache 2>/dev/null > "$TMP/new" "$PREBIN" "$ROOT" $v --no-cache 2>/dev/null > "$TMP/old" if diff -q "$TMP/old" "$TMP/new" >/dev/null; then @@ -538,6 +600,12 @@ if [ -n "$PREBIN" ] && [ -x "$PREBIN" ]; then diff "$TMP/old" "$TMP/new" | head -4 | cut -c1-200 fi done + # --stray-content on the paging fixture, for --whereis's reason above: its input is the refs. + "$BIN" "$PAGEFIX" --stray-content --no-cache 2>/dev/null > "$TMP/new" + "$PREBIN" "$PAGEFIX" --stray-content --no-cache 2>/dev/null > "$TMP/old" + diff -q "$TMP/old" "$TMP/new" >/dev/null \ + && ok "--stray-content (paging fixture): un-paginated output byte-identical to the pre-change binary" \ + || no "--stray-content (paging fixture): un-paginated output CHANGED vs the pre-change binary" "$BIN" "$ROOT" --graph-query='name("main")' --no-cache 2>/dev/null > "$TMP/new" "$PREBIN" "$ROOT" --graph-query='name("main")' --no-cache 2>/dev/null > "$TMP/old" diff -q "$TMP/old" "$TMP/new" >/dev/null \ From 97f574c930af25db2583bc31a5c9b0c2dff8077d Mon Sep 17 00:00:00 2001 From: joyful-ii-V-I Date: Sat, 12 Sep 2026 15:24:20 -0400 Subject: [PATCH 2/2] =?UTF-8?q?fix(pagingsweepcheck):=20GIT=5FCOMMON=5FDIR?= =?UTF-8?q?=20joins=20the=20unset=20list=20=E2=80=94=20it=20redirects=20re?= =?UTF-8?q?fs=20even=20when=20GIT=5FDIR=20is=20unset?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CodeRabbit (#206, test/pagingsweepcheck.sh:120): an inherited GIT_COMMON_DIR could aim the paging fixture's git calls and the --whereis/--stray-content ref scans at the caller's repository. cd "$ROOT" also exits 2 if it fails (SC2164). Co-Authored-By: Claude Opus 5 --- test/pagingsweepcheck.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/pagingsweepcheck.sh b/test/pagingsweepcheck.sh index 75325bb8..6f48469f 100755 --- a/test/pagingsweepcheck.sh +++ b/test/pagingsweepcheck.sh @@ -105,10 +105,10 @@ no(){ printf ' FAIL %s\n' "$*"; fail=1; } [ -x "$BIN" ] || { echo "no ripwire binary at $BIN — build first"; exit 2; } # Inherited from a hook running the suite, these would aim every git and ripwire call below at the caller's -# repository: the paging fixture's own init/commit/branch calls, and the ref scans of the --whereis and +# repository (GIT_COMMON_DIR too: it redirects refs even when GIT_DIR is unset): the paging fixture's own init/commit/branch calls, and the ref scans of the --whereis and # --stray-content rows, which must read ONLY the refs that fixture holds (see mkPagingFixture). -unset GIT_DIR GIT_WORK_TREE GIT_INDEX_FILE -cd "$ROOT" +unset GIT_DIR GIT_WORK_TREE GIT_INDEX_FILE GIT_COMMON_DIR +cd "$ROOT" || exit 2 echo "pagingsweepcheck: BIN=$BIN PREBIN=${PREBIN:-}"