-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathMakefile
More file actions
173 lines (146 loc) · 9.24 KB
/
Copy pathMakefile
File metadata and controls
173 lines (146 loc) · 9.24 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
# Makefile — one command reproduces every gate (producibility, repeatability).
# `make verify` is the full merge gate; CI runs exactly these targets, so green
# locally means green in CI (reproducibility, process capabilities).
VENV ?= .venv
# Prefer the project venv (created by `make install`'s `uv sync`, in CI too —
# uv always materializes $(VENV) rather than installing into system Python), but
# fall back to python3 so a target still resolves before `install` has run.
# `?=` also lets a caller override, e.g. `make i18n PY=python`. This closes the
# i18n gate's `.venv/bin/python: No such file or directory` (exit 127) failure.
PY ?= $(if $(wildcard $(VENV)/bin/python),$(VENV)/bin/python,python3)
.DEFAULT_GOAL := help
.PHONY: help venv install lock lint format type test cov audit accessibility acr demo serve \
i18n i18n-compile claims secret-scan workflow-lint perf container mutation verify clean
help: ## Show this help
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) \
| awk 'BEGIN{FS=":.*?## "}{printf " \033[36m%-14s\033[0m %s\n", $$1, $$2}'
venv: ## Create the virtual environment
uv venv $(VENV)
# SEC-13/CQ-09/CQ-27: install exactly the pinned, hash-locked graph in uv.lock
# (the runtime dependency plus the `dev` PEP 735 dependency group), never a fresh
# resolve against version ranges. `--locked` fails the build instead of silently
# re-resolving if pyproject.toml and uv.lock ever drift apart, so "it installed"
# means "it installed the audited, committed lockfile" (reproducibility).
install: ## Install ledger plus dev tooling from the locked dependency graph
uv sync --locked --group dev
lock: ## Regenerate uv.lock after a pyproject.toml dependency change
uv lock
lint: ## Static analysis (ruff): correctness, security, import hygiene
$(PY) -m ruff check src tests
$(PY) -m ruff format --check src tests
format: ## Auto-format
$(PY) -m ruff format src tests
$(PY) -m ruff check --fix src tests
type: ## Strict type checking (mypy)
$(PY) -m mypy
test: ## Run the test suite (preservation + disclosure + no-outing audit)
$(PY) -m pytest
cov: ## Run tests with coverage (95% floor on the access/consent/dual-control core)
$(PY) -m pytest --cov --cov-report=term-missing
# Per-module floor (CODE-QUALITY-STANDARD, security/crypto-critical paths): the
# access-policy, consent, and dual-control modules must hold >=95% branch
# coverage, above the 85% baseline. Scoped re-report over the .coverage data.
$(PY) -m coverage report --include="src/ledger/access/*,src/ledger/consent.py,src/ledger/dualcontrol.py" --fail-under=95
backup-test: ## Exercise the full back-up -> wipe -> restore disaster-recovery cycle
$(PY) -m pytest -m recovery
audit: ## Dependency vulnerability scan (blocking)
# SECURITY-AND-SUPPLY-CHAIN-STANDARD §4 forbids muting this gate by name; a
# finding must be fixed or explicitly triaged/waived, never `|| true`d away.
$(PY) -m pip_audit
accessibility: ## Run the accessibility checks over the built web surface
$(PY) -m ledger.accessibility_check web
acr: ## Regenerate the Accessibility Conformance Report (VPAT 2.5)
$(PY) -m ledger.acr_gen > docs/accessibility/ACR.md
@echo "ACR regenerated at docs/accessibility/ACR.md"
demo: ## Scripted end-to-end: ingest -> seal -> grant -> verified-replica -> no-outing proof
$(PY) -m ledger.demo
serve: ## Run the accessible archive browse server locally
$(PY) -m ledger.cli serve --root ./local-archive
i18n: ## i18n gettext catalog gate: POT current + en/es/fr/ar parity + PO compiles + UTF-8 + BCP-47 + CLDR pin
# G2-lite — regenerate the extraction template and fail if it drifts from the
# committed one (a new/changed user-facing string without a re-extract is a
# merge-blocker). The normalizer freezes volatile header/flag noise so this is a
# meaningful diff, not a flaky timestamp check. Local == CI.
$(PY) -m babel.messages.frontend extract -F babel.cfg --no-location \
--sort-output --project=ledger-archive --version=0.1.0 \
-o src/ledger/locales/messages.pot src/
$(PY) tools/i18n_normalize_pot.py src/ledger/locales/messages.pot
git diff --exit-code -- src/ledger/locales/messages.pot
# G7 — every PO compiles cleanly (format + domain checks), no msgfmt errors.
for lang in en es fr ar; do \
msgfmt --check --check-format --check-domain -o /dev/null \
src/ledger/locales/$$lang/LC_MESSAGES/messages.po || exit 1; \
done
# G1 — every catalog and every rendered page is valid UTF-8 (charset declared).
$(PY) tools/check_i18n_utf8.py
# G6 key-parity + G5 completeness/placeholder parity across en/es/fr/ar.
$(PY) tools/check_catalog_parity.py
# G3 — BCP 47 / RFC 5646 validity of every authored locale tag.
$(PY) tools/check_bcp47.py
# G12 — CLDR/locale-data freshness pin (Babel within the reviewed range, data loads).
$(PY) tools/check_i18n_deps.py
@echo "i18n: POT current; en/es/fr/ar key-parity + completeness; PO compiles; UTF-8; BCP-47 valid; CLDR pinned."
i18n-compile: ## Compile the committed PO catalogs to MO (run after editing a .po)
for lang in en es fr ar; do \
msgfmt -o src/ledger/locales/$$lang/LC_MESSAGES/messages.mo \
src/ledger/locales/$$lang/LC_MESSAGES/messages.po || exit 1; \
done
@echo "i18n-compile: refreshed messages.mo for en, es, fr, ar."
claims: ## Truthfulness gate: verify README/doc factual claims against the repo
$(PY) tools/check_claims.py
secret-scan: ## Secret scan (gitleaks) — mirrors ci.yml's supply-chain job locally
# CI-authoritative: CI pins and downloads gitleaks 8.30.1 itself
# (.github/workflows/ci.yml, supply-chain job) regardless of what is on this
# machine, so CI is the gate of record even if a local binary is missing or a
# different version. This target just lets a contributor catch a leak before
# pushing when gitleaks happens to be installed locally.
@command -v gitleaks >/dev/null 2>&1 || { \
echo "gitleaks not found locally; skipping (CI is authoritative — see ci.yml supply-chain job)"; \
exit 0; \
}
gitleaks detect --source . --config .gitleaks.toml --no-banner --redact --exit-code 1
workflow-lint: ## Static analysis of the workflow YAML itself (zizmor) — mirrors ci.yml's workflow-lint job
# zizmor ships a compiled binary via its pip wheel, not a `python -m`-runnable
# module, so it's invoked directly off $(VENV)/bin, falling back to PATH.
@if [ -x "$(VENV)/bin/zizmor" ]; then "$(VENV)/bin/zizmor" .github/workflows; \
elif command -v zizmor >/dev/null 2>&1; then zizmor .github/workflows; \
else echo "zizmor not found; run 'make install' (or 'pip install zizmor')"; exit 1; fi
perf: ## Performance budgets (QM-02): CAS, fixity, ingest, browse must clear their time budgets
# Not part of `verify`: timing is meaningful relative to the machine it runs
# on, and a contributor's laptop under load is not that machine. CI's `perf`
# job (ci.yml) — a fixed, roughly consistent runner — is the gate of record;
# this target just lets a contributor run the same budgets locally out of
# curiosity or to reproduce a CI failure.
$(PY) tools/perf_budget.py
container: ## Build the self-host image and scan it for CRITICAL/HIGH CVEs (Trivy)
# Not part of `verify`: it needs a working Docker daemon, which not every
# contributor's environment has, and a Dockerfile-only change is rare enough
# that gating every `make verify` run on a container build/scan is the wrong
# trade-off. CI's `container` job (ci.yml) is the gate of record and runs
# unconditionally on every push/PR — this target just mirrors it locally.
docker build -f infra/Dockerfile -t ledger:local-scan .
trivy image --severity CRITICAL,HIGH --ignore-unfixed --exit-code 1 ledger:local-scan
mutation: ## ADVISORY (never a merge gate): mutation-test the safety-critical core (CQ-47)
@echo "mutation: ADVISORY ONLY — this is NOT part of 'make verify' and never gates a PR."
@echo " Scoped to access/, identity.py, fixity.py — see docs/MUTATION-TESTING.md."
@# Install the isolated mutation extra on demand while honoring the lockfile.
@# Scope (which files get mutated) and kill oracle (which tests run) live in
@# [tool.mutmut] in pyproject.toml.
@$(PY) -c "import mutmut" 2>/dev/null || uv sync --locked --group dev --extra mutation
@# `-` prefixes keep this target advisory: a surviving mutant never fails the build.
-$(PY) -m mutmut run
-$(PY) -m mutmut results
@echo "mutation: advisory run complete. Review any survivors above against the"
@echo " documented baseline in docs/MUTATION-TESTING.md (equivalent mutants noted)."
# The full gate. Determinism + reproducibility: same inputs, same result, every run.
# Matches CI's required-check set byte-for-byte (CICD-27): the `gate`, `i18n`,
# `accessibility`, `supply-chain`, and `workflow-lint` jobs in ci.yml run exactly
# these targets, so green here means green in CI. (The `no-outing-audit` job is
# `test`'s own `disclosure`-marked subset, run standalone in CI for visibility, not
# a distinct local gate; `container` is intentionally excluded — see its own
# target comment.)
verify: lint type test i18n accessibility audit secret-scan claims workflow-lint ## Run the complete merge gate (== CI's required checks)
@echo "verify: all gates green"
clean: ## Remove caches and build artifacts (never touches an archive's data)
rm -rf build dist .pytest_cache .mypy_cache .ruff_cache *.egg-info src/*.egg-info
find . -type d -name __pycache__ -prune -exec rm -rf {} +