Stay inactive until update.php has created the tables - #14
Merged
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #14 +/- ##
============================================
+ Coverage 86.27% 86.62% +0.35%
- Complexity 523 551 +28
============================================
Files 61 62 +1
Lines 1807 1907 +100
============================================
+ Hits 1559 1652 +93
- Misses 248 255 +7 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
An extension can be loaded on a wiki before update.php has run there — a farm switching it on across many wikis at once does exactly that. Every entry point that reads a table now asks whether the tables are there, and behaves as if the extension were not loaded when they are not, rather than failing with a database error: * the code login route offers no button and abstains from a request submitted anyway, and no account is reported as a member, so a password change is not refused either; * single sign-on is left alone, neither refused nor provisioned: refusing every login would shut a working identity provider out of the wiki; * the last-login record, which runs for every login on the wiki, is skipped; * a password reset keeps the accounts it names; * the management API answers 503 with the stable error code `schema_missing`. The question costs at most one `tableExists` query per request: it is asked only where an entry point would touch a table, and answered from memory after the first time. A wiki without the tables is warned once on the `MemberAccess` log channel to run update.php. A connection that cannot answer counts as having the tables, so nothing here can turn a working wiki off. What loading the extension changes wiki-wide is untouched, being settings rather than tables. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Every request builds the entry points, and most go on to do nothing with the tables, so probing from the constructor would put a query on requests that never need one. Nothing held that in place. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The installation note read as though nothing could log in, while a single sign-on login is left exactly as the wiki has it, and named no channel to look on for the warning. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
JeroenDeDauw
force-pushed
the
feature/tolerate-missing-schema
branch
from
August 19, 2026 19:58
904fcb0 to
49d9cd4
Compare
JeroenDeDauw
marked this pull request as ready for review
August 19, 2026 21:14
…e away A wiki without the tables is not always a fresh one: dropping them and re-running update.php is this repository's own schema procedure, and a partial restore gets there too. On such a wiki the accounts the allowlist created are still there, and leaving every route alone took two rules off them that exist for exactly those accounts: a member never holds a password, and a removed member's parked account is never signed back into. The reader group is what is left to go on. It is core's data rather than the extension's, provisioning adds it before the roster row, a removal leaves it in place, and the user-list handler already reads it on its own. While the roster cannot be read it therefore stands in: an account carrying it is refused a password, dropped from a password reset, and refused a single sign-on login where the allowlist governs that route. It is the wider of the two answers, since the group can also be given by hand, and that is the way round to err while the roster cannot say which accounts are members. A wiki that runs update.php has the roster answering again on the next request. The rest keeps the leave-alone rule: staff carry no reader group and sign in as before, a login with no account yet has no groups either and is still admitted, the code route stays closed, and no account is reported as a member. The refusal says in the log what is known — the group and the missing tables — rather than that the roster forgot the account, which no one can tell here, and the warning the wiki already gets on this channel no longer claims the extension does nothing. `isMemberName` is now `isRefusedAPassword`, since without the tables it answers about the group rather than the roster, and it is the same rule the password reset filters on. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The probe reads whether a table exists, which `IConnectionProvider` does not promise: it hands back an `IReadableDatabase`, and only the object behind it happens to be maintainable. A future core that narrows the concrete class takes every guard in the extension off at once and quietly, since a connection that cannot be asked deliberately counts as having the tables — the wiki keeps working, and the day it has no tables it fails with database errors instead. The test cannot be seen failing against this code, since what it pins is the environment rather than a branch: it asserts of the real wiki connection what the stub in the test beside it is built to lack. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This provider offers a request to the login form and to nothing else, yet it asked whether the tables were there before looking at which action it was being asked about. An account creation render therefore paid the one query the whole feature is meant to cost, on a form that touches no table of ours. A password change or credential removal render reaches the question by another way regardless: core follows the same call with an `allowsAuthenticationDataChange` of every request it collected, which this provider answers about the account named. Account creation and account linking are what the order spares. What every input gets back is unchanged, which is why nothing new pins it: what moved is a query on paths with no observable output. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The gate answers who may call before it says anything about the tables, so that a wiki waiting for update.php turns callers away exactly as it will once it has them. The right was pinned; the token was not, and it is the half a caller from another site meets. Verified by moving the schema question above the token: the write is then told the wiki has no tables, which is a 503 where a 403 belongs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The filter replaced an up-to-date roster read, and the provider beside it reads the group the same way, with the reason written next to it: a group given moments ago that read as absent would let a temporary password through. The filter's read was the one left answering from a replica. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The test database has the tables, so a fixture with a roster row and no group is what tells the implementations apart: one that reached for the roster would find the row, answer with it, and pass every bare-account test here while being a database error on a real wiki. Both refusal sites get one. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The installation note calls the code route not offered while the sections above hand out what a route brings — the anonymous autocreation right, the captcha relaxation, the session duration — using the same word for the setting that asks for them. Registration runs before services exist, so those cannot follow the tables: the setting is called turned on, what it brings is said to arrive before update.php has run, and the two docblocks that promised otherwise now say what holds. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
An extension can be loaded on a wiki before update.php has run there — a farm switching it on
across many wikis at once does exactly that. It would arrive on wikis before any of them
has run update.php. Every entry point that reads a table now checks whether the tables are there,
and behaves as if the extension were not loaded when they are not, rather than failing with a
database error:
schema_missing.The exception is the reader group: core's own data, and the mark provisioning puts on an account
before its roster row. While the roster cannot be read, the mark carries the rules that must not
lapse. An account holding it is refused a password — one set in that window would be a permanent
way in that the allowlist never governs — is dropped from a password reset rather than mailed a
temporary one, and where the allowlist governs single sign-on is refused that login too: an identity
provider still points at a removed member's parked account, and the roster that would clear such an
account is exactly what cannot be read. Every other account is left alone. Staff sign in through the
identity provider as before; refusing everyone would shut a working provider out of the wiki.
The check costs at most one
tableExistsquery per request: it runs only where an entry point wouldtouch a table, and the answer is kept for the rest of the process, which for a web request is the
request. A wiki without the tables warns once per such process on the
MemberAccesschannel,telling the administrator to run update.php. A connection that cannot answer counts as having the
tables, so an unanswerable check cannot switch the feature off on a wiki that has them. What loading
the extension changes wiki-wide is untouched: those are settings, not tables.
Production notes
Commits through 49d9cd4 are the original build by
Opus 5 (max). The seven commits after it are apr-review round by
Fable 5 (max)with Opus subagents — six independent review passes, each blockingfinding adversarially verified before fixing. The review found that on a wiki whose tables go missing
after it has members (the drop-and-rerun procedure in AGENTS.md, or a partial restore), the original
head turned two fail-closed crashes into fail-opens: a temporary password mailed into a member's
mailbox, and single sign-on back into a removed member's parked account. The reader-group fallback in
ecc2162 closes both and is a semantics change against the original spec's "single sign-on is left
alone". The other review commits are ordering and probe-liveness pins and README/docblock accuracy.