17 October 2026¶
Three entries today:
- The authorization server and the public MCP endpoint, from the first installation to switch them on
- Migrations that converge with an older schema: order, names, a key rebuilt every run, duplicate tokens
schema:diff: an installation's tables against the framework's shape, and--apply
The authorization server and the public MCP endpoint, from the first installation to switch them on¶
FW-153.
Fixed¶
- The MCP endpoint accepted the API's own sign-in JWT. The API's middleware identifies a
caller from any token it signed, the sign-in token (
tokentypeauth) included, and the endpoint took that as a credential. A bearer now has to be an access token, issued by the authorization server ormcp:token; anything else gets the discovery401. A call with no bearer (a same-origin session) is unaffected. mcp:tokensigned with the console application's key and printedsURL/mcp. The endpoint is an API route, verified with the API's key under/<api>/<version>/mcp, so the token answered401.- It now signs with the API's key and prints that address.
--urlis the API's base. - It adds a
jti, so two tokens minted in one second no longer collide. - It refuses, saying to widen the column, when
usertokens.tokenis too short for the encrypted token. An oldervarchar(255)cut it without a word.
- It now signs with the API's key and prints that address.
- The authorization server no longer makes its signing keys mid-request. A pair made that way
belonged to the web server's user, was never backed up, and a new container made a new one.
- Missing keys now fail with the command to run, in the
oauthlog and in thesigning_keyshealth check. - A key the process cannot read says whose it is.
oauth:keysalso makesencryption.keyonce, takes--owner=<user>, and warns when run as root without it.
- Missing keys now fail with the command to run, in the
- Discovery named the authorization server two ways.
issuerwashttps://host/andauthorization_serverswas["https://host"]. A strict client compares them and refuses the pair. Both now come fromDiscovery::issuer(), with the trailing slash that relying parties already compareisswith. ApplicationService::create()andupdate()failed on an olderapplicationstable.- They wrote
broadcast_secret, which that table lacks. - They wrote NULL into
NOT NULLcolumns with no default. - They now write only the columns that exist, with
''or0where NULL is refused.
Changed¶
/.well-known/healthanswersstatusand 200/503 publicly. Thecomponentsmap, which names what is failing, appears only with'health' => ['public_details' => true].- The consent screen is translated. Its words, the scope categories and every scope
description go through
t(), the MCP scopes' included.
Added¶
SchemaBuilder::columnLength($table, $column): the most characters a column holds, 0 for none.ApplicationService::callbackCeiling()uses it.
Documentation¶
- The MCP, AuthServer Integration, Health, Console and Schema Builder guides describe the above.
Migrations that converge with an older schema: order, names, a key rebuilt every run, duplicate tokens¶
FW-154, the bugs. A fresh installation and one that had tables before the framework now end with the same indexes and keys.
Fixed¶
add_missing_indexes_to_existing_tablesran before the tables it indexes. At the default priority it ran before theauthfeature madetokenactionsandusertokens, did nothing and was recorded as run. So a fresh installation lacked the tokenactions indexes that one migrated table by table had.- It now runs at priority 200.
add_missing_indexes_after_their_tablesruns it again for installations it already passed over.FrameworkMigrationOrderTestholds the order for every migration that adds to other migrations' tables.
- Indexes and foreign keys were looked for by name only. On a table that held them as
tokenidorusertokens_ibfk_1, a second one was added.add_missing_indexes_to_existing_tablesandadd_missing_foreign_keys_to_existing_tablesnow look by columns as well. cascade_usertokens_applicationrebuiltusertokens.- It dropped and re-created the key on every run, and on MySQL that copies the whole table.
- It also added the key where the key-adding migration had skipped it for orphans or a type mismatch, and then failed for the same reason.
- It now changes only a key that exists and does not cascade yet, in place.
add_token_lookup_to_usertokensfailed on a token stored twice. It now adds a plain index where the digests repeat, and logs how many there are and how to resolve them.- Its encrypting backfill read
usertokenswithout the prefix. - Whether it encrypts is now the
encrypt_tokenssetting (below), not the mere presence of anAPP_KEY.
- Its encrypting backfill read
Added¶
'auth' => ['encrypt_tokens' => false]keeps token values unencrypted even with anAPP_KEY, for an application with lookups of its own onusertokens.token.Token::encryptsAtRest()says which way it is.SchemaBuilder::indexColumns(),hasIndexOn()andhasForeignKeyOn()find an index or a key by what it covers.
Documentation¶
- The Security Guide gives the order for moving an application's own token lookups, with the setting. The Migration Guide explains when a migration has to run late. The Schema Builder Guide covers the new methods.
schema:diff: an installation's tables against the framework's shape, and --apply¶
FW-154, the comparison and the convergence.
Added¶
schema:diffcompares the database with the shape the framework's migrations would make.- The reference is built, not stored: the migrations run into an empty database on the same
server with the same prefix, so the engines match.
--referencenames one for a user that may not create databases. - Columns are compared by type (without integer display widths), nullability, default and charset.
- Indexes are compared by the columns they cover and whether they are unique.
- Foreign keys are compared by column, target and delete rule.
- Every difference has a key, and globs over the keys in
schema.allowmark the intentional ones. It exits 1 while differences remain.
- The reference is built, not stored: the migrations run into an empty database on the same
server with the same prefix, so the engines match.
schema:diff --applyadds the missing columns and indexes, each exactly as the reference declares it. It changes and removes nothing.- This is the convergence: the CREATE migrations that return when their table exists stay as they are.
- A latin1 or utf8mb3 column where the framework's is utf8mb4 is a
charsetdifference.
Database\SchemaDiff:describe(),compare()andapply(), for a test of an application's own.
Documentation¶
- The Migration Guide has a section on
schema:diff. The Console Guide lists it.