Skip to content

RH-M0: freeze the external-validity manifest - #69

Merged
PhysShell merged 13 commits into
mainfrom
rh-m0-manifest
Aug 30, 2026
Merged

PhysShell merged 13 commits into
mainfrom
rh-m0-manifest

Conversation

@PhysShell

@PhysShell PhysShell commented Aug 29, 2026

Copy link
Copy Markdown
Owner

Freezes the RH-M0 manifest for the finding-lineage external-validity experiment.

No B3 run has happened. No truth label exists. No correctness result is in this PR.

Current state

Item Value
Manifest SHA-256 3a99631292122919b1dc5d9750d899da0ecde23d9ba452c731da6c29209730b4
Manifest revision 13
Truth targets 63 hashes — 50 natural, 4 rename, 9 copy
Primary holdout the 50 natural targets only
Hard challenge 13 targets, reported separately, never pooled into primary precision
Step-1 policy canonical merge 011c1362861f6b8b20c45e8ebd5bcb912401c1c0
Reviewed Step-1 head 6dcc02f7d82bebeb5db9be83f77ab8c5455f5692

The 63 frozen target hashes are byte-identical across every revision of this PR.
Only the specification surface changed.

Revision history

Rev Manifest SHA-256 Commit Change
1 b22cb93d… cde73560 initial freeze
2 df0e9f8d… 9c0a8618 five external review findings
3 b113a318… 6555e689 permission-shaped booleans replaced by prohibitions
4 2b8ce701… 03d56ace no verdict axis passable without resolving anything
5 f4c5181e… 5c89ba51 rename failure token aligned with its own axis
6 7d087e8a… ac54fcb3 consistency rule narrowed to the axes it checks
7 71f4c923… 6e3304a4 total natural outcome rule, concrete axis dispositions
8 ff6a5ed4… ad58b293 exact truth tokens, total hard axis, LF checksum artifact
9 502477c4… 11057180 checker prose follows the structure it describes
10 08f266e9… b10d48b2 cached derived value removed, not managed
11 858d80be… e418ec72 truth endpoints and evidence-card allowlist
12 5a6d6746… ff66d0fd fail-closed endpoint identity, explicit projection mapping
13 3a996312… current candidate reference contract and persisted mapping

Corrections carried in

Identical pattern_id multiset does not mean a trivial pair. pattern_id is
sha1(path + rule + message) and excludes the line, so an identical multiset is
compatible with every occurrence having moved. Such a pair is a direct line-drift
witness. The natural sample is not resampled.

The old hard-transform criterion was unsatisfiable. It required B3 to have
fewer unsafe relation errors than B1/B2. Both baselines key on same_pattern_id,
which includes the path, so across a rename or copy they answer unresolved — not
an unsafe error. Their unsafe-error count is naturally zero. Replaced by a
safe-added-resolution criterion.

Side determination is per class. Rename and copy candidates must be A-side
findings on the transform source path, so their side is forced by construction and
the side hash is never consulted. Applying the natural rule to the hard classes
would yield side B for 6 of the 13 hard targets and those target hashes would not
reproduce. seeds.side_determination states the rule per class and carries a
reproduction warning.

Every constraint is a prohibition that is true, never a permission that is
false. No reader has to decide what a denied permission means.

No axis may be passed without resolving anything. Zero unsafe errors is
trivially achieved by answering unresolved everywhere; safety without
resolution is not a capability. Every axis carries a resolution floor, and the
rename and branch outcome tables are exhaustive and precedence-ordered.

NO_TARGET is kept, not repaired

17 of 21 rename pairs and 20 of 25 copy pairs yield no target: findings cover
roughly 199 of ~1900 .cs files, and the intersection with rename and copy sources
is small. Those pairs stay in the frame as evidence of sparsity. No pair was
replaced.

Ceiling

merged_symbols has no observation source, and none was invented for the
benchmark. MERGE-INCONCLUSIVE is fixed for this experiment.

🤖 Generated with Claude Code

https://claude.ai/code/session_015vtUmvBDf69ccY5ju3PSHV

Preregisters the real-history lineage experiment before any B3 run against
the STS holdout. No correctness result belongs to this commit.

Frozen here:
- 63 truth target hashes: 50 natural, 4 rename, 9 copy
- baseline definitions B0/B1/B2/B3
- truth protocol: two blinded labels, human adjudication, hash freeze
  before the first STS B3 run
- verdict taxonomy, per axis, no monolithic PASS
- 26 input artifact hashes from RH-0, RH-O1 and the targetability census

Primary holdout is the 50 natural targets only. The 13 hard-transform
targets are reported separately and never enter the primary denominator.

The old hard-transform criterion "B3 has fewer unsafe relation errors than
B1/B2" is replaced: both baselines require same_pattern_id, pattern_id
includes the path, so across a rename or copy they return unresolved, which
is not an unsafe error. Their unsafe-error count is naturally zero and
"fewer than zero" is unsatisfiable. The criterion is now safe added
resolution: zero unsafe errors AND at least three more correct resolutions
than the better baseline.

merged_symbols has no observation source and none was invented.
MERGE-INCONCLUSIVE is the fixed ceiling for this experiment.

Step-1 contract at canonical merge 011c136,
reviewed head 6dcc02f.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015vtUmvBDf69ccY5ju3PSHV
@coderabbitai

coderabbitai Bot commented Aug 29, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 9b23f4d6-87db-40ee-b5af-5b2a3e190475


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Copy link
Copy Markdown
Owner Author

@coderabbitai review

Scoped final review on exact HEAD cde73560f029235959b7775a8466fca059c3f34e. This PR is the frozen RH-M0 experiment manifest only. Please flag semantic contradictions, broken hashes/provenance, target/sample inconsistencies, or anything that could invalidate the preregistration. Do not request adjacent production work or stylistic expansion.

Copy link
Copy Markdown
Owner Author

@codex review

Final external review requested on exact HEAD cde73560f029235959b7775a8466fca059c3f34e. Scope: only the frozen RH-M0 manifest and checksum sidecar. Look for contradictions in preregistration semantics, sample/target counts, hashes/provenance, baseline definitions, truth protocol, or verdict criteria. No production mapper work is in scope.

@coderabbitai

coderabbitai Bot commented Aug 29, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

requested_head='cde73560f029235959b7775a8466fca059c3f34e'

echo '== HEAD identity =='
git rev-parse HEAD
git cat-file -t "$requested_head"
git show -s --format='commit=%H%nparents=%P%nsubject=%s' "$requested_head"

echo '== Changed files in PR commit range =='
base="$(git merge-base "$requested_head" HEAD^ 2>/dev/null || true)"
git diff-tree --no-commit-id --name-status -r "$requested_head"
echo "parent=${base:-unknown}"

echo '== Commit file statistics and diff =='
git show --stat --oneline --decorate "$requested_head"
git diff "$requested_head^" "$requested_head" -- . ':!*.png' ':!*.pdf'

echo '== Candidate manifest and experiment files =='
fd -HI -t f . | rg -i '(manifest|rh-m0|lineage|external.valid|holdout|target|benchmark|prereg)'

echo '== Text references to frozen identifiers =='
rg -n -i -C 2 \
  'b22cb93d7ea1dc2392a318796abc3cfbf67615c300de40dc124f1039b0471d82|011c1362861f6b8b20c45e8ebd5bcb912401c1c0|6dcc02f7d82bebeb5db9be83f77ab8c5455f5692|RH-M0|MERGE-INCONCLUSIVE|NO_TARGET|same_pattern_id' \
  .

Length of output: 104120


🏁 Script executed:

#!/bin/bash
set -euo pipefail

manifest='research/rh-m0/rh-m0-manifest.json'
sidecar='research/rh-m0/rh-m0-manifest.sha256'
merge='011c1362861f6b8b20c45e8ebd5bcb912401c1c0'
reviewed='6dcc02f7d82bebeb5db9be83f77ab8c5455f5692'

echo '== Manifest checksum =='
sha256sum "$manifest"
cat "$sidecar"
sha256sum --check "$sidecar"

echo '== JSON invariants, samples, and side derivation =='
python3 - <<'PY'
import hashlib, json, subprocess, sys
from collections import Counter, defaultdict

p = "research/rh-m0/rh-m0-manifest.json"
with open(p, encoding="utf-8") as f:
    m = json.load(f)

targets = m["frozen_targets"]["targets"]
classes = Counter(t["class"] for t in targets)
print("target_count_declared=", m["frozen_targets"]["count"])
print("target_count_actual=", len(targets))
print("target_classes=", dict(sorted(classes.items())))
print("targets_after_census=", m["sample"]["targets_after_census"])
print("hard_total_declared=", m["hard_challenge"]["frozen_hard_sample"]["total"])
print("no_target_counts=", {k: len(v) for k,v in m["sample"]["no_target"].items() if isinstance(v,list)})

pairs_by_class = defaultdict(set)
for t in targets:
    pairs_by_class[t["class"]].add((t["a_sha"],t["b_sha"]))
print("target_pair_counts=", {k:len(v) for k,v in sorted(pairs_by_class.items())})
for cls in ("natural","rename","copy"):
    selected=m["sample"]["selected_pairs"][cls]
    no_target=len(m["sample"]["no_target"][cls])
    targetable=len(pairs_by_class[cls])
    print(f"{cls}: selected={selected}, no_target={no_target}, targetable={targetable}, sum={no_target+targetable}")

all_pairs = defaultdict(set)
for cls in ("natural","rename","copy"):
    all_pairs[cls] |= pairs_by_class[cls]
    all_pairs[cls] |= {(x["a_sha"],x["b_sha"]) for x in m["sample"]["no_target"][cls]}
for a in all_pairs:
    for b in all_pairs:
        if a < b:
            overlap=all_pairs[a] & all_pairs[b]
            print(f"pair_overlap_{a}_{b}=",len(overlap))

seed=bytes.fromhex(m["seeds"]["truth_seed"])
bad_sides=[]
for i,t in enumerate(targets):
    value=["rh-m0-side/v1", m["seeds"]["truth_seed"], "sts", t["a_sha"], t["b_sha"]]
    raw=json.dumps(value, ensure_ascii=False, separators=(",",":")).encode()
    expected="A" if (hashlib.sha256(raw).digest()[0]&1)==0 else "B"
    if t["side"] != expected: bad_sides.append((i,t["side"],expected))
print("side_rule_mismatches=",bad_sides)
print("unique_target_hashes=",len({t["target_hash"] for t in targets}))
print("malformed_target_hashes=",sum(len(t["target_hash"]) != 64 or any(c not in "0123456789abcdef" for c in t["target_hash"]) for t in targets))

pairs=sorted(set().union(*all_pairs.values()))
bad_objects=[]; bad_adjacency=[]
for a,b in pairs:
    for sha in (a,b):
        if subprocess.run(["git","cat-file","-e",sha+"^{commit}"]).returncode:
            bad_objects.append(sha)
    parent=subprocess.check_output(["git","show","-s","--format=%P",b], text=True).split()
    if not parent or parent[0] != a:
        bad_adjacency.append((a,b,parent[:1]))
print("unique_listed_pairs=",len(pairs))
print("missing_commit_objects=",bad_objects)
print("not_first_parent_adjacent=",bad_adjacency)
PY

echo '== Frozen Step-1 provenance =='
git cat-file -e "${merge}^{commit}"
git cat-file -e "${reviewed}^{commit}"
git show -s --format='merge=%H%nmerge_parents=%P%nmerge_time=%cI' "$merge"
git merge-base --is-ancestor "$reviewed" "$merge"; echo "reviewed_head_is_ancestor_of_merge=$?"

for spec in contracts/finding-lineage-decision-v1.json contracts/finding-lineage-v1.json; do
  echo "-- $spec"
  printf 'merge_blob='
  git rev-parse "${merge}:${spec}"
  printf 'reviewed_blob='
  git rev-parse "${reviewed}:${spec}"
  git diff --exit-code "$merge" "$reviewed" -- "$spec"
done

echo '== Normalizer tree identity at merge parents =='
for path in aggregate/normalize.py aggregate/provenance.py aggregate/sarif_read.py; do
  printf '%s parent_1=' "$path"; git rev-parse "${merge}^1:${path}"
  printf '%s parent_2=' "$path"; git rev-parse "${merge}^2:${path}"
  printf '%s merge=' "$path"; git rev-parse "${merge}:${path}"
done

Length of output: 4750


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
import hashlib, json, subprocess
from collections import Counter, defaultdict
p="research/rh-m0/rh-m0-manifest.json"
with open(p,encoding="utf-8") as f: m=json.load(f)
targets=m["frozen_targets"]["targets"]
print("json_valid=true")
print("target_count:",m["frozen_targets"]["count"],len(targets),dict(Counter(t["class"] for t in targets)))
print("census_targets:",m["sample"]["targets_after_census"])
print("hard_total:",m["hard_challenge"]["frozen_hard_sample"]["total"])
print("no_target:",{k:len(v) for k,v in m["sample"]["no_target"].items() if isinstance(v,list)})

target_pairs=defaultdict(set)
for t in targets: target_pairs[t["class"]].add((t["a_sha"],t["b_sha"]))
all_pairs=defaultdict(set)
for c in ("natural","rename","copy"):
  all_pairs[c]=target_pairs[c]|{(x["a_sha"],x["b_sha"]) for x in m["sample"]["no_target"][c]}
  print(f"{c}_accounting:", "selected",m["sample"]["selected_pairs"][c],"targetable",len(target_pairs[c]),"no_target",len(m["sample"]["no_target"][c]))
for a,b in (("natural","rename"),("natural","copy"),("rename","copy")):
  print(f"pair_overlap_{a}_{b}:",len(all_pairs[a]&all_pairs[b]))

bad_sides=[]
for i,t in enumerate(targets):
  raw=json.dumps(["rh-m0-side/v1",m["seeds"]["truth_seed"],"sts",t["a_sha"],t["b_sha"]],ensure_ascii=False,separators=(",",":")).encode()
  expected="A" if hashlib.sha256(raw).digest()[0]&1==0 else "B"
  if t["side"]!=expected: bad_sides.append(i)
print("side_rule_mismatches:",bad_sides)
print("unique_target_hashes:",len({t["target_hash"] for t in targets}))
print("invalid_target_hashes:",[i for i,t in enumerate(targets) if len(t["target_hash"])!=64 or set(t["target_hash"])-set("0123456789abcdef")])

pairs=set().union(*all_pairs.values())
bad=[]
for a,b in pairs:
  subprocess.run(["git","cat-file","-e",a+"^{commit}"],check=True)
  subprocess.run(["git","cat-file","-e",b+"^{commit}"],check=True)
  first=subprocess.check_output(["git","show","-s","--format=%P",b],text=True).split()[0]
  if first!=a: bad.append((a,b,first))
print("listed_pairs:",len(pairs))
print("nonadjacent_or_wrong_direction:",bad)
PY

merge='011c1362861f6b8b20c45e8ebd5bcb912401c1c0'
reviewed='6dcc02f7d82bebeb5db9be83f77ab8c5455f5692'
echo '== Merge provenance =='
git show -s --format='merge=%H%nparents=%P%ncommitter_time=%cI' "$merge"
git merge-base --is-ancestor "$reviewed" "$merge" && echo 'reviewed_head_is_ancestor=true'
for path in contracts/finding-lineage-decision-v1.json contracts/finding-lineage-v1.json; do
  echo "$path $(git rev-parse "$merge:$path") $(git rev-parse "$reviewed:$path")"
done
for path in aggregate/normalize.py aggregate/provenance.py aggregate/sarif_read.py; do
  echo "$path $(git rev-parse "$merge^1:$path") $(git rev-parse "$merge^2:$path") $(git rev-parse "$merge:$path")"
done

Length of output: 4285


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '== Sidecar bytes =='
od -An -tx1c research/rh-m0/rh-m0-manifest.sha256

echo '== Side-rule mismatches =='
python3 - <<'PY'
import hashlib,json
with open("research/rh-m0/rh-m0-manifest.json",encoding="utf-8") as f: m=json.load(f)
for i,t in enumerate(m["frozen_targets"]["targets"]):
    value=["rh-m0-side/v1",m["seeds"]["truth_seed"],"sts",t["a_sha"],t["b_sha"]]
    raw=json.dumps(value,ensure_ascii=False,separators=(",",":")).encode("utf-8")
    expected="A" if hashlib.sha256(raw).digest()[0]&1 == 0 else "B"
    if t["side"] != expected:
        print(json.dumps({
            "index":i, "class":t["class"], "a_sha":t["a_sha"], "b_sha":t["b_sha"],
            "declared_side":t["side"], "derived_side":expected,
            "side_preimage_sha256":hashlib.sha256(raw).hexdigest(),
            "target_hash":t["target_hash"]
        },separators=(",",":")))
PY

echo '== Step-1 local Git provenance =='
merge='011c1362861f6b8b20c45e8ebd5bcb912401c1c0'
reviewed='6dcc02f7d82bebeb5db9be83f77ab8c5455f5692'
git show -s --format='merge=%H%nparents=%P%ncommitter_time=%cI' "$merge"
if git merge-base --is-ancestor "$reviewed" "$merge"; then echo 'reviewed_head_is_ancestor=true'; else echo 'reviewed_head_is_ancestor=false'; fi
for path in contracts/finding-lineage-decision-v1.json contracts/finding-lineage-v1.json; do
  printf '%s merge_blob=%s reviewed_blob=%s\n' "$path" \
    "$(git rev-parse "$merge:$path")" "$(git rev-parse "$reviewed:$path")"
done
for path in aggregate/normalize.py aggregate/provenance.py aggregate/sarif_read.py; do
  printf '%s parent1=%s parent2=%s merge=%s\n' "$path" \
    "$(git rev-parse "$merge^1:$path")" "$(git rev-parse "$merge^2:$path")" "$(git rev-parse "$merge:$path")"
done

Length of output: 2118


@PhysShell The previous verification stopped because this sandbox does not provide od. I will rerun the skipped checks without that utility.

You are interacting with an AI system.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: cde73560f0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread research/rh-m0/rh-m0-manifest.json Outdated
"<A>",
"<B>"
],
"side_rule": "sha256(canonical).digest()[0] & 1 ; 0 -> A, 1 -> B",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Document the forced-side exception before freezing

Recomputing the declared canonical side preimage with the frozen truth_seed assigns side B to six hard targets that are stored as side A. For example, the pair 43e13bce.../45eca2c5... hashes to a first byte of 0xa3, so this rule selects B, while its target records A; the same happens for one other rename pair and four copy targets. Because <side> also enters the target-hash preimage, these frozen targets cannot be reproduced from the manifest unless the apparent hard-sample forced-A exception is explicitly specified.

Useful? React with 👍 / 👎.

Comment thread research/rh-m0/rh-m0-manifest.json Outdated
"roslyn": "Microsoft.CodeAnalysis.CSharp 4.9.2"
},
"rename_capability": {
"all_four_targets_labelled": true,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Disambiguate classification from completed truth labelling

At this M0 state the manifest separately says truth_labels_created: false, and the truth protocol requires two blinded labels plus human sign-off, so all_four_targets_labelled: true reads as a contradictory assertion that rename truth labels already exist. If this only means the four targets were selected or tagged with class rename, name the field accordingly so downstream readers cannot treat it as leaked or completed truth.

Useful? React with 👍 / 👎.

Comment thread research/rh-m0/rh-m0-manifest.json Outdated
Comment on lines +55 to +59
"if_three_or_more_and_any_endpoint_lost_or_invented": "FAIL/PIVOT",
"if_zero_to_two_genuine_branches": "BRANCH-INCONCLUSIVE regardless of B3 output",
"validated_requires": [
">= 3 adjudicated truth groups whose truth label is 'branched'",
"those groups span >= 2 distinct revision pairs",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Cover the single-pair branch outcome

If adjudication finds at least three genuine branches but all belong to one revision pair, this policy assigns no branch verdict: the zero-to-two condition does not apply, the two-pair validation requirement is unmet, and correct endpoint sets do not trigger FAIL/PIVOT. Multiple frozen copy targets share revision pairs, so this is reachable; preregister this case as an explicit verdict, presumably BRANCH-INCONCLUSIVE, before results are observed.

Useful? React with 👍 / 👎.

Comment thread research/rh-m0/rh-m0-manifest.json Outdated
"resolved precision >= 95%",
"zero fabricated new/ended where truth has a counterpart",
"B3 coverage > B0",
"report B3-vs-B2 coverage; an absolute loss greater than 10 percentage points requires explicit utility justification and cannot be an unconditional PASS"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Freeze the disposition for excessive B2 coverage loss

When B3 loses more than ten percentage points of coverage versus B2, this requirement leaves the natural verdict dependent on an unspecified post-result “utility justification.” It neither defines what makes that justification sufficient nor chooses FAIL versus INCONCLUSIVE, so the same measurements could receive different verdicts after unblinding; freeze an objective disposition or acceptance rule now.

Useful? React with 👍 / 👎.

Comment thread research/rh-m0/rh-m0-manifest.json Outdated
Comment on lines +1115 to +1119
[
"PASS-NATURAL",
"PASS-RENAME",
"PASS-BRANCH",
"MERGE-INCONCLUSIVE"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Include every verdict axis in composite examples

The first composite example omits the hard axis even though the taxonomy defines five axes and later requires every run to report one verdict per axis; the second example similarly omits rename. Consumers following these frozen examples would produce four-element composites with inconsistent schemas, so both examples should contain natural, rename, hard, branch, and merge verdicts.

Useful? React with 👍 / 👎.

The 63 frozen target hashes are unchanged. Nothing was re-selected.

P1, forced-side exception. The manifest described the hash-derived side rule as
if it governed every class. It does not: rename and copy candidates must be
A-side findings on the transform SOURCE path, so their side is forced to A by
construction and the side hash is never consulted. Applying the natural rule to
the hard classes yields side B for 6 of the 13 hard targets, and since <side>
enters the target-hash preimage those 6 hashes would not reproduce. Confirmed on
the frozen data: natural 0 of 50, rename 2 of 4, copy 4 of 9. seeds.side_determination
now states the rule per class and carries a reproduction warning with the worked
example Codex used. Side now reproduces 63 of 63.

P2, truth-label wording. all_four_targets_labelled read as a claim that rename
labels already exist, contradicting truth_labels_created=false. Renamed to
all_four_targets_enter_labelling.

P2, single-pair branch outcome. Three or more genuine branches confined to one
revision pair previously fell through every rule. Preregistered as
BRANCH-INCONCLUSIVE in an exhaustive four-row outcome table.

P2, B2 coverage-loss disposition. "Requires utility justification" left the
verdict decidable after unblinding. Frozen: a loss above 10 percentage points
caps the natural axis at INCONCLUSIVE, and no after-the-fact justification can
raise a capped verdict.

P2, composite examples. All examples now carry all five axes.

Manifest SHA-256 df0e9f8d7cac50b969ac5d53003885d2ad3d8c05584d011b9e0bcef09ef2c043
supersedes b22cb93d7ea1dc2392a318796abc3cfbf67615c300de40dc124f1039b0471d82.

No B3 run. No truth labels. No correctness result.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015vtUmvBDf69ccY5ju3PSHV
@PhysShell

Copy link
Copy Markdown
Owner Author

Five review findings addressed on a new commit

New exact HEAD 9c0a8618f131e1bf0362d80538bb2af41f2d2c9a, parent cde73560f029235959b7775a8466fca059c3f34e.

The 63 frozen target hashes are byte-identical between the two commits. Nothing was re-selected.

Finding Disposition
P1 forced-side exception Valid. Documentation defect, not a selection defect.
P2 truth-label wording Valid. Field renamed.
P2 single-pair branch outcome Valid. Preregistered as BRANCH-INCONCLUSIVE.
P2 B2 coverage-loss disposition Valid. Frozen objective cap.
P2 composite examples Valid. All examples now carry all five axes.

On P1. The finding is correct and the example reproduces exactly. Recomputing the natural side rule over the frozen truth_seed yields side B for 43e13bce/45eca2c5 at first byte 0xa3, while the target records A. Measured across the whole set: natural 0 of 50, rename 2 of 4, copy 4 of 9, so 6 of 63. The cause is that rename and copy candidates must be A-side findings on the transform SOURCE path, so their side is forced by construction and the side hash is never consulted — but the manifest described the hash rule as if it governed every class. seeds.side_determination now states the rule per class and carries a reproduction warning. Side now reproduces 63 of 63.

On the B2 disposition. The previous wording left the verdict decidable after unblinding. It is now frozen: a coverage loss above 10 percentage points caps the natural axis at INCONCLUSIVE, and no after-the-fact utility argument can raise a capped verdict. FAIL stays reserved for incorrect relations, since losing coverage against a deliberately conservative baseline is evidence of unhelpfulness, not of error.

Manifest SHA-256 df0e9f8d7cac50b969ac5d53003885d2ad3d8c05584d011b9e0bcef09ef2c043 supersedes b22cb93d7ea1dc2392a318796abc3cfbf67615c300de40dc124f1039b0471d82.

No B3 run. No truth labels. No correctness result.

@PhysShell

Copy link
Copy Markdown
Owner Author

@coderabbitai review

Final review requested on new exact HEAD 9c0a8618f131e1bf0362d80538bb2af41f2d2c9a. The previous round on cde73560 is stale: five findings were addressed in one commit. The 63 frozen target hashes are unchanged.

@PhysShell

Copy link
Copy Markdown
Owner Author

@codex review

Final review requested on new exact HEAD 9c0a8618f131e1bf0362d80538bb2af41f2d2c9a. All five findings from your review of cde73560 were addressed in one commit; P1 reproduced exactly and is fixed as a documentation defect. The 63 frozen target hashes are byte-identical across both commits.

@coderabbitai

coderabbitai Bot commented Aug 30, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

head='9c0a8618f131e1bf0362d80538bb2af41f2d2c9a'
parent='cde73560f029235959b7775a8466fca059c3f34e'
manifest='research/rh-m0/rh-m0-manifest.json'
sidecar='research/rh-m0/rh-m0-manifest.sha256'
merge='011c1362861f6b8b20c45e8ebd5bcb912401c1c0'
reviewed='6dcc02f7d82bebeb5db9be83f77ab8c5455f5692'

echo '== Exact commit =='
test "$(git rev-parse HEAD)" = "$head"
git show -s --format='commit=%H%nparents=%P%nsubject=%s' "$head"
test "$(git rev-parse "$head^")" = "$parent"
git diff-tree --no-commit-id --name-status -r "$head"

echo '== Changed manifest diff =='
git diff --no-ext-diff --unified=80 "$parent" "$head" -- "$manifest" "$sidecar"

echo '== Checksum =='
sha256sum "$manifest"
cat "$sidecar"
sha256sum --check "$sidecar"

echo '== Manifest invariants and preservation =='
python3 - <<'PY'
import hashlib, json, subprocess
from collections import Counter, defaultdict

oldp = "research/rh-m0/rh-m0-manifest.json"
new = json.loads(subprocess.check_output(
    ["git", "show", "cde73560f029235959b7775a8466fca059c3f34e:" + oldp], text=True))
with open(oldp, encoding="utf-8") as f:
    cur = json.load(f)

targets = cur["frozen_targets"]["targets"]
old_hashes = [x["target_hash"] for x in new["frozen_targets"]["targets"]]
new_hashes = [x["target_hash"] for x in targets]
print("target_hash_list_byte_identical=", old_hashes == new_hashes)
print("declared_actual_classes=", cur["frozen_targets"]["count"], len(targets),
      dict(sorted(Counter(x["class"] for x in targets).items())))
print("unique_target_hashes=", len(set(new_hashes)))
print("target_hash_format_valid=", all(len(x) == 64 and set(x) <= set("0123456789abcdef") for x in new_hashes))

pairsets=defaultdict(set)
for x in targets:
    pairsets[x["class"]].add((x["a_sha"],x["b_sha"]))
for cls in ("natural","rename","copy"):
    no_target=cur["sample"]["no_target"][cls]
    all_pairs=pairsets[cls] | {(x["a_sha"],x["b_sha"]) for x in no_target}
    print(f"{cls}: selected={cur['sample']['selected_pairs'][cls]} targetable={len(pairsets[cls])} no_target={len(no_target)} accounted={len(all_pairs)}")
print("hard_total=",cur["hard_challenge"]["frozen_hard_sample"]["total"])

seed=cur["seeds"]["truth_seed"]
side_bad=[]
for i, x in enumerate(targets):
    if x["class"] == "natural":
        raw=json.dumps(["rh-m0-side/v1",seed,"sts",x["a_sha"],x["b_sha"]],
                       ensure_ascii=False,separators=(",",":")).encode()
        expected="A" if hashlib.sha256(raw).digest()[0] & 1 == 0 else "B"
    else:
        expected="A"
    if x["side"] != expected:
        side_bad.append((i,x["class"],x["side"],expected))
print("side_rule_mismatches=",side_bad)

pairs=set().union(*pairsets.values())
pairs |= {(x["a_sha"],x["b_sha"]) for c in ("natural","rename","copy") for x in cur["sample"]["no_target"][c]}
bad=[]
for a,b in pairs:
    subprocess.run(["git","cat-file","-e",a+"^{commit}"],check=True)
    subprocess.run(["git","cat-file","-e",b+"^{commit}"],check=True)
    first=subprocess.check_output(["git","show","-s","--format=%P",b],text=True).split()[0]
    if first != a: bad.append((a,b,first))
print("nonadjacent_or_wrong_direction=",bad)
PY

echo '== Frozen Step-1 provenance =='
git cat-file -e "${merge}^{commit}"
git cat-file -e "${reviewed}^{commit}"
git show -s --format='merge=%H%nparents=%P%ncommitter_time=%cI' "$merge"
git merge-base --is-ancestor "$reviewed" "$merge" && echo 'reviewed_head_is_ancestor=true'
for path in contracts/finding-lineage-decision-v1.json contracts/finding-lineage-v1.json; do
  printf '%s merge_blob=%s reviewed_blob=%s\n' "$path" \
    "$(git rev-parse "$merge:$path")" "$(git rev-parse "$reviewed:$path")"
done
for path in aggregate/normalize.py aggregate/provenance.py aggregate/sarif_read.py; do
  printf '%s parent1=%s parent2=%s merge=%s\n' "$path" \
    "$(git rev-parse "$merge^1:$path")" "$(git rev-parse "$merge^2:$path")" "$(git rev-parse "$merge:$path")"
done

echo '== Manifest structure =='
ast-grep outline "$manifest" --lang json --items all
echo '== Referenced contracts =='
for path in contracts/finding-lineage-decision-v1.json contracts/finding-lineage-v1.json; do
  echo "-- $path"
  sed -n '1,260p' "$path"
done

Length of output: 32179


🏁 Script executed:

#!/bin/bash
set -u -o pipefail

manifest='research/rh-m0/rh-m0-manifest.json'
sidecar='research/rh-m0/rh-m0-manifest.sha256'
merge='011c1362861f6b8b20c45e8ebd5bcb912401c1c0'
reviewed='6dcc02f7d82bebeb5db9be83f77ab8c5455f5692'

echo '== Sidecar byte and verifier checks =='
python3 - <<'PY'
from pathlib import Path
p=Path("research/rh-m0/rh-m0-manifest.sha256")
data=p.read_bytes()
print("sidecar_bytes_repr=",repr(data))
print("contains_crlf=",b"\r\n" in data)
print("contains_bare_cr=",b"\r" in data.replace(b"\r\n",b""))
PY
echo '-- verifier from manifest directory --'
( cd research/rh-m0 && sha256sum --check rh-m0-manifest.sha256 ) || true
echo '-- verifier after only newline normalization, in manifest directory --'
tmp="$(mktemp)"
tr -d '\r' < "$sidecar" > "$tmp"
( cd research/rh-m0 && sha256sum --check "$tmp" ) || true
rm -f "$tmp"

echo '== JSON invariants, selection accounting, and forced-side rule =='
python3 - <<'PY'
import hashlib, json, subprocess
from collections import Counter, defaultdict

p="research/rh-m0/rh-m0-manifest.json"
old="cde73560f029235959b7775a8466fca059c3f34e:"+p
before=json.loads(subprocess.check_output(["git","show",old],text=True))
with open(p,encoding="utf-8") as f: m=json.load(f)
targets=m["frozen_targets"]["targets"]

print("json_valid=true")
print("target_hash_list_byte_identical=", [x["target_hash"] for x in before["frozen_targets"]["targets"]] == [x["target_hash"] for x in targets])
print("declared_actual_by_class=",m["frozen_targets"]["count"],len(targets),dict(sorted(Counter(x["class"] for x in targets).items())))
print("unique_target_hashes=",len({x["target_hash"] for x in targets}))
print("hash_formats_valid=",all(len(x["target_hash"])==64 and set(x["target_hash"])<=set("0123456789abcdef") for x in targets))

target_pairs=defaultdict(set)
for x in targets: target_pairs[x["class"]].add((x["a_sha"],x["b_sha"]))
all_pairs={}
for cls in ("natural","rename","copy"):
  no=m["sample"]["no_target"][cls]
  all_pairs[cls]=target_pairs[cls]|{(x["a_sha"],x["b_sha"]) for x in no}
  print(f"{cls}_accounting=",m["sample"]["selected_pairs"][cls],len(target_pairs[cls]),len(no),len(all_pairs[cls]))
print("hard_total_declared=",m["hard_challenge"]["frozen_hard_sample"]["total"])
print("hard_total_actual=",sum(1 for x in targets if x["class"] in ("rename","copy")))
print("pair_class_overlaps=", {f"{a}:{b}":len(all_pairs[a]&all_pairs[b]) for a,b in (("natural","rename"),("natural","copy"),("rename","copy"))})

bad_side=[]
seed=m["seeds"]["truth_seed"]
for i,x in enumerate(targets):
  if x["class"]=="natural":
    raw=json.dumps(["rh-m0-side/v1",seed,"sts",x["a_sha"],x["b_sha"]],ensure_ascii=False,separators=(",",":")).encode()
    expected="A" if hashlib.sha256(raw).digest()[0]&1==0 else "B"
  else: expected="A"
  if x["side"]!=expected: bad_side.append((i,x["class"],x["side"],expected))
print("side_mismatches=",bad_side)

bad_adj=[]
for a,b in set().union(*all_pairs.values()):
  for x in (a,b): subprocess.run(["git","cat-file","-e",x+"^{commit}"],check=True)
  parents=subprocess.check_output(["git","show","-s","--format=%P",b],text=True).split()
  if not parents or parents[0]!=a: bad_adj.append((a,b,parents))
print("bad_first_parent_adjacency=",bad_adj)

print("all_composite_examples_five_axes=", all(set(x)==set(m["verdict_taxonomy"]["axes_are_five"]) for x in m["verdict_taxonomy"]["examples"]))
PY

echo '== Step-1 provenance =='
git show -s --format='merge=%H%nparents=%P%ncommitter_time=%cI' "$merge"
if git merge-base --is-ancestor "$reviewed" "$merge"; then echo 'reviewed_head_is_ancestor=true'; else echo 'reviewed_head_is_ancestor=false'; fi
for path in contracts/finding-lineage-decision-v1.json contracts/finding-lineage-v1.json; do
  printf '%s merge_blob=%s reviewed_blob=%s\n' "$path" "$(git rev-parse "$merge:$path")" "$(git rev-parse "$reviewed:$path")"
done
for path in aggregate/normalize.py aggregate/provenance.py aggregate/sarif_read.py; do
  printf '%s parent1=%s parent2=%s merge=%s\n' "$path" "$(git rev-parse "$merge^1:$path")" "$(git rev-parse "$merge^2:$path")" "$(git rev-parse "$merge:$path")"
done

echo '== Policy and contract text =='
python3 - <<'PY'
import json
paths=[
 "research/rh-m0/rh-m0-manifest.json",
 "contracts/finding-lineage-decision-v1.json",
 "contracts/finding-lineage-v1.json",
]
for p in paths:
  print("\n--",p)
  with open(p,encoding="utf-8") as f: o=json.load(f)
  if p.endswith("rh-m0-manifest.json"):
    for k in ("baselines","branch_capability","hard_challenge","primary_holdout","rename_capability","truth_protocol","verdict_taxonomy"):
      print(k+"=",json.dumps(o[k],indent=2,sort_keys=True))
  else:
    print(json.dumps(o,indent=2,sort_keys=True))
PY

Length of output: 156934


@PhysShell I found one blocking manifest-integrity defect.

  • research/rh-m0/rh-m0-manifest.sha256, Line 1: The sidecar uses CRLF. GNU sha256sum --check reads the carriage return as part of rh-m0-manifest.json\r and fails, even when run from research/rh-m0. The digest is correct after CR removal, but the committed sidecar is not directly machine-verifiable. Use LF for this file and regenerate or revalidate the sidecar.

The remaining completed checks passed:

  • HEAD and parent match the requested commits.
  • Only the manifest and sidecar changed.
  • All 63 target hashes are byte-identical to the prior manifest.
  • Counts remain 50 natural, 4 rename, and 9 copy.
  • The revised class-dependent side rule reproduces all recorded sides.
  • The revised verdict examples contain all five axes.
  • Step-1 contract blobs and normalizer blobs match the frozen merge provenance.

I could not independently verify every external STS commit object and first-parent relationship. The sandbox does not contain STS object b507027502b565d9ceb1ef35533da945800b35fa, and its configured remote cannot provide it.

You are interacting with an AI system.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9c0a8618f1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread research/rh-m0/rh-m0-manifest.json Outdated
],
"status": "NO_TARGET entries remain in the frame as evidence of sample sparsity. They are NOT truth groups, they are never replaced, and no pair is substituted for them."
},
"no_target_replacement_allowed": false,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Disallow replacement of NO_TARGET pairs

When an experiment executor reads this dedicated boolean, false indicates that NO_TARGET replacement is allowed, contradicting the adjacent status text (they are never replaced) and the frozen pair-selection flags. Trusting this field would permit any of the 37 empty rename/copy pairs to be substituted after preregistration, changing the sampling frame and invalidating subsequent results; this should be true (or be renamed to a positive no_target_replacement_may_be_performed flag while remaining false).

Useful? React with 👍 / 👎.

…ssion

External review flagged sample.no_target_replacement_allowed=false as ambiguous
for a machine reader: a dedicated permission flag carrying false forces the
reader to decide what a denied permission means.

The same shape occurred twice more, once in the same object. Fixing only the
flagged field would have left two identical ambiguities, so all three are
converted:

  no_target_replacement_allowed  = false  ->  no_target_replacement_prohibited      = true
  pair_selection_may_be_modified = false  ->  pair_selection_modification_prohibited = true
  merge_detector_may_be_added    = false  ->  merge_detector_addition_prohibited     = true

sample.boolean_naming_rule now records the convention, and the NO_TARGET status
prose points at the new field instead of restating the rule in words.

The 63 frozen target hashes are byte-identical. No target, sampling, truth or B3
surface is touched. The old field names survive only inside
manifest_revision.findings_applied, where they describe the change.

Manifest SHA-256 b113a31813636c31c2a78685631a8c4304f80bb13dd341151a604d0bc05d8d31
supersedes df0e9f8d7cac50b969ac5d53003885d2ad3d8c05584d011b9e0bcef09ef2c043.

No B3 run. No truth labels. No correctness result.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015vtUmvBDf69ccY5ju3PSHV
@PhysShell

Copy link
Copy Markdown
Owner Author

One review finding addressed, plus two of the same shape found alongside it

New exact HEAD 6555e68944296c0f47dd5dbf52c59c76548f8422, parent 9c0a8618f131e1bf0362d80538bb2af41f2d2c9a.

The 63 frozen target hashes are byte-identical across all three revisions of this PR. Verified programmatically; the patch asserts it and would abort otherwise.

P1, NO_TARGET replacement. Valid. A dedicated permission flag carrying false makes a machine reader decide what a denied permission means, which is exactly the kind of thing that gets re-litigated six months later.

The same shape occurred twice more, once in the very same object. Fixing only the flagged field would have left two identical ambiguities for the next round to find, so all three were converted:

Before After
no_target_replacement_allowed = false no_target_replacement_prohibited = true
pair_selection_may_be_modified = false pair_selection_modification_prohibited = true
merge_detector_may_be_added = false merge_detector_addition_prohibited = true

sample.boolean_naming_rule now records the convention: every constraint is a prohibition that is true, never a permission that is false.

Expected grep hit. The three old field names still appear in the raw file, only inside manifest_revision.findings_applied, where they describe the change. They are not live fields — sample and merge_capability carry none of them.

PR body corrected. It still advertised the revision-1 manifest hash while the file had moved on. Asking reviewers to check provenance while showing them stale provenance in the header is not a great look. It now reads b113a318… with a revision-history table.

Manifest SHA-256 b113a31813636c31c2a78685631a8c4304f80bb13dd341151a604d0bc05d8d31 supersedes df0e9f8d…, which superseded b22cb93d….

No target, sampling, truth or B3 surface was touched. No B3 run. No truth labels. No correctness result.

@PhysShell

Copy link
Copy Markdown
Owner Author

@coderabbitai review

Final review requested on new exact HEAD 6555e68944296c0f47dd5dbf52c59c76548f8422. The round on 9c0a8618 is stale. One finding addressed plus two of the same shape found alongside it; the 63 frozen target hashes are unchanged across all three revisions.

@PhysShell

Copy link
Copy Markdown
Owner Author

@codex review

Final review requested on new exact HEAD 6555e68944296c0f47dd5dbf52c59c76548f8422. Your P1 on 9c0a8618 is fixed, and the same permission-shaped pattern occurred twice more in the manifest, so all three were converted to prohibitions. The 63 frozen target hashes are byte-identical across revisions 1, 2 and 3.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6555e68944

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread research/rh-m0/rh-m0-manifest.json Outdated
Comment on lines +855 to +857
"validated_requires": [
">= 3 adjudicable rename truth groups",
"zero unsafe relation errors on the adjudicable rename groups"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Require B3 rename resolutions before PASS

When at least three rename groups are adjudicable but B3 returns unresolved for every one, both listed requirements are satisfied because unresolved results are not unsafe relation errors. The pooled hard axis can separately gain its three correct resolutions entirely from copy targets, so the composite can report PASS-RENAME (and even HARD-PASS) without B3 resolving a single rename. Require a minimum number of correctly resolved rename groups before permitting PASS-RENAME.

Useful? React with 👍 / 👎.

@coderabbitai

coderabbitai Bot commented Aug 30, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

head='6555e68944296c0f47dd5dbf52c59c76548f8422'
rev2='9c0a8618f131e1bf0362d80538bb2af41f2d2c9a'
rev1='cde73560f029235959b7775a8466fca059c3f34e'
manifest='research/rh-m0/rh-m0-manifest.json'
sidecar='research/rh-m0/rh-m0-manifest.sha256'
merge='011c1362861f6b8b20c45e8ebd5bcb912401c1c0'
reviewed='6dcc02f7d82bebeb5db9be83f77ab8c5455f5692'

echo '== Commit identity and scope =='
test "$(git rev-parse HEAD)" = "$head"
test "$(git rev-parse "$head^")" = "$rev2"
git show -s --format='commit=%H%nparents=%P%nsubject=%s' "$head"
git diff-tree --no-commit-id --name-status -r "$head"
git diff --no-ext-diff --unified=30 "$rev2" "$head" -- "$manifest" "$sidecar"

echo '== Sidecar portability and checksum =='
python3 - <<'PY'
from pathlib import Path
p=Path("research/rh-m0/rh-m0-manifest.sha256")
b=p.read_bytes()
print("sidecar_bytes=",repr(b))
print("has_cr=",b"\r" in b)
print("has_final_lf=",b.endswith(b"\n"))
PY
( cd research/rh-m0 && sha256sum --check rh-m0-manifest.sha256 )
sha256sum "$manifest"
cat "$sidecar"

echo '== Frozen target preservation and manifest invariants =='
python3 - <<'PY'
import hashlib,json,subprocess
from collections import Counter,defaultdict
p="research/rh-m0/rh-m0-manifest.json"
revs=[
 "cde73560f029235959b7775a8466fca059c3f34e",
 "9c0a8618f131e1bf0362d80538bb2af41f2d2c9a",
 "6555e68944296c0f47dd5dbf52c59c76548f8422",
]
docs=[json.loads(subprocess.check_output(["git","show",f"{r}:{p}"],text=True)) for r in revs]
lists=[[x["target_hash"] for x in d["frozen_targets"]["targets"]] for d in docs]
print("target_lists_all_byte_identical=",lists[0]==lists[1]==lists[2])
m=docs[-1]
ts=m["frozen_targets"]["targets"]
print("revision=",m.get("manifest_revision",{}).get("revision"))
print("declared_actual_class_counts=",m["frozen_targets"]["count"],len(ts),dict(sorted(Counter(x["class"] for x in ts).items())))
print("unique_valid_target_hashes=",len(set(lists[-1])),all(len(x)==64 and set(x)<=set("0123456789abcdef") for x in lists[-1]))
pairs=defaultdict(set)
for x in ts: pairs[x["class"]].add((x["a_sha"],x["b_sha"]))
allpairs={}
for c in ("natural","rename","copy"):
    no=m["sample"]["no_target"][c]
    allpairs[c]=pairs[c]|{(x["a_sha"],x["b_sha"]) for x in no}
    print(f"{c}_selection_accounting=",m["sample"]["selected_pairs"][c],len(pairs[c]),len(no),len(allpairs[c]))
print("pair_class_overlaps=",{
 f"{a}:{b}":len(allpairs[a]&allpairs[b])
 for a,b in (("natural","rename"),("natural","copy"),("rename","copy"))
})
print("hard_total_declared_actual=",m["hard_challenge"]["frozen_hard_sample"]["total"],sum(x["class"] in ("rename","copy") for x in ts))

seed=m["seeds"]["truth_seed"]
bad=[]
for i,x in enumerate(ts):
    if x["class"]=="natural":
        raw=json.dumps(["rh-m0-side/v1",seed,"sts",x["a_sha"],x["b_sha"]],ensure_ascii=False,separators=(",",":")).encode()
        want="A" if hashlib.sha256(raw).digest()[0]&1==0 else "B"
    else: want="A"
    if x["side"] != want: bad.append((i,x["class"],x["side"],want))
print("side_determination_mismatches=",bad)

old_names=("no_target_replacement_allowed","pair_selection_may_be_modified","merge_detector_may_be_added")
for name in old_names:
    locations=[]
    def walk(v,path="$"):
        if isinstance(v,dict):
            for k,w in v.items():
                if k==name: locations.append(path+"."+k)
                walk(w,path+"."+k)
        elif isinstance(v,list):
            for i,w in enumerate(v): walk(w,f"{path}[{i}]")
    walk(m)
    print(f"{name}_locations=",locations)

examples=m["verdict_taxonomy"]["examples"]
axes=set(m["verdict_taxonomy"]["axes_are_five"])
print("examples_have_exactly_five_axes=",all(set(e)==axes for e in examples))
PY

echo '== Step-1 provenance =='
git cat-file -e "${merge}^{commit}"
git cat-file -e "${reviewed}^{commit}"
git show -s --format='merge=%H%nparents=%P%ncommitter_time=%cI' "$merge"
git merge-base --is-ancestor "$reviewed" "$merge" && echo 'reviewed_head_is_ancestor=true'
for path in contracts/finding-lineage-decision-v1.json contracts/finding-lineage-v1.json; do
  printf '%s merge_blob=%s reviewed_blob=%s\n' "$path" \
    "$(git rev-parse "$merge:$path")" "$(git rev-parse "$reviewed:$path")"
done
for path in aggregate/normalize.py aggregate/provenance.py aggregate/sarif_read.py; do
  printf '%s parent1=%s parent2=%s merge=%s\n' "$path" \
    "$(git rev-parse "$merge^1:$path")" "$(git rev-parse "$merge^2:$path")" "$(git rev-parse "$merge:$path")"
done

echo '== Relevant manifest sections =='
python3 - <<'PY'
import json
p="research/rh-m0/rh-m0-manifest.json"
with open(p,encoding="utf-8") as f: m=json.load(f)
for key in ("manifest_revision","sample","seeds","baselines","truth_protocol","verdict_taxonomy","branch_capability","merge_capability","hard_challenge","primary_holdout"):
    print("\n== "+key+" ==")
    print(json.dumps(m[key],indent=2,sort_keys=True))
PY

Length of output: 17790


🏁 Script executed:

#!/bin/bash
set -euo pipefail

head='6555e68944296c0f47dd5dbf52c59c76548f8422'
rev2='9c0a8618f131e1bf0362d80538bb2af41f2d2c9a'
rev1='cde73560f029235959b7775a8466fca059c3f34e'
manifest='research/rh-m0/rh-m0-manifest.json'
merge='011c1362861f6b8b20c45e8ebd5bcb912401c1c0'
reviewed='6dcc02f7d82bebeb5db9be83f77ab8c5455f5692'

echo '== Digest value after parsing sidecar fields =='
python3 - <<'PY'
from pathlib import Path
import hashlib
m=Path("research/rh-m0/rh-m0-manifest.json").read_bytes()
s=Path("research/rh-m0/rh-m0-manifest.sha256").read_bytes()
declared=s.split()[0].decode("ascii")
actual=hashlib.sha256(m).hexdigest()
print("declared=",declared)
print("actual=",actual)
print("digest_matches=",declared==actual)
PY

echo '== Targets, selection, side rule, and prohibitions =='
python3 - <<'PY'
import hashlib,json,subprocess
from collections import Counter, defaultdict
p="research/rh-m0/rh-m0-manifest.json"
commits=["cde73560f029235959b7775a8466fca059c3f34e","9c0a8618f131e1bf0362d80538bb2af41f2d2c9a","6555e68944296c0f47dd5dbf52c59c76548f8422"]
docs=[json.loads(subprocess.check_output(["git","show",f"{c}:{p}"],text=True)) for c in commits]
m=docs[-1]; ts=m["frozen_targets"]["targets"]
hs=[[t["target_hash"] for t in d["frozen_targets"]["targets"]] for d in docs]
print("targets_byte_identical_across_3_revisions=",hs[0]==hs[1]==hs[2])
print("counts=",m["frozen_targets"]["count"],len(ts),dict(Counter(t["class"] for t in ts)))
print("target_hashes_unique_and_valid=",len(set(hs[-1]),)==len(hs[-1]),all(len(h)==64 and set(h)<=set("0123456789abcdef") for h in hs[-1]))
byclass=defaultdict(set)
for t in ts: byclass[t["class"]].add((t["a_sha"],t["b_sha"]))
allpairs={}
for c in ("natural","rename","copy"):
    no={(x["a_sha"],x["b_sha"]) for x in m["sample"]["no_target"][c]}
    allpairs[c]=byclass[c]|no
    print(f"{c}_selected_targetable_no_target_accounted=",m["sample"]["selected_pairs"][c],len(byclass[c]),len(no),len(allpairs[c]))
print("hard_declared_actual=",m["hard_challenge"]["frozen_hard_sample"]["total"],sum(t["class"]!="natural" for t in ts))
print("pair_class_overlap=", {a+":"+b:len(allpairs[a]&allpairs[b]) for a,b in (("natural","rename"),("natural","copy"),("rename","copy"))})
seed=m["seeds"]["truth_seed"]; bad=[]
for i,t in enumerate(ts):
    if t["class"]=="natural":
        raw=json.dumps(["rh-m0-side/v1",seed,"sts",t["a_sha"],t["b_sha"]],ensure_ascii=False,separators=(",",":")).encode()
        expected="A" if hashlib.sha256(raw).digest()[0]&1==0 else "B"
    else: expected="A"
    if t["side"]!=expected: bad.append((i,t["class"],t["side"],expected))
print("side_mismatches=",bad)
print("required_prohibitions=",{
 k:m["sample"].get(k) for k in ("no_target_replacement_prohibited","pair_selection_modification_prohibited")
}|{"merge_detector_addition_prohibited":m["merge_capability"].get("merge_detector_addition_prohibited")})
old=("no_target_replacement_allowed","pair_selection_may_be_modified","merge_detector_may_be_added")
def paths(v, name, at="$"):
    r=[]
    if isinstance(v,dict):
        for k,w in v.items():
            if k==name:r.append(at+"."+k)
            r+=paths(w,name,at+"."+k)
    elif isinstance(v,list):
        for i,w in enumerate(v):r+=paths(w,name,f"{at}[{i}]")
    return r
print("old_name_history_only=",{x:paths(m,x) for x in old})
print("truth_labels_created=",m["manifest_revision"]["truth_labels_created"])
print("b3_executed=",m["manifest_revision"]["b3_executed"])
axes=set(m["verdict_taxonomy"]["axes_are_five"])
print("every_composite_example_has_exactly_all_axes=",all(set(x)==axes for x in m["verdict_taxonomy"]["examples"]))
PY

echo '== Commit-object and first-parent checks for all listed pairs =='
python3 - <<'PY'
import json,subprocess
from collections import defaultdict
with open("research/rh-m0/rh-m0-manifest.json",encoding="utf-8") as f:m=json.load(f)
pairs=set()
for x in m["frozen_targets"]["targets"]:pairs.add((x["a_sha"],x["b_sha"]))
for c in ("natural","rename","copy"):
  pairs|={(x["a_sha"],x["b_sha"]) for x in m["sample"]["no_target"][c]}
missing=[]; bad=[]
for a,b in sorted(pairs):
  for sha in (a,b):
    if subprocess.run(["git","cat-file","-e",sha+"^{commit}"]).returncode:missing.append(sha)
  if a not in missing and b not in missing:
    ps=subprocess.check_output(["git","show","-s","--format=%P",b],text=True).split()
    if not ps or ps[0]!=a:bad.append((a,b,ps))
print("pair_count=",len(pairs))
print("missing_commit_objects=",sorted(set(missing)))
print("bad_first_parent_adjacency=",bad)
PY

echo '== Frozen Step-1 provenance =='
git show -s --format='merge=%H%nparents=%P%ncommitter_time=%cI' "$merge"
git merge-base --is-ancestor "$reviewed" "$merge" && echo 'reviewed_head_is_ancestor=true'
for path in contracts/finding-lineage-decision-v1.json contracts/finding-lineage-v1.json; do
 printf '%s merge_blob=%s reviewed_blob=%s\n' "$path" "$(git rev-parse "$merge:$path")" "$(git rev-parse "$reviewed:$path")"
done
for path in aggregate/normalize.py aggregate/provenance.py aggregate/sarif_read.py; do
 printf '%s parent1=%s parent2=%s merge=%s\n' "$path" "$(git rev-parse "$merge^1:$path")" "$(git rev-parse "$merge^2:$path")" "$(git rev-parse "$merge:$path")"
done

Length of output: 5517


@PhysShell I found one blocking manifest-integrity defect.

  • research/rh-m0/rh-m0-manifest.sha256, Line 1: The sidecar still uses CRLF. GNU sha256sum --check treats the carriage return as part of the filename and fails with rh-m0-manifest.json\r: FAILED open or read. Use LF line endings for this sidecar, then verify it with sha256sum --check from research/rh-m0.

The requested HEAD and parent match. The commit changes only the manifest and checksum sidecar.

The follow-up validation timed out before it produced output. Therefore, I could not complete independent verification of target preservation, sample accounting, side determination, and Step-1 provenance for this revision.

You are interacting with an AI system.

External review found that PASS-RENAME required only >= 3 adjudicable rename
groups plus zero unsafe relation errors. Four `unresolved` answers satisfy both,
so the axis was reachable without resolving a single rename. The finding is
valid and material.

The identical vacuity existed on the branch axis, where "all endpoint sets
exact" is trivially true when B3 produced no endpoint sets at all. It was not
flagged, but it is the same defect class, so both axes are fixed together.

Both are now exhaustive, precedence-ordered outcome tables:

  rename: 4 rows. The denominator counts only groups whose TRUTH is continued
  1:1 across the rename, because correctly calling `ended` on a renamed file
  does not demonstrate R-CONT-RENAME. PASS requires >= 3 correct exact-counterpart
  resolutions.

  branch: 5 rows. PASS requires >= 3 correct resolutions with exact successor
  endpoint sets, spanning >= 2 revision pairs.

verdict_taxonomy.no_vacuous_pass states the invariant once and records the
resolution floor of every axis. natural and hard already carried floors, >= 20
B3-resolved and +3 over the better baseline; they needed no change.

Also recorded: HARD-PASS pools rename and copy, so its +3 can be satisfied
entirely by copy while no rename resolves. That is why rename has its own axis,
and why HARD-PASS with RENAME-INCONCLUSIVE is a coherent composite rather than a
contradiction.

Both tables were verified by mechanical enumeration of every reachable state:
zero uncovered states, zero vacuous PASS.

Manifest SHA-256 2b8ce7018b6a67a6a522ac8d77638b4ccbd3f76634aa15b2f790443d0e6ad614
supersedes b113a31813636c31c2a78685631a8c4304f80bb13dd341151a604d0bc05d8d31.

The 63 frozen target hashes are byte-identical. No sampling, truth or B3 surface
was touched. No B3 run. No truth labels. No correctness result.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015vtUmvBDf69ccY5ju3PSHV
@PhysShell

Copy link
Copy Markdown
Owner Author

The rename axis was passable without resolving a single rename

New exact HEAD 03d56ace0623db64abe9db9d88c385717968ccca, parent 6555e68944296c0f47dd5dbf52c59c76548f8422.

The finding is valid and material. PASS-RENAME required only >= 3 adjudicable rename groups plus zero unsafe relation errors. Four unresolved answers satisfy both. An axis that can be passed by silence measures nothing.

The identical vacuity existed on the branch axis and was not flagged: row "all endpoint sets exact" is trivially true when B3 produced no endpoint sets at all. Same defect class, so both are fixed together rather than leaving the next round an identical finding.

Both axes are now exhaustive, precedence-ordered outcome tables.

rename, 4 rows. The denominator counts only groups whose truth is continued 1:1 through the rename — correctly calling ended on a renamed file does not demonstrate R-CONT-RENAME. PASS-RENAME requires >= 3 correct exact-counterpart resolutions.

branch, 5 rows. PASS-BRANCH requires >= 3 correct resolutions with exact successor endpoint sets, spanning >= 2 revision pairs.

verdict_taxonomy.no_vacuous_pass states the invariant once and records the resolution floor of every axis. natural and hard already carried floors — >= 20 B3-resolved and +3 over the better baseline — and needed no change.

Also recorded explicitly: HARD-PASS pools rename and copy, so its +3 can be satisfied entirely by copy while no rename resolves. That is precisely why rename carries its own axis, and why HARD-PASS together with RENAME-INCONCLUSIVE is a coherent composite rather than a contradiction.

Verified by mechanical enumeration of every reachable state of both tables: zero uncovered states, zero vacuous PASS. The exact case raised in review — 4 adjudicable, 0 resolved, 0 unsafe — now yields RENAME-INCONCLUSIVE.

Manifest SHA-256 2b8ce7018b6a67a6a522ac8d77638b4ccbd3f76634aa15b2f790443d0e6ad614 supersedes b113a318…. The PR header carried a stale hash for one revision and now matches.

The 63 frozen target hashes are byte-identical across all four revisions. No sampling, truth or B3 surface was touched. No B3 run. No truth labels. No correctness result.

@PhysShell

Copy link
Copy Markdown
Owner Author

@coderabbitai review

Final review requested on new exact HEAD 03d56ace0623db64abe9db9d88c385717968ccca. The round on 6555e689 is stale. One verdict-taxonomy finding addressed plus the identical vacuity found on the branch axis; the 63 frozen target hashes are unchanged across all four revisions.

@PhysShell

Copy link
Copy Markdown
Owner Author

@codex review

Final review requested on new exact HEAD 03d56ace0623db64abe9db9d88c385717968ccca. Your P1 on 6555e689 is fixed with an exhaustive precedence-ordered table, the denominator now restricted to truth = continued 1:1 through the rename. The branch axis carried the same vacuity and was fixed alongside it. Both tables were checked by enumerating every reachable state.

@coderabbitai

coderabbitai Bot commented Aug 30, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

head='03d56ace0623db64abe9db9d88c385717968ccca'
parent='6555e68944296c0f47dd5dbf52c59c76548f8422'
rev1='cde73560f029235959b7775a8466fca059c3f34e'
manifest='research/rh-m0/rh-m0-manifest.json'
sidecar='research/rh-m0/rh-m0-manifest.sha256'
merge='011c1362861f6b8b20c45e8ebd5bcb912401c1c0'
reviewed='6dcc02f7d82bebeb5db9be83f77ab8c5455f5692'

echo '== Commit identity and scope =='
test "$(git rev-parse HEAD)" = "$head"
test "$(git rev-parse "$head^")" = "$parent"
git show -s --format='commit=%H%nparents=%P%nsubject=%s' "$head"
git diff-tree --no-commit-id --name-status -r "$head"
git diff --no-ext-diff --unified=25 "$parent" "$head" -- "$manifest" "$sidecar"

echo '== Checksum sidecar =='
python3 - <<'PY'
from pathlib import Path
import hashlib
manifest=Path("research/rh-m0/rh-m0-manifest.json").read_bytes()
sidecar=Path("research/rh-m0/rh-m0-manifest.sha256").read_bytes()
print("sidecar_bytes=",repr(sidecar))
print("has_carriage_return=",b"\r" in sidecar)
print("has_final_lf=",sidecar.endswith(b"\n"))
declared=sidecar.split()[0].decode("ascii")
actual=hashlib.sha256(manifest).hexdigest()
print("declared_digest=",declared)
print("actual_digest=",actual)
print("digest_matches=",declared==actual)
PY
( cd research/rh-m0 && sha256sum --check rh-m0-manifest.sha256 )

echo '== Manifest outline =='
ast-grep outline "$manifest" --lang json --items all

echo '== Target preservation, sampling, sides, and outcome-table structure =='
python3 - <<'PY'
import hashlib, json, subprocess
from collections import Counter, defaultdict

p="research/rh-m0/rh-m0-manifest.json"
commits=[
 "cde73560f029235959b7775a8466fca059c3f34e",
 "9c0a8618f131e1bf0362d80538bb2af41f2d2c9a",
 "6555e68944296c0f47dd5dbf52c59c76548f8422",
 "03d56ace0623db64abe9db9d88c385717968ccca",
]
docs=[json.loads(subprocess.check_output(["git","show",f"{c}:{p}"],text=True)) for c in commits]
m=docs[-1]
targets=m["frozen_targets"]["targets"]
hashes=[[x["target_hash"] for x in d["frozen_targets"]["targets"]] for d in docs]
print("target_hash_lists_byte_identical_across_four_revisions=",hashes[0]==hashes[1]==hashes[2]==hashes[3])
print("manifest_revision=",m.get("manifest_revision",{}).get("revision"))
print("target_count_declared_actual=",m["frozen_targets"]["count"],len(targets))
print("target_class_counts=",dict(sorted(Counter(x["class"] for x in targets).items())))
print("unique_valid_target_hashes=",len(set(hashes[-1]))==len(hashes[-1]),
      all(len(h)==64 and set(h)<=set("0123456789abcdef") for h in hashes[-1]))

byclass=defaultdict(set)
for t in targets: byclass[t["class"]].add((t["a_sha"],t["b_sha"]))
allpairs={}
for c in ("natural","rename","copy"):
    no={(x["a_sha"],x["b_sha"]) for x in m["sample"]["no_target"][c]}
    allpairs[c]=byclass[c]|no
    print(f"{c}_selected_targetable_no_target_accounted=",
          m["sample"]["selected_pairs"][c],len(byclass[c]),len(no),len(allpairs[c]))
print("hard_total_declared_actual=",
      m["hard_challenge"]["frozen_hard_sample"]["total"],
      sum(t["class"] in ("rename","copy") for t in targets))
print("pair_class_overlaps=",{
    a+":"+b:len(allpairs[a]&allpairs[b])
    for a,b in (("natural","rename"),("natural","copy"),("rename","copy"))
})

seed=m["seeds"]["truth_seed"]
mismatches=[]
for i,t in enumerate(targets):
    if t["class"]=="natural":
        raw=json.dumps(["rh-m0-side/v1",seed,"sts",t["a_sha"],t["b_sha"]],
                       ensure_ascii=False,separators=(",",":")).encode()
        expected="A" if hashlib.sha256(raw).digest()[0]&1==0 else "B"
    else:
        expected="A"
    if t["side"]!=expected:
        mismatches.append((i,t["class"],t["side"],expected))
print("side_determination_mismatches=",mismatches)
print("truth_labels_created=",m["manifest_revision"].get("truth_labels_created"))
print("b3_executed=",m["manifest_revision"].get("b3_executed"))

for key in ("rename_capability","branch_capability","verdict_taxonomy"):
    print("\n== "+key+" ==")
    print(json.dumps(m.get(key),indent=2,sort_keys=True))
PY

echo '== Frozen Step-1 provenance available locally =='
git show -s --format='merge=%H%nparents=%P%ncommitter_time=%cI' "$merge"
if git merge-base --is-ancestor "$reviewed" "$merge"; then
  echo 'reviewed_head_is_ancestor=true'
else
  echo 'reviewed_head_is_ancestor=false'
fi
for path in contracts/finding-lineage-decision-v1.json contracts/finding-lineage-v1.json; do
  printf '%s merge_blob=%s reviewed_blob=%s\n' "$path" \
    "$(git rev-parse "$merge:$path")" "$(git rev-parse "$reviewed:$path")"
done
for path in aggregate/normalize.py aggregate/provenance.py aggregate/sarif_read.py; do
  printf '%s parent1=%s parent2=%s merge=%s\n' "$path" \
    "$(git rev-parse "$merge^1:$path")" "$(git rev-parse "$merge^2:$path")" "$(git rev-parse "$merge:$path")"
done

echo '== Listed commit objects and first-parent adjacency =='
python3 - <<'PY'
import json, subprocess
with open("research/rh-m0/rh-m0-manifest.json",encoding="utf-8") as f: m=json.load(f)
pairs={(x["a_sha"],x["b_sha"]) for x in m["frozen_targets"]["targets"]}
for c in ("natural","rename","copy"):
    pairs|={(x["a_sha"],x["b_sha"]) for x in m["sample"]["no_target"][c]}
missing=[]; bad=[]
for a,b in sorted(pairs):
    available=True
    for sha in (a,b):
        if subprocess.run(["git","cat-file","-e",sha+"^{commit}"]).returncode:
            missing.append(sha); available=False
    if available:
        parents=subprocess.check_output(["git","show","-s","--format=%P",b],text=True).split()
        if not parents or parents[0]!=a: bad.append((a,b,parents))
print("pair_count=",len(pairs))
print("missing_commit_objects=",sorted(set(missing)))
print("bad_first_parent_adjacency=",bad)
PY

Length of output: 25438


@PhysShell I found one blocking manifest-integrity defect.

  • research/rh-m0/rh-m0-manifest.sha256, Line 1: The sidecar uses CRLF line endings. The declared digest matches the manifest bytes, but GNU sha256sum --check fails because it reads the filename as rh-m0-manifest.json\r.

Convert the sidecar to LF line endings. Then run sha256sum --check rh-m0-manifest.sha256 from research/rh-m0.

The exact HEAD and parent match the requested commits. The commit changes only the manifest and checksum sidecar.

The script stopped at the checksum verification failure. Therefore, target preservation, verdict-table validation, and provenance checks remain inconclusive for this revision.

You are interacting with an AI system.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 03d56ace06

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread research/rh-m0/rh-m0-manifest.json Outdated
{
"condition": "any unsafe relation error on an adjudicable rename target",
"row": 1,
"verdict": "FAIL/PIVOT"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Align the rename failure with its verdict taxonomy

When any adjudicable rename target has an unsafe relation error, this row emits FAIL/PIVOT, but verdict_taxonomy.axes.rename permits only PASS-RENAME, RENAME-INCONCLUSIVE, and FAIL. The resulting five-axis composite therefore cannot both follow this outcome table and satisfy the frozen taxonomy; use the taxonomy's FAIL value here or add FAIL/PIVOT to the allowed rename verdicts before execution.

Useful? React with 👍 / 👎.

rename_capability.outcome_table row 1 emitted FAIL/PIVOT while
verdict_taxonomy.axes.rename declares PASS-RENAME / RENAME-INCONCLUSIVE / FAIL.
No run could satisfy both. The mirror image of the same defect was present and
unreported: FAIL was declared for the rename axis but reachable from no row.

The row now emits FAIL. The rename token set is deliberately NOT widened to
FAIL/PIVOT. That compound belongs to the branch axis by preregistration, and a
result vocabulary that grows to fit one row stops discriminating.

The pivot semantics are preserved as an experiment disposition instead:

  axis verdict FAIL         -> disposition PIVOT
  axis verdict INCONCLUSIVE -> disposition HOLD
  axis verdict PASS         -> disposition PROCEED

A verdict records what was measured; a disposition records what the project does
next. Fusing them makes the measured result depend on a management decision.

The branch axis keeps its compound FAIL/PIVOT token. It was preregistered that
way and is internally consistent with its own token set, so it is recorded as a
known wart rather than silently rewritten. Editing a frozen token to satisfy a
style preference is the kind of post-hoc change this manifest exists to prevent.

verdict_taxonomy.consistency_rule now states the invariant: every verdict in any
outcome_table must belong to its axis token set, and every declared token should
be reachable. Verified mechanically in both directions, zero mismatches and zero
unreachable tokens. Enumerating every reachable rename state yields exactly the
three declared verdicts.

Why prose review missed it: the rename table was written by analogy with the
branch table, which legitimately uses FAIL/PIVOT. Reading compares a row against
its neighbours; only a mechanical check compares it against its own axis.

Manifest SHA-256 f4c5181eaf0948dc38f6d86c884404331a5111f275228d78cd7ca22c0f97f47e
supersedes 2b8ce7018b6a67a6a522ac8d77638b4ccbd3f76634aa15b2f790443d0e6ad614.

The 63 frozen target hashes are byte-identical. No sampling, truth or B3 surface
was touched. No B3 run. No truth labels. No correctness result.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015vtUmvBDf69ccY5ju3PSHV
@PhysShell

Copy link
Copy Markdown
Owner Author

One verdict token, plus the check that would have caught it

New exact HEAD 5c89ba515da4e5e84b3fb7749ccdf9d99bd28ffa, parent 03d56ace0623db64abe9db9d88c385717968ccca.

Valid. rename_capability.outcome_table row 1 emitted FAIL/PIVOT while verdict_taxonomy.axes.rename declares PASS-RENAME / RENAME-INCONCLUSIVE / FAIL. No run could satisfy both.

Running the check mechanically surfaced the mirror image of the same defect, which had not been reported: FAIL was declared for the rename axis and reachable from no row. One token, two symptoms.

Fixed minimally. Row 1 now emits FAIL. The rename token set is deliberately not widened: FAIL/PIVOT belongs to the branch axis by preregistration, and a result vocabulary that grows to fit one row stops discriminating.

Pivot semantics kept, but separated from the measurement:

Axis verdict Disposition
FAIL PIVOT
INCONCLUSIVE HOLD
PASS PROCEED

A verdict records what was measured; a disposition records what the project does next. Fusing them makes the measured result depend on a management decision, and groups badly for any consumer.

The branch axis keeps its compound token. It was preregistered that way and is internally consistent with its own token set, so it is recorded as a known wart rather than silently rewritten. Editing a frozen token to satisfy a style preference is precisely the post-hoc change this manifest exists to prevent.

The invariant is now written down. verdict_taxonomy.consistency_rule: every verdict in any outcome table must belong to its axis token set, and every declared token should be reachable. Verified in both directions — zero mismatches, zero unreachable tokens. Enumerating every reachable rename state yields exactly the three declared verdicts.

Why prose review missed it: the rename table was written by analogy with the branch table, which legitimately uses FAIL/PIVOT. Reading compares a row against its neighbours; only a mechanical check compares it against its own axis.

Manifest SHA-256 f4c5181eaf0948dc38f6d86c884404331a5111f275228d78cd7ca22c0f97f47e supersedes 2b8ce701…. PR header and revision table updated.

The 63 frozen target hashes are byte-identical across all five revisions. No sampling, truth or B3 surface was touched. No B3 run. No truth labels. No correctness result.

@PhysShell

Copy link
Copy Markdown
Owner Author

@coderabbitai review

Final review requested on new exact HEAD 5c89ba515da4e5e84b3fb7749ccdf9d99bd28ffa. The round on 03d56ace is stale. One verdict-token mismatch fixed, and the invariant that would have caught it is now stated and verified mechanically. The 63 frozen target hashes are unchanged across all five revisions.

@PhysShell

Copy link
Copy Markdown
Owner Author

@codex review

Final review requested on new exact HEAD 5c89ba515da4e5e84b3fb7749ccdf9d99bd28ffa. Your P2 on 03d56ace is fixed by emitting FAIL rather than widening the rename token set; the mirror defect you did not report, FAIL being declared but unreachable, is resolved by the same change. verdict_taxonomy.consistency_rule now states the invariant and it was checked in both directions.

@coderabbitai

coderabbitai Bot commented Aug 30, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

head='5c89ba515da4e5e84b3fb7749ccdf9d99bd28ffa'
parent='03d56ace0623db64abe9db9d88c385717968ccca'
manifest='research/rh-m0/rh-m0-manifest.json'
sidecar='research/rh-m0/rh-m0-manifest.sha256'
revisions=(
  'cde73560f029235959b7775a8466fca059c3f34e'
  '9c0a8618f131e1bf0362d80538bb2af41f2d2c9a'
  '6555e68944296c0f47dd5dbf52c59c76548f8422'
  '03d56ace0623db64abe9db9d88c385717968ccca'
  '5c89ba515da4e5e84b3fb7749ccdf9d99bd28ffa'
)
merge='011c1362861f6b8b20c45e8ebd5bcb912401c1c0'
reviewed='6dcc02f7d82bebeb5db9be83f77ab8c5455f5692'

echo '== Commit identity and change scope =='
test "$(git rev-parse HEAD)" = "$head"
test "$(git rev-parse "$head^")" = "$parent"
git show -s --format='commit=%H%nparent=%P%nsubject=%s' "$head"
git diff-tree --no-commit-id --name-status -r "$head"
git diff --no-ext-diff --unified=12 "$parent" "$head" -- "$manifest" "$sidecar"

echo '== Sidecar bytes and standard verification =='
python3 - <<'PY'
from pathlib import Path
import hashlib
manifest = Path("research/rh-m0/rh-m0-manifest.json").read_bytes()
sidecar = Path("research/rh-m0/rh-m0-manifest.sha256").read_bytes()
print("sidecar_repr=", repr(sidecar))
print("contains_carriage_return=", b"\r" in sidecar)
print("ends_with_lf=", sidecar.endswith(b"\n"))
fields = sidecar.split()
print("sidecar_field_count=", len(fields))
print("declared_digest=", fields[0].decode("ascii") if fields else None)
print("actual_digest=", hashlib.sha256(manifest).hexdigest())
print("digest_matches=", bool(fields) and fields[0].decode("ascii") == hashlib.sha256(manifest).hexdigest())
PY
( cd research/rh-m0 && sha256sum --check rh-m0-manifest.sha256 )

echo '== Frozen-target preservation and structural accounting =='
python3 - <<'PY'
import hashlib, json, subprocess
from collections import Counter, defaultdict

p = "research/rh-m0/rh-m0-manifest.json"
revs = [
 "cde73560f029235959b7775a8466fca059c3f34e",
 "9c0a8618f131e1bf0362d80538bb2af41f2d2c9a",
 "6555e68944296c0f47dd5dbf52c59c76548f8422",
 "03d56ace0623db64abe9db9d88c385717968ccca",
 "5c89ba515da4e5e84b3fb7749ccdf9d99bd28ffa",
]
docs = [json.loads(subprocess.check_output(["git","show",f"{r}:{p}"], text=True)) for r in revs]
m = docs[-1]
targets = m["frozen_targets"]["targets"]
hash_lists = [[x["target_hash"] for x in d["frozen_targets"]["targets"]] for d in docs]
print("target_hash_lists_byte_identical_all_revisions=", all(v == hash_lists[0] for v in hash_lists[1:]))
print("manifest_revision=", m["manifest_revision"]["revision"])
print("declared_actual_target_count=", m["frozen_targets"]["count"], len(targets))
print("target_class_counts=", dict(sorted(Counter(x["class"] for x in targets).items())))
print("unique_target_hashes=", len(set(hash_lists[-1])))
print("target_hashes_valid=", all(len(h)==64 and set(h) <= set("0123456789abcdef") for h in hash_lists[-1]))
by_class=defaultdict(set)
for t in targets: by_class[t["class"]].add((t["a_sha"],t["b_sha"]))
all_pairs={}
for cls in ("natural","rename","copy"):
    no_target={(x["a_sha"],x["b_sha"]) for x in m["sample"]["no_target"][cls]}
    all_pairs[cls]=by_class[cls] | no_target
    print(f"{cls}_selected_targetable_no_target_accounted=", m["sample"]["selected_pairs"][cls],len(by_class[cls]),len(no_target),len(all_pairs[cls]))
print("hard_declared_actual=",m["hard_challenge"]["frozen_hard_sample"]["total"],sum(t["class"] != "natural" for t in targets))
print("cross_class_pair_overlaps=", {f"{a}:{b}":len(all_pairs[a]&all_pairs[b]) for a,b in (("natural","rename"),("natural","copy"),("rename","copy"))})

seed=m["seeds"]["truth_seed"]
bad_sides=[]
for i,t in enumerate(targets):
    if t["class"] == "natural":
        raw=json.dumps(["rh-m0-side/v1",seed,"sts",t["a_sha"],t["b_sha"]],ensure_ascii=False,separators=(",",":")).encode()
        expected="A" if hashlib.sha256(raw).digest()[0]&1 == 0 else "B"
    else:
        expected="A"
    if t["side"] != expected: bad_sides.append((i,t["class"],t["side"],expected))
print("side_determination_mismatches=",bad_sides)
print("truth_labels_created=",m["manifest_revision"]["truth_labels_created"])
print("b3_executed=",m["manifest_revision"]["b3_executed"])
PY

echo '== Verdict vocabulary consistency and relevant sections =='
python3 - <<'PY'
import json
p="research/rh-m0/rh-m0-manifest.json"
with open(p,encoding="utf-8") as f: m=json.load(f)

taxonomy=m["verdict_taxonomy"]
print("consistency_rule=",taxonomy.get("consistency_rule"))
for capability_name in ("rename_capability","branch_capability"):
    capability=m[capability_name]
    axis=capability["axis"]
    declared=set(taxonomy["axes"][axis]["tokens"])
    rows=capability["outcome_table"]
    emitted={row["verdict"] for row in rows}
    print(f"{capability_name}_axis=",axis)
    print(f"{capability_name}_declared_tokens=",sorted(declared))
    print(f"{capability_name}_emitted_tokens=",sorted(emitted))
    print(f"{capability_name}_out_of_vocabulary=",sorted(emitted-declared))
    print(f"{capability_name}_unreachable_declared=",sorted(declared-emitted))
    print(json.dumps(capability,indent=2,sort_keys=True))
print("no_vacuous_pass=",taxonomy.get("no_vacuous_pass"))
print("axes_are_five=",taxonomy.get("axes_are_five"))
print("examples_exact_axes=",all(set(x)==set(taxonomy["axes_are_five"]) for x in taxonomy["examples"]))
PY

echo '== Frozen Step-1 provenance =='
git show -s --format='merge=%H%nparents=%P%ncommitter_time=%cI' "$merge"
git merge-base --is-ancestor "$reviewed" "$merge" && echo 'reviewed_head_is_ancestor=true'
for path in contracts/finding-lineage-decision-v1.json contracts/finding-lineage-v1.json; do
  printf '%s merge_blob=%s reviewed_blob=%s\n' "$path" \
    "$(git rev-parse "$merge:$path")" "$(git rev-parse "$reviewed:$path")"
done
for path in aggregate/normalize.py aggregate/provenance.py aggregate/sarif_read.py; do
  printf '%s parent1=%s parent2=%s merge=%s\n' "$path" \
    "$(git rev-parse "$merge^1:$path")" "$(git rev-parse "$merge^2:$path")" "$(git rev-parse "$merge:$path")"
done

Length of output: 18693


@PhysShell I found one blocking manifest-integrity defect.

  • research/rh-m0/rh-m0-manifest.sha256, Line 1: The sidecar still uses CRLF line endings. Its declared digest matches the manifest bytes. However, GNU sha256sum --check reads the filename as rh-m0-manifest.json\r and fails.

Convert this sidecar to LF line endings. Then run this command from research/rh-m0:

sha256sum --check rh-m0-manifest.sha256

The requested HEAD and parent match. The commit changes only the manifest and checksum sidecar.

The verification script stopped at the checksum failure. Therefore, verification of target preservation across all five revisions, sample accounting, side determination, and verdict-table consistency is incomplete for this revision.

You are interacting with an AI system.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5c89ba515d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread research/rh-m0/rh-m0-manifest.json Outdated
Comment on lines +1289 to +1292
"method": "cross-product check of all outcome_table rows against verdict_taxonomy.axes, in both directions",
"result_at_this_revision": {
"declared_but_unreachable": 0,
"mismatches": 0

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Scope the reachability result to axes with outcome tables

The stated mechanical method cannot produce declared_but_unreachable: 0 across all five axes: only branch_capability and rename_capability contain an outcome_table, while the hard, natural, and merge axes collectively declare seven tokens that appear in no outcome-table row. At exact HEAD 5c89ba5, enumerating each axis's declared tokens against its corresponding outcome_table therefore reports those seven as unreachable. Either restrict the invariant/result explicitly to the two table-backed axes, or encode/check equivalent outcome rows for hard, natural, and merge before claiming a bidirectional zero.

Useful? React with 👍 / 👎.

@PhysShell

Copy link
Copy Markdown
Owner Author

Documentation consistency only

New exact HEAD 11057180fe0c12011ea82c7f85cb57483c0225da, parent ad58b29383d36b9fad0f0dc65a5faa3ace6636e8.

Valid. Two sentences inside consistency_rule stopped being true when natural gained an outcome table in revision 7 and hard in revision 8. They still said "the other three axes are NOT given outcome tables" and reported seven falsely unreachable tokens as natural 3, hard 3, merge 1. Both were correct at revision 6; they are history, not current state.

Current state, derived from the structure: four axes are table-backed, and applying table reachability to all five would falsely report exactly one unreachable token — MERGE-INCONCLUSIVE — solely because merge intentionally has no outcome table.

The count is no longer prose. It moved into consistency_rule.falsely_unreachable_if_applied_to_all_axes, computed from the structure, with the revision-6 value retained as history. why_the_scope_matters now points at that field as authoritative rather than at itself. A number living in prose goes stale the moment the structure moves, which is precisely what happened.

Two structural invariants are now asserted — against the structure, never against any wording:

set(consistency_rule.scope) == set(axes whose section contains an outcome_table)
set(non_table_backed_axes)  == set(verdict_taxonomy.axes) - set(scope)

Asserting the wording would require a checker for the checker's description, and then one for that.

Nothing else moved. Asserted before writing: verdict_taxonomy.axes, axis_disposition, consistency_rule.scope, all four outcome tables, truth_protocol, seeds and the 63 frozen target hashes. A leaf-level diff against the previous commit touches manifest_revision and consistency_rule and nothing else.

Verified from the committed blobs: zero carriage returns in either file, sha256sum --check prints rh-m0-manifest.json: OK. Across all nine revisions the 63 target hashes are identical, the axis taxonomy is unchanged since revision 4, and all four outcome tables are byte-identical between revisions 8 and 9.

Manifest SHA-256 502477c44fb9a315efef10d3504ef33eabfa12f17401ed4abc7905c5c9c50b94 supersedes ff6a5ed4….

No B3 run. No truth labels. No correctness result.

@PhysShell

Copy link
Copy Markdown
Owner Author

@coderabbitai review

Final review requested on new exact HEAD 11057180fe0c12011ea82c7f85cb57483c0225da. Documentation-consistency correction only: two stale sentences in consistency_rule now follow the structure, and the falsely-unreachable count became a derived field. No rule, table, threshold, vocabulary or disposition changed, and the patch asserts that. Sidecar remains LF and sha256sum --check passes from the committed blobs.

@PhysShell

Copy link
Copy Markdown
Owner Author

@codex review

Final review requested on new exact HEAD 11057180fe0c12011ea82c7f85cb57483c0225da. Your P2 on ad58b293 is fixed: no_tables_added_for_uniformity now names merge alone, why_the_scope_matters states the current figure of one and marks seven as historical, and the count moved into a derived field so it cannot drift again. Two structural invariants tying scope to the presence of outcome tables are asserted.

@coderabbitai

coderabbitai Bot commented Aug 30, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

head='11057180fe0c12011ea82c7f85cb57483c0225da'
parent='ad58b29383d36b9fad0f0dc65a5faa3ace6636e8'
manifest='research/rh-m0/rh-m0-manifest.json'
sidecar='research/rh-m0/rh-m0-manifest.sha256'

echo '== Commit identity and changed paths =='
test "$(git rev-parse HEAD)" = "$head"
test "$(git rev-parse "$head^")" = "$parent"
git show -s --format='commit=%H%nparent=%P%nsubject=%s' "$head"
git diff-tree --no-commit-id --name-status -r "$head"
git diff --no-ext-diff --unified=30 "$parent" "$head" -- "$manifest" "$sidecar"

echo '== Committed-blob checksum and line endings =='
python3 - <<'PY'
import hashlib, subprocess, tempfile
from pathlib import Path

commit = "11057180fe0c12011ea82c7f85cb57483c0225da"
manifest = "research/rh-m0/rh-m0-manifest.json"
sidecar = "research/rh-m0/rh-m0-manifest.sha256"
m = subprocess.check_output(["git", "show", f"{commit}:{manifest}"])
s = subprocess.check_output(["git", "show", f"{commit}:{sidecar}"])
print("manifest_has_cr=", b"\r" in m)
print("sidecar_has_cr=", b"\r" in s)
print("manifest_sha256=", hashlib.sha256(m).hexdigest())
print("sidecar=", repr(s))
with tempfile.TemporaryDirectory() as d:
    root = Path(d)
    (root / "rh-m0-manifest.json").write_bytes(m)
    (root / "rh-m0-manifest.sha256").write_bytes(s)
    r = subprocess.run(["sha256sum", "--check", "rh-m0-manifest.sha256"],
                       cwd=d, capture_output=True, text=True)
    print("sha256sum_exit=", r.returncode)
    print("sha256sum_stdout=", r.stdout.strip())
    print("sha256sum_stderr=", r.stderr.strip())
PY

echo '== Revision-preservation and consistency-rule structure =='
python3 - <<'PY'
import hashlib, json, subprocess
from collections import Counter

p="research/rh-m0/rh-m0-manifest.json"
before_commit="ad58b29383d36b9fad0f0dc65a5faa3ace6636e8"
after_commit="11057180fe0c12011ea82c7f85cb57483c0225da"
before_raw=subprocess.check_output(["git","show",f"{before_commit}:{p}"])
after_raw=subprocess.check_output(["git","show",f"{after_commit}:{p}"])
before=json.loads(before_raw)
after=json.loads(after_raw)

print("revision=",after["manifest_revision"]["revision"])
print("target_hash_lists_identical=", [x["target_hash"] for x in before["frozen_targets"]["targets"]] == [x["target_hash"] for x in after["frozen_targets"]["targets"]])
print("target_counts=",after["frozen_targets"]["count"],len(after["frozen_targets"]["targets"]),dict(Counter(t["class"] for t in after["frozen_targets"]["targets"])))
for key in ("axes","axis_disposition"):
    print("taxonomy_"+key+"_unchanged=",before["verdict_taxonomy"][key] == after["verdict_taxonomy"][key])
for key in ("truth_protocol","seeds","primary_holdout","hard_challenge","rename_capability","branch_capability"):
    print(key+"_unchanged=",before[key] == after[key])

tax=after["verdict_taxonomy"]
rule=tax["consistency_rule"]
axes=set(tax["axes"])
table_sections={
    "natural":"primary_holdout",
    "hard":"hard_challenge",
    "rename":"rename_capability",
    "branch":"branch_capability",
    "merge":"merge_capability",
}
table_backed={axis for axis, section in table_sections.items() if "outcome_table" in after[section]}
scope=set(rule["scope"])
non_table=set(rule["non_table_backed_axes"])
print("table_backed_axes=",sorted(table_backed))
print("scope=",sorted(scope))
print("non_table_backed_axes=",sorted(non_table))
print("scope_equals_table_backed=",scope == table_backed)
print("non_table_equals_axes_minus_scope=",non_table == axes-scope)

emitted=set()
for axis in scope:
    emitted |= {row["verdict"] for row in after[table_sections[axis]]["outcome_table"]}
falsely_unreachable=sorted(
    token for axis in axes-scope for token in after["verdict_taxonomy"]["axes"][axis]
    if token not in emitted
)
print("computed_falsely_unreachable=",falsely_unreachable)
print("derived_field=",rule.get("falsely_unreachable_if_applied_to_all_axes"))
print("derived_field_matches=",rule.get("falsely_unreachable_if_applied_to_all_axes") == falsely_unreachable)

for entry in after["manifest_revision"]["history"]:
    raw=subprocess.check_output(["git","show",f"{entry['commit']}:{p}"])
    print("history_sha",entry["revision"],hashlib.sha256(raw).hexdigest() == entry["sha256"])
print("truth_labels_created=",after["manifest_revision"]["truth_labels_created"])
print("b3_executed=",after["manifest_revision"]["b3_executed"])
PY

echo '== Relevant current text =='
python3 - <<'PY'
import json
with open("research/rh-m0/rh-m0-manifest.json", encoding="utf-8") as f:
    m=json.load(f)
print(json.dumps(m["manifest_revision"], indent=2, sort_keys=True))
print(json.dumps(m["verdict_taxonomy"]["consistency_rule"], indent=2, sort_keys=True))
PY

Length of output: 30566


🏁 Script executed:

#!/bin/bash
set -euo pipefail

manifest='research/rh-m0/rh-m0-manifest.json'

echo '== Derived-field location =='
rg -n -C 12 '"falsely_unreachable_if_applied_to_all_axes"|"derived": true|"why_derived"' "$manifest"

echo '== References outside the manifest =='
rg -n -C 3 \
  'falsely_unreachable_if_applied_to_all_axes|scope_equals_table_backed_axes|non_table_backed_is_the_complement|verified_mechanically' \
  . -g '!research/rh-m0/rh-m0-manifest.json'

echo '== Value-level validation =='
python3 - <<'PY'
import json
with open("research/rh-m0/rh-m0-manifest.json", encoding="utf-8") as f:
    m=json.load(f)

tax=m["verdict_taxonomy"]
rule=tax["consistency_rule"]
section={"natural":"primary_holdout","hard":"hard_challenge","rename":"rename_capability","branch":"branch_capability","merge":"merge_capability"}
axes=set(tax["axes"])
table_backed={axis for axis, name in section.items() if "outcome_table" in m[name]}
non_table=axes-table_backed
expected=[
    f"{axis}: {token}"
    for axis in sorted(non_table)
    for token in tax["axes"][axis]
]
actual=rule["falsely_unreachable_if_applied_to_all_axes"]
print("expected_count=",len(expected))
print("actual_count=",actual["count"])
print("expected_tokens=",expected)
print("actual_tokens=",actual["tokens"])
print("content_matches=",actual["count"]==len(expected) and actual["tokens"]==expected)
PY

Length of output: 5261


🏁 Script executed:

#!/bin/bash
set -u -o pipefail

echo '== Files in the RH-M0 directory =='
fd -HI -t f . research/rh-m0 | sort

echo '== Nonfatal external-reference search =='
rg -n -C 3 \
  'falsely_unreachable_if_applied_to_all_axes|scope_equals_table_backed_axes|non_table_backed_is_the_complement|verified_mechanically' \
  . -g '!research/rh-m0/rh-m0-manifest.json' || true

echo '== Value-level validation =='
python3 - <<'PY'
import json
with open("research/rh-m0/rh-m0-manifest.json", encoding="utf-8") as f:
    m=json.load(f)

tax=m["verdict_taxonomy"]
rule=tax["consistency_rule"]
section={"natural":"primary_holdout","hard":"hard_challenge","rename":"rename_capability","branch":"branch_capability","merge":"merge_capability"}
axes=set(tax["axes"])
table_backed={axis for axis, name in section.items() if "outcome_table" in m[name]}
non_table=axes-table_backed
expected=[
    f"{axis}: {token}"
    for axis in sorted(non_table)
    for token in tax["axes"][axis]
]
actual=rule["falsely_unreachable_if_applied_to_all_axes"]
print("expected_count=",len(expected))
print("actual_count=",actual["count"])
print("expected_tokens=",expected)
print("actual_tokens=",actual["tokens"])
print("content_matches=",actual["count"]==len(expected) and actual["tokens"]==expected)
PY

Length of output: 1727


@PhysShell I found one manifest-integrity issue.

  • research/rh-m0/rh-m0-manifest.json, Line 1410: falsely_unreachable_if_applied_to_all_axes is described as derived and unable to drift. It is still a literal count and tokens value inside the manifest. The repository has no executable validator or generated artifact that recomputes this object from verdict_taxonomy.axes and the presence of outcome_table. A later structural change can therefore make the field stale again.

    Add a committed checker that recomputes the count and tokens and fails on a mismatch, or do not store the duplicated values in the manifest. The current values are correct: they resolve to one token, merge: MERGE-INCONCLUSIVE.

The remaining checks passed:

  • The requested HEAD and parent match.
  • Only the manifest and checksum sidecar changed.
  • The committed manifest and sidecar use LF line endings.
  • The extracted committed blobs pass sha256sum --check.
  • The checksum is 502477c44fb9a315efef10d3504ef33eabfa12f17401ed4abc7905c5c9c50b94.
  • The 63 frozen target hashes are unchanged from revision 8.
  • The target counts remain 50 natural, 4 rename, and 9 copy.
  • The verdict taxonomy, axis dispositions, truth protocol, seeds, and all four outcome tables are unchanged.
  • consistency_rule.scope equals the table-backed axes.
  • non_table_backed_axes equals the complement of that scope.

You are interacting with an AI system.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 11057180fe

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread research/rh-m0/rh-m0-manifest.json Outdated
Comment on lines +1411 to +1413
"count": 1,
"derivation": "for every axis without an outcome_table, every token declared for it in verdict_taxonomy.axes",
"derived": true,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Actually derive the tableless-axis count

At exact HEAD 1105718, this is still a cached JSON literal rather than a derived value: repo-wide search finds no generator or validator for this field, and the new structural invariants are likewise unevaluated strings followed by verified_mechanically: true. Consequently, adding another outcome table or taxonomy token can make count and tokens stale exactly as before; derived: true alone does not make the claimed invariant hold. Add an executable check/generation step or avoid storing the redundant calculated values.

Useful? React with 👍 / 👎.

…ain it

Both external reviewers independently found that
falsely_unreachable_if_applied_to_all_axes stored a literal count and token list
while declaring derived: true, with no generator committed anywhere in the
repository. The arithmetic was correct and both reviewers confirmed the value.
The claim about its provenance was not: a reader of this repository sees a JSON
literal and a declaration that something derives it, and nothing does.

The materialised current value is removed rather than backed by a new checker.
current_value_materialization is now PROHIBITED, with the derivation rule kept so
a consumer can compute the value from the authoritative structure. The revision-6
entry stays, because it records what the computation yielded then and cannot
become semantically stale.

why_the_scope_matters no longer names the number at all. Stating it in a third
place would only schedule the next archaeological layer.

The same seam ran wider than the finding. Four verified_mechanically flags and
three result snapshots read as claims that a permanent validator enforces those
properties. RH-M0 commits the manifest and its checksum sidecar only, so nothing
does. They are renamed to say what they are:

  verified_mechanically  -> verified_during_revision_construction
  mechanical_result      -> result_at_revision_construction
  result_at_this_revision -> result_at_revision_construction

verdict_taxonomy.verification_status states the position once: these are
construction-time observations, not live repository invariants, and no checker is
committed to service one counter.

Documentation only. Asserted before writing: verdict_taxonomy.axes,
axis_disposition, consistency_rule.scope, all four outcome tables, truth_protocol,
seeds and the 63 frozen target hashes. A key-level scan confirms none of the
retired names survive as keys; they appear only inside prose that describes the
rename.

Both files remain LF; sha256sum --check passes.

Manifest SHA-256 08f266e927d733ac177a6b5592da6cab4db51df8b82467e0bacc3b7f6b342758
supersedes 502477c44fb9a315efef10d3504ef33eabfa12f17401ed4abc7905c5c9c50b94.

No B3 run. No truth labels. No correctness result.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015vtUmvBDf69ccY5ju3PSHV
@PhysShell

Copy link
Copy Markdown
Owner Author

The cache is removed, not managed

New exact HEAD b10d48b231de80fa4a5deeb636174a555188a74b, parent 11057180fe0c12011ea82c7f85cb57483c0225da.

Valid, and raised independently by both reviewers. falsely_unreachable_if_applied_to_all_axes stored a literal count and token list while declaring derived: true, with no generator committed anywhere in the repository. Confirmed: research/ contains the manifest and its sidecar and nothing else. The arithmetic was right and both reviewers reproduced it; the provenance claim was wrong.

Removed rather than backed by a new checker. current_value_materialization is now PROHIBITED, the derivation rule stays so a consumer can compute the value from the authoritative structure, and the revision-6 entry is retained — it records what the computation yielded then, so it cannot become semantically stale. why_the_scope_matters no longer names the number at all; stating it in a third place would only schedule the next archaeological layer.

Committing a validator to service one counter would have re-expanded the RH-M0 surface at the exact moment the remaining defects became purely meta-level.

The seam ran wider than the finding. Four verified_mechanically flags and three result snapshots read as claims that a permanent validator enforces those properties. Nothing does.

Retired Replacement
verified_mechanically verified_during_revision_construction
mechanical_result result_at_revision_construction
result_at_this_revision result_at_revision_construction

verdict_taxonomy.verification_status states the position once: these are construction-time observations, not live repository invariants, and no checker is committed.

Two things worth noting about how this was verified. The patch's first assertion attempt scanned the serialised JSON for the retired names and fired — because those names legitimately appear inside the prose describing the rename. The assertion was rewritten to scan keys, which is the property that actually matters, and the run aborted before writing rather than producing a half-correct artifact. A leftover why_derived key describing behaviour that no longer exists was then removed before commit.

Verified from the committed blobs: zero carriage returns in either file, sha256sum --check prints rh-m0-manifest.json: OK, no retired key survives, and truth_protocol, seeds, the axis taxonomy, all four outcome tables and the 63 target hashes are identical to revision 9.

Manifest SHA-256 08f266e927d733ac177a6b5592da6cab4db51df8b82467e0bacc3b7f6b342758 supersedes 502477c4….

No B3 run. No truth labels. No correctness result.

@PhysShell

Copy link
Copy Markdown
Owner Author

@coderabbitai review

Final review requested on new exact HEAD b10d48b231de80fa4a5deeb636174a555188a74b. The round on 11057180 is stale. You and Codex raised the same finding independently: the materialised current value is now removed rather than backed by a new checker, and the four verified_mechanically flags plus three result snapshots are renamed to say they are construction-time observations, since RH-M0 commits only the manifest and its sidecar.

@PhysShell

Copy link
Copy Markdown
Owner Author

@codex review

Final review requested on new exact HEAD b10d48b231de80fa4a5deeb636174a555188a74b. Your P2 on 11057180 is fixed by deleting count, tokens and derived rather than committing a generator for one counter; current_value_materialization is now PROHIBITED and why_the_scope_matters no longer names the number. The same seam in four verified_mechanically flags and three snapshots was closed alongside.

@coderabbitai

coderabbitai Bot commented Aug 30, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

head='b10d48b231de80fa4a5deeb636174a555188a74b'
parent='11057180fe0c12011ea82c7f85cb57483c0225da'
manifest='research/rh-m0/rh-m0-manifest.json'
sidecar='research/rh-m0/rh-m0-manifest.sha256'
merge='011c1362861f6b8b20c45e8ebd5bcb912401c1c0'
reviewed='6dcc02f7d82bebeb5db9be83f77ab8c5455f5692'

echo '== Commit identity and changed paths =='
test "$(git rev-parse HEAD)" = "$head"
test "$(git rev-parse "$head^")" = "$parent"
git show -s --format='commit=%H%nparent=%P%nsubject=%s' "$head"
git diff-tree --no-commit-id --name-status -r "$head"
git diff --no-ext-diff --unified=20 "$parent" "$head" -- "$manifest" "$sidecar"

echo '== Committed-blob checksum and encoding =='
python3 - <<'PY'
import hashlib, subprocess, tempfile
from pathlib import Path
commit='b10d48b231de80fa4a5deeb636174a555188a74b'
manifest='research/rh-m0/rh-m0-manifest.json'
sidecar='research/rh-m0/rh-m0-manifest.sha256'
m=subprocess.check_output(['git','show',f'{commit}:{manifest}'])
s=subprocess.check_output(['git','show',f'{commit}:{sidecar}'])
print('manifest_has_cr=', b'\r' in m)
print('sidecar_has_cr=', b'\r' in s)
print('manifest_sha256=', hashlib.sha256(m).hexdigest())
print('sidecar_repr=', repr(s))
with tempfile.TemporaryDirectory() as d:
    root=Path(d)
    (root/'rh-m0-manifest.json').write_bytes(m)
    (root/'rh-m0-manifest.sha256').write_bytes(s)
    r=subprocess.run(['sha256sum','--check','rh-m0-manifest.sha256'],
                     cwd=d,capture_output=True,text=True)
    print('sha256sum_exit=',r.returncode)
    print('sha256sum_stdout=',r.stdout.strip())
    print('sha256sum_stderr=',r.stderr.strip())
PY

echo '== Revision-10 semantic, vocabulary, and target invariants =='
python3 - <<'PY'
import hashlib, json, subprocess
from collections import Counter, defaultdict

p='research/rh-m0/rh-m0-manifest.json'
revs=[
 'cde73560f029235959b7775a8466fca059c3f34e',
 '9c0a8618f131e1bf0362d80538bb2af41f2d2c9a',
 '6555e68944296c0f47dd5dbf52c59c76548f8422',
 '03d56ace0623db64abe9db9d88c385717968ccca',
 '5c89ba515da4e5e84b3fb7749ccdf9d99bd28ffa',
 'ac54fcb3420550f0423366842fdd488f69bf1b3a',
 '6e3304a498951089f8945f6cb3ec99978e65e887',
 'ad58b29383d36b9fad0f0dc65a5faa3ace6636e8',
 '11057180fe0c12011ea82c7f85cb57483c0225da',
 'b10d48b231de80fa4a5deeb636174a555188a74b',
]
docs=[json.loads(subprocess.check_output(['git','show',f'{r}:{p}'])) for r in revs]
before, m=docs[-2], docs[-1]
targets=m['frozen_targets']['targets']
lists=[[t['target_hash'] for t in d['frozen_targets']['targets']] for d in docs]

print('json_valid=true')
print('revision=',m['manifest_revision']['revision'])
print('target_hash_lists_identical_all_10=',all(x==lists[0] for x in lists[1:]))
print('target_counts=',m['frozen_targets']['count'],len(targets),
      dict(sorted(Counter(t['class'] for t in targets).items())))
print('target_hashes_unique_valid=',len(set(lists[-1]))==len(lists[-1]) and all(
      len(h)==64 and set(h)<=set('0123456789abcdef') for h in lists[-1]))

pairs=defaultdict(set)
for t in targets: pairs[t['class']].add((t['a_sha'],t['b_sha']))
for cls in ('natural','rename','copy'):
    no={(x['a_sha'],x['b_sha']) for x in m['sample']['no_target'][cls]}
    print(cls+'_selected_targetable_no_target_accounted=',
          m['sample']['selected_pairs'][cls],len(pairs[cls]),len(no),len(pairs[cls]|no))
print('hard_declared_actual=',m['hard_challenge']['frozen_hard_sample']['total'],
      sum(t['class']!='natural' for t in targets))

seed=m['seeds']['truth_seed']; bad_sides=[]
for i,t in enumerate(targets):
    if t['class']=='natural':
        raw=json.dumps(['rh-m0-side/v1',seed,'sts',t['a_sha'],t['b_sha']],
                       ensure_ascii=False,separators=(',',':')).encode()
        expected='A' if hashlib.sha256(raw).digest()[0]&1==0 else 'B'
    else: expected='A'
    if t['side']!=expected: bad_sides.append((i,t['class'],t['side'],expected))
print('side_determination_mismatches=',bad_sides)
print('truth_labels_created=',m['manifest_revision']['truth_labels_created'])
print('b3_executed=',m['manifest_revision']['b3_executed'])

# Assert the documented unchanged surfaces.
for key in ('truth_protocol','seeds','primary_holdout','hard_challenge',
            'rename_capability','branch_capability'):
    print(key+'_unchanged_from_rev9=',before[key]==m[key])
for key in ('axes','axis_disposition'):
    print('taxonomy_'+key+'_unchanged_from_rev9=',
          before['verdict_taxonomy'][key]==m['verdict_taxonomy'][key])

# Retired keys must not survive as keys; prose may legitimately quote them.
retired={'falsely_unreachable_if_applied_to_all_axes','derived','why_derived',
         'verified_mechanically','mechanical_result','result_at_this_revision'}
found=[]
def walk(x,path='$'):
    if isinstance(x,dict):
        for k,v in x.items():
            if k in retired: found.append(path+'.'+k)
            walk(v,path+'.'+k)
    elif isinstance(x,list):
        for i,v in enumerate(x): walk(v,f'{path}[{i}]')
walk(m)
print('retired_key_locations=',found)

tax=m['verdict_taxonomy']
status=tax.get('verification_status')
print('verification_status=',json.dumps(status,sort_keys=True))
rule=tax['consistency_rule']
print('current_value_materialization=',rule.get('current_value_materialization'))
print('consistency_rule_scope=',rule.get('scope'))
print('non_table_backed_axes=',rule.get('non_table_backed_axes'))

axes=tax['axes']
sections={'natural':'primary_holdout','hard':'hard_challenge',
          'rename':'rename_capability','branch':'branch_capability',
          'merge':'merge_capability'}
table_backed={a for a,s in sections.items() if 'outcome_table' in m[s]}
print('scope_equals_table_backed=',set(rule['scope'])==table_backed)
print('non_table_equals_complement=',set(rule['non_table_backed_axes'])==set(axes)-table_backed)

declared={t for vals in axes.values() for t in vals}
mapping=tax['axis_disposition']['mapping']
print('tokens_unmapped=',sorted(declared-set(mapping)))
print('mapped_but_undeclared=',sorted(set(mapping)-declared))
for axis in table_backed:
    emitted={r['verdict'] for r in m[sections[axis]]['outcome_table']}
    print(axis+'_emitted_not_declared=',sorted(emitted-set(axes[axis])))
    print(axis+'_declared_unreachable=',sorted(set(axes[axis])-emitted))
vocab=set(m['truth_protocol']['label_vocabulary'])
for name in ('rename_capability','branch_capability'):
    print(name+'_unknown_truth_refs=',sorted(set(m[name].get('truth_labels_referenced',[]))-vocab))
PY

echo '== Revision-history hashes and frozen Step-1 provenance =='
python3 - <<'PY'
import hashlib,json,subprocess
p='research/rh-m0/rh-m0-manifest.json'
m=json.loads(subprocess.check_output(['git','show','b10d48b231de80fa4a5deeb636174a555188a74b:'+p]))
for e in m['manifest_revision']['history']:
    raw=subprocess.check_output(['git','show',f"{e['commit']}:{p}"])
    print('history_sha',e['revision'],hashlib.sha256(raw).hexdigest()==e['sha256'])
PY
git show -s --format='merge=%H%nparents=%P' "$merge"
git merge-base --is-ancestor "$reviewed" "$merge" && echo 'reviewed_head_is_ancestor=true'
for path in contracts/finding-lineage-decision-v1.json contracts/finding-lineage-v1.json; do
  printf '%s merge_blob=%s reviewed_blob=%s\n' "$path" \
    "$(git rev-parse "$merge:$path")" "$(git rev-parse "$reviewed:$path")"
done

Length of output: 28932


@PhysShell No blocking findings found in b10d48b231de80fa4a5deeb636174a555188a74b.

The previous cache finding is addressed. falsely_unreachable_if_applied_to_all_axes remains as a derivation-rule container, but it no longer stores a current count, current tokens, or derived claim. Its remaining revision-6 entry is explicitly historical. It cannot become stale as a current value.

The renamed verified_during_revision_construction flags and result_at_revision_construction objects are consistent with verdict_taxonomy.verification_status. The manifest does not claim that a committed validator enforces these construction-time observations.

Verified items:

  • The requested commit and parent match.
  • Only the manifest and checksum sidecar changed.
  • Both committed blobs use LF line endings.
  • The extracted sidecar passes sha256sum --check.
  • The manifest SHA-256 is 08f266e927d733ac177a6b5592da6cab4db51df8b82467e0bacc3b7f6b342758.
  • All 63 target hashes are byte-identical across revisions 1 through 10.
  • Target counts remain 50 natural, 4 rename, and 9 copy.
  • Sample accounting remains complete: 50 natural, 21 rename, and 25 copy pairs.
  • The class-dependent side rule reproduces all target sides.
  • No truth labels exist, and no B3 run occurred.
  • The declared frozen surfaces are unchanged from revision 9.
  • No retired cache or live-validation keys remain. The retained object name is expected and does not materialize a current value.
  • Scope matches the four table-backed axes. The merge axis is the only non-table-backed axis.
  • Outcome-table tokens, truth references, and axis dispositions remain internally consistent.
  • All revision-history SHA-256 values reproduce.
  • The reviewed Step-1 head is an ancestor of the declared merge, and both declared contract blobs match.

You are interacting with an AI system.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b10d48b231

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread research/rh-m0/rh-m0-manifest.json Outdated
"outcomes"
],
"labels_per_target": 2,
"resolved_branch_or_merge_carries": "exact endpoint sets, not only the outcome",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Record exact endpoints for continued labels

When a target has multiple plausible findings on the opposite side, the label continued 1:1 does not identify which finding is the true counterpart. This protocol requires exact endpoint sets only for branch and merge labels, yet the rename table scores whether B3 selected the “exact counterpart” and natural precision likewise needs to distinguish correct from false continuations. Require every resolved continuation label to carry its exact counterpart endpoint; otherwise these correctness metrics cannot be evaluated reproducibly after truth is frozen.

Useful? React with 👍 / 👎.

Comment thread research/rh-m0/rh-m0-manifest.json Outdated
"ai_agreement_is_not_truth": "two agreeing agents produce PROPOSED truth only; the final truth artifact requires human/owner adjudication and sign-off",
"evidence_cards_contain": [
"source at A and B",
"raw finding",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Redact rule IDs from evidence-card findings

When evidence cards are built from the normalized findings used by this experiment, a “raw finding” includes the rule field (aggregate/normalize.py:87), contradicting the later requirement that labelers must not see rule IDs. Unless the card explicitly strips that field, both labelers receive information the blinded protocol prohibits, so the resulting truth artifact cannot satisfy the preregistered blinding conditions.

Useful? React with 👍 / 👎.

Two substantive truth-protocol defects, both found before any truth label exists.

P1. A `continued 1:1` label recorded what happened but not with whom. Endpoint
sets were required only for branched 1:N and merged N:1, yet the rename axis
scores resolution to the exact counterpart, and natural precision must separate a
correct continuation from a continuation to a neighbouring similar finding. With
two plausible candidates on the far side, nobody could decide after the freeze
whether B3 was right.

endpoint_contract now covers every entry of the vocabulary with an explicit
cardinality:

  continued 1:1  exactly 1 opposite-side endpoint
  branched 1:N   >= 2 successor endpoints
  merged N:1     >= 2 predecessor endpoints
  ended          empty opposite-side set
  new            empty opposite-side set
  unresolved-by-truth, unadjudicable   not resolved relations

ended and new carry a structurally empty set on purpose. Natural row 1 fails on a
fabricated new/ended where truth has a counterpart, so the scorer applies one
endpoint contract instead of guessing per verdict what absence meant.

Endpoint identity is a frozen hash over the canonical identity of one normalized
finding, computed by the truth-artifact builder. Labelers designate endpoints by
the candidate index on the evidence card and never see the preimage. Prose such as
"the finding one line below" is prohibited: a natural-language foreign key cannot
be scored.

P1. The evidence card carried a raw finding while the protocol forbids labelers to
see rule ids. This is not hypothetical. The canonical normalized finding carries
rule, category, category_name, pattern_id and occurrence_id, confirmed against a
real normalized snapshot from the probe.

The fix is an allowlist, never a subtraction: path, line, start_line,
start_column, message. "Raw finding minus rule" would silently start carrying
whatever field the normalizer gains next. Forbidden fields are enumerated, and the
naming gap between "rule ids" in the prohibition and `rule` in the schema is
stated rather than assumed. tool is excluded because one producer is used for the
whole experiment, so it is constant and carries nothing for adjudication.

truth_protocol changes on purpose here, having been asserted unchanged in earlier
revisions: both defects are in the truth representation itself. label_vocabulary,
labels_per_target, labelers_must_not_see and ai_agreement_is_not_truth are
untouched.

Construction-time checks: allowlist and forbidden list disjoint, every vocabulary
entry has an endpoint contract, no entry outside the vocabulary, no resolved
outcome without a cardinality. Not repository-enforced; see
verdict_taxonomy.verification_status.

Asserted unchanged: frozen_targets, seeds, verdict_taxonomy.axes,
axis_disposition, consistency_rule.scope, all four outcome tables, and
truth_protocol.label_vocabulary.

Manifest SHA-256 858d80beeeb82619a0adc2fb29c505d1c03831cea2344541b576a7bb0139ee3e
supersedes 08f266e927d733ac177a6b5592da6cab4db51df8b82467e0bacc3b7f6b342758.

No B3 run. No truth labels. No correctness result.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015vtUmvBDf69ccY5ju3PSHV
@PhysShell

Copy link
Copy Markdown
Owner Author

Truth needs endpoints, and the evidence card needs an allowlist

New exact HEAD e418ec72076605e8c735c8971c7eff6228b34ae6, parent b10d48b231de80fa4a5deeb636174a555188a74b.

Both findings are valid, both are substantive, and both surfaced before any truth label exists — which is what preregistration is for.

P1 — continued 1:1 said what happened, not with whom. Endpoint sets were required only for branched 1:N and merged N:1, yet the rename axis scores resolution to the exact counterpart and natural precision must separate a correct continuation from one to a neighbouring similar finding. With two plausible candidates on the far side the question was undecidable after the freeze.

endpoint_contract now covers every vocabulary entry:

Truth label Endpoint set Cardinality
continued 1:1 opposite side exactly 1
branched 1:N successors ≥ 2
merged N:1 predecessors ≥ 2
ended opposite side empty
new opposite side empty
unresolved-by-truth, unadjudicable not resolved relations

ended and new carry a structurally empty set on purpose: natural row 1 fails on a fabricated new/ended where truth has a counterpart, so the scorer applies one contract instead of guessing per verdict what absence meant.

Endpoint identity is a frozen hash over the canonical identity of one normalized finding, computed by the truth-artifact builder. Labelers designate endpoints by the candidate index on the card and never see the preimage — which keeps rule and identity machinery out of their view while still producing a machine-comparable record. Prose such as "the finding one line below" is prohibited; a natural-language foreign key cannot be scored.

P1 — the evidence card leaked forbidden fields. Not hypothetical: evidence_cards_contain listed "raw finding" while labelers_must_not_see forbids rule ids, and the canonical normalized finding carries rule, category, category_name, pattern_id and occurrence_id. Confirmed against a real normalized snapshot from the probe.

Fixed with an allowlist, never a subtraction: path, line, start_line, start_column, message. "Raw finding minus rule" would silently start carrying whatever field the normalizer gains next. Forbidden fields are enumerated, and the naming gap between "rule ids" in the prohibition and rule in the schema is stated rather than assumed. tool is excluded because a single producer makes it constant and uninformative for adjudication.

Expected grep hit: "raw finding" still appears twice, in labeler_finding_projection.principle and .supersedes, where it describes the defect being removed. evidence_cards_contain no longer lists it.

truth_protocol changes on purpose here, having been asserted unchanged in earlier revisions — both defects are in the truth representation itself. label_vocabulary, labels_per_target, labelers_must_not_see and ai_agreement_is_not_truth are untouched.

Construction-time checks: allowlist and forbidden list disjoint, every vocabulary entry has an endpoint contract, none outside it, no resolved outcome without a cardinality. Not repository-enforced — see verdict_taxonomy.verification_status.

Verified from the committed blobs: zero carriage returns, sha256sum --check OK, and frozen_targets, seeds, the axis taxonomy, axis_disposition, all four outcome tables and label_vocabulary identical to revision 10.

Manifest SHA-256 858d80beeeb82619a0adc2fb29c505d1c03831cea2344541b576a7bb0139ee3e supersedes 08f266e9….

No B3 run. No truth labels. No correctness result.

@PhysShell

Copy link
Copy Markdown
Owner Author

@coderabbitai review

Final review requested on new exact HEAD e418ec72076605e8c735c8971c7eff6228b34ae6. Your clean verdict on b10d48b2 is superseded: Codex found two substantive truth-protocol defects there. Every truth label now carries an explicit endpoint cardinality, and the evidence card uses an allowlist instead of a raw finding, since the normalized payload carries rule, category and pattern_id.

@PhysShell

Copy link
Copy Markdown
Owner Author

@codex review

Final review requested on new exact HEAD e418ec72076605e8c735c8971c7eff6228b34ae6. Both P1 findings from b10d48b2 are fixed: endpoint_contract now assigns an exact cardinality to every vocabulary entry, including empty sets for ended and new so the scorer has one contract; and the evidence card is an explicit field allowlist rather than a raw finding minus one field. Endpoint identity is a frozen hash resolved by the builder from a card-local candidate index, so labelers never see rule.

@coderabbitai

coderabbitai Bot commented Aug 30, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

head='e418ec72076605e8c735c8971c7eff6228b34ae6'
parent='b10d48b231de80fa4a5deeb636174a555188a74b'
manifest='research/rh-m0/rh-m0-manifest.json'
sidecar='research/rh-m0/rh-m0-manifest.sha256'

echo '== Commit identity and scope =='
test "$(git rev-parse HEAD)" = "$head"
test "$(git rev-parse "$head^")" = "$parent"
git show -s --format='commit=%H%nparent=%P%nsubject=%s' "$head"
git diff-tree --no-commit-id --name-status -r "$head"
git diff --no-ext-diff --unified=16 "$parent" "$head" -- "$manifest" "$sidecar"

echo '== Committed-blob checksum and encoding =='
python3 - <<'PY'
import hashlib, subprocess, tempfile
from pathlib import Path

commit='e418ec72076605e8c735c8971c7eff6228b34ae6'
manifest='research/rh-m0/rh-m0-manifest.json'
sidecar='research/rh-m0/rh-m0-manifest.sha256'
m=subprocess.check_output(['git','show',f'{commit}:{manifest}'])
s=subprocess.check_output(['git','show',f'{commit}:{sidecar}'])
print('manifest_has_cr=',b'\r' in m)
print('sidecar_has_cr=',b'\r' in s)
print('manifest_sha256=',hashlib.sha256(m).hexdigest())
print('sidecar=',repr(s))
with tempfile.TemporaryDirectory() as d:
    root=Path(d)
    (root/'rh-m0-manifest.json').write_bytes(m)
    (root/'rh-m0-manifest.sha256').write_bytes(s)
    result=subprocess.run(['sha256sum','--check','rh-m0-manifest.sha256'],
                          cwd=d,capture_output=True,text=True)
    print('sha256sum_exit=',result.returncode)
    print('sha256sum_stdout=',result.stdout.strip())
    print('sha256sum_stderr=',result.stderr.strip())
PY

echo '== Changed top-level JSON surfaces =='
python3 - <<'PY'
import json, subprocess
p='research/rh-m0/rh-m0-manifest.json'
a=json.loads(subprocess.check_output(['git','show','b10d48b231de80fa4a5deeb636174a555188a74b:'+p]))
b=json.loads(subprocess.check_output(['git','show','e418ec72076605e8c735c8971c7eff6228b34ae6:'+p]))
print('changed_top_level_keys=',sorted(k for k in set(a)|set(b) if a.get(k)!=b.get(k)))
for k in ('frozen_targets','seeds','primary_holdout','hard_challenge','rename_capability','branch_capability'):
    print(k+'_unchanged=',a[k]==b[k])
for k in ('axes','axis_disposition'):
    print('verdict_taxonomy.'+k+'_unchanged=',a['verdict_taxonomy'][k]==b['verdict_taxonomy'][k])
PY

echo '== Targets, sample accounting, history, and side determination =='
python3 - <<'PY'
import hashlib,json,subprocess
from collections import Counter,defaultdict
p='research/rh-m0/rh-m0-manifest.json'
revs=['cde73560f029235959b7775a8466fca059c3f34e','9c0a8618f131e1bf0362d80538bb2af41f2d2c9a','6555e68944296c0f47dd5dbf52c59c76548f8422','03d56ace0623db64abe9db9d88c385717968ccca','5c89ba515da4e5e84b3fb7749ccdf9d99bd28ffa','ac54fcb3420550f0423366842fdd488f69bf1b3a','6e3304a498951089f8945f6cb3ec99978e65e887','ad58b29383d36b9fad0f0dc65a5faa3ace6636e8','11057180fe0c12011ea82c7f85cb57483c0225da','b10d48b231de80fa4a5deeb636174a555188a74b','e418ec72076605e8c735c8971c7eff6228b34ae6']
docs=[json.loads(subprocess.check_output(['git','show',f'{r}:{p}'])) for r in revs]
m=docs[-1]; targets=m['frozen_targets']['targets']
lists=[[t['target_hash'] for t in d['frozen_targets']['targets']] for d in docs]
print('revision=',m['manifest_revision']['revision'])
print('target_lists_identical_all_11=',all(x==lists[0] for x in lists[1:]))
print('counts=',m['frozen_targets']['count'],len(targets),dict(sorted(Counter(t['class'] for t in targets).items())))
pairs=defaultdict(set)
for t in targets:pairs[t['class']].add((t['a_sha'],t['b_sha']))
for c in ('natural','rename','copy'):
    no={(x['a_sha'],x['b_sha']) for x in m['sample']['no_target'][c]}
    print(c+'_selected_targetable_no_target_accounted=',m['sample']['selected_pairs'][c],len(pairs[c]),len(no),len(pairs[c]|no))
seed=m['seeds']['truth_seed']; bad=[]
for i,t in enumerate(targets):
    if t['class']=='natural':
        raw=json.dumps(['rh-m0-side/v1',seed,'sts',t['a_sha'],t['b_sha']],ensure_ascii=False,separators=(',',':')).encode()
        expected='A' if hashlib.sha256(raw).digest()[0]&1==0 else 'B'
    else: expected='A'
    if t['side']!=expected:bad.append((i,t['class'],t['side'],expected))
print('side_mismatches=',bad)
print('truth_labels_created=',m['manifest_revision']['truth_labels_created'])
print('b3_executed=',m['manifest_revision']['b3_executed'])
for h in m['manifest_revision']['history']:
    actual=hashlib.sha256(subprocess.check_output(['git','show',f"{h['commit']}:{p}"])).hexdigest()
    print('history_sha',h['revision'],actual==h['sha256'])
PY

echo '== Truth endpoint and evidence-card protocol =='
python3 - <<'PY'
import json
p='research/rh-m0/rh-m0-manifest.json'
with open(p,encoding='utf-8') as f:m=json.load(f)
t=m['truth_protocol']
print(json.dumps(t,indent=2,sort_keys=True))
vocab=set(t['label_vocabulary'])
contract=t.get('endpoint_contract',{})
print('contract_labels_missing=',sorted(vocab-set(contract)))
print('contract_labels_unknown=',sorted(set(contract)-vocab))
for label,entry in contract.items():
    print('endpoint_contract',repr(label),json.dumps(entry,sort_keys=True))
projection=t.get('labeler_finding_projection',{})
allow=set(projection.get('allowlist',projection.get('allowed_fields',[])))
forbidden=set(projection.get('forbidden_fields',[]))
print('projection_allowlist=',sorted(allow))
print('projection_forbidden=',sorted(forbidden))
print('allow_forbidden_overlap=',sorted(allow&forbidden))
print('labelers_must_not_see=',json.dumps(t.get('labelers_must_not_see'),sort_keys=True))
PY

echo '== Relevant normalized-finding schema references =='
rg -n -i -C 3 \
  'occurrence_id|pattern_id|category_name|"rule"|"start_column"|normalized finding|normalized_finding' \
  aggregate contracts research -g '*.py' -g '*.json' || true

Length of output: 172879


🏁 Script executed:

#!/bin/bash
set -euo pipefail

manifest='research/rh-m0/rh-m0-manifest.json'

echo '== Exact truth-protocol section with line numbers =='
rg -n -C 8 \
  '"truth_protocol"|"endpoint_contract"|"by_outcome"|"endpoint_identity"|"labeler_finding_projection"|"labels_per_target"|"truth_artifact_hashed_and_frozen_before"' \
  "$manifest"

echo '== Endpoint-contract and projection invariants =='
python3 - <<'PY'
import json
from collections import Counter

p='research/rh-m0/rh-m0-manifest.json'
with open(p, encoding='utf-8') as f:
    m=json.load(f)

truth=m['truth_protocol']
vocabulary=truth['label_vocabulary']
by_outcome=truth['endpoint_contract']['by_outcome']
projection=truth['labeler_finding_projection']
allowlist=projection['allowlist']
forbidden=projection['forbidden_finding_fields']

print('vocabulary_duplicates=', [x for x,n in Counter(vocabulary).items() if n > 1])
print('endpoint_contract_missing_vocabulary=', sorted(set(vocabulary)-set(by_outcome)))
print('endpoint_contract_unknown_outcomes=', sorted(set(by_outcome)-set(vocabulary)))
print('allowlist_duplicates=', [x for x,n in Counter(allowlist).items() if n > 1])
print('forbidden_duplicates=', [x for x,n in Counter(forbidden).items() if n > 1])
print('allowlist_forbidden_overlap=', sorted(set(allowlist)&set(forbidden)))

for outcome in vocabulary:
    entry=by_outcome[outcome]
    print('outcome=',repr(outcome),
          'resolved=',entry.get('resolved'),
          'endpoint_set=',repr(entry.get('endpoint_set')),
          'cardinality=',repr(entry.get('cardinality')))

resolved=[name for name,entry in by_outcome.items() if entry['resolved']]
unresolved=[name for name,entry in by_outcome.items() if not entry['resolved']]
print('resolved_without_endpoint_set=', [x for x in resolved if by_outcome[x]['endpoint_set'] is None])
print('resolved_without_cardinality=', [x for x in resolved if not by_outcome[x]['cardinality']])
print('unresolved_with_endpoint_set=', [x for x in unresolved if by_outcome[x]['endpoint_set'] is not None])

identity=truth['endpoint_contract']['endpoint_identity']
print('endpoint_digest=',identity.get('digest'))
print('endpoint_preimage=',identity.get('preimage'))
print('endpoint_serialisation=',identity.get('serialisation'))
print('candidate_index_designation=',identity.get('how_labelers_designate_endpoints'))
print('free_text_prohibition=',identity.get('free_text_prohibited'))
PY

echo '== Candidate-index and truth-artifact references =='
rg -n -i -C 4 \
  'candidate index|candidate_index|endpoint_id|endpoint set|truth artifact|truth_artifact|endpoint_contract' \
  . -g '*.json' -g '*.py' -g '*.md' || true

echo '== Actual normalized-finding field set versus evidence projection =='
python3 - <<'PY'
import ast, json
from pathlib import Path

manifest=json.loads(Path('research/rh-m0/rh-m0-manifest.json').read_text(encoding='utf-8'))
allow=set(manifest['truth_protocol']['labeler_finding_projection']['allowlist'])
forbidden=set(manifest['truth_protocol']['labeler_finding_projection']['forbidden_finding_fields'])

tree=ast.parse(Path('aggregate/normalize.py').read_text(encoding='utf-8'))
v1=None
for node in tree.body:
    if isinstance(node, ast.Assign):
        if any(isinstance(t,ast.Name) and t.id=='V1_FIELDS' for t in node.targets):
            v1=set(ast.literal_eval(node.value))
            break
if v1 is None:
    raise SystemExit('V1_FIELDS not found')
identity={'pattern_id','occurrence_id','physical_anchor','identity_limitations'}
normalized=v1|identity
print('normalized_fields=',sorted(normalized))
print('allowlist_not_normalized=',sorted(allow-normalized))
print('normalized_sensitive_not_forbidden=',sorted((normalized-allow)-forbidden))
print('allowlist_sensitive=',sorted(allow&forbidden))
PY

Length of output: 20837


@PhysShell I found one blocking truth-protocol defect.

  • research/rh-m0/rh-m0-manifest.json, Line 1436: labeler_finding_projection.allowlist names start_line and start_column. Those are not top-level fields of the canonical normalized finding. The normalized record contains line and physical_anchor.start_line / physical_anchor.start_column.

    The manifest does not define a mapping from physical_anchor to these evidence-card fields. The truth-artifact builder can therefore choose this mapping after the freeze. Define the projection by canonical source paths and output names, or use the normalized record’s actual field paths. Keep the coordinate projection explicit without admitting physical_anchor as an unrestricted object.

The other requested checks passed:

  • The requested commit and parent match.
  • Only the manifest and checksum sidecar changed.
  • The committed blobs use LF line endings.
  • The extracted blobs pass sha256sum --check.
  • The manifest SHA-256 is 858d80beeeb82619a0adc2fb29c505d1c03831cea2344541b576a7bb0139ee3e.
  • The 63 target hashes are identical across revisions 1 through 11.
  • Target counts remain 50 natural, 4 rename, and 9 copy.
  • Sample accounting and side determination remain valid.
  • No truth labels exist, and no B3 run occurred.
  • endpoint_contract.by_outcome covers every truth label with no unknown entries.
  • All resolved outcomes declare an endpoint set and cardinality.
  • The evidence-card allowlist and forbidden-field list are disjoint.

You are interacting with an AI system.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e418ec7207

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +1407 to +1410
"<rule>",
"<message>",
"<start_line>",
"<start_column>"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Refuse ambiguous endpoint hashes

When two normalized findings have identical path, rule, message, line, and column, this preimage gives them the same endpoint_id; normalize_results preserves such duplicate SARIF rows, and the existing occurrence-identity contract deliberately refuses identity for this case. A branch or merge containing both rows therefore collapses to one set member, so its required >= 2 cardinality and exact-set scoring cannot be represented. Add an ambiguity census and emit a limitation/refusal rather than minting the colliding endpoint ID.

AGENTS.md reference: AGENTS.md:L10-L10

Useful? React with 👍 / 👎.

Comment thread research/rh-m0/rh-m0-manifest.json Outdated
Comment on lines +1439 to +1441
"line",
"start_line",
"start_column",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Address the nested physical-anchor fields

When a candidate's column is needed to distinguish two findings on the same line, this allowlist cannot expose it: normalized-findings/v2 has a top-level line, but start_line and start_column are nested under physical_anchor, not top-level finding fields. Unless nested paths or explicit derived projection fields are defined, both names select nothing and same-line candidates can appear indistinguishable to labelers even though the builder hashes their columns into different endpoints.

AGENTS.md reference: AGENTS.md:L10-L10

Useful? React with 👍 / 👎.

…to real fields

P1. endpoint_id was minted from a canonical preimage that two identical rows of a
normalized snapshot can share. The normalizer is not obliged to deduplicate SARIF
rows, so a branch relation over two physically present findings could collapse to
a one-element set and silently break the >= 2 cardinality the contract promises.

Fixed by refusing an identity rather than inventing one. An endpoint_id is minted
only when its canonical preimage occurs exactly once in the snapshot of that
revision; otherwise the id is unavailable, with the limitation
endpoint-id-unavailable:ambiguous-canonical-identity. Ordinal and result-index
tiebreakers are PROHIBITED: disambiguating by position would reintroduce exactly
the artificial identity the occurrence contract already refused.

The truth consequence is deliberately narrow. A resolved relation that would need
an unavailable endpoint must not be materialized as continued, branched or merged,
and its truth is unadjudicable. An ambiguous preimage elsewhere does NOT by itself
spoil a target: if the truth is genuinely ended or new, the structurally empty
opposite-side set is still representable and the target is adjudicated normally.

Measured while constructing this revision: zero ambiguous preimages across 271
snapshots and 76253 findings of the frozen sample. The rule is preventive, not
corrective. It has to be specified before truth exists rather than discovered
afterwards, because the normalizer guarantees nothing here.

P2. The projection allowlist named start_line and start_column, which do not exist
at the top level of a normalized finding; they live inside physical_anchor, while
`line` does exist at the top level. The builder was therefore still free to decide
after the freeze what those two names referred to. Confirmed against a real
normalized snapshot, not against the schema text.

Replaced by explicit leaf-level mappings:

  path    <- path
  line    <- physical_anchor.start_line
  column  <- physical_anchor.start_column
  message <- message

One semantic coordinate per output name, and no output called start_line or
start_column, so no name maps to two possible sources. physical_anchor is never
admitted as a whole object: an allowlist that hands over a container has stopped
deciding anything.

Construction-time checks: every projection source exists in the schema, output
names are unique, no source is a forbidden field or lies beneath one, and the
endpoint preimage multiplicity was measured. Checks on the truth artifact itself
constrain a builder that does not exist yet; only their preconditions could be
measured. Not repository-enforced.

Asserted unchanged: frozen_targets, seeds, verdict_taxonomy.axes,
axis_disposition, all four outcome tables, truth_protocol.label_vocabulary and
endpoint_contract.by_outcome.

Manifest SHA-256 5a6d6746691ccac2b8e653ed29c4194d206f28bc98624252b644d3726019b3f3
supersedes 858d80beeeb82619a0adc2fb29c505d1c03831cea2344541b576a7bb0139ee3e.

No B3 run. No truth labels. No correctness result.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015vtUmvBDf69ccY5ju3PSHV
@PhysShell

Copy link
Copy Markdown
Owner Author

Refusing an ambiguous identity, and mapping the projection to fields that exist

New exact HEAD ff66d0fde283e692935538b181e30602ed2073da, parent e418ec72076605e8c735c8971c7eff6228b34ae6.

P1 — the identity was not injective where cardinality is promised. Valid, and a good catch immediately after making truth machine-comparable. Two rows sharing every preimage field collapse to one endpoint_id, so a branch over two physically present findings could present a one-element set and break the >= 2 the contract promises. The normalizer is not obliged to deduplicate SARIF rows.

Fixed by refusing an identity rather than inventing one:

  • an endpoint_id is minted only when its canonical preimage occurs exactly once in that revision's snapshot;
  • otherwise it is unavailable, with limitation endpoint-id-unavailable:ambiguous-canonical-identity;
  • ordinal and result-index tiebreakers are PROHIBITED — disambiguating by position would reintroduce precisely the artificial identity the occurrence contract already refused.

The truth consequence is deliberately narrow. A resolved relation needing an unavailable endpoint must not be materialized as continued/branched/merged, and its truth is unadjudicable. An ambiguous preimage elsewhere does not by itself spoil a target: if the truth is genuinely ended or new, the structurally empty opposite-side set remains representable and the target is adjudicated normally.

Measured while building this revision: zero ambiguous preimages across 271 snapshots and 76,253 findings of the frozen sample. The rule is therefore preventive, not corrective — which is exactly why it has to be specified before truth exists rather than discovered afterwards.

P2 — the projection named fields that do not exist at that level. Valid, raised independently by both reviewers. start_line and start_column live inside physical_anchor, while line does exist at the top level, so the builder could still decide after the freeze what those names meant. Confirmed against a real normalized snapshot, not against the schema text.

Output Source
path path
line physical_anchor.start_line
column physical_anchor.start_column
message message

One semantic coordinate per output, no output named start_line or start_column, and physical_anchor is never admitted as a whole object — an allowlist that hands over a container has stopped deciding anything.

Construction-time checks: every projection source exists in the schema, output names are unique, no source is a forbidden field or lies beneath one, and endpoint preimage multiplicity was measured. The two checks that constrain the truth artifact itself bind a builder that does not exist yet; only their preconditions could be measured here. Not repository-enforced.

Verified from the committed blobs: zero carriage returns, sha256sum --check OK, and frozen_targets, seeds, the axis taxonomy, all four outcome tables, label_vocabulary and endpoint_contract.by_outcome identical to revision 11.

Manifest SHA-256 5a6d6746691ccac2b8e653ed29c4194d206f28bc98624252b644d3726019b3f3 supersedes 858d80be….

No B3 run. No truth labels. No correctness result.

@PhysShell

Copy link
Copy Markdown
Owner Author

@coderabbitai review

Final review requested on new exact HEAD ff66d0fde283e692935538b181e30602ed2073da. The round on e418ec72 is stale. Your projection blocker is fixed with explicit leaf-level source-to-output mappings verified against a real normalized snapshot, and Codex found that endpoint identity was not injective, now closed fail-closed with ordinal tiebreakers prohibited.

@PhysShell

Copy link
Copy Markdown
Owner Author

@codex review

Final review requested on new exact HEAD ff66d0fde283e692935538b181e30602ed2073da. Both findings from e418ec72 are fixed. endpoint_id is minted only when the canonical preimage is unique, otherwise unavailable with an explicit limitation, and ordinal disambiguation is prohibited; the truth refusal is scoped to relations that actually need the ambiguous endpoint, so genuine ended/new stay adjudicable. Measured zero ambiguous preimages across 271 snapshots and 76253 findings, so the rule is preventive.

@coderabbitai

coderabbitai Bot commented Aug 30, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

head='ff66d0fde283e692935538b181e30602ed2073da'
parent='e418ec72076605e8c735c8971c7eff6228b34ae6'
manifest='research/rh-m0/rh-m0-manifest.json'
sidecar='research/rh-m0/rh-m0-manifest.sha256'

echo '== Commit identity and changed paths =='
test "$(git rev-parse HEAD)" = "$head"
test "$(git rev-parse "$head^")" = "$parent"
git show -s --format='commit=%H%nparent=%P%nsubject=%s' "$head"
git diff-tree --no-commit-id --name-status -r "$head"
git diff --no-ext-diff --unified=20 "$parent" "$head" -- "$manifest" "$sidecar"

echo '== Committed-blob encoding and checksum =='
python3 - <<'PY'
import hashlib, subprocess, tempfile
from pathlib import Path
commit='ff66d0fde283e692935538b181e30602ed2073da'
manifest='research/rh-m0/rh-m0-manifest.json'
sidecar='research/rh-m0/rh-m0-manifest.sha256'
m=subprocess.check_output(['git','show',f'{commit}:{manifest}'])
s=subprocess.check_output(['git','show',f'{commit}:{sidecar}'])
print('manifest_has_cr=',b'\r' in m)
print('sidecar_has_cr=',b'\r' in s)
print('manifest_sha256=',hashlib.sha256(m).hexdigest())
print('sidecar=',repr(s))
with tempfile.TemporaryDirectory() as d:
    root=Path(d)
    (root/'rh-m0-manifest.json').write_bytes(m)
    (root/'rh-m0-manifest.sha256').write_bytes(s)
    r=subprocess.run(['sha256sum','--check','rh-m0-manifest.sha256'],
                     cwd=d,text=True,capture_output=True)
    print('sha256sum_exit=',r.returncode)
    print('sha256sum_stdout=',r.stdout.strip())
    print('sha256sum_stderr=',r.stderr.strip())
PY

echo '== Updated truth protocol =='
python3 - <<'PY'
import json
p='research/rh-m0/rh-m0-manifest.json'
with open(p,encoding='utf-8') as f:m=json.load(f)
print(json.dumps(m['truth_protocol'],indent=2,sort_keys=True))
PY

echo '== Protocol and projection invariants =='
python3 - <<'PY'
import ast,json
from pathlib import Path
m=json.loads(Path('research/rh-m0/rh-m0-manifest.json').read_text(encoding='utf-8'))
truth=m['truth_protocol']
identity=truth['endpoint_contract']['endpoint_identity']
projection=truth['labeler_finding_projection']
mappings=projection.get('mappings',projection.get('field_mappings',[]))
print('projection_mappings=',json.dumps(mappings,sort_keys=True))
outputs=[x.get('output') for x in mappings]
sources=[x.get('source') for x in mappings]
print('projection_outputs_unique=',len(outputs)==len(set(outputs)))
print('projection_sources=',sources)
print('whole_physical_anchor_admitted=',any(x=='physical_anchor' for x in sources))
forbidden=set(projection['forbidden_finding_fields'])
print('projection_source_forbidden_or_below=',[
 x for x in sources if x in forbidden or any(x.startswith(f+'.') for f in forbidden)
])
print('endpoint_identity=',json.dumps(identity,indent=2,sort_keys=True))
print('ordinal_or_result_index_allowed=',[
 (k,v) for k,v in identity.items()
 if 'ordinal' in k.lower() or 'result' in k.lower()
])
print('endpoint_unavailable_rule=',json.dumps(
 truth['endpoint_contract'].get('unavailable_endpoint_handling',{}),sort_keys=True))

tree=ast.parse(Path('aggregate/normalize.py').read_text(encoding='utf-8'))
v1=None
for node in tree.body:
    if isinstance(node,ast.Assign) and any(isinstance(t,ast.Name) and t.id=='V1_FIELDS' for t in node.targets):
        v1=set(ast.literal_eval(node.value))
        break
if v1 is None: raise SystemExit('V1_FIELDS not found')
schema=set(v1)|{'physical_anchor.start_line','physical_anchor.start_column',
                'pattern_id','occurrence_id','identity_limitations'}
print('projection_sources_not_in_schema=',sorted(set(sources)-schema))
PY

echo '== Frozen targets, samples, and revision preservation =='
python3 - <<'PY'
import hashlib,json,subprocess
from collections import Counter,defaultdict
p='research/rh-m0/rh-m0-manifest.json'
revs=['cde73560f029235959b7775a8466fca059c3f34e','9c0a8618f131e1bf0362d80538bb2af41f2d2c9a','6555e68944296c0f47dd5dbf52c59c76548f8422','03d56ace0623db64abe9db9d88c385717968ccca','5c89ba515da4e5e84b3fb7749ccdf9d99bd28ffa','ac54fcb3420550f0423366842fdd488f69bf1b3a','6e3304a498951089f8945f6cb3ec99978e65e887','ad58b29383d36b9fad0f0dc65a5faa3ace6636e8','11057180fe0c12011ea82c7f85cb57483c0225da','b10d48b231de80fa4a5deeb636174a555188a74b','e418ec72076605e8c735c8971c7eff6228b34ae6','ff66d0fde283e692935538b181e30602ed2073da']
docs=[json.loads(subprocess.check_output(['git','show',f'{r}:{p}'])) for r in revs]
before,m=docs[-2],docs[-1]
targets=m['frozen_targets']['targets']
lists=[[x['target_hash'] for x in d['frozen_targets']['targets']] for d in docs]
print('revision=',m['manifest_revision']['revision'])
print('target_lists_identical_all_12=',all(x==lists[0] for x in lists[1:]))
print('target_counts=',m['frozen_targets']['count'],len(targets),dict(sorted(Counter(x['class'] for x in targets).items())))
pairs=defaultdict(set)
for t in targets:pairs[t['class']].add((t['a_sha'],t['b_sha']))
for cls in ('natural','rename','copy'):
 no={(x['a_sha'],x['b_sha']) for x in m['sample']['no_target'][cls]}
 print(cls+'_selected_targetable_no_target_accounted=',m['sample']['selected_pairs'][cls],len(pairs[cls]),len(no),len(pairs[cls]|no))
seed=m['seeds']['truth_seed']; bad=[]
for i,t in enumerate(targets):
 if t['class']=='natural':
  raw=json.dumps(['rh-m0-side/v1',seed,'sts',t['a_sha'],t['b_sha']],ensure_ascii=False,separators=(',',':')).encode()
  expected='A' if hashlib.sha256(raw).digest()[0]&1==0 else 'B'
 else: expected='A'
 if t['side']!=expected:bad.append((i,t['class'],t['side'],expected))
print('side_errors=',bad)
for key in ('seeds','frozen_targets','primary_holdout','hard_challenge','rename_capability','branch_capability'):
 print(key+'_unchanged_from_rev11=',before[key]==m[key])
print('truth_labels_created=',m['manifest_revision']['truth_labels_created'])
print('b3_executed=',m['manifest_revision']['b3_executed'])
for h in m['manifest_revision']['history']:
 raw=subprocess.check_output(['git','show',f"{h['commit']}:{p}"])
 print('history_sha',h['revision'],hashlib.sha256(raw).hexdigest()==h['sha256'])
PY

Length of output: 39985


🏁 Script executed:

#!/bin/bash
set -euo pipefail

manifest='research/rh-m0/rh-m0-manifest.json'
merge='011c1362861f6b8b20c45e8ebd5bcb912401c1c0'
reviewed='6dcc02f7d82bebeb5db9be83f77ab8c5455f5692'

echo '== Candidate-index and truth-artifact references =='
rg -n -i -C 5 \
  'candidate index|candidate_index|endpoint_id|endpoint set|endpoint_identity|truth artifact|truth_artifact|labeler_finding_projection' \
  "$manifest" aggregate contracts research -g '*.json' -g '*.py' -g '*.md' || true

echo '== Actual projection mapping validation =='
python3 - <<'PY'
import ast,json
from pathlib import Path
m=json.loads(Path('research/rh-m0/rh-m0-manifest.json').read_text(encoding='utf-8'))
projection=m['truth_protocol']['labeler_finding_projection']
fields=projection['fields']
outputs=[x['output'] for x in fields]
sources=[x['source'] for x in fields]
forbidden=set(projection['forbidden_finding_fields'])
print('field_count=',len(fields))
print('outputs=',outputs)
print('outputs_unique=',len(outputs)==len(set(outputs)))
print('sources=',sources)
print('whole_physical_anchor_admitted=',any(x=='physical_anchor' for x in sources))
print('sources_forbidden_or_below=',[
 s for s in sources if s in forbidden or any(s.startswith(f+'.') for f in forbidden)
])

tree=ast.parse(Path('aggregate/normalize.py').read_text(encoding='utf-8'))
v1=None
for node in tree.body:
    if isinstance(node,ast.Assign) and any(isinstance(t,ast.Name) and t.id=='V1_FIELDS' for t in node.targets):
        v1=set(ast.literal_eval(node.value))
        break
if v1 is None: raise SystemExit('V1_FIELDS not found')
valid_paths=v1|{'physical_anchor.start_line','physical_anchor.start_column',
                'pattern_id','occurrence_id','identity_limitations'}
print('sources_not_in_normalized_schema=',sorted(set(sources)-valid_paths))
PY

echo '== Candidate-index contract shape =='
python3 - <<'PY'
import json
from pathlib import Path
m=json.loads(Path('research/rh-m0/rh-m0-manifest.json').read_text(encoding='utf-8'))
truth=m['truth_protocol']
identity=truth['endpoint_contract']['endpoint_identity']
text=json.dumps(truth,sort_keys=True).lower()
for term in ('candidate index','candidate_index','namespace','side','revision','resolver','endpoint_id'):
    print(term.replace(' ','_')+'_occurrences=',text.count(term))
print('designation=',identity['how_labelers_designate_endpoints'])
print('all_endpoint_identity_keys=',sorted(identity))
print('evidence_card_fields=',[x['output'] for x in truth['labeler_finding_projection']['fields']])
PY

echo '== Frozen Step-1 provenance =='
git cat-file -e "${merge}^{commit}"
git cat-file -e "${reviewed}^{commit}"
git show -s --format='merge=%H%nparents=%P%nsubject=%s' "$merge"
git merge-base --is-ancestor "$reviewed" "$merge" && echo 'reviewed_head_is_ancestor=true'
for path in contracts/finding-lineage-decision-v1.json contracts/finding-lineage-v1.json; do
  printf '%s merge_blob=%s reviewed_blob=%s\n' "$path" \
    "$(git rev-parse "$merge:$path")" "$(git rev-parse "$reviewed:$path")"
done

Length of output: 33497


@PhysShell I found one blocking truth-protocol defect in ff66d0fde283e692935538b181e30602ed2073da.

  • research/rh-m0/rh-m0-manifest.json, Line 1427: The protocol says that a labeler designates an endpoint “by the candidate index printed on the evidence card,” but it does not define that index as an unambiguous foreign key. It does not specify its namespace, uniqueness rule, side, revision, or the frozen truth-artifact representation.

    A candidate index can repeat between the A and B finding lists, or between cards. For example, both sides can contain candidate 1. The builder then has an unspecified choice when it resolves the labeler’s selected index to an endpoint_id. This reintroduces a post-freeze identity decision despite the new fail-closed endpoint rule.

    Define a candidate reference that binds at least the card or target, revision, side, and candidate index. Require uniqueness within that namespace. Specify that the truth artifact stores this reference and its resolved available endpoint_id. The reference must not use a SARIF ordinal or result index as an identity tiebreaker.

The remaining checks passed:

  • The requested commit and parent match.
  • The commit changes only the manifest and checksum sidecar.
  • The committed blobs contain no carriage-return bytes.
  • The extracted blobs pass sha256sum --check.
  • The manifest SHA-256 is 5a6d6746691ccac2b8e653ed29c4194d206f28bc98624252b644d3726019b3f3.
  • The evidence-card projection uses valid leaf-level normalized-finding paths.
  • The projection output names are unique.
  • The projection does not expose physical_anchor as a whole object or any forbidden field.
  • All 63 target hashes are identical across revisions 1 through 12.
  • The target counts remain 50 natural, 4 rename, and 9 copy.
  • Sample accounting and the class-dependent side rule remain valid.
  • No truth labels exist and no B3 run occurred.
  • The reviewed Step-1 head is an ancestor of the declared merge. Both declared contract blobs match.

You are interacting with an AI system.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Keep it up!

Reviewed commit: ff66d0fde2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Revision 12 closed guessing at the level of endpoint identity and left it one
level above, in the reference a human uses to point at that identity. A bare
"candidate 3" has no namespace: candidate 3 can exist on side A and on side B, and
on two different cards, so after the labels were written the builder would still
be choosing which one was meant.

candidate_ref is now the frozen reference:

  target_hash     the frozen target whose card this is
  revision_role   A or B
  revision_sha    the exact commit of that side
  candidate_index non-negative integer assigned by the card

The full four-part key must be unique across the evidence corpus, and role and sha
cross-check: role A requires the target's a_sha, role B its b_sha. A:1 and B:1
become trivially distinguishable, and a mistyped sha is caught rather than
guessed.

candidate_index is presentation-local. It must not participate in endpoint_id, in
the canonical preimage, in ambiguity resolution, or in any tiebreaker. Otherwise
the SARIF ordinal, prohibited one revision ago, walks back in through the front
door under a new name. The index addresses a row on a card; it never identifies a
finding.

candidate_endpoint_mapping resolves candidate_ref to endpoint_id or unavailable.
It is built during evidence-card construction, before any labeler response is
interpreted and before any label exists, and is persisted and hashed with the
truth artifact. Truth stores both candidate_ref and resolved_endpoint_id: the
scorer needs a machine-comparable identity, and a later reader needs to see what
the human was actually looking at.

A candidate_ref resolving to unavailable must not be stored as an endpoint of a
resolved relation, reusing the revision-12 fail-closed semantics rather than
introducing a second set. The labeler never sees endpoint_id, so the hash cannot
become a side channel back to rule.

Deliberately not done: no canonical candidate ordering is frozen. The finding was
a missing foreign key, not a missing ordering, and the meaning of an index comes
from the frozen mapping rather than from a sort algorithm. Freezing an ordering
too would grow a second identity scheme out of one integer.

Endpoint identity is untouched: mint iff the canonical preimage is unique,
otherwise unavailable, ordinal tiebreakers prohibited, narrow unavailable
semantics.

Construction-time checks: all 63 targets carry both a_sha and b_sha so the
role-to-sha rule is checkable, all 63 target_hash values are unique so
candidate_ref keys cannot collide across targets, candidate_index appears nowhere
in the endpoint preimage, and the role vocabulary matches the sides present in the
frozen targets.

Asserted unchanged: the endpoint preimage, minting rule, ordinal prohibition,
endpoint_contract.by_outcome, unavailable_endpoint_semantics, label_vocabulary,
labeler_finding_projection.fields, frozen_targets, seeds, verdict_taxonomy.axes
and all four outcome tables.

Manifest SHA-256 3a99631292122919b1dc5d9750d899da0ecde23d9ba452c731da6c29209730b4
supersedes 5a6d6746691ccac2b8e653ed29c4194d206f28bc98624252b644d3726019b3f3.

No B3 run. No truth labels. No correctness result.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015vtUmvBDf69ccY5ju3PSHV
@PhysShell

Copy link
Copy Markdown
Owner Author

The labeler's reference now has a namespace

New exact HEAD 493000deb1149135437442e58add9a1107f2323d, parent ff66d0fde283e692935538b181e30602ed2073da.

Valid. Revision 12 closed guessing at endpoint identity and left it one level above, in the reference a human uses to point at that identity. A bare "candidate 3" has no namespace: it can exist on side A and on side B, and on two different cards, so after the labels were written the builder would still be choosing which one was meant.

candidate_ref is now the frozen reference:

Field Meaning
target_hash the frozen target whose card this is
revision_role A or B
revision_sha the exact commit of that side
candidate_index non-negative integer assigned by the card

The full four-part key must be unique across the evidence corpus, and role and sha cross-check: role A requires the target's a_sha, role B its b_sha. A:1 and B:1 become trivially distinguishable, and a mistyped sha is caught rather than guessed.

candidate_index is presentation-local and barred from endpoint_id, the canonical preimage, ambiguity resolution and any tiebreaker. Otherwise the SARIF ordinal, prohibited one revision ago, walks back in through the front door under a new name. The index addresses a row on a card; it never identifies a finding.

candidate_endpoint_mapping resolves candidate_ref to endpoint_id or unavailable. It is built during card construction — before any labeler response is interpreted and before any label exists — and is persisted and hashed with the truth artifact. Truth stores both candidate_ref and resolved_endpoint_id: the scorer needs a machine-comparable identity, a later reader needs to see what the human was actually looking at, and storing only one loses a different half of the audit trail.

A candidate_ref resolving to unavailable cannot be an endpoint of a resolved relation — reusing the revision-12 semantics rather than introducing a second set. The labeler never sees endpoint_id, so the hash cannot become a side channel back to rule.

Deliberately not done: no canonical candidate ordering is frozen. The finding was a missing foreign key, not a missing ordering, and the meaning of an index comes from the frozen mapping rather than from a sort algorithm. Freezing an ordering as well would grow a second identity scheme out of one integer.

Endpoint identity is untouched and asserted so: preimage, minting rule, ordinal prohibition, by_outcome and unavailable_endpoint_semantics are byte-identical to revision 12, as are the projection fields, label_vocabulary, frozen_targets, seeds, the axis taxonomy and all four outcome tables.

Construction-time checks: all 63 targets carry both a_sha and b_sha so the role-to-sha rule is checkable, all 63 target_hash values are unique so candidate_ref keys cannot collide across targets, candidate_index appears nowhere in the endpoint preimage, and the role vocabulary matches the sides present in the targets.

Verified from the committed blobs: zero carriage returns, sha256sum --check OK.

Manifest SHA-256 3a99631292122919b1dc5d9750d899da0ecde23d9ba452c731da6c29209730b4 supersedes 5a6d6746….

No B3 run. No truth labels. No correctness result.

@PhysShell

Copy link
Copy Markdown
Owner Author

@coderabbitai review

Final review requested on new exact HEAD 493000deb1149135437442e58add9a1107f2323d. The round on ff66d0fd is stale. Your blocker is fixed: candidate_ref is now (target_hash, revision_role, revision_sha, candidate_index) with a uniqueness requirement and role-to-sha cross-checks, the index is barred from any identity role, and candidate_ref -> endpoint_id is built before labelling and persisted with the truth artifact. No canonical ordering was frozen, since the finding was a missing foreign key rather than a missing ordering.

@PhysShell

Copy link
Copy Markdown
Owner Author

@codex review

Final review requested on new exact HEAD 493000deb1149135437442e58add9a1107f2323d. Your review of ff66d0fd was clean; this revision addresses a CodeRabbit blocker one level above endpoint identity. Endpoint identity itself is untouched and asserted byte-identical: preimage, minting rule, ordinal prohibition, by_outcome and unavailable semantics all unchanged.

@coderabbitai

coderabbitai Bot commented Aug 30, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

head='493000deb1149135437442e58add9a1107f2323d'
parent='ff66d0fde283e692935538b181e30602ed2073da'
manifest='research/rh-m0/rh-m0-manifest.json'
sidecar='research/rh-m0/rh-m0-manifest.sha256'
merge='011c1362861f6b8b20c45e8ebd5bcb912401c1c0'
reviewed='6dcc02f7d82bebeb5db9be83f77ab8c5455f5692'

echo '== Requested commit identity and changed paths =='
test "$(git rev-parse HEAD)" = "$head"
test "$(git rev-parse "$head^")" = "$parent"
git show -s --format='commit=%H%nparent=%P%nsubject=%s' "$head"
git diff-tree --no-commit-id --name-status -r "$head"
git diff --no-ext-diff --unified=18 "$parent" "$head" -- "$manifest" "$sidecar"

echo '== Committed-blob checksum and encoding =='
python3 - <<'PY'
import hashlib, subprocess, tempfile
from pathlib import Path
commit='493000deb1149135437442e58add9a1107f2323d'
manifest='research/rh-m0/rh-m0-manifest.json'
sidecar='research/rh-m0/rh-m0-manifest.sha256'
m=subprocess.check_output(['git','show',f'{commit}:{manifest}'])
s=subprocess.check_output(['git','show',f'{commit}:{sidecar}'])
print('manifest_has_cr=',b'\r' in m)
print('sidecar_has_cr=',b'\r' in s)
print('manifest_sha256=',hashlib.sha256(m).hexdigest())
print('sidecar_bytes=',repr(s))
with tempfile.TemporaryDirectory() as d:
    root=Path(d)
    (root/'rh-m0-manifest.json').write_bytes(m)
    (root/'rh-m0-manifest.sha256').write_bytes(s)
    r=subprocess.run(['sha256sum','--check','rh-m0-manifest.sha256'],
                     cwd=d,text=True,capture_output=True)
    print('sha256sum_exit=',r.returncode)
    print('sha256sum_stdout=',r.stdout.strip())
    print('sha256sum_stderr=',r.stderr.strip())
PY

echo '== Candidate-reference and endpoint contract =='
python3 - <<'PY'
import json
from pathlib import Path

m=json.loads(Path('research/rh-m0/rh-m0-manifest.json').read_text(encoding='utf-8'))
truth=m['truth_protocol']
endpoint=truth['endpoint_contract']
identity=endpoint['endpoint_identity']

for key in ('candidate_reference','candidate_ref','candidate_endpoint_mapping',
            'truth_artifact','endpoint_identity'):
    if key in endpoint:
        print(f'endpoint_contract.{key}=',json.dumps(endpoint[key],indent=2,sort_keys=True))
    if key in truth:
        print(f'truth_protocol.{key}=',json.dumps(truth[key],indent=2,sort_keys=True))

print('endpoint_identity_keys=',sorted(identity))
print('endpoint_identity=',json.dumps(identity,indent=2,sort_keys=True))
print('truth_protocol_keys=',sorted(truth))

# Locate all structured candidate-reference objects without relying on prose.
def walk(value,path='$'):
    if isinstance(value,dict):
        keys=set(value)
        if {'target_hash','revision_role','revision_sha','candidate_index'} <= keys:
            print('candidate_ref_shape_at=',path,'keys=',sorted(keys))
            print('candidate_ref_object=',json.dumps(value,sort_keys=True))
        for k,v in value.items():
            walk(v,path+'.'+k)
    elif isinstance(value,list):
        for i,v in enumerate(value):
            walk(v,f'{path}[{i}]')
walk(truth)
PY

echo '== Candidate-reference semantic checks against frozen targets =='
python3 - <<'PY'
import json
from pathlib import Path
m=json.loads(Path('research/rh-m0/rh-m0-manifest.json').read_text(encoding='utf-8'))
truth=m['truth_protocol']
targets=m['frozen_targets']['targets']
target_by_hash={t['target_hash']:t for t in targets}

# Print every value structurally associated with candidate refs/mappings.
def collect(v,path='$',out=None):
    out=[] if out is None else out
    if isinstance(v,dict):
        for k,x in v.items():
            p=path+'.'+k
            if any(word in k.lower() for word in ('candidate','mapping','artifact','reference')):
                out.append((p,x))
            collect(x,p,out)
    elif isinstance(v,list):
        for i,x in enumerate(v): collect(x,f'{path}[{i}]',out)
    return out
for path,value in collect(truth):
    print(path,'=',json.dumps(value,sort_keys=True))

print('unique_target_hashes=',len(target_by_hash)==len(targets))
print('target_missing_a_or_b=',[
    t['target_hash'] for t in targets if not t.get('a_sha') or not t.get('b_sha')
])
print('target_sides=',sorted({t['side'] for t in targets}))
PY

echo '== Frozen targets, sample accounting, side rule, and history =='
python3 - <<'PY'
import hashlib,json,subprocess
from collections import Counter,defaultdict
p='research/rh-m0/rh-m0-manifest.json'
revs=[
 'cde73560f029235959b7775a8466fca059c3f34e',
 '9c0a8618f131e1bf0362d80538bb2af41f2d2c9a',
 '6555e68944296c0f47dd5dbf52c59c76548f8422',
 '03d56ace0623db64abe9db9d88c385717968ccca',
 '5c89ba515da4e5e84b3fb7749ccdf9d99bd28ffa',
 'ac54fcb3420550f0423366842fdd488f69bf1b3a',
 '6e3304a498951089f8945f6cb3ec99978e65e887',
 'ad58b29383d36b9fad0f0dc65a5faa3ace6636e8',
 '11057180fe0c12011ea82c7f85cb57483c0225da',
 'b10d48b231de80fa4a5deeb636174a555188a74b',
 'e418ec72076605e8c735c8971c7eff6228b34ae6',
 'ff66d0fde283e692935538b181e30602ed2073da',
 '493000deb1149135437442e58add9a1107f2323d',
]
docs=[json.loads(subprocess.check_output(['git','show',f'{r}:{p}'])) for r in revs]
before,m=docs[-2],docs[-1]
targets=m['frozen_targets']['targets']
lists=[[t['target_hash'] for t in d['frozen_targets']['targets']] for d in docs]
print('revision=',m['manifest_revision']['revision'])
print('target_lists_identical_all_13=',all(x==lists[0] for x in lists[1:]))
print('target_counts=',m['frozen_targets']['count'],len(targets),dict(sorted(Counter(t['class'] for t in targets).items())))
print('unique_valid_target_hashes=',len(set(lists[-1]))==len(lists[-1]) and all(len(h)==64 and set(h)<=set('0123456789abcdef') for h in lists[-1]))
pairs=defaultdict(set)
for t in targets:pairs[t['class']].add((t['a_sha'],t['b_sha']))
for cls in ('natural','rename','copy'):
    no={(x['a_sha'],x['b_sha']) for x in m['sample']['no_target'][cls]}
    print(cls+'_selected_targetable_no_target_accounted=',m['sample']['selected_pairs'][cls],len(pairs[cls]),len(no),len(pairs[cls]|no))
seed=m['seeds']['truth_seed']; bad=[]
for i,t in enumerate(targets):
    if t['class']=='natural':
        raw=json.dumps(['rh-m0-side/v1',seed,'sts',t['a_sha'],t['b_sha']],ensure_ascii=False,separators=(',',':')).encode()
        expected='A' if hashlib.sha256(raw).digest()[0]&1==0 else 'B'
    else:
        expected='A'
    if t['side']!=expected: bad.append((i,t['class'],t['side'],expected))
print('side_rule_errors=',bad)
print('truth_labels_created=',m['manifest_revision']['truth_labels_created'])
print('b3_executed=',m['manifest_revision']['b3_executed'])
for key in ('seeds','frozen_targets','primary_holdout','hard_challenge','rename_capability','branch_capability'):
    print(key+'_unchanged_from_rev12=',before[key]==m[key])
for key in ('axes','axis_disposition'):
    print('taxonomy_'+key+'_unchanged_from_rev12=',before['verdict_taxonomy'][key]==m['verdict_taxonomy'][key])
for e in m['manifest_revision']['history']:
    raw=subprocess.check_output(['git','show',f"{e['commit']}:{p}"])
    print('history_sha_'+str(e['revision'])+'=',hashlib.sha256(raw).hexdigest()==e['sha256'])
PY

echo '== Truth vocabulary, outcome tables, and Step-1 provenance =='
python3 - <<'PY'
import json
from pathlib import Path
m=json.loads(Path('research/rh-m0/rh-m0-manifest.json').read_text(encoding='utf-8'))
truth=m['truth_protocol']; tax=m['verdict_taxonomy']
vocab=set(truth['label_vocabulary'])
contract=truth['endpoint_contract']['by_outcome']
print('endpoint_contract_missing_vocab=',sorted(vocab-set(contract)))
print('endpoint_contract_unknown_outcomes=',sorted(set(contract)-vocab))
for name in ('rename_capability','branch_capability'):
    print(name+'_unknown_truth_refs=',sorted(set(m[name].get('truth_labels_referenced',[]))-vocab))
for axis,section in (('natural','primary_holdout'),('hard','hard_challenge'),('rename','rename_capability'),('branch','branch_capability')):
    emitted={row['verdict'] for row in m[section]['outcome_table']}
    declared=set(tax['axes'][axis])
    print(axis+'_emitted_not_declared=',sorted(emitted-declared))
    print(axis+'_declared_unreachable=',sorted(declared-emitted))
PY
git show -s --format='merge=%H%nparents=%P' "$merge"
git merge-base --is-ancestor "$reviewed" "$merge" && echo 'reviewed_head_is_ancestor=true'
for path in contracts/finding-lineage-decision-v1.json contracts/finding-lineage-v1.json; do
  printf '%s merge_blob=%s reviewed_blob=%s\n' "$path" \
    "$(git rev-parse "$merge:$path")" "$(git rev-parse "$reviewed:$path")"
done

Length of output: 41388


@PhysShell No blocking findings found in 493000deb1149135437442e58add9a1107f2323d.

The previous candidate-reference blocker is addressed.

  • candidate_ref contains target_hash, revision_role, revision_sha, and candidate_index.
  • The manifest requires the full candidate_ref key to be unique across the evidence corpus.
  • The role-to-SHA rules bind role A to a_sha and role B to b_sha.
  • candidate_index is presentation-local. It cannot participate in endpoint_id, the endpoint preimage, ambiguity resolution, or an identity tiebreaker.
  • candidate_endpoint_mapping is built before label interpretation, persists with the truth artifact, and is hashed with it.
  • The truth record retains both candidate_ref and resolved_endpoint_id.
  • A reference that resolves to unavailable cannot become an endpoint in a resolved truth relation.

I also verified these manifest invariants:

  • The requested commit and parent match.
  • Only the manifest and checksum sidecar changed.
  • Both committed blobs use LF line endings.
  • The extracted sidecar passes sha256sum --check.
  • The manifest SHA-256 is 3a99631292122919b1dc5d9750d899da0ecde23d9ba452c731da6c29209730b4.
  • All 63 frozen target hashes are identical across revisions 1 through 13.
  • Target counts remain 50 natural, 4 rename, and 9 copy.
  • Sample accounting remains complete: 50 natural, 21 rename, and 25 copy pairs.
  • The class-dependent side rule reproduces every target side.
  • No truth labels exist, and no B3 run occurred.
  • The frozen targets, seeds, axis taxonomy, dispositions, and four outcome tables are unchanged from revision 12.
  • Every endpoint-contract label and outcome-table token remains valid.
  • The reviewed Step-1 head is an ancestor of the declared merge. Both declared contract blobs match.

You are interacting with an AI system.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Chef's kiss.

Reviewed commit: 493000deb1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@PhysShell
PhysShell merged commit 9c2f8fe into main Aug 30, 2026
8 checks passed
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