Skip to content

v0.4.11 — latent drift fix (range-diff sweep + distinct counters)#17

Merged
jinhongkuan merged 1 commit into
mainfrom
chore/bump-v0.4.11
Apr 15, 2026
Merged

v0.4.11 — latent drift fix (range-diff sweep + distinct counters)#17
jinhongkuan merged 1 commit into
mainfrom
chore/bump-v0.4.11

Conversation

@jinhongkuan

@jinhongkuan jinhongkuan commented Apr 15, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes a class of "invisible drift" where decisions silently went stale because `link_commit` only swept files in HEAD's own diff. After a gap of N commits without a bicameral invocation, drift introduced by intermediate commits stayed hidden until someone happened to re-edit the same files.

Two changes:

  1. Range-diff sweep. `ingest_commit` now diffs `last_synced..HEAD` via `git diff --name-only` and sweeps every file in the range. New `sweep_scope` field on `LinkCommitResponse` reports `head_only` (first sync, or fallback) vs `range_diff` (default after first sync) vs `range_truncated` (range exceeded 200-file cap). Falls back to head-only when the base SHA is unreachable. New `range_size` field reports how many files were swept.

  2. Distinct intent counters. `decisions_drifted` and `decisions_reflected` now count distinct intent_ids that flipped, not (region, intent) pairs. Witnessed on the Accountable demo where one Google Calendar decision flipped 4 regions and the old counter reported `decisions_drifted=4` while only 1 distinct intent was actually drifted.

Both changes were teed up in conversation after the Phase 2 drift demo on Accountable surfaced the per-region count discrepancy and the latent-drift mental model.

Test plan

  • 11 cases in `tests/test_v0411_latent_drift.py` covering:
    • `get_changed_files_in_range` helper (in-range, empty range, unreachable base)
    • `sweep_scope` semantics (first sync = head_only, second sync after gap = range_diff, fast-path = head_only with size 0, unreachable cursor = head_only fallback)
    • Distinct intent counter dedup (one decision with N drifted regions = 1 in counter)
  • Full v0.4.11 regression: 163 passed in 14s
  • Manual: run on Accountable to verify latent drift surfaces correctly

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Enhanced commit synchronization now scans the full range from last sync to current HEAD, surfacing drift previously undetected during gaps
    • Added response metadata reporting scan scope and file count swept
  • Bug Fixes

    • Fixed overcounting of drift and reflection metrics by deduplicating based on intent rather than individual region changes
  • Tests

    • Added comprehensive regression test suite validating latent drift detection and range-diff functionality

Fixes a class of "invisible drift" where decisions silently went
stale because link_commit only swept files in HEAD's own diff.
After a gap of N commits without a bicameral invocation, drift
introduced by intermediate commits stayed hidden until someone
happened to re-edit the same files.

Two changes:

1. Range-diff sweep. ingest_commit now diffs last_synced..HEAD via
   `git diff --name-only` and sweeps every file in the range. New
   sweep_scope field on LinkCommitResponse reports head_only (first
   sync, or fallback) vs range_diff (default after first sync) vs
   range_truncated (range exceeded 200-file cap). Falls back to
   head-only when the base SHA is unreachable (force-push, shallow
   clone). New range_size field reports how many files were swept.

2. Distinct intent counters. decisions_drifted and decisions_reflected
   now count distinct intent_ids that flipped, not (region, intent)
   pairs. Witnessed on the Accountable demo where one Google Calendar
   decision flipped 4 regions and the old counter reported
   decisions_drifted=4 while only 1 distinct intent was actually
   drifted. Fix: dedupe by intent_id via sets; counters now match
   what users mentally expect from "how many decisions just changed
   status."

Both changes were teed up in conversation after the Phase 2 drift
demo on Accountable surfaced the per-region count discrepancy and
the latent-drift mental model.

Tests: 11 cases in tests/test_v0411_latent_drift.py covering the
range-diff helper directly (in-range files, empty range, unreachable
base), the sweep_scope semantics (first sync = head_only, second
sync after gap = range_diff, fast-path = head_only with size 0,
unreachable cursor = head_only fallback), and the distinct-intent
counter dedup. Full v0.4.11 regression: 163 passed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@jinhongkuan jinhongkuan merged commit 18e7dc8 into main Apr 15, 2026
@jinhongkuan jinhongkuan deleted the chore/bump-v0.4.11 branch April 15, 2026 02:55
@coderabbitai

coderabbitai Bot commented Apr 15, 2026

Copy link
Copy Markdown

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3129ad88-9be3-4cac-a7eb-e79f875d8274

📥 Commits

Reviewing files that changed from the base of the PR and between 96abd45 and fa1804d.

📒 Files selected for processing (7)
  • CHANGELOG.md
  • contracts.py
  • handlers/link_commit.py
  • ledger/adapter.py
  • ledger/status.py
  • pyproject.toml
  • tests/test_v0411_latent_drift.py

