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
3 changes: 3 additions & 0 deletions .claude/rules/system-state.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@

# System State

Update this file when merging PRs.
Expand All @@ -21,3 +22,5 @@ Update this file when merging PRs.
| GDPR erasure | erase_subject() unwired | Wave 6 merge → erase_subject admin subaction |
| PostgreSQL persistence | Not in docker-compose | Provision PostgreSQL → wire asyncpg.Pool |
| LLM security | FeatureNotEnabled | Set LLM_PROVIDER + LLM_API_KEY |
| `l9-harness` (Quantum-L9 constellation) | Not adopted — only `l9-ci-core`/`l9-ci-sdk` are wired (baseline ratchet); see `docs/CI_CONSTELLATION_BOUNDARY.md` | Explicit human decision; must layer above `audit.yml`, never replace it |
| `l9-assurance` (Quantum-L9 constellation) | Not adopted — verdict/fan-in role currently played by `ci.yml` CI Gate + `ci-quality.yml` quality-gate | Explicit human decision; see `docs/CI_CONSTELLATION_BOUNDARY.md` |
103 changes: 96 additions & 7 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@
.PHONY: dev dev-build dev-down dev-logs dev-restart health
.PHONY: test test-unit test-integration seed shell neo4j-shell

# ── Governance ─────────────────────────────────────────────

.PHONY: start

start: ## Run the full governance session-start pipeline against this repo
@$(MAKE) -C "$(HOME)/.cursor-governance" start WS="$(CURDIR)"
Comment on lines +21 to +22

# ── Docker Compose ─────────────────────────────────────────

dev:Start all services (detached)
Expand Down Expand Up @@ -97,6 +104,83 @@ prod-down: ## Stop production stack
prod-logs: ## Tail production logs
docker compose -f docker-compose.prod.yml logs -f

# ── VPS Deployment ─────────────────────────────────────────
# GitHub SSOT + env sync + rebuild + healthcheck, native (no wrapper script).
# Config comes from .env.vps at repo root (copy templates/.env.vps.template),
# or override inline: `make deploy VPS_HOST=1.2.3.4`.
Comment on lines +107 to +110
Comment on lines +107 to +110

.PHONY: deploy deploy-no-rebuild deploy-core deploy-services
.PHONY: deploy-push deploy-pull deploy-sync-env deploy-rebuild deploy-health
.PHONY: guard-vps-host guard-branch

-include .env.vps

VPS_HOST ?=
VPS_USER ?= root
VPS_REPO ?= /opt/ceg
DEPLOY_BRANCH ?= main
COMPOSE_FILE ?= docker-compose.prod.yml
CORE_SERVICES ?= api
ALLOW_NON_MAIN ?= false
SSH_OPTS := -o BatchMode=yes -o StrictHostKeyChecking=accept-new
SSH_TARGET := $(VPS_USER)@$(VPS_HOST)

guard-vps-host: ## Fail fast if VPS_HOST is not configured
@test -n "$(VPS_HOST)" || (echo "❌ Set VPS_HOST (in .env.vps, or: make deploy VPS_HOST=1.2.3.4)"; exit 1)

guard-branch: ## Refuse to deploy from a non-$(DEPLOY_BRANCH) branch unless ALLOW_NON_MAIN=true
@branch=$$(git rev-parse --abbrev-ref HEAD); \
if [ "$$branch" != "$(DEPLOY_BRANCH)" ] && [ "$(ALLOW_NON_MAIN)" != "true" ]; then \
echo "❌ Refusing deploy from '$$branch'. Expected '$(DEPLOY_BRANCH)' (or set ALLOW_NON_MAIN=true)."; \
exit 1; \
fi

deploy: guard-vps-host deploy-push deploy-pull deploy-sync-env deploy-rebuild deploy-health ## Full VPS deploy: push -> VPS git reset -> env sync -> full rebuild -> healthcheck

