Motivation
architecture.md is authoritative on the error model, and its mode table says:
| Mode |
An undecided error… |
Installed by |
output |
fails the run; <PrintErrors> can print instead |
every <Output> region |
§6.8.1 says the same from the other side: a failure an output decision selected is deliberately not in the never-printed list — "the region it left has already stopped, so printing it resumes nothing." throw is named as the one mode a boundary does not replace.
On 511776e an outer <PrintErrors> does not print such a failure. The run fails instead. The contract is the architecture's; this is the engine not meeting it.
Reproduction
Shared fixture — a name that resolves to nothing, which raises an ordinary printed error under print:
<!-- components/OutFail.md -->
<Output>before <Nope /> after</Output>
<!-- components/Holder.md -->
<Output><Content /></Output>
Control — print at the root works.
<PrintErrors>
<Nope />
</PrintErrors>
TAIL REACHED
exit 0; stdout carries <!-- ERROR: Failed to import component Nope … --> and TAIL REACHED. Correct.
Case 1 — the failure is in the callee's own <Output> region.
<PrintErrors>
<OutFail />
</PrintErrors>
TAIL REACHED
Expected: the boundary prints one error whose cause is the original failure, and TAIL REACHED renders.
Actual: exit 1. stdout holds only the partial text before ; the message goes to stderr; TAIL REACHED never renders.
Case 2 — the failure is projected into a callee's <Output> region.
<PrintErrors>
<Holder><Nope /></Holder>
</PrintErrors>
TAIL REACHED
Expected: §6.3 says "the error mode is captured at the expansion site and carried across", and the expansion site here is inside <PrintErrors> — so this should print for that reason alone, independently of the output question.
Actual: exit 1, nothing on stdout, TAIL REACHED never renders.
Where to look
decidedByOutput() (packages/core/src/errors.ts:264) exists precisely to distinguish an output decision from a throw decision at a printing boundary, and its comment states the intended difference. Its only consumer is expand.ts:2292, on the ContentExpansionFailure branch. Case 1 does not reach that branch at all, and case 2 reaches it but still fails — so the two cases may not share a cause. Both should be checked rather than assumed to be one fix.
Partial output
Case 1 shows the partial-output rule already working: before reached the output stream before the failure. Whatever fixes printing must keep that — the region's already-rendered text stays, and printing adds the error after it rather than replacing it.
Acceptance criteria
- An outer
<PrintErrors> turns a failure an <Output> region decided into exactly one printed error whose cause is the complete original failure, and the run continues past the boundary.
- The same holds whether the failure arose in the callee's own
<Output> region or in content projected into one.
- A
throw decision is still never printed — documentation and value roots keep failing through a boundary.
- Text a failing region had already rendered still reaches the output stream, and the printed error does not replace it.
- A durability failure is still never printed.
- Tests discriminate the three cases above by exit status and by whether the tail after the boundary renders — an assertion on stderr alone would pass today.
Impact
Reported while synchronizing the #181 living end-goal target (#292). The workflow documents describe the architectural behavior, as architecture.md is authoritative, and classify this as missing pending this issue. No workflow document writes <PrintErrors> today, so nothing is blocked on it — but a stage that wants to survive a failed region cannot, and the target's error-mode table would otherwise have to contradict the registry.
Motivation
architecture.mdis authoritative on the error model, and its mode table says:output<PrintErrors>can print instead<Output>region§6.8.1 says the same from the other side: a failure an
outputdecision selected is deliberately not in the never-printed list — "the region it left has already stopped, so printing it resumes nothing."throwis named as the one mode a boundary does not replace.On
511776ean outer<PrintErrors>does not print such a failure. The run fails instead. The contract is the architecture's; this is the engine not meeting it.Reproduction
Shared fixture — a name that resolves to nothing, which raises an ordinary printed error under
print:Control —
printat the root works.exit 0; stdout carries<!-- ERROR: Failed to import component Nope … -->andTAIL REACHED. Correct.Case 1 — the failure is in the callee's own
<Output>region.Expected: the boundary prints one error whose
causeis the original failure, andTAIL REACHEDrenders.Actual:
exit 1. stdout holds only the partial textbefore; the message goes to stderr;TAIL REACHEDnever renders.Case 2 — the failure is projected into a callee's
<Output>region.Expected: §6.3 says "the error mode is captured at the expansion site and carried across", and the expansion site here is inside
<PrintErrors>— so this should print for that reason alone, independently of theoutputquestion.Actual:
exit 1, nothing on stdout,TAIL REACHEDnever renders.Where to look
decidedByOutput()(packages/core/src/errors.ts:264) exists precisely to distinguish anoutputdecision from athrowdecision at a printing boundary, and its comment states the intended difference. Its only consumer isexpand.ts:2292, on theContentExpansionFailurebranch. Case 1 does not reach that branch at all, and case 2 reaches it but still fails — so the two cases may not share a cause. Both should be checked rather than assumed to be one fix.Partial output
Case 1 shows the partial-output rule already working:
beforereached the output stream before the failure. Whatever fixes printing must keep that — the region's already-rendered text stays, and printing adds the error after it rather than replacing it.Acceptance criteria
<PrintErrors>turns a failure an<Output>region decided into exactly one printed error whosecauseis the complete original failure, and the run continues past the boundary.<Output>region or in content projected into one.throwdecision is still never printed — documentation and value roots keep failing through a boundary.Impact
Reported while synchronizing the #181 living end-goal target (#292). The workflow documents describe the architectural behavior, as
architecture.mdis authoritative, and classify this as missing pending this issue. No workflow document writes<PrintErrors>today, so nothing is blocked on it — but a stage that wants to survive a failed region cannot, and the target's error-mode table would otherwise have to contradict the registry.