Skip to content
Closed
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
d910ea5
fix(node): Bound GraphQL query cost.
euxaristia Sep 7, 2026
1d1ec25
test(node): Pin the GraphQL depth boundary.
euxaristia Sep 7, 2026
e77bfda
fix(node): Charge GraphQL mutations and list sizes against query limits.
euxaristia Sep 9, 2026
c975747
fix(node): paginate visible GraphQL repository lists
euxaristia Sep 9, 2026
9d38e04
fix(node): charge subscription roots and test query boundaries
euxaristia Sep 9, 2026
ebafeae
test(graphql): verify unauthorized caller, legacy bound, and producti…
euxaristia Sep 10, 2026
99426ca
fix(graphql): serve the advertised repository page range
euxaristia Sep 12, 2026
0e5ea50
test(graphql): cover every annotated root cost
euxaristia Sep 12, 2026
8d665c7
test(graphql): isolate the repository cursor length guard
euxaristia Sep 12, 2026
9d53e13
fix(node): pin GraphQL introspection depth and pagination contracts
euxaristia Sep 12, 2026
a06cb43
test(graphql): anti-correlate legacy repos sort and pin keyset tuple …
euxaristia Sep 13, 2026
e91b1ea
test(graphql): pin normalized owner ordering across page cursors
euxaristia Sep 14, 2026
f564ec0
test(node): make Windows regression fixtures portable
euxaristia Sep 14, 2026
1de5eb9
fix(sync): use a supported Windows promisor blob filter
euxaristia Sep 14, 2026
e9998a7
test(graphql): anti-correlate pagination ids and cover full terminal …
euxaristia Sep 15, 2026
ac6c134
revert(sync): split Windows promisor filter out of GraphQL query cost PR
euxaristia Sep 15, 2026
4e6488d
test(graphql): pin case ordering and test documented max page limit
euxaristia Sep 17, 2026
f778793
fix(db): pin COLLATE "C" for visible repos page ordering and keyset r…
euxaristia Sep 18, 2026
c0aa2dc
fix(db): deny malformed reader lists without failing repo pages
euxaristia Sep 20, 2026
a80845e
fix(graphql): reject NUL bytes in repository cursors
euxaristia Sep 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ Good today:

- Local or Docker node startup.
- Postgres-backed repo metadata.
- Bounded GraphQL queries with [repository pagination](docs/graphql-pagination.md).
- Bare git repository storage.
- Git smart-HTTP clone/fetch/push.
- RFC 9421-signed writes.
Expand Down
54 changes: 54 additions & 0 deletions crates/gitlawb-node/src/db/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ use std::time::Duration;
use tracing::info;
use uuid::Uuid;

/// Maximum visible repositories per page, plus one internal look-ahead row.
pub(crate) const MAX_VISIBLE_REPO_PAGE_SIZE: usize = 200;

// ── Public data types ─────────────────────────────────────────────────────────

#[derive(Debug, Clone, Serialize, Deserialize)]
Expand Down Expand Up @@ -1540,6 +1543,57 @@ impl Db {
Ok(rows.into_iter().map(row_to_repo).collect())
}

/// A bounded, mirror-deduplicated page ordered by owner key and repository
/// name. Apply root visibility before LIMIT: private rows must not consume
/// page slots or influence continuation metadata. The root-rule predicate
/// mirrors `visibility::listable_at_root`; a differential test pins it.
/// Cursors only select a position and never confer read authority.
pub async fn list_visible_repos_page(
&self,
caller: Option<&str>,
after: Option<(&str, &str)>,
limit: usize,
) -> Result<Vec<RepoRecord>> {
let sql = format!(
"{}
SELECT d.id, d.name, d.owner_did, d.description, d.is_public,
d.default_branch, d.created_at, d.updated_at, d.disk_path,
d.forked_from, d.machine_id
FROM deduped d
LEFT JOIN LATERAL (
SELECT reader_dids FROM visibility_rules v
WHERE v.repo_id = d.id AND v.path_glob ~ '^/*([*][*])*$'
ORDER BY v.path_glob DESC LIMIT 1
) root_rule ON TRUE
WHERE (
($2::text IS NOT NULL AND ({key}) = $2)
OR CASE WHEN root_rule.reader_dids IS NULL THEN d.is_public
WHEN jsonb_typeof(root_rule.reader_dids::jsonb) = 'array' THEN
COALESCE(root_rule.reader_dids::jsonb ? $3::text, FALSE)
AND NOT EXISTS (
SELECT 1 FROM jsonb_array_elements(root_rule.reader_dids::jsonb) reader
WHERE jsonb_typeof(reader) <> 'string'
)
ELSE FALSE END
)
AND ($4::text IS NULL OR (({key}), d.name) > ($4, $5::text))
ORDER BY ({key}), d.name
LIMIT $6",
Self::dedup_cte(),
key = OWNER_KEY_CASE_SQL,
);
let rows = sqlx::query(&sql)
.bind(None::<&str>)
.bind(caller.map(normalize_owner_key))
.bind(caller)
.bind(after.map(|(owner, _)| normalize_owner_key(owner)))
.bind(after.map(|(_, name)| name))
.bind(limit.min(MAX_VISIBLE_REPO_PAGE_SIZE + 1) as i64)
.fetch_all(&self.pool)
.await?;
Ok(rows.into_iter().map(row_to_repo).collect())
}

/// Repos currently quarantined (admitted as mirrors but withheld from every
/// listing surface). `list_all_repos_deduped` excludes these (its `DEDUP_CTE`
/// filters `quarantined = FALSE`), so a gate that resolves a slug against the
Expand Down
Loading
Loading