You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
finding: add-member was not the only one — 6 of 17 ObjectStack auth mounts are undocumented and 9 of 17 are in no ledger, and nothing catches a mount that lands without either #10534
#10050 asked whether POST /organization/add-member being mounted-but-undocumented was a one-off. It was not. Census below, measured on origin/main at 4544be260, with positive controls on both legs.
Population
auth-plugin.ts mounts routes directly on the raw Hono app, ahead of the better-auth catch-all. Those mounts are the ObjectStack-owned auth surface — the catch-all never sees them, so the vendor's own route table cannot account for them.
Enumerated by matching rawApp.<verb>(\${basePath}/…`)inpackages/plugins/plugin-auth/src/auth-plugin.ts(excluding therawApp.allcatch-all and the.well-known/*mounts, which are not underbasePath`): 17 routes.
Leg 1 — documented?
Test: does the literal wire path /api/v1/auth<path> appear anywhere in the hand-written docs corpus (392 .md/.mdx files under content/docs, content/docs/releases/** excluded as release-owned)?
6 of 17 undocumented, before this card's PR:
Route
In a ledger?
POST /api/v1/auth/admin/sso/request-domain-verification
no
POST /api/v1/auth/admin/sso/verify-domain
no
POST /api/v1/auth/admin/unban-user
vendor inventory only
GET /api/v1/auth/bootstrap-status
yes — objectstack / sdk
POST /api/v1/auth/organization/add-member
yes — objectstack / server-only
POST /api/v1/auth/set-initial-password
no
#10050's PR documents the last-but-one, leaving 5 of 17.
⚠️ Two refinements the raw number hides. set-initial-password is named once in prose (authentication.mdx, "offers set-initial-password") without its URL — named, not documented. And add-member was named three times in tenancy-modes.mdx with no URL anywhere, which is how it stayed invisible while looking discussed.
Positive control (leg 1): the same expression, run before the docs were written, returned organization/add-member — the known-present member of the set. So a zero would have been a real zero. Control output: POSITIVE CONTROL organization/add-member present in undocumented set: true.
Leg 2 — ledgered?
auth-route-ledger.ts has two halves: the reviewed AUTH_ROUTE_LEDGER rows and the machine-maintained BETTER_AUTH_MOUNTED_SURFACE inventory. A route in neither is unaccounted for.
Only 3 of the 17 carry a reviewed source: 'objectstack' row: /config, /bootstrap-status, /organization/add-member. The rest are either attributed to the vendor (correct where the vendor also declares the path — the ObjectStack mount shadows it) or absent entirely.
Verified two ways that agree: a regex parse of the ledger source, and a direct grep "auth<path>'" per route. Positive control (leg 2): the same grep returns 1 for /organization/add-member and 1 for /config, so an absence is an absence and not a broken pattern.
Leg 3 — why nothing caught it
Both of #10050's asks land on the same hole, and it is upstream of either ledger row.
scripts/docs-audit/affected-docs.mjs --bridge-coverage on this tree:
Every client-bound row in the auth ledger is unreachable by the sdk doc-nomination bridge. The bridge finds route registrars by filename convention — REGISTRAR_FILE_RE = /(?:^|\/)(?:[\w.-]*route[\w.-]*|[\w.-]*-server)\.ts$/ — and the file that actually registers this surface is auth-plugin.ts, which matches neither alternative. No registrar tail is produced for the auth surface, so no auth ledger row can be selected, so the sdk anchor never nominates a doc for an auth route.
add-member is then invisible a second time on top of that: its row is disposition: 'server-only' with no client, so it is not even among the 55 client-bound rows the bridge fails to reach.
The 221→45 shape is already recorded in #9572 (closed). What is new here is the auth slice being 55 of 55 and the concrete cause being one filename convention — plus the demonstration that this is what let a route mount and its documentation gap ship separately with nothing objecting.
⛔ Deliberately not acted on: scripts/docs-audit/affected-docs.mjs is being edited by open PR #10501, which is fixing a different silent-parse defect in the same file. Any remedy here belongs after that lands.
Suggested follow-ups
Document the 5 remaining undocumented mounts (or record a deliberate "server-only, not user-facing" verdict for each — bootstrap-status may well be one).
Ledger the 9 unledgered mounts as source: 'objectstack' rows, which is what the ledger's own header says objectstack mounts are for.
#10050 asked whether
POST /organization/add-memberbeing mounted-but-undocumented was a one-off. It was not. Census below, measured onorigin/mainat4544be260, with positive controls on both legs.Population
auth-plugin.tsmounts routes directly on the raw Hono app, ahead of the better-auth catch-all. Those mounts are the ObjectStack-owned auth surface — the catch-all never sees them, so the vendor's own route table cannot account for them.Enumerated by matching
rawApp.<verb>(\${basePath}/…`)inpackages/plugins/plugin-auth/src/auth-plugin.ts(excluding therawApp.allcatch-all and the.well-known/*mounts, which are not underbasePath`): 17 routes.Leg 1 — documented?
Test: does the literal wire path
/api/v1/auth<path>appear anywhere in the hand-written docs corpus (392.md/.mdxfiles undercontent/docs,content/docs/releases/**excluded as release-owned)?6 of 17 undocumented, before this card's PR:
POST /api/v1/auth/admin/sso/request-domain-verificationPOST /api/v1/auth/admin/sso/verify-domainPOST /api/v1/auth/admin/unban-userGET /api/v1/auth/bootstrap-statusobjectstack/sdkPOST /api/v1/auth/organization/add-memberobjectstack/server-onlyPOST /api/v1/auth/set-initial-password#10050's PR documents the last-but-one, leaving 5 of 17.
set-initial-passwordis named once in prose (authentication.mdx, "offers set-initial-password") without its URL — named, not documented. Andadd-memberwas named three times intenancy-modes.mdxwith no URL anywhere, which is how it stayed invisible while looking discussed.Positive control (leg 1): the same expression, run before the docs were written, returned
organization/add-member— the known-present member of the set. So a zero would have been a real zero. Control output:POSITIVE CONTROL organization/add-member present in undocumented set: true.Leg 2 — ledgered?
auth-route-ledger.tshas two halves: the reviewedAUTH_ROUTE_LEDGERrows and the machine-maintainedBETTER_AUTH_MOUNTED_SURFACEinventory. A route in neither is unaccounted for.9 of 17 appear in neither half:
/admin/import-users·/admin/oauth2/toggle-disabled·/admin/sso/register·/admin/sso/register-saml·/admin/sso/request-domain-verification·/admin/sso/verify-domain·/admin/unlock-user·/set-initial-password·/sys-oauth-application/registerOnly 3 of the 17 carry a reviewed
source: 'objectstack'row:/config,/bootstrap-status,/organization/add-member. The rest are either attributed to the vendor (correct where the vendor also declares the path — the ObjectStack mount shadows it) or absent entirely.Verified two ways that agree: a regex parse of the ledger source, and a direct
grep "auth<path>'"per route. Positive control (leg 2): the same grep returns 1 for/organization/add-memberand 1 for/config, so an absence is an absence and not a broken pattern.Leg 3 — why nothing caught it
Both of #10050's asks land on the same hole, and it is upstream of either ledger row.
scripts/docs-audit/affected-docs.mjs --bridge-coverageon this tree:Every client-bound row in the auth ledger is unreachable by the
sdkdoc-nomination bridge. The bridge finds route registrars by filename convention —REGISTRAR_FILE_RE = /(?:^|\/)(?:[\w.-]*route[\w.-]*|[\w.-]*-server)\.ts$/— and the file that actually registers this surface isauth-plugin.ts, which matches neither alternative. No registrar tail is produced for the auth surface, so no auth ledger row can be selected, so thesdkanchor never nominates a doc for an auth route.add-memberis then invisible a second time on top of that: its row isdisposition: 'server-only'with noclient, so it is not even among the 55 client-bound rows the bridge fails to reach.The 221→45 shape is already recorded in #9572 (closed). What is new here is the auth slice being 55 of 55 and the concrete cause being one filename convention — plus the demonstration that this is what let a route mount and its documentation gap ship separately with nothing objecting.
⛔ Deliberately not acted on:
scripts/docs-audit/affected-docs.mjsis being edited by open PR #10501, which is fixing a different silent-parse defect in the same file. Any remedy here belongs after that lands.Suggested follow-ups
bootstrap-statusmay well be one).source: 'objectstack'rows, which is what the ledger's own header says objectstack mounts are for.sdkbridge should reach the auth surface at all — either by teachingREGISTRAR_FILE_REaboutauth-plugin.ts, or by declaring registrars explicitly rather than by filename convention. After fix(docs-audit): a PARTIAL ledger read is a verdict, not a smaller number #10501.rawAppmount underbasePathwith no ledger row is exactly the state that produced [finding]sys_member"Add Member" action targetsPOST /organization/add-member, which better-auth 1.7.0-rc.2 never mounts (server-only) — on multi-org there is NO remaining UI path to attach an existing user to an org #9941 and docs: the newly mountedPOST /organization/add-memberis undocumented — and it is the ONLY multi-org path to attach an existing user #10050, and it is mechanically detectable.Refs
#10050 (the card this sweep came from) · #9941 / PR #10049 (the mount) · #9572 (the 221→45 bridge measurement, closed) · #10501 (open, owns
affected-docs.mjs) · #9969 / #9652 (adjacent: vendor/admin/routes and their admit sets)