fix(orbit-local): record code indexing failures instead of dropping them
What does this MR do and why?
Local code indexing could lose a failure three ways: a repository whose indexing failed partway was still reported as successfully indexed, a repository that could not be opened at all left no trace in the manifest, and when a failure reason was recorded it was never shown by the list command. This makes failures durable and visible, so orbit list tells you which repositories failed and why instead of silently pretending everything is fine.
Related Issues
Relates to #1109 (accessible analytics for code indexing internals).
Testing
Three new tests (a unit test per manifest-write path plus an end-to-end CLI test that indexes a commitless repo and asserts an error row with a reason). orbit-local suite 34 passed; CLI suite 31 passed. One unrelated repo-map fixture test fails identically on clean main (environment-dependent, not touched here).
Performance Analysis
No hot-path change. Failure recording is one extra best-effort upsert only on the error path; the success path is unchanged.
- This merge request does not introduce any performance regression. If a performance regression is expected, explain why.
Agent context — file-by-file, control flow, alternatives
Fixes three gaps in crates/orbit-local, all found in an audit of local vs server failure capture. Local already matches server on per-file gl_file.reason; the gaps were all at the per-repo (_orbit_manifest) level.
Gap 1 — false success on fatal pipeline errors (main.rs). index_repo logged v2_result.errors (which can carry fatal: true task-level errors — Arrow conversion, sink write, thread-pool) then wrote Indexed unconditionally. Now it counts fatals and, if any, bail!s with the first error's stage and message before the Indexed write, so the caller's existing error arm records error + reason. Also folded the two duplicate warn-loops over errors into one.
Gap 3 — unopenable repo left no row (main.rs + workspace.rs). When git_info fails (e.g. a repo with no commits, unresolvable HEAD), the loop only did failed += 1; continue. Added workspace::record_git_info_failure: a best-effort error upsert keyed on the canonicalized (falling back to raw) path, project_id derived from that path via the existing hash. Best-effort by design — any DB error there is logged, never propagated, so it cannot mask the original git error.
Gap 2 — reason never surfaced (list.rs). MANIFEST_QUERY now selects error_message, so orbit list shows the reason on error rows. Docs (cli.md) updated to match.
Not fixed here (deliberate follow-up): a hard crash / SIGKILL between the start indexing write and the terminal write leaves the row stuck at indexing. That needs a stale-indexing reconciler, a larger design question; locally the fix is a re-run. Candidate follow-up under #1109.
Tests. workspace::record_git_info_failure_writes_error_row, list::surfaces_error_message_for_failed_repos, and CLI unindexable_repo_records_error_in_manifest (git-init a commitless repo → non-zero exit → one error row with a non-empty reason).
%{all_commits}