Immunity plan WP7, step 3 of 3 — stacked on #535 (driver arm) ← #525 (protocol layer). Retarget down the stack as they merge. Replaces #537 (same tree plus the fence DACL fix, as one gate-clean commit; no force-push by house rule). With this, the fence protocol is live end to end wherever both sides open the fences; every other pairing keeps the keyed-mutex arm.
Negotiation (no flag day)
Every ring generation carries two fresh shared ID3D11Fences (fresh, so both value sequences restart at zero on both sides; created with the SYSTEM-only DACL like every other frame object — design/idd-push-security.md item 10) in SetFrameChannelRequestV2, and a v4 header with the slot table. A pre-D3D11.4 device gets neither and stays on the mutex arm.
The first ring on a box is a keyed-mutex probe carrying fences with CAP_FENCE_RING clear. The open-time attach's capability word says whether the driver opened them — remembered process-wide (DRIVER_FENCE_CAPABLE) — and a capable driver's probe ring is rebuilt on the fence protocol before any frame flows (one extra recreate on the first session per process). A fence-mode attach that fails falls back to the mutex arm and remembers that.
Fence-mode textures are SHARED | NTHANDLE without a keyed mutex (HostSlot.mutex: Option); FrameChannelSender / send_frame_channel carry the v2 request (a pre-fence driver reads the v1 prefix).
Consume (D5)
Newest PUBLISHED slot above the last delivery from the slot table (fence::consumer_pick) — older publishes freed (newest-wins, the S2 finding), and any PUBLISHED record whose packed token names another generation freed too (a leftover of a superseded publisher; closes the race a ring rebuild opens). CAS PUBLISHED → READING, GPU Wait(ready, record.ready_value), the existing convert/blend path unchanged, Signal(retire, ++retire_value), publish the value in the record, then FREE (Release). A cursor regen re-reads the last slot only while it is FREE and retires it the same way. FenceSlotGuard puts a READING slot back to FREE on any early return.
Recreate
Fresh fences + slot table zeroed before the generation bump, CAP_FENCE_RING set/cleared per mode, v2 delivery.
Verification
Writing gate passes. pf-capture / pf-vdisplay / punktfunk-host cannot build on macOS; windows-host dispatch on this branch is the compile proof. The driver arm's windows-drivers / driver-build already passed (run 22731).
Live proof pending on the .173 box (both vendors, BGRA/FP16, chaos, process death); the S2 spike passed 10/10 there with the same shape, and WP4's attach log will show negotiated & CAP_FENCE_RING.
Immunity plan **WP7**, step 3 of 3 — stacked on #535 (driver arm) ← #525 (protocol layer). Retarget down the stack as they merge. Replaces #537 (same tree plus the fence DACL fix, as one gate-clean commit; no force-push by house rule). With this, the fence protocol is live end to end wherever both sides open the fences; every other pairing keeps the keyed-mutex arm.
## Negotiation (no flag day)
- Every ring generation carries **two fresh shared `ID3D11Fence`s** (fresh, so both value sequences restart at zero on both sides; created with the SYSTEM-only DACL like every other frame object — `design/idd-push-security.md` item 10) in `SetFrameChannelRequestV2`, and a **v4 header** with the slot table. A pre-D3D11.4 device gets neither and stays on the mutex arm.
- The **first ring on a box is a keyed-mutex probe** carrying fences with `CAP_FENCE_RING` clear. The open-time attach's capability word says whether the driver opened them — remembered process-wide (`DRIVER_FENCE_CAPABLE`) — and a capable driver's probe ring is **rebuilt on the fence protocol before any frame flows** (one extra recreate on the first session per process). A fence-mode attach that fails falls back to the mutex arm and remembers that.
- Fence-mode textures are `SHARED | NTHANDLE` without a keyed mutex (`HostSlot.mutex: Option`); `FrameChannelSender` / `send_frame_channel` carry the v2 request (a pre-fence driver reads the v1 prefix).
## Consume (D5)
Newest PUBLISHED slot above the last delivery from the slot table (`fence::consumer_pick`) — older publishes freed (newest-wins, the S2 finding), and **any PUBLISHED record whose packed token names another generation freed too** (a leftover of a superseded publisher; closes the race a ring rebuild opens). CAS PUBLISHED → READING, GPU `Wait(ready, record.ready_value)`, the existing convert/blend path unchanged, `Signal(retire, ++retire_value)`, publish the value in the record, then FREE (Release). A cursor regen re-reads the last slot only while it is FREE and retires it the same way. `FenceSlotGuard` puts a READING slot back to FREE on any early return.
## Recreate
Fresh fences + slot table zeroed before the generation bump, `CAP_FENCE_RING` set/cleared per mode, v2 delivery.
## Verification
- Writing gate passes. `pf-capture` / `pf-vdisplay` / `punktfunk-host` cannot build on macOS; **`windows-host` dispatch on this branch is the compile proof**. The driver arm's `windows-drivers / driver-build` already passed (run 22731).
- **Live proof pending** on the `.173` box (both vendors, BGRA/FP16, chaos, process death); the S2 spike passed 10/10 there with the same shape, and WP4's attach log will show `negotiated & CAP_FENCE_RING`.
enricobuehler
changed target branch from worktree-wp7-driver-fence-v2 to main2026-09-02 09:13:49 +00:00
Immunity plan WP7, step 3 of 3. The IDD-push capturer runs the CAS plus
shared-fence slot protocol once the driver has proven it opens the
fences; every other pairing stays on the keyed-mutex arm.
Every ring generation carries two fresh shared ID3D11Fences (fresh so
both value sequences restart at zero on both sides), created with the
SYSTEM-only DACL and delivered in the v2 request, plus a v4 header with
the slot table. The first ring on a box is a keyed-mutex probe: the
attach's capability word says whether the driver opened the fences,
remembered process-wide; a capable driver's probe ring is rebuilt on the
fence protocol before any frame flows, and a failed fence-mode attach
falls back to the mutex arm.
Fence-mode consume: newest PUBLISHED slot above the last delivery from
the table (older publishes freed; a record whose packed token names
another generation freed too), CAS to READING, GPU-wait producer-ready,
convert as before, signal consumer-retire, publish the value, then FREE.
A RAII guard frees a slot on any early return. Fence textures are SHARED
plus NTHANDLE without a keyed mutex; the sender carries the v2 request.
windows-host run 22795 on the host arm: wait_for_attach lives in the open child module as a private method, and the fence-mode upgrade after the probe ring calls it from the parent. pub(super), like create_ring_slots.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Immunity plan WP7, step 3 of 3 — stacked on #535 (driver arm) ← #525 (protocol layer). Retarget down the stack as they merge. Replaces #537 (same tree plus the fence DACL fix, as one gate-clean commit; no force-push by house rule). With this, the fence protocol is live end to end wherever both sides open the fences; every other pairing keeps the keyed-mutex arm.
Negotiation (no flag day)
ID3D11Fences (fresh, so both value sequences restart at zero on both sides; created with the SYSTEM-only DACL like every other frame object —design/idd-push-security.mditem 10) inSetFrameChannelRequestV2, and a v4 header with the slot table. A pre-D3D11.4 device gets neither and stays on the mutex arm.CAP_FENCE_RINGclear. The open-time attach's capability word says whether the driver opened them — remembered process-wide (DRIVER_FENCE_CAPABLE) — and a capable driver's probe ring is rebuilt on the fence protocol before any frame flows (one extra recreate on the first session per process). A fence-mode attach that fails falls back to the mutex arm and remembers that.SHARED | NTHANDLEwithout a keyed mutex (HostSlot.mutex: Option);FrameChannelSender/send_frame_channelcarry the v2 request (a pre-fence driver reads the v1 prefix).Consume (D5)
Newest PUBLISHED slot above the last delivery from the slot table (
fence::consumer_pick) — older publishes freed (newest-wins, the S2 finding), and any PUBLISHED record whose packed token names another generation freed too (a leftover of a superseded publisher; closes the race a ring rebuild opens). CAS PUBLISHED → READING, GPUWait(ready, record.ready_value), the existing convert/blend path unchanged,Signal(retire, ++retire_value), publish the value in the record, then FREE (Release). A cursor regen re-reads the last slot only while it is FREE and retires it the same way.FenceSlotGuardputs a READING slot back to FREE on any early return.Recreate
Fresh fences + slot table zeroed before the generation bump,
CAP_FENCE_RINGset/cleared per mode, v2 delivery.Verification
pf-capture/pf-vdisplay/punktfunk-hostcannot build on macOS;windows-hostdispatch on this branch is the compile proof. The driver arm'swindows-drivers / driver-buildalready passed (run 22731)..173box (both vendors, BGRA/FP16, chaos, process death); the S2 spike passed 10/10 there with the same shape, and WP4's attach log will shownegotiated & CAP_FENCE_RING.