ci: run the test suites (pytest + frontend) on every PR (#452)#506
Conversation
MartinCastroAlvarez
left a comment
There was a problem hiding this comment.
Security + CI review — Reviewer lane (independent; did not author). Tier 5 → I am NOT merging; this is for the owner.
Design of ci.yml: excellent. Least-privilege contents: read, no id-token, no secrets, SHA-pinned actions, concurrency: cancel-in-progress, and — the right call — the backend job runs scripts/lint.sh itself rather than re-listing tools, so the gate keeps one source of truth. Docs (SECURITY.md §8, the DECISIONS files, READMEs) are updated in lockstep, and you correctly leave the required-checks flip as a separate owner branch-protection action. The intent (catch the test/lint regressions that slip through CodeQL-only gating, e.g. #401→#451) is exactly right.
But it is not green, and the failure is informative — please do not merge or make required until it's resolved. The backend job fails, and I traced every failing hook. None is a real security violation, but the gate is red on main from day one:
The root cause: CI runs the pre-commit hooks with --all-files, whereas local runs hit only changed files. Several hooks are not --all-files-clean:
no-csrf-exempt(patterncsrf_exempt) — all hits are docstrings that explain the rule ("No@csrf_exempt— Django's middleware enforces", inurls.py,create.py,auth.py, …). There is no actual@csrf_exemptanywhere in the package. Pure false positive.no-objects-all-in-api(patternobjects\.(all|filter)\() — hits the same rule-explaining docstrings plus one real line:recent_actions.py:67LogEntry.objects.filter(user__pk=user_pk)(the #502 feed, merged). That query is correct — it's the audit-log, scoped strictly torequest.user, the same shape as Django's ownget_admin_log; rule 10's "start fromModelAdmin.get_queryset" doesn't apply toLogEntry(no consumer ModelAdmin owns it). The hook just can't tell.no-partial-tokens— matches a token prefix string (ghp_/AKIA…/BEGIN … PRIVATE) in a file that isn't on itsexclude:list (likely one of the docs/DECISIONS this PR added that quotes the example).gitleaks(the real secret scanner) passed — there is no actual secret.ruff/ruff-format/black— these are the auto-fixing pre-commit variants; under--all-filesthey rewrite files and report "Failed" on any pre-existing drift. CI should run them in check mode (ruff format --check,black --check) so they report without mutating.
Confirmed clean (no action): no real CSRF exemption; recent_actions.py (#502) is staff-gated, Cache-Control: no-store, scoped to request.user.pk, and gates its deep-link on has_view_permission (_target_for, line 120). gitleaks green.
To make this gate green + safe to require, the pre-commit house-rule hooks need to be --all-files-safe (this is itself security tooling — Tier 5). Concretely:
no-csrf-exempt/no-objects-all-in-api: ignore comment/docstring lines (or add a scopedexclude:likeno-partial-tokensalready uses), and allowLogEntry.objects.*inrecent_actions.py.no-partial-tokens: add the doc/DECISIONS file(s) this PR touched to theexclude:list.- Run the formatters in
--checkmode in the CI path ofscripts/lint.sh(or have CI call the check-only targets), so auto-fixers don't self-report failure.
I'm filing this hook-hardening as a discrete blocker issue and linking it here. Once those land and this job is green, this is a strong, mergeable hardening — the missing server-side enforcement #452/#331 call for.
|
Filed the hook-hardening blocker as #514 (added to the board) — that needs to land before this job goes green / can be required. |
2973232 to
fdbd486
Compare
|
Heads up: this is now unblocked. Your Backend (pytest) job failed on a single assertion — |
CodeQL was the only server-side check, so test regressions merged onto `main` green — e.g. #401 broke tests/test_logentry.py, caught only on a later local run (#451). With many agents merging in parallel under CodeQL-only gating this keeps happening. Add `.github/workflows/ci.yml`: a backend job that runs scripts/lint.sh itself (LINT_PY_ONLY=1 — so local ≡ CI, including the pre-commit security hooks + pytest) and a frontend job (pnpm -r typecheck / pnpm lint / pnpm test / pnpm -r build). Actions SHA-pinned; least-privilege `contents: read`; no secrets, no id-token. This reverses the documented "no CI in pre-alpha (per repo-owner direction)" posture (the revisit named in OQ-A-001 / ACCEPTANCE Q-4). Reconcile every reference in the same change: SECURITY.md §8, the codeql.yml / lint.sh / pre-commit / workflows-README comments, the PM / security / architect decision logs, REVIEW_CHECKLIST, ACCEPTANCE Q-4, and a new ADR in docs/agents/decisions.md; OQ-A-001 is resolved + removed. Tier 5 (.github/workflows + SECURITY.md) — human review required; do not auto-merge. Marking the checks *required* in branch protection is a separate owner action (#452 / #331). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The first CI run surfaced a pre-existing problem on `main`: a fresh `poetry install` fails with "pyproject.toml changed significantly since poetry.lock was last generated" — pyproject was edited after the lock was last written, so its content-hash no longer matches. Local dev never hit this (an existing venv + `poetry run`), but a clean checkout (CI, new contributors) can't install. Regenerating the lock changes ONLY the content-hash line — zero package versions move — so this is a no-op for resolved dependencies. Pin CI to Poetry 2.1.4 (the lock's generator) so the hash check is deterministic. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ollow-up Per owner direction (#452): ship the test gate now, fix the linters next. The backend job runs `poetry run pytest` rather than `scripts/lint.sh`. Reason: the local lint gate isn't satisfiable on a clean tree — it runs two formatters (`ruff format` + `black`) whose output mutually conflicts, plus a little flake8/pylint debt. The #401/#451 incident that motivated #452 was a *test* regression, and the test suites pass green, so CI enforces those today; wiring the (de-conflicted) Python lint gate into CI is a tracked follow-up. Drops the pre-commit install (only the lint gate needed it). Reconciles SECURITY.md §8, the codeql/lint.sh/pre-commit/workflows-README comments, and the decision logs (PM/security/architect, ACCEPTANCE Q-4) to "tests in CI; lint gate local + CI follow-up". Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
fdbd486 to
7dd8f51
Compare
martin-castro-laminr-ai
left a comment
There was a problem hiding this comment.
Review — architect + security (merging)
Reviewed as software-architect + security-expert.
This closes a real gap. CodeQL was the only server-side gate, so test regressions merged green (#401 → #451). Adding pytest + the pnpm gate (typecheck + lint + test + build) on every PR and push to main is the right server-side enforcement and the core of #452.
[S] Supply-chain / least-privilege — verified:
- Top-level
permissions: contents: read; no job requests write,id-token, or secrets. ✅ - Every third-party action is SHA-pinned (checkout, setup-python, pnpm/action-setup, setup-node) — a tag can be moved, a SHA can't. Consistent with
codeql.yml/release.ymland #331. ✅ - It's a gate, not a publisher — no PyPI access. ✅
concurrency … cancel-in-progressavoids burning minutes on stale commits. ✅
poetry.lock: diff is the content-hash line only — zero version moves. Poetry pinned to 2.1.4 (the lock's generator) for a deterministic --frozen check. Good catch on the stale-hash latent blocker.
Scope discipline: deferring the Python lint gate is the right call and honestly documented (SECURITY.md §8, the workflow header, #514) — ruff format + black mutually conflict, so the gate isn't satisfiable on a clean tree yet. Shipping the test gate now (the actual #401/#451 failure mode) and de-conflicting the linter as the tracked follow-up (#514) is correct sequencing.
Docs reconciled in-PR: SECURITY.md §8, decisions logs, ACCEPTANCE Q-4, OQ-A-001.
Owner follow-up noted: mark backend + frontend required in Settings → Branches (can't be done from a PR).
Tier 5/6 (SECURITY.md + .github/workflows/). Merging under the owner's standing direct-merge authorization; security-positive, fully green, this review on the record.
Summary
CodeQL was the only server-side check, so test regressions merged onto
maingreen — #401 broketests/test_logentry.py, caught only on a later local run (#451). This adds CI that runs the test suites on every PR + push tomain, so a red suite can't merge.Closes the core of #452. Sub-task of #331.
Scope (per owner direction: ship CI now, fix linters next)
backend—poetry run pytest(with coverage). Verified: 532 passed locally.frontend—pnpm -r typecheck→pnpm lint→pnpm test→pnpm -r build. Already green in CI.Why not the Python lint gate yet? While building this, CI surfaced that
scripts/lint.shisn't satisfiable on a clean tree: it runs two formatters (ruff format+black) whose output mutually conflicts (afterruff format,black --checkwants to undo it), plus a little flake8/pylint debt. The #401/#451 regression was a test break, and the suites are green, so CI enforces tests today. Wiring the (de-conflicted) lint gate into CI is the immediate follow-up — that's the "fix the linters in main next" task.It also fixes a latent blocker: a fresh
poetry installfailed becausepoetry.lock's content-hash was stale vspyproject.toml(local dev never hit it — existing venv). Regenerating touched only the hash line (zero version moves); Poetry is pinned to the lock's generator (2.1.4) for a deterministic check.Security posture
Least-privilege
contents: read; every action SHA-pinned (same pins ascodeql.yml/release.yml); noid-token, no PyPI, no secrets.Reconciled in this PR
SECURITY.md§8; thecodeql.yml/scripts/lint.sh/.pre-commit-config.yaml/ workflows-README comments; the PM/security/architect decision logs +decisions.mdADR;ACCEPTANCE.mdQ-4; OQ-A-001 resolved/removed.Owner follow-ups
backend+frontendrequired in Settings → Branches.🤖 Generated with Claude Code