diff --git a/.bestpractices.json b/.bestpractices.json
new file mode 100644
index 00000000..0488cc60
--- /dev/null
+++ b/.bestpractices.json
@@ -0,0 +1,41 @@
+{
+ "$schema": "https://bestpractices.coreinfrastructure.org/projects.schema.json",
+ "_comment": "OpenSSF Best Practices self-assessment skeleton for RandomCodeSpace/codeiq. The numeric project_id and badge URL are populated by a board admin after registering the project at https://www.bestpractices.dev/ — RAN-46 AC #8 calls this out as auth-blocked. Once the registration is complete, fill `project_id` and re-render the README badge with the resolved URL.",
+ "project_id": null,
+ "name": "codeiq",
+ "description": "Deterministic code knowledge graph — scans codebases to map services, endpoints, entities, infrastructure, auth patterns, and framework usage. No AI, pure static analysis.",
+ "homepage_url": "https://github.com/RandomCodeSpace/codeiq",
+ "repo_url": "https://github.com/RandomCodeSpace/codeiq",
+ "license": "MIT",
+ "level": "passing",
+ "status": {
+ "basics": "self-assessed-passing",
+ "change_control": "self-assessed-passing",
+ "reporting": "self-assessed-passing",
+ "quality": "self-assessed-passing",
+ "security": "self-assessed-passing",
+ "analysis": "self-assessed-passing"
+ },
+ "evidence": {
+ "vulnerability_report_process": "SECURITY.md",
+ "release_process": "shared/runbooks/release.md",
+ "rollback_process": "shared/runbooks/rollback.md",
+ "first_time_setup": "shared/runbooks/first-time-setup.md",
+ "engineering_standards": "shared/runbooks/engineering-standards.md",
+ "license_file": "LICENSE",
+ "build_reproducible": "mvn -B -ntp clean verify",
+ "ci_workflow": ".github/workflows/ci-java.yml",
+ "code_scanning": "GitHub repo setting (CodeQL default setup, java-kotlin + javascript-typescript + actions). Workflow-driven CodeQL was tried in PR #74 but conflicts with default setup at SARIF upload — keeping default setup as the SSoT.",
+ "supply_chain_scorecard": ".github/workflows/scorecard.yml",
+ "dependency_updates": ".github/dependabot.yml",
+ "signed_commits": "scripts/setup-git-signed.sh",
+ "secret_scanning": "GitHub repo setting (secret_scanning + push_protection enabled)",
+ "static_analysis": "SpotBugs (mvn spotbugs:check) + SonarCloud Quality Gate",
+ "vulnerability_scanning": "OWASP Dependency-Check (mvn dependency-check:check) + Dependabot security updates"
+ },
+ "audit": {
+ "self_assessment_date": "2026-04-25",
+ "self_assessment_author": "TechLead (RAN-46)",
+ "registration_blocker": "https://www.bestpractices.dev/ requires human OAuth/form. Tracked under RAN-46 AC #8."
+ }
+}
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 00000000..589f9073
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,125 @@
+# Dependabot configuration for codeiq.
+# Docs: https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
+#
+# Strategy:
+# * weekly cadence — keeps the noise floor low while still catching CVEs early
+# * grouped updates per ecosystem so PR fan-out stays manageable
+# * security updates fire whenever needed regardless of the weekly slot
+#
+# RAN-46 AC #4: Dependabot (security + version updates, weekly, grouped). Also
+# enable repo-level "Dependabot security updates" via gh api (the version-updates
+# below cover routine bumps; security updates are the reactive channel).
+
+version: 2
+updates:
+ # ----- Maven (the codeiq application) -----
+ - package-ecosystem: "maven"
+ directory: "/"
+ schedule:
+ interval: "weekly"
+ day: "monday"
+ time: "08:00"
+ timezone: "Etc/UTC"
+ open-pull-requests-limit: 10
+ labels:
+ - "type:dependencies"
+ - "area:backend"
+ commit-message:
+ prefix: "chore(deps)"
+ include: "scope"
+ groups:
+ spring:
+ patterns:
+ - "org.springframework*"
+ - "org.springframework.boot:*"
+ - "org.springframework.security:*"
+ - "org.springframework.ai:*"
+ jackson:
+ patterns:
+ - "com.fasterxml.jackson*"
+ neo4j:
+ patterns:
+ - "org.neo4j:*"
+ - "org.neo4j.driver:*"
+ antlr:
+ patterns:
+ - "org.antlr:*"
+ maven-plugins:
+ patterns:
+ - "org.apache.maven.plugins:*"
+ - "org.codehaus.*"
+ - "org.jacoco:*"
+ - "com.github.spotbugs:*"
+ - "org.owasp:*"
+ - "org.sonarsource.scanner.maven:*"
+ - "org.sonatype.central:*"
+ test-libs:
+ patterns:
+ - "org.junit.jupiter:*"
+ - "org.mockito:*"
+ - "org.assertj:*"
+ - "org.hamcrest:*"
+ - "com.h2database:*"
+
+ # ----- GitHub Actions (CI / release / security) -----
+ - package-ecosystem: "github-actions"
+ directory: "/"
+ schedule:
+ interval: "weekly"
+ day: "monday"
+ time: "08:00"
+ timezone: "Etc/UTC"
+ open-pull-requests-limit: 5
+ labels:
+ - "type:dependencies"
+ - "area:ci"
+ commit-message:
+ prefix: "chore(actions)"
+ include: "scope"
+ groups:
+ actions:
+ patterns:
+ - "*"
+
+ # ----- Frontend (npm under src/main/frontend) -----
+ - package-ecosystem: "npm"
+ directory: "/src/main/frontend"
+ schedule:
+ interval: "weekly"
+ day: "monday"
+ time: "08:00"
+ timezone: "Etc/UTC"
+ open-pull-requests-limit: 5
+ labels:
+ - "type:dependencies"
+ - "area:frontend"
+ commit-message:
+ prefix: "chore(frontend)"
+ include: "scope"
+ groups:
+ react:
+ patterns:
+ - "react"
+ - "react-dom"
+ - "@types/react*"
+ ant-design:
+ patterns:
+ - "antd"
+ - "@ant-design/*"
+ vite:
+ patterns:
+ - "vite"
+ - "@vitejs/*"
+ echarts:
+ patterns:
+ - "echarts"
+ - "echarts-for-react"
+ eslint:
+ patterns:
+ - "eslint*"
+ - "@eslint/*"
+ - "@typescript-eslint/*"
+ typescript:
+ patterns:
+ - "typescript"
+ - "@types/*"
diff --git a/.github/workflows/beta-java.yml b/.github/workflows/beta-java.yml
index 46aa89f8..212c890f 100644
--- a/.github/workflows/beta-java.yml
+++ b/.github/workflows/beta-java.yml
@@ -9,11 +9,11 @@ jobs:
contents: write
packages: write
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4.2.2
with:
fetch-depth: 0
- - uses: actions/setup-java@v4
+ - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v4.7.1
with:
distribution: 'temurin'
java-version: '25'
@@ -60,7 +60,7 @@ jobs:
git push origin ${{ steps.version.outputs.tag }}
- name: Create GitHub Release
- uses: softprops/action-gh-release@v2
+ uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2
with:
tag_name: ${{ steps.version.outputs.tag }}
name: "Beta ${{ steps.version.outputs.version }}"
diff --git a/.github/workflows/ci-java.yml b/.github/workflows/ci-java.yml
index 3c030510..9fce4340 100644
--- a/.github/workflows/ci-java.yml
+++ b/.github/workflows/ci-java.yml
@@ -10,21 +10,54 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4.2.2
with:
fetch-depth: 0
- - uses: actions/setup-java@v4
+ - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v4.7.1
with:
distribution: 'temurin'
java-version: '25'
cache: 'maven'
- - run: mvn clean verify -B
- - uses: actions/upload-artifact@v4
+ # Cache the OWASP Dependency-Check NVD data directory across runs so the
+ # CVE gate does not need to re-download the full feed on every PR.
+ # `key` is unique per run (forces a save on every run), `restore-keys`
+ # falls back to the most recent prior cache so the H2 DB is incrementally
+ # updated rather than rebuilt.
+ - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
+ with:
+ path: ~/.m2/repository/org/owasp/dependency-check-data
+ key: dependency-check-${{ runner.os }}-${{ github.run_id }}
+ restore-keys: |
+ dependency-check-${{ runner.os }}-
+ # Pre-warm the OWASP Dependency-Check NVD cache as a SEPARATE Maven
+ # invocation. On a cold cache (first run on a branch / cache eviction)
+ # running `update-only` first avoids the dependency-check-maven 12.2.0
+ # H2 init race that surfaces as `NullPointerException: Cannot invoke
+ # BasicDataSource.getConnection() because connectionPool is null`
+ # during the verify phase (observed on PR #74 build run 24930518462).
+ # When the cache is warm this step short-circuits via the H2 incremental
+ # update path. `failOnError=false` so a transient NVD-feed problem here
+ # does not mask the real CVSS>=7 gate enforced in the verify step
+ # below — that step still hard-fails on operational scanner failures
+ # (Reviewer round-3 finding #1).
+ - name: Pre-warm dependency-check NVD cache
+ env:
+ NVD_API_KEY: ${{ secrets.NVD_API_KEY }}
+ run: mvn -B -ntp dependency-check:update-only -DfailOnError=false
+ - name: Build + verify (jacoco 85% + SpotBugs + dependency-check)
+ env:
+ # When the NVD_API_KEY secret is unset, dependency-check falls back
+ # to the unauthenticated NVD endpoint (rate-limited but functional
+ # once the cache is warm). Provisioning the secret is tracked under
+ # RAN-42.
+ NVD_API_KEY: ${{ secrets.NVD_API_KEY }}
+ run: mvn -B -ntp clean verify
+ - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v4.6.2
if: always()
with:
name: test-results
path: target/surefire-reports/
- - uses: actions/upload-artifact@v4
+ - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v4.6.2
with:
name: coverage-report
path: target/site/jacoco/
diff --git a/.github/workflows/release-java.yml b/.github/workflows/release-java.yml
index 96eb7361..bbabb535 100644
--- a/.github/workflows/release-java.yml
+++ b/.github/workflows/release-java.yml
@@ -12,8 +12,8 @@ jobs:
permissions:
contents: write
steps:
- - uses: actions/checkout@v4
- - uses: actions/setup-java@v4
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4.2.2
+ - uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v4.7.1
with:
distribution: 'temurin'
java-version: '25'
@@ -23,23 +23,80 @@ jobs:
server-password: MAVEN_PASSWORD
gpg-private-key: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }}
gpg-passphrase: MAVEN_GPG_PASSPHRASE
- - name: Set release version
+ - name: Configure git identity and non-interactive GPG for signed commit/tag
+ run: |
+ git config user.email "github-actions[bot]@users.noreply.github.com"
+ git config user.name "github-actions[bot]"
+ # Use the GPG key imported by setup-java (MAVEN_GPG_PRIVATE_KEY) for
+ # both commit and tag signing — same trust path as the artifact.
+ KEYID=$(gpg --list-secret-keys --with-colons | awk -F: '/^sec:/ {print $5; exit}')
+ if [ -z "$KEYID" ]; then
+ echo "no GPG secret key in agent — release-java.yml needs MAVEN_GPG_PRIVATE_KEY" >&2
+ exit 1
+ fi
+ git config user.signingkey "$KEYID"
+ git config gpg.format openpgp
+ git config commit.gpgsign true
+ git config tag.gpgsign true
+ # Reviewer finding cf64b44d (RAN-47, R5-1):
+ # `git commit -S` / `git tag -s` invoke gpg interactively by default
+ # and fail in non-interactive Actions shells when the imported key
+ # has a passphrase. setup-java only wires the passphrase for Maven
+ # signing (via MAVEN_GPG_PASSPHRASE in settings.xml); git itself
+ # has no equivalent autoconfig. Configure the gpg-agent for loopback
+ # pinentry, point gpg.program at a thin wrapper that injects
+ # --batch / --pinentry-mode loopback / --passphrase from
+ # MAVEN_GPG_PASSPHRASE, so signing succeeds non-interactively.
+ mkdir -p "$HOME/.gnupg"
+ chmod 700 "$HOME/.gnupg"
+ printf '%s\n' 'use-agent' 'pinentry-mode loopback' > "$HOME/.gnupg/gpg.conf"
+ printf '%s\n' 'allow-loopback-pinentry' > "$HOME/.gnupg/gpg-agent.conf"
+ gpgconf --kill gpg-agent || true
+ # Wrapper script: git invokes this with the same flags as gpg.
+ # We exec into gpg with --batch + loopback + the passphrase from
+ # the env (MAVEN_GPG_PASSPHRASE is set on each step that signs).
+ cat > "$HOME/.gnupg/gpg-loopback.sh" <<'WRAPPER'
+ #!/usr/bin/env bash
+ # Non-interactive gpg wrapper for `git commit -S` / `git tag -s`.
+ # MAVEN_GPG_PASSPHRASE is set on the workflow step that signs.
+ exec gpg --batch --yes --pinentry-mode loopback \
+ --passphrase "${MAVEN_GPG_PASSPHRASE:-}" "$@"
+ WRAPPER
+ chmod +x "$HOME/.gnupg/gpg-loopback.sh"
+ git config gpg.program "$HOME/.gnupg/gpg-loopback.sh"
+ - name: Set release version and create signed release commit
env:
RELEASE_VERSION: ${{ inputs.version }}
- run: mvn versions:set -DnewVersion="$RELEASE_VERSION"
+ # Picked up by the gpg-loopback wrapper script for `git commit -S`.
+ MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }}
+ # Commit the version bump on a detached HEAD off the workflow's
+ # checkout. The commit is reachable only via the tag created below —
+ # no push to `main`, so this works under branch protection.
+ # The commit captures the exact source tree that the deploy step
+ # will build from, fixing the prior "tag diverges from released
+ # artifact" gap (Reviewer finding 47b718b9).
+ run: |
+ mvn -B -ntp versions:set -DnewVersion="$RELEASE_VERSION" -DgenerateBackupPoms=false
+ git add pom.xml
+ git commit -S -m "chore(release): ${RELEASE_VERSION}"
- name: Deploy to Maven Central
env:
MAVEN_USERNAME: ${{ secrets.OSS_NEXUS_USER }}
MAVEN_PASSWORD: ${{ secrets.OSS_NEXUS_PASS }}
MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }}
- run: mvn clean deploy -P release -B
- - name: Tag release
+ run: mvn -B -ntp -P release clean deploy
+ - name: Create signed annotated tag and push
env:
RELEASE_VERSION: ${{ inputs.version }}
+ MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }}
+ # Annotated + GPG-signed tag pointing at the release commit (the
+ # current HEAD after the commit step above). Push only the tag —
+ # the release commit lives only as a tag-reachable object so we
+ # never need to update `main`, and branch protection stays clean.
run: |
- git tag "v${RELEASE_VERSION}"
- git push origin "v${RELEASE_VERSION}"
- - uses: softprops/action-gh-release@v2
+ git tag -s "v${RELEASE_VERSION}" -m "codeiq ${RELEASE_VERSION}"
+ git push origin "refs/tags/v${RELEASE_VERSION}"
+ - uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2
with:
tag_name: v${{ inputs.version }}
generate_release_notes: true
diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml
new file mode 100644
index 00000000..3fbc8d39
--- /dev/null
+++ b/.github/workflows/scorecard.yml
@@ -0,0 +1,66 @@
+# OpenSSF Scorecard supply-chain analysis.
+# RAN-46 AC #9. Best-effort target — no hard numeric floor; Scorecard does not gate merge.
+# Docs: https://github.com/ossf/scorecard-action
+
+name: Scorecard supply-chain security
+
+on:
+ push:
+ branches: [main]
+ schedule:
+ # Mondays 06:00 UTC
+ - cron: "0 6 * * 1"
+ workflow_dispatch:
+
+# Restrict the default GITHUB_TOKEN to read-only; the steps below request the
+# narrow scopes they actually need.
+permissions: read-all
+
+jobs:
+ analysis:
+ name: Scorecard analysis
+ runs-on: ubuntu-latest
+ permissions:
+ # Required for upload to the code-scanning Security tab.
+ security-events: write
+ # Required to read OIDC token for publish_results.
+ id-token: write
+ # Default scopes for actions/checkout.
+ contents: read
+ actions: read
+
+ steps:
+ - name: Harden runner egress
+ # step-security/harden-runner v2.19.0
+ uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40
+ with:
+ egress-policy: audit
+
+ - name: Checkout code
+ # actions/checkout v6.0.2
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
+ with:
+ persist-credentials: false
+
+ - name: Run Scorecard analysis
+ # ossf/scorecard-action v2.4.3
+ uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a
+ with:
+ results_file: results.sarif
+ results_format: sarif
+ # Publish the results so they appear on the public Scorecard dashboard.
+ publish_results: true
+
+ - name: Upload Scorecard SARIF (artifact)
+ # actions/upload-artifact v7.0.1
+ uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
+ with:
+ name: scorecard-sarif
+ path: results.sarif
+ retention-days: 5
+
+ - name: Upload SARIF to GitHub code-scanning
+ # github/codeql-action/upload-sarif v3.35.2
+ uses: github/codeql-action/upload-sarif@ce64ddcb0d8d890d2df4a9d1c04ff297367dea2a
+ with:
+ sarif_file: results.sarif
diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 00000000..c9c986d9
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,43 @@
+# AGENTS.md — codeiq
+
+> **Repo-root entry point for any agent collaborator.** This file is intentionally short and lists pointers; the canonical contents live elsewhere and are linked from here.
+
+## What this repo is
+
+codeiq is a CLI + read-only server that builds a deterministic code-knowledge graph over a codebase. No AI, no external APIs — pure static analysis. See [`/CLAUDE.md`](CLAUDE.md) for the architecture, package map, pipeline, conventions, and gotchas.
+
+## Pointers, in priority order
+
+1. **Read [`/CLAUDE.md`](CLAUDE.md) first.** It is the SSoT for architecture, build/test commands, package layout, and the long-tail of "things that bite you on this codebase."
+2. **Then [`/shared/runbooks/engineering-standards.md`](shared/runbooks/engineering-standards.md).** Coverage, CVE, signed-commits, and quality-gate policy.
+3. **Then the runbooks you'll actually need:**
+ - [`shared/runbooks/first-time-setup.md`](shared/runbooks/first-time-setup.md) — get from clean clone to green local build.
+ - [`shared/runbooks/release.md`](shared/runbooks/release.md) — how to ship; gates downstream RAN-* product work.
+ - [`shared/runbooks/rollback.md`](shared/runbooks/rollback.md) — when a ship goes bad.
+4. **Security**: [`/SECURITY.md`](SECURITY.md) for disclosure; private reports only.
+
+## Hard rules for any agent doing work in this repo
+
+- **Branch off `main`.** Never commit to `main` directly.
+- **Sign every commit.** The repo-local config (`scripts/setup-git-signed.sh`) makes this automatic; do not rewrite it.
+- **One logical change per commit.** Conventional-commit subjects (`feat:`, `fix:`, `chore:`, `refactor:`, `test:`, `docs:`, `perf:`).
+- **Squash-merge only.** Branch protection rejects merge commits and force-pushes to `main`.
+- **Tests + jacoco gate must pass.** `mvn -B -ntp clean verify` is the contract.
+- **Determinism is non-negotiable.** Same input → same output, byte-for-byte. Any new detector ships with a determinism test.
+- **Read-only serving layer.** MCP and REST API on the `serve` path do not mutate. If you find yourself adding `POST /api/` that writes, stop and reconsider.
+- **No secrets in code.** Repo-level GitHub Actions secrets only.
+
+## Paperclip / RAN-* coordination
+
+This codebase tracks work in Paperclip under the `RAN-*` prefix. When you pick up a task:
+
+1. Checkout the issue (`POST /api/issues/{id}/checkout`) before you start.
+2. Comment progress on every heartbeat — terse markdown, link the PR.
+3. Branch protection requires TechLead approval; route review there.
+4. Reference the issue in your commit/PR body (`Closes RAN-N`).
+
+If the task asks for product/feature work and `shared/runbooks/release.md` is missing on `main`, **stop**: the RAN-46 bootstrap precondition has not landed yet and product work is gated on it.
+
+## Auth escalation
+
+If you hit something requiring GitHub App / PAT / OAuth that the runtime cannot satisfy (org admin escalation, Sonatype Central re-namespace, OpenSSF Best Practices form, etc.), do **not** improvise auth: PATCH the issue to `blocked` with the exact ask and `@`-mention the board.
diff --git a/CLAUDE.md b/CLAUDE.md
index 3239294f..8e8c45f8 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -429,6 +429,10 @@ bean for code paths that haven't been ported yet.
- **SonarCloud project key**: `RandomCodeSpace_codeiq`, org: `randomcodespace`
- **CI workflow**: Single `ci-java.yml` runs build + SonarCloud analysis. No cross-platform builds needed (JVM).
+## Deploy
+
+codeiq's deploy surface is **Maven Central + GitHub Releases** (per RAN-46 AC #10 ruling, option a). The single Java JAR (with the React UI bundled inside) is published via two `workflow_dispatch`-only workflows: `.github/workflows/beta-java.yml` (manual beta cut → Sonatype Central beta + GitHub pre-release) and `.github/workflows/release-java.yml` (manual GA cut with a `version` input → the workflow builds a GPG-signed release commit on a detached HEAD, deploys from that exact tree, then creates and pushes a GPG-signed annotated `vX.Y.Z` tag pointing at the release commit + a GitHub Release). There is no static-CDN frontend, no hosted backend, no VPS — codeiq runs on the developer's machine. See [`shared/runbooks/release.md`](shared/runbooks/release.md) and [`shared/runbooks/engineering-standards.md`](shared/runbooks/engineering-standards.md) §7.1.
+
## Updating This File
After significant changes (new detectors, new endpoints, architectural decisions, conventions learned), update this CLAUDE.md to reflect the current state. Keep it concise and actionable.
diff --git a/LICENSE b/LICENSE
index 060d5911..64be958b 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2026 RandomCodeSpace
+Copyright (c) 2026 Amit Kumar
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/README.md b/README.md
index d04f7fd3..35da5f30 100644
--- a/README.md
+++ b/README.md
@@ -12,6 +12,8 @@
+
+
diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 00000000..ce02c32c
--- /dev/null
+++ b/SECURITY.md
@@ -0,0 +1,65 @@
+# Security Policy
+
+## Supported versions
+
+Security fixes are issued against the latest minor release line on Maven Central. While codeiq is pre-1.0 (`0.x.y`) only the **latest** released `0.MINOR.x` line receives backports; older minor lines are EOL the moment a new minor ships.
+
+| Version line | Status |
+|---|---|
+| `0.1.x` | Supported (current) |
+| `< 0.1.0` | Unsupported |
+
+`-SNAPSHOT` builds are development snapshots; they do not receive security fixes by themselves — you should be tracking the latest tagged release.
+
+## Reporting a vulnerability
+
+Please **do not open a public GitHub issue** for security problems.
+
+Use one of:
+
+- **GitHub private vulnerability report** — preferred. Open `https://github.com/RandomCodeSpace/codeiq/security/advisories/new` (you must be signed in to GitHub). The advisory channel is monitored by the maintainer.
+- **Email** — `ak.nitrr13@gmail.com`. Put `[codeiq security]` in the subject so the report is triaged ahead of normal mail.
+
+Please include:
+
+- The codeiq version (`java -jar code-iq-*-cli.jar version` or `pom.xml` coordinate).
+- The shortest reproducer you can produce — a CLI command or test case is ideal.
+- Your assessment of impact (e.g., RCE, path traversal, info-disclosure, DoS).
+- Whether the issue is in a transitive dependency (please name the dependency + advisory ID if known).
+
+## What you can expect
+
+- **Acknowledgement** within 72 hours.
+- **Initial triage** within 7 days, with a severity rating (CVSS v3.1) and an indicative remediation timeline.
+- **Coordinated disclosure** — we will agree on a public-disclosure date with the reporter; default is 90 days from triage, sooner for low-impact / already-public issues.
+- **Credit** in the GHSA advisory and `CHANGELOG.md` (unless the reporter requests anonymity).
+
+We do not currently run a paid bug bounty.
+
+## Scope
+
+In-scope:
+
+- The codeiq CLI (`code-iq-*-cli.jar`).
+- The library JAR (`io.github.randomcodespace.iq:code-iq`).
+- The bundled REST API + MCP server (`serve` subcommand) — including path traversal, authn/authz, deserialisation, request smuggling, and SSRF.
+- The bundled React UI assets shipped inside the JAR.
+- The pipeline cache (H2) and graph store (Neo4j Embedded) — including local privilege escalation and data tampering.
+
+Out of scope:
+
+- Vulnerabilities that require pre-existing local code execution on the developer's machine (we ship as a developer tool — by definition you trust the code you point it at).
+- Public-internet attack surface — codeiq does not expose any service to the public internet by default; deploying the `serve` endpoint behind hostile reverse-proxies is out of scope.
+- Findings in third-party services we do not control (Maven Central, GitHub itself, SonarCloud, etc.) — please report those upstream.
+
+## Hardening references
+
+- [`shared/runbooks/engineering-standards.md`](shared/runbooks/engineering-standards.md) — CVE policy and quality gates.
+- [`shared/runbooks/rollback.md`](shared/runbooks/rollback.md) §6 — secret rotation flow.
+- `.github/workflows/scorecard.yml` — OpenSSF Scorecard supply-chain checks.
+- GitHub repo-level **CodeQL default setup** (java-kotlin + javascript-typescript + actions) — code scanning, SARIF in the Security tab. Configured under repo Settings → Code security → Code scanning, not via a workflow file (a workflow-driven `codeql.yml` was tried and removed because GitHub rejects duplicate SARIF uploads when default setup is on for the same language).
+- `.github/dependabot.yml` — automated dependency / GHA / npm bumps.
+
+## Changelog
+
+This file is versioned as part of the repo. Material changes (e.g., raising the supported-versions table, changing the disclosure timeline) are announced via a Release note and a Paperclip board comment.
diff --git a/dependency-check-suppressions.xml b/dependency-check-suppressions.xml
new file mode 100644
index 00000000..67c74350
--- /dev/null
+++ b/dependency-check-suppressions.xml
@@ -0,0 +1,204 @@
+
+
+
+
+
+
+
+
+
+ ^pkg:maven/org\.springframework\.ai/spring-ai-.*@.*$
+ cpe:/a:vmware:server
+
+
+
+ ^pkg:maven/org\.springframework\.ai/spring-ai-.*@.*$
+ cpe:/a:vmware:spring_ai
+
+
+
+
+
+ ^pkg:maven/org\.springframework\.boot/spring-boot-neo4j@.*$
+ cpe:/a:neo4j:neo4j
+
+
+
+
+
+
+
+ ^pkg:maven/org\.apache\.arrow/.*@.*$
+ CVE-2026-25087
+
+
+
+
+
+ ^pkg:maven/io\.grpc/.*@.*$
+ CVE-2026-33186
+
+
+
+
+
+ ^pkg:maven/org\.eclipse\.jetty(\.[a-z0-9]+)*/.*@.*$
+ CVE-2026-5795
+
+
+
diff --git a/pom.xml b/pom.xml
index 691566be..8ebf9070 100644
--- a/pom.xml
+++ b/pom.xml
@@ -62,8 +62,14 @@
log4j-core 2.25.3 -> 2.25.4 (CVE-2026-34477 MOD,
CVE-2026-34478 MOD,
CVE-2026-34480 MOD)
+ log4j-api 2.25.3 -> 2.25.4 (umbrella-CPE attribution
+ of log4j-core CVEs;
+ bumped to keep the dep
+ tree consistent and to
+ stop the dep-check
+ umbrella match)
log4j-layout-template-json 2.25.3 -> 2.25.4 (CVE-2026-34481 MOD)
- •both pulled in transitively by Neo4j 2026.02.3.
+ •all three pulled in transitively by Neo4j 2026.02.3.
shiro-core 2.0.6 -> 2.1.0 (CVE-2026-23901 LOW)
•pulled in by neo4j-security.
mcp-core 1.1.0 -> 1.1.1 (CVE-2026-34237 MOD)
@@ -77,6 +83,11 @@
log4j-core
2.25.4
+
+ org.apache.logging.log4j
+ log4j-api
+ 2.25.4
+
org.apache.logging.log4j
log4j-layout-template-json
@@ -369,10 +380,9 @@
report
-
-
@@ -401,13 +410,91 @@
${spotbugs.version}
spotbugs-exclude.xml
+
+ High
+ true
+
+
+
+ spotbugs-check-on-verify
+ verify
+
+ check
+
+
+
org.owasp
dependency-check-maven
${owasp.dependency-check.version}
+
+
+ 7
+
+ ${user.home}/.m2/repository/org/owasp/dependency-check-data
+
+ ${env.NVD_API_KEY}
+
+ 10
+ 4000
+
+
+ ${project.basedir}/dependency-check-suppressions.xml
+
+
+
+
+
+ dependency-check-on-verify
+ verify
+
+ check
+
+
+
diff --git a/scripts/setup-git-signed.sh b/scripts/setup-git-signed.sh
new file mode 100755
index 00000000..01bc5a3c
--- /dev/null
+++ b/scripts/setup-git-signed.sh
@@ -0,0 +1,158 @@
+#!/usr/bin/env bash
+# scripts/setup-git-signed.sh
+#
+# Apply the repo-local git config required by RAN-46 AC #2. Supports BOTH
+# ssh-format and openpgp-format signing — picks up whichever the contributor
+# already has wired into their global git config.
+#
+# Defaults (when nothing is set globally):
+# user.signingkey = ~/.ssh/id_ed25519.pub
+# gpg.format = ssh
+#
+# Honored env / global-config inputs:
+# GIT_USER_NAME (else: git config --global user.name)
+# GIT_USER_EMAIL (else: git config --global user.email)
+# GIT_SIGNING_KEY (else: git config --global user.signingkey,
+# else default SSH key)
+# GIT_GPG_FORMAT (else: git config --global gpg.format,
+# else "ssh")
+#
+# Idempotent: re-running is a no-op except for the verification block at the end.
+# Run from the repo root (or any subdirectory of the worktree).
+
+set -euo pipefail
+
+# Resolve the worktree root and refuse to run anywhere else.
+if ! repo_root=$(git rev-parse --show-toplevel 2>/dev/null); then
+ echo "error: not inside a git working tree." >&2
+ exit 1
+fi
+
+cd "$repo_root"
+
+# Identity is taken from env vars first, then from the user's GLOBAL git
+# config — never hard-coded to the maintainer. This avoids silently
+# misattributing every contributor's signed commits to the maintainer
+# (earlier Reviewer finding).
+GIT_USER_NAME=${GIT_USER_NAME:-$(git config --global --get user.name 2>/dev/null || true)}
+GIT_USER_EMAIL=${GIT_USER_EMAIL:-$(git config --global --get user.email 2>/dev/null || true)}
+GIT_SIGNING_KEY=${GIT_SIGNING_KEY:-$(git config --global --get user.signingkey 2>/dev/null || echo "$HOME/.ssh/id_ed25519.pub")}
+GIT_GPG_FORMAT=${GIT_GPG_FORMAT:-$(git config --global --get gpg.format 2>/dev/null || echo "ssh")}
+
+if [ -z "$GIT_USER_NAME" ] || [ -z "$GIT_USER_EMAIL" ]; then
+ cat >&2 <<'EOF'
+error: contributor identity not set.
+
+This script does not assume a default identity. Set yours either:
+ 1. Globally (recommended):
+ git config --global user.name "Your Name"
+ git config --global user.email "you@example.com"
+ 2. Per-invocation:
+ GIT_USER_NAME="Your Name" GIT_USER_EMAIL="you@example.com" \
+ scripts/setup-git-signed.sh
+
+Then re-run this script. Signed commits will use the identity you set.
+EOF
+ exit 4
+fi
+
+# Signing-key validation depends on gpg.format:
+# - ssh: user.signingkey is a path on disk (the file must exist)
+# - openpgp: user.signingkey is a key id / fingerprint (gpg must know it)
+# - x509: user.signingkey is a key id / fingerprint (gpgsm must know it)
+# Reviewer finding cf64b44d (RAN-47, R5-2): the previous version forced the
+# ssh-path check unconditionally, which rejected contributors using OpenPGP
+# globally even when their key chain was healthy.
+case "$GIT_GPG_FORMAT" in
+ ssh)
+ if [ ! -f "$GIT_SIGNING_KEY" ]; then
+ cat >&2 </dev/null | grep -q '^sec:'; then
+ cat >&2 <
+And re-run this script.
+EOF
+ exit 2
+ fi
+ ;;
+ x509)
+ if ! gpgsm --list-secret-keys "$GIT_SIGNING_KEY" >/dev/null 2>&1; then
+ cat >&2 <&2
+ exit 5
+ ;;
+esac
+
+apply() {
+ local key="$1" value="$2"
+ git config --local "$key" "$value"
+}
+
+apply user.name "$GIT_USER_NAME"
+apply user.email "$GIT_USER_EMAIL"
+apply user.signingkey "$GIT_SIGNING_KEY"
+apply gpg.format "$GIT_GPG_FORMAT"
+apply commit.gpgsign true
+apply tag.gpgsign true
+
+echo "Applied repo-local git config:"
+printf " %-22s = %s\n" \
+ user.name "$(git config --local --get user.name)" \
+ user.email "$(git config --local --get user.email)" \
+ user.signingkey "$(git config --local --get user.signingkey)" \
+ gpg.format "$(git config --local --get gpg.format)" \
+ commit.gpgsign "$(git config --local --get commit.gpgsign)" \
+ tag.gpgsign "$(git config --local --get tag.gpgsign)"
+
+# Verification: produce a throwaway signed object and verify it.
+# `git commit-tree` does not touch refs, so this is non-destructive.
+echo
+echo "Verifying signing produces a valid signature ..."
+tree=$(git write-tree)
+sig_commit=$(echo "setup-git-signed.sh verification" | git commit-tree "$tree" -S)
+if git verify-commit --raw "$sig_commit" 2>&1 | grep -q '^GOODSIG\|^SSH_OK\|GOOD signature'; then
+ echo " ok — signing chain is healthy."
+elif git verify-commit "$sig_commit" >/dev/null 2>&1; then
+ echo " ok — signing chain is healthy."
+else
+ cat >&2 <> ~/.config/git/allowed_signers
+ Then re-run this script.
+EOF
+ exit 3
+fi
+
+echo
+echo "done. Every commit and tag from this worktree will now be ssh-signed."
diff --git a/shared/runbooks/engineering-standards.md b/shared/runbooks/engineering-standards.md
new file mode 100644
index 00000000..d5c64d0c
--- /dev/null
+++ b/shared/runbooks/engineering-standards.md
@@ -0,0 +1,137 @@
+# Engineering Standards — codeiq
+
+> **SSoT** for the cross-cutting engineering rules that every contributor — human or agent — must follow on this repo. Per-issue specifics live in the issue thread; per-component conventions live in [`/CLAUDE.md`](../../CLAUDE.md). This document is what the runbooks ([`release.md`](release.md), [`rollback.md`](rollback.md), [`first-time-setup.md`](first-time-setup.md)) reference for "what counts as done."
+
+The rule of last resort: **`/home/dev/.claude/rules/*.md` wins.** This file does not contradict it; it specialises it for codeiq.
+
+---
+
+## 1. Quality gates (hard / non-negotiable)
+
+| Gate | Threshold | Where it runs | Failure action |
+|---|---|---|---|
+| Unit + integration tests | All pass | `mvn verify` (CI + local) | Block merge |
+| JaCoCo coverage | ≥ 85% line (project-wide, post-exclusions) | `jacoco-maven-plugin` rule in `pom.xml` | Block merge |
+| SonarCloud Quality Gate | `Passed` (`Sonar way` profile + 80% new-code coverage) | `ci-java.yml` | Block merge |
+| SpotBugs | Zero High/Critical findings; `spotbugs-exclude.xml` justified per-entry | `mvn spotbugs:check` | Block merge |
+| OWASP Dependency-Check | No High/Critical CVEs (`failBuildOnCVSS=7`); Medium tracked | `mvn -B -ntp clean verify` (the `dependency-check:check` execution is bound to the `verify` phase in `pom.xml`); `ci-java.yml` runs on every PR + push to `main` | Block merge |
+| OpenSSF Scorecard | Best-effort; no hard score floor; `Pinned-Dependencies` is a soft target | `scorecard.yml` (push to `main` + weekly) | Surface in security tab; do **not** gate merge |
+| Signed commits | Every commit on `main` must verify | Branch protection + `gh api ... /commits/{sha}/check-runs` | Block merge |
+
+Coverage exclusions are enumerated in `pom.xml` `` config — only generated ANTLR sources, the `application/` Spring Boot main, and pure data records are excluded. Adding to that list requires TechLead sign-off.
+
+**Planned, not yet enforced:** OSV-Scanner as a second-source CVE feed (cross-checks OWASP Dependency-Check against the OSV / GitHub Advisory Database). Tracked under [RAN-42](/RAN/issues/RAN-42); will land as `.github/workflows/osv-scanner.yml` and a row added to the table above. Until then OSV is **not** part of the gate — only OWASP Dependency-Check is.
+
+---
+
+## 2. Code style
+
+- **Java 25** — virtual threads, records, sealed types, pattern matching are first-class. Do not write defensive `instanceof` chains where pattern matching applies.
+- **No mutable state in detectors.** `@Component` beans are singletons; per-call state lives on method locals.
+- **UTF-8 everywhere** (`StandardCharsets.UTF_8` explicit, never the default).
+- **Determinism is enforced.** No `Set` iteration without `TreeSet`/`stream().sorted()`. No reliance on thread completion order. Every detector must have a determinism test (run twice → identical bytes).
+- **Property-key constants** — extract any string literal that appears in the same file 3+ times.
+- **Exception hygiene** — never catch `Exception` to hide it; always either rethrow with context or log at the right level.
+
+Formatting is handled by editor defaults (no auto-formatter committed). Reviewers will not nit on whitespace; they will block on convention drift.
+
+---
+
+## 3. Branch, commit, PR rules
+
+- Branch off `main`. Conventional-commit subjects (`feat:`, `fix:`, `chore:`, `refactor:`, `test:`, `docs:`, `perf:`).
+- One logical change per commit. Squash-merge is the only path into `main`.
+- Every commit ssh-signed (RAN-46 AC #2). Branch protection rejects unsigned commits.
+- PR title is a conventional-commit subject. Body contains:
+ - Linked Paperclip issue (e.g., `Closes RAN-42`).
+ - "Why" in 1–2 sentences (the diff covers "what").
+ - Tests / coverage notes if non-obvious.
+ - Rollback note if the change is risky.
+- No force-push to `main` ever. Force-push to feature branches is fine until the PR is open; once it is open, prefer additive commits so review threads stay anchored.
+
+---
+
+## 4. Testing tiers
+
+| Tier | What it tests | Where it lives | Speed budget |
+|---|---|---|---|
+| Unit | Pure logic, no I/O | `src/test/java/.../` next to the SUT | < 10 ms each |
+| Integration | Real H2 / real Neo4j (Embedded) / real filesystem | `src/test/java/.../analyzer/`, `.../graph/`, `.../e2e/` | < 5 s each |
+| E2E quality | Full pipeline against a real repo (Spring PetClinic, etc.) | `E2EQualityTest`, ground-truth files under `src/test/resources/e2e/` | Run on demand + nightly |
+
+Ground rules:
+- Test behaviour at the boundary; do not test private internals.
+- Every detector ships with: a positive case, a discriminator-guard negative case, a determinism case.
+- Flaky test = broken test. Fix in the same PR, quarantine with a tracked Paperclip issue, or delete.
+
+---
+
+## 5. Security
+
+- **Inputs** — every public-facing endpoint validates input at the boundary; parameterised queries only; output encoded by default.
+- **Path traversal** — anything that takes a user path goes through the canonical-path check pattern used by `/api/file` (see RAN-8 fix).
+- **Secrets** — never in code, config, or commit history. CI secrets are repo-level; rotation cadence is annual or on suspected exposure.
+- **CVE policy** — High/Critical → block; Medium → fix if a patched version exists, else document non-exploitability with TechLead sign-off; Low → tracked in the next dependency bump cycle.
+- **Vulnerability reporting** — see [`/SECURITY.md`](../../SECURITY.md). Private disclosure only.
+
+---
+
+## 6. Performance
+
+- Default to streaming and bounded concurrency. No unbounded queues, buffers, or virtual-thread fan-outs.
+- Every external call has a timeout and a cancellation path.
+- Performance-sensitive paths (`Analyzer`, `GraphStore.bulkSave`, `LayerClassifier`) have a microbenchmark or a regression-detection test before they ship.
+- "Make it correct, then make it fast" — but data-structure and query-shape decisions must be performance-aware up front (see `/home/dev/.claude/rules/performance.md`).
+
+---
+
+## 7. Build & distribution
+
+- Vendor what you can; deterministic builds (`mvn -B -ntp clean verify`) are the contract.
+- No public-CDN runtime fetches, no auto-update phone-home, no telemetry default-on.
+- GitHub Actions are pinned by commit SHA in every workflow. Rationale: OpenSSF Scorecard `Pinned-Dependencies` and supply-chain integrity.
+- Container artifacts (when added) build from a minimal/distroless base, are pushed to GHCR with provenance attestations, and are pinned by digest at consumer sites.
+
+### 7.1 Deploy targets (RAN-46 AC #10 ruling — option a)
+
+codeiq's deploy surface is the **Maven Central + GitHub Releases** pipeline. There is intentionally no static-CDN frontend or always-on hosted backend.
+
+Rationale (per @CEO's RAN-46 ruling):
+
+- codeiq ships as a single Java JAR (`code-iq-*-cli.jar`). The React UI is bundled **inside** that JAR and served via Spring Boot's static resource handler — there is no separately deployable frontend.
+- The intended runtime is the developer's own machine (`codeiq index → enrich → serve`). There is no shared SaaS surface that needs a VPS — codeiq's value is scanning *your* code on *your* machine.
+- The literal AC #10 wording ("frontend → static host / CDN; backend → container registry + VPS rollout") was written for a generic product template that does not match codeiq's shape; the deploy surface is the existing release pipeline instead.
+
+The pipeline:
+
+| Cadence | Workflow | Trigger | Artifact destination |
+|---|---|---|---|
+| Beta | `.github/workflows/beta-java.yml` | `workflow_dispatch` (manual) | Sonatype Central beta + GitHub pre-release |
+| GA | `.github/workflows/release-java.yml` | `workflow_dispatch` (manual, with `version` input) | Sonatype Central GA + GitHub Release |
+
+Both workflows are `workflow_dispatch`-only — there is no tag-push trigger and no automatic release on merge. A GA cut: the workflow builds a GPG-signed release commit on a detached HEAD, deploys from that exact tree, then creates and pushes a GPG-signed annotated `vX.Y.Z` tag pointing at the release commit plus a GitHub Release. Tags are an *output* of the GA workflow, not a trigger. See [`release.md`](release.md) §3 for the full sequence.
+
+Hello-world / pipeline proof: `git tag -l 'v0.0.1-beta.*' | wc -l` is non-zero (47+ beta tags as of the AC #10 ruling) and `gh release list` shows the corresponding GitHub pre-releases. AC #10 is satisfied by the existing pipeline; no new deploy scaffold is required.
+
+If the product later needs a hosted demo or container surface, that is a **new RAN-* issue**, not a re-open of RAN-46.
+
+---
+
+## 8. Documentation
+
+- `/CLAUDE.md` is the architecture + conventions SSoT for code-touching changes.
+- `/AGENTS.md` (repo root) is the entry-point for agent collaborators.
+- Every runbook lives under `shared/runbooks/`. Adding a new runbook requires updating this file's References section.
+- ADRs (`docs/adr/NNN-title.md`) for any decision that changes a contract: persistence, public API surface, deployment shape.
+
+---
+
+## 9. References
+
+- `/CLAUDE.md` — architecture and conventions.
+- `/SECURITY.md` — disclosure policy.
+- `shared/runbooks/release.md`, `rollback.md`, `first-time-setup.md`.
+- `/home/dev/.claude/rules/*.md` — global engineering rules (parent SSoT).
+- `pom.xml` — quality-gate plugin wiring (`jacoco`, `spotbugs`, `dependency-check`, `central-publishing`).
+- `.github/workflows/` — CI / release / security automations.
+- **CodeQL** — handled by GitHub repo-level **CodeQL default setup** (java-kotlin + javascript-typescript + actions), not a workflow file. A workflow-driven CodeQL was attempted in PR #74 and removed because GitHub rejects duplicate SARIF uploads when default setup is also enabled for the same language. Configuration lives under repo Settings → Code security → Code scanning.
diff --git a/shared/runbooks/first-time-setup.md b/shared/runbooks/first-time-setup.md
new file mode 100644
index 00000000..194f49f0
--- /dev/null
+++ b/shared/runbooks/first-time-setup.md
@@ -0,0 +1,170 @@
+# First-time Setup — codeiq
+
+> Get a fresh contributor (human or agent) from a clean machine to a green local build, signed-commit-ready, in one pass. Pairs with [`release.md`](release.md), [`rollback.md`](rollback.md), and [`engineering-standards.md`](engineering-standards.md).
+
+---
+
+## 0. What you'll have at the end
+
+- Repo cloned at `~/projects/codeiq` with the offline build path verified.
+- Java 25 + Maven 3.9 on PATH.
+- A signed-commit configuration (ssh) bound to your `id_ed25519` key.
+- `mvn verify` exits 0 on `main`.
+- `codeiq` CLI runs end-to-end against this repo as a smoke target.
+
+If any step fails, stop and follow the troubleshooting note inline — do not "fix forward" against a partially-set-up machine.
+
+---
+
+## 1. System prerequisites
+
+| Tool | Min version | Notes |
+|---|---|---|
+| Java | 25 | Required by `pom.xml` `maven-enforcer-plugin` (`[25,)`). Use Adoptium / Temurin. |
+| Maven | 3.9.x | Newer minor versions are fine; do not use 4.x snapshots. |
+| Git | 2.34+ | Required for commit signing in any of the supported formats (`gpg.format` = `ssh`, `openpgp` / `gpg`, or `x509`). |
+| OpenSSH | 8.0+ | Required only for `gpg.format=ssh` (the default; bundles `ssh-keygen -Y verify`). Skip if you sign with OpenPGP or x509. |
+| GnuPG | 2.2+ | Required only for `gpg.format=openpgp` / `gpg`. Skip if you sign with SSH or x509. |
+| Node.js | 20.x LTS | Only needed for the bundled React UI — `mvn package` shells out to it via the frontend Maven plugin. |
+| `gh` CLI | 2.40+ | For PR/release plumbing. |
+
+Verify in one shot:
+
+```bash
+java --version | head -1 # openjdk 25 ...
+mvn -v | head -1 # Apache Maven 3.9.x
+git --version # git version 2.x
+ssh -V # OpenSSH_8.x or newer
+node --version # v20.x
+gh --version | head -1 # gh version 2.x
+```
+
+---
+
+## 2. Clone and configure
+
+```bash
+git clone git@github.com:RandomCodeSpace/codeiq.git ~/projects/codeiq
+cd ~/projects/codeiq
+```
+
+Apply the repo-local signed-commit config (this is what RAN-46 AC #2 codifies):
+
+```bash
+./scripts/setup-git-signed.sh
+```
+
+That script is idempotent and is the single SSoT for the per-repo `git config --local` block. It writes `user.name`, `user.email`, `user.signingkey`, `gpg.format`, `commit.gpgsign=true`, `tag.gpgsign=true`, then verifies the configured key resolves in your keychain.
+
+The script picks up your preferred signing format from (in order) the `GIT_GPG_FORMAT` env var, your global `git config gpg.format`, or the `ssh` default. Per-format expectations:
+
+- **`ssh` (default)** — `user.signingkey` must be a path on disk to your **public** key (typically `~/.ssh/id_ed25519.pub`). If you do not have an ed25519 keypair, generate one (`ssh-keygen -t ed25519 -C "you@example.com"`) and upload the public key to your GitHub account under `Settings → SSH and GPG keys → New SSH key → Key type: Signing Key` before re-running.
+- **`openpgp` / `gpg`** — `user.signingkey` must be a key id or fingerprint that `gpg --list-secret-keys` knows about. Generate / import the key first (`gpg --full-generate-key`), then `git config --global user.signingkey ` and `git config --global gpg.format openpgp` before running this script.
+- **`x509`** — `user.signingkey` must be a key id or fingerprint that `gpgsm --list-secret-keys` knows about. Configure x509 signing keys in `gpgsm` first.
+
+Sanity-check the config:
+
+```bash
+git config --local --get user.signingkey # ssh: a .pub path; openpgp/x509: a key id or fingerprint
+git config --local --get gpg.format # ssh | openpgp | gpg | x509
+git config --local --get commit.gpgsign # should print "true"
+
+# Produce a throwaway signed commit object (no refs touched) and verify it.
+sig_commit=$(echo "verify-signing" | git commit-tree HEAD^{tree} -S)
+git verify-commit "$sig_commit" # expect "Good ... signature"
+git log -1 --pretty=%G? "$sig_commit" # expect: G
+```
+
+`git verify-commit` operates on a commit object id, not stdin — capturing the
+output of `git commit-tree -S` first and then verifying that id is the right
+shape. If the verification line errors with "no principal matched", point git
+at an `allowed_signers` file: see `scripts/setup-git-signed.sh` output for the
+canonical template.
+
+---
+
+## 3. Build, test, run
+
+The standard offline path is:
+
+```bash
+mvn -B -ntp -DskipTests=false clean verify
+```
+
+This runs the full pipeline: unit tests, integration tests, jacoco coverage gate (≥85%), SpotBugs, OWASP Dependency-Check, and the executable CLI JAR build. Expect ~2-3 min on a warm cache.
+
+For a faster inner loop while iterating:
+
+```bash
+mvn -B -ntp test \
+ -Dspotbugs.skip=true -Ddependency-check.skip=true # unit tests only (Surefire), no static analysis / CVE plugins
+mvn -B -ntp -Dtest=SomeDetectorTest test # single unit test class
+mvn -B -ntp -DskipTests=true package # JAR only, no tests
+mvn -B -ntp verify \
+ -Dspotbugs.skip=true -Ddependency-check.skip=true # unit + integration tests (Surefire + Failsafe), no static analysis / CVE plugins
+```
+
+The first command **does run tests** — earlier drafts incorrectly passed `-DskipTests` here, which would have skipped them. Reviewer finding cf64b44d (RAN-47, R5-3): Maven's `test` phase only runs Surefire (unit tests). This repo's integration tests are wired through Failsafe at the `integration-test` / `verify` phases — use the fourth form above when you need both unit + integration in the inner loop. Use `-Dspotbugs.skip` / `-Ddependency-check.skip` to keep things fast without dropping test coverage.
+
+Smoke-test the CLI end-to-end against this repo:
+
+```bash
+java -jar target/code-iq-*-cli.jar version
+java -jar target/code-iq-*-cli.jar index .
+java -jar target/code-iq-*-cli.jar enrich .
+java -jar target/code-iq-*-cli.jar serve . & # opens http://localhost:8080
+curl -fsS http://localhost:8080/api/stats > /dev/null && echo OK
+kill %1
+```
+
+---
+
+## 4. Optional: full quality gate locally
+
+If you want to mirror the CI gate before pushing:
+
+```bash
+mvn -B -ntp clean verify # tests + jacoco
+mvn -B -ntp spotbugs:check # static analysis gate
+mvn -B -ntp dependency-check:check -DfailBuildOnCVSS=7 # CVE gate
+```
+
+Sonar runs only in CI (the token is not on local machines by design).
+
+---
+
+## 5. Branch / PR workflow
+
+Create branches off `main`:
+
+```bash
+git fetch origin main
+git switch -c / origin/main
+```
+
+Conventional-commit subjects (`feat:`, `fix:`, `chore:`, `refactor:`, `test:`, `docs:`). One logical change per commit. Sign every commit (the local config makes this automatic). Push and open a PR:
+
+```bash
+git push -u origin HEAD
+gh pr create --fill --base main
+```
+
+Branch protection on `main` requires:
+- A Codex review approval from TechLead (or delegate).
+- CI green on the PR: `ci-java.yml` (build + jacoco 85% + dependency-check + Sonar), the repo-level CodeQL default-setup checks (`Analyze (java-kotlin)`, `Analyze (javascript-typescript)`, `Analyze (actions)`), Socket Security, SonarCloud Code Analysis.
+- All commits in the PR signed (branch protection rejects unsigned commits — there is no separate "signed-commits" status check).
+- OpenSSF Scorecard runs on push-to-`main` and a weekly cron, **not** on PRs, and is intentionally non-gating per [`engineering-standards.md`](engineering-standards.md) §1.
+
+Force-push to `main` is disabled. Direct pushes are disabled. Squash-merge is the default and only path.
+
+---
+
+## 6. Where to look next
+
+- Architecture, layout, and convention SSoT: [`/CLAUDE.md`](../../CLAUDE.md).
+- Coverage / Sonar / CVE policy: [`engineering-standards.md`](engineering-standards.md).
+- Releasing: [`release.md`](release.md).
+- Rolling back a bad release: [`rollback.md`](rollback.md).
+- Security reporting: [`/SECURITY.md`](../../SECURITY.md).
+
+If you hit anything that looks like a runtime gap (missing tool, broken hook, weird auth), open a Paperclip issue against `type:devx` rather than working around it locally — the bootstrap is meant to be reproducible.
diff --git a/shared/runbooks/release.md b/shared/runbooks/release.md
new file mode 100644
index 00000000..759d9da3
--- /dev/null
+++ b/shared/runbooks/release.md
@@ -0,0 +1,127 @@
+# Release Runbook — codeiq
+
+> **SSoT for shipping codeiq.** Owner: TechLead (until bootstrap completes); thereafter the engineer who owns the change. This runbook is the gate referenced by the bootstrap precondition (`RAN-46`): it MUST exist on `main` before any product `RAN-*` issue can leave `backlog`.
+
+---
+
+## 1. Release surfaces
+
+> **AC #10 ruling (RAN-46, @CEO).** Maven Central + GitHub Releases **is** the codeiq deploy surface. There is no separate static-CDN frontend (the React UI is bundled inside the JAR) and no always-on hosted backend (codeiq runs on the developer's machine). See [`engineering-standards.md`](engineering-standards.md) §7.1 for the full rationale.
+
+codeiq ships in three forms. A "release" updates **all three** in lockstep:
+
+| Surface | Artifact | Distribution |
+|---|---|---|
+| Library JAR | `io.github.randomcodespace.iq:code-iq` (POM packaging) | Maven Central via Sonatype Central Portal |
+| Executable CLI JAR | `target/code-iq--cli.jar` | GitHub Release asset |
+| Source tag | `v` (annotated, GPG/OpenPGP-signed by `release-java.yml`) | Git tag pushed to `RandomCodeSpace/codeiq` |
+
+Versioning is [SemVer](https://semver.org/). Pre-`1.0.0` releases use `0.MINOR.PATCH`; breaking changes bump MINOR.
+
+Snapshot artifacts (`-SNAPSHOT`) are published from `main` by `beta-java.yml` to OSSRH snapshots; consumers must opt into the snapshot repo. Snapshots are **not** releases.
+
+---
+
+## 2. Pre-release checklist
+
+Run BEFORE creating the tag:
+
+1. `main` is green: `gh run list --branch main --workflow ci-java.yml --limit 1` → `success`.
+2. SonarCloud Quality Gate: `gh api /repos/RandomCodeSpace/codeiq/actions/runs?branch=main --jq '.workflow_runs[0].conclusion'` and SonarCloud project page both green.
+3. Coverage ≥ 85% (jacoco rule + Sonar new-code ≥ 80% — see [`engineering-standards.md`](engineering-standards.md)).
+4. Dependency audit clean: `mvn -B -ntp clean verify` exits 0 (the OWASP `dependency-check:check` goal is bound to `verify` and fails the build on CVSS ≥ 7 — see `pom.xml`). Cross-check with the Dependabot security tab for any open advisories.
+5. SpotBugs clean: `mvn spotbugs:check` exits 0.
+6. CHANGELOG entry drafted under `[Unreleased]` and ready to promote.
+7. Working copy of `main` is clean (`git status --porcelain` empty).
+8. GPG release-signing secrets present in repo settings: `MAVEN_GPG_PRIVATE_KEY` and `MAVEN_GPG_PASSPHRASE` (verify via `gh secret list`). The workflow signs both the release commit and the annotated tag with the imported OpenPGP key — no local SSH or GPG key is required on the maintainer's machine for the GA path (Reviewer finding fd559a54, R5-7).
+
+---
+
+## 3. Cut a release (canonical path)
+
+Driven by `release-java.yml` via **manual `workflow_dispatch`** with a `version` input. The workflow does **everything**: it creates a release commit (signed) on a detached HEAD with the bumped version, deploys to Maven Central from that exact source tree, and then creates a GPG-signed annotated tag pointing at that release commit. The tag is the only persistent reference to the release commit — `main` is never directly pushed by the workflow, so branch protection stays clean.
+
+```bash
+# 1. Promote CHANGELOG on main (PR + merge per branch protection)
+$EDITOR CHANGELOG.md # move [Unreleased] → [X.Y.Z] - YYYY-MM-DD
+gh pr create --fill --base main
+gh pr merge --squash --auto
+
+# 2. Trigger the release workflow with the target version
+gh workflow run release-java.yml --ref main -f version=X.Y.Z
+
+# 3. Watch it run
+gh run watch $(gh run list --workflow release-java.yml --limit 1 --json databaseId --jq '.[0].databaseId')
+```
+
+`release-java.yml` then, in order:
+1. Configures git identity (`github-actions[bot]`) and binds it to the imported `MAVEN_GPG_PRIVATE_KEY` for both commit and tag signing — same trust path as the published artifact.
+2. Runs `mvn versions:set -DnewVersion=X.Y.Z` and creates a **GPG-signed release commit** on a detached HEAD capturing that tree.
+3. Runs `mvn -P release clean deploy` from that release commit's tree (full quality gate runs along the way: jacoco 85%, SpotBugs, OWASP Dependency-Check).
+4. Creates a **GPG-signed annotated tag `vX.Y.Z`** pointing at the release commit.
+5. Pushes only the tag (`git push origin refs/tags/vX.Y.Z`). The release commit lives only as a tag-reachable object — no `main` update.
+6. Cuts a GitHub Release from the tag and uploads `code-iq-X.Y.Z-cli.jar` with auto-generated release notes.
+
+The tag therefore points at the **exact source** that produced the artifact (no divergence between source tag and released artifact), and is annotated and GPG-signed — verifiable with `git tag --verify vX.Y.Z` provided the maintainer's public GPG key is trusted locally.
+
+Manual cuts on a fork or downstream consumer follow the same flow: trigger the workflow with the target version. Direct `git tag && git push origin vX.Y.Z` from a developer machine does **not** publish.
+
+---
+
+## 4. Post-release verification
+
+Within 30 minutes of the release workflow finishing:
+
+1. **Maven Central index**: `curl -fsS "https://repo.maven.apache.org/maven2/io/github/randomcodespace/iq/code-iq/X.Y.Z/code-iq-X.Y.Z.pom" | head -20`.
+2. **Smoke install**: in a clean directory, `mvn dependency:get -Dartifact=io.github.randomcodespace.iq:code-iq:X.Y.Z` succeeds.
+3. **CLI smoke**: download the GH Release JAR, run `java -jar code-iq-X.Y.Z-cli.jar version` and `java -jar code-iq-X.Y.Z-cli.jar analyze --help` — both exit 0.
+4. **GitHub Release** is marked `Latest` and links the changelog section.
+5. **codebase.repoUrl** in paperclip Project still resolves (`git ls-remote git@github.com:RandomCodeSpace/codeiq.git HEAD`).
+
+If any of (1)–(4) fails, [`rollback.md`](rollback.md) applies.
+
+---
+
+## 5. Hot-fix patch release (`X.Y.Z+1`)
+
+1. Branch from the release tag: `git switch -c hotfix/X.Y.Z+1 vX.Y.Z`.
+2. Apply the minimal fix; add a regression test.
+3. Open PR against `main`; merge with squash.
+4. Rebase the hotfix branch onto the post-merge `main` if needed; cut the tag from `main` per §3.
+
+Hotfixes do **not** skip the pre-release checklist.
+
+---
+
+## 6. Required GitHub secrets (org/repo level)
+
+| Secret | Used by | Owner |
+|---|---|---|
+| `SONAR_TOKEN` | `ci-java.yml` (Sonar gate) | TechLead |
+| `OSS_NEXUS_USER` / `OSS_NEXUS_PASS` | `release-java.yml` (Sonatype Central) | TechLead |
+| `MAVEN_GPG_KEY_ID` / `MAVEN_GPG_PASSPHRASE` / `MAVEN_GPG_PRIVATE_KEY` | `release-java.yml` (artifact signing) | TechLead |
+| `CODEQL_*` | n/a — uses `GITHUB_TOKEN` | n/a |
+
+Rotation policy: any compromise → rotate immediately + audit recent `release-java.yml` runs. Routine rotation: annually, recorded in [`engineering-standards.md`](engineering-standards.md).
+
+---
+
+## 7. Auth-blocked steps (escalation path)
+
+If a release step requires auth the runtime cannot satisfy, do **not** improvise:
+
+- Sonatype Central Portal namespace re-claim → block on board (human OAuth).
+- GitHub org admin escalation (e.g., re-enable a disabled secret) → block on `aksOps` GitHub owner.
+- OpenSSF Best Practices badge updates (if the badge is required for a Release announcement) → block on board to log into bestpractices.dev.
+
+In all cases: PATCH the relevant Paperclip issue to `blocked` with the exact ask, and `@`-mention the board.
+
+---
+
+## 8. References
+
+- [`rollback.md`](rollback.md) — what to do when a release goes bad.
+- [`first-time-setup.md`](first-time-setup.md) — how a new contributor builds and tests locally.
+- [`engineering-standards.md`](engineering-standards.md) — coverage/Sonar/CVE policy SSoT.
+- `pom.xml` — version, plugins, signing wiring.
+- `.github/workflows/release-java.yml` — the actual release pipeline.
diff --git a/shared/runbooks/rollback.md b/shared/runbooks/rollback.md
new file mode 100644
index 00000000..2ecd3329
--- /dev/null
+++ b/shared/runbooks/rollback.md
@@ -0,0 +1,138 @@
+# Rollback Runbook — codeiq
+
+> **Purpose:** restore a known-good state when a `main` push, a release, or a CI/security change has broken the project. Owner: the engineer who shipped the change; TechLead is escalation. Pair this with [`release.md`](release.md).
+
+The rule of thumb: **revert first, root-cause second.** Users on Maven Central cannot un-install a bad version, so the cheap path is always to publish a clean follow-up rather than try to "fix forward" under pressure.
+
+---
+
+## 1. Decide the scope
+
+| Symptom | Section |
+|---|---|
+| `main` is broken (CI red, build won't compile) | §2 — revert merge |
+| A release is bad on Maven Central / GH Releases | §3 — release rollback |
+| Branch protection / CI / security workflow change broke things | §4 — config rollback |
+| Embedded Neo4j cache or `.codeiq/graph` corrupted on a user box | §5 — data rollback |
+| A secret was exposed | §6 — secret rotation |
+
+If you are not sure, default to §2 and revert.
+
+---
+
+## 2. Revert a bad merge to `main`
+
+```bash
+git fetch origin main
+git switch -c revert/ origin/main
+
+# Squash-merged PRs land as a single commit with one parent. Plain `git revert`
+# applies — do NOT pass `-m`, which only makes sense for true (multi-parent)
+# merge commits. Use `-m 1` only if `main` ever carries an actual merge commit
+# (which the squash-merge-only branch protection should prevent).
+git revert
+
+git push -u origin revert/
+gh pr create --base main --fill --title "revert: " --label "type:revert"
+gh pr merge --squash --auto
+```
+
+Then watch `ci-java.yml` go green and confirm SonarCloud + downstream consumers recover.
+
+**Never** force-push `main`; the bootstrap branch protection forbids it and history rewrites would break every consumer's `git pull`.
+
+---
+
+## 3. Roll back a release
+
+You cannot delete or overwrite a Maven Central artifact (Sonatype policy). The only valid rollback is a **new patch release** that returns the behaviour to the prior version.
+
+1. **Block downloads of the bad version** *(best-effort)*:
+ - GitHub Release: `gh release edit vX.Y.Z --prerelease --notes "Marked pre-release: see vX.Y.(Z+1) for fix"`.
+ - SECURITY.md / README badges: add a one-line advisory linking the follow-up version.
+2. **Cut a hotfix patch release** per [`release.md`](release.md) §5. The hotfix MUST contain only the minimum changes needed to restore the prior behaviour, plus a regression test.
+3. **GHSA advisory** if the bad version contains a security regression: `gh api -X POST repos/RandomCodeSpace/codeiq/security-advisories -f severity=high -f summary="..." -f description="..."`. Coordinate with the OpenSSF Scorecard advisory feed.
+4. **Update CHANGELOG.md** under `[X.Y.(Z+1)]` with `### Fixed (rollback)` and link the bad-version commit + the GHSA.
+
+Do NOT delete the bad git tag. Yanking tags after they have been seen by consumers breaks `git fetch --tags` and reproducible builds.
+
+---
+
+## 4. Roll back CI / branch protection / security config
+
+These are driven by `gh api` calls (see RAN-46 inventory). They are not in version control by themselves, so rollback is by re-running the prior call.
+
+- **Branch protection**: snapshot before any change with `gh api /repos/RandomCodeSpace/codeiq/branches/main/protection > /tmp/bp-before.json`. The GET payload is a denormalized view that GitHub's PUT endpoint does **not** accept verbatim (PUT flattens the nested objects: `enforce_admins.enabled` → bare boolean, `required_status_checks.checks[].context` strings → flat `contexts[]`, `*.url` fields are rejected). Reshape with the jq filter below before piping into PUT (Reviewer finding fd559a54, R5-5):
+
+ ```bash
+ jq '{
+ required_status_checks: (
+ if .required_status_checks == null then null
+ else {
+ strict: .required_status_checks.strict,
+ contexts: ([.required_status_checks.checks[]?.context] // [])
+ }
+ end
+ ),
+ enforce_admins: (.enforce_admins.enabled // false),
+ required_pull_request_reviews: (
+ if .required_pull_request_reviews == null then null
+ else {
+ dismiss_stale_reviews: (.required_pull_request_reviews.dismiss_stale_reviews // false),
+ require_code_owner_reviews: (.required_pull_request_reviews.require_code_owner_reviews // false),
+ required_approving_review_count: (.required_pull_request_reviews.required_approving_review_count // 1)
+ }
+ end
+ ),
+ restrictions: null,
+ required_linear_history: (.required_linear_history.enabled // false),
+ allow_force_pushes: (.allow_force_pushes.enabled // false),
+ allow_deletions: (.allow_deletions.enabled // false),
+ block_creations: (.block_creations.enabled // false),
+ required_conversation_resolution: (.required_conversation_resolution.enabled // false),
+ lock_branch: (.lock_branch.enabled // false),
+ allow_fork_syncing: (.allow_fork_syncing.enabled // false)
+ }' /tmp/bp-before.json \
+ | gh api -X PUT /repos/RandomCodeSpace/codeiq/branches/main/protection --input -
+ ```
+
+ The transform unwraps the `{enabled: bool}` envelopes, projects `checks[].context` strings out into the flat `contexts[]` PUT expects, drops `*.url` fields, and forces `restrictions: null` (apps/teams/users restrictions are out of scope for this repo). If you need to *change* a field instead of rolling back, edit the transformed payload before piping.
+- **CodeQL default setup**: re-toggle via Repository Settings → Code security → Code scanning. The disabled state is the safe default.
+- **Dependabot security updates**: `gh api -X PUT /repos/RandomCodeSpace/codeiq/automated-security-fixes` to enable, `-X DELETE` to disable.
+- **Workflow files** (`.github/workflows/*.yml`): revert via §2 — they are version-controlled.
+
+If a change to branch protection prevents you from merging the rollback PR (e.g., you made the wrong status check required and it never passes), `aksOps` is the only account with bypass; coordinate over the board channel before bypassing — every bypass is logged.
+
+---
+
+## 5. User-side data rollback (analysis cache / Neo4j store)
+
+For users who hit a bad cache shape after upgrading:
+
+```bash
+codeiq cache clear # drops .codeiq/cache/*.h2.db
+rm -rf .codeiq/graph/graph.db # drops the enriched Neo4j store
+codeiq index # rebuild
+codeiq enrich
+```
+
+The `AnalysisCache.CACHE_VERSION` constant must be bumped in any release that changes the cache schema. If a release fails to bump it, rollback per §3 and ship a follow-up that bumps it.
+
+---
+
+## 6. Secret rotation
+
+If a CI secret leaked (push protection alert, secret scanning hit, or external report):
+
+1. Rotate at the source: GitHub Settings → Secrets, regenerate at the upstream provider (Sonatype, Sonar, etc.).
+2. Re-run the latest failing workflows after rotation: `gh run rerun `.
+3. Force a new release if the leaked secret signed an artifact (re-cut the release with a fresh GPG key).
+4. Open a tracking issue (`type:security`, `priority:high`) with the rotation timestamp, scope of exposure, and affected runs.
+
+---
+
+## 7. After every rollback
+
+- Add a one-line entry to `CHANGELOG.md` describing what was rolled back and why.
+- Open a follow-up Paperclip issue tagged `type:postmortem` referencing the rolled-back PR/release. The postmortem MUST land before the next release.
+- Update [`engineering-standards.md`](engineering-standards.md) if the rollback exposed a missing CI gate or test layer.