@@ -77,13 +77,14 @@ log() { printf '[setup-matrix:%s] %s\n' "$SM_ID" "$*"; }
7777json_str () { printf ' %s' " $1 " | tr -d ' \r' | tr ' \n' ' ' | sed ' s/\\/\\\\/g; s/"/\\"/g' ; }
7878emit_result () {
7979 local actual=" $1 " primary_present=" $2 " setup_exit=" $3 " install_exit=" $4 " target=" $5 " status=" $6 "
80- printf ' {"id":"%s","ecosystem":"%s","pm":"%s","scenario":"%s","patchset":"%s","run_setup":%s,"expect_applied":%s,"actual_applied":%s,"primary_marker_present":%s,"setup_exit":%s,"install_exit":%s,"check_after_setup_exit ":%s,"remove_exit ":%s,"check_after_remove_exit ":%s,"remove_clean ":%s,"target":"%s","status":"%s","notes":"%s"}\n' \
80+ printf ' {"id":"%s","ecosystem":"%s","pm":"%s","scenario":"%s","patchset":"%s","run_setup":%s,"expect_applied":%s,"actual_applied":%s,"applied_before_setup":%s,"applied_after_remove":%s," primary_marker_present":%s,"setup_exit":%s,"install_exit":%s,"check_before_setup_exit ":%s,"check_after_setup_exit ":%s,"remove_exit ":%s,"check_after_remove_exit ":%s,"target":"%s","status":"%s","notes":"%s"}\n' \
8181 " $( json_str " $SM_ID " ) " " $( json_str " $SM_ECOSYSTEM " ) " " $( json_str " $SM_PM " ) " \
8282 " $( json_str " $SM_SCENARIO " ) " " $( json_str " $SM_PATCHSET " ) " \
8383 " $( [ " $SM_RUN_SETUP " = 1 ] && echo true || echo false) " \
8484 " $( [ " $SM_EXPECT_APPLIED " = 1 ] && echo true || echo false) " \
85- " $actual " " $primary_present " " $setup_exit " " $install_exit " \
86- " ${CHECK_AFTER_SETUP_EXIT:- null} " " ${REMOVE_EXIT:- null} " " ${CHECK_AFTER_REMOVE_EXIT:- null} " " ${REMOVE_CLEAN:- null} " \
85+ " $actual " " ${APPLIED_BEFORE_SETUP:- null} " " ${APPLIED_AFTER_REMOVE:- null} " " $primary_present " \
86+ " $setup_exit " " $install_exit " \
87+ " ${CHECK_BEFORE_SETUP_EXIT:- null} " " ${CHECK_AFTER_SETUP_EXIT:- null} " " ${REMOVE_EXIT:- null} " " ${CHECK_AFTER_REMOVE_EXIT:- null} " \
8788 " $( json_str " $target " ) " " $( json_str " $status " ) " " $( json_str " $NOTES " ) " >&3
8889}
8990
@@ -449,6 +450,49 @@ resolve_targets() {
449450 esac
450451}
451452
453+ # --- native install dispatch (layout-aware) --------------------------
454+ do_install () {
455+ case " $SM_LAYOUT " in
456+ workspace) run_install_workspace ;;
457+ monorepo) run_install_monorepo ;;
458+ * ) run_install ;;
459+ esac
460+ }
461+
462+ # Wipe installed modules so the NEXT install re-fetches a pristine copy and
463+ # re-fires the lifecycle hook. This is what lets us observe the patch-apply
464+ # BEHAVIOR (marker present/absent on a freshly installed file) at each stage
465+ # of the (setup)·(install) sequence, rather than inspecting package.json.
466+ reset_modules () {
467+ rm -rf node_modules packages/* /node_modules 2> /dev/null || true
468+ }
469+
470+ # Resolve every on-disk copy of the patched file and decide whether the patch
471+ # was applied (marker present). Sets APPLIED / PRIMARY_PRESENT / TARGET.
472+ verify_applied () {
473+ local check_marker=" $SM_MARKER "
474+ [ " $SM_PATCHSET " = alt ] && check_marker=" $SM_ALT_MARKER "
475+ APPLIED=false
476+ PRIMARY_PRESENT=null
477+ TARGET=" "
478+ local n_found=0 cand
479+ while IFS= read -r cand; do
480+ [ -n " $cand " ] && [ -f " $cand " ] || continue
481+ n_found=$(( n_found + 1 ))
482+ [ -z " $TARGET " ] && TARGET=" $cand "
483+ if grep -q " $check_marker " " $cand " 2> /dev/null; then APPLIED=true; TARGET=" $cand " ; fi
484+ if grep -q " $SM_MARKER " " $cand " 2> /dev/null; then PRIMARY_PRESENT=true; fi
485+ done < <( resolve_targets)
486+ [ " $PRIMARY_PRESENT " = null ] && [ " $n_found " -gt 0 ] && PRIMARY_PRESENT=false
487+ log " verify: marker '$check_marker ' present=$APPLIED (candidates=$n_found , target=${TARGET:- <none>} )"
488+ }
489+
490+ # npm-family is the surface `setup` actually configures today — the only place
491+ # the behavioral check/remove round-trip is expected to do real work.
492+ is_npm_family () {
493+ [[ " $SM_PM " =~ ^(npm| yarn| pnpm| bun)$ ]] || [ " $SM_LAYOUT " = monorepo ]
494+ }
495+
452496# ============================ main ====================================
453497log " binary: $SP_BIN ($( " $SP_BIN " --version 2> /dev/null || echo ' ??' ) ) layout=$SM_LAYOUT "
454498
@@ -494,76 +538,82 @@ export SOCKET_TELEMETRY_DISABLED=1 SOCKET_EXPERIMENTAL_MAVEN=1 SOCKET_EXPERIMENT
494538# make every member apply target the root manifest and fail with "no
495539# packages found on disk" mid-install, breaking `npm install`.
496540
497- # 1. setup (configures hooks; no-op where there is no package.json)
541+ # 1-3. Configure + install + verify.
542+ #
543+ # For npm-family cases that run setup we exercise the FULL behavioral sequence
544+ # — (install)·(setup)·(install)·(remove)·(install) — observing both the patch
545+ # marker and `setup --check` at each stage. A clean reinstall precedes every
546+ # observation so the lifecycle hook acts on a pristine package. This verifies
547+ # behavior end-to-end rather than reading package.json:
548+ # * patch: NOT applied before setup → applied after setup → NOT applied after remove
549+ # * check: fails before setup → passes after setup → fails after remove
550+ #
551+ # Every other case (run_setup=0, or non-npm-family ecosystems) keeps the simple
552+ # single-install flow, preserving the existing aspirational expect_applied
553+ # classification untouched.
498554SETUP_EXIT=" null"
555+ CHECK_BEFORE_SETUP_EXIT=" null"
499556CHECK_AFTER_SETUP_EXIT=" null"
500- if [ " $SM_RUN_SETUP " = 1 ]; then
557+ REMOVE_EXIT=" null"
558+ CHECK_AFTER_REMOVE_EXIT=" null"
559+ APPLIED_BEFORE_SETUP=null
560+ APPLIED_AFTER_REMOVE=null
561+ INSTALL_EXIT=" null"
562+
563+ if is_npm_family && [ " $SM_RUN_SETUP " = 1 ]; then
564+ # (1) BEFORE setup: no hook configured → install must NOT apply the patch.
565+ log " [before-setup] install for pm=$SM_PM (layout=$SM_LAYOUT )"
566+ do_install; log " [before-setup] install exit=$? "
567+ verify_applied; APPLIED_BEFORE_SETUP=" $APPLIED "
568+
569+ # (2) check must report "needs configuration" (non-zero) before setup.
570+ " $SP_BIN " setup --check --json; CHECK_BEFORE_SETUP_EXIT=$?
571+ log " check-before-setup exit=$CHECK_BEFORE_SETUP_EXIT "
572+
573+ # (3) setup, then check must report "configured" (zero).
501574 log " running: socket-patch setup --yes"
502575 " $SP_BIN " setup --yes --json; SETUP_EXIT=$?
503576 log " setup exit=$SETUP_EXIT "
504577 [ -f package.json ] && { log " package.json scripts after setup:" ; grep -A6 ' "scripts"' package.json || true ; }
505-
506- # Read-only verification: a project we just configured must pass --check
507- # (exit 0). Recorded for the harness; does not touch disk.
508- log " running: socket-patch setup --check (after setup)"
509578 " $SP_BIN " setup --check --json; CHECK_AFTER_SETUP_EXIT=$?
510579 log " check-after-setup exit=$CHECK_AFTER_SETUP_EXIT "
511- fi
512580
513- # 2. native install (this is where a configured hook fires)
514- log " running install for pm=$SM_PM (layout=$SM_LAYOUT )"
515- case " $SM_LAYOUT " in
516- workspace) run_install_workspace ;;
517- monorepo) run_install_monorepo ;;
518- * ) run_install ;;
519- esac
520- INSTALL_EXIT=$?
521- log " install exit=$INSTALL_EXIT "
522-
523- # 3. verify — applied if ANY discovered copy of the patched file carries
524- # the expected marker (covers hoisting, the pnpm store, member dirs and
525- # the shared venv in workspace/monorepo layouts).
526- check_marker=" $SM_MARKER "
527- [ " $SM_PATCHSET " = alt ] && check_marker=" $SM_ALT_MARKER "
528- APPLIED=false
529- PRIMARY_PRESENT=null
530- TARGET=" "
531- n_found=0
532- while IFS= read -r cand; do
533- [ -n " $cand " ] && [ -f " $cand " ] || continue
534- n_found=$(( n_found + 1 ))
535- [ -z " $TARGET " ] && TARGET=" $cand "
536- if grep -q " $check_marker " " $cand " 2> /dev/null; then APPLIED=true; TARGET=" $cand " ; fi
537- if grep -q " $SM_MARKER " " $cand " 2> /dev/null; then PRIMARY_PRESENT=true; fi
538- done < <( resolve_targets)
539- [ " $PRIMARY_PRESENT " = null ] && [ " $n_found " -gt 0 ] && PRIMARY_PRESENT=false
540- note " candidate files found: $n_found "
541- log " resolved target: ${TARGET:- <none>} (candidates=$n_found )"
542- [ " $n_found " -eq 0 ] && note " target file not found"
543- log " marker '$check_marker ' present: $APPLIED "
544-
545- # 4. remove round-trip — only meaningful where setup configured hooks.
546- # Done LAST (after install + verify) so it cannot disturb the apply check.
547- # Asserts the inverse of setup: --remove strips the hook, --check then fails,
548- # and `socket-patch` no longer appears in the root package.json.
549- REMOVE_EXIT=" null"
550- CHECK_AFTER_REMOVE_EXIT=" null"
551- REMOVE_CLEAN=" null"
552- if [ " $SM_RUN_SETUP " = 1 ] && [ -f package.json ]; then
581+ # (4) AFTER setup: clean reinstall → the hook fires → MAIN applied result.
582+ reset_modules
583+ log " [after-setup] install for pm=$SM_PM (layout=$SM_LAYOUT )"
584+ do_install; INSTALL_EXIT=$?
585+ log " [after-setup] install exit=$INSTALL_EXIT "
586+ verify_applied # sets the canonical APPLIED / PRIMARY_PRESENT / TARGET
587+
588+ # (5) remove, then check must report "needs configuration" (non-zero) again.
553589 log " running: socket-patch setup --remove --yes"
554590 " $SP_BIN " setup --remove --yes --json; REMOVE_EXIT=$?
555591 log " remove exit=$REMOVE_EXIT "
556- log " package.json scripts after remove:" ; grep -A6 ' "scripts"' package.json || true
557-
558- log " running: socket-patch setup --check (after remove)"
592+ [ -f package.json ] && { log " package.json scripts after remove:" ; grep -A6 ' "scripts"' package.json || true ; }
559593 " $SP_BIN " setup --check --json; CHECK_AFTER_REMOVE_EXIT=$?
560594 log " check-after-remove exit=$CHECK_AFTER_REMOVE_EXIT "
561595
562- if grep -q " socket-patch" package.json 2> /dev/null; then
563- REMOVE_CLEAN=false; note " remove left socket-patch in root package.json"
564- else
565- REMOVE_CLEAN=true
596+ # (6) AFTER remove: clean reinstall → no hook → must NOT apply the patch.
597+ # Preserve the main (after-setup) result while re-probing the disk.
598+ _MAIN_APPLIED=" $APPLIED " ; _MAIN_PRIMARY=" $PRIMARY_PRESENT " ; _MAIN_TARGET=" $TARGET "
599+ reset_modules
600+ log " [after-remove] install for pm=$SM_PM (layout=$SM_LAYOUT )"
601+ do_install; log " [after-remove] install exit=$? "
602+ verify_applied; APPLIED_AFTER_REMOVE=" $APPLIED "
603+ APPLIED=" $_MAIN_APPLIED " ; PRIMARY_PRESENT=" $_MAIN_PRIMARY " ; TARGET=" $_MAIN_TARGET "
604+ else
605+ # Simple flow: optional setup (no-op where there is no package.json), one
606+ # install, one verify.
607+ if [ " $SM_RUN_SETUP " = 1 ]; then
608+ log " running: socket-patch setup --yes"
609+ " $SP_BIN " setup --yes --json; SETUP_EXIT=$?
610+ log " setup exit=$SETUP_EXIT "
611+ [ -f package.json ] && { log " package.json scripts after setup:" ; grep -A6 ' "scripts"' package.json || true ; }
566612 fi
613+ log " running install for pm=$SM_PM (layout=$SM_LAYOUT )"
614+ do_install; INSTALL_EXIT=$?
615+ log " install exit=$INSTALL_EXIT "
616+ verify_applied
567617fi
568618
569619# Driver-level status: did actual match the aspirational expectation?
0 commit comments