Discriminated Union - Allow Pattern Matching but Restrict Construction

Unless there's a particular reason that a discriminated union is required, given the particular use case you've provided it sounds like you don't actually want a discriminated union at all since an active pattern would be more useful. For example:

let (|ValidInt|ValidString|Invalid|) (value:obj) = 
    match value with
    | :? int as x -> if x > 0 then ValidInt x else Invalid
    | :? string as x -> if x.Length > 0 then ValidString x else Invalid
    | _ -> Invalid

At that point, callers can match and be assured that the logic has been applied.

match someValue with
| ValidInt x -> // ...
| _ -> // ...

You can have private case constructors but expose public active patterns with the same names. Here's how you would define and use them (creation functions omitted for brevity):

module Helpers =
    type ValidValue = 
        private
        | ValidInt of int
        | ValidString of string

    let (|ValidInt|ValidString|) = function
        | ValidValue.ValidInt i -> ValidInt i
        | ValidValue.ValidString s -> ValidString s

module Usage =
    open Helpers

    let validValueToString = function
        | ValidInt i -> string i
        | ValidString s -> s
    // 😎 Easy to use ✔

    // Let's try to make our own ValidInt 🤔
    ValidInt -1
    // error FS1093: The union cases or fields of the type
    // 'ValidValue' are not accessible from this code location
    // 🤬 Blocked by the compiler ✔