(Sorry about the delay; I've been on holiday and moving house.)
Good find :) This is due to how templating works.
To demonstrate, I ran your deps with some #tapp (tap & print; essentially tap { puts inspect }) in the right places:
dirname "~#{user}/.ssh".tapp
end
This shows that the requirement happens before dirname is set:
> babushka 'dot ssh.dir' user=ben
dot ssh.dir {
test.rb:14:in `block (2 levels) in <top (required)>': nil
test.rb:19:in `block in <top (required)>': "~ben/.ssh"
directory exists {
dirname?
When a dep is "defined", what's happening is that its outer block is run against the dep's context (which is an instance of DepContext specific to that dep. It's the scope in which things like 'met?', 'meet', 'requires', etc are defined.
When a templated dep is defined, there's an extra step. First, the 'template' block is run against the dep context, to build the shared pieces of that dep. Then the dep's outer block itself is run against the same context, naturally overriding the template. The upshot of this is that the logic within the template can't see things defined within the dep itself, because at the time the template is run the dep hasn't been defined yet.
In this case, the template running first means that the 'directory exists' requirement happens before the 'dirname' setting in the dep.
I'd structure it like this instead: