Repository navigation
ci(core): add dependency-purity allowlist gate for gitlawb-core #241
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,112 @@ | ||
| # Dependency allowlist for the `gitlawb-core` crate. | ||
| # | ||
| # gitlawb-core is the shared-primitives crate every consumer embeds (the `gl` | ||
| # CLI, git-remote-gitlawb, the node daemon). It must stay lean so those | ||
| # consumers do not inherit daemon-weight dependencies. This file is the source | ||
| # of truth for the crates gitlawb-core's NORMAL (non-dev, non-build) transitive | ||
| # dependency closure is allowed to contain. CI (`scripts/check-gitlawb-core-deps.sh`, | ||
| # wired into the `core-deps-purity` job in .github/workflows/pr-checks.yml) | ||
| # fails if gitlawb-core's tree contains any crate not listed here. | ||
| # | ||
| # The allowlist is exhaustive-by-construction: a NEW dependency reds CI whether | ||
| # or not anyone thought to ban it, which a denylist cannot do. | ||
| # | ||
| # Measured in-workspace, so feature unification across the workspace can, in | ||
| # rare cases, surface a crate here that gitlawb-core would not pull standalone. | ||
| # An unexpected new entry is a prompt to check what pulled it in, not noise to | ||
| # rubber-stamp. | ||
| # | ||
| # Regenerate (from repo root, after an INTENTIONAL dependency change): | ||
| # cargo tree -p gitlawb-core --edges normal --prefix none \ | ||
| # | sed -E 's/ v[0-9].*$//' \ | ||
| # | grep -v '^gitlawb-core$' \ | ||
| # | sort -u | ||
| # then paste the result below this header block. (The checker snapshots and | ||
| # restores Cargo.lock; a manual regen may refresh it — `git checkout Cargo.lock` | ||
| # afterward if you did not intend that.) | ||
| # | ||
| # Lines starting with `#` and blank lines are ignored by the checker. | ||
| aead | ||
| anyhow | ||
| base-x | ||
| base256emoji | ||
| base64 | ||
| base64ct | ||
| block-buffer | ||
| cfg-if | ||
| chacha20 | ||
| chacha20poly1305 | ||
| chrono | ||
| cid | ||
| cipher | ||
| const-oid | ||
| const-str | ||
| core2 | ||
| cpufeatures | ||
| crypto-common | ||
| crypto_box | ||
| crypto_secretbox | ||
| curve25519-dalek | ||
| curve25519-dalek-derive | ||
| data-encoding | ||
| data-encoding-macro | ||
| data-encoding-macro-internal | ||
| der | ||
| digest | ||
| ed25519 | ||
| ed25519-dalek | ||
| equivalent | ||
| generic-array | ||
| getrandom | ||
| hashbrown | ||
| hex | ||
| iana-time-zone | ||
| indexmap | ||
| inout | ||
| itoa | ||
| libc | ||
| match-lookup | ||
| memchr | ||
| multibase | ||
| multihash | ||
| multihash-codetable | ||
| multihash-derive | ||
| multihash-derive-impl | ||
| num-traits | ||
| opaque-debug | ||
| pem-rfc7468 | ||
| pkcs8 | ||
| poly1305 | ||
| ppv-lite86 | ||
| proc-macro-crate | ||
| proc-macro2 | ||
| quote | ||
| rand | ||
| rand_chacha | ||
| rand_core | ||
| salsa20 | ||
| serde | ||
| serde_core | ||
| serde_derive | ||
| serde_json | ||
| sha2 | ||
| signature | ||
| spki | ||
| subtle | ||
| syn | ||
| synstructure | ||
| thiserror | ||
| thiserror-impl | ||
| toml_datetime | ||
| toml_edit | ||
| toml_parser | ||
| typenum | ||
| unicode-ident | ||
| universal-hash | ||
| unsigned-varint | ||
| uuid | ||
| winnow | ||
| zerocopy | ||
| zeroize | ||
| zeroize_derive | ||
| zmij | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,71 @@ | ||
| #!/usr/bin/env bash | ||
| # | ||
| # Dependency-purity gate for the `gitlawb-core` crate. | ||
| # | ||
| # gitlawb-core is embedded by every consumer (the gl CLI, git-remote-gitlawb, | ||
| # the node daemon), so it must stay lean. This script recomputes gitlawb-core's | ||
| # NORMAL (non-dev, non-build) transitive dependency set and fails if it contains | ||
| # any crate not present in ci/gitlawb-core-allowed-deps.txt. | ||
| # | ||
| # Hard-fail direction: a crate present now but NOT allowlisted. That is the case | ||
| # the gate exists to catch (core silently gaining a heavy dependency). | ||
| # Informational only: an allowlisted crate no longer present (stale entry) — a | ||
| # legitimate dependency removal should not red CI, so it is reported, not failed. | ||
| # | ||
| # Runnable from anywhere in the repo. | ||
| set -euo pipefail | ||
|
|
||
| ROOT="$(git rev-parse --show-toplevel)" | ||
| ALLOW="$ROOT/ci/gitlawb-core-allowed-deps.txt" | ||
|
|
||
| if [ ! -f "$ALLOW" ]; then | ||
| echo "ERROR: allowlist not found at $ALLOW" >&2 | ||
| exit 1 | ||
| fi | ||
|
|
||
| # `cargo tree` resolves like the build/test jobs (no --locked): the committed | ||
| # Cargo.lock can lag the manifests, and --locked would red this gate for | ||
| # lock-staleness reasons unrelated to gitlawb-core's dependencies. Resolving can | ||
| # refresh Cargo.lock as a side effect, so snapshot and restore it — this check | ||
| # must never leave the working tree dirty when run locally. | ||
| lock_backup="$(mktemp)" | ||
| cp "$ROOT/Cargo.lock" "$lock_backup" | ||
| restore_lock() { cp "$lock_backup" "$ROOT/Cargo.lock"; rm -f "$lock_backup"; } | ||
| trap restore_lock EXIT | ||
|
|
||
| # Current normal-dependency closure of gitlawb-core, one crate name per line. | ||
| # Must match the regen command documented in the allowlist header exactly. | ||
| current="$( | ||
| cargo tree -p gitlawb-core --edges normal --prefix none --manifest-path "$ROOT/Cargo.toml" \ | ||
| | sed -E 's/ v[0-9].*$//' \ | ||
| | grep -v '^gitlawb-core$' \ | ||
| | sort -u | ||
|
Comment on lines
+39
to
+42
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '\n== files ==\n'
git ls-files scripts/check-gitlawb-core-deps.sh ci/gitlawb-core-allowed-deps.txt
printf '\n== script excerpt ==\n'
cat -n scripts/check-gitlawb-core-deps.sh | sed -n '30,55p'
printf '\n== allowlist excerpt ==\n'
cat -n ci/gitlawb-core-allowed-deps.txt | sed -n '1,40p'Repository: Gitlawb/node Length of output: 3420 🌐 Web query:
💡 Result: The cargo tree command in Rust is used to visualize the dependency graph of a project [1][2]. By default, when no target is specified, cargo tree displays the dependencies for the host platform [1][3]. The --target flag allows you to filter the dependency graph based on a specific target triple [1][4]. When you provide the value all—specifically using --target all—cargo tree includes all platform-specific dependencies in the graph, regardless of the host platform [1][2]. This is particularly useful for identifying dependencies that are only pulled in when building for specific operating systems or architectures (e.g., dependencies guarded by platform-specific cfg attributes) [5]. Citations:
🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '\n== cargo tree help (if available) ==\n'
cargo tree --help | sed -n '1,220p'Repository: Gitlawb/node Length of output: 142 🏁 Script executed: #!/bin/bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
for p in [Path("scripts/check-gitlawb-core-deps.sh"), Path("ci/gitlawb-core-allowed-deps.txt")]:
print(f"\n### {p}")
text = p.read_text()
for needle in ["--target all", "cargo tree -p gitlawb-core", "Regenerate (from repo root"]:
print(needle, "=>", needle in text)
PYRepository: Gitlawb/node Length of output: 142 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '\n== references ==\n'
rg -n "gitlawb-core-allowed-deps|check-gitlawb-core-deps|cargo tree -p gitlawb-core" -S .
printf '\n== package metadata relevant to target-specific deps ==\n'
cat -n Cargo.toml | sed -n '1,220p'Repository: Gitlawb/node Length of output: 142 Include all Cargo targets in the dependency allowlist check.
📍 Affects 2 files
🤖 Prompt for AI AgentsSource: MCP tools |
||
| )" | ||
|
|
||
| # Allowlist with comments and blank lines stripped. | ||
| allowed="$(grep -vE '^[[:space:]]*(#|$)' "$ALLOW" | sort -u)" | ||
|
Comment on lines
+38
to
+46
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: set -euo pipefail
printf '--- file outline ---\n'
git ls-files scripts/check-gitlawb-core-deps.sh
printf '\n--- relevant lines ---\n'
cat -n scripts/check-gitlawb-core-deps.sh | sed -n '1,140p'
printf '\n--- pipeline behavior probe ---\n'
tmpdir="$(mktemp -d)"
cat >"$tmpdir/probe.sh" <<'SH'
#!/usr/bin/env bash
set -o pipefail
probe() {
label="$1"
shift
if out="$("$@")"; then
printf '%s: ok, out=%q, status=%s\n' "$label" "$out" "$?"
else
st=$?
printf '%s: fail, out=%q, status=%s\n' "$label" "$out" "$st"
fi
}
probe grep_all_filtered bash -lc "printf 'a\nb\n' | grep -v . | cat"
probe grep_some_left bash -lc "printf 'a\nb\n' | grep -v '^a$' | cat"
probe sed_all_filtered bash -lc "printf 'a\nb\n' | sed '/./d' | cat"
probe count_empty bash -lc "current=''; if [ -n \"\$current\" ]; then dep_count=\"\$(printf '%s\n' \"\$current\" | wc -l | tr -d ' ')\"; else dep_count=0; fi; printf 'dep_count=%s\n' \"\$dep_count\""
probe count_nonempty bash -lc "current='x
y'; if [ -n \"\$current\" ]; then dep_count=\"\$(printf '%s\n' \"\$current\" | wc -l | tr -d ' ')\"; else dep_count=0; fi; printf 'dep_count=%s\n' \"\$dep_count\""
SH
bash "$tmpdir/probe.sh"Repository: Gitlawb/node Length of output: 241 🏁 Script executed: set -euo pipefail
cat -n scripts/check-gitlawb-core-deps.sh | sed -n '1,140p'
python3 - <<'PY'
import subprocess, textwrap, os, tempfile, pathlib
def run(cmd):
p = subprocess.run(cmd, shell=True, executable='/bin/bash', stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
print(f"$ {cmd}\nstatus={p.returncode}\n{p.stdout}")
run("bash -lc \"set -o pipefail; printf 'a\\nb\\n' | grep -v . | cat\"")
run("bash -lc \"set -o pipefail; printf 'a\\nb\\n' | grep -v '^a$' | cat\"")
run("bash -lc \"set -o pipefail; printf 'a\\nb\\n' | sed '/./d' | cat\"")
run("bash -lc \"current=''; if [ -n \\\"$current\\\" ]; then dep_count=\\\"$(printf '%s\\n' \\\"$current\\\" | wc -l | tr -d ' ')\\\"; else dep_count=0; fi; printf 'dep_count=%s\\n' \\\"$dep_count\\\"\"")
PYRepository: Gitlawb/node Length of output: 3942 🏁 Script executed: python3 - <<'PY'
import subprocess, textwrap
cases = [
("grep pipeline, all filtered", r"""bash -lc 'set -euo pipefail; current="$(printf "gitlawb-core\n" | grep -v "^gitlawb-core$" | sort -u)"; printf "current=%q\n" "$current"'"""),
("grep pipeline, one dep remains", r"""bash -lc 'set -euo pipefail; current="$(printf "gitlawb-core\nfoo\n" | grep -v "^gitlawb-core$" | sort -u)"; printf "current=%q\n" "$current"'"""),
("count empty current", r"""bash -lc 'current=""; printf "%s\n" "$current" | wc -l | tr -d " "'"""),
]
for label, cmd in cases:
p = subprocess.run(cmd, shell=True, executable="/bin/bash", stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True)
print(f"== {label} ==")
print(f"status={p.returncode}")
print(p.stdout)
PYRepository: Gitlawb/node Length of output: 291 Avoid 🤖 Prompt for AI Agents |
||
|
|
||
| # comm needs sorted input; both sides are sorted above. | ||
| offenders="$(comm -23 <(printf '%s\n' "$current") <(printf '%s\n' "$allowed"))" | ||
| stale="$(comm -13 <(printf '%s\n' "$current") <(printf '%s\n' "$allowed"))" | ||
|
|
||
| if [ -n "$stale" ]; then | ||
| echo "NOTE: allowlisted crates no longer in gitlawb-core's dependency tree" | ||
| echo " (safe to prune from ci/gitlawb-core-allowed-deps.txt):" | ||
| printf ' %s\n' $stale | ||
| echo | ||
| fi | ||
|
|
||
| if [ -n "$offenders" ]; then | ||
| { | ||
| echo "ERROR: gitlawb-core gained dependencies not on the allowlist:" | ||
| printf ' %s\n' $offenders | ||
| echo | ||
| echo "gitlawb-core must stay embeddable and lean. If a new dependency is" | ||
| echo "intentional, add it to ci/gitlawb-core-allowed-deps.txt (regen command" | ||
| echo "is in that file's header). Otherwise, drop the dependency." | ||
| } >&2 | ||
| exit 1 | ||
| fi | ||
|
|
||
| echo "gitlawb-core dependency purity: OK ($(printf '%s\n' "$current" | wc -l | tr -d ' ') normal deps, all allowlisted)." | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Fix the dependency-gate wording.
reds CIshould be rewritten asturns CI redfor grammatical correctness.Proposed fix
🧰 Tools
🪛 LanguageTool
[style] ~11-~11: Consider shortening this phrase to just ‘whether’, unless you mean ‘regardless of whether’.
Context: ...-construction: a NEW dependency reds CI whether # or not anyone thought to ban it, which a denyl...
(WHETHER)
🤖 Prompt for AI Agents
Source: Linters/SAST tools