Commit Graph

35 Commits

Author SHA1 Message Date
Paperclip CTO
dafbfa2bc4 feat(TRA-372): live groupcall backend — WebRTC signaling + session control
- Add Django Channels 4 + channels-redis to requirements and INSTALLED_APPS
- Upgrade ASGI config to ProtocolTypeRouter with JWT-authenticated WebSocket routing
- Add JWTAuthMiddleware for WebSocket token auth via query param
- Add CallSession, CallParticipant, CallEvent models with migration 0004
- MeetingCallConsumer: join/leave, P2P SDP/ICE relay via per-user groups,
  instructor mute/unmute/kick with DB audit and WS broadcast
- REST endpoints: GET/POST/DELETE /meetings/{id}/call/ (session lifecycle),
  POST /moderate/ (mute/unmute/kick), POST /screen-share/, GET /events/ (audit log)
- IsMeetingModerator permission (accepts training:signoff or meeting:moderate)
- Services: get_or_create_call_session, end_call_session, apply_moderation_action,
  toggle_screen_share with full CallEvent audit trail
- Integration tests covering REST lifecycle, moderation, and WebSocket signaling

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-18 14:12:48 +02:00
Paperclip CTO
7225775e12 feat(TRA-370): attendance UI, API endpoints, and migration
Backend:
- Add migration 0002 for Meeting, MeetingParticipant, EmployeeAttendance models
- Add EmployeeAttendanceSerializer with meeting_title helper field
- Add MeetingDetailView (GET /meetings/{id}/ with participants embedded)
- Add EmployeeAttendanceListView (GET /training/attendance/) with
  role-based access: own records always visible; other users require
  progress:view_all / progress:view_team / users:manage capability
- Register meeting-detail and attendance-list routes in urls.py

Frontend:
- attendance.html + attendance.js: JWT login flow, attendance history
  table with status badges, token refresh via sessionStorage only
  (no localStorage for access tokens)
- meetings.html + meetings.js: meeting list, create-meeting form,
  per-meeting participant management, inline attendance recording
  with select + save per row; UUID input validated before submit
- attendance.css: shared stylesheet for both pages

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-18 12:12:05 +02:00
Paperclip CTO
72e319767f feat(TRA-368): add attendance app with check-in/check-out core
Some checks failed
CI / lint (push) Failing after 46s
CI / test (push) Failing after 11s
CI / build-container (push) Has been skipped
- AttendanceSession model with open/closed status, org_id, timestamps
- POST /api/v1/attendance/check-in/ – creates session, rejects duplicate open check-ins (409)
- POST /api/v1/attendance/check-out/ – closes open session, validates checkout > checkin
- GET /api/v1/attendance/status/ – current open session for requesting user
- GET /api/v1/attendance/history/ – paginated session history per user
- GET /api/v1/attendance/admin/sessions/ – admin view for managers/admins with filters
- Initial migration with composite indexes on (user, status) and (user, checked_in_at)
- 18 test cases covering core flows and edge cases

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-18 12:11:16 +02:00
Paperclip CTO
e92ae6c136 feat(TRA-362): add docker-compose.prod.yml with nginx as web server
Introduces a production Docker Compose stack that places nginx in front
of gunicorn. nginx serves the frontend SPA and Django static files
directly, and proxies all backend routes (/api, /admin, /healthz, etc.)
to the Django container. SECURE_SSL_REDIRECT is now env-configurable so
plain-HTTP nginx deployments work without separate settings files.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-08 08:08:52 +02:00
Paperclip CTO
9e3c28b191 feat(TRA-360): enforce license_user_limit when assigning users to an org
Some checks failed
CI / lint (push) Failing after 6s
CI / test (push) Failing after 10s
CI / build-container (push) Has been skipped
Before creating a UserRoleBinding for a specific org_id, check whether
the org's OrganizationProfile.license_user_limit has been reached. New
users attempting to join a full org receive HTTP 403 with a descriptive
message. Re-assigning a user already in the org (role change) is
unaffected, as is assignment to an org with no OrganizationProfile.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-08 07:54:22 +02:00
Paperclip CTO
e711519413 feat(TRA-359): add POST /api/v1/accounts/organizations/ to create OrganizationProfile
Some checks failed
CI / lint (push) Failing after 11s
CI / test (push) Failing after 10s
CI / build-container (push) Has been skipped
- OrganizationProfileListCreateView (admin-only) handles POST
- OrganizationProfileCreateSerializer drops auto UniqueValidator so the
  view can return 409 (instead of 400) for duplicate org_id
- Returns 201 on success, 400 on validation errors, 409 on duplicate
- Tests: create success, defaults, duplicate 409, missing field 400, non-admin 403

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-08 07:52:08 +02:00
Paperclip CTO
22a472b72e fix(TRA-335): make license_user_limit a free-form integer, not fixed tiers
Replace the choices-constrained field (20/50/100/1000 only) with a plain
PositiveIntegerField so any positive integer is accepted. Adds migration
0004 to apply the schema change. Adds a test asserting arbitrary values
(e.g. 75) are accepted. Updates README with a field table and curl example
that documents common values without enforcing them.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-08 07:52:08 +02:00
Paperclip CTO
ca0352fa33 fix(ci): resolve ruff lint violations blocking CI pipeline
- Move `from datetime import timedelta` to top of base.py (E402)
- Add `# noqa: E402` to conditional sentry imports in prod.py (E402)
- Remove unused `import time` and `IsAdminUser` from api/views.py (F401)
- Remove unused `NoReverseMatch` import from tests/test_smoke.py (F401)

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-08 07:52:08 +02:00
Paperclip CTO
295ac1c31f feat(TRA-335): license limits, per-customer branding, frontend, Gitea CI, and deployment README
- Added OrganizationProfile model with license_user_limit choices (20/50/100/1000), company_name, brand_logo_url
- Added migration 0003_organization_profile
- Admin-only REST endpoint GET/PATCH /api/v1/accounts/organizations/{org_id}/profile/
- OrganizationProfileSerializer, OrganizationProfileAdmin, factory, and integration tests
- Static Nginx-hostable frontend under frontend/public with configurable API_BASE_URL via config.js
- frontend/nginx.conf reference server config
- .gitea/workflows/ci.yml: ruff lint, pytest with Postgres/Redis services, docker build
- README.md: English deployment guide covering env vars, Docker Compose, migration, org profile API, frontend deploy

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-08 07:52:08 +02:00
403dcb1d05 chore(TRA-306): trigger deploy via push path (add comment)
Co-Authored-By: Paperclip <noreply@paperclip.ai>
2026-05-07 11:57:41 +00:00
fa74f87fbe fix(TRA-306): use docker exec python for health checks, runner can't reach container IPs directly
Co-Authored-By: Paperclip <noreply@paperclip.ai>
2026-05-07 11:55:10 +00:00
d9a24112a3 fix(TRA-306): remove migrate from startup cmd, workflow handles it via docker exec
Some checks failed
Deploy Waitlist / Deploy to VPS (push) Failing after 3m9s
Co-Authored-By: Paperclip <noreply@paperclip.ai>
2026-05-07 11:49:16 +00:00
518bdf0d9f fix(TRA-306): run migrations via docker exec, separate from gunicorn startup
Some checks failed
Deploy Waitlist / Deploy to VPS (push) Has been cancelled
- Release stale Django migration advisory locks before migrating
- Run migrate via docker exec (not in container startup command)
- Update container CMD to start gunicorn directly (no migrate)
- This unblocks the 15-min migrate hang caused by leftover pg advisory lock

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-07 11:46:06 +00:00
df8fcc7322 fix(TRA-306): improve deploy workflow health polling and diagnostics
Some checks failed
Deploy Waitlist / Deploy to VPS (push) Has been cancelled
- Replace single healthz curl (no timeout) with polling loop (30x10s)
- Add --connect-timeout and --max-time to all curl calls
- Print container logs when gunicorn fails to start
- Print container logs in always() step for visibility

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-07 11:39:17 +00:00
ba3fa67194 fix(TRA-306): make celery import conditional in config/__init__.py
Waitlist deployment uses requirements/waitlist.txt which does not include celery.
Making the import conditional allows the app to start without celery installed.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-07 11:31:54 +00:00
Paperclip DevOpsSRE
1687b0e9d8 fix(TRA-306): fix deploy workflow container name and Dockerfile build
Some checks failed
Deploy Waitlist / Deploy to VPS (push) Failing after 2m26s
- Use COMPOSE_PROJECT_NAME=training-software so container names are
  predictable: training-software-waitlist-web-1
- Fix Dockerfile collectstatic: inject dummy env vars so the build
  step does not fail when celery or other settings are not installed
- Fix verify step to use correct container name

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-07 10:29:20 +02:00
Paperclip DevOpsSRE
4b8622d197 feat(TRA-306): add waitlist app + VPS deploy workflow
Some checks failed
Deploy Waitlist / Deploy to VPS (push) Failing after 2m26s
Adds Django waitlist app to serve the launch-gating endpoints:
- GET  /training/django-cohort/waitlist  → HTML cohort waitlist page
- POST /v1/waitlist/django-cohort        → JSON signup with attribution

Changes:
- waitlist/ app: WaitlistSignup model, views, urls, admin, template
- config/settings/waitlist.py: minimal prod settings for waitlist-only deploy
- config/urls_waitlist.py: slimmed URL conf (waitlist + healthz + admin)
- config/urls.py: registers waitlist routes on full project
- config/settings/base.py: adds waitlist to INSTALLED_APPS
- docker-compose.waitlist.yml: Traefik-labelled deploy for VPS
- requirements/waitlist.txt: minimal dependency set for waitlist build
- .gitea/workflows/deploy-waitlist.yml: CI deploy job using Docker socket
- Dockerfile: parameterise DJANGO_SETTINGS_FOR_COLLECTSTATIC

DNS action required after deploy (board):
  api.usepaperclip.app  → 76.13.129.223 (VPS)
  usepaperclip.app      → 76.13.129.223 (VPS) OR Vercel creds for Next.js fix

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-07 10:17:25 +02:00
Paperclip CTO
90f7a78197 feat(TRA-248): WCAG 2.1 AA baseline and de/en i18n framework
Some checks failed
CI / Tests (Python 3.12) (push) Failing after 7m3s
CI / OpenAPI Schema (push) Has been skipped
CI / Lint (push) Failing after 4s
A11y:
- Add alt_text field to MediaAsset and ContentBlock (WCAG 2.1 AA 1.1.1)
- Expose alt_text in MediaAssetSerializer and ContentBlockSerializer so
  frontends can render <img alt="..."> and <video aria-label="..."> correctly
- Migration 0002 adds the two alt_text columns

i18n:
- Add LocaleMiddleware to MIDDLEWARE stack (after SessionMiddleware per Django docs)
- Add LANGUAGES = [("de", ...), ("en", ...)] and LOCALE_PATHS = [BASE_DIR/"locale"]
- Add USE_L10N = True
- Seed locale/de/LC_MESSAGES/django.po and locale/en/LC_MESSAGES/django.po with
  translations for all user-facing API strings (upload errors, notification titles)

Tests (tests/a11y/test_a11y_i18n.py):
- alt_text field round-trip on MediaAsset and ContentBlock
- Serializer exposes and accepts alt_text on create, list, PATCH
- LANGUAGES/LOCALE_PATHS/LocaleMiddleware settings assertions
- Accept-Language header switching smoke test against /api/v1/notifications/

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-07 09:36:31 +02:00
Paperclip CTO
6384eac890 feat(TRA-245): Notification service with email and in-app delivery
Some checks failed
CI / Tests (Python 3.12) (push) Has been cancelled
CI / Lint (push) Has been cancelled
CI / OpenAPI Schema (push) Has been cancelled
- Notification model with idempotency_key dedup (unique per event+object+recipient)
- NotificationDelivery audit model (pending/sent/failed/delivered per channel)
- notify() service: creates notification idempotently, enqueues per-channel tasks
- deliver_notification_task Celery task: sends email via send_mail, marks in-app
  as sent without email; marks FAILED with error_detail on exception (autoretry x3)
- Event trigger helpers: notify_course_assigned, notify_attempt_limit_reached,
  notify_certificate_issued, notify_certificate_expiring (daily-reminder safe)
- send_course_due_reminders_task: periodic Celery Beat stub for due-date alerts
- REST API: list notifications (with ?unread=true filter), mark-read, mark-all-read
- Admin registrations with inline delivery audit view
- Initial migration (Notification + NotificationDelivery tables)
- Pytest test suite: idempotency, delivery dispatch, view auth/filtering

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-07 09:34:24 +02:00
Paperclip CTO
a27663e6fc feat(TRA-244): Reporting and CSV export for completion, progress, and quiz attempts
Some checks failed
CI / Tests (Python 3.12) (push) Has been cancelled
CI / Lint (push) Has been cancelled
CI / OpenAPI Schema (push) Has been cancelled
- CompletionReportView + CSV: enrollment-level completion status, filterable
  by course_id, org_id, date_from, date_to
- ProgressReportView + CSV: page-level dwell-time aggregates, same filters
- AttemptReportView + CSV: quiz attempt scores, pass/fail, timestamps; org
  filter joins through Enrollment.org_id; course filter traverses
  quiz -> page -> lesson -> module -> course
- Streaming CSV responses with _EchoWriter to avoid buffering large exports
- pytest test suite covering filters, aggregation accuracy, and CSV format

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-07 09:31:31 +02:00
Paperclip CTO
b087a63b56 feat(TRA-237): CMS content blocks, media upload pipeline, and course theme
Some checks failed
CI / Tests (Python 3.12) (push) Has been cancelled
CI / Lint (push) Has been cancelled
CI / OpenAPI Schema (push) Has been cancelled
- MediaAsset model with file metadata and AV scan status tracking
- ContentBlock model (richtext/image/video/embed/download) with ordered
  blocks per page and unique constraint on (page, order)
- CourseTheme one-to-one per course with primary/secondary color and logo
- validate_upload/save_upload helpers with extension and size enforcement
- scan_media_asset_task Celery stub (marks clean; replace with ClamAV)
- REST API: media upload, page content blocks CRUD, block patch/delete
- Admin registrations for all three models
- Factory-boy factories and pytest test suite for views, upload, and task

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-07 09:28:54 +02:00
Paperclip CTO
4f7232db56 feat(TRA-243): public certificate verification endpoint with hash integrity check
Some checks failed
CI / Tests (Python 3.12) (push) Has been cancelled
CI / Lint (push) Has been cancelled
CI / OpenAPI Schema (push) Has been cancelled
- CertificateVerifyView: GET /certificates/verify/{serial}/ — AllowAny
- Re-computes SHA-256 from stored PDF and compares to archived hash
- Returns {valid, serial_number, course_title, issued_at, hash} — no PII
- Returns 404 for non-completed or non-existent certificates
- 7 integration tests: hash match, tampered hash, missing file, PII exclusion, 404 cases

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-07 09:24:05 +02:00
Paperclip CTO
b3a7537364 feat(TRA-242): async certificate generation pipeline with hash-based verification
Some checks failed
CI / Tests (Python 3.12) (push) Has been cancelled
CI / Lint (push) Has been cancelled
CI / OpenAPI Schema (push) Has been cancelled
- Certificate model: serial_number (unique), status (pending/rendering/completed/failed),
  pdf_path, pdf_hash (SHA-256), verification_url, render_attempts, training_record FK
- renderer.py: LaTeX template → pdflatex subprocess → PDF; _escape_latex for XSS safety;
  compute_pdf_hash for immutable verification metadata
- services.py: issue_certificate() generates serial, creates record, enqueues Celery task
- tasks.py: render_certificate_task (bind=True, autoretry 3x with 60s backoff);
  sets RENDERING → COMPLETED with hash; FAILED with error on all retries exhausted
- API: /certificates/ (own list), /certificates/{id}/ (own detail)
- 15 unit + integration tests: hash consistency, LaTeX escaping, mocked render, retry behavior

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-07 09:22:56 +02:00
Paperclip CTO
87cbf23a9d feat(TRA-241): training modes and trainer signoff state machine
Some checks failed
CI / Tests (Python 3.12) (push) Has been cancelled
CI / Lint (push) Has been cancelled
CI / OpenAPI Schema (push) Has been cancelled
- TrainingRecord model with completion_mode (online/offline/blended) and CompletionStatus state machine
- TrainerSignoff model with decision, notes, trainer FK, and audit timestamp
- SignoffEvidence model for uploaded proof files
- services.py state transitions:
  - mark_in_progress: not_started → in_progress
  - mark_online_passed: online → completed; blended → pending_signoff
  - submit_trainer_signoff: offline/blended approved → completed; rejected → in_progress
  - InvalidTransitionError on illegal state moves
- IsTrainer permission class based on training:signoff capability
- API: record detail, start, mark-online-passed, trainer-signoff, pending-signoff list
- 20 unit + integration tests covering all mode paths, invalid transitions, and access control
- Blended completion requires both online pass AND trainer approval

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-07 09:20:34 +02:00
Paperclip CTO
6d7cc5ed16 feat(TRA-240): attempt policy enforcement — limits, timers, auto-expiry
Some checks failed
CI / Tests (Python 3.12) (push) Has been cancelled
CI / Lint (push) Has been cancelled
CI / OpenAPI Schema (push) Has been cancelled
- max_attempts enforcement in start_attempt: raises AttemptLimitError when
  quota of finalized attempts is exhausted (in_progress not counted)
- Timer enforcement: start_attempt sets expired_at = now + time_limit_seconds;
  check_attempt_not_expired auto-finalizes and raises AttemptExpiredError on deadline breach
- finalize_timed_out_attempt: grades partial responses, sets TIMED_OUT status
- expire_timed_out_attempts Celery task: idempotent sweep of overdue in-progress attempts
- Views: 409 on limit breach (start), 410 on expired response submit; timed_out result on submit
- 14 unit + integration tests covering policy boundary conditions and API responses

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-07 09:17:25 +02:00
Paperclip CTO
b59d06fddc feat(TRA-239): implement Quiz Engine — SC/MC + short answer with deterministic randomization
Some checks failed
CI / Tests (Python 3.12) (push) Has been cancelled
CI / Lint (push) Has been cancelled
CI / OpenAPI Schema (push) Has been cancelled
- Quiz, Question, Choice, QuizAttempt, QuestionResponse models with migrations
- seed-based deterministic randomization for questions and choices per attempt
- SC/MC grading (all-or-nothing per question), short-answer regex/keyword matching
- submit_attempt: grades all responses, computes score_percent, marks passed/failed
- DRF API: start attempt, submit response, submit (finalize) attempt, quiz detail
- 25 unit + integration tests covering grading correctness, boundary conditions,
  randomization reproducibility, API flows, and access control

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-07 09:15:18 +02:00
Paperclip CTO
3d541d818a feat(TRA-249): M5 observability, SLOs, backup, and release readiness
- Add prometheus-client to base requirements; sentry-sdk to prod
- api/metrics.py: define HTTP latency histogram, request/error counters, in-flight gauge
- api/middleware.py: extend SecurityAuditMiddleware to observe all four Prometheus collectors per request; low-cardinality path_template label via URL resolver
- api/views.py: /metrics/ endpoint (gated by METRICS_ENABLED setting)
- api/urls.py: wire /metrics/ route
- config/settings/prod.py: METRICS_ENABLED flag; optional Sentry SDK init via SENTRY_DSN env var
- ops/prometheus/alerts.yml: Prometheus alert rules for p95 latency SLO (≤500 ms), error rate SLO (<1%), availability, and saturation
- ops/prometheus/prometheus.yml: scrape config for app + blackbox healthcheck probe
- ops/scripts/backup.sh: pg_dump → S3 STANDARD_IA with retention metadata
- ops/scripts/restore.sh: pg_restore from S3 or local file with interactive confirmation guard
- ops/scripts/synthetic-check.sh: post-deploy smoke test (healthz, metrics gate, schema, 404 shape)
- docs/TRA-249-observability-slos.md: SLO table, PromQL reference queries, alert routing
- docs/TRA-249-backup-restore.md: RPO/RTO targets, drill procedure, restore validation steps
- docs/TRA-249-release-checklist.md: pre/post-deploy checklist
- docs/TRA-249-rollback-runbook.md: decision matrix, app rollback, migration revert, DB restore path

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-07 09:14:18 +02:00
Paperclip CTO
2c38fd862d feat(TRA-234): implement OIDC auth and group-to-role mapping
Some checks failed
CI / Tests (Python 3.12) (push) Has been cancelled
CI / Lint (push) Has been cancelled
CI / OpenAPI Schema (push) Has been cancelled
- AccountUser custom user model (UUID PK, email login, oidc_sub field)
- Role, UserRoleBinding, GroupRoleMap domain models with migrations
- TrainingOIDCBackend: create_user/update_user with Authentik claim hooks
- sync_roles_from_oidc_claims: reconciles OIDC-sourced bindings only,
  preserving manually-granted bindings
- get_effective_capabilities: flat capability set from role slugs
- DRF views: /me/, /me/permissions/, /users/, /users/{id}/roles/
- IsAdminOrManager and IsAdmin permission classes
- Audit signal logging on UserRoleBinding post_save/post_delete
- Seed migration for canonical role slugs (learner/trainer/author/manager/admin)
- AUTH_USER_MODEL = accounts.AccountUser wired in base settings
- OIDC settings: scopes, username algo, store_access/refresh_token flags
- Test suite: 20 unit + integration tests covering sync, capabilities, API

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-07 09:11:23 +02:00
Paperclip CTO
cfa9ad6f53 feat(TRA-236,TRA-238): M2 course domain model and dwell-time tracking
TRA-236 — Course domain model:
- courses/models.py: Course → Module → Lesson → Page hierarchy with UUID
  PKs, ordering fields, required_seconds navigation gate, version tracking,
  and UniqueConstraint per (parent, order) pair
- courses/migrations/0001_initial.py: initial migration (applies cleanly on
  a fresh DB)
- tests/test_course_domain.py: migration smoke, relation integrity, cascade
  delete, ordering, and uniqueness-constraint tests

TRA-238 — Dwell-time tracking:
- tracking/models.py: Enrollment, PageProgress (can_advance property), and
  DwellEvent models appended alongside existing AuditEvent
- tracking/services.py: record_dwell_event, compute_eligible_seconds (pure),
  check_navigation_gate, _recompute_accumulated — reconnect merging within
  RECONNECT_TOLERANCE_SECONDS and anti-idle cap at MAX_VALID_EVENT_SECONDS
- tracking/migrations/0001_initial.py: updated to include all four models
  (AuditEvent, Enrollment, PageProgress, DwellEvent) with FK dependencies
  on courses.Course and courses.Page
- tests/test_dwell_tracking.py: event replay, reconnect tolerance, anti-idle
  cap, gate pass/block, and can_advance DB integration tests

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-07 09:11:23 +02:00
Paperclip CTO
686acf259a feat(TRA-246): implement audit logging and compliance controls (M5)
Delivers the append-only audit trail system for all compliance-critical
actions per the TRA-246 acceptance criteria.

- tracking/models.py: AuditEvent model with ORM-level immutability guard
  (save raises on update, delete raises on direct call)
- tracking/audit.py: single record() call point; never raises in production
- tracking/admin.py: read-only Django admin for AuditEvent inspection
- tracking/migrations/0001_initial.py: DB schema with composite indexes
- tracking/serializers.py: PII metadata gating (oidc_sub stripped for
  non-admin callers)
- tracking/views.py: read-only AuditEventViewSet (IsPrivileged + 60/min
  throttle)
- tracking/urls.py: registers audit/events/ router
- tracking/management/commands/prune_audit_log.py: retention enforcement
  command with --dry-run and --class filter; writes access.admin_action
  event on real prune runs
- config/settings/base.py: AUDIT_RETENTION_DAYS per event class + audit
  throttle rate
- api/exceptions.py: wires access.permission_denied audit event on every
  PermissionDenied exception (M1 integration point)
- tests/test_audit.py: 26-event taxonomy coverage, immutability, retention,
  API permission, PII gating, and service helper unit tests

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-07 09:11:23 +02:00
Paperclip CTO
1f6a4183d4 feat(TRA-247): M5 security hardening — tests, markers, and header enforcement
- tests/test_security.py: 30 security regression tests covering secure
  headers, CSP directives, middleware ordering, DRF throttle configuration,
  and SecurityAuditMiddleware event-detection logic
- tests/test_upload.py: 19 upload defense tests covering extension allow-list,
  byte-length limits, and magic-byte signature validation (polyglot / disguised
  executable detection)
- pytest.ini: register 'security' and 'upload' markers (--strict-markers
  enforcement was already on)

Security settings already committed in feat(TRA-233) via harness include:
SECURE_REFERRER_POLICY, CSP_* directives, DEFAULT_THROTTLE_*, MAX_UPLOAD_SIZE,
SESSION/CSRF cookie hardening, AWS presigned URL policy, and
SecurityAuditMiddleware with dual-logger (access + security) pattern.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-07 09:11:23 +02:00
Paperclip CTO
8054c1e1e4 feat(TRA-233): Django M1 foundation scaffold
- Environment-split settings: base/local/test/prod with django-environ
- Postgres + Redis + Celery wiring (broker, beat, result backend)
- All 9 domain app stubs: accounts, courses, cms, tracking, quizzes,
  training, certificates, reports, notifications
- api app: /healthz/ endpoint, custom DRF exception handler,
  SecurityAuditMiddleware, permissions/throttle/upload-validation stubs
- DRF global baseline: JWT+session auth, closed-by-default permissions,
  cursor/page pagination, drf-spectacular schema generation
- Dockerfile (multi-env build arg), docker-compose.yml (local),
  docker-compose.test.yml (CI-friendly tmpfs Postgres)
- pytest.ini with smoke + settings marker definitions
- tests/test_smoke.py: startup, URL resolution, healthcheck shape
- tests/test_settings_matrix.py: per-profile security assertions
- .github/workflows/ci.yml: test, lint, schema CI jobs
- .env.example with all required vars documented
- .gitignore

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-07 09:11:23 +02:00
Paperclip CTO
3c59a4c9fc docs(TRA-253): add DRF settings baseline and handoff checklist
- Add section 7: DRF global settings (REST_FRAMEWORK dict, required
  packages, custom exception handler, drf-spectacular config, URL
  routing skeleton) to satisfy the DoD requirement for concrete
  implementation conventions
- Add section 9: handoff checklist for domain tracks (TRA-254/255/256),
  frontend track (TRA-257), and QA/CI track (TRA-258) with per-gate
  merge criteria and oasdiff command
- Fix section numbering: old section 7 OpenAPI subsections were
  labelled 6.x; renumbered to 8.x; old sections 8 and 9 become 10 and 11

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-07 09:11:23 +02:00
Paperclip CTO Agent
143c0be1e1 feat: add canonical execution policy dispatch codepaths 2026-05-06 11:46:24 +02:00
Paperclip CTO
a573e40684 chore: bootstrap repository with initial main commit 2026-05-06 10:39:44 +02:00