)]}'
{
  "commit": "b190840e1d85f806575f58635827502d714febcf",
  "tree": "76c6abf45f3670313d5812f3c453d849a98371f9",
  "parents": [
    "a5938f71fbc704c85979121bb9e37d5d636ef20d"
  ],
  "author": {
    "name": "hathach",
    "email": "thach@tinyusb.org",
    "time": "Thu Aug 27 15:26:43 2026 +0700"
  },
  "committer": {
    "name": "hathach",
    "email": "thach@tinyusb.org",
    "time": "Fri Aug 28 14:18:41 2026 +0700"
  },
  "message": "test/hil: drop the sysfs blindness subsystem and derive the recovery reserve\n\nTwo layers whose cost was a contract to reason about rather than an outcome.\n\nSYSFS_UNKNOWN was a three-valued return five files had to keep apart, and\nmisreading unknown as absence was silent: a healthy board reported as a firmware\nregression. What it guarded is real -- `serial` is served by usb_string_attr,\nwhich takes usb_lock_device_interruptible (v6.12.96 sysfs.c:141-143), the same\nlock a wedged usbfs ioctl holds -- so the BOUND stays, on every caller by\ndefault. usb_scan reads `serial` on every device matching the VID, and hil_lock\u0027s\ncontroller_of does that on essentially every board, so one wedged DUT would\notherwise stall every worker, not one. What goes is the third value.\n\nread_sysfs now returns str or None, and the question the third value existed to\nanswer is asked directly instead, by two predicates that say which question they\nanswer: sysfs_stranded() is process-wide and sticky, for hil_pool_check\u0027s footer\n(\"could anything here be the tool losing sight of healthy hardware?\"), and\npath_stranded(path) is per-device, which is what usbtest needs to tell a DUT\nwhose `serial` is held under device_lock from one that genuinely left the bus --\nthat difference decides whether it performs driver-registry writes that take the\nuninterruptible device_lock.\n\nGone: _SysfsUnknown, SYSFS_UNKNOWN, sysfs_blind, sysfs_blind_note,\nnote_sysfs_strand, the cross-process blindness publishing and its report banner,\nusb_scan\u0027s (list, bool) return, usbtest\u0027s inconclusive abort, _blind_note\u0027s slot\nin the result tuple, and bounded_open, whose last caller went in the previous\ncommit.\n\nThe strand memo is rewritten around the one invariant that makes it safe to\nreuse: it is keyed by the path\u0027s kernfs inode, captured BEFORE the read. A\nbusport does not change when a board returns to the same physical port, so a\npath-only blacklist outlives the wedge and hil_pool_check\u0027s own recovery flow --\nreset, reflash, wait_device polling that busport -- would never look at the\nboard again. A re-enumeration destroys the kernfs node and makes a new one, so a\nchanged inode is the all-clear. Two ceilings bound different things: per path\n(_PATH_STRAND_MAX) for a board that flaps while still wedged, and per process\n(_STRAND_MAX) as a backstop against RLIMIT_NOFILE, counted per PATH rather than\nper reader because hil_pool_check runs four poll threads over one bus.\n\nA board the pool guard never reached is now reported as run-aborted rather than\npool-timed-out, and outranks a stale board-locked cell for the same reason the\npool-timeout cell does.\n\nBoth predicates answer conservatively where they are consulted before something\nirreversible. path_stranded() covers the paths read_sysfs answered None for\nWITHOUT reading -- past _STRAND_MAX it declines to start another reader, and\nvouching for a path nobody looked at hands usbtest\u0027s fail-CLOSED guard a\nfabricated all-clear, running remove_id/unbind against a wedged device. usbtest\u0027s\nstartup lookup carries the same caveat hil_test\u0027s absent arm already did, because\nits stderr is relayed verbatim into the report cell.\n\nstrand_note() survives the removal for the same reason master had it: every\ncaller that can say \"not found\" needs the same sentence, and the one site left to\nre-invent it got missed -- a wedged-but-enumerated printer was reported as an\nenumeration failure, sending a maintainer after firmware.\n\nThe two predicates are not interchangeable, and usbtest needs both. Its per-case\nverdict is per-DUT -- a peer that stranded at case 2 must not make our board\nreport wedged at case 29 -- but the finally block\u0027s cleanup is process-wide:\nremove_id plus an unbind of EVERY interface under the driver, including that\npeer\u0027s, each taking the uninterruptible device_lock. So the verdict uses\npath_stranded() and the global cleanup stays gated on sysfs_stranded().\n\nUSBTEST_RECOVERY_BUDGET was an independent 250s that could not actually contain\nthe ladder it reserved for, which is why usbtest.py carried a _time_left() gate\nre-deciding before every step -- with a bare \u0027- 35\u0027 for costs paid downstream\nthat nobody could re-derive. Between them the two produced a recovery that\nskipped its own steps for most real hangs.\n\nThe reserve is now derived from the bounds usbtest itself declares, per flasher\nand per target: a probe reset, a reflash, and the Rescue-DP POR plus retry a\nwedged RP DAP needs, plus the settles and hil_util.REAP_GRACE for each bounded\nstep. The Rescue-DP legs are openocd-only and gated on the RP target cfg, and\na stub reset is screened out, so the reserve tracks each board\u0027s real ladder\ninstead of one fleet number: 390s for the two RP boards -- whose ladder the old\n250 could not contain, which is exactly why the gates skipped their steps --\n190s for the other seventeen probe-reset boards, and 150s for esptool and\nlm4flash, whose reset is a no-op. Changing a bound in usbtest moves the\nreserve with it, and a unit test asserts it covers the ladder.\n\nWith the room actually reserved, the child runs the ladder straight through:\nrecovery_steps, _time_left, the three per-step gates and the parsed-but-unused\n--outer-timeout are gone. What stays is what decides outcomes -- the convoy_safe\ngate, reset-before-reflash, the no_op screen so a stub that resets nothing is not\nclaimed, and wedged_pids() as the arbiter, because a clean flash only proves the\nprobe wrote the MCU.\n\nhil_util.py 616 -\u003e 514 lines.\n",
  "tree_diff": [
    {
      "type": "modify",
      "old_id": "d9010d28d7bc46c2516026635b58ae2d8d7c833b",
      "old_mode": 33188,
      "old_path": ".claude/skills/hil/SKILL.md",
      "new_id": "03a462ce6a99c968664545c0d7ea65ad5dc74d60",
      "new_mode": 33188,
      "new_path": ".claude/skills/hil/SKILL.md"
    },
    {
      "type": "modify",
      "old_id": "4d7c6e5ab28e897f71ab2f3bfc168e9254d59bcf",
      "old_mode": 33188,
      "old_path": ".claude/skills/usbtest/SKILL.md",
      "new_id": "606b379b5e32982dfa1167f17b46f155b745f2ed",
      "new_mode": 33188,
      "new_path": ".claude/skills/usbtest/SKILL.md"
    },
    {
      "type": "modify",
      "old_id": "9ac2de228c3036b82ad77f86d0392d2c980b3bef",
      "old_mode": 33188,
      "old_path": ".pre-commit-config.yaml",
      "new_id": "537ed3bc4bbd03b76490e27c2af4483f46fcf3cd",
      "new_mode": 33188,
      "new_path": ".pre-commit-config.yaml"
    },
    {
      "type": "modify",
      "old_id": "7b8da7b9cb1a1b92c3103b5897aad456f9cd4cbb",
      "old_mode": 33188,
      "old_path": "docs/superpowers/followup/pr3840-mret-board-result.md",
      "new_id": "77b76b605cf1843adf0e326355f9a997bf1f6918",
      "new_mode": 33188,
      "new_path": "docs/superpowers/followup/pr3840-mret-board-result.md"
    },
    {
      "type": "modify",
      "old_id": "3ed0c15193785d5200508f44517072b30f8f2004",
      "old_mode": 33188,
      "old_path": "docs/superpowers/specs/2026-07-30-hil-usbtest-fleet-wedge-design.md",
      "new_id": "cc1c95d534d48c243421c05accbb515645162c25",
      "new_mode": 33188,
      "new_path": "docs/superpowers/specs/2026-07-30-hil-usbtest-fleet-wedge-design.md"
    },
    {
      "type": "modify",
      "old_id": "7757ef17de7eee71517e95af5668f969eb4ffc91",
      "old_mode": 33261,
      "old_path": "test/hil/helper/hil_lock.py",
      "new_id": "91f05ca86b8925487bf63379e60436d9c4bb2342",
      "new_mode": 33261,
      "new_path": "test/hil/helper/hil_lock.py"
    },
    {
      "type": "modify",
      "old_id": "4623ce45f13c6f374333f89760d234bc7b75e208",
      "old_mode": 33188,
      "old_path": "test/hil/helper/hil_pool_check.py",
      "new_id": "d98b92bd455af03cd3fab85bf0748e008b614b2c",
      "new_mode": 33188,
      "new_path": "test/hil/helper/hil_pool_check.py"
    },
    {
      "type": "modify",
      "old_id": "d059c62c9862c39c3fee8e2f71c1685f0f258ec0",
      "old_mode": 33188,
      "old_path": "test/hil/helper/hil_report.py",
      "new_id": "c93c8e6a1f1aec033d795853099ba51e0ec7abdc",
      "new_mode": 33188,
      "new_path": "test/hil/helper/hil_report.py"
    },
    {
      "type": "modify",
      "old_id": "f279cfa77f0b45f1ff080c9c095c6ad5f3b470a3",
      "old_mode": 33188,
      "old_path": "test/hil/helper/hil_util.py",
      "new_id": "b2c233cf343b1901b12062d48e3c98c62528b6fe",
      "new_mode": 33188,
      "new_path": "test/hil/helper/hil_util.py"
    },
    {
      "type": "modify",
      "old_id": "c4d4e6552abfa7145ba788b277e40cd3653e7614",
      "old_mode": 33261,
      "old_path": "test/hil/hil_flash.py",
      "new_id": "15f476ccd081c8c97c0c7070ee6d29322dc02599",
      "new_mode": 33261,
      "new_path": "test/hil/hil_flash.py"
    },
    {
      "type": "modify",
      "old_id": "51c4667e6484ab748323401793a164a2a901bf2b",
      "old_mode": 33261,
      "old_path": "test/hil/hil_test.py",
      "new_id": "ac89bdd95c50f9abf56b4dfe9c5b3bbf296788d8",
      "new_mode": 33261,
      "new_path": "test/hil/hil_test.py"
    },
    {
      "type": "modify",
      "old_id": "e72aeea5784c656e4d4f4d63c6595362db2c4be4",
      "old_mode": 33188,
      "old_path": "test/hil/test/stubs/hid.py",
      "new_id": "20a6cccef9e2c97002b963a1bbce9399e88f147e",
      "new_mode": 33188,
      "new_path": "test/hil/test/stubs/hid.py"
    },
    {
      "type": "modify",
      "old_id": "0934a73c12e65a91ecbf3d1f07084e8e21fde964",
      "old_mode": 33188,
      "old_path": "test/hil/test/test_hil_bounded.py",
      "new_id": "c30c58cbd11f12aecf3cadff036aac9b583a564d",
      "new_mode": 33188,
      "new_path": "test/hil/test/test_hil_bounded.py"
    },
    {
      "type": "modify",
      "old_id": "c7864f5684526af88f851c1f3050ed4c13748f83",
      "old_mode": 33188,
      "old_path": "test/hil/test/test_hil_health.py",
      "new_id": "ceecc8d3743de34bf252293a5e5023ed3827b1ce",
      "new_mode": 33188,
      "new_path": "test/hil/test/test_hil_health.py"
    },
    {
      "type": "modify",
      "old_id": "9ab39bde3f28fcd93c433573bf959942b40df1f9",
      "old_mode": 33188,
      "old_path": "test/hil/test/test_hil_report.py",
      "new_id": "7c7a097ef9f525eec6d1732cb363529525123958",
      "new_mode": 33188,
      "new_path": "test/hil/test/test_hil_report.py"
    },
    {
      "type": "modify",
      "old_id": "1a283bda7d8a279e5d4893f8af438b627bf49584",
      "old_mode": 33188,
      "old_path": "test/hil/test/test_hil_util.py",
      "new_id": "17abe52aadbb4eddce81a8a698eaba0dc50ecfa7",
      "new_mode": 33188,
      "new_path": "test/hil/test/test_hil_util.py"
    },
    {
      "type": "modify",
      "old_id": "485e9e0e446d88a9103a59d4a0b7d14208acbd03",
      "old_mode": 33261,
      "old_path": "test/hil/usbtest.py",
      "new_id": "d23217417337b3b147d357b1db8c4990581c487e",
      "new_mode": 33261,
      "new_path": "test/hil/usbtest.py"
    }
  ]
}
