::outer combines two s/keys specs with s/and. s/and flows conformed specs so the second spec will see the conformed value, not the original value. Also of importance, s/keys always conforms all registered keys in the spec. Combined these are leading to the failure of ::outer.
That is, when ::outer is conformed, it passes the input into the first spec and returns the conformed value {::inner [:ident [:a/b 1]]}
, which is passed as input to the second spec. The second spec sees a registered key ::inner and tries to conform [:ident [:a/b 1]]
against ::inner, which will fail.
I think there are probably a couple things possible here and without knowing the full story it's hard to say what's best. One is using s/merge instead of s/and - that will NOT pass the conformed value to each spec so simply replacing s/and with s/merge in your example will make things valid as expected. The caveat here is that you will only get the conformed value of the last spec in the merge. It's unclear if that's what you want.
Another option for the ::one/::two case is to use the or
support built into s/keys :req to combine all of this into one spec:
(s/keys :req [::inner (or ::one ::two)])