Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
17 changes: 17 additions & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# API config / startup surfaces
/src/config.ts @metanallok
/src/main.ts @metanallok
/src/app.ts @metanallok

# Solana / Futarchy / external data surfaces
/src/services/solanaService.ts @metanallok
/src/services/futarchyService.ts @metanallok
/src/services/launchpadService.ts @metanallok
/src/services/meteoraService.ts @metanallok
/src/services/databaseService.ts @metanallok
/src/services/externalDatabaseService.ts @metanallok

# Operational scripts
/scripts/safe-update.ts @metanallok
/scripts/backfill.ts @metanallok
/scripts/backfillV06.ts @metanallok
28 changes: 28 additions & 0 deletions .github/branch-protection.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# GitHub protection for `master`

## What the package-protection check does

**Merge-blocking:**
- **Lockfile drift** — `bun.lock` must match `package.json`.
- **Socket package scan** — runs through Bun's security scanner on every install.
- **Exact dependency pinning** — package manifests must use exact versions or `workspace:*`.
- **Minimum dependency age** — newly introduced external npm packages must be at least 14 days old.
- **Phantom deps** — imported packages must be declared in `package.json`.

**Review hint:**
- **Sensitive wallet / program diff** — warns on Solana address literals, program IDs, wallet-routing identifiers, signing paths, and `new PublicKey(` changes. CODEOWNERS is the merge gate for sensitive paths.

## Recommended branch protection for `master`

1. Require a pull request before merging.
2. Require at least 1 approval.
3. Dismiss stale approvals when new commits are pushed.
4. Require conversation resolution before merging.
5. Require status checks to pass before merging.
6. Require the `package-protection` job from the `Package Protection` workflow.
7. Require code owner review so `.github/CODEOWNERS` gates sensitive API, Solana, and operational-script paths.

## Emergency bypass

- Label: `emergency-override` downgrades repo-guard and phantom-dep failures to warnings.
- Lockfile drift / install failures are never bypassed.
179 changes: 179 additions & 0 deletions .github/workflows/package-protection.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
name: Package Protection

on:
pull_request:

permissions:
contents: read
pull-requests: write

jobs:
package-protection:
runs-on: ubuntu-latest
env:
EMERGENCY_BYPASS: ${{ contains(github.event.pull_request.labels.*.name, 'emergency-override') }}

steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
fetch-depth: 0

- name: Fetch PR base branch
run: git fetch origin "${{ github.base_ref }}" --depth=1

- uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
with:
bun-version: 1.3.14

- name: Verify lockfile and scan packages
id: lockfile
continue-on-error: true
run: bun install --frozen-lockfile

- name: Run repository guard checks
id: guard
continue-on-error: true
env:
GITHUB_BASE_REF: ${{ github.base_ref }}
PACKAGE_MIN_AGE_DAYS: 14
REPO_GUARD_SUMMARY_PATH: repo-guard-summary.md
run: bun run repo:guard

- name: Check for undeclared dependencies (phantom deps)
id: knip
if: steps.lockfile.outcome == 'success'
continue-on-error: true
run: |
set +e
bun run deps:check > knip-raw.txt 2>&1
knip_exit=$?
grep -E '^[^:[:space:]]+\.(ts|tsx|js|mjs|cjs|jsx): [^[:space:]]' knip-raw.txt > knip-output.txt || true
echo "--- knip-raw.txt (full) ---"
cat knip-raw.txt
echo "--- knip-output.txt (filtered findings) ---"
cat knip-output.txt
if [ "$knip_exit" -ne 0 ] && [ -s knip-output.txt ]; then
while IFS= read -r line; do
file="${line%%:*}"
pkgs="${line#*: }"
echo "::error file=${file}::Undeclared import(s): ${pkgs}. Declare them in package.json — hoisting works locally but breaks in production Docker."
done < knip-output.txt
fi
exit $knip_exit

- name: Build PR comment
id: comment
env:
LOCKFILE_OUTCOME: ${{ steps.lockfile.outcome }}
GUARD_OUTCOME: ${{ steps.guard.outcome }}
KNIP_OUTCOME: ${{ steps.knip.outcome }}
run: |
{
echo "<!-- package-protection -->"
echo "**Package Protection**"
echo

if [ "$LOCKFILE_OUTCOME" = "success" ]; then
echo "- Lockfile + Socket scan: pass"
else
echo "- Lockfile + Socket scan: fail"
echo " - Run \\`bun install\\` and commit the updated lockfile."
fi

if [ "$GUARD_OUTCOME" = "success" ]; then
echo "- Repo guard: pass"
else
if [ "$EMERGENCY_BYPASS" = "true" ]; then
echo "- Repo guard: bypassed with \\`emergency-override\\`"
else
echo "- Repo guard: fail"
fi
fi

if [ "$KNIP_OUTCOME" = "success" ]; then
echo "- Phantom deps: pass"
elif [ "$KNIP_OUTCOME" = "skipped" ]; then
echo "- Phantom deps: skipped (lockfile check failed — fix that first)"
elif [ -s knip-output.txt ]; then
count=$(wc -l < knip-output.txt | tr -d ' ')
echo "- Phantom deps: fail — ${count} file(s) import packages not declared in package.json."
echo
echo "<details><summary>Undeclared imports (${count})</summary>"
echo
echo '```'
cat knip-output.txt
echo '```'
echo
echo "</details>"
else
echo "- Phantom deps: fail — check logs for details."
fi

echo
if [ -f repo-guard-summary.md ]; then
cat repo-guard-summary.md
else
echo "_Repository guard summary was not generated._"
fi
} > body.md

delimiter="PACKAGE_PROTECTION_BODY_$(date +%s%N)"
echo "body<<${delimiter}" >> "$GITHUB_OUTPUT"
cat body.md >> "$GITHUB_OUTPUT"
echo "${delimiter}" >> "$GITHUB_OUTPUT"

- name: Find existing comment
uses: peter-evans/find-comment@3eae4d37986fb5a8592848f6a574fdf654e61f9e # v3
id: find
with:
issue-number: ${{ github.event.pull_request.number }}
body-includes: "<!-- package-protection -->"

- name: Create or update PR comment
uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4
with:
issue-number: ${{ github.event.pull_request.number }}
comment-id: ${{ steps.find.outputs.comment-id }}
body: ${{ steps.comment.outputs.body }}
edit-mode: replace

- name: Fail if any check failed
env:
LOCKFILE_OUTCOME: ${{ steps.lockfile.outcome }}
GUARD_OUTCOME: ${{ steps.guard.outcome }}
KNIP_OUTCOME: ${{ steps.knip.outcome }}
run: |
failed=()
bypassed=()

if [ "$LOCKFILE_OUTCOME" != "success" ]; then
failed+=("lockfile / Socket scan")
fi

if [ "$GUARD_OUTCOME" != "success" ]; then
if [ "$EMERGENCY_BYPASS" = "true" ]; then
bypassed+=("repo guard")
else
failed+=("repo guard")
fi
fi

if [ "$KNIP_OUTCOME" = "failure" ]; then
if [ "$EMERGENCY_BYPASS" = "true" ]; then
bypassed+=("phantom deps")
else
failed+=("phantom deps")
fi
fi

if [ ${#bypassed[@]} -gt 0 ]; then
echo "::warning::Bypassed via emergency-override label: ${bypassed[*]}"
fi

if [ ${#failed[@]} -gt 0 ]; then
echo "::error::Package Protection failed:"
for item in "${failed[@]}"; do
echo "::error:: - ${item}"
done
exit 1
fi
1 change: 1 addition & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
save-exact=true
Loading
Loading