Skip to content

Fix/gl acceptance findings#236

Merged
kevincodex1 merged 8 commits into
mainfrom
fix/gl-acceptance-findings
Jul 22, 2026
Merged

Fix/gl acceptance findings#236
kevincodex1 merged 8 commits into
mainfrom
fix/gl-acceptance-findings

Conversation

@kevincodex1

@kevincodex1 kevincodex1 commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes the four CLI issues surfaced when a fresh agent, given only the published docs, drove gl from zero to a signed push: status not seeing gl init's remote, init's push hint failing on uncommitted repos, cert show not actually verifying, and doctor red-flagging healthy local nodes.

Motivation & context

A docs-only acceptance test (agent + local harness) reached a verified push but hit exactly these traps; the docs currently carry caveats working around each. Closes #.

Kind of change

  • Bug fix
  • Feature

What changed

  • gl (status.rs): detect_gitlawb_remote scans all remotes — origin first (clones), then gitlawb (the name gl init adds), then any gitlawb:// URL.
  • gl (init.rs): git init -b main with plain-init fallback for git <2.28; the closing hint is state-aware — with no commits it prints the add/commit step before the push, using the repo's actual branch name.
  • gl (cert.rs): cert show rebuilds the node's canonical signing payload, derives the Ed25519 key from the certificate's node DID, and prints a VALID/INVALID verdict; --verify makes the exit code reflect it. Round-trip, tampered-payload, and malformed-signature unit tests.
  • gl (doctor.rs): a localhost GITLAWB_NODE passes with an explanatory note instead of failing — the connectivity check still fails loudly when the configured node is unreachable.

How a reviewer can verify

cargo test -p gl            # includes cert::tests::verify_signature_round_trip_and_tamper
# functional (local harness): gl init in empty dir → hint shows commit step; branch is main;
# gl status shows the repo; gl doctor passes; gl cert show <repo> <id> --verify prints VALID, exit 0

Before you request review

  • Scope is one logical change; no unrelated churn
  • cargo test --workspace passes locally
  • New behavior is covered by tests (required for fixes)
  • cargo fmt --all and cargo clippy --workspace --all-targets -- -D warnings are clean
  • Commit titles use Conventional Commits (feat(...), fix(...), docs(...))
  • Docs / .env.example updated if behavior or config changed (or N/A — docs cleanup follows the release, see notes)
  • Checked existing PRs so this isn't a duplicate

Protocol & signing impact

  • Touches DID / did:key, Ed25519 / RFC 9421 signatures, UCAN, ref certs, or P2P wire formats
  • Discussed in an issue before implementation — verification only; no wire/format change
  • Backward-compatible with existing nodes and previously signed history — cert verification is read-only and mirrors the node's existing signing payload byte-for-byte

Notes for reviewers

Verified functionally against a local node: all four flows exercised end-to-end, including a real VALID verdict on a live certificate (proving the payload reconstruction matches node signing exactly). After this ships in 0.6.1, the docs lose three workaround caveats and skill.md's cert section gets corrected (it documents a cert verify subcommand that has never existed).

Validation recap: four per-fix commits; full workspace gate green (fmt, clippy -D warnings, all tests incl. DB suites against Postgres); functional verification against a live node — the init hint, main branch, status detection, doctor note, and a real VALID Ed25519 verdict with exit 0.

Also notable: this is the first PR through your armed merge gate, so expect the four required checks to show as Required — worth confirming they do. After merge: release-please opens 0.6.1 → merging it runs the first fully-hands-free multi-channel release → then I do the docs cleanup pass (drop the status/branch caveats, fix the phantom cert verify <cert-file> in skill.md, restore "verify with gl cert show --verify"). CodeRabbit findings are mine to address when they arrive.

Summary by CodeRabbit

  • New Features

    • Added --verify to gl cert show for offline Ed25519 signature verification with VALID/INVALID.
    • Added --expect-node to gl cert show (requires --verify) to enforce issuer anchoring.
    • gl init now initializes with main when available and prints branch/HEAD-aware next steps.
  • Bug Fixes

    • Improved gl status detection of gitlawb:// remotes across all remotes/URL variants and updated the no-remote message.
    • gl doctor now treats loopback GITLAWB_NODE values as intentional and detects zsh alias shadowing for gl.
  • Documentation

    • Homebrew caveats and installer notes warn about oh-my-zsh gl='git pull' alias shadowing and suggest unalias gl.
  • Tests

    • Added unit tests for certificate verification (canonical payload + failure cases) and gl doctor alias/loopback detection.

gl init names its remote 'gitlawb', but detect_gitlawb_remote() only
ever read origin — so gl status reported 'not in a gitlawb repo' in the
very repo gl init had just set up. Scan all remotes, preferring origin
(clones) then gitlawb, then any other gitlawb:// URL. (#231 acceptance
test finding)
- git init -b main (plain-init fallback for git <2.28) so a fresh repo
  can't land on master while every hint says main.
- The closing hint checks HEAD: with no commits it prints the
  add/commit step first — a bare 'git push gitlawb main' at that point
  fails with 'src refspec main does not match any', the exact trap the
  acceptance test walked into. The current branch name is used in the
  hint either way.
show printed the signing payload and told the user to verify offline
themselves. It now rebuilds the node's canonical payload (field order
mirrors gitlawb-node cert issuance), derives the Ed25519 key from the
certificate's node DID, verifies, and prints a VALID/INVALID verdict;
--verify makes the exit code reflect it for scripting. Round-trip +
tamper + malformed-signature unit tests included.
…ailure

A localhost GITLAWB_NODE red-flagged every self-hosted and dev-harness
setup even when the node was healthy; the connectivity check already
fails loudly when the configured node is unreachable. The env check now
passes with an explanatory note.
@github-actions github-actions Bot added the needs-issue PR has no linked issue label Jul 22, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Thanks for the contribution. A couple of things will help us review this faster:

  • Link the issue this addresses (Closes #123). For protocol changes, open an issue first.

See CONTRIBUTING.md. Update the PR and these notes will clear automatically.

@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

gl adds certificate signature verification, improves repository initialization and push guidance, discovers Gitlawb remotes across configured remotes, and adds local-node and shell-alias diagnostics.

Changes

gl CLI behavior

Layer / File(s) Summary
Certificate signature verification
crates/gl/Cargo.toml, crates/gl/src/cert.rs
gl cert show --verify reconstructs and verifies Ed25519 signatures, compares node DIDs, reports validity, and tests tampered or malformed signatures.
Repository initialization and push guidance
crates/gl/src/init.rs
gl init prefers the main branch, falls back to plain Git initialization, and prints commit-aware push instructions.
Gitlawb remote discovery
crates/gl/src/status.rs
gl status scans configured remotes for gitlawb:// URLs and updates the missing-remote message.
Node and shell diagnostics
crates/gl/src/doctor.rs, install.sh, .github/workflows/release.yml
Local node addresses pass the GITLAWB_NODE check, while doctor, installation, and Homebrew output identify gl shell alias shadowing and provide unalias instructions.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant cert_show
  participant verify_signature
  participant configured_node
  cert_show->>verify_signature: Rebuild canonical payload and verify signature
  verify_signature-->>cert_show: Return signature verdict
  cert_show->>configured_node: Fetch current node DID
  configured_node-->>cert_show: Return node identity
  cert_show-->>cert_show: Print validity and identity comparison
Loading

Suggested labels: kind:bug, needs-tests

Suggested reviewers: beardthelion

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title is related to the change, but it is too vague to clearly describe the main fixes. Rename it to name the core change, e.g. Fix gl status, init, cert verify, and doctor acceptance issues.
✅ Passed checks (4 passed)
Check name Status Explanation
Description check ✅ Passed The description matches the template well and includes summary, motivation, changes, verification, and impact notes.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% 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.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/gl-acceptance-findings

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 Checkov (3.3.8)
.github/workflows/release.yml

Traceback (most recent call last):
File "/usr/local/bin/checkov", line 2, in
from checkov.main import Checkov
ModuleNotFoundError: No module named 'checkov'


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

@beardthelion beardthelion added the crate:gl gl — the contributor CLI label Jul 22, 2026

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/gl/src/cert.rs (1)

142-169: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Missing-field fallbacks ("?"/"") get fed into signature verification, masking malformed responses as "INVALID".

cert_id, ref_name, old_sha, etc. default to "?" (display-only fallback) and repo_id defaults to "" when absent, then those same values are used to rebuild the signed payload for verify_signature. If the API response is missing a field, this manifests as a generic "Ed25519 signature does not match" rather than a clear "certificate response is malformed" error, which could mislead a user into thinking a cert was tampered with.

🤖 Prompt for 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.

In `@crates/gl/src/cert.rs` around lines 142 - 169, Validate that all certificate
fields required by signature verification are present and strings before
constructing the display values or calling verify_signature; return a clear
malformed certificate response error identifying missing or invalid fields
instead of substituting "?" or "" for verification inputs. Update the
certificate-processing function around cert_id, ref_name, old_sha, new_sha,
pusher, node_did, issued_at, signature, and repo_id, while retaining
display-only fallbacks only for fields not used in the signed payload.
🧹 Nitpick comments (1)
crates/gl/src/cert.rs (1)

209-249: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Wrap these fields in a struct before calling verify_signature; 8 positional &str arguments make it easy to silently swap the DID-like values later.

🤖 Prompt for 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.

In `@crates/gl/src/cert.rs` around lines 209 - 249, Introduce a parameter struct
for the signed fields and update verify_signature to accept that struct instead
of eight positional &str arguments. Populate and access the struct fields by
name at the call site and within verify_signature, preserving the existing
payload contents and verification behavior while preventing accidental swaps of
DID-like values.
🤖 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/gl/src/cert.rs`:
- Around line 181-200: Make the node-info request and JSON parsing in the
certificate display flow non-fatal: handle errors from client.get("/") and the
“failed to fetch node info” context by printing a warning and continuing to the
existing signature verdict handling. Preserve the DID comparison output when
node info is available, and keep require_valid enforcement unchanged.

In `@crates/gl/src/doctor.rs`:
- Around line 151-155: Update the GITLAWB_NODE validation match in the doctor
check to parse the value as a URL and inspect its exact host, accepting only
localhost or 127.0.0.1. Remove the substring-based contains checks while
preserving the existing intentional-local-node pass behavior and handling
invalid URLs through the existing non-local path.

In `@crates/gl/src/init.rs`:
- Around line 189-204: Update the branch detection and push-instruction logic in
the initialization flow to preserve a detached HEAD as a missing branch instead
of defaulting to "main". When no branch is available, instruct the user to check
out or create a branch before pushing; retain the existing git push command only
when a valid branch name was detected.

In `@crates/gl/src/status.rs`:
- Around line 179-195: Update the remote URL inspection loop around
parse_gitlawb_url to query both fetch and push URLs using git remote get-url
--all and git remote get-url --push --all for each remote. Parse every returned
line, preserving the existing error and unsuccessful-command handling, and
return the first Gitlawb URL found.

---

Outside diff comments:
In `@crates/gl/src/cert.rs`:
- Around line 142-169: Validate that all certificate fields required by
signature verification are present and strings before constructing the display
values or calling verify_signature; return a clear malformed certificate
response error identifying missing or invalid fields instead of substituting "?"
or "" for verification inputs. Update the certificate-processing function around
cert_id, ref_name, old_sha, new_sha, pusher, node_did, issued_at, signature, and
repo_id, while retaining display-only fallbacks only for fields not used in the
signed payload.

---

Nitpick comments:
In `@crates/gl/src/cert.rs`:
- Around line 209-249: Introduce a parameter struct for the signed fields and
update verify_signature to accept that struct instead of eight positional &str
arguments. Populate and access the struct fields by name at the call site and
within verify_signature, preserving the existing payload contents and
verification behavior while preventing accidental swaps of DID-like values.
🪄 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

Run ID: f1f0f7d4-dab5-482c-8a08-6ad2b67def6b

📥 Commits

Reviewing files that changed from the base of the PR and between e14c2d3 and 94c61f7.

📒 Files selected for processing (5)
  • crates/gl/Cargo.toml
  • crates/gl/src/cert.rs
  • crates/gl/src/doctor.rs
  • crates/gl/src/init.rs
  • crates/gl/src/status.rs

Comment thread crates/gl/src/cert.rs Outdated
Comment thread crates/gl/src/doctor.rs Outdated
Comment thread crates/gl/src/init.rs Outdated
Comment thread crates/gl/src/status.rs
oh-my-zsh's default git plugin ships gl='git pull', silently shadowing
gl in every interactive zsh — the symptom is the baffling 'fatal: not a
git repository'. Aliases beat PATH, so no installer can fix it; doctor
now detects the setup (explicit alias gl= in ~/.zshrc, or the omz git
plugin without an unalias) and prescribes the one-line fix. Best-effort
heuristic, warning-level, covered by four unit tests.
install.sh detects the common shadowing setup (omz git plugin, no
unalias in ~/.zshrc) and prints the fix right after installing; the
generated Homebrew formula gains a caveats block saying the same, so
brew install/upgrade surfaces it too. Catching this at install time
beats letting the first gl invocation print git's error.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

🤖 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/gl/src/doctor.rs`:
- Around line 331-332: Make alias cleanup detection shell-aware in
crates/gl/src/doctor.rs lines 331-332 and install.sh lines 200-202: replace raw
substring checks with parsing of actual unalias gl commands, and compare command
ordering with oh-my-zsh loading so only cleanup occurring after loading
suppresses the warning; handle comments, unalias global, and pre-load unalias gl
without suppressing detection.
🪄 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

Run ID: 752fab8a-027a-4b6f-9df8-1da5e1ffb619

📥 Commits

Reviewing files that changed from the base of the PR and between 94c61f7 and e3df51d.

📒 Files selected for processing (3)
  • .github/workflows/release.yml
  • crates/gl/src/doctor.rs
  • install.sh

Comment thread crates/gl/src/doctor.rs Outdated
…opback, detached HEAD, multi-URL remotes, command-aware unalias

- cert show: the node-DID comparison is contextual; a node-info fetch
  error now prints a note instead of failing the command after the
  certificate and verdict were already shown.
- doctor: loopback detection parses the URL host exactly — substring
  matching misread localhost.example and localhost-in-path URLs.
- init: a detached HEAD gets its own hint (switch to a branch first)
  instead of a guessed 'git push gitlawb main'.
- status: every fetch AND push URL of each remote is inspected — a
  gitlawb:// push URL behind an https fetch URL now counts.
- doctor/install.sh: unalias detection is command-aware — comments,
  'unalias global', and gl-only-in-a-trailing-comment no longer
  suppress the warning (BSD-grep-compatible pattern; ordering vs
  oh-my-zsh load documented as accepted slack). Eight new test cases.
@kevincodex1

Copy link
Copy Markdown
Contributor Author

@coderabbitai please review and approve

@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

@kevincodex1 I’ll review PR #236 and approve it if no blocking issues are found.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@kevincodex1

Copy link
Copy Markdown
Contributor Author

@coderabbitai please make a thorough review here

@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

@kevincodex1 I’m starting a full, thorough review of PR #236, including the certificate-verification paths, Git URL/remote handling, initialization edge cases, shell alias detection, and regression-test coverage.

✅ Action performed

Full review finished.

@beardthelion

Copy link
Copy Markdown
Collaborator

Reviewed the new cert verification path and the doctor/init/status fixes against the node's signing code. The core is sound: the Ed25519 verify fails closed (a bad DID, non-base64 signature, wrong length, or wrong key all render INVALID and --verify exits non-zero), the canonical payload in verify_signature is byte-identical to what gitlawb-node/src/cert.rs::issue_ref_certificate signs (same json! fields and order, no preserve_order anywhere), and all five earlier review findings landed correctly. No shell-injection surface in the git-invoking paths.

One design question, then a few nits.

Design question: what does cert show --verify promise?

The signature is verified against the key embedded in the cert's own node_did, and the node-DID comparison below it is contextual-only: it prints a warning on a mismatch but never affects the --verify exit, and it reads the "current" DID from the same node that served the cert. For checking a cert from your own configured node that is a fine integrity check.

But the module docs frame certs as "receipts proving a push was accepted," and the code warns when a cert was "issued by a different node," which reads like cross-node receipt verification. Under that reading a self-signed cert passes: a hostile source generates a keypair, sets node_did to it, signs the payload, and gets VALID + exit 0. Verifying against a key the cert supplies proves internal consistency, not that a node you trust issued it.

If --verify is only ever meant for a cert from your own node, this is fine as-is, though a doc note would keep VALID from being read as "trusted." If it is meant to verify certs from an untrusted source, it needs an out-of-band anchor: pin or resolve the expected node DID and require cert.node_did to equal it, gating the exit on that match. Your call on the intended scope.

Nits (P3)

  • verify_signature's round-trip test signs and verifies with the CLI's own json!, so it can't catch canonicalization drift from the node. Interop is correct today, but a future added field or a preserve_order bump would make every real cert render INVALID with no test failing. A fixture cert produced by the node's signing path would pin it.
  • The printed unalias gl remediation (install.sh, the brew caveats, gl doctor) uses echo 'unalias gl' >> ~/.zshrc && source ~/.zshrc, which appends a duplicate line on every run and errors on the bare unalias gl when gl is not currently aliased. gl doctor already uses the unalias gl 2>/dev/null form worth matching in the printed advice.
  • Minor: the alias-shadow check is zsh-only (an explicit alias gl=... in ~/.bashrc is not flagged), and is_loopback_url does not treat 127.0.0.0/8 or a trailing-dot host as loopback. Both are fine to leave; noting for completeness.

Otherwise this is in good shape. The cert --verify scope is the only thing I would want settled before it goes in.

…oad form

Settles the review's design question: a valid signature alone proves
internal consistency — the payload was signed by whatever key the
certificate itself names — so a hostile source could self-sign and
pass. --verify now also requires the issuing node_did to match a
trusted anchor: --expect-node when given, else the DID of the node
being queried; unanchorable (node info unreachable, no --expect-node)
fails closed. The displayed verdict wording says what it proves.

Also from review:
- A frozen-literal test pins gl's payload serialization to the node's
  canonical byte form, so serialization drift (new field, a
  preserve_order feature unifying across the workspace) fails a test
  instead of silently rendering every real certificate INVALID.
- The printed unalias remediation uses the idempotent-safe
  'unalias gl 2>/dev/null' form everywhere (doctor, install.sh, brew
  caveats), matching what doctor already suggested.
- Acknowledged, deliberately left: bash-side alias detection and
  127/8-range loopback matching (noted as out of scope for a
  warning-level heuristic).
xdev00 pushed a commit to Gitlawb/node-explorer that referenced this pull request Jul 22, 2026
Matches the CLI's printed advice: 'unalias gl 2>/dev/null' doesn't
error when gl isn't currently aliased (review feedback on
Gitlawb/node#236).

@coderabbitai coderabbitai Bot 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.

🧹 Nitpick comments (2)
crates/gl/src/doctor.rs (1)

360-398: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Consider gating on the active shell to avoid false positives.

check_shell_alias_shadowing only inspects the contents of ~/.zshrc; it never checks whether zsh is actually the user's active shell (e.g. via $SHELL). A stale or dotfiles-synced ~/.zshrc on a bash-only machine would trigger a spurious "shell alias" warning even though gl is never actually shadowed for that user.

♻️ Suggested guard
 fn check_shell_alias_shadowing(home: Option<PathBuf>) -> Option<Check> {
     let home = home?;
+    // Skip when zsh isn't the active shell — a stale .zshrc from dotfiles
+    // sync or an old zsh trial would otherwise produce a false warning.
+    let uses_zsh = std::env::var("SHELL")
+        .map(|s| s.contains("zsh"))
+        .unwrap_or(false);
+    if !uses_zsh {
+        return None;
+    }
     let rc = std::fs::read_to_string(home.join(".zshrc")).ok()?;
🤖 Prompt for 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.

In `@crates/gl/src/doctor.rs` around lines 360 - 398, Update
check_shell_alias_shadowing to first verify that the user’s active shell is zsh,
using the existing environment or platform shell lookup mechanism. Return None
before reading ~/.zshrc when the active shell is not zsh, while preserving the
current alias and oh-my-zsh detection for zsh users.
.github/workflows/release.yml (1)

594-601: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Caveats display unconditionally, unlike the detection-based warnings elsewhere.

Unlike doctor.rs/install.sh, which only warn when the oh-my-zsh git plugin is actually detected, this caveats method always prints the oh-my-zsh-specific note to every Homebrew installer, including bash users or those without the git plugin. Homebrew's caveats runs client-side, so it can check the same conditions (presence of ~/.zshrc, oh-my-zsh git plugin dir, plugins=(...git...), absence of unalias gl) before deciding to print.

🤖 Prompt for 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.

In @.github/workflows/release.yml around lines 594 - 601, The Homebrew formula’s
caveats method unconditionally prints an oh-my-zsh warning. Update caveats to
emit that note only when the client-side checks confirm ~/.zshrc exists, the
oh-my-zsh git plugin is present and enabled in plugins=(...git...), and unalias
gl is absent; otherwise return no caveat while preserving the existing warning
text and remediation.
🤖 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.

Nitpick comments:
In @.github/workflows/release.yml:
- Around line 594-601: The Homebrew formula’s caveats method unconditionally
prints an oh-my-zsh warning. Update caveats to emit that note only when the
client-side checks confirm ~/.zshrc exists, the oh-my-zsh git plugin is present
and enabled in plugins=(...git...), and unalias gl is absent; otherwise return
no caveat while preserving the existing warning text and remediation.

In `@crates/gl/src/doctor.rs`:
- Around line 360-398: Update check_shell_alias_shadowing to first verify that
the user’s active shell is zsh, using the existing environment or platform shell
lookup mechanism. Return None before reading ~/.zshrc when the active shell is
not zsh, while preserving the current alias and oh-my-zsh detection for zsh
users.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1260b3e5-3f9f-4e00-b58d-ad5199c4bfa7

📥 Commits

Reviewing files that changed from the base of the PR and between e14c2d3 and 3fa38dc.

📒 Files selected for processing (7)
  • .github/workflows/release.yml
  • crates/gl/Cargo.toml
  • crates/gl/src/cert.rs
  • crates/gl/src/doctor.rs
  • crates/gl/src/init.rs
  • crates/gl/src/status.rs
  • install.sh

@kevincodex1
kevincodex1 enabled auto-merge July 22, 2026 14:46
@kevincodex1

Copy link
Copy Markdown
Contributor Author

@coderabbitai make a thorough review her eplease

@coderabbitai

coderabbitai Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor
🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '== PR diff stat and changed files =='
git diff --stat HEAD^ HEAD
git diff --name-only HEAD^ HEAD

echo '== Recent commits =='
git log --oneline -8

echo '== Full PR-head diff =='
git diff --find-renames --find-copies HEAD^ HEAD -- \
  crates/gl/Cargo.toml \
  crates/gl/src/cert.rs \
  crates/gl/src/doctor.rs \
  crates/gl/src/init.rs \
  crates/gl/src/status.rs \
  .github/workflows/release.yml \
  install.sh

echo '== Source outlines =='
ast-grep outline crates/gl/src/cert.rs --items all
ast-grep outline crates/gl/src/doctor.rs --items all
ast-grep outline crates/gl/src/init.rs --items all
ast-grep outline crates/gl/src/status.rs --items all

echo '== Targeted references and tests =='
rg -n -C 3 --glob '*.rs' \
  'verify_signature|cmd_show|expect_node|CertCmd::Show|detect_gitlawb_remote|parse_gitlawb_url|is_loopback_url|alias.*gl|init -b|symbolic-ref' \
  crates

Length of output: 64710


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '== PR metadata =='
gh pr view 236 --repo Gitlawb/node --json baseRefName,baseRefOid,headRefName,headRefOid,commits

base="$(gh pr view 236 --repo Gitlawb/node --json baseRefOid --jq .baseRefOid)"
echo "== Complete diff from base ${base} to HEAD =="
git diff --find-renames --find-copies "$base" HEAD -- \
  crates/gl/Cargo.toml crates/gl/src/cert.rs crates/gl/src/doctor.rs \
  crates/gl/src/init.rs crates/gl/src/status.rs .github/workflows/release.yml install.sh

echo '== Certificate command implementation and tests =='
sed -n '1,430p' crates/gl/src/cert.rs

echo '== Node certificate issuer implementation =='
ast-grep outline crates/gitlawb-node/src/cert.rs --items all
rg -n -C 8 'issue_ref_certificate|node_did|signature|/info|node.*did' crates/gitlawb-node/src/cert.rs crates/gl/src/http.rs crates/gl/src/cert.rs

echo '== Tests covering changed status/init/doctor behavior =='
sed -n '130,285p' crates/gl/src/status.rs
sed -n '1,275p' crates/gl/src/init.rs
sed -n '120,575p' crates/gl/src/doctor.rs

Length of output: 107350


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '== Workspace dependency and lockfile references =='
rg -n -C 2 '^\s*base64\s*=|name = "base64"' Cargo.toml Cargo.lock crates/gl/Cargo.toml

echo '== DID parsing and public-key derivation =='
ast-grep outline crates/gitlawb-core/src/did.rs --items all
rg -n -C 8 'impl .*Did|fn from_str|to_verifying_key|enum Did|did:key' crates/gitlawb-core/src/did.rs

echo '== Remaining init and cert test implementations =='
sed -n '245,430p' crates/gl/src/init.rs
sed -n '300,430p' crates/gl/src/cert.rs

echo '== Git remote command usage and status test coverage =='
rg -n -C 4 --glob '*.rs' 'remote", "get-url"|get-url", "--all"|get-url", "--push"|detect_gitlawb_remote' crates

echo '== CLI parser conventions for requires and command tests =='
rg -n -C 3 --glob '*.rs' 'requires = "|try_parse_from|parse_from' crates/gl/src

Length of output: 23371


@kevincodex1 Thorough review complete — I found no blocking issues.

Highlights:

  • cert show --verify now correctly distinguishes signature integrity from issuer trust and fails closed without an anchor.
  • The CLI reconstructs the same canonical payload as the node’s certificate issuer, with a frozen serialization test guarding against drift.
  • Remote detection covers named remotes plus fetch and push URL variants.
  • gl init’s fallback and state-aware push guidance handle unborn branches and detached HEAD.
  • The localhost and zsh alias diagnostics are scoped appropriately and avoid shell-injection concerns.

I inspected the complete PR diff and relevant implementation/tests. I did not execute repository tests because this review sandbox does not run project code.

✅ Action performed

Full review finished.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 2

🧹 Nitpick comments (2)
crates/gl/src/init.rs (1)

178-218: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider extracting the push-guidance logic for direct unit testing.

This match block replaces the exact logic that a past review flagged as buggy (detached-HEAD defaulting to main), yet none of the tests in this file exercise the (branch, has_commits) matrix. Extracting the detection + message selection into a small, pure helper (taking has_commits: bool and branch: Option<String>, returning the lines to print) would let each of the three cases be regression-tested without spawning real git processes for the println behavior.

🤖 Prompt for 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.

In `@crates/gl/src/init.rs` around lines 178 - 218, Extract the push-guidance
selection currently handled by the match in the initialization flow into a small
pure helper that accepts has_commits and branch and returns the output lines or
equivalent message data. Keep git detection and printing at the existing call
site, have the helper preserve all three branch/commit cases including detached
HEAD, and add unit tests covering each case without spawning git processes.
crates/gl/src/cert.rs (1)

246-286: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider replacing the 8 positional &str args with a named struct.

verify_signature and its three test call sites (Lines 364-397) all pass the same 8 same-typed &str values positionally. Nothing in the type system prevents a future edit from transposing e.g. old_sha/new_sha or pusher/node_did — the compiler can't catch it since every parameter has the same type.

♻️ Proposed refactor sketch
+struct CertPayloadFields<'a> {
+    repo_id: &'a str,
+    ref_name: &'a str,
+    old_sha: &'a str,
+    new_sha: &'a str,
+    pusher: &'a str,
+    node_did: &'a str,
+    issued_at: &'a str,
+    signature_b64: &'a str,
+}
+
-fn verify_signature(
-    repo_id: &str,
-    ref_name: &str,
-    old_sha: &str,
-    new_sha: &str,
-    pusher: &str,
-    node_did: &str,
-    issued_at: &str,
-    signature_b64: &str,
-) -> std::result::Result<(), String> {
+fn verify_signature(fields: CertPayloadFields<'_>) -> std::result::Result<(), String> {
+    let CertPayloadFields { repo_id, ref_name, old_sha, new_sha, pusher, node_did, issued_at, signature_b64 } = fields;
     ...
 }
🤖 Prompt for 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.

In `@crates/gl/src/cert.rs` around lines 246 - 286, Replace the positional
parameters of verify_signature with a named parameter struct containing the
eight signature fields, and update its three test call sites to construct and
pass that struct. Use the struct fields when building the payload so values such
as old_sha, new_sha, pusher, and node_did are explicitly associated with their
names.
🤖 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/gl/src/init.rs`:
- Around line 202-218: Update the detached-HEAD branch guidance in the (None, _)
match arm so it works whether a local main branch already exists: avoid always
suggesting git switch -c main, and provide a command or conditional guidance
that switches to the existing branch or creates it only when absent. Keep the
push instruction targeting the selected main branch.

In `@install.sh`:
- Around line 196-208: Update the negative unalias check in the install-time
oh-my-zsh detection block to treat `;`, `&`, and `|` immediately following `gl`
as valid command terminators, matching `doctor.rs`’s `rc_unaliases_gl` behavior.
Preserve the existing handling for whitespace, comments, and end-of-line so an
existing `unalias gl` suppresses the NOTE in all supported command-separator
forms.

---

Nitpick comments:
In `@crates/gl/src/cert.rs`:
- Around line 246-286: Replace the positional parameters of verify_signature
with a named parameter struct containing the eight signature fields, and update
its three test call sites to construct and pass that struct. Use the struct
fields when building the payload so values such as old_sha, new_sha, pusher, and
node_did are explicitly associated with their names.

In `@crates/gl/src/init.rs`:
- Around line 178-218: Extract the push-guidance selection currently handled by
the match in the initialization flow into a small pure helper that accepts
has_commits and branch and returns the output lines or equivalent message data.
Keep git detection and printing at the existing call site, have the helper
preserve all three branch/commit cases including detached HEAD, and add unit
tests covering each case without spawning git processes.
🪄 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

Run ID: 1f493c3a-bb5a-47cc-93e0-fef8af38a31c

📥 Commits

Reviewing files that changed from the base of the PR and between e14c2d3 and c681af7.

📒 Files selected for processing (7)
  • .github/workflows/release.yml
  • crates/gl/Cargo.toml
  • crates/gl/src/cert.rs
  • crates/gl/src/doctor.rs
  • crates/gl/src/init.rs
  • crates/gl/src/status.rs
  • install.sh

Comment thread crates/gl/src/init.rs
Comment on lines 202 to +218
println!();
println!("Ready! Push with:");
println!(" git push gitlawb main");
match (branch, has_commits) {
(Some(branch), true) => {
println!("Ready! Push with:");
println!(" git push gitlawb {branch}");
}
(Some(branch), false) => {
println!("Ready! Nothing is committed yet — commit, then push:");
println!(" git add -A && git commit -m \"initial commit\"");
println!(" git push gitlawb {branch}");
}
(None, _) => {
println!("Ready! HEAD is detached — create or switch to a branch, then push:");
println!(" git switch -c main");
println!(" git push gitlawb main");
}
}

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

git switch -c main fails if main already exists while HEAD is detached.

This correctly stops guessing a branch for the detached-HEAD case (fixing the prior finding), but the fallback hint always suggests creating main with -c. If a local main branch already exists (e.g., user checked out a tag/commit while main still exists), git switch -c main errors with "branch already exists," leaving the user with a broken copy-paste command.

🔧 Proposed fix
         (None, _) => {
             println!("Ready! HEAD is detached — create or switch to a branch, then push:");
-            println!("  git switch -c main");
+            let main_exists = std::process::Command::new("git")
+                .args(["show-ref", "--verify", "--quiet", "refs/heads/main"])
+                .current_dir(&cwd)
+                .status()
+                .map(|s| s.success())
+                .unwrap_or(false);
+            if main_exists {
+                println!("  git switch main");
+            } else {
+                println!("  git switch -c main");
+            }
             println!("  git push gitlawb main");
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
println!();
println!("Ready! Push with:");
println!(" git push gitlawb main");
match (branch, has_commits) {
(Some(branch), true) => {
println!("Ready! Push with:");
println!(" git push gitlawb {branch}");
}
(Some(branch), false) => {
println!("Ready! Nothing is committed yet — commit, then push:");
println!(" git add -A && git commit -m \"initial commit\"");
println!(" git push gitlawb {branch}");
}
(None, _) => {
println!("Ready! HEAD is detached — create or switch to a branch, then push:");
println!(" git switch -c main");
println!(" git push gitlawb main");
}
}
println!();
match (branch, has_commits) {
(Some(branch), true) => {
println!("Ready! Push with:");
println!(" git push gitlawb {branch}");
}
(Some(branch), false) => {
println!("Ready! Nothing is committed yet — commit, then push:");
println!(" git add -A && git commit -m \"initial commit\"");
println!(" git push gitlawb {branch}");
}
(None, _) => {
println!("Ready! HEAD is detached — create or switch to a branch, then push:");
let main_exists = std::process::Command::new("git")
.args(["show-ref", "--verify", "--quiet", "refs/heads/main"])
.current_dir(&cwd)
.status()
.map(|s| s.success())
.unwrap_or(false);
if main_exists {
println!(" git switch main");
} else {
println!(" git switch -c main");
}
println!(" git push gitlawb main");
}
}
🤖 Prompt for 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.

In `@crates/gl/src/init.rs` around lines 202 - 218, Update the detached-HEAD
branch guidance in the (None, _) match arm so it works whether a local main
branch already exists: avoid always suggesting git switch -c main, and provide a
command or conditional guidance that switches to the existing branch or creates
it only when absent. Keep the push instruction targeting the selected main
branch.

Comment thread install.sh
Comment on lines +196 to +208
# oh-my-zsh's default git plugin aliases gl='git pull', which silently shadows
# the gl binary in every interactive zsh (aliases beat PATH). Detect the common
# setup and say so now, at install time, instead of letting the first `gl`
# print git's baffling "fatal: not a git repository".
if [ -f "$HOME/.zshrc" ] && [ -d "$HOME/.oh-my-zsh/plugins/git" ] \
&& grep -Eq '^[[:space:]]*plugins=\(([^)]*[[:space:]])?git([[:space:]][^)]*)?\)' "$HOME/.zshrc" \
&& ! grep -Eq '^([^#]*[[:space:];&|])?unalias[[:space:]]+([^;&|#]*[[:space:]])?gl([[:space:]]|$)' "$HOME/.zshrc"; then
echo ""
echo "NOTE: oh-my-zsh's git plugin aliases gl='git pull', which will shadow"
echo "the gl command in interactive shells. To use gl by name, run:"
echo ""
echo " echo 'unalias gl 2>/dev/null' >> ~/.zshrc && source ~/.zshrc"
fi

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Unalias regex doesn't accept ;/&/| immediately after gl, causing false-positive NOTE.

The negative-lookahead-style check requires gl be followed by whitespace or end-of-line. A line like unalias gl; (no space before the semicolon) won't match, so the install-time NOTE still fires even though gl was already unaliased. doctor.rs's rc_unaliases_gl avoids this by splitting the line on ;/&/| before tokenizing.

🛠️ Proposed fix: accept command separators as valid terminators after `gl`
-  && ! grep -Eq '^([^#]*[[:space:];&|])?unalias[[:space:]]+([^;&|#]*[[:space:]])?gl([[:space:]]|$)' "$HOME/.zshrc"; then
+  && ! grep -Eq '^([^#]*[[:space:];&|])?unalias[[:space:]]+([^;&|#]*[[:space:]])?gl([[:space:];&|]|$)' "$HOME/.zshrc"; then
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# oh-my-zsh's default git plugin aliases gl='git pull', which silently shadows
# the gl binary in every interactive zsh (aliases beat PATH). Detect the common
# setup and say so now, at install time, instead of letting the first `gl`
# print git's baffling "fatal: not a git repository".
if [ -f "$HOME/.zshrc" ] && [ -d "$HOME/.oh-my-zsh/plugins/git" ] \
&& grep -Eq '^[[:space:]]*plugins=\(([^)]*[[:space:]])?git([[:space:]][^)]*)?\)' "$HOME/.zshrc" \
&& ! grep -Eq '^([^#]*[[:space:];&|])?unalias[[:space:]]+([^;&|#]*[[:space:]])?gl([[:space:]]|$)' "$HOME/.zshrc"; then
echo ""
echo "NOTE: oh-my-zsh's git plugin aliases gl='git pull', which will shadow"
echo "the gl command in interactive shells. To use gl by name, run:"
echo ""
echo " echo 'unalias gl 2>/dev/null' >> ~/.zshrc && source ~/.zshrc"
fi
# oh-my-zsh's default git plugin aliases gl='git pull', which silently shadows
# the gl binary in every interactive zsh (aliases beat PATH). Detect the common
# setup and say so now, at install time, instead of letting the first `gl`
# print git's baffling "fatal: not a git repository".
if [ -f "$HOME/.zshrc" ] && [ -d "$HOME/.oh-my-zsh/plugins/git" ] \
&& grep -Eq '^[[:space:]]*plugins=\(([^)]*[[:space:]])?git([[:space:]][^)]*)?\)' "$HOME/.zshrc" \
&& ! grep -Eq '^([^#]*[[:space:];&|])?unalias[[:space:]]+([^;&|#]*[[:space:]])?gl([[:space:];&|]|$)' "$HOME/.zshrc"; then
echo ""
echo "NOTE: oh-my-zsh's git plugin aliases gl='git pull', which will shadow"
echo "the gl command in interactive shells. To use gl by name, run:"
echo ""
echo " echo 'unalias gl 2>/dev/null' >> ~/.zshrc && source ~/.zshrc"
fi
🤖 Prompt for 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.

In `@install.sh` around lines 196 - 208, Update the negative unalias check in the
install-time oh-my-zsh detection block to treat `;`, `&`, and `|` immediately
following `gl` as valid command terminators, matching `doctor.rs`’s
`rc_unaliases_gl` behavior. Preserve the existing handling for whitespace,
comments, and end-of-line so an existing `unalias gl` suppresses the NOTE in all
supported command-separator forms.

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

Approving. The signature-verification path is the substantive change here and it holds up. I rebuilt the node's canonical signing payload on both sides: gl reconstructs the identical 7-field form the node signs, issued_at is stored as TEXT so it round-trips byte-exact, and the single-cert response carries every field the verifier reads. --verify fails closed on every case I threw at it (bad signature, missing or "?" node DID, an unreachable node with no --expect-node, a hostile self-signed cert, malformed base64 or DID input), none of which panic. 299 gl tests pass, and the new tamper test goes red when I stub the verifier to always succeed, so it is load-bearing.

Four P3 polish items below, none blocking, all yours to take or leave.

Findings

  • [P3] Qualify the VALID line so a scripted reader can't misread it. crates/gl/src/cert.rs:184 — under --verify, VALID prints to stdout before the issuer-anchor check can bail, so a valid-signature/untrusted-issuer cert shows VALID on stdout while the command exits non-zero. The exit code is correct and authoritative, but a consumer doing gl cert show ... --verify | grep -q VALID would be misled. Either defer the line until after the anchor passes, or label it VALID (signature only).

  • [P3] Don't suggest git switch -c main when main already exists. crates/gl/src/init.rs:213 — the (None, _) arm always prints git switch -c main, which fails with "a branch named 'main' already exists" on a detached HEAD in a repo that already has main (checkout an old commit or tag, then gl init). It is a printed hint, not an executed command, so recovery is trivial, but the instruction is wrong in a real scenario. Check refs/heads/main first and print git switch main when it exists. This is the open CodeRabbit thread.

  • [P3] Fall back to an assumed scheme in is_loopback_url. crates/gl/src/doctor.rs:345Url::parse needs a scheme, so a scheme-less GITLAWB_NODE=127.0.0.1:7545 returns false and the doctor reports a bare loopback address as an ordinary remote node. The documented http://127.0.0.1:7545 form is detected correctly and this change fixes the real localhost.example false positives, so it is net positive; retrying the parse as http://{value} closes the remaining shorthand gap.

  • [P3] Match the shell unalias regex to the Rust splitter. install.sh:202 vs rc_unaliases_gl in crates/gl/src/doctor.rs — the shell regex requires whitespace or end-of-line after gl, so unalias gl; is not matched and the install NOTE prints even though gl is already unaliased. rc_unaliases_gl splits on ;&| first and handles it, so the two drift. Extending the trailing class to ([[:space:];&|]|$) realigns them. This is the other open CodeRabbit thread.

@kevincodex1
kevincodex1 disabled auto-merge July 22, 2026 15:28
@kevincodex1
kevincodex1 merged commit b75aa2f into main Jul 22, 2026
16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

crate:gl gl — the contributor CLI needs-issue PR has no linked issue

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants