fix(locks): canonicalize paths so one file cannot yield two lock keys
Found by running examples/two-agent-collision.ts, which was the first thing to exercise the lock path from outside the test harness. Symlinks were not resolved. On macOS /var is a symlink to /private/var, so one physical file reached by two valid names produced two different lock keys. The loud version of this is a refused lock. The dangerous version is the opposite: two developers reaching one file through different symlinked paths both get GRANTED and the lock system silently does not lock. normalizeLockPath (the key generator) and validateFileOnly (the containment check) now both canonicalize, resolving symlinks down to the deepest existing ancestor so files that do not exist yet can still be locked. projectRoot was accepted as a NerveCenter option and never reached lock validation, which always used process.cwd(). Any server whose working directory is not the repo root refused every lock. It is carried on CoreServices as a live getter, not a snapshot, because detectProjectName can reassign the root to a discovered ancestor after construction. Three regression tests, all verified to fail against the pre-fix code. One of them initially passed against broken code for the wrong reason: the symlinked path returned REJECTED (bogus containment failure) rather than REQUIRES_ORCHESTRATION (real conflict), which satisfied a loose not.toBe(GRANTED) while proving nothing. It now pins the status and requires the holder in the message. Follow-up: switchProject calls process.chdir, which partly masked this. Nothing in the lock path needs it now and it is worth removing.
V
Vir Sanghavi committed
35932f25e55d1462d39d989f027eb36ef74b44f1
Parent: 1c4a965