From 5d137018afed8de18a3899ecd72252546d4ec7e8 Mon Sep 17 00:00:00 2001 From: ravendevhub Date: Sat, 29 Aug 2026 12:01:54 +0630 Subject: [PATCH] ci(deps): add reproducible dependency installation check and workflow (#719) - Add scripts/check-dependency-locks.sh auditing npm and cargo lockfiles - Detect package-lock.json and Cargo.lock drift against root manifests - Add GitHub Actions CI workflow enforcing frozen lockfile installation - Document reproducible dependency policy in docs/REPRODUCIBLE_DEPENDENCY_INSTALLATION.md --- .../workflows/reproducible-dependencies.yml | 47 ++++++++ docs/REPRODUCIBLE_DEPENDENCY_INSTALLATION.md | 33 ++++++ scripts/check-dependency-locks.sh | 100 ++++++++++++++++++ 3 files changed, 180 insertions(+) create mode 100644 .github/workflows/reproducible-dependencies.yml create mode 100644 docs/REPRODUCIBLE_DEPENDENCY_INSTALLATION.md create mode 100755 scripts/check-dependency-locks.sh diff --git a/.github/workflows/reproducible-dependencies.yml b/.github/workflows/reproducible-dependencies.yml new file mode 100644 index 00000000..2eaf52b2 --- /dev/null +++ b/.github/workflows/reproducible-dependencies.yml @@ -0,0 +1,47 @@ +name: Reproducible Dependency Installation Check + +on: + pull_request: + paths: + - "**/package.json" + - "**/package-lock.json" + - "**/Cargo.toml" + - "**/Cargo.lock" + - "scripts/check-dependency-locks.sh" + push: + branches: [main] + paths: + - "**/package.json" + - "**/package-lock.json" + - "**/Cargo.toml" + - "**/Cargo.lock" + workflow_dispatch: + +concurrency: + group: deps-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + verify-lockfiles: + name: verify-frozen-lockfiles + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: "20" + + - name: Set up Rust toolchain + uses: dtolnay/rust-toolchain@stable + + - name: Run Reproducible Dependency Verification + run: | + chmod +x scripts/check-dependency-locks.sh + ./scripts/check-dependency-locks.sh diff --git a/docs/REPRODUCIBLE_DEPENDENCY_INSTALLATION.md b/docs/REPRODUCIBLE_DEPENDENCY_INSTALLATION.md new file mode 100644 index 00000000..1c037769 --- /dev/null +++ b/docs/REPRODUCIBLE_DEPENDENCY_INSTALLATION.md @@ -0,0 +1,33 @@ +# 🔒 Reproducible Dependency Installation & Lockfile Policy + +This document details NotifyChain's policy for reproducible, deterministic builds and CI dependency verification (Issue #719). + +--- + +## 1. Core Principles + +To prevent supply-chain drift, build non-determinism, and upstream package discrepancies, all dependencies must be installed from committed lockfiles using **frozen/locked mode**: + +| Component | Language / Toolchain | Committed Lockfile | CI Install Command | +|---|---|---|---| +| **Listener** | Node.js (TypeScript) | `listener/package-lock.json` | `npm ci` | +| **Dashboard** | Node.js (React/Vite) | `dashboard/package-lock.json` | `npm ci` | +| **Smart Contracts** | Rust / Soroban | `contract/Cargo.lock` | `cargo check --locked` | + +--- + +## 2. Preventing Lockfile Drift + +* When editing `package.json`, developers must commit the resulting `package-lock.json`. +* CI strictly executes `scripts/check-dependency-locks.sh` on every pull request touching package manifests. +* If a lockfile drifts out-of-sync with its manifest, CI fails with clear remediation instructions. + +--- + +## 3. Local Verification + +Verify all repository lockfiles locally: + +```bash +./scripts/check-dependency-locks.sh +``` diff --git a/scripts/check-dependency-locks.sh b/scripts/check-dependency-locks.sh new file mode 100755 index 00000000..43c49990 --- /dev/null +++ b/scripts/check-dependency-locks.sh @@ -0,0 +1,100 @@ +#!/usr/bin/env bash +# ============================================================================== +# NotifyChain Reproducible Dependency Installation Checker (Issue #719) +# ============================================================================== +# Verifies that dependencies in Node.js (listener, dashboard) and Rust (contract) +# can be reproduced deterministically from committed lockfiles without drift. +# ============================================================================== +set -u + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +ROOT_DIR="$(cd "${SCRIPT_DIR}/.." && pwd)" + +ERRORS=0 + +echo -e "${BLUE}==================================================================${NC}" +echo -e "${BLUE} 🔒 NotifyChain Reproducible Dependency Installation Verification ${NC}" +echo -e "${BLUE}==================================================================${NC}\n" + +check_npm_lockfile() { + local dir_name="$1" + local full_path="${ROOT_DIR}/${dir_name}" + + printf "%-35s " "Checking ${dir_name} package-lock.json..." + if [ ! -f "${full_path}/package.json" ]; then + echo -e "${YELLOW}SKIPPED (no package.json)${NC}" + return + fi + + if [ ! -f "${full_path}/package-lock.json" ]; then + echo -e "${RED}✗ FAILED (missing package-lock.json)${NC}" + echo -e " ${YELLOW}➔ Action: Run 'npm install --package-lock-only' in ${dir_name}/${NC}" + ERRORS=$((ERRORS + 1)) + return + fi + + if command -v npm >/dev/null 2>&1; then + # Run npm ci dry-run to ensure lockfile is in sync with package.json + if (cd "${full_path}" && npm ci --dry-run >/dev/null 2>&1); then + echo -e "${GREEN}✓ VALID & REPRODUCIBLE${NC}" + else + echo -e "${RED}✗ LOCKFILE DRIFT DETECTED${NC}" + echo -e " ${YELLOW}➔ Action: Lockfile is out of sync with package.json. Run 'npm install' in ${dir_name}/${NC}" + ERRORS=$((ERRORS + 1)) + fi + else + echo -e "${GREEN}✓ PRESENT${NC} (npm CLI not found locally, skipping live dry-run)" + fi +} + +check_cargo_lockfile() { + local dir_name="$1" + local full_path="${ROOT_DIR}/${dir_name}" + + printf "%-35s " "Checking ${dir_name} Cargo.lock..." + if [ ! -f "${full_path}/Cargo.toml" ]; then + echo -e "${YELLOW}SKIPPED (no Cargo.toml)${NC}" + return + fi + + if [ ! -f "${full_path}/Cargo.lock" ]; then + echo -e "${RED}✗ FAILED (missing Cargo.lock)${NC}" + echo -e " ${YELLOW}➔ Action: Run 'cargo generate-lockfile' in ${dir_name}/${NC}" + ERRORS=$((ERRORS + 1)) + return + fi + + if command -v cargo >/dev/null 2>&1; then + if (cd "${full_path}" && cargo check --locked --quiet >/dev/null 2>&1); then + echo -e "${GREEN}✓ VALID & FROZEN${NC}" + else + echo -e "${RED}✗ CARGO LOCK DRIFT DETECTED${NC}" + echo -e " ${YELLOW}➔ Action: Cargo.lock is out of sync. Run 'cargo check' in ${dir_name}/${NC}" + ERRORS=$((ERRORS + 1)) + fi + else + echo -e "${GREEN}✓ PRESENT${NC} (cargo CLI not found locally, skipping live check)" + fi +} + +echo -e "${BLUE}--- [1/2] Verifying Node.js Package Lockfiles ---${NC}" +check_npm_lockfile "listener" +check_npm_lockfile "dashboard" + +echo -e "\n${BLUE}--- [2/2] Verifying Rust Smart Contract Lockfile ---${NC}" +check_cargo_lockfile "contract" + +echo -e "\n${BLUE}==================================================================${NC}" +if [ $ERRORS -eq 0 ]; then + echo -e "${GREEN}🎉 All dependency lockfiles are reproducible with zero drift!${NC}" + exit 0 +else + echo -e "${RED}❌ Found ${ERRORS} dependency lockfile issue(s). Action required above.${NC}" + exit 1 +fi