Skip to content

feat: path-scoped repository visibility (Phase 1) for #18#25

Merged
kevincodex1 merged 2 commits into
Gitlawb:mainfrom
beardthelion:feat/path-scoped-visibility
Jun 5, 2026
Merged

feat: path-scoped repository visibility (Phase 1) for #18#25
kevincodex1 merged 2 commits into
Gitlawb:mainfrom
beardthelion:feat/path-scoped-visibility

Conversation

@beardthelion
Copy link
Copy Markdown
Contributor

@beardthelion beardthelion commented Jun 3, 2026

Implements path-scoped repository visibility (Phase 1), the feature proposed in #18: a repo's "private" status becomes a property of a path subtree rather than a whole-repo boolean, enforced on the git read path using the node's existing DID identity and RFC 9421 HTTP signatures.

This supersedes the demo in #24 (which deliberately enforced only the existing is_public boolean to make the design concrete). This PR is the real, mergeable Phase 1.

What this does

  • visibility_rules data model (path_glob, mode, reader_dids) with DB access methods, modeled on protected_branches.
  • visibility_check pure decision function: owner is always allowed, most-specific matching rule wins, reader-DID allow-list, falls back to the legacy is_public flag. Exhaustively unit-tested (truth table).
  • Read-path enforcement on git_info_refs and git_upload_pack: an unauthorized read returns 404 byte-identical to a missing repo, so a private repo's existence does not leak.
  • Owner-only management API: PUT/DELETE/GET /api/v1/repos/{owner}/{repo}/visibility behind require_signature.
  • CLI: gl visibility set/remove/list.

Phasing and a deliberate constraint

mode A (hide existence) is whole-repo (/) only by construction: hiding a subtree's existence would rewrite every ancestor tree hash and diverge history (the Merkle DAG hashes child names+hashes). Subtrees therefore use mode B (content withheld, SHAs intact). The data model carries mode from day one so later phases need no migration.

Two pieces are intentionally not in this PR and are flagged as such in the CLI/API output:

Tests

260 tests pass (69 gitlawb-node + 191 gl); cargo fmt --check and cargo clippy --all-targets clean.

Closes nothing on its own; advances #18.

Summary by CodeRabbit

  • New Features

    • Path-scoped visibility rules for granular repo access (set/remove/list) and owner-only API endpoints
    • Visibility-based enforcement for git read/clone/fetch operations
    • CLI commands to set, remove and list visibility rules
  • Tests

    • Unit and integration tests covering visibility rules, matching behavior, and CLI flows

@beardthelion beardthelion changed the title feat: path-scoped repository visibility (Phase 1) — #18 feat: path-scoped repository visibility (Phase 1) for #18 Jun 3, 2026
@kevincodex1
Copy link
Copy Markdown
Contributor

hello bro @beardthelion kindly rebase to main and please fix conflicts

Implements per-path read visibility for repositories, addressing Gitlawb#18.
A repo's private status becomes a property of a path subtree rather
than a whole-repo boolean, enforced on the git read path via the node's
existing DID identity and RFC 9421 HTTP signatures.

Phase 1 scope (whole-repo enforcement, data model for the rest):
- visibility_rules data model (path_glob, mode, reader_dids) + DB access
- visibility_check pure decision function (owner-always-allow, most-
  specific rule wins, reader-DID allow-list, is_public fallback)
- enforcement on git_info_refs and git_upload_pack: unauthorized reads
  return 404 byte-identical to a missing repo (no existence leak)
- owner-only management API: PUT/DELETE/GET /api/v1/repos/{o}/{r}/visibility
- gl visibility set/remove/list CLI

mode A (hide) is whole-repo ("/") only by construction: hiding a
subtree's existence would rewrite ancestor tree hashes and diverge
history. Subtrees use mode B (content withheld, SHAs intact); B-subtree
clone filtering is deferred to a later phase and the CLI/API say so.

260 tests (69 node + 191 gl); fmt + clippy clean.
@beardthelion beardthelion force-pushed the feat/path-scoped-visibility branch from 5f14098 to b5fc7dd Compare June 4, 2026 13:48
@beardthelion
Copy link
Copy Markdown
Contributor Author

