directory-keys-shared.test.ts: the store-down case now expects the store error instead of an 'invalid' signature, since a store outage no longer counts as a bad signature.
The marker-write test from d28ee361c now expects the 503 path (a rejected promise) instead of false: refusing with "Token already used" sent admins looking for a replay that never happened, and the jti is now remembered in memory only after it was recorded, so a retry works once the store or state dir is back.
Config, policy, branding, OAuth setup, password and setup wizard writes
used to answer 500 when the shared store couldn't be reached, and the
wizard's token check failed outside any handler. They answer 503 with
Retry-After now, through the helper that already turned a read-only
refusal into 409. The step-session-secret test's wizard mock gains
wizardSessionRefusal, which the setup routes now call.
The earlier fix answered 503 to every read and write of a settings file
or plugin vault that doesn't decrypt, so a rotated SESSION_SECRET left
the user with no way out: even disconnecting a plugin failed. Now such a
document reads as nothing stored, and the next write first moves it
aside, then saves as usual. The bytes are kept unchanged, so the data can
be restored with the old secret:
- local mode renames the file to `<file>.unreadable-<time>` next to it
- shared mode copies the document to `<id>.unreadable-<time>`, then
deletes the original only if it is still the same document
A settings file too short to hold anything reads as nothing stored and
is written over, as before 585e10041. The 503 is now only for a shared
store that can't be reached or a file that can't be read at all.
Credentials the admin set up for a plugin no longer read the user's
vault, so they keep working when it can't be read, and the credential
list still reports their status.
Fixes from the review of the admin part:
- GET /api/admin/policy reads the policy and its version together; an
identity check waiting on the store let another replica's change pair
the old policy with the new ETag, and a save with it then won.
- A policy save answers with the stored policy and its ETag, and the
policy, organisation, plugins and themes pages continue from that
instead of reading it back from an instance that may not have it yet.
- A stored policy that doesn't parse keeps its version, so a save can
replace it instead of answering 409 forever.
- A reload of another replica's change throws when this replica's writes
kept landing while it read, so the change poller tries again; a write
whose reload raced it reads the document again instead of applying its
older result.
- Dropping a deleted plugin from the policy rereads policy.json first in
local mode, as every other write does.
- New updateAdminConfig(key, change) builds a value on the stored one.
The policy route's test mocks gain getPolicyWithVersion.