Commit Graph
295 Commits
Author SHA1 Message Date
thatguygriff 694366a6c8 Merge pull request 'Add editable, previewable payment-due email template' (#203) from feature/editable-payment-due-email into main
CI / Coding Standards (push) Successful in 15s
CI / No Debug Code (push) Successful in 4s
CI / Tests (PHP 8.5) (push) Successful in 23s
CI / Tests (PHP 8.2) (push) Successful in 29s
CI / Tests (PHP 8.1) (push) Successful in 52s
CI / Tests (PHP 8.3) (push) Successful in 52s
CI / Static Analysis (push) Successful in 1m2s
CI / Build Plugin Zip (push) Successful in 12s
Release / Build and Publish Release (push) Successful in 22s
Release / Open next-version bump PR (push) Successful in 4s
Reviewed-on: #203
Reviewed-by: James Griffin-Allwood <[email protected]>
v1.6.0
2026-09-17 20:40:36 +00:00
Kydoimosandanthropic/claude-opus-4-8 a2c609803e Add editable, previewable payment-due email template
CI / Coding Standards (pull_request) Successful in 29s
CI / Tests (PHP 8.2) (pull_request) Successful in 31s
CI / Tests (PHP 8.3) (pull_request) Successful in 31s
CI / Tests (PHP 8.5) (pull_request) Successful in 31s
CI / No Debug Code (pull_request) Successful in 9s
CI / Tests (PHP 8.1) (pull_request) Successful in 44s
CI / Static Analysis (pull_request) Successful in 51s
CI / Build Plugin Zip (pull_request) Skipped
The "Payment due" notice the daily billing scan sends was fixed wording
baked into PaymentDueMailer. Studio admins can now view, edit and preview
its subject and body under Studio Settings -> Payment Due Email.

- PaymentDueEmailTemplate stores subject/body/item-line as us_payment_due_email_*
  options, each falling back to the built-in default when blank, and renders
  via {token} substitution.
- PaymentDueMailer now builds a token map from the same consolidated
  items/credit/e-transfer/reference data and renders the stored template; the
  default template reproduces the previous wording exactly.
- PaymentEmailController serves the view/edit/save/reset admin page
  (manage_billing, nonce-checked) with a server-rendered sample preview.
- PaymentEmailPreviewEndpoint (POST us-scheduler/v1/payment-email/preview,
  manage_billing) + payment-email-admin.js drive a live preview of unsaved edits.
- Submenu wired under Studio Settings; endpoint wired in RestRegistrar.
- Bump to 1.6.0 and add CHANGELOG entry. New options default gracefully, so
  existing sites need no migration and send the identical notice until edited.

Co-authored-by: anthropic/claude-opus-4-8
2026-09-17 17:33:23 -03:00
thatguygriff ad4e4ae357 Merge pull request 'Apply account credit on same-month rebook; show total credit on student page' (#202) from feature/student-credit-display-and-rebook-fix into main
CI / Coding Standards (push) Successful in 28s
CI / Tests (PHP 8.2) (push) Successful in 31s
CI / Tests (PHP 8.5) (push) Successful in 32s
CI / No Debug Code (push) Successful in 9s
CI / Static Analysis (push) Successful in 37s
CI / Tests (PHP 8.1) (push) Successful in 44s
CI / Tests (PHP 8.3) (push) Successful in 44s
CI / Build Plugin Zip (push) Successful in 13s
Reviewed-on: #202
Reviewed-by: James Griffin-Allwood <[email protected]>
2026-09-17 18:50:43 +00:00
Kydoimosandanthropic/claude-opus-4-8 1a166eb4e3 Add changelog entries for the credit fix and student-page display
CI / Coding Standards (pull_request) Successful in 30s
CI / Tests (PHP 8.2) (pull_request) Successful in 33s
CI / Tests (PHP 8.5) (pull_request) Successful in 33s
CI / Static Analysis (pull_request) Successful in 38s
CI / No Debug Code (pull_request) Successful in 9s
CI / Tests (PHP 8.1) (pull_request) Successful in 42s
CI / Tests (PHP 8.3) (pull_request) Successful in 41s
CI / Build Plugin Zip (pull_request) Skipped
Recorded under the current 1.5.8 section; no version bump.

Co-authored-by: anthropic/claude-opus-4-8
2026-09-17 15:49:16 -03:00
Kydoimosandanthropic/claude-opus-4-8 362980d008 Apply available credit to any at-booking charge, not just scheduled
CI / Coding Standards (pull_request) Successful in 31s
CI / Tests (PHP 8.2) (pull_request) Successful in 30s
CI / Tests (PHP 8.3) (pull_request) Successful in 31s
CI / No Debug Code (pull_request) Successful in 5s
CI / Tests (PHP 8.1) (pull_request) Successful in 43s
CI / Tests (PHP 8.5) (pull_request) Successful in 42s
CI / Static Analysis (pull_request) Successful in 56s
CI / Build Plugin Zip (pull_request) Skipped
A payer's account credit should offset any charge raised at booking, so
drop the scheduled-only guard. Covers the one-time pay-now flow too.

Co-authored-by: anthropic/claude-opus-4-8
2026-09-17 15:46:11 -03:00
Kydoimosandanthropic/claude-opus-4-8 e3ab7973d6 Clarify credit-at-booking comment scopes to scheduled charges
CI / Coding Standards (pull_request) Successful in 30s
CI / Tests (PHP 8.2) (pull_request) Successful in 32s
CI / Tests (PHP 8.3) (pull_request) Successful in 37s
CI / Static Analysis (pull_request) Successful in 38s
CI / No Debug Code (pull_request) Successful in 9s
CI / Tests (PHP 8.1) (pull_request) Successful in 46s
CI / Tests (PHP 8.5) (pull_request) Successful in 45s
CI / Build Plugin Zip (pull_request) Skipped
Co-authored-by: anthropic/claude-opus-4-8
2026-09-17 15:44:41 -03:00
Kydoimosandanthropic/claude-opus-4-8 1758c253a1 Trim credit-at-booking comment to one line
CI / Coding Standards (pull_request) Successful in 23s
CI / No Debug Code (pull_request) Successful in 9s
CI / Tests (PHP 8.2) (pull_request) Successful in 33s
CI / Tests (PHP 8.5) (pull_request) Successful in 34s
CI / Static Analysis (pull_request) Successful in 39s
CI / Tests (PHP 8.3) (pull_request) Successful in 38s
CI / Tests (PHP 8.1) (pull_request) Successful in 46s
CI / Build Plugin Zip (pull_request) Skipped
Co-authored-by: anthropic/claude-opus-4-8
2026-09-17 15:43:57 -03:00
Kydoimosandanthropic/claude-opus-4-8 67017409d7 Drop stale comment about not applying credit at booking
CI / Coding Standards (pull_request) Successful in 29s
CI / Tests (PHP 8.1) (pull_request) Successful in 36s
CI / No Debug Code (pull_request) Successful in 11s
CI / Tests (PHP 8.5) (pull_request) Successful in 36s
CI / Static Analysis (pull_request) Successful in 41s
CI / Tests (PHP 8.2) (pull_request) Successful in 43s
CI / Tests (PHP 8.3) (pull_request) Successful in 43s
CI / Build Plugin Zip (pull_request) Skipped
This change applies credit at booking for scheduled rebooks, so the note
saying the pay-now flow deliberately does not was contradictory.

Co-authored-by: anthropic/claude-opus-4-8
2026-09-17 15:42:38 -03:00
Kydoimosandanthropic/claude-opus-4-8 e7de627752 Apply account credit on same-month rebook, surface credit on student page
CI / Coding Standards (pull_request) Successful in 27s
CI / Tests (PHP 8.2) (pull_request) Successful in 28s
CI / No Debug Code (pull_request) Successful in 9s
CI / Tests (PHP 8.5) (pull_request) Successful in 35s
CI / Tests (PHP 8.1) (pull_request) Successful in 37s
CI / Tests (PHP 8.3) (pull_request) Successful in 43s
CI / Static Analysis (pull_request) Successful in 51s
CI / Build Plugin Zip (pull_request) Skipped
A monthly/weekly lesson booked into a month whose billing date has
already passed is charged at booking time by LessonBooker::settle,
bypassing the daily scan where account credit is otherwise applied. So
rebooking a cancelled paid lesson within the same month charged the
family in full while their cancellation credit sat unused — billed twice
for the same slot.

Apply the payer's credit to that charge-at-booking payment for scheduled
offerings, mirroring the daily scan: a payment fully covered by credit
settles and confirms its lesson. Add PaymentService::findPayment so
settle can re-read the row after applyCredits writes to it.

Also surface the student's total account credit at the top of their
detail page when they hold a balance, so the studio sees it at a glance.

Co-authored-by: anthropic/claude-opus-4-8
2026-09-17 15:39:53 -03:00
thatguygriff d8d842b1ef Merge pull request 'Fix two duplicate-charge bugs in scheduled billing' (#201) from fix/duplicate-payment-due-notice into main
CI / Coding Standards (push) Successful in 16s
CI / No Debug Code (push) Successful in 5s
CI / Tests (PHP 8.5) (push) Successful in 24s
CI / Tests (PHP 8.1) (push) Successful in 40s
CI / Tests (PHP 8.3) (push) Successful in 46s
CI / Static Analysis (push) Successful in 1m2s
CI / Tests (PHP 8.2) (push) Successful in 1m1s
CI / Build Plugin Zip (push) Successful in 15s
Reviewed-on: #201
Reviewed-by: James Griffin-Allwood <[email protected]>
2026-09-17 18:25:18 +00:00
Kydoimosandanthropic/claude-opus-4-8 acda3cda0f Reconcile up-front charges when a class switches to monthly billing
CI / Coding Standards (pull_request) Successful in 25s
CI / No Debug Code (pull_request) Successful in 3s
CI / Tests (PHP 8.1) (pull_request) Successful in 36s
CI / Tests (PHP 8.2) (pull_request) Successful in 39s
CI / Tests (PHP 8.3) (pull_request) Successful in 51s
CI / Tests (PHP 8.5) (pull_request) Successful in 59s
CI / Static Analysis (pull_request) Successful in 1m3s
CI / Build Plugin Zip (pull_request) Skipped
A student who enrols while a group class is pay-now is charged once at
enrolment, and that charge carries no period_key. When the class is
later switched to monthly, the daily scan — which dedups scheduled
charges by period_key — does not see the up-front charge and bills the
enrolment again for the current month, double-charging students who
had already paid. The differing payer between the two rows (student vs
guardian) was a side effect of guardian links created between the two
charge dates, not the cause.

Switching a group class into monthly now adopts each active enrolment's
up-front charge into the current month (stamping period_key and
due_date) so the scan treats that month as billed and charges from the
next month on. Enrolments with no up-front charge, or already billed
for the month, are left alone; weekly and non-group offerings are not
touched. Wired into both offering-update paths (admin form and REST).

Co-authored-by: anthropic/claude-opus-4-8
2026-09-17 15:07:22 -03:00
Kydoimosandanthropic/claude-opus-4-8 e389e40843 Send each scheduled payment's due notice exactly once
CI / Coding Standards (pull_request) Successful in 28s
CI / Tests (PHP 8.1) (pull_request) Successful in 35s
CI / No Debug Code (pull_request) Successful in 9s
CI / Tests (PHP 8.3) (pull_request) Successful in 44s
CI / Tests (PHP 8.2) (pull_request) Successful in 47s
CI / Tests (PHP 8.5) (pull_request) Successful in 48s
CI / Static Analysis (pull_request) Successful in 52s
CI / Build Plugin Zip (pull_request) Skipped
The daily billing scan runs on request via WP-Cron and can overlap
itself under concurrent traffic. Each run emailed the payments it
created with no record that a notice had gone out, so two overlapping
runs could send a payer two identical "Payment due" emails for one
charge — read by families as being billed twice, though only one row
exists.

Stamp us_payments.notice_sent_at atomically before emailing: the scan
now claims each payment with a conditional UPDATE ... WHERE
notice_sent_at IS NULL and only notices, credits and batches the rows
it won. A competing run finds them claimed and stays quiet, so exactly
one notice is sent regardless of how the scan is triggered. A one-time
backfill stamps existing scheduled rows on upgrade so already-noticed
charges are not re-emailed.

Co-authored-by: anthropic/claude-opus-4-8
2026-09-17 13:19:25 -03:00
thatguygriff 6c92fc35fd Delete directory '.claude'
CI / Coding Standards (push) Successful in 17s
CI / No Debug Code (push) Successful in 5s
CI / Tests (PHP 8.5) (push) Successful in 24s
CI / Tests (PHP 8.2) (push) Successful in 35s
CI / Tests (PHP 8.1) (push) Successful in 51s
CI / Tests (PHP 8.3) (push) Successful in 52s
CI / Static Analysis (push) Successful in 1m2s
CI / Build Plugin Zip (push) Successful in 16s
2026-09-17 00:10:58 +00:00
thatguygriff cd86f6bdeb Merge pull request 'Add AGENTS.md, point CLAUDE.md files at it' (#200) from docs/agent-instructions into main
CI / Coding Standards (push) Successful in 24s
CI / No Debug Code (push) Successful in 8s
CI / Tests (PHP 8.5) (push) Successful in 33s
CI / Static Analysis (push) Successful in 38s
CI / Tests (PHP 8.2) (push) Successful in 36s
CI / Tests (PHP 8.1) (push) Successful in 47s
CI / Tests (PHP 8.3) (push) Successful in 47s
CI / Build Plugin Zip (push) Successful in 12s
Reviewed-on: #200
Reviewed-by: James Griffin-Allwood <[email protected]>
2026-09-16 15:29:58 +00:00
Kydoimosandopencode/muse-spark-1.3-contributor-free 63ee248695 Add AGENTS.md, point CLAUDE.md files at it
CI / Coding Standards (pull_request) Successful in 14s
CI / No Debug Code (pull_request) Successful in 3s
CI / Tests (PHP 8.1) (pull_request) Successful in 34s
CI / Tests (PHP 8.3) (pull_request) Successful in 43s
CI / Tests (PHP 8.5) (pull_request) Successful in 45s
CI / Tests (PHP 8.2) (pull_request) Successful in 1m0s
CI / Static Analysis (pull_request) Successful in 1m2s
CI / Build Plugin Zip (pull_request) Skipped
Co-authored-by: opencode/muse-spark-1.3-contributor-free
2026-09-16 12:21:55 -03:00
thatguygriff 91eb30b222 Merge pull request 'Sign the automated version bump commit in CI' (#199) from ci/sign-version-bump-commit into main
CI / Coding Standards (push) Successful in 30s
CI / Tests (PHP 8.5) (push) Successful in 27s
CI / Tests (PHP 8.3) (push) Successful in 34s
CI / No Debug Code (push) Successful in 6s
CI / Static Analysis (push) Successful in 38s
CI / Tests (PHP 8.1) (push) Successful in 41s
CI / Tests (PHP 8.2) (push) Successful in 40s
CI / Build Plugin Zip (push) Successful in 23s
Reviewed-on: #199
Reviewed-by: James Griffin-Allwood <[email protected]>
2026-09-05 23:29:51 +00:00
KydoimosandClaude Opus 5 c4b2b5ccff Sign the automated version bump commit in CI
CI / Coding Standards (pull_request) Successful in 23s
CI / Tests (PHP 8.5) (pull_request) Successful in 27s
CI / Tests (PHP 8.2) (pull_request) Successful in 35s
CI / Static Analysis (pull_request) Successful in 39s
CI / Tests (PHP 8.3) (pull_request) Successful in 40s
CI / Tests (PHP 8.1) (pull_request) Successful in 49s
CI / No Debug Code (pull_request) Successful in 35s
CI / Build Plugin Zip (pull_request) Skipped
main now requires signed commits, and Gitea refuses to merge a pull request
carrying an unsigned one, so the post-release bump PR could not be merged
without disabling the protection first.

The runner now signs that commit with a dedicated release-bot SSH key held as
an organisation Actions secret, trusted by the instance through
[repository.signing] TRUSTED_SSH_KEYS. The key Gitea signs merge commits with
lives on the server and no runner can reach it.

The step fails fast when the secret is missing, unparseable or passphrase
protected, and the commit is re-read before pushing so an unsigned one is
caught here rather than days later at merge time.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-05 20:25:58 -03:00
thatguygriff a4f694a966 Merge pull request 'Bump version to 1.5.7' (#198) from release/bump-1.5.7 into main
CI / Coding Standards (push) Successful in 21s
CI / Tests (PHP 8.1) (push) Successful in 30s
CI / Static Analysis (push) Successful in 36s
CI / Tests (PHP 8.2) (push) Successful in 40s
CI / Tests (PHP 8.5) (push) Successful in 25s
CI / Tests (PHP 8.3) (push) Successful in 36s
CI / No Debug Code (push) Successful in 37s
CI / Build Plugin Zip (push) Successful in 22s
Reviewed-on: #198
Reviewed-by: James Griffin-Allwood <[email protected]>
2026-09-05 19:45:41 -03:00
Release Bot b9f7c96ce6 Bump version to 1.5.7 and open changelog section 2026-09-05 15:04:47 +00:00
thatguygriff f58585be0d Merge pull request 'Fix five findings from a security assessment of the plugin' (#197) from security/assessment-hardening into main
CI / Coding Standards (push) Successful in 18s
CI / No Debug Code (push) Successful in 6s
CI / Tests (PHP 8.5) (push) Successful in 33s
CI / Static Analysis (push) Successful in 37s
CI / Tests (PHP 8.1) (push) Successful in 37s
CI / Tests (PHP 8.3) (push) Successful in 37s
CI / Tests (PHP 8.2) (push) Successful in 39s
CI / Build Plugin Zip (push) Successful in 12s
Release / Build and Publish Release (push) Successful in 25s
Release / Open next-version bump PR (push) Successful in 4s
Reviewed-on: #197
Reviewed-by: James Griffin-Allwood <[email protected]>
v1.5.6
2026-09-05 15:00:33 +00:00
KydoimosandClaude Opus 5 ae1a62883e Fix the CI failures in the uninstaller work
CI / Coding Standards (pull_request) Successful in 28s
CI / No Debug Code (pull_request) Successful in 3s
CI / Tests (PHP 8.1) (pull_request) Successful in 38s
CI / Tests (PHP 8.3) (pull_request) Successful in 40s
CI / Tests (PHP 8.2) (pull_request) Successful in 49s
CI / Static Analysis (pull_request) Successful in 1m0s
CI / Tests (PHP 8.5) (pull_request) Successful in 57s
CI / Build Plugin Zip (pull_request) Skipped
Both were mine, and both were in code the earlier commit could not run.

The four test failures shared one cause: UninstallerTest stubbed get_option
with an arrow function, which captures by value, so every read answered from
a snapshot of the options taken at setUp — before the test set any and before
the run wrote any. Every assertion that depended on reading back what had
just been written therefore saw an empty store. The file's other stubs
already use by-reference closures; this one now does too.

The phpcs error is WordPress.DB.PreparedSQL.NotPrepared on the table drop.
The sniff cannot follow $sql across the null guard that PHPStan requires
(prepare() is nullable), and unlike the repositories — which call through a
typed $this->db property the sniff does not track at all — the uninstaller
calls the global $wpdb, so the sniff sees it. Silenced explicitly, with the
reason.

composer test (996 tests, 2871 assertions), composer lint and composer cs all
pass locally on PHP 8.4.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-05 11:55:53 -03:00
KydoimosandClaude Opus 5 1847159e31 Fix five findings from a security assessment of the plugin
The assessment looked for three things: whether students can reach each
other's bookings, whether payment settings can be dodged, and whether the
plugin opens a way into the rest of the install. The student-isolation and
payment paths held up. These are what did not.

- The front-end login form told WordPress not to work out whether the site
  was secure, so on HTTPS every student's session cookie was issued without
  the Secure flag. wp_signon() only derives it from is_ssl() when the second
  argument is left at its default; an explicit false reads like "no
  preference" and is not.

- The update check took whatever download URL the release API returned and
  handed it to core, which unpacks it over the installed plugin. The package
  must now be https on git.unsupervised.ca exactly, compared on the parsed
  host so a lookalike name cannot pass.

- Uninstalling dropped 2 of 14 tables and left the Stripe secret and webhook
  signing key in wp_options. Removal is now a choice made in advance on
  Access -> Plugin removal: records are kept unless the owner opts in (with a
  typed confirmation), while credentials and the borrowed core registration
  settings go every time.

- Open registration switches on the site-wide users_can_register and makes
  Student the default role, arming any other signup form on the site to mint
  students who could book and be billed immediately. The pending state is now
  decided once, on user_register, rather than by whichever form created the
  account.

- Cancel and withdraw answered "not yours" differently from "does not exist",
  which let a signed-in student enumerate the studio's bookings. Both now
  give the same 404.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-09-05 11:55:53 -03:00
thatguygriff 74df3f5ba8 Merge pull request 'Bump version to 1.5.6' (#196) from release/bump-1.5.6 into main
CI / Coding Standards (push) Successful in 25s
CI / No Debug Code (push) Successful in 7s
CI / Tests (PHP 8.5) (push) Successful in 36s
CI / Tests (PHP 8.1) (push) Successful in 44s
CI / Static Analysis (push) Successful in 46s
CI / Tests (PHP 8.2) (push) Successful in 45s
CI / Tests (PHP 8.3) (push) Successful in 49s
CI / Build Plugin Zip (push) Successful in 22s
Reviewed-on: #196
2026-09-05 11:54:29 -03:00
Release Bot f8f211929c Bump version to 1.5.6 and open changelog section 2026-08-25 02:20:56 +00:00
thatguygriff 61a40f0e8d Merge pull request 'Update stripe-php from 17.6.0 to 21.2.1' (#195) from feature/194-stripe-php-21 into main
CI / Coding Standards (push) Successful in 17s
CI / No Debug Code (push) Successful in 1s
CI / Tests (PHP 8.1) (push) Successful in 32s
CI / Tests (PHP 8.5) (push) Successful in 29s
CI / Tests (PHP 8.3) (push) Successful in 42s
CI / Static Analysis (push) Successful in 52s
CI / Tests (PHP 8.2) (push) Successful in 52s
CI / Build Plugin Zip (push) Successful in 23s
Release / Build and Publish Release (push) Successful in 20s
Release / Open next-version bump PR (push) Successful in 4s
Reviewed-on: #195
v1.5.5
2026-08-25 02:18:36 +00:00
thatguygriffandClaude Opus 5 88a8d0ae5c Date the Stripe changelog entry by version rather than by "current"
CI / Coding Standards (pull_request) Successful in 26s
CI / Tests (PHP 8.1) (pull_request) Successful in 28s
CI / No Debug Code (pull_request) Successful in 7s
CI / Tests (PHP 8.2) (pull_request) Successful in 36s
CI / Tests (PHP 8.5) (pull_request) Successful in 34s
CI / Static Analysis (pull_request) Successful in 37s
CI / Tests (PHP 8.3) (pull_request) Successful in 36s
CI / Build Plugin Zip (pull_request) Skipped
"the current Stripe library" is only true until Stripe ships the next major.
A changelog is read long after the release it describes, so name the
versions — 17 to 21, API version 2026-07-29.dahlia — and let those date the
entry.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01D9acV1mHktGAb1uyvNmrR2
2026-08-24 23:17:33 -03:00
thatguygriffandClaude Opus 5 e2da45a1b0 Update stripe-php from 17.6.0 to 21.2.1
CI / Coding Standards (pull_request) Successful in 27s
CI / No Debug Code (pull_request) Successful in 6s
CI / Tests (PHP 8.1) (pull_request) Successful in 35s
CI / Tests (PHP 8.2) (pull_request) Successful in 34s
CI / Tests (PHP 8.5) (pull_request) Successful in 24s
CI / Tests (PHP 8.3) (pull_request) Successful in 37s
CI / Static Analysis (pull_request) Successful in 45s
CI / Build Plugin Zip (pull_request) Skipped
Four majors behind on the plugin's only production dependency. No site is
using Stripe yet and the integration has not been verified end to end, so
the pinned API version can move now at close to zero cost — later it would
mean coordinating with live payment traffic.

The library surface this plugin uses is four symbols, all in StripeGateway:
StripeClient, paymentIntents->create, Webhook::constructEvent and Event.
Checked each against the installed v21 by reflection; all present with
compatible signatures. Most of the breakage flagged across 18 to 21 is in
the V2 Events API, which this does not use — webhooks go through the V1
Webhook::constructEvent path.

The pinned Stripe API version moves to 2026-07-29.dahlia.

Verified beyond the suite, which mocks the client: a real HMAC-signed
payload round-trips through Webhook::constructEvent and returns a
Stripe\Event with the expected type and data, and a forged signature still
raises SignatureVerificationException. The 21.0.0 ErrorObject retyping to
null|string produces no new PHPStan findings.

Part of #194

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01D9acV1mHktGAb1uyvNmrR2
2026-08-24 23:15:01 -03:00
thatguygriff dfa29745df Merge pull request 'Update the dev dependencies' (#193) from chore/update-dev-dependencies into main
CI / Coding Standards (push) Successful in 13s
CI / No Debug Code (push) Successful in 3s
CI / Tests (PHP 8.1) (push) Successful in 30s
CI / Tests (PHP 8.5) (push) Successful in 30s
CI / Tests (PHP 8.2) (push) Successful in 46s
CI / Tests (PHP 8.3) (push) Successful in 45s
CI / Static Analysis (push) Successful in 54s
CI / Build Plugin Zip (push) Successful in 9s
Reviewed-on: #193
2026-08-25 02:10:14 +00:00
thatguygriffandClaude Opus 5 54a8b906f7 Update the dev dependencies
CI / Coding Standards (pull_request) Successful in 20s
CI / No Debug Code (pull_request) Successful in 5s
CI / Static Analysis (pull_request) Successful in 31s
CI / Tests (PHP 8.2) (pull_request) Successful in 31s
CI / Tests (PHP 8.1) (pull_request) Successful in 32s
CI / Tests (PHP 8.3) (pull_request) Successful in 32s
CI / Tests (PHP 8.5) (pull_request) Successful in 35s
CI / Build Plugin Zip (pull_request) Skipped
Routine catch-up on the test and analysis tooling. Every package here is
already allowed by the existing constraints, so composer.json does not move
and no production dependency is touched.

  mockery/mockery              1.6.12  -> 1.6.15
  phpstan/phpstan               2.2.2  -> 2.2.9
  phpunit/phpunit             10.5.63  -> 10.5.64
  hamcrest/hamcrest-php        v2.1.1  -> v3.0.0   (transitive, via mockery)
  myclabs/deep-copy            1.13.4  -> 1.14.0
  nikic/php-parser             v5.7.0  -> v5.8.0
  sebastian/recursion-context   5.0.1  -> 5.0.2

Seven patch releases of PHPStan is the one worth watching, since a static
analyser routinely gains inferences between them. It reports no new errors
here, and PHPCS and the suite are unchanged.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01D9acV1mHktGAb1uyvNmrR2
2026-08-24 23:08:54 -03:00
thatguygriff b3ed3a67d5 Merge pull request 'Pin the Composer platform to PHP 8.1 and clear the PHPCS security advisories' (#192) from fix/187-composer-platform-pin into main
CI / Coding Standards (push) Successful in 17s
CI / No Debug Code (push) Successful in 2s
CI / Tests (PHP 8.2) (push) Successful in 32s
CI / Tests (PHP 8.1) (push) Successful in 48s
CI / Static Analysis (push) Successful in 38s
CI / Tests (PHP 8.3) (push) Successful in 47s
CI / Tests (PHP 8.5) (push) Successful in 47s
CI / Build Plugin Zip (push) Successful in 21s
Reviewed-on: #192
2026-08-25 02:08:07 +00:00
thatguygriffandClaude Opus 5 908b7fcd1f Update the coding-standards tooling off three security advisories
CI / Coding Standards (pull_request) Successful in 16s
CI / No Debug Code (pull_request) Successful in 4s
CI / Tests (PHP 8.3) (pull_request) Successful in 21s
CI / Tests (PHP 8.5) (pull_request) Successful in 22s
CI / Tests (PHP 8.2) (pull_request) Successful in 48s
CI / Tests (PHP 8.1) (pull_request) Successful in 49s
CI / Static Analysis (pull_request) Successful in 57s
CI / Build Plugin Zip (pull_request) Skipped
composer audit reported three advisories against the PHPCS stack, two rated
high:

  squizlabs/php_codesniffer  3.13.5 -> 3.13.6  CVE-2026-67434, OS command injection
  wp-coding-standards/wpcs   3.3.0  -> 3.4.1   CVE-2026-45293, arbitrary code execution
  phpcsstandards/phpcsutils  1.2.2  -> 1.2.3   CVE-2026-65954, arbitrary code execution

All three are dev-only and none ship in the plugin — bin/build-zip.sh installs
--no-dev — but they execute against repository content on every CI run.

The existing ^3.7 and ^3.0 constraints already allowed the fixed releases, so
composer.json is unchanged. phpcsextra and the codesniffer installer come
along as transitive dependencies. Five packages move, none are added or
removed, and the updated standards report no new violations.

composer audit is now clean.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01D9acV1mHktGAb1uyvNmrR2
2026-08-24 22:50:34 -03:00
thatguygriffandClaude Opus 5 b17adf02ff Pin the Composer platform to PHP 8.1
CI / Tests (PHP 8.2) (pull_request) Successful in 26s
CI / No Debug Code (pull_request) Successful in 3s
CI / Tests (PHP 8.3) (pull_request) Successful in 29s
CI / Tests (PHP 8.5) (pull_request) Successful in 30s
CI / Coding Standards (pull_request) Successful in 36s
CI / Tests (PHP 8.1) (pull_request) Successful in 46s
CI / Static Analysis (pull_request) Successful in 49s
CI / Build Plugin Zip (pull_request) Skipped
composer.json declares "php": ">=8.1", but resolution ran against whatever
PHP the machine doing the update happened to have. Running composer update
on 8.5 could therefore produce a lock containing a package that requires 8.2
or newer, which installs fine locally and fails on the 8.1 leg of the test
matrix — a lock that is only discovered to be wrong in CI.

config.platform.php makes resolution always target the declared minimum, so
a lock that resolves at all is a lock that installs everywhere the plugin
claims to run.

No package versions change: nothing currently locked requires a PHP newer
than 8.1, so this only records platform-overrides and the new content hash.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01D9acV1mHktGAb1uyvNmrR2
2026-08-24 22:46:11 -03:00
thatguygriff a23490ec80 Merge pull request 'CI: run jobs in shared ci-php images, commit composer.lock' (#191) from feature/187-run-jobs-in-ci-images into main
CI / Coding Standards (push) Successful in 16s
CI / No Debug Code (push) Successful in 5s
CI / Tests (PHP 8.3) (push) Successful in 25s
CI / Tests (PHP 8.5) (push) Successful in 31s
CI / Static Analysis (push) Successful in 37s
CI / Tests (PHP 8.2) (push) Successful in 35s
CI / Tests (PHP 8.1) (push) Successful in 39s
CI / Build Plugin Zip (push) Successful in 9s
Reviewed-on: #191
2026-08-25 01:41:19 +00:00
thatguygriffandClaude Opus 5 1552bf4b5f Move the CI image definitions to the shared ci-php repository
CI / Coding Standards (pull_request) Successful in 14s
CI / Tests (PHP 8.1) (pull_request) Successful in 30s
CI / Tests (PHP 8.2) (pull_request) Successful in 30s
CI / Tests (PHP 8.3) (pull_request) Successful in 29s
CI / No Debug Code (pull_request) Successful in 3s
CI / Tests (PHP 8.5) (pull_request) Successful in 21s
CI / Static Analysis (pull_request) Successful in 37s
CI / Build Plugin Zip (pull_request) Skipped
The image definition and its publishing workflow were only here because this
was the first project to need them. They are project-agnostic, so they now
live in Unsupervised/ci-php and any PHP project can consume the same tags.
The registry path is unchanged — Gitea container images are named
owner/image, not after the repository that pushes them — so nothing here
needs repointing.

Split the combined quality job into separate phpcs and phpstan jobs. They
were folded together to halve the number of toolchain installs per run; with
the toolchain in the image that saves nothing, and separating them means a
coding-standards failure no longer hides the static analysis result.

Rewrite the workflow comments to describe what the jobs do rather than the
setup step they replaced, and cut docs/ci.md down to what is specific to this
project — the image contents and publishing are documented in ci-php.

Closes #187

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01D9acV1mHktGAb1uyvNmrR2
2026-08-24 22:39:08 -03:00
thatguygriffandClaude Opus 5 f8762e1095 Correct the registry auth docs: the Actions token does not work
CI Images / Build CI image (PHP 8.2) (pull_request) Successful in 3s
CI Images / Build CI image (PHP 8.5) (pull_request) Successful in 8s
CI / Tests (PHP 8.3) (pull_request) Successful in 30s
CI Images / Build CI image (PHP 8.3) (pull_request) Successful in 4s
CI / Tests (PHP 8.2) (pull_request) Successful in 23s
CI / Tests (PHP 8.1) (pull_request) Successful in 28s
CI / No Debug Code (pull_request) Successful in 4s
CI / Coding Standards & Static Analysis (pull_request) Successful in 43s
CI / Tests (PHP 8.5) (pull_request) Successful in 23s
CI / Build Plugin Zip (pull_request) Skipped
CI Images / Build CI image (PHP 8.1) (pull_request) Successful in 1m3s
docs/ci.md and the workflow comment both described secrets.GITHUB_TOKEN as
the working credential with REGISTRY_TOKEN as a fallback. That is backwards:
the task token is rejected by Gitea's container registry (go-gitea/gitea#23642)
and the first publish attempt failed on exactly that. REGISTRY_TOKEN is
required.

Part of #187

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01D9acV1mHktGAb1uyvNmrR2
2026-08-24 22:35:10 -03:00
thatguygriffandClaude Opus 5 c9d18fec74 Commit composer.lock so CI and releases install a fixed dependency set
CI / Tests (PHP 8.2) (pull_request) Successful in 25s
CI / Tests (PHP 8.1) (pull_request) Successful in 26s
CI / No Debug Code (pull_request) Successful in 3s
CI / Tests (PHP 8.3) (pull_request) Successful in 32s
CI / Coding Standards & Static Analysis (pull_request) Successful in 42s
CI / Tests (PHP 8.5) (pull_request) Successful in 32s
CI / Build Plugin Zip (pull_request) Skipped
composer.lock was gitignored, so `composer install` resolved the graph afresh
on every job. CI could quietly start testing different dependency versions
than it did the day before, and two builds of the same tag could ship
different vendor trees — the run that produces the zip is the same run that
picks the versions.

Track the lock, and make the two places that consume it use it:

- the Composer cache keys hash composer.lock rather than composer.json, since
  the lock is now what actually fingerprints the dependency set;
- bin/build-zip.sh stages the lock alongside composer.json, so the --no-dev
  production install resolves to the locked versions. It already deleted both
  from the staging directory before zipping, so the shipped plugin is
  unchanged.

The current lock installs cleanly on 8.1: nothing in it requires a PHP newer
than the >=8.1 composer.json declares.

Closes #187

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01D9acV1mHktGAb1uyvNmrR2
2026-08-24 22:33:04 -03:00
thatguygriffandClaude Opus 5 ab609898d6 Run CI and release jobs inside the prebuilt CI images
CI / Coding Standards & Static Analysis (pull_request) Successful in 37s
CI / Tests (PHP 8.1) (pull_request) Successful in 21s
CI / Tests (PHP 8.2) (pull_request) Successful in 20s
CI / Tests (PHP 8.3) (pull_request) Successful in 19s
CI / Tests (PHP 8.5) (pull_request) Successful in 19s
CI / No Debug Code (pull_request) Successful in 2s
CI / Build Plugin Zip (pull_request) Skipped
Replaces setup-php with `container:` in every job that needs PHP. The images
published by ci-images.yml already carry PHP, Composer, the intl and zip
extensions and the CLI tools the workflow scripts shell out to, so nothing
installs PHP at job time any more.

That removes the apt path #178 was about: a ~145s floor on 8.3 against ~35s
on 8.1/8.2, with a tail that twice ran past the step timeout and failed the
run. All three copies of the `Keep downloaded .debs` / `Cache apt packages`
workaround go with it.

no-debug and bump-version stay on the runner image — neither needs PHP, and
no-debug wants the runner's GNU `grep --include`.

The Composer cache moves to /composer/cache, which is COMPOSER_HOME in the
image, and release.yml gains the cache step it never had.

Also corrects the image size in docs/ci.md to the 67MB measured from the
published manifest; the ~120MB in the original was an estimate.

Closes #187

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01D9acV1mHktGAb1uyvNmrR2
2026-08-24 22:25:02 -03:00
thatguygriff 4bc8e80837 Merge pull request 'CI: install GNU tar in the CI image so actions/cache works' (#190) from fix/187-ci-image-gnu-tar into main
CI Images / Build CI image (PHP 8.3) (push) Successful in 1m14s
CI Images / Build CI image (PHP 8.2) (push) Successful in 1m30s
CI Images / Build CI image (PHP 8.1) (push) Successful in 1m31s
CI Images / Build CI image (PHP 8.5) (push) Successful in 1m54s
CI / Tests (PHP 8.2) (push) Successful in 52s
CI / No Debug Code (push) Successful in 2s
CI / Tests (PHP 8.5) (push) Successful in 3m0s
CI / Tests (PHP 8.1) (push) Successful in 6m0s
CI / Tests (PHP 8.3) (push) Successful in 6m1s
CI / Coding Standards & Static Analysis (push) Failing after 17m19s
CI / Build Plugin Zip (push) Skipped
Reviewed-on: #190
2026-08-25 01:23:12 +00:00
thatguygriffandClaude Opus 5 28f586d207 Install GNU tar in the CI image so actions/cache works
CI Images / Build CI image (PHP 8.1) (pull_request) Successful in 48s
CI / Tests (PHP 8.1) (pull_request) Successful in 55s
CI Images / Build CI image (PHP 8.2) (pull_request) Successful in 1m46s
CI Images / Build CI image (PHP 8.3) (pull_request) Successful in 1m48s
CI Images / Build CI image (PHP 8.5) (pull_request) Successful in 2m26s
CI / No Debug Code (pull_request) Successful in 3s
CI / Tests (PHP 8.2) (pull_request) Successful in 53s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m50s
CI / Tests (PHP 8.5) (pull_request) Successful in 2m58s
CI / Coding Standards & Static Analysis (pull_request) Failing after 17m18s
CI / Build Plugin Zip (pull_request) Skipped
actions/cache shells out to `tar --posix -P`. Alpine's busybox tar rejects
both flags, so the cache step would fail in every job that runs inside these
images — which is all of them once ci.yml switches over.

coreutils does not cover this: tar is its own Alpine package. Add it, add
zstd (which actions/cache prefers over gzip when present), and assert GNU
tar in the image's smoke test so a future base-image change cannot quietly
drop it again.

Part of #187

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01D9acV1mHktGAb1uyvNmrR2
2026-08-24 22:15:40 -03:00
thatguygriff e097b7a0bf Merge pull request 'CI: publish prebuilt CI images to the Gitea container registry' (#188) from feature/187-ci-container-images into main
CI Images / Build CI image (PHP 8.1) (push) Failing after 5s
CI Images / Build CI image (PHP 8.2) (push) Failing after 7s
CI Images / Build CI image (PHP 8.3) (push) Failing after 6s
CI Images / Build CI image (PHP 8.5) (push) Failing after 17s
CI / No Debug Code (push) Successful in 9s
CI / Tests (PHP 8.2) (push) Successful in 52s
CI / Tests (PHP 8.3) (push) Successful in 2m47s
CI / Coding Standards & Static Analysis (push) Successful in 3m41s
CI / Tests (PHP 8.1) (push) Successful in 5m59s
CI / Tests (PHP 8.5) (push) Successful in 7m5s
CI / Build Plugin Zip (push) Successful in 6m0s
Reviewed-on: #188
2026-08-25 01:05:33 +00:00
thatguygriffandClaude Opus 5 572aaf5b49 Publish prebuilt CI images to the Gitea container registry
CI Images / Build CI image (PHP 8.2) (pull_request) Successful in 1m14s
CI Images / Build CI image (PHP 8.1) (pull_request) Successful in 1m26s
CI Images / Build CI image (PHP 8.3) (pull_request) Successful in 2m3s
CI Images / Build CI image (PHP 8.5) (pull_request) Successful in 2m9s
CI / Tests (PHP 8.2) (pull_request) Successful in 1m7s
CI / No Debug Code (pull_request) Successful in 2s
CI / Tests (PHP 8.3) (pull_request) Successful in 3m8s
CI / Tests (PHP 8.1) (pull_request) Successful in 5m55s
CI / Tests (PHP 8.5) (pull_request) Successful in 6m21s
CI / Coding Standards & Static Analysis (pull_request) Successful in 18m26s
CI / Build Plugin Zip (pull_request) Skipped
setup-php installs PHP 8.3+ from apt on these arm64 runners: a ~145s floor
against ~35s for 8.1/8.2, with a tail that twice ran past the step timeout
and failed the run (#178). Caching the .debs softened it without removing
the apt step, and 8.5 has the same problem.

Add a per-version CI image built on php:<version>-cli-alpine and a workflow
that publishes it to git.unsupervised.ca/unsupervised/ci-php:<version>. The
org is public, so the packages pull anonymously.

The image carries bash and nodejs because act_runner runs JavaScript actions
inside the job container, GNU coreutils/grep/sed because the workflow scripts
use `tac` and `grep --include`, and curl/jq/git/zip for release.yml and
bin/build-zip.sh. Composer 2 and the intl and zip extensions round it out.

Nothing consumes the images yet — ci.yml and release.yml switch over in a
follow-up, because a job cannot run in an image that has not been published.

Part of #187

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01D9acV1mHktGAb1uyvNmrR2
2026-08-24 21:55:28 -03:00
thatguygriff 116394f2ff Merge pull request 'Bump version to 1.5.5' (#189) from release/bump-1.5.5 into main
CI / No Debug Code (push) Successful in 3s
CI / Tests (PHP 8.1) (push) Successful in 53s
CI / Tests (PHP 8.5) (push) Successful in 3m28s
CI / Tests (PHP 8.3) (push) Successful in 5m46s
CI / Tests (PHP 8.2) (push) Successful in 6m14s
CI / Coding Standards & Static Analysis (push) Failing after 12m16s
CI / Build Plugin Zip (push) Skipped
Reviewed-on: #189
2026-08-25 00:50:11 +00:00
Release Bot dda8386c1f Bump version to 1.5.5 and open changelog section 2026-08-24 23:50:02 +00:00
thatguygriff 7278309bf5 Merge pull request 'Let the studio register the students who cannot register themselves' (#186) from fix/185-book-for-child-and-vet-group-enrolment into main
CI / Coding Standards & Static Analysis (push) Successful in 13m23s
CI / Tests (PHP 8.1) (push) Successful in 55s
CI / Tests (PHP 8.2) (push) Successful in 1m7s
CI / Tests (PHP 8.3) (push) Successful in 6m59s
CI / Tests (PHP 8.5) (push) Successful in 12m55s
CI / No Debug Code (push) Successful in 3s
CI / Build Plugin Zip (push) Successful in 5m53s
Release / Build and Publish Release (push) Successful in 8m5s
Release / Open next-version bump PR (push) Successful in 5s
Reviewed-on: #186
v1.5.4
2026-08-24 22:03:11 +00:00
thatguygriffandClaude Opus 5 5ce42f0003 Let the studio register the students who cannot register themselves
CI / No Debug Code (pull_request) Successful in 4s
CI / Tests (PHP 8.2) (pull_request) Successful in 50s
CI / Tests (PHP 8.1) (pull_request) Successful in 1m3s
CI / Tests (PHP 8.5) (pull_request) Successful in 2m48s
CI / Tests (PHP 8.3) (pull_request) Successful in 3m24s
CI / Coding Standards & Static Analysis (pull_request) Successful in 8m21s
CI / Build Plugin Zip (pull_request) Skipped
The Book a lesson for a student panel built its picker from the us_student
role but vetted the submission with the book_lesson capability. ChildLoginGate
and RegistrationLoginGate withhold that capability from accounts that keep the
role, so the panel offered every guardian-managed child and every unapproved
signup and then refused them — with a message claiming no student had been
chosen, and a form cleared of all five fields.

Withholding book_lesson stops those accounts registering in their own name. It
was never meant to stop the studio acting for them, which is what the panel is
for, and for a child is the only route to a lesson besides their guardian.

Guard the student role instead, via a new RoleManager::isStudent() shared with
every picker and guard on the staff side so the two cannot drift apart again.
Group enrolment gets the same predicate: addDirect() and grantAccess() vetted
their posted ids not at all, and would enrol an instructor, an administrator,
or an account deleted since the page was drawn — raising a real payment against
them for a priced class.

Keep a refused booking's fields as submitted, reading the form through one
LessonController::submittedBooking() so what gets booked and what is shown
again cannot disagree about a field name. A booking that succeeds still leaves
an empty form, so the next one does not inherit it.

Closes #185

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01XunBYk2sFEc1oL14sUiuBU
2026-08-24 18:42:59 -03:00
thatguygriff 76530878b5 Merge pull request 'Bump version to 1.5.4' (#184) from release/bump-1.5.4 into main
CI / Tests (PHP 8.1) (push) Successful in 58s
CI / No Debug Code (push) Successful in 2s
CI / Tests (PHP 8.5) (push) Successful in 4m20s
CI / Coding Standards & Static Analysis (push) Successful in 5m21s
CI / Tests (PHP 8.2) (push) Successful in 6m4s
CI / Tests (PHP 8.3) (push) Successful in 12m56s
CI / Build Plugin Zip (push) Successful in 2m39s
Reviewed-on: #184
2026-08-24 17:59:40 +00:00
Release Bot 6031a75012 Bump version to 1.5.4 and open changelog section 2026-08-24 17:56:38 +00:00
thatguygriff 41843e5253 Merge pull request 'Let the studio book lessons and record intake collected elsewhere' (#183) from feature/182-admin-booking-and-intake-recording into main
Release / Build and Publish Release (push) Successful in 4m43s
Release / Open next-version bump PR (push) Successful in 3s
CI / Tests (PHP 8.1) (push) Successful in 51s
CI / Tests (PHP 8.2) (push) Successful in 56s
CI / Tests (PHP 8.3) (push) Successful in 4m41s
CI / Tests (PHP 8.5) (push) Successful in 3m6s
CI / No Debug Code (push) Successful in 4s
CI / Coding Standards & Static Analysis (push) Successful in 8m23s
CI / Build Plugin Zip (push) Successful in 2m39s
Reviewed-on: #183
v1.5.3
2026-08-24 17:50:46 +00:00
thatguygriffandClaude Opus 5 8c21a3fa9d Let the studio book lessons and record intake collected elsewhere
CI / Tests (PHP 8.1) (pull_request) Successful in 6m39s
CI / Tests (PHP 8.2) (pull_request) Successful in 57s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m59s
CI / Tests (PHP 8.5) (pull_request) Successful in 3m31s
CI / No Debug Code (pull_request) Successful in 3s
CI / Coding Standards & Static Analysis (pull_request) Successful in 3m28s
CI / Build Plugin Zip (pull_request) Skipped
Two related gaps, closed together because the second is created by the first.

A private lesson could only be booked by the student or their guardian, so a
booking taken over the phone had no way in — where group classes have had "Add
students directly" all along. "Book a lesson for a student" is now a panel on
Scheduler and My Lessons: student, open time, lesson type, with weekly term
reservations and a no-charge option for make-up lessons. The booking core is
extracted to Booking\LessonBooker and shared with POST /bookings, so the two
paths cannot drift on offering rules, slot claiming, or billing.

That leaves a registration with no intake answers and no policy acceptances,
because nobody was at a keyboard to give them — already true of every directly
added group-class student. Ticking the boxes on a student's behalf would be an
audit trail that says something untrue, so instead the answers are collected
another way and recorded afterwards, from a lesson's or an enrolment's detail
page. Every recording must say how it was collected, which is stamped on each
row along with who typed it and shown in a new "How it was given" column: a
policy ticked online and one transcribed from paper must never look alike.

Only staff-made registrations qualify (us_lessons.booked_by,
us_group_enrollments.enrolled_by) — one the student made already holds their
own answers. Only what is still missing can be recorded, re-checked at write
time, so a stale or double-posted form cannot duplicate or overwrite. No IP is
stored for a transcription, and accepted_by stays the student while recorded_by
names the staff member.

Intake is now generic over Registration\IntakeSubject, which Lesson and
Enrollment both implement; LessonDetail became Registration\IntakeAudit and is
shared by both detail views rather than duplicated.

Closes #182

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01QfHt6CyJHz6KkA4RuaS7WK
2026-08-24 14:06:16 -03:00
thatguygriff 8a34ec41e9 Merge pull request 'Cache the packages php-builder installs, and restore PHP 8.5' (#181) from ci/cache-php-builder-deps into main
CI / No Debug Code (push) Successful in 3s
CI / Tests (PHP 8.2) (push) Successful in 2m27s
CI / Tests (PHP 8.1) (push) Successful in 2m33s
CI / Tests (PHP 8.3) (push) Successful in 2m55s
CI / Coding Standards & Static Analysis (push) Successful in 3m26s
CI / Tests (PHP 8.5) (push) Successful in 7m59s
CI / Build Plugin Zip (push) Successful in 6m59s
Reviewed-on: #181
2026-08-20 19:18:46 +00:00