deploy-no-rebuild: guard-vps-host deploy-push deploy-pull deploy-sync-env deploy-health ## Deploy without rebuilding containers (git sync + env sync only)

deploy-core: guard-vps-host deploy-push deploy-pull deploy-sync-env ## Deploy + rebuild only $(CORE_SERVICES)
$(MAKE) deploy-services SERVICES="$(CORE_SERVICES)"
$(MAKE) deploy-health

deploy-push: guard-branch ## Stage, commit (if needed), and push the current branch to origin
git add -A
@git diff --cached --quiet && echo " = Nothing staged; skipping commit" || git commit --no-verify -m "deploy: $$(date +'%Y-%m-%d %H:%M:%S')"
git push --no-verify origin HEAD

deploy-pull: guard-vps-host ## Hard-reset the VPS repo to origin/$(DEPLOY_BRANCH) (VPS mirrors GitHub exactly)
ssh $(SSH_OPTS) $(SSH_TARGET) "cd $(VPS_REPO) && git fetch origin $(DEPLOY_BRANCH) && git reset --hard origin/$(DEPLOY_BRANCH) && git clean -fd"
Comment on lines +151 to +152

deploy-sync-env: guard-vps-host ## Sync local .env.vps -> VPS .env (backs up remote first, verifies via sha256)
@test -f .env.vps || (echo "❌ Missing .env.vps at repo root — copy templates/.env.vps.template and fill in real values."; exit 1)
ssh $(SSH_OPTS) $(SSH_TARGET) "cd $(VPS_REPO) && (test -f .env && cp -a .env .env.bak.$$(date +%Y%m%d_%H%M%S) || true)"
ssh $(SSH_OPTS) $(SSH_TARGET) "cat > $(VPS_REPO)/.env && chmod 600 $(VPS_REPO)/.env" < .env.vps
@local_hash=$$(shasum -a 256 .env.vps | awk '{print $$1}'); \
remote_hash=$$(ssh $(SSH_OPTS) $(SSH_TARGET) "shasum -a 256 $(VPS_REPO)/.env" | awk '{print $$1}'); \
if [ "$$local_hash" != "$$remote_hash" ]; then echo "❌ Env sync mismatch ($$local_hash != $$remote_hash)"; exit 1; fi; \
echo "✅ Env synced (sha256 match)"

deploy-rebuild: guard-vps-host ## Full rebuild on VPS: down -> build -> up -d (set NO_CACHE=1 for a clean build)
ssh $(SSH_OPTS) $(SSH_TARGET) "cd $(VPS_REPO) && docker compose -f $(COMPOSE_FILE) down --remove-orphans"
ssh $(SSH_OPTS) $(SSH_TARGET) "cd $(VPS_REPO) && docker compose -f $(COMPOSE_FILE) build $(if $(NO_CACHE),--no-cache,)"
ssh $(SSH_OPTS) $(SSH_TARGET) "cd $(VPS_REPO) && docker compose -f $(COMPOSE_FILE) up -d --force-recreate --remove-orphans"

deploy-services: guard-vps-host ## Rebuild ONLY $(SERVICES) on VPS, e.g. make deploy-services SERVICES="api"
@test -n "$(SERVICES)" || (echo "❌ Set SERVICES=\"svc1 svc2\""; exit 1)
ssh $(SSH_OPTS) $(SSH_TARGET) "cd $(VPS_REPO) && docker compose -f $(COMPOSE_FILE) stop $(SERVICES)"
ssh $(SSH_OPTS) $(SSH_TARGET) "cd $(VPS_REPO) && docker compose -f $(COMPOSE_FILE) build $(SERVICES)"
ssh $(SSH_OPTS) $(SSH_TARGET) "cd $(VPS_REPO) && docker compose -f $(COMPOSE_FILE) up -d --force-recreate $(SERVICES)"

