Skip to content

fix(node): bound federated repository aggregation - #466

Open
cairn-intern wants to merge 6 commits into
Twigpine:mainfrom
cairn-intern:recreate-409-codex-fix-federated-response-bounds
Open

cairn-intern wants to merge 6 commits into
Twigpine:mainfrom
cairn-intern:recreate-409-codex-fix-federated-response-bounds

Conversation

@cairn-intern

Copy link
Copy Markdown

Summary

Bound federated repository aggregation so peer responses cannot cause unbounded
allocation, fan-out, or request duration. Return partial results with an explicit
truncated flag when a peer or aggregate budget is exceeded.

Closes #427

Changes

  • Request the existing 200-row peer page instead of the legacy unpaged route.
  • Accept at most 512 KiB and 200 repository objects from each peer.
  • Run at most four peer fetches concurrently, with timeouts covering complete response reads.
  • Limit aggregate results to 1,000 repositories and 2 MiB of serialized repository data.
  • Bound peer selection and total aggregation time; cancel outstanding work when the budget is exhausted.
  • Rate-limit federated requests to 12 per minute per IP using shared application state.
  • Report truncation and count peers that returned a valid page.

Prior reviewer feedback addressed

  • Register the peers-table writer in the ledger and its guard test.
  • Name the aggregate deadline and peer limit constants.
  • Keep the rate limiter in application state and test rejection before database access.

Test plan

  • cargo fmt --all -- --check
  • cargo check --workspace --all-targets
  • cargo clippy --workspace --all-targets -- -D warnings
  • cargo test -p gitlawb-node federated
  • Verify per-peer byte and row ceilings, aggregate budgets, bounded concurrency,
    cancellation, deadline-driven partial results, and shared rate-limit admission.

Recreated from closed PR #409 by @euxaristia (approved but unmerged). Original branch: euxaristia/node:codex/fix-federated-response-bounds

Register the bounded peer-query fixture in the writer ledger, name federation bounds, and share the state-owned limiter across routers and the cleanup sweep. Cover exhausted-bucket rejection before database access.
@coderabbitai

coderabbitai Bot commented Sep 24, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

Next included review available in 6 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Only developers with an assigned seat can use this organization's usage-based review budget, and seats here are assigned manually. Ask an admin to assign a seat, or change the review continuation mode in Billing.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: 0382a0c4-ce83-48ef-901a-0f93689a1cbc

📥 Commits

Reviewing files that changed from the base of the PR and between bfc44f9 and a7f931d.

📒 Files selected for processing (7)
  • crates/gitlawb-node/src/api/repos.rs
  • crates/gitlawb-node/src/auth/mod.rs
  • crates/gitlawb-node/src/db/mod.rs
  • crates/gitlawb-node/src/main.rs
  • crates/gitlawb-node/src/server.rs
  • crates/gitlawb-node/src/state.rs
  • crates/gitlawb-node/src/test_support.rs

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

@greptile-apps greptile-apps 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.

Your trial has ended. Reactivate Greptile to resume code reviews.

@beardthelion beardthelion added crate:node gitlawb-node — the serving node and REST API kind:bug Defect fix — wrong or unsafe behavior subsystem:identity DID/UCAN, http-sig auth, push authorization labels Sep 24, 2026

@beardthelion beardthelion left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Verified on this head: the peers query is LIMIT-bound, the per-peer body cap fires mid-stream without Content-Length, the 200-row page bound truncates rather than rejects, the aggregate deadline and concurrency cap hold, the per-IP brake rejects before any database access, and the peers-table write guard is registered. Removing each of those guards individually turns the new tests red, so the protections are real. One gap in the coverage, not the logic.

Findings

  • [P2] Pin the federated response contract end to end
    crates/gitlawb-node/src/api/repos.rs:3206
    The new envelope is exercised only at unit level: tests assert budget internals and a self-constructed JSON body, but nothing reads truncated, nodes_queried, or the node_url/node_did/local annotations from a real handler response. On this head I hardcoded "truncated": false in the handler, deleted the failed-peer aggregate.truncated = true, and dropped node_did from enrich_federated_repo; all nine federated tests stayed green in each case. Assert the fields on an actual 200 body (the route tests already drive build_router), and add one case that observes truncated == true; seeding MAX_FEDERATED_PEERS + 2 peer rows with the fixture INSERT makes that reachable without a live peer.

One process note, not a finding: the handler still scans all local repos (list_all_repos_with_stars plus the visibility-rules load) outside the aggregate deadline, so per-request work stays proportional to local repo count. That matches the standing exposure on GET /api/v1/repos, so it is not an ask here, but bounding the local page with the same LIMIT-plus-sentinel shape used for peers would finish the bound this PR starts.

Not an ask, recorded only: nodes_queried counts peers that returned a usable page, not peers contacted; an exactly-full budget reports truncated: true even when nothing was omitted; a peers-table error now fails the whole request instead of degrading to local-only results; and a compliant peer whose 200-row page exceeds 512 KiB is dropped whole rather than partially used.

…eview)

