Found while measuring the recovery path for #14495 (docs card); filed unassigned and unlabeled for triage.
boot-sign-in-reachability.ts (landed for #14353) ends its error line with two remedies. Measured on the exact population the report fires on — real ObjectQL over better-sqlite3, three human sys_user rows, zero sys_account rows, NODE_ENV=test, default invite_only — neither remedy does what its sentence says.
Remedy (2): "OPEN THE AUDIENCE POSTURE ... so an existing person can register their own login"
An existing person cannot.
POST /sign-in/email -> 403 {"code":"EMAIL_NOT_VERIFIED"}
On a deployment with no mail transport wired — which is the shape a locked-out self-hosted install usually is — remedy (2) therefore produces no login at all.
Remedy (1): "write a sys_account credential row for one of the existing sys_user rows directly against the store"
The row shape is public; the format of the secret in its password column is the platform's own. A row carrying a plaintext password authenticates nothing:
POST /sign-in/email -> 401 {"code":"INVALID_EMAIL_OR_PASSWORD"}
…and it SILENCES this very diagnostic, because probeSignInAccountsPresence asks only whether any sys_account row exists:
probeSignInReachability -> {"humanUsers":"present","signInAccounts":"present"}
So the operator's first attempt at the named remedy turns the loud dead end back into the silent one it was written to end, with nobody able to sign in.
What does work (measured, same population)
Insert one pending sys_invitation row (email — an address the directory does not already hold, status: 'pending', a future expires_at, inviter_id of any existing sys_user), then have that person register through the ordinary sign-up endpoint. The invitation carve-out admits that one creation under every posture, no door is widened, no mail transport is needed: sign-up 200, sign-in 200. On the single posture the next boot's bootstrapPlatformAdmin then promotes that account holder (adminPromoted: true).
That path is now documented on content/docs/deployment/self-hosting.mdx by #14495's PR. This card is the runtime half: the message an operator reads at boot should name a remedy that works, and should not describe one whose first step blinds the check.
Suggested acceptance
The report's remedy text names the invitation row as the primary way out, states that a hand-written credential row must carry the platform's own password hash (and that writing one silences this report), and states that widening the audience posture forces email verification. Message-text only — no admission semantics move, exactly as #14353 scoped itself.
Refs: #14353 (this diagnostic) · #14349 (option A, ruled) · #14495 (the docs half) · #15587 (the phantom 200).
Generated by Claude Code
Found while measuring the recovery path for #14495 (docs card); filed unassigned and unlabeled for triage.
boot-sign-in-reachability.ts(landed for #14353) ends itserrorline with two remedies. Measured on the exact population the report fires on — realObjectQLover better-sqlite3, three humansys_userrows, zerosys_accountrows,NODE_ENV=test, defaultinvite_only— neither remedy does what its sentence says.Remedy (2): "OPEN THE AUDIENCE POSTURE ... so an existing person can register their own login"
An existing person cannot.
email_domain, a seeded person's own registration answers 200 and persists nothing — no row, no account, sign-in still 401 (filed separately as plugin-auth: a sign-up for an address that already has asys_userrow answers 200 and persists nothing (postureemail_domain) #15587). Underinvite_onlythe same call is refused422 USER_ALREADY_EXISTS_USE_ANOTHER_EMAIL.invite_onlyforces email verification on, so its first sign-in is refused:On a deployment with no mail transport wired — which is the shape a locked-out self-hosted install usually is — remedy (2) therefore produces no login at all.
Remedy (1): "write a
sys_accountcredential row for one of the existingsys_userrows directly against the store"The row shape is public; the format of the secret in its
passwordcolumn is the platform's own. A row carrying a plaintext password authenticates nothing:…and it SILENCES this very diagnostic, because
probeSignInAccountsPresenceasks only whether anysys_accountrow exists:So the operator's first attempt at the named remedy turns the loud dead end back into the silent one it was written to end, with nobody able to sign in.
What does work (measured, same population)
Insert one pending
sys_invitationrow (email— an address the directory does not already hold,status: 'pending', a futureexpires_at,inviter_idof any existingsys_user), then have that person register through the ordinary sign-up endpoint. The invitation carve-out admits that one creation under every posture, no door is widened, no mail transport is needed: sign-up 200, sign-in 200. On thesingleposture the next boot'sbootstrapPlatformAdminthen promotes that account holder (adminPromoted: true).That path is now documented on
content/docs/deployment/self-hosting.mdxby #14495's PR. This card is the runtime half: the message an operator reads at boot should name a remedy that works, and should not describe one whose first step blinds the check.Suggested acceptance
The report's remedy text names the invitation row as the primary way out, states that a hand-written credential row must carry the platform's own password hash (and that writing one silences this report), and states that widening the audience posture forces email verification. Message-text only — no admission semantics move, exactly as #14353 scoped itself.
Refs: #14353 (this diagnostic) · #14349 (option A, ruled) · #14495 (the docs half) · #15587 (the phantom 200).
Generated by Claude Code