fix(trace): stop crediting finished sessions on unrelated commits
The Chainloop-Trace-Sessions trailer was chosen by intersecting the staged files with every ai-lines ledger on disk, with no scoping in time, branch or session liveness. Those ledgers never expire — WipeTraceDir preserves them and GCOrphans only drops one when no surviving commit references it — so a session pinned itself into the repository after its first commit and kept being stamped onto every later commit that touched any file it had ever edited.
The consequences compounded downstream: a session's evidence file set comes from the diffs of the commits credited to it, so a foreign commit contributed its whole diff, and every file the session had no ranges for was reported as human-written. A single session accumulated commits from unrelated branches and surfaced on pull requests it never touched.
What changed
Attribution is now consumed when it lands:
- Each
ai-linesentry carries arecorded_at, and the post-commit hook appends aconsumed_bymarker for the files the commit contained. - Matching runs against the pending set instead of the full range history. Ranges are retained for push-time enrichment, and per-file consumption keeps credit intact for a session committing its work in chunks.
- Entries written before
recorded_atexisted are never pending, so ledgers already on disk stop matching immediately.
Two supporting changes:
session-endpersistsactive: falseandsession-startrevives it on resume, which lets a push with no commits to go on skip sessions that have ended.activewas previously written once and never read.GCOrphansretires sessions older than a week once everything they produced has been attested, since reachability alone never expires them.
active remains unsuitable as an attribution signal — session-end is not installed for trace run and never fires on SIGKILL or a closed terminal.