I think I have figured out the answer. The results have to do with the difference between the integral of a continuous Gaussian function, versus the sum of a discretized Gaussian.
In the ideal case, continuous and discrete sums of Gaussians already differ
As one illustrative example, consider the integral of a Gaussian versus the discrete summation of a Gaussian with unit steps is
$$ \int_{-\infty}^\infty e^{-x^2}\!\text{d}x=\sqrt{\pi}\approx 1.77245,\qquad \sum_{x\in\mathbb{Z}} e^{-x^2} = \theta_3(0,e^{-1})\approx 1.77264.$$
Apparently, the two expressions differ in the details (after 4 decimals). Such things matter when we compare a continuous convolution ($(f*g)(x)=\int_\tau f(x-\tau)g(\tau)\text{d}\tau $) against a discrete convolution ($(f*g)[x]=\sum_\tau f[x-\tau]g[\tau]$) using Gaussian kernels.
Symmetric versus asymmetric functions
A Gaussian is a symmetric function, i.e., $G(x)=G(-x)$. The first derivative of a Gaussian is antisymmetric, i.e., $\text{d} G(x)/\text{d} x=-\text{d} G(-x)/\text{d} x$. The second derivative of a Gaussian is symmetric again, and so on.
Implication for convolutions
We consider truncated (to $2T+1$ terms, although we include $T\to\infty$ in this description) convolutions with Gaussians $G_0$ or their $n$-th derivatives ($G_n$) with the following computation:
$$ (f*G_n)[x]= \sum_{\tau=-T}^T f[x-\tau] G_n[\tau]. $$
Knowing that Gaussian functions are either anti-symmetric or symmetric, that means we can 'simplify' the summation to only consider non-negative integer values for $\tau$,
$$ (f*G_n)[x] = f[x]G_n[0] + \sum_{\tau=1}^T \left(f[x-\tau]+(-1)^n f[x+\tau]\right)G_n[\tau].$$
We now note that for an odd (i.e., first-order, third-order, ...) derivative of a Gaussian, the antisymmetry also implies that $G_n[0]=0$. This is not true for an even derivative of a Gaussian, where $G_n[0]\neq 0$.
Specific example for constant functions, $f=1$
We can thus symbolically write down what happens when convolving a Gaussian derivative with a constant function $f=1$. For a first derivative, with $n=1$, we compute
$$ (f*G_1)[x] = \underbrace{G_1[0]}_{=0} + \sum_{\tau=1}^T \underbrace{(1-(-1)^11)}_{=0}G_1[\tau] = 0, $$
that is, it evaluates to 0, which we expect from differentiating a constant function.
Conversely, for a second derivative, $n=2$, it results in
$$ (f*G_2)[x] = G_2[0] + \sum_{\tau=1}^T\underbrace{(1+(-1)^2 1)}_{=2} G_2[\tau] = G_2[0] + 2\sum_{\tau=1}^T G_2[\tau].$$
This expression only equals the expected result of 0, if the truncated and discrete kernel $G_2$ sums to 0, which is alternatively expressed as the expression above. This strict requirement is not met for discrete sums of a Gaussian derivative, even without truncation (i.e., when letting $T\to\infty$ the sum is still not 0)!
Explaining the observed behavior
So, I can now answer the question posed above (which was 'why do two successive 1st-order derivatives give the correct result, but a single 2nd-order derivative not?'): after applying the first derivative operation, only zeroes remain (per the result for $G_1$ described above), which leads to field of zeroes for the second convolution operation. Conversely, an immediate application of $G_2$ simply returns the sum of the kernel $G_2$ multiplied with the local value. That second definition should be 0 in the ideal and continuous case -- but because we're working with a discrete application of convolutions it ceases to hold. Even if we use a very large truncation (e.g., specify truncate=1000
when calling the Gaussian filter in Python), it will not converge to 0. Instead, it will converge to $G_0[0]\sum_{\tau=-T}^T G_2[\tau]=-8.42695\times10^{-8}$ (the term $G_0[0]$ comes from the Gaussian which is applied in the horizontal direction), which we can confirm with our code:
import numpy as np
import matplotlib.pyplot as plt
A = np.zeros((30,30))
A[:,15] = 1
from scipy.ndimage import gaussian_filter
B = gaussian_filter(A, sigma=1, order=[2, 0], mode='reflect', truncate=10000)
plt.imshow(B)
plt.colorbar()
plt.show()
print(B[0,15]) # >> -8.426948379372423e-08
So, to conclude, the scipy implementation is correct, and it does what it is supposed to do, from a discrete point of view. However, from a continuous point of view, the results are slightly unexpected and seem wrong. I'll ponder what the most appropriate fix is. Perhaps, we must simply ensure that the summation convention holds, by slightly modifying the kernel $G_2[0]$, after which the discrete kernel will mimic the continuous kernel, at least for constant functions!