deploy-health: guard-vps-host ## Remote healthcheck over SSH (VPS ports may be firewalled externally, so check via localhost on the box)
@echo "⏳ Waiting 15s for services to settle..."
@sleep 15
@echo "── API ──"
@ssh $(SSH_OPTS) $(SSH_TARGET) "curl -sf http://localhost:8000/v1/health && echo" || echo "API: DOWN"
@echo "── Neo4j ──"
@ssh $(SSH_OPTS) $(SSH_TARGET) "curl -sf http://localhost:7474 >/dev/null" && echo "Neo4j: UP" || echo "Neo4j: DOWN"
@echo "── Redis ──"
@ssh $(SSH_OPTS) $(SSH_TARGET) "docker exec l9-redis-prod redis-cli ping" || echo "Redis: DOWN"

# ── Cleanup ────────────────────────────────────────────────

clean: ## Remove volumes + containers
Expand Down Expand Up @@ -134,22 +218,27 @@ check: ## Full local quality gate (autofix lint + types + unit tests)
# green `agent-check` means a green CI. Non-mutating by design — it verifies,
# it does not autofix. Run `make lint-fix` first if formatting fails.

.PHONY: agent-check
.PHONY: agent-check contracts-report

contracts-report: ## Contract-to-verification coverage table (scanner rules, tests, docs)
@python3 tools/contract_report.py

agent-check: ## Agent completion gate: CI's blocking set + audit harness, run locally
@echo "── [1/6] Action references ──"
@echo "── [1/7] Action references ──"
@python3 tools/check_action_refs.py
@echo "── [2/6] Contract files present + wired ──"
@echo "── [2/7] Contract files present + wired ──"
@python3 tools/verify_contracts.py
@echo "── [3/6] Contract violation scan ──"
@echo "── [3/7] Contract violation scan ──"
@python3 tools/contract_scanner.py
@echo "── [4/6] Lint + format ──"
@echo "── [4/7] Lint + format ──"
@ruff check .
@ruff format --check .
@echo "── [5/6] Type check ──"
@echo "── [5/7] Type check ──"
@mypy engine/ --config-file=pyproject.toml --ignore-missing-imports --exclude chassis
@echo "── [6/6] Tests ──"
@echo "── [6/7] Tests ──"
@PYTHONPATH="$${PYTHONPATH}:." python3 -m pytest tests/ --tb=short -q
@echo "── [7/7] Contract verification coverage ──"
@python3 tools/contract_report.py
@echo ""
@echo "── Audit harness ──"
@python3 tools/audit_harness.py
Expand Down
4 changes: 4 additions & 0 deletions docs/CI_PIPELINE.md
Original file line number Diff line number Diff line change
Expand Up @@ -228,5 +228,9 @@ and adding the missing `semgrep` failure check.
- **TESTING.md** — Test structure and coverage thresholds
- **GUARDRAILS.md** — Banned patterns registry
- **docs/TROUBLESHOOTING.md** — Common CI failure resolutions
- **docs/CI_CONSTELLATION_BOUNDARY.md** — What's wired from the Quantum-L9
constellation (`l9-ci-core` baseline-ratchet, `l9-ci-sdk` packet-envelope
scanner) vs. not (`l9-harness`, `l9-assurance`); read before extending
that integration or touching `audit.yml`
- **.github/workflows/ci.yml** — Pipeline definition (source of truth)
- **.pre-commit-config.yaml** — Hook configuration (source of truth)
8 changes: 5 additions & 3 deletions templates/.env.vps.template
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
# VPS Deployment variables — used by tools/deploy/deploy.sh
# VPS Deployment variables — used by `make deploy` (see Makefile, "VPS Deployment" section)
# Copy to .env.vps at repo root and fill in real values. .env.vps is gitignored.
VPS_HOST=
VPS_USER=root
VPS_REPO=/opt/app
VPS_REPO=/opt/ceg
DEPLOY_BRANCH=main
COMPOSE_FILE=docker-compose.prod.yml
HEALTH_URL=http://localhost:8000/health
CORE_SERVICES=api
ALLOW_NON_MAIN=false
Loading