|
|
@@ -1633,6 +1633,123 @@ OIDC kill-switch (`OIDC_ENABLED=false`):
|
|
|
same way the sprints table on `/` does — also mirrored on
|
|
|
JS-built rows in `sprint-planner.js::buildTaskRow`.
|
|
|
|
|
|
+- [x] **R01-N22 — migrations move to deploy time + refuse-to-serve
|
|
|
+ safety net** (`43b2fc9` / `114de03`). `bin/docker-entrypoint.sh`
|
|
|
+ now runs `php bin/migrate.php` before Apache binds the port; the
|
|
|
+ request path only checks for pending migrations and 503s on
|
|
|
+ mismatch (no auto-migration mid-request). Closes the "fatal
|
|
|
+ error during migration leaves the schema partially applied"
|
|
|
+ class of failure. Fix from `doc/REVIEW_01.md`.
|
|
|
+
|
|
|
+- [x] **R01-N23 — soft erasure via `users.tombstoned_at`**
|
|
|
+ (`22a3840` / `3853dda`). Privacy-request flow: a TOMBSTONE
|
|
|
+ action on `users` zeroes identifying columns and stamps
|
|
|
+ `tombstoned_at`; the row stays for FK integrity so audit
|
|
|
+ history doesn't break. Fix from `doc/REVIEW_01.md`.
|
|
|
+
|
|
|
+- [x] **R01-N24 — JSON body cap (1 MiB) + batch cap (5000 items)**
|
|
|
+ (`821122d` / `abe9595`). All JSON endpoints reject oversized
|
|
|
+ bodies and oversized arrays before any parse/loop work, so a
|
|
|
+ runaway or malicious client can't OOM PHP or hold a worker.
|
|
|
+ Fix from `doc/REVIEW_01.md`.
|
|
|
+
|
|
|
+- [x] **R01-N25 + R01-N26 + R01-N27 — `X-Permitted-Cross-Domain-Policies`,
|
|
|
+ one-shot delete-chip flash, backgrounded session GC**
|
|
|
+ (`f6ce13f` / `1f11117` / `32d03fc` / `b706a17`). Three LOW-severity
|
|
|
+ fixes batched: response header pinned to `none`; the post-delete
|
|
|
+ success chip is now a one-shot session flash (no replay on
|
|
|
+ refresh); the entrypoint runs a backgrounded session-file GC
|
|
|
+ loop so stale `sess_*` files don't pile up under low traffic.
|
|
|
+ Fixes from `doc/REVIEW_01.md`.
|
|
|
+
|
|
|
+- [x] **R01-N28 — drop dead `$changed[]` accumulator**
|
|
|
+ (`216c15d` / `8e5a8d1`). `SettingsController::update` built a
|
|
|
+ `$changed[]` list it never read; deleted along with surrounding
|
|
|
+ bookkeeping. Fix from `doc/REVIEW_01.md`.
|
|
|
+
|
|
|
+- [x] **R01-N29 / R01-N30 / R01-N32 / R01-N33 / R01-N34 —
|
|
|
+ accepted-by-design** (`8e5a8d1` / `1edc853`). Five findings
|
|
|
+ closed without code changes after audit-side concurrence: N29
|
|
|
+ (`Migrator::file_get_contents` — migrations are committed code
|
|
|
+ reviewed in PRs and now run at deploy time post-N22); N30
|
|
|
+ (import_upload colour-mapping — only emitter is admin-gated
|
|
|
+ `GET /sprints/import`); N32 (sprint delete confirmation —
|
|
|
+ server-side `confirm_name` check is authoritative,
|
|
|
+ `data-confirm-name` is JS UX); N33 (`data-csrf` exposure —
|
|
|
+ standard fetch/htmx pattern, token rotation + strict CSP keep
|
|
|
+ it safe); N34 (migrations/ on disk — committed code,
|
|
|
+ deploy-time apply per N22). Closes `doc/REVIEW_01.md` with no
|
|
|
+ open findings.
|
|
|
+
|
|
|
+- [x] **Release v0.22.0** (`7449fb2`). Apache 2.0 license,
|
|
|
+ `CHANGELOG.md`, and per-file SPDX headers. No behavioural
|
|
|
+ change.
|
|
|
+
|
|
|
+- [x] **OIDC kill-switch + prod-bootstrap guard + Runtime-panel
|
|
|
+ refresh — Release v0.23.0** (`60aeb55` / `56e6526` / `5f6febf`
|
|
|
+ / `3f014af`). New `OIDC_ENABLED=false` env var (default on)
|
|
|
+ hard-disables OIDC and routes all sign-ins through
|
|
|
+ `LOCAL_ADMIN_*` — meant for dev / testing / on-prem deploys.
|
|
|
+ The bootstrap path refuses to start in prod when ENTRA vars
|
|
|
+ look unconfigured (loud over silent). The admin-only Runtime
|
|
|
+ panel on `/` reads the OIDC enabled/disabled state and
|
|
|
+ surfaces app version + creator from `App\Meta::VERSION` /
|
|
|
+ `::CREATOR`.
|
|
|
+
|
|
|
+- [x] **2-pane task hamburger popup + non-admin actions —
|
|
|
+ Release v0.24.0** (`8c72e7a` / `31506c6` / `2a20f35` /
|
|
|
+ `970ebe4`). Per-task hamburger menu becomes a 2-pane popup
|
|
|
+ (action list left, sprint flyout right for move/copy).
|
|
|
+ Non-admin viewers get a reduced action set (info popover, no
|
|
|
+ destructive ops). Task title row gains inline reference icons
|
|
|
+ + a per-row info popover. The beamer view keeps the hamburger
|
|
|
+ trigger visible so the popup works there too.
|
|
|
+
|
|
|
+- [x] **Welcome/login logo polish + Status column drop —
|
|
|
+ Release v0.25.0** (`cdb1249` / `8b2bd48` / `90b10d5` /
|
|
|
+ `5f28590` / `d8e7f89` / `b8a6603` / `5cbecee`). Five SVG logo
|
|
|
+ concepts shipped under `doc/`; concept #3 (cycle, with radial
|
|
|
+ indigo glow) wired in as the page-header brand mark + SVG
|
|
|
+ favicon. Welcome / local-login flows centre the logo above the
|
|
|
+ card at 144 px. The Home sprint table drops its Status column
|
|
|
+ (derivable from start/end dates, no audit signal lost).
|
|
|
+ Audit-log "When" flips to viewer-local time
|
|
|
+ (`dd.mm.YYYY HH:mm:ss`); the beamer view centres vertical
|
|
|
+ worker-name headers in their cells (`964a2f5` / `d2e570e`).
|
|
|
+
|
|
|
+- [x] **Present view: brand logo + sprint switcher + htmx CSP fix**
|
|
|
+ (`f10a1dd` / `4ae4db6` / `1dba877`). Beamer view picks up the
|
|
|
+ header brand mark; a sprint-switcher dropdown sits next to
|
|
|
+ Close so the operator can hop to a neighbouring sprint without
|
|
|
+ visiting `/`. Side issue: htmx was injecting an inline
|
|
|
+ indicator `<style>` that would have required relaxing the
|
|
|
+ strict CSP — fixed by disabling the indicator-style injection
|
|
|
+ so `style-src 'self'` stays.
|
|
|
+
|
|
|
+- [x] **REVIEW_02 audit landing** (`937fbfd` / `dab0e66`). Initial
|
|
|
+ simplification + maintainability audit at `doc/REVIEW_02.md`,
|
|
|
+ paired with a workflow note requiring SPEC.md to be read
|
|
|
+ before any finding. R02-N01..N03 (above) are the resulting
|
|
|
+ fixes.
|
|
|
+
|
|
|
+- [x] **R02-N02 — Task-row markup unified between server and JS**
|
|
|
+ (`d5a09ff`). `sprint-planner.js::buildTaskRow` hand-rolled
|
|
|
+ ~150 lines of `<tr>` construction to mirror what
|
|
|
+ `views/sprints/_task_list.twig` rendered server-side, so any
|
|
|
+ task-row tweak had to be made in two places. Drift had bitten
|
|
|
+ three times (`7c298d3` owner-dropdown empty until refresh,
|
|
|
+ `23ab365` missing `data-col` stamps, `f204611` whitespace-nowrap
|
|
|
+ re-mirror). Fix: extract the row markup into
|
|
|
+ `views/sprints/_task_row.twig`, call it from `_task_list.twig`
|
|
|
+ in the for-loop, and once more inside a hidden
|
|
|
+ `<template data-task-row-template>` (admin-only) at the bottom.
|
|
|
+ `buildTaskRow` now clones the template and populates only the
|
|
|
+ varying fields; the unused `ownerChoices()` /
|
|
|
+ `sprintWorkerHeaders()` helpers are gone. `TwigViewTest` pins
|
|
|
+ both directions — marker on the admin template, absence of
|
|
|
+ `<template data-task-row-template>` for read-only viewers.
|
|
|
+ Second finding from `doc/REVIEW_02.md`.
|
|
|
+
|
|
|
- [x] **R02-N01 — Capacity formula no longer duplicated in JS**
|
|
|
(`14b1cfd`). The JS reproduced `roundHalf` / `after_reserves` /
|
|
|
`committed_p1` / `available` in `sprint-planner.js` so on-screen
|
|
|
@@ -1654,9 +1771,46 @@ OIDC kill-switch (`OIDC_ENABLED=false`):
|
|
|
`per_worker` for the source sprint (it didn't before, because the
|
|
|
old code refreshed via `recomputeAllCapacity()`); the move handler
|
|
|
applies it like every other mutation. JS file shrinks ~40 lines;
|
|
|
- `CapacityCalculator.php` and its tests are untouched. First fix
|
|
|
+ `CapacityCalculator.php` and its tests are untouched.
|
|
|
+
|
|
|
+- [x] **R02-N03 — Form-handler boilerplate consolidated into
|
|
|
+ `SessionGuard::requireAdminForm`** (`57f4143`). Nine form
|
|
|
+ controllers (`SprintController::create / ::delete`,
|
|
|
+ `WorkerController::create / ::update`,
|
|
|
+ `UserController::update / ::tombstone`,
|
|
|
+ `SettingsController::update`,
|
|
|
+ `ImportController::upload / ::commit`) all opened with the same
|
|
|
+ three-line block: `SessionGuard::requireAdmin()` early-return,
|
|
|
+ then `verifyCsrf()` → 403 on mismatch. New form endpoints could
|
|
|
+ silently land missing either gate and the surrounding code
|
|
|
+ looked fine in review — the JSON path already had a twin
|
|
|
+ (`SessionGuard::requireAdminJson`,
|
|
|
+ `SprintController::gateJsonAdmin`); the form path just lacked
|
|
|
+ one. Adds `SessionGuard::requireAdminForm` mirroring
|
|
|
+ `requireAdminJson` but speaking the form dialect (redirect to
|
|
|
+ `/auth/login` on anonymous, 403 text on non-admin / CSRF
|
|
|
+ mismatch); replaces all nine inlined blocks with a single call;
|
|
|
+ migrates `gateJsonAdmin`'s eight call sites to
|
|
|
+ `requireAdminJson` and drops the now-unused private method.
|
|
|
+ Net `+37 / -64` lines, 340 tests still green. Third finding
|
|
|
from `doc/REVIEW_02.md`.
|
|
|
|
|
|
+- [x] **`appctl prod upgrade` — pinned redeploy by release tag**
|
|
|
+ (`f87660e`). New subcommand on the `bin/appctl` wrapper that
|
|
|
+ stops the prod stack, fetches, checks out a target ref, rebuilds
|
|
|
+ images, and starts the stack again. The target defaults to the
|
|
|
+ `latest` lightweight git tag (manually pointed at the current
|
|
|
+ release; bump with `git tag -f latest vX.Y.Z`). The keyword
|
|
|
+ `test` is virtual and resolves to `origin/main` HEAD after
|
|
|
+ fetch, so bleeding-edge redeploys don't need a real branch tag.
|
|
|
+ Dirty working trees print the diff and prompt before any
|
|
|
+ destructive step; an unknown ref errors out *before* the stack
|
|
|
+ is stopped. Bash completion offers `latest`, `test`, and every
|
|
|
+ `v*` tag from `git tag` at the third word. `latest` is local-
|
|
|
+ only on the repo where this was introduced (`bcdc63a` =
|
|
|
+ `v0.26.0`) — it is not pushed to `origin` and won't appear in
|
|
|
+ fresh clones until someone runs `git push origin latest`.
|
|
|
+
|
|
|
### Upcoming
|
|
|
|
|
|
Nothing scheduled.
|
|
|
@@ -1686,6 +1840,7 @@ invocations. Pick the one that matches what you're doing:
|
|
|
| Goal | Command | What runs |
|
|
|
|---|---|---|
|
|
|
| Run prod build (or operate it) | `./appctl prod start` | `docker-compose.yml` only — the `runtime` image with baked CSS |
|
|
|
+| Pin a prod box to a release | `./appctl prod upgrade [VER]` | Stop → `git fetch --tags origin` → `git checkout --detach <ref>` → rebuild → start. `VER` defaults to the `latest` tag (manually pointed at the current release; bump locally with `git tag -f latest vX.Y.Z`). `test` is a virtual keyword that resolves to `origin/main` HEAD after fetch. Warns + prompts before proceeding on a dirty working tree |
|
|
|
| Iterate on code locally | `./appctl dev start` | `docker-compose.yml` + `docker-compose.dev.yml` — adds source bind mounts, `APP_ENV=development`, and a `css-watcher` sidecar that runs `tailwindcss --watch` against the host |
|
|
|
| Lint + tests, one-shot | `./appctl check` | Builds the `tests` Dockerfile target on demand and runs `php -l` + PHPUnit in a `--rm` container; doesn't require the dev stack to be running |
|
|
|
|
|
|
@@ -1847,7 +2002,66 @@ before acting — nothing here is load-bearing once it grows stale.
|
|
|
## 13. Git history (as of this writing)
|
|
|
|
|
|
```
|
|
|
+c913b7d Docs: SPEC §9 + §13 — note appctl prod upgrade tooling
|
|
|
+86baa95 Docs: SPEC §11 — add `appctl prod upgrade` row to the command table
|
|
|
+f87660e Tooling: appctl prod upgrade — pin a prod box to a release tag
|
|
|
+c5fafcf Release v0.26.0: tooling/dev-stack split + R02-N01..N03 simplifications
|
|
|
+50d34de Tooling: replace Makefile with bin/appctl bash wrapper + completion
|
|
|
+4354266 Build: make published host port configurable via HTTP_PORT (default 8080)
|
|
|
+cccc7de Sprint create: pre-check only weekdays inside the date range
|
|
|
+d1c3a0c Fix: dev stack — css-watcher restart loop + readonly-DB 500
|
|
|
+14b0068 Docs: dev/prod compose split + /check skill (SPEC §2 / §3 / §11, README, admin-manual)
|
|
|
+c1557b6 Tooling: /check Claude Code skill + container-tester (Haiku) subagent
|
|
|
+a004c1a Build: split Dockerfile into 4 targets + dev compose overlay + Makefile
|
|
|
+6a68633 Docs: mark R02-N03 fixed-in-57f4143
|
|
|
+57f4143 Fix R02-N03: extract SessionGuard::requireAdminForm + drop SprintController::gateJsonAdmin
|
|
|
+22d5e2a Docs: mark R02-N01 fixed, refresh SPEC §5 / §9 / §13
|
|
|
14b1cfd Fix R02-N01: drop JS-side capacity arithmetic, server is authoritative
|
|
|
+dab0e66 doc/REVIEW_02.md: require reading SPEC.md before any finding
|
|
|
+a0ccd12 Docs: mark R02-N02 fixed-in-d5a09ff
|
|
|
+d5a09ff Fix R02-N02: clone <template data-task-row-template> instead of mirroring _task_list in JS
|
|
|
+937fbfd doc/REVIEW_02.md: simplification + maintainability audit
|
|
|
+1dba877 Present view: sprint switcher dropdown next to Close
|
|
|
+4ae4db6 CSP: stop htmx from injecting its inline indicator <style>
|
|
|
+f10a1dd Present view: add brand logo to the header
|
|
|
+5cbecee Release v0.25.0: welcome/login logo polish + drop Status column
|
|
|
+b8a6603 Home: drop the Status column from the sprint table
|
|
|
+d8e7f89 Welcome/login: move logo inside the card and triple its size (48 → 144)
|
|
|
+5f28590 Welcome: match local-login layout with centered logo above the box
|
|
|
+90b10d5 Brand: cycle logo in the page header + SVG favicon
|
|
|
+8b2bd48 Logo 3 (cycle): add a radial indigo glow inside the loop
|
|
|
+cdb1249 Doc: add 5 SVG logo concepts and a light/dark preview page
|
|
|
+d2e570e Beamer: horizontally center vertical worker-name headers in their cells
|
|
|
+964a2f5 Audit log: render When in viewer's local time, dd.mm.YYYY HH:mm:ss
|
|
|
+970ebe4 Release v0.24.0: 2-pane task hamburger popup; non-admin task actions
|
|
|
+2a20f35 Beamer: keep the task hamburger trigger visible
|
|
|
+31506c6 Hamburger popup: 2-pane layout, sprint flyout, non-admin actions
|
|
|
+8c72e7a Task title row: inline reference icons + per-row info popover
|
|
|
+3f014af Release v0.23.0: OIDC kill-switch + prod-bootstrap guard + Runtime-panel refresh
|
|
|
+5f6febf Runtime panel on / shows app version + creator; OIDC reads enabled/disabled
|
|
|
+56e6526 Docs: CHANGELOG entry for OIDC_ENABLED kill-switch + prod-bootstrap guard
|
|
|
+60aeb55 Add OIDC_ENABLED kill-switch for dev / testing on local-admin only
|
|
|
+7449fb2 Release v0.22.0: Apache 2.0 license + CHANGELOG + per-file headers
|
|
|
+1edc853 Docs: mark R01-N32/N33/N34 accepted-by-design
|
|
|
+8e5a8d1 Docs: mark R01-N28 fixed-in-216c15d; R01-N29/N30 accepted-by-design
|
|
|
+216c15d Fix R01-N28: drop dead $changed[] in SettingsController::update
|
|
|
+b706a17 Docs: mark R01-N25/N26/N27 fixed in f6ce13f/1f11117/32d03fc
|
|
|
+32d03fc Fix R01-N27: backgrounded session-file GC loop in entrypoint
|
|
|
+1f11117 Fix R01-N26: one-shot session flash for post-delete chip
|
|
|
+f6ce13f Fix R01-N25: X-Permitted-Cross-Domain-Policies: none
|
|
|
+abe9595 Docs: mark R01-N24 fixed in 821122d
|
|
|
+821122d Fix R01-N24: 1 MiB body cap + 5000-item batch cap on JSON endpoints
|
|
|
+3853dda Docs: mark R01-N23 fixed in 22a3840
|
|
|
+22a3840 Fix R01-N23: users.tombstoned_at — soft erasure for privacy requests
|
|
|
+114de03 Docs: mark R01-N22 fixed in 43b2fc9
|
|
|
+43b2fc9 Fix R01-N22: move migrations to deploy time, refuse-to-serve safety net
|
|
|
+f3ba328 Docs: mark R01-N21 fixed in 00bcf73
|
|
|
+00bcf73 Fix R01-N21: pin Twig auto-escape with regression tests
|
|
|
+2ea4b0b Docs: mark R01-N20 fixed in f1aa924
|
|
|
+f1aa924 Fix R01-N20: Response::redirect rejects non-path locations
|
|
|
+99f9850 Docs: mark R01-N19 fixed in f59f368
|
|
|
+f59f368 Fix R01-N19: CSP report-uri + audit endpoint
|
|
|
+cebaf0b Docs: mark R01-N16 / R01-N17 / R01-N18 closed, refresh SPEC §3 / §9 / §11 / §13
|
|
|
ef9b9b8 Fix R01-N16, doc R01-N17: composer-audit helper + admin-manual cadence note
|
|
|
a0b717e Fix R01-N18: trust OIDC email only when issuer hasn't marked it unverified
|
|
|
50f9bd2 Docs: mark R01-N09 / R01-N13 / R01-N14 fixed, refresh SPEC §3 / §9 / §11 / §13
|