Address beardthelion's P2: the federated envelope was only exercised at
unit level, so hardcoding "truncated": false, deleting the failed-peer
truncated flag, or dropping node_did from enrich_federated_repo each left
all nine federated tests green.

Add four route tests driving build_router and asserting on the real HTTP
200 body:
- envelope fields (count, nodes_queried, truncated) and local annotations
  (node_url, node_did, local) for a seeded public repo
- truncated == true when the peer table overflows MAX_FEDERATED_PEERS
  (unreachable peer URLs, no live peer needed)
- truncated == true on the failed-fetch path with the table under the bound
- peer annotations (node_url, node_did, local: false) via a mockito peer

@beardthelion beardthelion left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Verified on this head against main: the new route tests do pin the envelope
end to end. Deleting the failed-fetch truncated assignment, hardcoding
"truncated": false, or dropping the node_did enrichment each turns a route
test red, and the deadline, peers-query clamp, streaming byte cap, and
route-layer ordering all re-verified the same way. One suite-level failure
and two pins that still are not load-bearing.

Findings

  • [P1] Disposition the three new peers-table writers in the writer ledger
    crates/gitlawb-node/src/db/mod.rs:8387
    cargo test -p gitlawb-node peers_table_writer_guard fails on this head: the
    scan finds 12 writers against a 9-entry LEDGER. Each new route test issues a
    raw INSERT INTO peers (repos.rs:3946, 3972, 4008) with no ledger row and no
    entry in the disposition table above it. Raw SQL is the right fixture shape
    here since upsert_peer refuses the loopback URLs these tests need; add the
    three ("name", 1) rows and matching table entries, same as the
    federated_peer_query_is_bounded precedent.

  • [P2] Give the peer-overflow flag its own witness
    crates/gitlawb-node/src/api/repos.rs:3946
    The overflow test seeds 202 peers at an unreachable address, so its
    truncated == true is satisfied by the failed-fetch arm even with the count
    check gone: deleting aggregate.truncated |= peers.len() > MAX_FEDERATED_PEERS
    keeps it green. Point the seeds at one reachable stub returning [] and
    assert nodes_queried, so only the bound can set the flag. Same shape of gap
    in the envelope test at 3911: the seeded repo is owned by the node DID, so
    stamping owner_did in place of node_did at 3149 also stays green. Seed a
    repo owned by a different DID.

  • [P3] Pin the outbound peer page request on the mock
    crates/gitlawb-node/src/api/repos.rs:3999
    match_query(Matcher::Any) accepts any query string, so the
    ?limit=200&offset=0 request the bounded fan-out depends on is unpinned;
    dropping the params stays green. Match or capture the query in
    federated_route_annotates_peer_repos_on_real_200_body.

Not an ask, recorded only: the fetcher dials whatever http_url a peers row
carries with no fetch-time re-validation beyond last_ping_ok; identical on
main, and this change narrows the reachable set, so it is worth a separate
fix-up rather than an ask here. nodes_queried counts peers that returned a
usable page rather than attempts, and truncated also fires on an exact-boundary
aggregate fill; both match the docstring's reading.

- db/mod.rs: add writer-ledger entries and disposition docs for the
  three federated route tests touching the peers table
- api/repos.rs: seed the envelope test repo under a non-node owner DID
  so node_did must equal the actual node DID; pin the outbound peer
  query to exact limit=200&offset=0; make the peer-table-overflow test
  use a reachable stub for every peer so the count bound is the only
  possible reason for truncated=true, and assert nodes_queried

@euxaristia euxaristia 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.

Re-submission of the closed #409 (federated aggregation bounds). Same note as the closed round: the per-peer byte budget and row caps need tests, and the rebase should account for the current federation collector shape. Coordinating the landing order with #465 avoids a double-rebase of the shared collector.

@beardthelion beardthelion left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Re-checked head a7f931d against origin/main at bfc44f9. The prior round ledger, overflow witness, envelope pins, and mock query assertions are all on this head. I ran cargo test -p gitlawb-node federated_, peers_table_writer_guard, and cargo clippy -p gitlawb-node --all-targets -- -D warnings on the PR branch locally; all passed. Removing aggregate.truncated |= peers.len() > MAX_FEDERATED_PEERS turns federated_route_reports_truncated_when_peer_table_overflows red, so the peer-table bound is load-bearing, not only the failed-fetch path.

Not an ask, recorded only: the handler still loads the full local repo inventory before the aggregate budget applies, same shape as on main before this PR. nodes_queried counts peers that returned a usable page, and truncated also fires on exact-boundary aggregate fill, matching the docstring.

One process note, not a finding: rebasing onto recent main will likely conflict with other open work on repos.rs and state.rs; coordinate with #465 if you are both touching federation-adjacent code.

@euxaristia euxaristia 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.

LGTM. Re-review of head a7f931d confirms federated query bounds, peer ledger registrations, and response truncation behavior are fully verified.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

crate:node gitlawb-node — the serving node and REST API kind:bug Defect fix — wrong or unsafe behavior subsystem:identity DID/UCAN, http-sig auth, push authorization

Projects

None yet

Development

Successfully merging this pull request may close these issues.

api(repos): Unbounded aggregate allocation in federated repository response

3 participants