| name | slice |
| description | Isolate one file/function/module and exercise it in isolation with full observability — instrument its inputs, intermediate values, and return with timestamped trace logs (Python or TS/JS), drive it with crafted cases, and assert expected results. The fastest empirical layer: between forbidden static reasoning and a full Docker/Playwright boot. Drives a tight Observe→Orient→Decide→Act loop. Runs inline. Use when the bug lives in a specific function/module, the user says "isolate", "exercise the slice", "test it in isolation", "instrument inputs/outputs", or when the full stack is overkill or won't boot. |
slice — isolate, instrument, exercise
The fastest way to get empirical evidence about a localized bug: pull the suspect slice out of the running app, drive it directly with crafted inputs, and log its inputs, intermediate values, and return with the trace spine. No full boot required — and far more reliable than reading the code. This is the default empirical layer (the "smallest sufficient layer" in practice); reach for the full Docker/Playwright journey only when the bug needs the browser or DB.
OODA loop
- Observe — run the isolated slice with
trace() on inputs, key intermediates, and the return; read the timeline.
- Orient — compare observed values to expected (the assertion in your probe). The first intermediate that diverges from expectation is the bug's location.
- Decide — form the minimal hypothesis at that divergence point.
- Act — edit, re-run the same probe, confirm the divergence is gone. Seconds per cycle.
How
- Pick the slice — the function/module closest to the symptom (
reference/architecture-patterns.md → which pattern; control-surfaces.md → the seam).
- Install the spine if not already:
./scripts/inject-trace.sh --worktree <wt> (copies pldebug_trace.py / pldebug-trace.ts).
- Write a probe from
templates/slice-probe.py or templates/slice-probe.ts: import the slice, define cases {args, expect}, trace input → call → trace output+ok.
- Instrument the intermediates — add ring-0
trace()/span() inside the slice's source at each transform/branch, so you see where the value goes wrong, not just that it did. A probe that only logs the return tells you it's wrong, not why.
- Run it with
./scripts/slice-run.sh <journey-id> probe.py — Python probes run inside the warm container (PL's real env: native deps + the prairielearn package + the injected pldebug_trace spine importable), TS probes run on the host via tsx. Read the sorted trace.
- Slice at the layer that owns the bug, stubbing across the boundary. Server-side logic → run it in the app's real runtime (the container, where its native deps + packages resolve). A template/client-side bug → render the template and drive its script on the host with the rest stubbed (a minimal fake of whatever it talks to). If a module pulls heavy deps you can't load, cut the slice free — import just the function, or copy its body into the probe and stub the deps. Exercise the logic, not the whole module. ("Won't import" is not a reason to fall back to reasoning — isolate around it.)
- Fix, re-run the probe (all cases
ok, including the new bug case), then widen verification via the journey only if the bug also has a UI/DB surface.
Rules
- Inputs, intermediates, AND outputs — instrument all three.
- If the slice produces a visual artifact, RENDER it — don't just trace its data. The real output of a drawing/shape/canvas/plot/SVG slice is the painted image, not the SVG string, coordinate list, or canvas-command buffer it returns. Write that output to an actual image file and look at it (rasterize the SVG to PNG, paint the canvas, drive the element's client script headless and screenshot the node). A passing data assertion on an artifact you never rendered is the same false positive as screenshotting a blank page — if it won't render in isolation, force it (total license) until you can see the real shape.
- Every case carries an expected value — observe-vs-expect is the signal, not eyeballing.
- Add the bug case first (it must fail), then fix until it passes — that failing case is your regression-test seed; fold it into the project's real test suite.
- Ring-0 discipline: if the slice's inputs themselves look wrong, the bug is upstream — isolate the caller next.
Cleanup
Probes are scratch — delete them (or promote the bug case into the project's test suite). Revert instrumentation with inject-trace.sh --revert.