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
17 changes: 13 additions & 4 deletions .github/workflows/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@ GitHub Actions workflows for django-admin-react.

## What lives here

- **`ci.yml`** — the test gate on every PR and push to `main`: backend
`pytest` (with coverage) and the frontend `pnpm` gate (`-r typecheck`,
`lint`, `test`, `-r build`). A red suite blocks merge. Added in #452
(reversing the prior local-only posture). The Python *lint* gate
(`scripts/lint.sh`) is not in CI yet — it runs two formatters that
conflict, so it's de-conflicted first (follow-up off #452). Marking the
checks **required** is an owner branch-protection action (#452 / #331).
- **`codeql.yml`** — CodeQL static analysis (Python + JS/TS) on push/PR and a
weekly schedule. This is the project's security dataflow scanner.
- **`release.yml`** — automated PyPI publishing. Triggered when a GitHub
Expand All @@ -16,10 +23,12 @@ GitHub Actions workflows for django-admin-react.

## What does not belong here

- The local quality gate (`ruff`/`black`/`bandit`/`pytest`/`pnpm lint`) lives
in `scripts/lint.sh` and `.pre-commit-config.yaml`, not in CI — see
`SECURITY.md` §8 for the (deliberately) local-only posture and issue #331
for the forward CI-hardening plan.
- The Python *lint* gate (`scripts/lint.sh` + `.pre-commit-config.yaml`).
CI runs `pytest` and the frontend `pnpm` gate; the Python lint gate
isn't wired into CI yet (it must be de-conflicted first — two formatters
disagree). See `SECURITY.md` §8 and #331 for the forward hardening plan
(SHA-pinning is already done; lint-in-CI / required-checks /
version-matrix remain).
- Secrets. Workflows authenticate via OIDC (release) or the default
`GITHUB_TOKEN` (CodeQL). No long-lived tokens are stored as secrets.

Expand Down
116 changes: 116 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
# CI test gate — run the test suites on every pull request and on push to
# `main`, so a red suite can't merge.
#
# WHY: until now the lint/test pipeline was local-only — `scripts/lint.sh`
# + the pre-commit hooks, run on a contributor's laptop before the Merger
# pulled a PR (the deliberate "no CI in pre-alpha" posture). With many
# agents merging in parallel and only CodeQL gating server-side, test
# regressions slipped onto `main` green (e.g. #401 broke
# `tests/test_logentry.py`, undetected until a later full local run —
# #451). This enforces the suites server-side. Resolves the core of #452
# (run the test suites in CI); the no-CI revisit is recorded in
# `SECURITY.md` §8 / `docs/agents/decisions.md` / OQ-A-001.
#
# SCOPE: the backend job runs `pytest` (the regression that motivated
# #452 was a broken test). The frontend job runs the full `pnpm` gate —
# typecheck + lint + test + build — which is already self-consistent and
# green. Enforcing the *Python lint* gate in CI is a deliberate near-term
# follow-up: `scripts/lint.sh` currently runs two formatters (`ruff
# format` + `black`) whose output conflicts, so it is not yet satisfiable
# on a clean tree. That gets de-conflicted and the small existing lint
# debt cleared first (follow-up off #452), then the lint step is added
# here.
#
# SECURITY POSTURE:
# - Least-privilege: top-level `contents: read`; no job needs write.
# - All third-party actions are pinned to a full commit SHA (a tag can
# be moved, a SHA cannot) — consistent with codeql.yml / release.yml
# and the supply-chain hardening in #331.
# - This is a *gate*, not a publisher: it has no access to PyPI, no
# `id-token`, and no stored secrets.
#
# NOTE: making these checks *required* (branch protection) is a separate
# owner action in repo Settings → Branches — see #452 / #331.
name: ci

on:
push:
branches: [main]
pull_request:
branches: [main]

permissions:
contents: read

# A newer push to the same ref supersedes an in-flight run — don't burn
# minutes finishing a run whose commit is already stale.
concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
backend:
name: Backend (pytest)
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

- name: Set up Python
uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0
with:
python-version: "3.12"

- name: Install Poetry
# Poetry is pinned to the version that generated poetry.lock (see
# its header). `poetry install` compares the lock's content-hash
# against the one it computes from pyproject; a different Poetry
# can compute it differently and reject an otherwise-valid lock.
# Bump this in lockstep whenever the lock is regenerated.
run: pipx install poetry==2.1.4

- name: Install dependencies (locked)
run: poetry install --no-interaction

# pytest with coverage (per pyproject `addopts`), including
# tests/test_security.py. `filterwarnings = ["error"]` means a new
# warning fails the run.
- name: Tests (pytest)
run: poetry run pytest

frontend:
name: Frontend (typecheck + lint + test + build)
runs-on: ubuntu-latest
defaults:
run:
working-directory: frontend
steps:
- name: Checkout
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

- name: Set up pnpm
uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0
with:
version: 9

- name: Set up Node
uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0
with:
node-version: 22
cache: pnpm
cache-dependency-path: frontend/pnpm-lock.yaml

- name: Install dependencies (frozen lockfile)
run: pnpm install --frozen-lockfile

- name: Typecheck (tsc --noEmit, every package)
run: pnpm -r typecheck

- name: Lint (eslint --max-warnings 0 + stylelint + dark-mode coverage)
run: pnpm lint

- name: Unit tests (vitest)
run: pnpm test

- name: Build (Vite bundle, every package)
run: pnpm -r build
19 changes: 8 additions & 11 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
# CodeQL static analysis.
#
# This is the ONE CI workflow the package ships (SECURITY.md §8 keeps
# the lint/test pipeline local-only for v0.x; CodeQL is the documented
# exception — issue #144, post-public-flip hardening). It runs GitHub's
# semantic code analysis over the Python package and the TypeScript
# frontend, surfacing findings in the repository's Security tab where
# external reporters and maintainers both look. Free for public repos.
#
# It complements, not replaces, the local gates: `bandit` / `ruff -S`
# (Python) and `eslint` (frontend) still run via `scripts/lint.sh`;
# CodeQL adds dataflow-based detection (injection, path traversal,
# unsafe deserialization) those rule-based linters can miss.
# Dataflow-based security analysis (Python + TypeScript), surfacing
# findings in the repository's Security tab where external reporters and
# maintainers both look. Free for public repos. It complements the test
# gate in `ci.yml` and the local lint gate (`scripts/lint.sh`): `bandit` /
# `ruff -S` (Python) and `eslint` (frontend) are rule-based; CodeQL adds
# dataflow detection (injection, path traversal, unsafe deserialization)
# those linters can miss. Added in #144 (post-public-flip hardening);
# the test CI gate followed in #452.
name: CodeQL

on:
Expand Down
8 changes: 4 additions & 4 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@
# Anything below the [BLOCK] threshold in
# `docs/agents/security-expert/REVIEW_CHECKLIST.md` §1 aborts the commit.
#
# This file is part of the local-only quality gate. There is no CI
# in this repo by design (repo-owner direction); a developer who
# disables this hook still has to clear `scripts/lint.sh` before the
# Merger will pull their PR.
# This file is the commit-time half of the quality gate, run together
# with `scripts/lint.sh` before a PR. CI (`.github/workflows/ci.yml`)
# currently runs the test suites, not these hooks; wiring the lint/security
# hooks into CI is a follow-up (#452).

repos:
# -------------------------------------------------------------------
Expand Down
2 changes: 1 addition & 1 deletion ACCEPTANCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -436,7 +436,7 @@ Merger runs the pipeline locally before squash-merge.
| Q-1 | `./scripts/lint.sh` is the merge gate. A merge that bypassed it is a policy violation and a revert candidate. | Forum status / PR body. |
| Q-2 | `./scripts/build.sh` is the release gate. Both the SPA build and `poetry build` must succeed. | Run output before tagging. |
| Q-3 | A PR that touches `pyproject.toml` runtime deps, frontend root `package.json` deps, or `LICENSE` includes a locally-recorded `pip-audit` (Python) and `pnpm audit` (npm) clean run in the PR body. | PR-body inspection. |
| Q-4 | The "no CI" decision is revisited before leaving pre-alpha and recorded in [`docs/agents/decisions.md`](docs/agents/decisions.md). | Decision-log entry. |
| Q-4 | The "no CI" decision was revisited and reversed before leaving pre-alpha: the test suites now run in CI (`.github/workflows/ci.yml` — backend `pytest` + frontend gate), recorded in [`docs/agents/decisions.md`](docs/agents/decisions.md). Wiring the Python lint gate into CI and marking checks required in branch protection are the remaining follow-ups (#452). | Decision-log entry + `ci.yml`. |

### 3.8 Packaging

Expand Down
23 changes: 20 additions & 3 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,21 @@ Every endpoint added must include all of these tests before merging:
- TestPyPI may be used for verification by the maintainer with a
separate token; same hygiene rules apply.

## 8. Static analysis (local-only — no CI in v0.x)
## 8. Static analysis (local + CI)

The earlier "local-only, no CI in v0.x" posture was revisited and
reversed (issue #452 — regressions were slipping onto `main` under
CodeQL-only gating; see `docs/agents/decisions.md`). **The test suites
now run server-side in CI** (`.github/workflows/ci.yml`): backend
`pytest` and the frontend `pnpm` gate (typecheck + lint + test + build),
so a red suite cannot merge.

Enforcing the **Python lint gate** in CI is a near-term follow-up:
`scripts/lint.sh` currently runs two formatters (`ruff format` + `black`)
whose output conflicts, so it isn't satisfiable on a clean tree yet. The
local script below is still the authoritative lint gate; it gets
de-conflicted and the small existing debt cleared first, then the lint
step is added to CI.

Run via `./scripts/lint.sh`:

Expand All @@ -187,8 +201,11 @@ Run via `./scripts/lint.sh`:
- `mypy` (best-effort; tightening planned for v1.x)
- `bandit -r django_admin_react`
- `pytest -q` (including `tests/test_security.py`)
- Frontend: `prettier --check`, `pnpm -r typecheck`, `pnpm -r lint`
(`eslint` wires up in PR #6).
- Frontend: `pnpm -r typecheck`, `pnpm lint` (eslint `--max-warnings 0`
+ stylelint + dark-mode coverage), `pnpm test` (vitest), `pnpm -r build`.

Making the CI checks **required** in branch protection is a separate
owner action (#452 / #331).

Dependency audit runs separately via `./scripts/audit-deps.sh`
(see §6).
Expand Down
22 changes: 22 additions & 0 deletions docs/agents/decisions.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,28 @@ Newest decisions on top.

---

## 2026-05-28 — Reverse "no CI": the test suites now run server-side

The prior "no CI in pre-alpha (per repo-owner direction)" posture was
revisited (the trigger named in OQ-A-001 / ACCEPTANCE Q-4) and reversed.
Under CodeQL-only gating, test regressions were merging onto `main` green
with many agents working in parallel (e.g. #401 broke
`tests/test_logentry.py`, caught only on a later local run — #451).

- **CI now runs the test suites on every PR + push to `main`**
(`.github/workflows/ci.yml`): backend `pytest` (with coverage) and the
frontend gate (`pnpm -r typecheck` / `pnpm lint` / `pnpm test` /
`pnpm -r build`). Actions SHA-pinned; least-privilege `contents: read`.
— issue #452, ci.yml. Owner-directed merge despite Tier 5 (workflows +
SECURITY.md §8).
- **Follow-ups** (off #452 / #331): wire the Python *lint* gate into CI —
blocked first on de-conflicting `scripts/lint.sh`, which runs two
formatters (`ruff format` + `black`) whose output conflicts, plus a
little pylint/flake8 debt to clear; mark the CI checks **required** in
branch protection; optional Python/Django version matrix.

---

## 2026-05-27 — Ship a concrete recommended CSP (QSEC-03 resolved)

Security lane (`claude-security-opus47-2026-05-27`). The SPA shell loads
Expand Down
6 changes: 4 additions & 2 deletions docs/agents/product-manager/DECISIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,10 @@ Authoring session: `claude-pm-ux-opus47`.
11. **Lighthouse-equivalent budget:** FCP < 1.5 s cached, LCP < 2.5 s
cold on the reference profile (M-class laptop, 10 Mbps).
— [`PRODUCT_VISION.md`](../../PRODUCT_VISION.md) §7
12. **No `--force` push to `main`. No CI/CD (per repo-owner direction).**
Local linters via `scripts/lint.sh` are the gate.
12. **No `--force` push to `main`.** The test suites run in CI
(`.github/workflows/ci.yml`, added in #452 — the prior "no CI" stance
was reversed); the lint gate (`scripts/lint.sh`) runs locally, with
lint-in-CI a follow-up.
— [`docs/agents/decisions.md`](../../docs/agents/decisions.md) (engineering)
13. **`docs/agents/` handoff convention adopted** — durable role state
survives session loss. — [`docs/agents/decisions.md`](../DECISIONS.md)
Expand Down
11 changes: 7 additions & 4 deletions docs/agents/security-expert/DECISIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,13 @@ agents see it without reading this folder.
- **CSRF is mandatory.** No view in the package is `@csrf_exempt`.
Tests must assert that a missing or invalid `X-CSRFToken` header
on `POST` / `PATCH` / `DELETE` returns `403`. — invariant
- **No CI in v1.** The Merger runs `scripts/lint.sh` locally; security
scans (`ruff S`, `bandit`, `pip-audit`, secret grep) are part of
that script. Acceptance criteria below treat the local pipeline as
authoritative until CI is reintroduced. — repo-owner directive
- **Tests run in CI; the lint gate stays local (for now).**
`.github/workflows/ci.yml` runs `pytest` + the frontend gate
server-side on every PR so a red suite can't merge (#452 — the earlier
"no CI" stance was reversed). The Python lint/security gate
(`scripts/lint.sh`: `ruff S`, `bandit`, the pre-commit secret/pygrep
hooks) plus `pip-audit` stay local pre-merge steps until the gate is
de-conflicted and wired into CI (follow-up off #452). — #452
- **Frontend never holds permission state alone.** `@dar/data` may
cache `permissions: {view, add, change, delete}` from the API for
UI courtesy (hide buttons), but **every** write call re-verifies
Expand Down
3 changes: 2 additions & 1 deletion docs/agents/security-expert/REVIEW_CHECKLIST.md
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,8 @@ human takes it from there.

- "We'll add tests in a follow-up PR."
- "It's only a refactor, no review needed."
- "CI will catch it." (We have no CI.)
- "CI will catch it." (CI runs the gate (#452), but a green pipeline is
the floor, not the review — dataflow/logic bugs still need human eyes.)
- "The frontend handles it." (Defense in depth; backend must enforce.)
- "It's behind staff auth." (Necessary, not sufficient — the
backend still must obey `ModelAdmin`.)
Expand Down
13 changes: 13 additions & 0 deletions docs/agents/software-architect/DECISIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,19 @@

---

## 2026-05-28

- **OQ-A-001 resolved — "no CI" reversed.** The test suites now run
server-side on every PR (`.github/workflows/ci.yml`): backend `pytest`
+ the frontend gate. Trigger: regressions slipping onto `main` under
CodeQL-only gating (#401 / #451). Cross-role record:
[`docs/agents/decisions.md`](../../docs/agents/decisions.md)
(2026-05-28). — #452. Follow-ups: wire the Python lint gate into CI
(de-conflict `ruff format`/`black` first), required-checks branch
protection.

---

## 2026-05-25

- **`ACCEPTANCE.md` §3 is the engineering bar.** Every criterion is
Expand Down
18 changes: 0 additions & 18 deletions docs/agents/software-architect/OPEN_QUESTIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,6 @@

---

## OQ-A-001 — When to revisit "no CI"

- **Opened**: 2026-05-25.
- **Owner**: Architect.
- **Question**: The repo owner asked for no GitHub Actions in
pre-alpha. `scripts/lint.sh` is the local gate. At what point do we
flip CI on?
- **Default assumption** (per [`ACCEPTANCE.md`](../../ACCEPTANCE.md)
§3.7 Q-4): revisit before leaving pre-alpha. The trigger is the
first `0.1.0rcN` candidate.
- **Cost of waiting**: every PR depends on the Merger remembering to
run linters locally. Reasonable for a small repo; risky if the
contributor count grows beyond ~5.
- **Cost of doing it now**: `workflow` scope is missing from the
current PAT, so a fresh token or `gh auth login` is required.
- **Resolution path**: file a follow-up PR with `.github/workflows/`
re-introduced when the repo owner rotates the token.

## OQ-A-002 — `API_CONTRACT.md` location

- **Opened**: 2026-05-25.
Expand Down
2 changes: 1 addition & 1 deletion poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 5 additions & 2 deletions scripts/lint.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@
# LINT_PY_ONLY=1 bash scripts/lint.sh # skip frontend
# LINT_FE_ONLY=1 bash scripts/lint.sh # skip Python
#
# The project does not run CI for now (per repo-owner direction); every
# contributor must run this locally before opening / merging a PR.
# Run this locally before opening / merging a PR — it is the fast
# feedback loop and the authoritative lint gate. CI
# (.github/workflows/ci.yml) runs the test suites (backend `pytest` + the
# frontend gate); adding this script's Python lint gate to CI is a
# follow-up (it must be de-conflicted first — two formatters disagree, #452).
set -euo pipefail

ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
Expand Down
Loading