Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
0c00f9b
feat(platform): phase 0 — CI test workflow, request-id logging, metri…
Shaan-Shoukath Sep 3, 2026
8be9047
refactor(frontend): split the eleven files over the 300-line ceiling,…
Shaan-Shoukath Sep 3, 2026
6377a79
feat(platform): phase 1 — live updates over SSE, one full-text ?q= co…
Shaan-Shoukath Sep 3, 2026
e0ab2a5
test(e2e): phase 2 — Playwright over the Hard Rules, axe gates, seed_…
Shaan-Shoukath Sep 3, 2026
e2ff42e
feat(notifications): phase 3 — signed webhook channel with re-queue f…
Shaan-Shoukath Sep 3, 2026
8414015
feat(platform): phase 4 — deployment editions and the single-box inst…
Shaan-Shoukath Sep 3, 2026
63f9801
feat: forward plan phase 5 — member ID cards and certification gating
Shaan-Shoukath Sep 3, 2026
f1492c2
feat: forward plan phase 6 — refunds, loan money, membership plans, i…
Shaan-Shoukath Sep 4, 2026
029e9e3
refactor(payments): split reconciliation.py under the 300-line ceiling
Shaan-Shoukath Sep 5, 2026
773f98c
feat(payments): record debts with no gateway, and fix unreconcilable …
Shaan-Shoukath Sep 5, 2026
768576f
feat(payments): track money owed independently of the payments module
Shaan-Shoukath Sep 5, 2026
97bb3aa
feat(payments): append-only manual settlement ledger for offline money
Shaan-Shoukath Sep 5, 2026
3de8338
feat(payments): members can see what they owe and how to pay it
Shaan-Shoukath Sep 5, 2026
143eae9
feat(makerspaces): make the payments module opt-in, and document the …
Shaan-Shoukath Sep 5, 2026
efd1362
feat(operations): break payment reports down by how the money arrived
Shaan-Shoukath Sep 5, 2026
33b9088
feat(staff): capture the settlement receipt in the console
Shaan-Shoukath Sep 5, 2026
0b69f91
fix(payments): resolve every finding from the Codex review gate
Shaan-Shoukath Sep 5, 2026
3b270fa
fix(payments): second Codex review round — backfill intent, org grain…
Shaan-Shoukath Sep 5, 2026
0868cbc
fix(tenant_migration): remap settlement ids in audit metadata
Shaan-Shoukath Sep 6, 2026
f948805
test: two suites that assumed the old payment contract
Shaan-Shoukath Sep 6, 2026
11db2f3
style(makerspaces): keep module_registry.py from growing past its cei…
Shaan-Shoukath Sep 6, 2026
8ddce21
feat(payments): split the provider rail out of the ledger (D3)
Shaan-Shoukath Sep 6, 2026
bc4d73b
feat(tenant_migration): pending payments travel in a dump (D5)
Shaan-Shoukath Sep 6, 2026
d641b10
feat(members): a real member dashboard behind the membership module (D8)
Shaan-Shoukath Sep 6, 2026
6629e60
fix: resolve the Stage-4 review findings on D3/D5/D8
Shaan-Shoukath Sep 6, 2026
dbba46a
docs: the Docker test command tested the wrong checkout
Shaan-Shoukath Sep 7, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
21 changes: 20 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,17 @@ permissions:
packages: write

jobs:
# Images are never published from a red tree: the whole test workflow runs first and
# every job below waits on it.
tests:
if: github.ref == 'refs/heads/main'
uses: ./.github/workflows/tests.yml
permissions:
contents: read

prepare:
if: github.ref == 'refs/heads/main'
needs: tests
runs-on: ubuntu-latest
outputs:
base: ${{ steps.release.outputs.base }}
Expand Down Expand Up @@ -62,9 +71,16 @@ jobs:
matrix:
include:
- context: backend
dockerfile: backend/Dockerfile
image: spaceworks-backend
- context: frontend
dockerfile: frontend/Dockerfile
image: spaceworks-frontend
# The single-box image (Dockerfile.allinone) builds from the repo root because it
# needs both trees.
- context: .
dockerfile: Dockerfile.allinone
image: spaceworks-allinone
steps:
- uses: actions/checkout@v4

Expand All @@ -88,6 +104,7 @@ jobs:
uses: docker/build-push-action@v6
with:
context: ./${{ matrix.context }}
file: ./${{ matrix.dockerfile }}
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
Expand Down Expand Up @@ -118,6 +135,7 @@ jobs:

- `ghcr.io/${{ needs.prepare.outputs.registry_owner }}/spaceworks-backend:${{ needs.prepare.outputs.version }}`
- `ghcr.io/${{ needs.prepare.outputs.registry_owner }}/spaceworks-frontend:${{ needs.prepare.outputs.version }}`
- `ghcr.io/${{ needs.prepare.outputs.registry_owner }}/spaceworks-allinone:${{ needs.prepare.outputs.version }}` (single-box)

promote-latest:
needs:
Expand Down Expand Up @@ -165,7 +183,7 @@ jobs:
SERIES: ${{ needs.prepare.outputs.series }}
run: |
set -euo pipefail
for package in spaceworks-backend spaceworks-frontend; do
for package in spaceworks-backend spaceworks-frontend spaceworks-allinone; do
image="ghcr.io/${OWNER}/${package}"
docker buildx imagetools create \
--tag "${image}:${SERIES}" \
Expand Down Expand Up @@ -236,6 +254,7 @@ jobs:
--owner "$OWNER" \
--package spaceworks-backend \
--package spaceworks-frontend \
--package spaceworks-allinone \
--version "$VERSION" \
--sha-tag "sha-${SHORT_SHA}" \
--series "$SERIES" \
Expand Down
279 changes: 279 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,279 @@
name: Tests

# The suite that every pull request must pass, and that the release workflow depends on.
# Environment parity with scripts/dev-local.sh is deliberate: the host topology there is
# what developers run, and a CI job that reproduced a different one would report reds
# nobody can see locally (docs/DEV-WORKFLOW.md, "Docker vs host test split").
on:
pull_request:
branches: [dev, main]
push:
branches: [dev]
workflow_call:
workflow_dispatch:

permissions:
contents: read

env:
PYTHON_VERSION: "3.12"
NODE_VERSION: "22"

jobs:
docs-drift:
name: CLAUDE.md and AGENTS.md are one document
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: diff CLAUDE.md AGENTS.md

frontend:
name: Frontend typecheck, unit tests, build
runs-on: ubuntu-latest
timeout-minutes: 20
defaults:
run:
working-directory: frontend
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: npm
cache-dependency-path: frontend/package-lock.json
- run: npm ci
- run: npx tsc -b
- run: npm test
- run: npm run build

backend-host:
# Mirrors `./scripts/dev-local.sh test`: Postgres, Redis and MinIO as services, the
# Django test process on the runner. tests/backup and tests/tenant_migration are
# excluded here and run in the job below with a version-matched Postgres client.
name: Backend suite (host topology)
runs-on: ubuntu-latest
timeout-minutes: 120
services:
postgres:
image: postgres:16
env:
POSTGRES_USER: makerspace
POSTGRES_PASSWORD: makerspace
POSTGRES_DB: makerspace_manager
ports: ["5432:5432"]
options: >-
--health-cmd "pg_isready -U makerspace"
--health-interval 5s --health-timeout 5s --health-retries 20
redis:
image: redis:7-alpine
ports: ["6379:6379"]
options: >-
--health-cmd "redis-cli ping" --health-interval 5s --health-timeout 5s --health-retries 20
# GitHub Actions does not support YAML anchors, so this block is repeated verbatim in
# backend-pg-client below; keep the two in step.
env:
DATABASE_URL: postgres://makerspace:makerspace@localhost:5432/makerspace_manager
CELERY_BROKER_URL: redis://localhost:6379/0
AWS_S3_ENDPOINT_URL: http://localhost:9200
AWS_S3_PUBLIC_ENDPOINT_URL: http://localhost:9200
PUBLIC_IMAGE_BASE_URL: http://localhost:9200/public-images
AWS_ACCESS_KEY_ID: minioadmin
AWS_SECRET_ACCESS_KEY: minioadmin
AWS_STORAGE_BUCKET_NAME: evidence
PUBLIC_IMAGE_BUCKET: public-images
STORAGE_PRESIGN_METHOD: post
DEBUG: "True"
SECRET_KEY: ci-only-secret-key-not-for-production
ALLOWED_HOSTS: localhost,127.0.0.1
CORS_ALLOWED_ORIGINS: http://localhost:5000,http://localhost:5173
AUTH_COOKIE_SECURE: "False"
AUTH_COOKIE_SAMESITE: Lax
EMAIL_BACKEND: django.core.mail.backends.console.EmailBackend
PYTHONDONTWRITEBYTECODE: "1"
steps:
- uses: actions/checkout@v4
- name: Start MinIO and create the buckets the compose stack creates
run: |
set -euo pipefail
docker run -d --name minio -p 9200:9000 minio/minio:latest server /data
for _ in $(seq 1 30); do
curl -sf http://localhost:9200/minio/health/live && break
sleep 1
done
docker run --rm --network host --entrypoint sh minio/mc:latest -c '
mc alias set local http://localhost:9200 minioadmin minioadmin &&
mc mb --ignore-existing local/evidence &&
mc version enable local/evidence &&
mc anonymous set none local/evidence &&
mc mb --ignore-existing local/public-images &&
mc version enable local/public-images &&
mc anonymous set download local/public-images'
- uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
cache: pip
cache-dependency-path: backend/requirements.txt
- run: pip install -r backend/requirements.txt
- name: Django system checks
working-directory: backend
run: python manage.py check
- name: pytest (excluding the Postgres-client-bound suites)
working-directory: backend
run: >-
pytest -q -p no:cacheprovider
--ignore=tests/backup --ignore=tests/tenant_migration

backend-pg-client:
# tests/backup and tests/tenant_migration shell out to pg_dump/pg_restore and refuse
# unless the client MAJOR equals the server's (16). The runner gets postgresql-client-16
# from PGDG so postgres_client.client_binary resolves /usr/lib/postgresql/16/bin.
name: Backup and tenant-migration suites (pg client 16)
runs-on: ubuntu-latest
timeout-minutes: 90
services:
postgres:
image: postgres:16
env:
POSTGRES_USER: makerspace
POSTGRES_PASSWORD: makerspace
POSTGRES_DB: makerspace_manager
ports: ["5432:5432"]
options: >-
--health-cmd "pg_isready -U makerspace"
--health-interval 5s --health-timeout 5s --health-retries 20
redis:
image: redis:7-alpine
ports: ["6379:6379"]
env:
DATABASE_URL: postgres://makerspace:makerspace@localhost:5432/makerspace_manager
CELERY_BROKER_URL: redis://localhost:6379/0
AWS_S3_ENDPOINT_URL: http://localhost:9200
AWS_S3_PUBLIC_ENDPOINT_URL: http://localhost:9200
PUBLIC_IMAGE_BASE_URL: http://localhost:9200/public-images
AWS_ACCESS_KEY_ID: minioadmin
AWS_SECRET_ACCESS_KEY: minioadmin
AWS_STORAGE_BUCKET_NAME: evidence
PUBLIC_IMAGE_BUCKET: public-images
STORAGE_PRESIGN_METHOD: post
DEBUG: "True"
SECRET_KEY: ci-only-secret-key-not-for-production
ALLOWED_HOSTS: localhost,127.0.0.1
CORS_ALLOWED_ORIGINS: http://localhost:5000,http://localhost:5173
AUTH_COOKIE_SECURE: "False"
AUTH_COOKIE_SAMESITE: Lax
EMAIL_BACKEND: django.core.mail.backends.console.EmailBackend
PYTHONDONTWRITEBYTECODE: "1"
steps:
- uses: actions/checkout@v4
- name: Install postgresql-client-16 and age
run: |
set -euo pipefail
sudo install -d /usr/share/postgresql-common/pgdg
curl -fsSL https://www.postgresql.org/media/keys/ACCC4CF8.asc \
| sudo gpg --dearmor -o /usr/share/postgresql-common/pgdg/apt.postgresql.org.gpg
. /etc/os-release
echo "deb [signed-by=/usr/share/postgresql-common/pgdg/apt.postgresql.org.gpg] https://apt.postgresql.org/pub/repos/apt ${VERSION_CODENAME}-pgdg main" \
| sudo tee /etc/apt/sources.list.d/pgdg.list
sudo apt-get update
sudo apt-get install -y --no-install-recommends postgresql-client-16 age
/usr/lib/postgresql/16/bin/pg_dump --version
- name: Start MinIO and create buckets
run: |
set -euo pipefail
docker run -d --name minio -p 9200:9000 minio/minio:latest server /data
for _ in $(seq 1 30); do
curl -sf http://localhost:9200/minio/health/live && break
sleep 1
done
docker run --rm --network host --entrypoint sh minio/mc:latest -c '
mc alias set local http://localhost:9200 minioadmin minioadmin &&
mc mb --ignore-existing local/evidence &&
mc version enable local/evidence &&
mc anonymous set none local/evidence &&
mc mb --ignore-existing local/public-images &&
mc version enable local/public-images &&
mc anonymous set download local/public-images'
- uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
cache: pip
cache-dependency-path: backend/requirements.txt
- run: pip install -r backend/requirements.txt
- name: pytest tests/backup tests/tenant_migration
working-directory: backend
run: pytest -q -p no:cacheprovider tests/backup tests/tenant_migration

e2e:
# Playwright against a real stack in the host topology (Django runserver + Vite + the
# same Postgres/Redis/MinIO services), driven by scripts/e2e-local.sh so CI and a laptop
# run the identical recipe. Pins the Hard Rules through a real browser and a real
# presigned upload to object storage.
name: End-to-end (Playwright)
runs-on: ubuntu-latest
timeout-minutes: 30
services:
postgres:
image: postgres:16
env:
POSTGRES_USER: makerspace
POSTGRES_PASSWORD: makerspace
POSTGRES_DB: makerspace_manager
ports: ["5432:5432"]
options: >-
--health-cmd "pg_isready -U makerspace"
--health-interval 5s --health-timeout 5s --health-retries 20
redis:
image: redis:7-alpine
ports: ["6379:6379"]
env:
PYTHON: python
PG_PORT: "5432"
MINIO_PORT: "9200"
DATABASE_URL: postgres://makerspace:makerspace@localhost:5432/makerspace_manager
CELERY_BROKER_URL: redis://localhost:6379/0
SECRET_KEY: ci-only-secret-key-not-for-production
DEBUG: "True"
ALLOWED_HOSTS: localhost,127.0.0.1
PYTHONDONTWRITEBYTECODE: "1"
CI: "true"
steps:
- uses: actions/checkout@v4
- name: Start MinIO and create buckets
run: |
set -euo pipefail
docker run -d --name minio -p 9200:9000 minio/minio:latest server /data
for _ in $(seq 1 30); do
curl -sf http://localhost:9200/minio/health/live && break
sleep 1
done
docker run --rm --network host --entrypoint sh minio/mc:latest -c '
mc alias set local http://localhost:9200 minioadmin minioadmin &&
mc mb --ignore-existing local/evidence &&
mc version enable local/evidence &&
mc anonymous set none local/evidence &&
mc mb --ignore-existing local/public-images &&
mc version enable local/public-images &&
mc anonymous set download local/public-images'
- uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
cache: pip
cache-dependency-path: backend/requirements.txt
- run: pip install -r backend/requirements.txt
- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: npm
cache-dependency-path: frontend/package-lock.json
- run: npm ci
working-directory: frontend
- run: npx playwright install --with-deps chromium
working-directory: frontend
- name: Seed, start the stack, run Playwright
run: ./scripts/e2e-local.sh
- uses: actions/upload-artifact@v4
if: failure()
with:
name: playwright-report
path: frontend/playwright-report
retention-days: 7
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,8 @@ docs/roadmap.md
.DS_Store
.idea/
.vscode/

# End-to-end test artifacts (scripts/e2e-local.sh)
frontend/e2e/.seed.json
frontend/playwright-report/
frontend/test-results/
Loading
Loading