Done, rebased onto current main and pushed. The only real conflict was in server.rs: the visibility route now goes through the new add_auth_layers helper alongside protect (so it gets HTTP Signature + UCAN chain validation, with UCAN passthrough when no X-Ucan header is sent, matching the owner-direct flow). The visibility_rules table auto-merged into the v1 migration list next to protected_branches.

281 tests pass (86 gitlawb-node + 195 gl), fmt and clippy clean. Shows mergeable now.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 4, 2026

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 27f27c1d-b91f-4ea2-9fc1-f7d2b09a3b53

📥 Commits

Reviewing files that changed from the base of the PR and between b5fc7dd and 5a59064.

📒 Files selected for processing (2)
  • crates/gitlawb-node/src/api/visibility.rs
  • crates/gitlawb-node/src/db/mod.rs
🚧 Files skipped from review as they are similar to previous changes (1)
  • crates/gitlawb-node/src/db/mod.rs

📝 Walkthrough

Walkthrough

Adds path-scoped repository visibility: core decision logic and tests, DB migration and CRUD, owner-only REST endpoints (PUT/DELETE/GET), enforcement in git read handlers, server routing, and a CLI with signed requests and tests.

Changes

Repository Visibility Rules

Layer / File(s) Summary
Core visibility domain and authorization logic
crates/gitlawb-node/src/db/mod.rs, crates/gitlawb-node/src/visibility.rs
Decision enum, VisibilityMode/VisibilityRule domain types, visibility_check pure function with most-specific glob-match semantics and unit tests.
Database schema and persistence methods
crates/gitlawb-node/src/db/mod.rs
Adds visibility_rules migration and Db::set_visibility_rule/remove_visibility_rule/list_visibility_rules with JSON reader_dids and mode string conversion.
API handlers for visibility management
crates/gitlawb-node/src/api/visibility.rs, crates/gitlawb-node/src/api/mod.rs
Owner-gated set_visibility (PUT) with path_glob validation and mode mapping/restrictions, remove_visibility (DELETE), and list_visibility (GET); request/response JSON payloads and unit tests for glob validation.
Git read endpoint authorization
crates/gitlawb-node/src/api/repos.rs
git_info_refs and git_upload_pack accept optional AuthenticatedDid; upload-pack read path loads rules and calls visibility_check, denying with RepoNotFound when Decision::Deny.
Server routing and middleware configuration
crates/gitlawb-node/src/server.rs, crates/gitlawb-node/src/main.rs
Registers /api/v1/repos/{owner}/{repo}/visibility routes (PUT/DELETE/GET), reorders git_read_routes to include info/refs and adds auth::optional_signature middleware, and exposes mod visibility.
CLI client for visibility management
crates/gl/src/http.rs, crates/gl/src/main.rs, crates/gl/src/visibility.rs
NodeClient::get_signed for RFC 9421-signed GET; gl visibility subcommand with set/remove/list; owner/repo resolution; HTTP interactions (PUT/DELETE/GET) and mockito-based async tests.

Sequence Diagram

sequenceDiagram
  participant CLI
  participant NodeClient
  participant Server
  participant Db
  participant GitHandler
  CLI->>NodeClient: cmd_list / cmd_set / cmd_remove
  NodeClient->>Server: PUT/DELETE/GET /api/v1/repos/{owner}/{repo}/visibility (signed for GET)
  Server->>Db: set_visibility_rule / remove_visibility_rule / list_visibility_rules
  Db-->>Server: OK / rules
  Server->>GitHandler: git-upload-pack/info-refs request (client)
  GitHandler->>Db: list_visibility_rules
  GitHandler->>Server: visibility_check decision (owner/caller, path)
  Server-->>GitHandler: Allow or RepoNotFound (Deny)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

Poem

