feat(source-control): surface babysit bot-to-personal identity fallback as an attribution-drift finding (#450)#606
Conversation
…ck as an attribution-drift finding (#450) Adds an `attribution_drift` reconciliation arm to the babysit-prs snapshot engine. For each write the mutation ledger recorded performing, it verifies the landed timeline author is the configured intended write-identity, not merely some accepted self-login. A recorded write that landed under a different self-login -- the canonical case being a bot write-identity that degraded to the operator's personal login when a token mint failed -- becomes a first-class material finding on the affected PR's cycle-status line instead of drifting silently. It is the complement of `foreign_activity` (which reconciles same-login events the ledger cannot account for) and is mutually exclusive with it per comment; unlike `foreign_activity` it reports without suppressing dispatch, since the PR is still ours to babysit. Configured via the new `babysit_intended_write_identity` userConfig key, threaded as `--intended-write-identity` to the snapshot; absent it, the arm is dormant. This is pure plugin-side authorship verification: the token-generation root cause is a cross-repo concern (medley `gh-bot.sh`) and no change there is needed for the finding to fire. Coverage is bounded to the write class the ledger records with a recoverable author (review-trigger comments); drift on reactions, classification replies, and branch pushes awaits ledgering their identifiers with authorship. Closes #450. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 2m 44s —— View job PR Review
The approach is well-designed and the implementation is correct.
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4e6a967be3
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…it scope `build_snapshot`'s `--pr owner/repo#N` path skipped the self-login resolution the discovery path performs, so `self_logins` derived from raw `--author @me,...` with `@me` dropped — leaving the resolved personal identity absent. The same-login classification arms (`foreign_activity` and the new `attribution_drift`) require the landed author to be in `self_logins`, so a bot-to-personal fallback was silently ignored for every single-PR run, even though `--pr` scope is a documented invocation. Hoist the `@me` resolution above the scope branch so it runs for both single-PR and discovery scopes; the single resolution point also removes the former duplication. Adds a regression test asserting a `--pr` run resolves the authenticated login into the self-login set. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 2m 53s —— View job PR Review
New commit assessment —
|
…arrow approval-downgrade docs; bump 0.12.0 Composes main's source-control 0.11.0 (#606 attribution_drift arm) with this PR's Approve-with-nits classification fix — both sides preserved. Conflict resolutions: - plugin.json: version 0.9.4/0.11.0 -> 0.12.0 (minor from current main; this PR changes a userConfig key's semantics, and repo convention treats behavior/contract changes as minor). userConfig kept both #606's babysit_intended_write_identity and this PR's approval-downgrade keys. - CHANGELOG.md: this PR's entry re-headed [0.9.4] -> [0.12.0], placed atop #606's [0.11.0]. Thread 2 (docs overstate the knob, #499 decision): narrowed all six doc surfaces for babysit_approval_downgrade_logins to the code's actual reach (README cell, SKILL cell, FeedbackConfig docstring, --approval-downgrade-logins help, CHANGELOG entry, plugin.json userConfig description). The knob only elevates to material an approval whose body carries blocking-looking prose that parses as an approval verdict — the one case the structural downgrade routes; a real APPROVED-state review or a plain clean approval is ignored regardless. No change to collect_feedback logic. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Eu6ZSEy8ptQjCXbhbjBKpo
…t review as non-blocking (#499) (#567) ## Summary Babysit snapshot misclassified an Approve-with-nits bot review: a clean approval delivered as an issue comment (the #499 claude[bot] shape) was treated as blocking feedback, stalling merge-ready classification. This PR makes the approval downgrade structural and narrows every doc surface to the code's actual behavior. Composed atop the #606 attribution-drift arm (source-control 0.11.0) as **0.12.0**. ## Fix - Structural approve→ignored: an explicit approval verdict with no surviving CRITICAL/IMPORTANT or required-fix marker routes to `ignored` for ANY bot — no login configuration required. - `babysit_approval_downgrade_logins` KEPT with repurposed semantics (decision logged on #499): a configured login's approval-carrying-blocking-prose routes to `material` instead of `ignored`. Reach documented narrowly: fires only for an approval whose body carries blocking-looking prose; never for a formal APPROVED-state review (pre-existing short-circuit, see #621) or a plain clean approval. - Severity guards: `BLOCKING_SEVERITY_RE` (case-sensitive CRITICAL|IMPORTANT) + `has_blocking_severity` on both `approval_downgrade` AND `skip_downgrade` (Codex P2 — a configured skip comment carrying `CRITICAL:` stays blocking); negated-marker handling; "request(s|ing) changes" added to `BLOCKING_TEXT_RE`. - Six doc surfaces narrowed identically (README, SKILL, plugin.json userConfig description, CLI help, FeedbackConfig + approval_downgrade docstrings); APPROVED-state caveat cites #621. ## Verification - Full engine suite green at head 348d3fa: 236 unittests OK, ruff clean, guarded-wrapper bash checks pass; regression tests added for the skip_downgrade severity guard and the structural approve path. - All 16 CI checks green; 5/5 review threads resolved (thread 1 → #594 spin-out; thread 2 → docs narrowed; P2 → 69a3560; P1 → #621 defer per the no-false-guarantees rule). - Compose integrity verified at merge digest: `babysit_delta.py` + delta tests byte-identical to main's #606 arm; this PR stacks cleanly on 0.11.0. Closes #499 ## Related - #499 (source issue; config decision logged there 2026-07-20) - #594 (advisory-P2-under-approval routing policy — thread-1 spin-out) - #621 (APPROVED-state severity-scanning design question — P1 defer, #534 space) - #606 / #450 (attribution-drift arm this PR is composed atop) - #595 (superseded by the narrowed docs — closing separately with evidence) --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Summary
On a safe-tier worker run against
melodic-software/medley, the consumer's identity wrapper reported "Bot token generation failed, using personal identity" and every write silently landed authored as the personal user. The fallback was local-log-only: nothing reached the babysit orchestrator or the PR, so attribution drifted with no signal unless a human read the worker transcript.Per the maintainer decision on #450, the token machinery itself is sound where creds are bound — the medley-session failure is environment/scope-specific (unbound creds or the App not installed on that repo), owned by medley (
tools/github-auth/gh-bot.sh) and tracked separately. This repo's actionable fix is observability: make the silent bot→personal degrade a first-class, orchestrator-visible finding. Nogh-bot.shchange is required for the finding to fire.Change
Adds an
attribution_driftreconciliation arm to the babysit-prs snapshot engine, extending the existing ledger↔timeline reconciliation with an intended-write-identity dimension:babysit_self_loginsdefines acceptable-as-mine, not intended-as-author. This introduces the intended-write-identity notion via a newbabysit_intended_write_identityuserConfig key (threaded as--intended-write-identityto the snapshot).foreign_activityuses), naming the drift and the affected PR on that PR's status line. No new silent-log-only path.foreign_activity, not a change to it. That arm reconciles same-login timeline events the ledger cannot account for; this reconciles the writes it did record. They are mutually exclusive per comment (in-ledger vs not-in-ledger), so the new dimension is purely additive. Unlikeforeign_activity, drift reports without suppressing dispatch — the PR is still ours to babysit; only a past write's authorship is wrong.Coverage is bounded to the write class the ledger records with a recoverable author (review-trigger comments); drift on reactions, classification replies, and branch pushes is out of reach until the ledger records their identifiers with authorship (see deferred follow-up below).
Docs updated:
SKILL.md(config table row + snapshot invocation threading),reference/orchestration.md(the arm, and why it reports rather than suppresses),CHANGELOG.md(0.11.0). Version bumped0.10.0→0.11.0.Verification
test_babysit_delta.py(AttributionDriftTests): dormant-without-intended, detects-drift, no-drift-when-intended, unledgered-write-is-not-drift, and the acceptance path — the finding lands inmaterial_findingsviaclassify_prwith nogh-bot.shin the loop (trivially true; there is nogh-bot.shin this repo).ruffclean, guarded-wrapper behavior checks pass.markdownlint-cli2andtyposclean on all changed markdown;git diff --checkclean.Deferred follow-up (not filed)
attribution_driftdetection to those write classes — closing the coverage gap this PR's review-trigger-only reconciliation leaves.Closes #450.
Related
tools/github-auth/gh-bot.sh— the token-generation root cause (bot token mint failing and falling back). Cross-repo, owned by and tracked against medley; no change here — this PR only makes the drift visible.🤖 Generated with a Claude Code implementation subagent (issue #450)