Document Opus 4.8 handoff checkpoint

ober

18ccc488d68a03a7b29440e5a8534eb70b1dba00

diff --git a/HANDOFF_OPUS_4_8.md b/HANDOFF_OPUS_4_8.md
index 3f4ceb1..71f54e8 100644
--- a/HANDOFF_OPUS_4_8.md
+++ b/HANDOFF_OPUS_4_8.md
@@ -1,11 +1,13 @@
 # Opus 4.8 Handoff: jerboa-semgrep Semgrep Parity
 
-Date: 2026-05-29 10:37 MDT
+Date: 2026-05-29 10:44 MDT
 Workspace: `/Users/user/mine/jerboa-semgrep`
 Sibling upstream Semgrep checkout: `/Users/user/mine/semgrep`
 Packaged Semgrep oracle: `/Users/user/.local/bin/semgrep`
 Base HEAD before this checkpoint:
 `3e1fa2b Cover Scala upstream fixtures`
+Latest implementation commit before this handoff document:
+`405e3db Cover C++ upstream fixtures`
 
 The user wants this project carried forward until the pure Jerboa port reaches
 Semgrep parity. Continue from the current frontier below. Do not restart broad
@@ -2062,6 +2064,115 @@ sweep. The three packaged-Semgrep `current_error` cases are
 current CLI rule validation because their upstream test YAML contains an empty
 `metadata:`.
 