📝 Walkthrough

Walkthrough

The pull request extends the git synchronization system to sweep files across the full last_synced..HEAD commit range instead of only the current HEAD commit, surfacing latent drift during sync-cursor lag periods. It introduces new metadata fields (sweep_scope, range_size) to report sweep provenance, adds a range-diff utility function, and deduplicates decision counters by distinct intent identifiers rather than per-region flips.

Changes

Cohort / File(s) Summary
Configuration & Package Metadata
pyproject.toml
Version bump from 0.4.10 to 0.4.11.
Type Definitions
contracts.py
Updated LinkCommitResponse with new fields sweep_scope (literal enum) and range_size (int); redefined decisions_reflected and decisions_drifted semantics from per-flip counts to distinct intent-ID flips.
Git Utilities
ledger/status.py
Added get_changed_files_in_range(base_sha, head_sha, repo_path) function that runs git diff --name-only base..head and returns None sentinel on failure to enable safe fallback to HEAD-only scanning.
Sync Implementation
ledger/adapter.py
Refactored ingest_commit() to use cursor-based range sweeps by default; added range capping (200 file limit), fallback to HEAD-only on range failure, decision dedup via intent-ID sets, and sweep-scope reporting (head_only, range_diff, range_truncated).
API Handler
handlers/link_commit.py
Populated sweep_scope and range_size fields on returned LinkCommitResponse from adapter result payload.
Documentation & Tests
CHANGELOG.md, tests/test_v0411_latent_drift.py
Added changelog entry and comprehensive regression test suite validating range-diff behavior, latent-drift discovery, fallback handling, and distinct-intent deduplication logic across multiple scenarios.

Sequence Diagram(s)

sequenceDiagram
    actor Client
    participant Handler as handle_link_commit
    participant Adapter as SurrealDBLedgerAdapter
    participant GitStatus as ledger/status
    participant Git as git (subprocess)
    
    Client->>Handler: link_commit(commit_hash)
    Handler->>Adapter: ingest_commit(commit_hash)
    
    Adapter->>Adapter: read last_synced_commit from sync state
    
    alt Range cursor exists & not empty
        Adapter->>GitStatus: get_changed_files_in_range(last_synced, commit_hash)
        GitStatus->>Git: git diff --name-only last_synced..commit_hash
        
        alt git diff succeeds
            Git-->>GitStatus: file list
            GitStatus->>GitStatus: check file count vs 200 limit
            
            alt file count <= 200
                GitStatus-->>Adapter: file list (sweep_scope=range_diff)
            else file count > 200
                GitStatus-->>Adapter: truncated list (sweep_scope=range_truncated)
            end
        else git diff fails/times out
            Git-->>GitStatus: error
            GitStatus-->>Adapter: None (sentinel)
            Adapter->>GitStatus: get_changed_files(commit_hash) [fallback]
            GitStatus->>Git: git show --name-only commit_hash
            Git-->>GitStatus: file list
            GitStatus-->>Adapter: file list (sweep_scope=head_only)
        end
    else First sync or empty cursor
        Adapter->>GitStatus: get_changed_files(commit_hash)
        GitStatus->>Git: git show --name-only commit_hash
        Git-->>GitStatus: file list
        GitStatus-->>Adapter: file list (sweep_scope=head_only)
    end
    
    Adapter->>Adapter: process files & compute decision flips (by intent_id dedup)
    Adapter->>Adapter: construct LinkCommitResponse with sweep_scope & range_size
    Adapter-->>Handler: response payload
    
    Handler->>Handler: populate sweep_scope & range_size on LinkCommitResponse
    Handler-->>Client: LinkCommitResponse
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Poem

🐰 A sweep across the git-time span,
No drift shall hide—we have a plan!
From last_synced to HEAD we run,
Intent dedup when flips are done,
Latent shadows now bring light,
Version bumped to point-four-one-one—all right! 🌙✨

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch chore/bump-v0.4.11

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Knapp-Kevin added a commit to Knapp-Kevin/bicameral-mcp that referenced this pull request Apr 29, 2026
…attempt

GATE TRIBUNAL entry for plan-48-pre-push-drift-hook.md. Verdict:
PASS first-attempt — no remediation cycle needed. Chain hash
bf890347 extends from BicameralAI#16 (BicameralAI#44 SEAL, 567170e0) on dev.

Three non-blocking observations recorded:
- O1: run_setup parameter-name cosmetic nit (functionally fine).
- O2: latent post-commit-hook bug — bicameral-mcp link_commit HEAD
  is not a registered subcommand. Recommend separate issue. Out
  of scope for BicameralAI#48.
- O3: two-renderer modules (cli/drift_report.py for PR comments
  vs cli/branch_scan.py for terminal hooks) accepted as parallel
  non-duplication; different output formats and exit semantics.

SG-PLAN-GROUNDING-DRIFT instance BicameralAI#4 prevented — first plan this
session where author-side grounding mitigation worked rather than
audit-side catching. Issue BicameralAI#114 (CI lint enforcement) remains the
durable countermeasure.

Plan PASS at 79abcc2; chain to /qor-implement.
Knapp-Kevin added a commit to Knapp-Kevin/bicameral-mcp that referenced this pull request Apr 29, 2026
Phase 0 — branch-scan CLI subcommand:
- cli/branch_scan.py (177 LOC): pure-function render_terminal_summary
  + main() CLI entry. Lazy-imports handle_link_commit; graceful skip
  when no ~/.bicameral/ledger.db. Exit codes 0/1/2 documented in
  module header.
- server.py: add `branch-scan` subparser to cli_main, dispatch to
  cli.branch_scan:main.
- tests/test_branch_scan_cli.py (144 LOC, 7 tests): renderer shape +
  exit-code matrix (no drift, drift+block-env, drift+non-TTY, etc.)

Phase 1 — setup_wizard pre-push hook:
- setup_wizard.py: new _GIT_PRE_PUSH_HOOK constant + _install_git_pre_push_hook
  function modeled on _install_git_post_commit_hook. Idempotent install
  with append-on-existing-hook-without-bicameral semantics.
- run_setup() gains keyword-only `with_push_hook: bool = False`. Setup
  wizard step 7b conditionally installs the hook.
- server.py setup_parser gains `--with-push-hook` flag.
- tests/test_setup_pre_push_hook.py (92 LOC, 5 tests): fresh-install,
  idempotent re-install, append-when-non-bicameral, no-git-root path,
  executable-bit (POSIX-only).

Phase 2 — docs:
- CHANGELOG.md [Unreleased] entry
- docs/guides/pre-push-drift-hook.md (129 LOC): What/When/Quickstart/
  Reference/Pitfalls/See-also user guide

Validation:
- 11 new tests + 8 BicameralAI#49 regression = 19/20 green (1 Windows-only chmod
  test skipped on this platform)
- ruff check + format: clean on all 5 changed/new source files
- mypy on cli/branch_scan.py: no issues
- End-to-end smoke: `bicameral-mcp branch-scan` dispatches via cli_main
  → cli.branch_scan.main → graceful skip with exit 0 (no ledger)

Razor: cli/branch_scan.py 177 LOC (≤250); all entry funcs ≤25 LOC;
helpers ≤20 LOC; nesting ≤2; zero nested ternaries.

Maintainer-locked design (audit-PASS at META_LEDGER BicameralAI#17, chain
bf890347): Q1 cli/ placement, Q2 deliberate non-modeling on possibly-
broken post-commit-hook predecessor, Q3 HEAD-only v1, Q4-Q6 TTY/skip
behaviors all per plan.

Audit's separate-issue recommendation (post-commit hook command not
registered as CLI subcommand) NOT addressed in this PR — out of scope.

Closes BicameralAI#48

Plan: plan-48-pre-push-drift-hook.md (audit PASS, chain hash bf890347).
Implementation chains to seal in /qor-substantiate.
Knapp-Kevin added a commit to Knapp-Kevin/bicameral-mcp that referenced this pull request Apr 29, 2026
Substantiation seal for plan-48-pre-push-drift-hook.md (Issue BicameralAI#48,
audit PASS at META_LEDGER BicameralAI#17, chain hash bf890347).

Verification gates (10 of 12 passed; 2 advisory skipped per
capability shortfalls):
- Reality vs Promise: ✓ all 4 new + 3 modified files exist; zero
  plan deviations
- Test audit: 27/28 (11 new + 16 regression on PR BicameralAI#113 drift_report
  tests; 1 chmod test skipped on Windows non-POSIX)
- Razor final: cli/branch_scan.py 177 LOC (≤250); entry funcs ≤25;
  helpers ≤20; nesting ≤2; zero nested ternaries
- Skill file integrity: N/A (no MCP tool changes)
- SYSTEM_STATE.md synced
- Merkle seal computed: eacc6f89f707ce958fa2485177c9706808fdfeb32
- Step 4.6 reliability sweep: skipped (qor/reliability/ absent)
- Step 7.5 version bump: skipped (per maintainer direction)

This is the first implementation in the session with ZERO plan
deviations — plan was thorough enough that implementation was
direct. Pairs with the prior turn's first-attempt audit PASS
(Entry BicameralAI#17). Both ends of the QOR cycle clean.

Audit's separate-issue recommendation (post-commit hook command
not a registered subcommand) tracked but NOT addressed in this PR
— separate workstream.

Chain: 18 entries on this branch; integrity VALID. Next:
/qor-document.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant