Przeglądaj źródła

Release v0.22.0: Apache 2.0 license + CHANGELOG + per-file headers

Add LICENSE (Apache 2.0) and CHANGELOG.md summarizing build phases 1-22
plus the R01-N* security-review hardening pass. Inject SPDX/Apache-2.0
header into every owned PHP and JS file (92 files); vendored deps
under public/assets/js/vendor, vendor/, and node_modules/ untouched.

Copyright holder: Alessandro Chiapparini <sprint_planer_web@chiapparini.org>

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
chiappa 2 dni temu
rodzic
commit
e4e49e9a0b
94 zmienionych plików z 1195 dodań i 0 usunięć
  1. 258 0
      CHANGELOG.md
  2. 201 0
      LICENSE
  3. 8 0
      bin/migrate.php
  4. 8 0
      public/assets/js/app.js
  5. 8 0
      public/assets/js/sprint-planner.js
  6. 8 0
      public/assets/js/sprint-settings.js
  7. 8 0
      public/assets/js/theme-init.js
  8. 8 0
      public/index.php
  9. 8 0
      src/Auth/BootstrapAdmin.php
  10. 8 0
      src/Auth/LocalAdmin.php
  11. 8 0
      src/Auth/OidcClaims.php
  12. 8 0
      src/Auth/OidcClient.php
  13. 8 0
      src/Auth/SessionGuard.php
  14. 8 0
      src/Controllers/AuditController.php
  15. 8 0
      src/Controllers/AuthController.php
  16. 8 0
      src/Controllers/CspReportController.php
  17. 8 0
      src/Controllers/ImportController.php
  18. 8 0
      src/Controllers/SettingsController.php
  19. 8 0
      src/Controllers/SprintController.php
  20. 8 0
      src/Controllers/TaskController.php
  21. 8 0
      src/Controllers/UserController.php
  22. 8 0
      src/Controllers/WorkerController.php
  23. 8 0
      src/Db/Connection.php
  24. 8 0
      src/Db/Migrator.php
  25. 8 0
      src/Domain/Import/ImportResult.php
  26. 8 0
      src/Domain/Import/ParsedAssignment.php
  27. 8 0
      src/Domain/Import/ParsedSheet.php
  28. 8 0
      src/Domain/Import/ParsedTask.php
  29. 8 0
      src/Domain/Import/ParsedWeek.php
  30. 8 0
      src/Domain/Import/ParsedWorker.php
  31. 8 0
      src/Domain/Sprint.php
  32. 8 0
      src/Domain/SprintWeek.php
  33. 8 0
      src/Domain/SprintWorker.php
  34. 8 0
      src/Domain/SprintWorkerDay.php
  35. 8 0
      src/Domain/Task.php
  36. 8 0
      src/Domain/TaskAssignment.php
  37. 8 0
      src/Domain/User.php
  38. 8 0
      src/Domain/Worker.php
  39. 8 0
      src/Http/FatalErrorHandler.php
  40. 8 0
      src/Http/Request.php
  41. 8 0
      src/Http/Response.php
  42. 8 0
      src/Http/Router.php
  43. 8 0
      src/Http/TrustedProxies.php
  44. 8 0
      src/Http/View.php
  45. 8 0
      src/Repositories/AppSettingsRepository.php
  46. 8 0
      src/Repositories/AuditRepository.php
  47. 8 0
      src/Repositories/AuthThrottleRepository.php
  48. 8 0
      src/Repositories/SprintRepository.php
  49. 8 0
      src/Repositories/SprintWeekRepository.php
  50. 8 0
      src/Repositories/SprintWorkerDayRepository.php
  51. 8 0
      src/Repositories/SprintWorkerRepository.php
  52. 8 0
      src/Repositories/TaskAssignmentRepository.php
  53. 8 0
      src/Repositories/TaskRepository.php
  54. 8 0
      src/Repositories/UserRepository.php
  55. 8 0
      src/Repositories/WorkerRepository.php
  56. 8 0
      src/Services/AuditLogger.php
  57. 8 0
      src/Services/CapacityCalculator.php
  58. 8 0
      src/Services/Import/SprintImporter.php
  59. 8 0
      src/Services/Import/XlsxColorClassifier.php
  60. 8 0
      src/Services/Import/XlsxSprintImporter.php
  61. 8 0
      tailwind.config.js
  62. 8 0
      tests/Auth/BootstrapAdminTest.php
  63. 8 0
      tests/Auth/LocalAdminTest.php
  64. 8 0
      tests/Auth/OidcClaimsTest.php
  65. 8 0
      tests/Auth/SessionGuardTest.php
  66. 8 0
      tests/Cascade/CascadeAuditTest.php
  67. 8 0
      tests/Controllers/AuditControllerTest.php
  68. 8 0
      tests/Controllers/CspReportControllerTest.php
  69. 8 0
      tests/Controllers/ImportControllerTest.php
  70. 8 0
      tests/Controllers/SprintControllerTest.php
  71. 8 0
      tests/Controllers/TaskControllerTest.php
  72. 8 0
      tests/Controllers/UserControllerTest.php
  73. 8 0
      tests/Db/MigrationBackfillTest.php
  74. 8 0
      tests/Db/MigratorTest.php
  75. 8 0
      tests/Domain/SprintWeekTest.php
  76. 8 0
      tests/Domain/TaskAssignmentTest.php
  77. 8 0
      tests/Http/FatalErrorHandlerTest.php
  78. 8 0
      tests/Http/RequestTest.php
  79. 8 0
      tests/Http/ResponseTest.php
  80. 8 0
      tests/Http/TrustedProxiesTest.php
  81. 8 0
      tests/Http/TwigAutoescapeTest.php
  82. 8 0
      tests/Http/TwigViewTest.php
  83. 8 0
      tests/Repositories/AppSettingsRepositoryTest.php
  84. 8 0
      tests/Repositories/AuditRepositoryTest.php
  85. 8 0
      tests/Repositories/AuthThrottleRepositoryTest.php
  86. 8 0
      tests/Repositories/SprintWeekRepositoryTest.php
  87. 8 0
      tests/Repositories/TaskAssignmentRepositoryTest.php
  88. 8 0
      tests/Repositories/UserRepositoryTest.php
  89. 8 0
      tests/Services/AuditLoggerTest.php
  90. 8 0
      tests/Services/CapacityCalculatorTest.php
  91. 8 0
      tests/Services/Import/SprintImporterCommitTest.php
  92. 8 0
      tests/Services/Import/XlsxColorClassifierTest.php
  93. 8 0
      tests/Services/Import/XlsxSprintImporterTest.php
  94. 8 0
      tests/TestCase.php

+ 258 - 0
CHANGELOG.md

@@ -0,0 +1,258 @@
+# Changelog
+
+All notable changes to this project are documented here. The format is based
+on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project
+adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+
+## [Unreleased]
+
+Nothing scheduled.
+
+## [0.22.0] — 2026-05-07
+
+First tagged release. The minor version mirrors the latest build phase
+shipped (Phase 22). Section grouping below reflects the build-phase history
+captured in `SPEC.md` §9 plus the security-review hardening pass tracked in
+`doc/REVIEW_01.md` (R01-N01..R01-N34).
+
+### Added
+
+- **Phase 1 — Skeleton.** Front controller, router, request/response
+  primitives, view layer.
+- **Phase 2 — Auth.** Microsoft Entra ID via OpenID Connect (Authorization
+  Code + PKCE), session + CSRF, first-user-is-admin bootstrap, optional
+  local-admin fallback for dev / on-prem deployments.
+- **Phase 3 — Workers, sprints, audit.** Worker CRUD, sprint create,
+  per-cell audit log.
+- **Phase 4 — Sprint settings.** Sprint metadata edit, weeks resize,
+  worker membership add/remove/reorder, per-row reserve-time-buffer (RTB).
+- **Phase 5 — Arbeitstage grid.** Editable working-days matrix, capacity
+  calculation, per-cell persistence with audit.
+- **Phase 6 — Task list.** CRUD, assignments grid, sort/filter/search,
+  drag-reorder.
+- **Phase 7 — Audit viewer + polish.** `/audit` admin page with filters,
+  pagination, collapsible diffs; security headers + strict-ish CSP; CSRF
+  audit (18/18 mutations); PHPUnit harness with 59 tests.
+- **Phase 8 — Cascade audit integrity.** Three FK cascade paths
+  (sprint_worker → sprint_worker_days, sprint_worker → task_assignments,
+  sprint_week → sprint_worker_days on shrink) snapshot-audit before the
+  parent delete fires.
+- **Phase 9 — Users management.** `GET /users` + `POST /users/{id}` with
+  self-demote and last-admin guardrails.
+- **Phase 10 — Task list polish.** Multi-select owner filter and column
+  visibility toggle, both pure client-side with localStorage persistence.
+- **Phase 11 — CSP hardening.** Vendored Tailwind via a Node css-builder
+  Docker stage; inline `onclick` replaced by `data-href` + `app.js`; CSP
+  dropped `unsafe-inline` and the Tailwind CDN host.
+- **Phase 12 — Per-week weekday selection (Mo–Fr) drives Arbeitstage.**
+  `sprint_weeks.active_days_mask` becomes the source of truth;
+  `max_working_days` is a cached `popcount(mask)` projection. Five
+  checkboxes per week in Sprint Settings; row of five dots per week in
+  the sprint view (green = active, gray = off). Migration 002 backfills
+  legacy rows.
+- **Phase 13 — Focus filter + Reset in the task list.** New
+  `[data-focus-select]` picks one sprint worker; `applyFocusColumnVisibility`
+  hides all-zero columns; `[data-reset-filters]` wipes search, prio,
+  ownerFilterSet, focusWorker, and hiddenCols in one click. State persists
+  in localStorage.
+- **Phase 14 — Hamburger menu.** Workers / Users / Audit log / Sign out
+  collapsed into a dropdown behind a `data-menu-trigger` button with an
+  inline-SVG icon; vanilla-JS toggle (~30 lines) honours outside-click,
+  Escape, and focus return.
+- **Phase 15 — Big-screen (beamer) task viewer.** New signed-in route
+  `/sprints/{id}/present` renders a stripped-down view for projection;
+  shared partial with `/sprints/{id}` via a `loadSprintPage` helper.
+  Auto vertical column headers when overflow detected.
+- **Phase 16 — Dark-mode toggle + light-mode contrast cleanup.** Manual
+  toggle (no `prefers-color-scheme` auto-detect); `theme-init.js`
+  synchronously sets `<html class="dark">` from localStorage to prevent
+  FOUC; comprehensive `dark:` class sweep across every view.
+- **Phase 17 — Number-stepper popover (later replaced).** Hidden native
+  number-input spinners (`@layer base` reset); per-cell click-to-open
+  vertical-slider popover on day, RTB, and task-assignment inputs. After
+  seven iterations the implementation was removed in favour of plain
+  typed entry; ArrowUp/ArrowDown stepping retained via browser default.
+- **Phase 18 — Per-cell task-status colours + filter + global toggle.**
+  `task_assignments.status` ∈ {`zugewiesen`, `gestartet`, `abgeschlossen`,
+  `abgebrochen`}; new `app_settings` KV table with opt-in
+  `task_status_enabled` flag; admin `/settings` page; Status multi-select
+  filter; first non-admin write surface (`PATCH
+  /tasks/{id}/assignments/status`). Migration 003.
+- **Phase 19 — Twig 3 + Tailwind 3 + Alpine CSP + htmx + SortableJS;
+  jQuery removed.** Stack-shift of the entire UI layer with zero changes
+  to controllers, repositories, schema, capacity math, or audit
+  semantics. All 11 `views/*.php` rewritten as `*.twig`. Strict CSP:
+  `script-src 'self'` and `style-src 'self'` only — no third-party
+  hosts.
+- **Phase 20 — XLSX import wizard.** Two-step admin-only wizard at
+  `/sprints/import`; multipart upload (≤ 5 MB, ZIP magic-byte check),
+  preview screen with target picker (Create new / Merge into empty
+  existing), per-sheet skip toggle, transactional commit; per-sprint
+  `IMPORTED_FROM_XLSX` audit row. New service trio:
+  `XlsxColorClassifier`, `XlsxSprintImporter`, `SprintImporter`.
+  Composer dep `phpoffice/phpspreadsheet ^3.4`; runtime stage adds
+  `zip` + `gd` extensions.
+- **Phase 21 — Auto-derived week count in sprint settings.** `PATCH
+  /sprints/{id}` resyncs `sprint_weeks` whenever `start_date` or
+  `end_date` changes (target = `floor((end − start)/7) + 1`, capped at
+  26). Existing rows realign; appended rows default to `MASK_ALL`;
+  trailing rows shrink with the same audit-cascaded-days flow as the
+  legacy `replaceWeeks`.
+- **Phase 22 — Per-task hamburger menu: move / copy / edit / reorder.**
+  Per-row admin button gathers Edit details / Move to sprint / Copy to
+  sprint / Move (pick up) / Delete. Tasks gain `description` (≤ 8000
+  chars), `url` (`http(s)://`, ≤ 2048 chars), and `linked_task_id` for
+  bidirectional cross-sprint copy chips. Two new admin JSON endpoints
+  (`POST /tasks/{id}/move`, `POST /tasks/{id}/copy`). Migration 004.
+- **Sprint settings: secured Delete sprint action.** Danger-zone
+  `POST /sprints/{id}/delete` form gated by `requireAdmin` + CSRF +
+  typed-confirmation match; full-cascade audit (every descendant
+  snapshotted) plus an UPDATE audit for cross-sprint linked rows whose
+  `linked_task_id` was nulled out.
+
+### Changed
+
+- **Header cleanup: Import moved into the admin dropdown.** The
+  `/sprints/import` link moved from inline in the header to the top of
+  the admin section in the hamburger menu.
+- **New sprint form: drop weeks input.** `/sprints/new` no longer
+  collects `n_weeks`; the count is derived from `start_date` / `end_date`
+  (capped at 26; above that redirects with `?error=dates_too_long`).
+- **Task table polish.** Sortable headers gain `whitespace-nowrap`;
+  per-row assignment cells gain `whitespace-nowrap`; `.assign-status-*`
+  tint moved off the `<td>` onto the inner field.
+- **Filter dropdown close polish.** Owners / Status / Columns dropdowns
+  no longer get cropped on a single-row task table; close on
+  `mouseleave` with a 250 ms grace timer.
+- **Sprint view tabs + smart Close on present.** `/sprints/{id}` is
+  split into "Arbeitstage and capacity" + "Capacity and tasks" tabs,
+  persisted in localStorage. The present view's Close button calls
+  `history.back()` when possible, falling back to `window.close()` then
+  to navigation.
+- **Cell popover replaces per-cell status select.** Single body-attached
+  `.cell-popover` panel anchored 8 px right of the cell; left column is
+  a slider whose max comes from `assignment_slider_max` (1..100,
+  default 10), right column is four status pills.
+
+### Removed
+
+- **Number-stepper slider popover (Phase 17).** Removed after seven
+  iterations failed to land reliable behaviour; the team prefers plain
+  typed entry. The `@layer base` rule that hides native number-spinner
+  arrows app-wide stays.
+
+### Security (R01-N* — `doc/REVIEW_01.md`)
+
+- **R01-N01 — Hash-only local-admin password.** `LOCAL_ADMIN_PASSWORD`
+  → `LOCAL_ADMIN_PASSWORD_HASH`; `password_verify()` against a bcrypt
+  hash; no plaintext fallback.
+- **R01-N02 / R01-N31 — Runtime panel on `/` is admin-only.**
+  `views/home.twig`'s "Runtime" `<details>` block was leaking PHP
+  version, env, SQLite path, schema version, and OIDC flags to anonymous
+  visitors; tightened to `currentUser is not null and currentUser.isAdmin`.
+  In-page `/healthz` hint also removed (the route stays public for
+  liveness probes).
+- **R01-N03 — Explicit env-bootstrap for the first OIDC admin.** OIDC
+  no longer auto-promotes the first user to land on `/auth/callback`;
+  `BOOTSTRAP_ADMIN_OID` / `BOOTSTRAP_ADMIN_EMAIL` name the principal up
+  front. Without one of them set, OIDC never auto-promotes anyone.
+- **R01-N04 — `SESSION_SECRET` removed.** The env var was documented but
+  unused; deployments that rotated it got a false sense of security.
+- **R01-N05 + R01-N07 — Trusted-proxy aware HTTPS detection & client
+  IP.** `TrustedProxies` honours a comma-separated `TRUSTED_PROXIES=`
+  env var and walks the XFF chain right-to-left; with the env blank,
+  forwarded headers are ignored. Session cookie marked `Secure` when
+  either `APP_BASE_URL` is HTTPS or the live request is effectively
+  HTTPS via a trusted proxy. One-shot HTTP→HTTPS redirect (308) when
+  the proxy explicitly reports `X-Forwarded-Proto: http`.
+- **R01-N06 — Throttle local-admin login by (ip, email).** Migration
+  005 adds `auth_throttle`. Policy in `computeLockout(attempts, now)`:
+  1–4 → no lock; 5–9 → +5 min; 10–19 → +30 min; 20+ → +1 hour. Counter
+  rolls over after a 15-min idle window; success deletes the row.
+  `LOGIN_FAILED` audit row distinguishes throttled hits from
+  credential mismatches.
+- **R01-N08 — Idle session timeout + CSRF rotation on login.** 30-min
+  idle window (`IDLE_TIMEOUT_SECONDS = 1800`); `last_active` stamp
+  drops auth keys + regenerates the session id past the threshold.
+  Login now `unset()`s `csrf_token` so a pre-login token can't be
+  replayed against the authenticated session.
+- **R01-N10 — Bind `sprint_id` with placeholders in `MAX(sort_order)`
+  lookups.** Three repo-level read paths previously interpolated the
+  integer parameter directly into SQL (not exploitable today; route
+  layer int-casts) — switched to prepared statements.
+- **R01-N11 — Whitelist column in
+  `AuditRepository::distinctColumn`.** Private helper interpolated its
+  `$col` argument; added explicit `in_array($col, ['action',
+  'entity_type'], true)` guard.
+- **R01-N12 — Validate `/audit` date filters server-side.** Strict
+  `Y-m-d` parse via `DateTimeImmutable::createFromFormat` round-trip
+  equality; bad input drops the filter and surfaces a per-field error.
+- **R01-N13 — Fatal-error safety net.** New `FatalErrorHandler`
+  registers both `set_exception_handler` and a fatal-mask
+  `register_shutdown_function`. On fire, every output buffer is drained
+  and a minimal 500 page is written with the same security headers a
+  normal response carries (CSP / HSTS / X-Frame-Options /
+  X-Content-Type-Options / Referrer-Policy). `public/index.php` now
+  sources its security headers from
+  `FatalErrorHandler::securityHeaders()` — single source of truth.
+- **R01-N14 — XLSX session payload cap.** The full parsed workbook
+  was JSON-stashed in the session file with no upper bound on parsed
+  expansion; capped at 2 MiB via `MAX_SESSION_PAYLOAD_BYTES`. Pruning
+  paths emit an `IMPORT_PREVIEW_ABANDONED` audit row.
+- **R01-N15 — `noreferrer` on external task URL link.** The
+  user-controlled `t.url` link in `views/sprints/_task_list.twig` now
+  carries `rel="noopener noreferrer"`; sequential sprint IDs no longer
+  leak via Referer.
+- **R01-N16 — `bin/audit.sh` composer-audit helper.** Wraps `composer
+  audit --locked --no-interaction` inside the runtime image so the
+  audit reflects the live container's dependency tree (host PHP often
+  lacks the right ext-* set). Honours `SPRINT_PLANER_IMAGE` for
+  non-default tags. Admin manual §5.5 documents a recommended cadence.
+- **R01-N18 — OIDC email trust path.** `OidcClaims::resolveEmail()` is
+  the single decision point: drops the `preferred_username` fallback;
+  honours `claims.email` only when `email_verified !== false`; falls
+  back to `entra:<oid>` when no trusted email is available.
+- **R01-N19 — CSP `report-uri /csp-report` + audit endpoint.** Strict
+  CSP gains a report URI; new public `POST /csp-report` route
+  (`CspReportController`) writes one audit row per browser-fired
+  report. Bodies that fail to decode or exceed the 16 KiB cap return
+  204 with no row written.
+- **R01-N20 — `Response::redirect` rejects non-path locations.** Refuses
+  anything that is not a single `/` followed by a non-`/`, plus
+  CR/LF/NUL guard (header injection). The HTTP→HTTPS canonical
+  redirect moves to a new `Response::external($url, $status)` helper
+  that scheme-restricts to `http`/`https`.
+- **R01-N21 — Twig auto-escape pinned by tests.** Two regression tests
+  in `tests/Http/TwigAutoescapeTest.php`: a behaviour pin renders a
+  known XSS payload through the wired-up `View` env and asserts HTML
+  escaping; a static guard walks every `.twig` file under `views/` and
+  fails on any `|raw`, `|safe`, or `{% autoescape` directive.
+- **R01-N22 — Migrations move to deploy time.** `bin/docker-entrypoint.sh`
+  → `php bin/migrate.php` runs BEFORE Apache binds the port; a failed
+  migration aborts container start. The web request path only checks
+  `schema_version` and returns 503 when something is pending.
+- **R01-N23 — `users.tombstoned_at` for soft erasure.** Privacy-request
+  path that overwrites email / OID with placeholder values, retains
+  the row for FK integrity, and stops the user from signing back in.
+- **R01-N24 — 1 MiB body cap + 5000-item batch cap on JSON endpoints.**
+  Defends against memory-exhaustion via oversized POST bodies.
+- **R01-N25 — `X-Permitted-Cross-Domain-Policies: none` header.** Emit
+  alongside the other security headers.
+- **R01-N26 — One-shot session flash for post-delete chip.** Replaces
+  the query-string-driven flash so the message survives a cookie path
+  but doesn't bookmark.
+- **R01-N27 — Backgrounded session-file GC loop in entrypoint.** Long
+  running cleanup decoupled from the request path.
+- **R01-N28 — Drop dead `$changed[]` in `SettingsController::update`.**
+  Code-quality cleanup; no behaviour change.
+
+### Notes on accepted-by-design findings
+
+The following review findings were closed without code change after
+analysis; see `doc/REVIEW_01.md` for the rationale on each:
+R01-N09 (`SameSite=Lax` retained — `Strict` would block the OIDC
+callback), R01-N17 (concurrent-tab OIDC clobber is correct
+RFC behaviour), R01-N29, R01-N30, R01-N32, R01-N33, R01-N34.
+
+[Unreleased]: https://github.com/chiappa/sprint_planer_web/compare/v0.22.0...HEAD
+[0.22.0]: https://github.com/chiappa/sprint_planer_web/releases/tag/v0.22.0

+ 201 - 0
LICENSE

@@ -0,0 +1,201 @@
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for describing the origin of the Work and
+      reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may accept and charge a
+      fee for, acceptance of support, warranty, indemnity, or other
+      liability obligations and/or rights consistent with this License.
+      However, in accepting such obligations, You may act only on Your
+      own behalf and on Your sole responsibility, not on behalf of any
+      other Contributor, and only if You agree to indemnify, defend,
+      and hold each Contributor harmless for any liability incurred by,
+      or claims asserted against, such Contributor by reason of your
+      accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.

+ 8 - 0
bin/migrate.php

@@ -1,5 +1,13 @@
 #!/usr/bin/env php
 <?php
+/*
+ * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * See the LICENSE file in the project root for the full license text.
+ */
 
 declare(strict_types=1);
 

+ 8 - 0
public/assets/js/app.js

@@ -1,3 +1,11 @@
+/*
+ * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * See the LICENSE file in the project root for the full license text.
+ */
 /**
  * Site-wide behaviour. Three concerns:
  *   1. Vanilla-JS click delegation for [data-href] rows (sprint list).

+ 8 - 0
public/assets/js/sprint-planner.js

@@ -1,3 +1,11 @@
+/*
+ * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * See the LICENSE file in the project root for the full license text.
+ */
 /**
  * Sprint planning view — /sprints/{id} and /sprints/{id}/present.
  *

+ 8 - 0
public/assets/js/sprint-settings.js

@@ -1,3 +1,11 @@
+/*
+ * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * See the LICENSE file in the project root for the full license text.
+ */
 /**
  * /sprints/{id}/settings — vanilla JS + SortableJS.
  *  - debounced PATCH /sprints/{id} for sprint meta on change; when the

+ 8 - 0
public/assets/js/theme-init.js

@@ -1,3 +1,11 @@
+/*
+ * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * See the LICENSE file in the project root for the full license text.
+ */
 /* Phase 16: sync theme apply before stylesheet load — avoids FOUC.
    Loaded without defer/async from <head>. Reads sp:theme; dark → add class.
    Wrapped in try/catch so private-window localStorage denials no-op. */

+ 8 - 0
public/index.php

@@ -1,4 +1,12 @@
 <?php
+/*
+ * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * See the LICENSE file in the project root for the full license text.
+ */
 
 declare(strict_types=1);
 

+ 8 - 0
src/Auth/BootstrapAdmin.php

@@ -1,4 +1,12 @@
 <?php
+/*
+ * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * See the LICENSE file in the project root for the full license text.
+ */
 
 declare(strict_types=1);
 

+ 8 - 0
src/Auth/LocalAdmin.php

@@ -1,4 +1,12 @@
 <?php
+/*
+ * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * See the LICENSE file in the project root for the full license text.
+ */
 
 declare(strict_types=1);
 

+ 8 - 0
src/Auth/OidcClaims.php

@@ -1,4 +1,12 @@
 <?php
+/*
+ * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * See the LICENSE file in the project root for the full license text.
+ */
 
 declare(strict_types=1);
 

+ 8 - 0
src/Auth/OidcClient.php

@@ -1,4 +1,12 @@
 <?php
+/*
+ * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * See the LICENSE file in the project root for the full license text.
+ */
 
 declare(strict_types=1);
 

+ 8 - 0
src/Auth/SessionGuard.php

@@ -1,4 +1,12 @@
 <?php
+/*
+ * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * See the LICENSE file in the project root for the full license text.
+ */
 
 declare(strict_types=1);
 

+ 8 - 0
src/Controllers/AuditController.php

@@ -1,4 +1,12 @@
 <?php
+/*
+ * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * See the LICENSE file in the project root for the full license text.
+ */
 
 declare(strict_types=1);
 

+ 8 - 0
src/Controllers/AuthController.php

@@ -1,4 +1,12 @@
 <?php
+/*
+ * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * See the LICENSE file in the project root for the full license text.
+ */
 
 declare(strict_types=1);
 

+ 8 - 0
src/Controllers/CspReportController.php

@@ -1,4 +1,12 @@
 <?php
+/*
+ * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * See the LICENSE file in the project root for the full license text.
+ */
 
 declare(strict_types=1);
 

+ 8 - 0
src/Controllers/ImportController.php

@@ -1,4 +1,12 @@
 <?php
+/*
+ * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * See the LICENSE file in the project root for the full license text.
+ */
 
 declare(strict_types=1);
 

+ 8 - 0
src/Controllers/SettingsController.php

@@ -1,4 +1,12 @@
 <?php
+/*
+ * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * See the LICENSE file in the project root for the full license text.
+ */
 
 declare(strict_types=1);
 

+ 8 - 0
src/Controllers/SprintController.php

@@ -1,4 +1,12 @@
 <?php
+/*
+ * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * See the LICENSE file in the project root for the full license text.
+ */
 
 declare(strict_types=1);
 

+ 8 - 0
src/Controllers/TaskController.php

@@ -1,4 +1,12 @@
 <?php
+/*
+ * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * See the LICENSE file in the project root for the full license text.
+ */
 
 declare(strict_types=1);
 

+ 8 - 0
src/Controllers/UserController.php

@@ -1,4 +1,12 @@
 <?php
+/*
+ * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * See the LICENSE file in the project root for the full license text.
+ */
 
 declare(strict_types=1);
 

+ 8 - 0
src/Controllers/WorkerController.php

@@ -1,4 +1,12 @@
 <?php
+/*
+ * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * See the LICENSE file in the project root for the full license text.
+ */
 
 declare(strict_types=1);
 

+ 8 - 0
src/Db/Connection.php

@@ -1,4 +1,12 @@
 <?php
+/*
+ * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * See the LICENSE file in the project root for the full license text.
+ */
 
 declare(strict_types=1);
 

+ 8 - 0
src/Db/Migrator.php

@@ -1,4 +1,12 @@
 <?php
+/*
+ * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * See the LICENSE file in the project root for the full license text.
+ */
 
 declare(strict_types=1);
 

+ 8 - 0
src/Domain/Import/ImportResult.php

@@ -1,4 +1,12 @@
 <?php
+/*
+ * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * See the LICENSE file in the project root for the full license text.
+ */
 
 declare(strict_types=1);
 

+ 8 - 0
src/Domain/Import/ParsedAssignment.php

@@ -1,4 +1,12 @@
 <?php
+/*
+ * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * See the LICENSE file in the project root for the full license text.
+ */
 
 declare(strict_types=1);
 

+ 8 - 0
src/Domain/Import/ParsedSheet.php

@@ -1,4 +1,12 @@
 <?php
+/*
+ * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * See the LICENSE file in the project root for the full license text.
+ */
 
 declare(strict_types=1);
 

+ 8 - 0
src/Domain/Import/ParsedTask.php

@@ -1,4 +1,12 @@
 <?php
+/*
+ * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * See the LICENSE file in the project root for the full license text.
+ */
 
 declare(strict_types=1);
 

+ 8 - 0
src/Domain/Import/ParsedWeek.php

@@ -1,4 +1,12 @@
 <?php
+/*
+ * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * See the LICENSE file in the project root for the full license text.
+ */
 
 declare(strict_types=1);
 

+ 8 - 0
src/Domain/Import/ParsedWorker.php

@@ -1,4 +1,12 @@
 <?php
+/*
+ * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * See the LICENSE file in the project root for the full license text.
+ */
 
 declare(strict_types=1);
 

+ 8 - 0
src/Domain/Sprint.php

@@ -1,4 +1,12 @@
 <?php
+/*
+ * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * See the LICENSE file in the project root for the full license text.
+ */
 
 declare(strict_types=1);
 

+ 8 - 0
src/Domain/SprintWeek.php

@@ -1,4 +1,12 @@
 <?php
+/*
+ * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * See the LICENSE file in the project root for the full license text.
+ */
 
 declare(strict_types=1);
 

+ 8 - 0
src/Domain/SprintWorker.php

@@ -1,4 +1,12 @@
 <?php
+/*
+ * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * See the LICENSE file in the project root for the full license text.
+ */
 
 declare(strict_types=1);
 

+ 8 - 0
src/Domain/SprintWorkerDay.php

@@ -1,4 +1,12 @@
 <?php
+/*
+ * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * See the LICENSE file in the project root for the full license text.
+ */
 
 declare(strict_types=1);
 

+ 8 - 0
src/Domain/Task.php

@@ -1,4 +1,12 @@
 <?php
+/*
+ * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * See the LICENSE file in the project root for the full license text.
+ */
 
 declare(strict_types=1);
 

+ 8 - 0
src/Domain/TaskAssignment.php

@@ -1,4 +1,12 @@
 <?php
+/*
+ * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * See the LICENSE file in the project root for the full license text.
+ */
 
 declare(strict_types=1);
 

+ 8 - 0
src/Domain/User.php

@@ -1,4 +1,12 @@
 <?php
+/*
+ * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * See the LICENSE file in the project root for the full license text.
+ */
 
 declare(strict_types=1);
 

+ 8 - 0
src/Domain/Worker.php

@@ -1,4 +1,12 @@
 <?php
+/*
+ * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * See the LICENSE file in the project root for the full license text.
+ */
 
 declare(strict_types=1);
 

+ 8 - 0
src/Http/FatalErrorHandler.php

@@ -1,4 +1,12 @@
 <?php
+/*
+ * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * See the LICENSE file in the project root for the full license text.
+ */
 
 declare(strict_types=1);
 

+ 8 - 0
src/Http/Request.php

@@ -1,4 +1,12 @@
 <?php
+/*
+ * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * See the LICENSE file in the project root for the full license text.
+ */
 
 declare(strict_types=1);
 

+ 8 - 0
src/Http/Response.php

@@ -1,4 +1,12 @@
 <?php
+/*
+ * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * See the LICENSE file in the project root for the full license text.
+ */
 
 declare(strict_types=1);
 

+ 8 - 0
src/Http/Router.php

@@ -1,4 +1,12 @@
 <?php
+/*
+ * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * See the LICENSE file in the project root for the full license text.
+ */
 
 declare(strict_types=1);
 

+ 8 - 0
src/Http/TrustedProxies.php

@@ -1,4 +1,12 @@
 <?php
+/*
+ * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * See the LICENSE file in the project root for the full license text.
+ */
 
 declare(strict_types=1);
 

+ 8 - 0
src/Http/View.php

@@ -1,4 +1,12 @@
 <?php
+/*
+ * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * See the LICENSE file in the project root for the full license text.
+ */
 
 declare(strict_types=1);
 

+ 8 - 0
src/Repositories/AppSettingsRepository.php

@@ -1,4 +1,12 @@
 <?php
+/*
+ * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * See the LICENSE file in the project root for the full license text.
+ */
 
 declare(strict_types=1);
 

+ 8 - 0
src/Repositories/AuditRepository.php

@@ -1,4 +1,12 @@
 <?php
+/*
+ * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * See the LICENSE file in the project root for the full license text.
+ */
 
 declare(strict_types=1);
 

+ 8 - 0
src/Repositories/AuthThrottleRepository.php

@@ -1,4 +1,12 @@
 <?php
+/*
+ * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * See the LICENSE file in the project root for the full license text.
+ */
 
 declare(strict_types=1);
 

+ 8 - 0
src/Repositories/SprintRepository.php

@@ -1,4 +1,12 @@
 <?php
+/*
+ * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * See the LICENSE file in the project root for the full license text.
+ */
 
 declare(strict_types=1);
 

+ 8 - 0
src/Repositories/SprintWeekRepository.php

@@ -1,4 +1,12 @@
 <?php
+/*
+ * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * See the LICENSE file in the project root for the full license text.
+ */
 
 declare(strict_types=1);
 

+ 8 - 0
src/Repositories/SprintWorkerDayRepository.php

@@ -1,4 +1,12 @@
 <?php
+/*
+ * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * See the LICENSE file in the project root for the full license text.
+ */
 
 declare(strict_types=1);
 

+ 8 - 0
src/Repositories/SprintWorkerRepository.php

@@ -1,4 +1,12 @@
 <?php
+/*
+ * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * See the LICENSE file in the project root for the full license text.
+ */
 
 declare(strict_types=1);
 

+ 8 - 0
src/Repositories/TaskAssignmentRepository.php

@@ -1,4 +1,12 @@
 <?php
+/*
+ * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * See the LICENSE file in the project root for the full license text.
+ */
 
 declare(strict_types=1);
 

+ 8 - 0
src/Repositories/TaskRepository.php

@@ -1,4 +1,12 @@
 <?php
+/*
+ * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * See the LICENSE file in the project root for the full license text.
+ */
 
 declare(strict_types=1);
 

+ 8 - 0
src/Repositories/UserRepository.php

@@ -1,4 +1,12 @@
 <?php
+/*
+ * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * See the LICENSE file in the project root for the full license text.
+ */
 
 declare(strict_types=1);
 

+ 8 - 0
src/Repositories/WorkerRepository.php

@@ -1,4 +1,12 @@
 <?php
+/*
+ * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * See the LICENSE file in the project root for the full license text.
+ */
 
 declare(strict_types=1);
 

+ 8 - 0
src/Services/AuditLogger.php

@@ -1,4 +1,12 @@
 <?php
+/*
+ * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * See the LICENSE file in the project root for the full license text.
+ */
 
 declare(strict_types=1);
 

+ 8 - 0
src/Services/CapacityCalculator.php

@@ -1,4 +1,12 @@
 <?php
+/*
+ * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * See the LICENSE file in the project root for the full license text.
+ */
 
 declare(strict_types=1);
 

+ 8 - 0
src/Services/Import/SprintImporter.php

@@ -1,4 +1,12 @@
 <?php
+/*
+ * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * See the LICENSE file in the project root for the full license text.
+ */
 
 declare(strict_types=1);
 

+ 8 - 0
src/Services/Import/XlsxColorClassifier.php

@@ -1,4 +1,12 @@
 <?php
+/*
+ * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * See the LICENSE file in the project root for the full license text.
+ */
 
 declare(strict_types=1);
 

+ 8 - 0
src/Services/Import/XlsxSprintImporter.php

@@ -1,4 +1,12 @@
 <?php
+/*
+ * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * See the LICENSE file in the project root for the full license text.
+ */
 
 declare(strict_types=1);
 

+ 8 - 0
tailwind.config.js

@@ -1,3 +1,11 @@
+/*
+ * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * See the LICENSE file in the project root for the full license text.
+ */
 /** @type {import('tailwindcss').Config} */
 module.exports = {
     darkMode: 'class',

+ 8 - 0
tests/Auth/BootstrapAdminTest.php

@@ -1,4 +1,12 @@
 <?php
+/*
+ * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * See the LICENSE file in the project root for the full license text.
+ */
 
 declare(strict_types=1);
 

+ 8 - 0
tests/Auth/LocalAdminTest.php

@@ -1,4 +1,12 @@
 <?php
+/*
+ * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * See the LICENSE file in the project root for the full license text.
+ */
 
 declare(strict_types=1);
 

+ 8 - 0
tests/Auth/OidcClaimsTest.php

@@ -1,4 +1,12 @@
 <?php
+/*
+ * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * See the LICENSE file in the project root for the full license text.
+ */
 
 declare(strict_types=1);
 

+ 8 - 0
tests/Auth/SessionGuardTest.php

@@ -1,4 +1,12 @@
 <?php
+/*
+ * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * See the LICENSE file in the project root for the full license text.
+ */
 
 declare(strict_types=1);
 

+ 8 - 0
tests/Cascade/CascadeAuditTest.php

@@ -1,4 +1,12 @@
 <?php
+/*
+ * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * See the LICENSE file in the project root for the full license text.
+ */
 
 declare(strict_types=1);
 

+ 8 - 0
tests/Controllers/AuditControllerTest.php

@@ -1,4 +1,12 @@
 <?php
+/*
+ * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * See the LICENSE file in the project root for the full license text.
+ */
 
 declare(strict_types=1);
 

+ 8 - 0
tests/Controllers/CspReportControllerTest.php

@@ -1,4 +1,12 @@
 <?php
+/*
+ * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * See the LICENSE file in the project root for the full license text.
+ */
 
 declare(strict_types=1);
 

+ 8 - 0
tests/Controllers/ImportControllerTest.php

@@ -1,4 +1,12 @@
 <?php
+/*
+ * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * See the LICENSE file in the project root for the full license text.
+ */
 
 declare(strict_types=1);
 

+ 8 - 0
tests/Controllers/SprintControllerTest.php

@@ -1,4 +1,12 @@
 <?php
+/*
+ * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * See the LICENSE file in the project root for the full license text.
+ */
 
 declare(strict_types=1);
 

+ 8 - 0
tests/Controllers/TaskControllerTest.php

@@ -1,4 +1,12 @@
 <?php
+/*
+ * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * See the LICENSE file in the project root for the full license text.
+ */
 
 declare(strict_types=1);
 

+ 8 - 0
tests/Controllers/UserControllerTest.php

@@ -1,4 +1,12 @@
 <?php
+/*
+ * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * See the LICENSE file in the project root for the full license text.
+ */
 
 declare(strict_types=1);
 

+ 8 - 0
tests/Db/MigrationBackfillTest.php

@@ -1,4 +1,12 @@
 <?php
+/*
+ * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * See the LICENSE file in the project root for the full license text.
+ */
 
 declare(strict_types=1);
 

+ 8 - 0
tests/Db/MigratorTest.php

@@ -1,4 +1,12 @@
 <?php
+/*
+ * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * See the LICENSE file in the project root for the full license text.
+ */
 
 declare(strict_types=1);
 

+ 8 - 0
tests/Domain/SprintWeekTest.php

@@ -1,4 +1,12 @@
 <?php
+/*
+ * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * See the LICENSE file in the project root for the full license text.
+ */
 
 declare(strict_types=1);
 

+ 8 - 0
tests/Domain/TaskAssignmentTest.php

@@ -1,4 +1,12 @@
 <?php
+/*
+ * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * See the LICENSE file in the project root for the full license text.
+ */
 
 declare(strict_types=1);
 

+ 8 - 0
tests/Http/FatalErrorHandlerTest.php

@@ -1,4 +1,12 @@
 <?php
+/*
+ * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * See the LICENSE file in the project root for the full license text.
+ */
 
 declare(strict_types=1);
 

+ 8 - 0
tests/Http/RequestTest.php

@@ -1,4 +1,12 @@
 <?php
+/*
+ * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * See the LICENSE file in the project root for the full license text.
+ */
 
 declare(strict_types=1);
 

+ 8 - 0
tests/Http/ResponseTest.php

@@ -1,4 +1,12 @@
 <?php
+/*
+ * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * See the LICENSE file in the project root for the full license text.
+ */
 
 declare(strict_types=1);
 

+ 8 - 0
tests/Http/TrustedProxiesTest.php

@@ -1,4 +1,12 @@
 <?php
+/*
+ * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * See the LICENSE file in the project root for the full license text.
+ */
 
 declare(strict_types=1);
 

+ 8 - 0
tests/Http/TwigAutoescapeTest.php

@@ -1,4 +1,12 @@
 <?php
+/*
+ * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * See the LICENSE file in the project root for the full license text.
+ */
 
 declare(strict_types=1);
 

+ 8 - 0
tests/Http/TwigViewTest.php

@@ -1,4 +1,12 @@
 <?php
+/*
+ * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * See the LICENSE file in the project root for the full license text.
+ */
 
 declare(strict_types=1);
 

+ 8 - 0
tests/Repositories/AppSettingsRepositoryTest.php

@@ -1,4 +1,12 @@
 <?php
+/*
+ * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * See the LICENSE file in the project root for the full license text.
+ */
 
 declare(strict_types=1);
 

+ 8 - 0
tests/Repositories/AuditRepositoryTest.php

@@ -1,4 +1,12 @@
 <?php
+/*
+ * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * See the LICENSE file in the project root for the full license text.
+ */
 
 declare(strict_types=1);
 

+ 8 - 0
tests/Repositories/AuthThrottleRepositoryTest.php

@@ -1,4 +1,12 @@
 <?php
+/*
+ * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * See the LICENSE file in the project root for the full license text.
+ */
 
 declare(strict_types=1);
 

+ 8 - 0
tests/Repositories/SprintWeekRepositoryTest.php

@@ -1,4 +1,12 @@
 <?php
+/*
+ * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * See the LICENSE file in the project root for the full license text.
+ */
 
 declare(strict_types=1);
 

+ 8 - 0
tests/Repositories/TaskAssignmentRepositoryTest.php

@@ -1,4 +1,12 @@
 <?php
+/*
+ * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * See the LICENSE file in the project root for the full license text.
+ */
 
 declare(strict_types=1);
 

+ 8 - 0
tests/Repositories/UserRepositoryTest.php

@@ -1,4 +1,12 @@
 <?php
+/*
+ * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * See the LICENSE file in the project root for the full license text.
+ */
 
 declare(strict_types=1);
 

+ 8 - 0
tests/Services/AuditLoggerTest.php

@@ -1,4 +1,12 @@
 <?php
+/*
+ * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * See the LICENSE file in the project root for the full license text.
+ */
 
 declare(strict_types=1);
 

+ 8 - 0
tests/Services/CapacityCalculatorTest.php

@@ -1,4 +1,12 @@
 <?php
+/*
+ * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * See the LICENSE file in the project root for the full license text.
+ */
 
 declare(strict_types=1);
 

+ 8 - 0
tests/Services/Import/SprintImporterCommitTest.php

@@ -1,4 +1,12 @@
 <?php
+/*
+ * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * See the LICENSE file in the project root for the full license text.
+ */
 
 declare(strict_types=1);
 

+ 8 - 0
tests/Services/Import/XlsxColorClassifierTest.php

@@ -1,4 +1,12 @@
 <?php
+/*
+ * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * See the LICENSE file in the project root for the full license text.
+ */
 
 declare(strict_types=1);
 

+ 8 - 0
tests/Services/Import/XlsxSprintImporterTest.php

@@ -1,4 +1,12 @@
 <?php
+/*
+ * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * See the LICENSE file in the project root for the full license text.
+ */
 
 declare(strict_types=1);
 

+ 8 - 0
tests/TestCase.php

@@ -1,4 +1,12 @@
 <?php
+/*
+ * Copyright 2026 Alessandro Chiapparini <sprint_planer_web@chiapparini.org>
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * See the LICENSE file in the project root for the full license text.
+ */
 
 declare(strict_types=1);