+The most concrete next implementation target is the 29-case PHP bucket. PHP is
+already recognized as a text-backed language for the `.tpl` fixture, but
+`.php` targets are intentionally not included in `tests/oracle/upstream-sweep.sh`
+yet. If the next agent chooses PHP, first collect oracle output before adding
+`.php` to the suffix list. Do not widen the harness until the expected current
+Semgrep findings are known.
+
+PHP case list:
+
+```text
+TODO_taint_messy_sink
+attributes_match_multiple
+metavar_call
+metavar_call_case_insensitive
+metavar_pattern_fake_toks
+metavar_pattern_fake_toks1
+metavar_regex_include
+metavar_type_non_prim_php
+metavar_type_str_eq_php
+metavariable_pattern_php
+misc_php_new
+misc_php_new_taint
+option_ac_matching
+option_symmetric_eq
+taint_assume_safe_booleans2
+taint_assume_safe_funcs
+taint_assume_safe_numbers2
+taint_assume_safe_numbers4
+taint_best_fit_sink1
+taint_best_fit_sink4
+taint_field_sensitive5
+taint_not_conflicting_sanitizer
+taint_not_conflicting_sanitizer1
+taint_propagator_by_side_effect_false
+taint_propagator_by_side_effect_false1
+taint_wo_side_effects
+taint_wo_side_effects1
+taint_wo_side_effects2
+tainted-filename
+```
+
+PHP oracle collection command:
+
+```sh
+tmp=$(mktemp -d /tmp/php-oracle.XXXXXX)
+for b in TODO_taint_messy_sink attributes_match_multiple metavar_call metavar_call_case_insensitive metavar_pattern_fake_toks metavar_pattern_fake_toks1 metavar_regex_include metavar_type_non_prim_php metavar_type_str_eq_php metavariable_pattern_php misc_php_new misc_php_new_taint option_ac_matching option_symmetric_eq taint_assume_safe_booleans2 taint_assume_safe_funcs taint_assume_safe_numbers2 taint_assume_safe_numbers4 taint_best_fit_sink1 taint_best_fit_sink4 taint_field_sensitive5 taint_not_conflicting_sanitizer taint_not_conflicting_sanitizer1 taint_propagator_by_side_effect_false taint_propagator_by_side_effect_false1 taint_wo_side_effects taint_wo_side_effects1 taint_wo_side_effects2 tainted-filename; do
+  echo "===== $b"
+  SEMGREP_SEND_METRICS=off /Users/user/.local/bin/semgrep scan --quiet --json \
+    --config "/Users/user/mine/semgrep/tests/rules/$b.yaml" \
+    "/Users/user/mine/semgrep/tests/rules/$b.php" > "$tmp/$b.json" 2> "$tmp/$b.err"
+  rc=$?
+  if [ "$rc" != 0 ] && [ "$rc" != 1 ]; then
+    echo "CURRENT_ERROR rc=$rc"
+    sed 's/^/  /' "$tmp/$b.err"
+  else
+    /Users/user/mine/jerboa/.chez/bin/scheme \
+      --libdirs "lib:/Users/user/mine/jerboa-treesitter/lib:/Users/user/mine/jerboa/lib" \
+      --script tests/oracle/normalize-findings.ss < "$tmp/$b.json" | sort
+  fi
+done
+rm -rf "$tmp"
+```
+
+PHP source/rule inspection command:
+
+```sh
+for b in TODO_taint_messy_sink attributes_match_multiple metavar_call metavar_call_case_insensitive metavar_pattern_fake_toks metavar_pattern_fake_toks1 metavar_regex_include metavar_type_non_prim_php metavar_type_str_eq_php metavariable_pattern_php misc_php_new misc_php_new_taint option_ac_matching option_symmetric_eq taint_assume_safe_booleans2 taint_assume_safe_funcs taint_assume_safe_numbers2 taint_assume_safe_numbers4 taint_best_fit_sink1 taint_best_fit_sink4 taint_field_sensitive5 taint_not_conflicting_sanitizer taint_not_conflicting_sanitizer1 taint_propagator_by_side_effect_false taint_propagator_by_side_effect_false1 taint_wo_side_effects taint_wo_side_effects1 taint_wo_side_effects2 tainted-filename; do
+  echo "===== $b.yaml"
+  sed -n '1,240p' "/Users/user/mine/semgrep/tests/rules/$b.yaml"
+  echo "===== $b.php"
+  nl -ba "/Users/user/mine/semgrep/tests/rules/$b.php"
+done
+```
+
+PHP implementation checklist:
+
+- Add `php` to `target_for_rule` in `tests/oracle/upstream-sweep.sh` only after
+  collecting the current oracle outputs.
+- Add smoke coverage for direct `.php` language inference, not just `.tpl`.
+- Keep PHP routed through the existing generic text path only for fixtures that
+  the text matcher can honestly cover; add targeted fallbacks in
+  `src/semgrep/scan.ss` for observed range, type, taint, and case-insensitive
+  quirks.
+- Run the focused PHP sweep before the full sweep:
+
+```sh
+SEMGREP_CURRENT=/Users/user/.local/bin/semgrep CASE_REGEX='^(TODO_taint_messy_sink|attributes_match_multiple|metavar_call|metavar_call_case_insensitive|metavar_pattern_fake_toks|metavar_pattern_fake_toks1|metavar_regex_include|metavar_type_non_prim_php|metavar_type_str_eq_php|metavariable_pattern_php|misc_php_new|misc_php_new_taint|option_ac_matching|option_symmetric_eq|taint_assume_safe_booleans2|taint_assume_safe_funcs|taint_assume_safe_numbers2|taint_assume_safe_numbers4|taint_best_fit_sink1|taint_best_fit_sink4|taint_field_sensitive5|taint_not_conflicting_sanitizer|taint_not_conflicting_sanitizer1|taint_propagator_by_side_effect_false|taint_propagator_by_side_effect_false1|taint_wo_side_effects|taint_wo_side_effects1|taint_wo_side_effects2|tainted-filename)$' LIST_MISMATCHES=1 MAX_DIFFS=320 tests/oracle/upstream-sweep.sh
+```
+
+If all PHP cases pass and the packaged oracle still has only the known
+null-metadata errors, the expected full sweep should move from:
+
+```text
+366 passed, 0 mismatched, 0 jerboa errors, 3 current errors, 369 compared
+```
+
+to:
+
+```text
+395 passed, 0 mismatched, 0 jerboa errors, 3 current errors, 398 compared
+```
+
+At that point the skipped target-extension buckets should be only:
+
+```text
+42 java
+ 4 no separate target
+```
+
 If continuing parity beyond this sweep, broaden the oracle rather than chasing
 this now-empty frontier. Candidate next expansions: