# E-015 — Two equivalent physical paths hid a hook signal

- Date: 2026-08-03
- Status: confirmed integration false negative, fixed, and covered by E2E.

## Hierarchy of objectives

- Parent objective: detect deterministic drift with an early, traceable signal.
- Phase objective: integrate the global Claude and Codex hooks with a sanitized inventory.
- Round objective: verify that both harnesses detect a deviation seeded from a nested directory.

## Trajectory and evidence

The E2E scenario created a temporary repository, initialized a contract, changed an active test to `test.skip`, and ran both hooks. The contract was found correctly by walking up through the ancestors, but the hook did not record the event: the temporary directory was created as `/var/...` while `git rev-parse --show-toplevel` returned `/private/var/...`.

Both paths refer to the same physical directory on macOS. The textual comparison produced a relative path starting with `../`, so the hook discarded it as if it were outside the project. It was a false negative upstream of the detectors: `weak-test` never got to run.

## Fix and result

Claude and Codex now canonicalize the project root and the file with `realpath` before computing the relative path. The `test/hooks-e2e.test.mjs` regression requires that both hooks, from a nested subdirectory, detect `weak-test` and write isolated events with `tool = claude|codex`, `finding_count = 1`, and `rules = [weak-test]`.

The fix preserves fail-open behavior for paths that do not exist or are not readable, but debug mode can identify the reason for a discard during a test. The result does not prove general semantic sensitivity; it proves that transporting a covered signal to the inventory no longer depends on the textual representation of the path.
