Makefile
Rama main · réplica de lectura del repositorio original.
SHELL := /bin/bash
.DEFAULT_GOAL := help
DEV_IMAGE := psyb0t/decidealot-dev
CPU_IMAGE := psyb0t/decidealot
CUDA_IMAGE := psyb0t/decidealot
TORCHBASE_CPU_IMAGE := psyb0t/torchbase:py3.12-torch2.14-latest-cpu
TORCHBASE_CUDA_IMAGE := psyb0t/torchbase:py3.12-torch2.14-latest-cu126
TORCH_VENV_DIRECTORY := /opt/torch-venv
MIN_TEST_COVERAGE := 90
TAG = $(shell awk -F\" '/^version *= */ {print "v" $$2; exit}' pyproject.toml)
UID := $(shell id -u)
GID := $(shell id -g)
DOCKER_SOCK := /var/run/docker.sock
DOCKER_GID := $(shell stat -c '%g' $(DOCKER_SOCK) 2>/dev/null || echo 0)
DEPENDENCY_CUTOFF := 2026-09-16T22:52:02Z
MODEL_EXCEPTION_CUTOFF := 2026-09-23T13:10:08Z
DOCS_SITE := .testing/docs-site
DOCS_IMAGE := ghcr.io/actions/jekyll-build-pages:v1.0.13@sha256:6791ebfd912185ed59bfb5fb102664fa872496b79f87ff8b9cfba292a7345041
DEV_RUN := docker run --rm --init --user $(UID):$(GID) -e HOME=/tmp \
-e PYTHONPATH=/work/src -e VIRTUAL_ENV=/opt/venv -e PATH=/opt/venv/bin:/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin \
-v $(CURDIR):/work -w /work $(DEV_IMAGE)
DEV_TOOL_RUN := docker run --rm --init --user $(UID):$(GID) -e HOME=/tmp \
-v $(CURDIR):/work -w /work $(DEV_IMAGE):tools
# Sibling fixtures publish on the host daemon's loopback interface.
DEV_RUN_DIND := docker run --rm --init --network host --user $(UID):$(GID) \
--group-add $(DOCKER_GID) -e HOME=/tmp -e PYTHONPATH=$(CURDIR)/src -e VIRTUAL_ENV=/opt/venv -e PATH=/opt/venv/bin:/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin \
-v $(CURDIR):$(CURDIR) -w $(CURDIR) \
-v $(DOCKER_SOCK):$(DOCKER_SOCK) $(DEV_IMAGE)
.PHONY: help dev-image dev-tools-image shell pkg-lock pkg-add pkg-update pkg-upgrade pkg-remove model-lock dep format lint lint-fix audit sec test test-unit test-integration test-coverage test-real test-real-cuda test-real-clm test-real-jev generate build build-cuda build-all build-test build-test-cuda run run-cuda restart restart-cuda stop status audit-compose audit-compose-cuda docs version clean
help: ## List supported operations
@awk 'BEGIN {FS = ":.*## "}; /^[a-zA-Z0-9_.-]+:.*## / {printf "%-22s %s\n", $$1, $$2}' $(MAKEFILE_LIST)
dev-image: ## Build the isolated development toolchain
docker build -f Dockerfile.dev -t $(DEV_IMAGE) .
dev-tools-image: ## Build the lockfile-only development toolchain
docker build --target tools -f Dockerfile.dev -t $(DEV_IMAGE):tools .
shell: dev-image ## Open an interactive development shell
docker run --rm -it --init --user $(UID):$(GID) -e HOME=/tmp -e PYTHONPATH=/work/src -e VIRTUAL_ENV=/opt/venv -e PATH=/opt/venv/bin:/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin -v $(CURDIR):/work -w /work $(DEV_IMAGE) bash
pkg-lock: dev-tools-image ## Regenerate the age-gated application dependency lock
$(DEV_TOOL_RUN) bash -ceu 'uv lock --exclude-newer $(DEPENDENCY_CUTOFF)'
pkg-add: dev-tools-image ## Add one exact application dependency with PKG=name==version
test -n "$(PKG)"
case "$(PKG)" in *==*) ;; *) echo "PKG must pin an exact version with ==" >&2; exit 2;; esac
$(DEV_TOOL_RUN) bash -ceu 'uv add --no-sync --exclude-newer $(DEPENDENCY_CUTOFF) "$(PKG)"'
pkg-update: dev-tools-image ## Update one application dependency with PKG=name==version
test -n "$(PKG)"
case "$(PKG)" in *==*) ;; *) echo "PKG must pin an exact version with ==" >&2; exit 2;; esac
$(DEV_TOOL_RUN) bash -ceu 'uv lock --exclude-newer $(DEPENDENCY_CUTOFF) --upgrade-package "$(PKG)"'
pkg-upgrade: dev-tools-image ## Upgrade every application dependency under the age gate
$(DEV_TOOL_RUN) bash -ceu 'uv lock --exclude-newer $(DEPENDENCY_CUTOFF) --upgrade'
pkg-remove: dev-tools-image ## Remove one application dependency with PKG=name
test -n "$(PKG)"
$(DEV_TOOL_RUN) bash -ceu 'uv remove --no-sync --exclude-newer $(DEPENDENCY_CUTOFF) "$(PKG)"'
model-lock: dev-tools-image ## Generate hash-locked CPU and CUDA model dependency files
$(DEV_TOOL_RUN) bash -ceu 'uv pip compile requirements-providers-cpu.in --output-file requirements-providers-cpu.txt --generate-hashes --quiet --torch-backend cpu --exclude-newer $(DEPENDENCY_CUTOFF) --exclude-newer-package laya=$(MODEL_EXCEPTION_CUTOFF) --exclude-newer-package von-sdk=$(MODEL_EXCEPTION_CUTOFF)'
$(DEV_TOOL_RUN) bash -ceu 'uv pip compile requirements-providers-cuda.in --output-file requirements-providers-cuda.txt --generate-hashes --quiet --torch-backend cu126 --exclude-newer $(DEPENDENCY_CUTOFF) --exclude-newer-package laya=$(MODEL_EXCEPTION_CUTOFF) --exclude-newer-package von-sdk=$(MODEL_EXCEPTION_CUTOFF)'
dep: pkg-lock model-lock ## Refresh every locked dependency artifact
format: dev-image ## Format source
$(DEV_RUN) python -m ruff format src tests scripts
lint: dev-image ## Run all static checks
$(DEV_RUN) bash -ceu 'python -m ruff check src tests scripts && python -m pyright && python -m mypy src tests && python -m bandit -q -r src && shellcheck scripts/*.sh tests/integration/*.sh'
lint-fix: format ## Apply safe formatter fixes
@$(MAKE) lint
test: test-unit test-integration ## Run the normal complete test suite
test-unit: dev-image ## Run the unit suite
$(DEV_RUN) python -m pytest -q --ignore=tests/integration --ignore=tests/real
test-integration: dev-image ## Run local provider-process integration tests
$(DEV_RUN) python -m pytest -q -m integration tests/integration
test-coverage: dev-image ## Enforce coverage and write the badge input
$(DEV_RUN) bash -ceu 'COVERAGE_MINIMUM="$(MIN_TEST_COVERAGE)" bash scripts/test-coverage.sh'
test-real: build ## Run real HTTP requests against downloaded CPU Laya and Von weights
bash tests/integration/e2e_local_models.sh
test-real-cuda: build-cuda ## Run real HTTP requests against downloaded CUDA Laya and Von weights
bash tests/integration/e2e_local_models.sh --cuda
test-real-clm: build dev-image ## Run the real CLM head against a strict mock embeddings endpoint
$(DEV_RUN_DIND) bash tests/integration/e2e_clm.sh
test-real-jev: dev-image ## Run one live hosted Jev decision with a private TypeSafe key
@test -n "$$DECIDEALOT_TYPESAFE_API_KEY" || { echo "DECIDEALOT_TYPESAFE_API_KEY is required" >&2; exit 2; }
docker run --rm --init --user $(UID):$(GID) -e HOME=/tmp \
-e PYTHONPATH=/work/src -e VIRTUAL_ENV=/opt/venv -e PATH=/opt/venv/bin:/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin \
-e DECIDEALOT_TYPESAFE_API_KEY -v $(CURDIR):/work -w /work $(DEV_IMAGE) \
python -m pytest -q -m real tests/real/test_jev_live.py
generate: dev-image ## Regenerate every owned artifact
$(DEV_RUN) python scripts/generate.py
build: ## Build the CPU production image
docker build --build-arg TORCHBASE_IMAGE=$(TORCHBASE_CPU_IMAGE) -f Dockerfile -t $(CPU_IMAGE):local -t $(CPU_IMAGE):$(TAG) -t $(CPU_IMAGE):latest .
build-cuda: ## Build the CUDA production image
docker build --build-arg TORCHBASE_IMAGE=$(TORCHBASE_CUDA_IMAGE) -f Dockerfile.cuda -t $(CUDA_IMAGE):local-cuda -t $(CUDA_IMAGE):$(TAG)-cuda -t $(CUDA_IMAGE):latest-cuda .
build-all: build build-cuda ## Build both production images
build-test: build ## Import the CPU image and verify its immutable runtime metadata
docker run --rm --read-only --tmpfs /tmp:rw,noexec,nosuid,size=8m --entrypoint python $(CPU_IMAGE):local -c 'import os; from decidealot.settings import Settings; assert Settings().image_variant == "cpu"; assert os.environ["HOME"] == "/tmp"; assert (os.getuid(), os.getgid()) == (1000, 1000)'
docker run --rm --read-only --tmpfs /tmp:rw,noexec,nosuid,size=8m --entrypoint $(TORCH_VENV_DIRECTORY)/bin/python $(CPU_IMAGE):local -c 'import pathlib, torch, laya, von; assert torch.__version__ == "2.14.0+cpu"; assert pathlib.Path("$(TORCH_VENV_DIRECTORY)/bin/laya-serve").is_file(); assert pathlib.Path("$(TORCH_VENV_DIRECTORY)/bin/von").is_file(); assert not pathlib.Path("/opt/laya-venv").exists(); assert not pathlib.Path("/opt/von-venv").exists()'
build-test-cuda: build-cuda ## Import the CUDA image and verify its immutable runtime metadata
docker run --rm --read-only --tmpfs /tmp:rw,noexec,nosuid,size=8m --tmpfs /var/cache:rw,exec,nosuid,nodev,size=8m,uid=1000,gid=1000,mode=0755 --entrypoint python $(CUDA_IMAGE):local-cuda -c 'import os, shutil; from decidealot.settings import Settings; assert Settings().image_variant == "cuda"; assert os.environ["HOME"] == "/tmp"; assert (os.getuid(), os.getgid()) == (1000, 1000); assert os.environ["TRITON_CACHE_DIR"] == "/var/cache/triton"; assert os.access("/var/cache", os.W_OK); assert shutil.which("cc"); assert os.path.isfile("/usr/include/python3.12/Python.h")'
docker run --rm --read-only --tmpfs /tmp:rw,noexec,nosuid,size=8m --tmpfs /var/cache:rw,exec,nosuid,nodev,size=8m,uid=1000,gid=1000,mode=0755 --entrypoint $(TORCH_VENV_DIRECTORY)/bin/python $(CUDA_IMAGE):local-cuda -c 'import pathlib, torch, laya, von; assert torch.__version__ == "2.14.0+cu126"; assert torch.version.cuda == "12.6"; assert pathlib.Path("$(TORCH_VENV_DIRECTORY)/bin/laya-serve").is_file(); assert pathlib.Path("$(TORCH_VENV_DIRECTORY)/bin/von").is_file(); assert not pathlib.Path("/opt/laya-venv").exists(); assert not pathlib.Path("/opt/von-venv").exists()'
run: build ## Build and start the local hardened Compose service
DECIDEALOT_UID="$(UID)" DECIDEALOT_GID="$(GID)" docker compose up -d
run-cuda: build-cuda ## Build and start the local CUDA Compose service
DECIDEALOT_UID="$(UID)" DECIDEALOT_GID="$(GID)" docker compose -f docker-compose.yml -f docker-compose.cuda.yml up -d
restart: ## Rebuild and replace the local compose stack
DECIDEALOT_UID="$(UID)" DECIDEALOT_GID="$(GID)" docker compose up -d --build --remove-orphans
restart-cuda: ## Rebuild and replace the local CUDA Compose service
DECIDEALOT_UID="$(UID)" DECIDEALOT_GID="$(GID)" docker compose -f docker-compose.yml -f docker-compose.cuda.yml up -d --build --remove-orphans
stop: ## Stop this project's compose stack
docker compose down
status: ## Show this project's compose services
docker compose ps
audit: dev-image ## Scan the resolved Python dependency graph
$(DEV_RUN) python -m pip_audit
sec: dev-image ## Write Python security findings to sec.sarif
$(DEV_RUN) bash scripts/sec.sh
audit-compose: ## Enforce the production Compose hardening floor
bash scripts/audit-compose.sh
audit-compose-cuda: ## Enforce hardening after applying the CUDA Compose override
bash scripts/audit-compose.sh --cuda
docs: ## Build the documentation site into .testing/docs-site
mkdir --parents "$(DOCS_SITE)"
docker run --rm --user $(UID):$(GID) -e HOME=/tmp \
-e GITHUB_WORKSPACE=/work -e GITHUB_REPOSITORY=psyb0t/decidealot \
-e INPUT_SOURCE=docs -e INPUT_DESTINATION="$(DOCS_SITE)" -e INPUT_VERBOSE=false \
-v $(CURDIR):/work $(DOCS_IMAGE)
version: ## Print the canonical version
@echo $(TAG)
clean: ## Remove only project-owned build artifacts
docker image rm $(DEV_IMAGE) 2>/dev/null || true