🐰 A rabbit's note on visibility
I hopped through paths both wide and narrow,
Owner keys and rules to sparrow—
Subtrees guarded, clones now ask,
A gentle check performs its task;
Quiet hops, secure and thorough.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main change: implementing path-scoped repository visibility as Phase 1, which is the core objective of this PR.
Docstring Coverage ✅ Passed Docstring coverage is 93.18% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/gitlawb-node/src/api/visibility.rs`:
- Around line 56-75: Validate req.path_glob before calling
state.db.set_visibility_rule: ensure it's non-empty, begins with '/' and parses
as a valid glob pattern (use glob::Pattern::new or equivalent) and return
AppError::BadRequest on failure; keep the existing VisibilityMode::A check but
perform the path_glob format validation first (check req.path_glob, parse it,
then proceed to the mode == VisibilityMode::A subtree check and finally call
set_visibility_rule).

In `@crates/gitlawb-node/src/db/mod.rs`:
- Around line 612-624: Migration v1 currently contains creation of the
visibility_rules table and idx_visibility_rules_repo index which will be skipped
on nodes that already applied v1; extract those DDL statements out of migration
v1 and place them into a new sequential migration (e.g., migration v2) so they
are applied on upgrade, referencing the visibility_rules table name and
idx_visibility_rules_repo index in the new migration; ensure the migration
registry/versioning code recognizes and runs the new migration during startup
and add a small migration test that simulates upgrading from v1 to v2 to verify
the table and index get created.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: d09eabfb-ba85-4679-a379-23f2633c15e9

📥 Commits

Reviewing files that changed from the base of the PR and between 09a3397 and b5fc7dd.

📒 Files selected for processing (10)
  • crates/gitlawb-node/src/api/mod.rs
  • crates/gitlawb-node/src/api/repos.rs
  • crates/gitlawb-node/src/api/visibility.rs
  • crates/gitlawb-node/src/db/mod.rs
  • crates/gitlawb-node/src/main.rs
  • crates/gitlawb-node/src/server.rs
  • crates/gitlawb-node/src/visibility.rs
  • crates/gl/src/http.rs
  • crates/gl/src/main.rs
  • crates/gl/src/visibility.rs

Comment thread crates/gitlawb-node/src/api/visibility.rs
Comment thread crates/gitlawb-node/src/db/mod.rs Outdated
- migrations: move visibility_rules DDL out of the v1 migration into a
  new v3 (CodeRabbit, critical). Appending to v1 meant nodes that had
  already recorded v1 would skip the statements forever, so the table
  would be missing after upgrade. v3 matches the convention Gitlawb#23 set with
  v2 (agent_profiles) and the documented 'never append to v1' rule.
- api: validate path_glob on set (must start with '/', no trailing '/',
  only a trailing '/**' wildcard, '/' for whole-repo not '/**'). Empty
  or slash-less globs would otherwise silently misconfigure access.
  Added unit tests for the accepted/rejected forms.

281 -> 283 tests pass (88 node + 195 gl); fmt + clippy clean.
@beardthelion
Copy link
Copy Markdown
Contributor Author

Thanks @coderabbitai, both addressed in 5a59064:

  1. Migration v1 (critical): moved the visibility_rules table + index out of v1 into a new migration v3. You're right that appending to v1 would be skipped on any node that already recorded v1, so the table would never exist after upgrade. v3 follows the same pattern feat: agent profiles (display name, bio, avatar, social links) #23 used for v2 (agent_profiles) and the "never append to v1" rule documented above the catalogue.

  2. path_glob validation: set_visibility now rejects malformed globs before storing (must start with /, no trailing /, only a trailing /** wildcard, and / rather than /** for whole-repo scope). Scoped the rules to exactly what visibility_check understands. Added unit tests for the accepted and rejected forms.

283 tests pass (88 gitlawb-node + 195 gl), fmt and clippy clean.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 4, 2026

Tip

For best results, initiate chat on the files or code changes.

@beardthelion: Both fixes look solid — thank you for the detailed write-up.

  • Migration v3 is exactly the right pattern; putting visibility_rules in its own numbered migration guarantees it runs on already-deployed nodes that already recorded v1 as applied. Good to see it mirrors the v2 (agent_profiles) precedent.
  • path_glob validation upfront in set_visibility is the right place to enforce it — keeps visibility_check clean and prevents malformed rules from ever reaching the DB. The unit tests scoping accepted/rejected forms to what the checker actually understands are a nice complement.

281 → 283 tests with the new validation coverage is a healthy uptick. Happy with the state of this. 🐇✨

Copy link
Copy Markdown
Contributor

@kevincodex1 kevincodex1 left a comment

Choose a reason for hiding this comment

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

LGTM!

@kevincodex1 kevincodex1 merged commit 6abaf1d into Gitlawb:main Jun 5, 2026
2 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