Skip to content

fix(attribution): deterministic why metadata + interactive viewer + honest tag legend#1613

Open
suhaanthayyil wants to merge 19 commits into
mainfrom
fix-why-metadata-consistency
Open

fix(attribution): deterministic why metadata + interactive viewer + honest tag legend#1613
suhaanthayyil wants to merge 19 commits into
mainfrom
fix-why-metadata-consistency

Conversation

@suhaanthayyil

@suhaanthayyil suhaanthayyil commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Trail: https://entire.io/gh/entireio/cli/trails/737

What

entire why <file>[:<line>] could show three different answers on three equivalent clones of the same repo at the same commit — full metadata on one, partial on another, bare CheckpointID on a third. This makes the resolution deterministic: given the same remote state, every clone converges on the same output, and when it can't, the miss reason says exactly why and how to recover.

Root cause (what remained after #1535 / 4f435384d)

Earlier work already added MetadataMissingReason, file-level enrichment, and the fetch suggestion. Two residual defects kept the issue alive:

  1. The partial-blob divergence (the issue's "some see partial data"): when the checkpoint summary read fine but the per-session records were unreadable (treeless/partial clone, git gc-pruned objects — see KNOWN_LIMITATIONS ENT-241), the resolver set MetadataMissing with no reason and no remote refresh. Worse, on multi-session checkpoints the session loop silently continued over unreadable records, so which fallback session got picked depended on which blobs a clone happened to have — different agent/model/prompt per clone, unflagged.
  2. Per-miss fetch churn with swallowed diagnostics: each missing checkpoint ran its own remote-fetch chain (fetchCheckpointContext → fresh lookup → getMetadataTree) and discarded the fetch error. A file with N missing checkpoints paid up to N network timeouts, and the reason couldn't distinguish "fetch failed (offline/auth/no remote)" from "the remote genuinely lacks this checkpoint".

Fix

Restructured the resolver around a memoized once-per-resolver refresh:

  • Any incomplete local read — summary unreadable or session records partially/wholly unreadable — triggers one durable fetch of entire/checkpoints/v1; on success the store is re-pointed at a fresh repo handle (post-fetch packfiles are invisible to the already-open handle) and the read retried. Retries are gated on the store having actually changed during the call (no wasted double reads), and a retry that reads worse than the local pass (an unpushed local-only checkpoint) keeps the local data.
  • The refresh outcome is memoized: many misses share one fetch (or one failure) — bounding network attempts and making a single run internally consistent.
  • The refresh succeeds only when a remote fetch genuinely succeeded. It deliberately does not use getMetadataTree, whose local-branch fallback reports success when every actual fetch failed — that would have made an offline run claim "the remote doesn't have this checkpoint" (found by the audit; regression-pinned by a test that fails on that seam).
  • MetadataMissingReason now distinguishes all the recovery situations the issue reporters couldn't tell apart:
    • no fetch attempted (blame path) → the exact git fetch … entire/checkpoints/v1 to run;
    • refresh failed → the underlying error (auth/network/remote), plus the fetch command;
    • refresh succeeded but the checkpoint is absent → "may not have been pushed yet — its author can push entire/checkpoints/v1" (no pointless fetch suggestion);
    • refresh succeeded but only the session records are unavailable → says exactly that, without falsely claiming the just-resolved checkpoint is missing.
  • The resolution path emits debug logs (summary hit/miss, sessions read x/y, refresh attempt + outcome) to .entire/logs/ for field diagnosis (issue ask Improved entire enable messaging #6).

entire blame stays local-only/fast by design (unchanged semantics; it now also gets a reason for the sessions-unreadable case). File-level and line-level why share the same resolution path (both enrich).

Audit

Adversarially audited in a fix-until-clean loop (multi-lens review; every finding independently adversarially verified before being acted on; exit = two consecutive clean passes). Eleven fix rounds landed; highlights of what the loop caught in my own cuts, all fixed and regression-pinned:

  • Unsound refresh evidence (3 rounds): getMetadataTree's local-branch fallback reported success while offline (an offline run would claim "not pushed"); an origin fallback masked a configured checkpoint remote's failure; a URL-string-equality guard was evadable by token rewrites and false-positived on legitimately-equal URLs → replaced with an explicit FetchURLWithSource source signal (mirrors PushURL).
  • Evidence-gated wording: "not pushed" requires the refreshed store's genuine-absence sentinel; non-absence errors (storage/cancellation) make no remote claim; a rejected-worse retry contributes its proof instead of speculating; a settings-load failure is a refresh failure, not "no checkpoint remote".
  • TOCTOU: the authority is resolved exactly once and the fetch targets the verified URL — no re-resolution inside the fetch helper.
  • git-refs backend (main moved under the branch): the refreshed store keeps RefFetcher capability parity with the initial store, and the strong "the remote's entire/checkpoints/v1 does not contain it" evidence is gated on the git-branch backend (the v1 refresh proves nothing about per-checkpoint refs — refs-backend misses get neutral wording).
  • Consistency of guidance: the appended fetch suggestion can never contradict the failure it accompanies (no "git fetch origin" after refusing origin on policy; prose guidance renders as prose, not as a pasteable command).
  • Test hermeticity: all reason-path tests pin CWD and the checkpoint-backend env (ENTIRE_CHECKPOINTS_PRIMARY), and the real-seam tests clear ENTIRE_CHECKPOINT_TOKEN so no developer/CI environment leaks in (one test provably failed under a refs-backend env before the pin).

Testing

  • New attribution_refresh_test.go: unreadable-session reason on the no-fetch path; refresh restores unreadable sessions; refreshed-but-absent says "not pushed" (and does not suggest a fetch); at-most-once refresh on both the failure and success paths (fetch/reopen counted across misses); local-data-kept when the refreshed read is worse; differently-damaged clones converge on the identical context; partial multi-session reads trigger refresh and resolve the file-matching session; real-seam regression test (local metadata branch + no remote → "remote refresh failed", not "not pushed") — verified to fail on the pre-fix seam.
  • Existing attribution suite (incl. JSON stability, session-fallback, prompt provenance) — green unchanged.
  • mise run lint — 0 issues; mise run test:ci — unit + integration + Vogon canary green on the committed tree.

Related


Part 2: interactive viewer + insider-feedback fixes

An interactive viewer for entire why <file> plus the readability fixes insiders asked for — built strictly under the new Agent-Safe CLI Fallbacks rules (#1596).

entire why <file> --tui

Master-detail Bubble Tea viewer over the already-resolved attribution (no git/network I/O inside the TUI):

  • Left pane: the file's lines with [AI]/[MX]/[HU]/[??] tags and ~/? markers, windowed scrolling for large files.
  • Right pane: the selected line's full explanation — authorship sentence, commit/author/time, agent · model, session (OSC 8 hyperlink to the entire.io session when origin resolves — the CLI↔UI linking idea from the summit), checkpoint, prompt (with the Session prompt caveat), intent, metadata-missing reason, candidate checkpoints, and the checkpoint explain hint (suppressed when metadata is missing, matching the text view).
  • Keys: ↑/↓ line, n/p next/previous agent-attributed line (jump straight to the interesting lines), enter expands the full prompt/candidates, pgup/pgdn, g/G, q. why <file>:12 --tui opens positioned at line 12.

Agent-safe by construction

  • TUI is opt-in (--tui) and TTY-gated (interactive.IsTerminalWriter) and skipped in accessible mode — the exact experts --tui pattern the guidelines cite as good.
  • Non-TTY with --tui falls through to the same deterministic plain output; --json always wins over --tui so scripted callers never block. Both pinned by tests against a non-TTY writer.
  • No information is TUI-only: everything the TUI shows exists in the plain/JSON paths.

Insider-feedback fixes (text output)

  • Tag legend everywhere (blame, why file view, TUI footer): [AI] all agent · [MX] agent+human mixed · [HU] no agent · [??] uncommitted — wording per soph's correction (AI = 100% agent-authored checkpoint work; MX = agent + human edits; careful not to overclaim). Full sentences in the TUI detail pane.
  • Fits the blame table's 80-column budget (test-enforced).

Testing

  • Model-level TUI tests (the repo's established pattern — pure Update/View, color off): render + summary, navigation → prompt detail, start-line positioning, n/p agent-line jumps (including exhaustion + status message), expand toggle, missing-metadata reason + suppressed explain hint, quit keys, empty file, tiny window.
  • Agent-safe fallback pins: --tui on a non-TTY writer produces the plain file summary; --tui --json produces JSON.
  • Found during development: the shared keymap claims n/p for paging — the viewer rebinds paging to pgup/pgdn so n/p can do agent-line jumps (commented).
  • mise run lint 0 issues; full cmd/entire/cli suite green; mise run test:ci green.

Notes

entire why stays hidden/labs. The TUI addresses the insiders' "why is hard to read" feedback and pfleidi's browse-the-file/summit-linking ideas without touching the default output contract.


Part 3: entire blame — what actually changes (and what doesn't)

To be explicit about the two commands, since they share the resolver:

entire blame behavior is unchanged: local-only and fast, never fetches (now pinned by seam-trap tests that fail if the blame path ever refreshes). It gains only diagnostics + the legend:

  • the sessions-unreadable case now carries a MetadataMissingReason (before: flagged missing with no reason and no recovery hint);
  • the tag legend (below) is printed under the table.

entire why is where the behavior changes live: remote enrichment on miss (Part 1) and the opt-in TUI (Part 2).

Tag legend — per-commit inference, spelled out

Per the internal wording guidance: the [AI]/[MX]/[HU] tags are a per-commit inference, not a per-line truth. It shows [AI] only when 100% of the commit's checkpointed work was agent-authored; otherwise [MX]; [HU] when the commit had no agent checkpoint. Shown in blame, the why file view, and the TUI footer:

per commit: [AI] all agent · [MX] mixed — line may be either · [HU] no agent
note: [AI] requires a fully agent commit; one human edit turns all lines [MX]

The note line exists because the rule is not what people infer on their own (a mostly-agent commit with one human tweak renders every line [MX], including the purely agent-written ones). [??] appears on a conditional marker line only when uncommitted lines exist.

… clones

Three clones of the same repo at the same commit could see three different
'entire why' outputs (#1551). Two residual causes after the earlier
actionable-guidance work:

- When the checkpoint summary resolved but the per-session records were
  unreadable (treeless/partial clone, gc-pruned objects), the resolver marked
  the metadata missing with NO reason and never attempted the remote refresh —
  so clones with different local blob availability silently diverged, and a
  multi-session checkpoint could even pick a different fallback session per
  clone.
- Each missing checkpoint ran its own remote-fetch chain and discarded the
  fetch error: a file with many misses paid one network timeout per checkpoint,
  and the reason could not distinguish 'fetch failed' from 'the remote
  genuinely lacks this checkpoint'.

Restructure the resolver around a memoized once-per-resolver refresh:

- Any incomplete local read (summary unreadable OR session records partially/
  wholly unreadable) triggers ONE durable metadata fetch; on success the store
  is re-pointed at a fresh repo handle and the read retried, so every clone of
  the same remote converges on the same answer.
- The refresh outcome is memoized: many misses share one fetch (or one
  failure), making a single run internally consistent and bounding network
  attempts to one.
- A retry that reads worse than the local pass (an unpushed local-only
  checkpoint vanishing from the remote view) keeps the local data.
- MetadataMissingReason now distinguishes the three recovery situations: no
  fetch attempted (run this git fetch), refresh failed (the underlying error),
  and refresh succeeded but the remote lacks the checkpoint (ask its author to
  push entire/checkpoints/v1) — the last no longer suggests a pointless fetch.
- The sessions-unreadable case now carries a reason on the blame (no-fetch)
  path too, and the resolution path emits debug logs (summary miss, sessions
  read x/y, refresh outcome) for field diagnosis.

Closes #1551
…round 1)

An adversarial audit of the determinism fix confirmed four defects; all fixed:

- The refresh seam wrapped getMetadataTree, whose local-branch fallback
  reports success when every actual remote fetch failed (offline, no remote,
  auth). The resolver then confidently claimed 'the remote does not contain
  this checkpoint — it may not have been pushed' and suppressed the git fetch
  suggestion, in exactly the field conditions of #1551. The seam now calls the
  fetch primitives directly (checkpoint remote, then the full-content origin
  fetch — attribution needs session blobs, so the tree-only probe is not
  enough) and returns success ONLY when a remote fetch genuinely succeeded.
- The sessions-unreadable-after-refresh reason reused the whole-checkpoint
  wording, producing the self-contradiction 'checkpoint found, but … the
  remote does not contain it … or it predates checkpointing'. The refreshed
  wording now matches the miss kind: session records still unavailable after
  the refresh, without claiming the just-resolved checkpoint is missing.
- After one successful refresh, every later incomplete checkpoint re-read the
  same store twice (memoized nil triggered an identical retry). A store
  generation counter gates the retry on the store actually changing during the
  call.
- Test gaps: per-index session stubbing (a multi-session checkpoint with only
  some records readable now provably refreshes and resolves the file-matching
  session); the determinism test now converges two differently-damaged clones
  instead of two identical ones; the success path pins one-fetch-one-reopen
  across misses; and a real-seam regression test (local metadata branch, no
  remote) fails on the pre-fix seam and passes on the fixed one.
…fetch (audit round 2)

Second audit pass confirmed six diagnostic-accuracy issues; all fixed:

- A configured checkpoint remote is the source of truth: its fetch failure is
  no longer masked by an origin fallback that may hold a stale copy (which
  would let a miss claim 'not pushed' about a remote we never reached). The
  origin leg now runs only when no checkpoint remote is configured, which also
  removes the 'checkpoint remote: not configured' noise from every error.
- The 'not pushed' claim now requires evidence: the refreshed store must
  report the genuine-absence sentinel. A summary read that fails for any other
  reason (storage error, cancellation) after a successful refresh makes no
  claim about the remote.
- A retry rejected as worse no longer discards what it proved: when the
  refreshed store reports the checkpoint absent while local data survives (an
  unpushed local-only checkpoint with damaged session records), the reason says
  the checkpoint is not on the remote instead of speculating that the metadata
  branch 'may have been pushed without' the session records.
- Reasons are collapsed to one line (git fetch errors embed multi-line output
  that would break the renderer).
- Stale comment still attributing the fetch to getMetadataTree reworded.

Tests updated to present genuine absence via the sentinel where 'not pushed'
is asserted, plus new pins: non-absence errors make no remote claim; the
worse-retry reason uses the retry's proof; multi-line fetch errors collapse.
Third audit pass confirmed three residual issues; all fixed:

- The refreshed-but-unreadable reason pointed users at .entire/logs, which is
  empty for this path at the default log level (all entries are debug-only and
  the post-refresh read failure was never logged at all). The pointer is
  dropped — the cause is already embedded in the reason — and the post-refresh
  outcome now gets a debug log for field diagnosis.
- The resolver comments overclaimed: readCheckpointContextOnce is not
  'side-effect-free / never fetches' and the memoized refresh does not bound
  ALL network attempts — the underlying store's on-demand BlobFetcher
  (pre-existing behavior shared with blame, unchanged at runtime) may still
  attempt per-blob fetches. Comments now state the bound covers the refresh
  path only, and openAttributionStore documents that keeping the fetcher is
  deliberate (straggler healing after a genuine refresh).
- The authoritative-checkpoint-remote rule had no test on the real seam: a
  configured checkpoint remote whose resolution fails (no network needed) now
  pins that its failure surfaces as 'checkpoint remote fetch failed' with no
  origin fallback masking and no unfounded 'not pushed' claim.
…emote tests (audit round 4)

- remote.FetchURL silently returns the ORIGIN URL when the configured
  checkpoint remote's URL cannot be derived (unparseable origin, unknown
  provider) — a 'successful' refresh could then have hit origin, faking the
  evidence behind the not-pushed wording. fetchAttributionMetadata now detects
  that resolution fallback (resolved URL == origin URL) and treats it as a
  refresh failure, so no claim is made about a remote never reached.
- The real-seam tests cleared of environment dependence: with
  ENTIRE_CHECKPOINT_TOKEN set, FetchURL derives a live github.com URL with no
  git remote and the tests would fetch over the network with the environment's
  token. Both now clear the token (t.Setenv), keeping them fast and hermetic.
…nd 5)

The round-4 guard inferred FetchURL's origin fallback from string equality
with the origin URL — unsound in both directions: the token path rewrites the
origin URL to its HTTPS form before falling back (so the fallback never
string-matches and a token-based CI run could still fabricate not-pushed
evidence from an origin fetch), and a checkpoint_remote legitimately naming
the origin repo derives a byte-identical URL (so a genuine derivation was
misreported as a fallback, permanently disabling the refresh for that
configuration).

Replace the heuristic with an explicit signal: FetchURLWithSource returns
whether the URL was derived from the configured checkpoint_remote (mirroring
PushURL's boolean); FetchURL delegates. fetchAttributionMetadata now requires
usedCheckpointRemote=true before treating a configured-remote refresh as
authoritative.

Pins: remote-package table test for the source bool (derived, equal-URLs-
legit, token-fallback, no-config, token-derived); attribution-level hermetic
test that the token-path origin fallback surfaces as a refresh failure rather
than fake not-pushed evidence.
…(audit round 6)

- fetchAttributionMetadata resolved the authority up to four times (Configured,
  FetchURLWithSource, then the fetch helper's own Configured + FetchURL): any
  divergence between reads — a transient settings failure, a concurrent
  settings write — could route the fetch to origin while attribution recorded
  an authoritative checkpoint-remote refresh. Now: load settings once, branch
  on GetCheckpointRemote from that load, resolve the URL once via
  FetchURLWithSource, and fetch THAT verified URL directly
  (strategy.FetchMetadataBranch), so the fetched URL is by construction the
  one whose source was verified.
- A settings-load failure is now a refresh failure (honest 'remote refresh
  failed' wording) instead of being silently mapped to 'no checkpoint remote',
  which would have promoted origin to the evidence source.
- Stale render comment ('the remote fetch has failed') updated: the refresh
  may also have succeeded against a remote that lacks the data.
…ch suggestion (audit round 7)

- A retry rejected as worse whose failure is NOT genuine absence (corrupt
  fetched pack, cancellation) proves nothing about the remote — the reason no
  longer speculates that the metadata branch 'may have been pushed without'
  the session records, and the retry's error is surfaced instead of dropped
  (mirroring the evidence-gating the whole-checkpoint miss already had).
- suggestCheckpointFetchCommand no longer tells the user to fetch from origin
  when a configured checkpoint remote's URL could not be derived — the exact
  contradiction of the authoritative-remote policy the refresh just refused.
  It uses the explicit source signal and points at the checkpoint_remote
  configuration instead.
…audit round 8)

suggestCheckpointFetchCommand gated on remote.Configured, which collapses a
settings-load failure to 'not configured' — so a corrupt .entire/settings.json
produced a reason that simultaneously said the refresh refused origin (the
authoritative remote is unknowable) and told the user to 'git fetch origin'.
Load settings directly: on failure, point at the unreadable settings file
(whose checkpoint_remote determines the correct remote) instead of
recommending origin.
…prose (audit round 9)

- Two reason tests resolved the fetch suggestion against the developer's real
  CWD (settings.json + git remotes) — a corrupt local settings.local.json made
  one fail spuriously. Both now run in a hermetic temp repo, matching the
  file's own conventions.
- suggestCheckpointFetchCommand now returns (suggestion, isCommand): the
  prose fallbacks (unreadable settings, underivable checkpoint-remote URL) are
  no longer rendered behind a 'Run:' prefix or inside a pasteable command
  block, which produced 'Run: fix .entire/settings.json …, then re-run; …
  Then re-run …' style output. Prose composes as prose; commands keep the
  Run:/command-block rendering.
…ility parity (audit round 10)

Rebased onto main, which added the git-refs per-checkpoint backend. Two
integration hazards fixed:

- openAttributionStore now opens the refreshed store with the same RefFetcher
  the initial store has (plus the BlobFetcher) — without it, a post-refresh
  retry on a git-refs-backend repo silently lost on-demand per-checkpoint ref
  fetching and could read strictly worse than the first pass.
- The strong miss evidence ('the remote's entire/checkpoints/v1 does not
  contain it' / 'not on the remote') is now gated on the primary backend being
  the git-branch backend: the v1-branch refresh proves nothing about
  per-checkpoint refs, so refs-backend misses get neutral wording that says
  why the evidence is weaker. Backend topology is memoized per resolver.

Also: TestRefreshMetadataStoreRunsAtMostOnce gets the same hermetic-CWD
treatment its sibling suggestion-path tests received in round 9.
…audit round 11)

Round 10's backend gate made missReason consult the checkpoint topology
(ENTIRE_CHECKPOINTS_PRIMARY env, else CWD settings), which the evidence-wording
tests inherited from the ambient environment: under
ENTIRE_CHECKPOINTS_PRIMARY=git-refs, KeepsLocalWhenRefreshReadsWorse failed
deterministically (neutral refs wording instead of the asserted git-branch
claim) and siblings silently pinned a different branch than their names claim.
Pin t.Setenv(EnvCheckpointsPrimary, "git-branch") in every test that asserts
backend-gated wording, and add the refs-backend twin of the worse-retry case
(neutral wording, no 'not on the remote' claim), which had no coverage.
…ls to neutral (audit round 12)

- primaryBackendIsRefs now distinguishes 'config unreadable' from a known
  backend: an unreadable checkpoints config supports neither backend's claim,
  so such misses get generic neutral wording instead of the strongest
  git-branch claim (the previous failure direction overclaimed — inconsistent
  with the branch's own evidence discipline). The v1-speculation catch-all is
  likewise gated on a KNOWN git-branch backend.
- On a KNOWN git-refs-primary repo, a failing v1-branch fetch is now a soft
  outcome: the branch is not that backend's primary mechanism (a refs-only
  remote may not carry it), and the reopened store's per-checkpoint RefFetcher
  does the real work — previously every miss in the run surfaced 'remote
  refresh failed: … entire/checkpoints/v1 …' plus a suggestion guaranteed to
  fail for the same reason.

Pins: refs-primary + failing branch fetch (real fetch seam, hermetic repo with
no origin) yields the refs wording with no 'remote refresh failed'; an invalid
checkpoints block yields neutral wording with neither backend's claim.
…audit round 13)

The round-12 commit accidentally swept in-progress why-TUI wiring into this
branch while its defining file stayed untracked — the committed tree did not
compile (undefined: runWhyTUI / whyMarkerLegend); local builds passed only via
the untracked working-tree file. The TUI belongs to its own branch: the --tui
flag, dispatch, repo-name helper, and legend line are removed here.

Also, softened refreshes now stay honest: the soften decision moved out of the
fetch seam into refreshMetadataStore, deciding via the SAME memoized backend
gate missReason uses (one config resolution — no TOCTOU between the soften and
evidence gates), and a soft refresh records refreshSoft so every wording says
'a metadata refresh was attempted (the v1 branch could not be fetched...)'
instead of claiming the branch was refreshed. Pinned in the refs-primary
soft-fetch test.
…atch-all (audit round 14)

Round 13's move of the soften decision into refreshMetadataStore accidentally
widened its scope to EVERY refresh error: on a refs-primary repo, settings-read
failures, URL-derivation failures, and the authority-guard rejection (the
token-path evasion this branch hardened against) were silently softened behind
a false 'the v1 branch could not be fetched' description. A sentinel now marks
the genuine v1-branch fetch step; only errors carrying it are softened —
config and policy failures stay hard on every backend, pinned by a refs-primary
authority-guard test.

The sessions-miss catch-all also now words a soft refresh honestly
('refresh was attempted', never 'after refreshing'), pinned by a soft-refresh
sessions-miss test on the real fetch seam.
…fs-aware suggestion, env-hermetic tests (audit round 15)

An exhaustive 8-lens, loop-until-dry audit (full missReason state-machine
enumeration, adversarial scenario construction, both-backend test matrix)
confirmed the following; all fixed:

- Soft-refresh honesty completed: the rejected-retry wording no longer calls
  the store 'refreshed' in the same sentence that admits the v1 branch was not
  fetched; the reopen debug log is soft-aware; and a soft refresh whose store
  reopen ALSO fails keeps the swallowed fetch error as diagnostic context.
- Refs-aware recovery: a refs-primary no-refresh miss now suggests fetching
  the per-checkpoint refs namespace instead of the v1 branch that cannot heal
  it (suggestCheckpointFetchCommandForRefspec; the v1 wording is unchanged for
  the git-branch backend and for attach).
- Env hermeticity closed: newAttributionRepo pins the git-branch backend by
  default (tests wanting git-refs override after the call), the two remaining
  backend-sensitive tests pin explicitly, and the blame no-fetch contract is
  now non-vacuous (seam traps fail the test if fetchOnMiss=false ever fetches).
- suggestCheckpointFetchCommand's four outcomes pinned directly (derived
  command / origin command / unreadable-settings prose / underivable-URL
  prose); overstated 'exactly once' comment corrected; misleading pin-rationale
  comment fixed.

Verified under hostile environments: full attribution suite green with
ENTIRE_CHECKPOINTS_PRIMARY=git-refs and with a leaked ENTIRE_CHECKPOINT_TOKEN.
Copilot AI review requested due to automatic review settings July 2, 2026 19:50
@suhaanthayyil suhaanthayyil requested a review from a team as a code owner July 2, 2026 19:50
Adds an opt-in TUI for 'entire why <file>' and the readability fixes insiders
asked for, built under the Agent-Safe CLI Fallbacks rules:

- --tui opens a master-detail Bubble Tea viewer over the already-resolved
  attribution (no git/network I/O inside the TUI): file lines with
  [AI]/[MX]/[HU] tags on the left; the selected line's full explanation on the
  right (authorship sentence, commit, agent/model, session with an OSC 8
  entire.io hyperlink when origin resolves, checkpoint, prompt with the
  session-level caveat, intent, metadata-missing reason, candidates, explain
  hint). n/p jump to the next/previous agent-attributed line; enter expands
  the full prompt; why <file>:12 --tui opens positioned at line 12.
- Agent-safe: the TUI is opt-in AND TTY-gated (IsTerminalWriter) and skipped
  in accessible mode; non-TTY --tui falls through to the identical plain
  output, and --json always wins — both pinned by tests against a non-TTY
  writer. Nothing is TUI-only.
- Tag legend on blame, the why file view, and the TUI footer: [AI] all agent,
  [MX] agent+human mixed, [HU] no agent, [??] uncommitted — kept within the
  blame table's 80-column budget; full sentences in the detail views.

Model-level TUI tests cover rendering, navigation, agent-line jumps, expand,
missing-metadata handling, quit keys, empty files, and tiny windows. Paging is
bound to pgup/pgdn because the shared keymap claims n/p.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes a non-determinism bug in entire why <file>[:<line>] where equivalent clones could resolve different checkpoint context depending on which metadata/session blobs were locally available. It restructures attribution’s checkpoint metadata resolution to perform a memoized, once-per-run remote refresh when local metadata reads are incomplete, and improves miss reasons so users can distinguish “not fetched”, “fetch failed”, and “remote lacks checkpoint”.

Changes:

  • Added an explicit FetchURLWithSource API so callers can reliably tell whether the fetch URL came from checkpoint_remote vs an origin fallback.
  • Reworked attribution checkpoint context reads to detect incomplete local reads (including partially/fully unreadable session records), perform at-most-once durable refresh + repo reopen, and emit deterministic MetadataMissingReason.
  • Updated attach/error guidance to treat fetch suggestions as either a pasteable command or prose (when settings/URL derivation prevents a safe command), and added a comprehensive regression test suite for the refresh behavior.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
cmd/entire/cli/checkpoint/remote/util.go Adds FetchURLWithSource and routes FetchURL through it to expose a sound “source” signal.
cmd/entire/cli/checkpoint/remote/util_test.go Adds coverage for the new source-signal behavior, including token rewrite and “checkpoint remote equals origin” cases.
cmd/entire/cli/attribution.go Implements memoized remote refresh + store reopen, stronger evidence-gated miss reasons, and debug logging for diagnosis.
cmd/entire/cli/attribution_test.go Extends reader stubs and pins default backend env to keep attribution tests hermetic.
cmd/entire/cli/attribution_refresh_test.go Adds a comprehensive suite validating determinism, refresh semantics, evidence gating, and error messaging.
cmd/entire/cli/attach.go Improves missing-checkpoint guidance by distinguishing command vs prose suggestions and introduces branch-vs-refs refspec suggestions.

Comment thread cmd/entire/cli/attach.go
The [AI]/[MX]/[HU] tags are a per-commit inference, not a per-line truth:
[AI] means the commit's checkpointed work was fully agent-authored, [MX]
means the commit mixed agent and human edits (any given line may be either),
[HU] means no agent checkpoint. The legend now says so explicitly —
'per commit: [AI] all agent · [MX] mixed — line may be either · [HU] no
agent' — instead of reading like per-line labels. [??] moved to the
conditional marker legend line (shown only when uncommitted lines exist),
keeping the main legend inside the blame table's 80-column budget.
@suhaanthayyil suhaanthayyil changed the title fix(attribution): deterministic checkpoint metadata resolution across clones fix(attribution): deterministic why metadata + interactive viewer + honest tag legend Jul 2, 2026
The rule people do not guess: a single human edit anywhere in a commit turns
every line of it [MX] — [AI] appears only for fully agent-authored commits.
The blame and why legends now carry an explicit second note line saying so:
'note: [AI] requires a fully agent commit; one human edit turns all lines
[MX]'. Within the blame table's 80-column budget.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants