Skip to content

Establish single total order for commit lineage; list newest-first#383

Merged
ragnorc merged 5 commits into
mainfrom
claude/commit-list-branch-eval-8wtibx
Jul 25, 2026
Merged

Establish single total order for commit lineage; list newest-first#383
ragnorc merged 5 commits into
mainfrom
claude/commit-list-branch-eval-8wtibx

Conversation

@ragnorc

@ragnorc ragnorc commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

What & why

This change establishes a single canonical total order for commit lineage — (manifest_version, created_at, graph_commit_id) — and makes it the foundation for all ordered views of commits. The public list_commits API now returns commits newest-first (reversed from the internal ascending order), making the most recent commit the first element. This aligns the public contract with user expectations and prepares the foundation for future keyset-based pagination.

Key changes:

  1. Extract lineage ordering to GraphCommit::lineage_key(): A new method returns the canonical (u64, i64, &str) tuple that defines commit order. All comparisons now derive from this single source of truth.

  2. Simplify internal sorting: CommitGraph::load_commits() now uses lineage_key() instead of inline multi-field comparisons. The internal projection remains ascending (oldest-first).

  3. Add latest_commit_matching(): A new internal helper finds the maximal commit satisfying a predicate, using lineage_key() for comparison. This replaces imperative iteration patterns (e.g., in recovery code).

  4. Reverse public listing: Omnigraph::list_commits() now reverses the internal ascending order before returning, presenting newest-first to all consumers (CLI, HTTP server, SDK).

  5. Update head selection logic: should_replace_head() now uses lineage_key() comparison, ensuring head selection follows the same total order.

  6. Update documentation and tests: API docs clarify the newest-first contract and keyset-pagination intent. Tests verify the ordering invariant and branch-specific listing behavior.

Backing issue / RFC

Checklist

  • Change is focused (one logical change: establish single total order and reverse public listing)
  • Tests added/updated for behavior changes
    • New test block in lineage_projection.rs verifies public listing is strictly descending by lineage_key
    • Updated maintenance.rs and auth_policy.rs to use .first() instead of .last() (newest-first)
    • Added parity test for commit list --branch in CLI
  • Public docs updated if user-facing surface changed
    • Updated omnigraph-server handler docs to clarify newest-first ordering and keyset-pagination intent
    • Updated CLI reference and branching guide to document listing order and --branch semantics
    • Updated OpenAPI spec
  • Reviewed against docs/dev/invariants.md
    • No Hard Invariants weakened
    • Commit lineage ordering is now a single canonical source of truth (strengthens invariant)
    • Internal projection order (ascending) is decoupled from public presentation (descending)

Notes for reviewers

  • Order reversal is localized: Only Omnigraph::list_commits() reverses; internal code (CommitGraph::load_commits(), recovery, head selection) remains ascending. This keeps internal logic simple while meeting the public contract.
  • Lineage key is immutable: The tuple (manifest_version, created_at, graph_commit_id) is the single source of truth. Future pagination cursors will be keyset-based on this same order.
  • Test coverage: The new assertion in lineage_projection.rs pins the public ordering contract; existing tests updated to reflect newest-first indexing.
  • Recovery code simplified: converge_or_defer_roll_forward() now uses latest_commit_matching() instead of rfind(), making intent clearer.

https://claude.ai/code/session_01WXeTJLbMK5dHcQarn6UGFL


Note

Medium Risk
Changes the public commit-list ordering contract and HTTP policy normalization for omitted branch; impact is broad but localized to one API boundary with updated tests and docs.

Overview
Introduces GraphCommit::lineage_key() as the single total order (manifest_version, created_at, commit_id) for sorting, head selection, and future keyset pagination. Internal CommitGraph views stay oldest-first; Omnigraph::list_commits is the only public flip to newest-first for CLI, HTTP, and SDK.

GET /commits now treats an omitted branch as main for Cedar (matching snapshot behavior) and documents branch-scoped history only—no cross-branch listing. OpenAPI, user docs, and CLI reference reflect the contract.

Recovery uses latest_commit_matching instead of positional scans; tests and parity coverage move from .last() to .first() for head commits and add commit list --branch parity.

Reviewed by Cursor Bugbot for commit 7eecb2a. Bugbot is set up for automated code reviews on this repo. Configure here.

Greptile Summary

Establishes a canonical commit-lineage order and exposes public commit listings newest-first.

  • Centralizes ordering in GraphCommit::lineage_key() for sorting, head selection, and recovery lookup.
  • Reverses Omnigraph::list_commits() while retaining ascending internal projections.
  • Normalizes omitted HTTP commit-list branches to main for authorization and lookup.
  • Updates tests, CLI parity coverage, API documentation, and user documentation for the new contract.

Confidence Score: 5/5

The PR appears safe to merge with no blocking failures remaining.

No blocking failure remains.

Important Files Changed

Filename Overview
crates/omnigraph/src/db/commit_graph.rs Centralizes lineage comparison and replaces positional latest-match selection with a maximum over the same canonical key.
crates/omnigraph/src/db/omnigraph.rs Reverses the ascending internal commit projection at the public listing boundary.
crates/omnigraph/src/db/manifest/recovery.rs Replaces an ascending-list reverse search with an equivalent canonical-key maximum.
crates/omnigraph-server/src/handlers.rs Documents newest-first listing and consistently resolves an omitted branch to main before authorization and lookup.
crates/omnigraph/tests/lineage_projection.rs Pins descending public ordering, head-first behavior, and branch-specific history.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  M[Manifest lineage rows] --> C[CommitGraph]
  C -->|ascending lineage_key| I[Internal projection]
  I -->|reverse| O[Omnigraph list_commits]
  O -->|newest first| H[HTTP API]
  O -->|newest first| E[Embedded CLI and SDK]
Loading

Reviews (2): Last reviewed commit: "fix(server): authorize omitted commit-li..." | Re-trigger Greptile

claude added 3 commits July 24, 2026 15:17
…ge_key

The lineage total order previously existed as two hand-duplicated
comparators (the load_commits sort and should_replace_head) plus a
positional .rfind in recovery that silently encoded "ascending".
Introduce GraphCommit::lineage_key as the one definition, derive both
comparators from it, and replace recovery's positional scan with an
intent-named CommitGraph::latest_commit_matching so no caller couples
to iteration direction. No behavior change.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WXeTJLbMK5dHcQarn6UGFL
…nch parity row

The public listing assertions fail against the current ascending order
with the predicted symptom (first element is not the branch head); the
next commit turns them green. The parity row covers the previously
untested `commit list --branch` remote query-param fork and is green on
both sides of the flip.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WXeTJLbMK5dHcQarn6UGFL
Omnigraph::list_commits — the one door the embedded CLI, HTTP server,
and SDK all funnel through — now returns the branch's reachable history
most recent first, matching the ordering the OpenAPI description has
promised all along (the implementation shipped ascending). The internal
projection stays ascending; recovery, merge-base, and head selection are
untouched (they no longer depend on iteration direction after the
lineage_key refactor).

Also corrects the second stale claim in the /commits description:
omitting `branch` lists main's history, not "across all branches" —
stale since RFC-013 Phase 7 retired the global _graph_commits.lance
table. Ordering is documented as part of the contract, with future
cursor/limit pagination reserved as keyset-based on the same order.

This is a change to shipped observable behavior (Hyrum's-law item in
the deny-list): consumers indexing the listing positionally must use
[0]/first() for the head where they previously used last(). Record in
the next release's notes at the release boundary.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WXeTJLbMK5dHcQarn6UGFL

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4955517ec2

ℹ️ 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".

Comment on lines +1703 to +1704
/// fork plus the branch-authored commits); omitting it returns `main`'s
/// history. There is no cross-branch listing. Ordering is part of the

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Authorize omitted commit branch as main

When a Cedar policy grants read only for protected/main branches, GET /commits without branch is now documented and implemented as main history, but the handler still builds PolicyRequest { branch: query.branch.clone(), ... }, leaving branch as None. That makes protected/unprotected branch-scope rules see context.has_branch == false and incorrectly deny the default-main request; normalize the omitted query to Some("main") before authorizing, as the snapshot/export handlers do.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Confirmed valid and fixed in 55d0052 (red test) + 7eecb2a (fix). The handler now normalizes the omitted branch to main before building the PolicyRequest, exactly as server_snapshot/export do, so a read grant with branch_scope: protected treats plain GET /commits the same as GET /commits?branch=main.

Verification: the new auth_policy.rs::policy_authorizes_omitted_commit_list_branch_as_main test reproduces the 403-vs-200 split at the test commit and asserts the omitted and explicit forms return identical bodies after the fix. Within the supported rule surface (any/protected/unprotected, all requiring has_branch when scoped), the change only ever makes the omitted form match the explicit ?branch=main outcome. The remaining branch: None reads (schema get, branch list, commit show by id) are genuinely graph-scoped and unchanged.


Generated by Claude Code

claude added 2 commits July 24, 2026 21:19
GET /commits without ?branch is documented and implemented as main's
history, so under a read grant scoped to protected branches (main
protected) it must behave exactly like the explicit ?branch=main form.
Today the handler authorizes with the raw Option (branch: None), so
has_branch=false and the protected-scoped permit never matches: the
explicit form returns 200 while the omitted form returns 403.

Red at this commit with that exact split; the next commit normalizes
the omitted branch before authorizing, as server_snapshot already does.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WXeTJLbMK5dHcQarn6UGFL
Normalize the omitted `branch` query to `main` before building the
PolicyRequest, mirroring server_snapshot and export. The handler
executes an omitted branch as main's history, but authorized it with
branch: None — and branch-scoped Cedar grants compile to
`context.has_branch && ...`, so under default-deny a read grant scoped
to protected branches permitted GET /commits?branch=main while denying
the equivalent plain GET /commits.

Within the supported rule surface (any/protected/unprotected) this only
ever makes the omitted form behave exactly like the explicit
?branch=main form. The graph-scoped reads (schema get, branch list,
commit show by id) correctly keep branch: None and are unchanged.

Turns the previous commit's red test green. Raised by PR #383 review.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WXeTJLbMK5dHcQarn6UGFL
@ragnorc
ragnorc merged commit a616d1c into main Jul 25, 2026
8 checks passed
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.

2 participants