After re-re-(…)-reading the manuals, I may have figured it out.
From Amiga ROM Kernel Reference Manual: Libraries & Devices, p.584:
The CLI does not create a new process for a program; it jumps to the
program's code and the program shares the process with the CLI.
From this, I gather that the process returned by FindTask(NULL) is the CLI process, and tc_SPUpper and tc_SPLower refer to the stack for that process.
From AmigaDOS Developer's Manual, p. 160:
When the CLI starts up a program, it allocates a stack for that
program. This stack is initially 4000 bytes, but you may change the
stack size with the STACK command. AmigaDOS obtains this stack from
the general free memory heap just before you run the program; it is
not, however, the same as the stack that the CLI uses.
From this, I conclude that my program stack is separate from the stack in the task returned by FindTask(NULL).
Also from AmigaDOS Developer's Manual, p. 160:
AmigaDOS pushes a suitable return address onto the stack that tells
the CLI to regain control and unload your program. Below this on the
stack at 4(SP) is the size of the stack in bytes…
From this, I conclude that, for programs run from the CLI, the following code will give me the lowest address available on the stack.
move.l sp,d0 ; current stack pointer
addq.l #8,d0 ; return address and stack size
sub.l 4(sp),d0 ; size of stack
move.l d0,stack_lowest ; save for stack checking
For programs launched from Workbench, I think tc_SPUpper and tc_SPLower are the values that I want.
From Amiga ROM Kernel Reference Manual: Libraries & Devices, p.584:
When a user activates a tool or project, Workbench runs a program.
This program is a separate process and runs asynchronously to
Workbench.
I have confirmed that the difference between these two values is, indeed, the stack size specified in the .info file.