perf: Use stack-allocated OidHash in FileHashes and skip expanded hashes on normal runs (#12065)
## Summary
Two memory optimizations targeting large monorepos where per-file hash
storage is a significant memory consumer.
- **`FileHashes` now stores `OidHash` instead of `String`** — eliminates
one heap allocation (~80-96 bytes) per file across the entire repo.
`OidHash` is a 40-byte stack-allocated `Copy` type. The `OidHash` →
`String` conversion now only happens at the `expanded_inputs` trait
boundary, which only runs for `--summarize`/`--dry` JSON output.
- **Expanded hashes are conditional** — `calculate_file_hashes` takes a
`needs_expanded_hashes` flag. On normal `turbo run` (no `--dry`,
`--summarize`, or observability), the per-file `Arc<FileHashes>` maps
are computed for the collapsed task hash but not retained in
`TaskHashTracker`. This skips `O(tasks × files)` of path+hash data that
previously lived for the entire run.
## What changed
- **`crates/turborepo-hash/src/oid_hash.rs`** (new) — `OidHash` moved
from `turborepo-scm` to `turborepo-hash`. It's a pure hash-value type,
architecturally belonging with `FileHashes` and `TurboHash`.
- **`crates/turborepo-hash/src/lib.rs`** — `FileHashes` inner type
changed from `String` to `OidHash`. Capnp serialization uses `&**value`
(deref to `&str`).
- **`crates/turborepo-scm/src/lib.rs`** — Removed `OidHash` definition,
re-exports from `turborepo_hash`.
- **`crates/turborepo-task-hash/src/lib.rs`** — Removed three
`String::from(v)` conversions when constructing `FileHashes`. Added
`needs_expanded_hashes` parameter to `calculate_file_hashes`. Added
`OidHash` → `String` conversion in `expanded_inputs` trait impl (the
single JSON boundary point).
- **`crates/turborepo-run-cache/src/lib.rs`** — Removed
`String::from(v)` conversion.
- **`crates/turborepo-lib/src/run/mod.rs`** — Passes
`needs_expanded_hashes` flag computed from `dry_run`, `summarize`, and
`observability_handle`.
## Testing
- Added `test_expanded_inputs_none_when_not_collected` — verifies that
when `TaskHashTracker` is constructed with an empty expanded hashes map
(simulating `needs_expanded_hashes=false`), `get_expanded_inputs`
returns `None` rather than panicking, even though the collapsed task
hash exists.
- Updated all existing `FileHashes` test values to use valid 40-char hex
strings for `OidHash`.
- All 365 tests across affected crates pass.