10 changes:
- The suite's speed is back: 394 s of tests down to 303
- The suite: 5:24 down to 3:30 sequentially, and 1:25 in parallel
- The OIDC
phone_number claim falls back to the phone when there is no mobile
- Projects get the suite's process isolation, and a scaffolded MySQL no longer logs every statement
- A fatal error says what was running:
fatal.log
Email's debug log no longer writes the start of the SMTP password
- A logged stack trace no longer carries its calls' arguments
- Mail from a customer's own domain: optional DKIM signing and a per-message server
- A reply can go into the thread of the message it answers
- A queued message keeps everything it was queued with
13 changes:
- Deleting a role deletes its grants
Event::listen() does not register the same listener twice
- Erasing an account deletes its passkeys, devices, roles, memberships and notifications
createTestUser(): a test's users are erased when it ends
- What a test class leaves behind is removed and named, and an overgrown test database is rebuilt
- An OAuth2 access token survives a second of clock skew, and five intermittent tests stop failing
- The framework's suite: 380 s of tests down to 303
- Three more slow test classes: 303 s down to 288
- symfony/console 6.4
- The framework's suite runs in parallel:
./dockertest --parallel
- Flaky tests: the clock and one-second margins
- The suite passes on a database built from nothing
- Tests that passed or failed by the order they ran in
6 changes:
- Change-log events can belong to a tenant, and be read back by it
Permissions::allow() and deny() with a list of privileges grant to the right subject
- A failed migration no longer marks the schema as up to date
- The scaffolded SPA's navigation fits a phone
Permissions::allowMany(): an organisation's roles in one write
- The PostgreSQL test database commits without waiting for the disk
7 changes:
- Permission checks can be made in one organisation
- Every push notification has its own page in the administration
- A test notification says whether the device received it
- The scaffolded Omnibox test no longer prints a warning on every run
- An API test no longer makes the next test's requests go to the API
- Mailing-list and unsubscribe pages render in the theme, and a confirmation can be counted
StatTile: the figure-with-a-label every dashboard draws
9 changes:
- Two workers refreshing one OAuth connection no longer kill it
- A faked request says its method, URL, headers and body
QueueManager::addMany() queues a pass as one batch
- A pass can tell when its batch is done
- A failed queue task waits before it is retried
- A queue worker pool that grows and shrinks with its backlog
- An operator's stop is kept, and pools can be set from the database
- The services screen shows pools, batches and the scaling decision, and its Stop stays stopped
- A model's column cache tells two databases with the same table apart
13 changes:
- User groups, as an optional feature
- Roles held by a user group
- A permission check about a group was answered about a user
- The SQL and column caches tell apart databases with the same name
- Cookie consent switched off allows everything in the browser too
- A SPA that declined the cookie banner never released its
data-consent scripts
- The scaffolded daemons container runs as www-data
api:docs declares the responses every operation shares
userlog is deprecated, and new installations do not get it
- An application can exclude individual framework migrations
- Two framework migrations said they were the application's
- Destroying a cloned
Database no longer disconnects the original
Client::multipart() sends fields and files in one request
12 changes:
- "Don't ask again on this device" — trusted devices
- "Is it you trying to sign in?" — approving a sign-in from a trusted phone
- Registration, trusted devices and the phone prompt in System Settings
push:setup never found the subscriptions table
- The account screens on a phone
- A scaffold view's partials were lost beside an application's own view directory
- EU cookie consent — banner, preferences dialog and Consent Mode v2
- Four findings from glideday: a way back from the sign-in screens,
User::current(), a fresh caller per TestClient request, and resync --js --all
- The cookie banner in production: a centred dialog, the Consent Mode default before anything, and versioned asset URLs
- The suite passes in any order and under any
--filter
User::setupDb() builds the user tables from their migrations
- The
users migration gives four columns a default of 0
- A blank organisation setting named a table
authserver. and a column ""
54 changes:
- Claude.ai and ChatGPT can connect to the MCP endpoint
- The authorization endpoint's codes could not be redeemed, and tokens name the resource they are for
- A client that cannot keep a secret needs a registered redirect URI
- A token from
/oauth/token never authenticated an API call
- Invitations: the four gaps glideday found moving onto them
/oauth/userinfo refused the token /oauth/token had just issued
- The deploy webhook answered twice when it answered first
- A new continuous aggregate froze its open bucket
- A rollup can be compressed
Http\Client can download straight to a file
- Opt-in mailing lists: a newsletter, with or without an account
- The mailing-list form and its confirmation page
- Opt-in lists on the account screen and the preferences page
- Sending to a mailing list from
/admin/MassMessages
- A deactivated role went on granting its permissions
- Saving an organisation deactivated it
- Admin actions could be triggered from another site —
addWriteAction(), starting with Users and Privacy
- Every other admin screen's write actions are declared too
- A deny and an allow at the same priority decided differently on MySQL and PostgreSQL
permissions_changed for every change, and a Permissions form with every column
- Every framework admin screen opens at usertype 98; roles open the rest
/api/internal/permissions answers for one organisation
/admin/MailingLists: who is on each opt-in list
- The compose form follows the chosen mailing list
- Who opened and clicked one mass message
- An account's email choices on its administration page
- A WYSIWYG editor for messages, chosen per application
- A server without its signing key minted tokens anybody could forge
- The message editor's headings and lists look like headings and lists; push is written plain
- An application can be marked trusted without SQL
- A user's page lists the roles they hold
- A client's Allowed Scopes are enforced
- OpenID Connect: an ID token for
openid, and a discovery document that says what works
- Token lifetimes are set per server and per client
permissions_changed reaches only the applications it concerns
- A migration's new column is saved at once, on PostgreSQL too
- An organisation's own administrator
- An array setting is not sent to the settings table
- Redirects the framework issues can be seen by a test
- The suite runs clean: no failure, warning or deprecation, and it fails on the next one
- Two administrators can be given different administration areas
- Registration by invitation, within a domain list, and with a confirmed address
- A subset of the suite runs as clean as the whole of it
- A cached query answered for another database behind the same Redis
- A mass message filtered by organization reached nobody
- The suite passes on Linux, not only on a case-insensitive disk
RateLimitMiddleware can count an API key, a user, or a tier
- An application can send webhooks about its own events
- An API key is held to the scopes its application was granted
- Something writes
application_stats
- Applications can be managed from an application's own screen
- The MySQL test container keeps its tables in one tablespace
TestClient sends a PUT's data the way PHP receives it
- The scaffolded API client says whose contract it speaks
5 changes:
- Two of the three paths that run a migration left the column cache stale
- …and the cache they flush was keyed by table only, so two databases answered for each other
BotDetector can be widened to about 1,500 crawlers, for anybody counting visits
- The migration ledger records the version it reached, so an application can report it
- The auto-run fingerprint could not see a migration that was not timestamped
3 changes:
- The mail-template editor had no way in for an application's own categories
getData() on an ORM model published the ORM's bookkeeping, including the write allowlist
Http\Client handed back a compressed body it had not asked for and never decoded
4 changes:
- A migration that queued its statements reported Ran and changed nothing
- Changing a column was documented on the page nobody writing a migration reads
- System emails can be rewritten from the mail templates screen, which sent nothing until now
- …and the framework's own categories are in that screen now, instead of having to be guessed
4 changes:
- A second cache of the request meant a declared-public endpoint answered 403 in tests
- With no mail configured, nothing asks for a mailed code any more
- The authenticator app names the site, not Pramnos
- Queued mail is sent: mail:flush is on the framework schedule
10 changes:
- A working single server was being shown a red badge for a correct choice
Http\Client could not refuse a redirect, so it could not fetch a URL a user chose
api:docs could not see routes registered on the router, and wrote nothing over something
- A relying party could point the server's webhook deliveries at its own network
- Webhook endpoints on the administration screen, and the private network allowed by default
- Carrier-grade NAT counted as a public address, so
allow_private => false admitted the VPN
- The scaffold put the API documentation in
.gitignore, so production answered 403
ApiAuthMiddleware owned the whole auth surface, with no way in for an application
- The SPA bundle was ignored, and no deploy builds it — so production had no front end
- A CSRF test passed for the wrong reason, because a declared POST did not survive
17 changes:
Pramnos\User\User was not notifiable, and the guide said it was
- Scheduled tasks can run on one server instead of on each of them
- Sessions can be put in a shared store without touching
php.ini
- …and
health:check says when they are not
- The session store setting was never read
Pramnos\Storage was unreachable, and that is why nobody used it
- The media system can publish to a storage disk
- A generated detail view renders a picture, and a guard keeps it asking for the address
- A guide for taking a monolith to more than one server
- Three of the four things that guide said were not solved, are
- The scaffolded test runner reported success on a red suite
- A scheduled command that failed recorded its status and not its reason
- The deploy webhook did its work before it answered, so a slow push was dropped
- A middleware could not decide what a request is, because
init() already had
session_tracking => false was read by one of the two places that run the tracker
- A password toggle rendered inside an attribute broke three screens, and a guard found two
- A check that said it must not page for a correct installation paged for one
6 changes:
- The screen sweep's seeder invented values for generated and foreign keys
- A capped fetch threw away everything it had already read
- The scaffolded SPA stylesheet let Tailwind scan the whole repository
new MediaObject($id) looked like it loaded and silently did not
- The media system has an answer for transparency now
- …and a row for the new methods, which is the half of BC that was missing