Skip to content

💥 Make <Output> fail fast and rename failure capture to CaptureErrors #309

Description

@taras

Motivation

<Output> currently makes failures visible by collecting them as rendered error
segments and continuing. That is a dangerous default for operational documents:
a failed preview can still reach a later <Elicit> or destructive publish step.

PR #308 exposed the consequence. The npm-bootstrap workflow had to build a
fail-closed protocol from sentinel files, cat, and production uses of testing
assertions merely to express:

Show the operator what this stage produced, but stop the document if it
failed.

The language already has an explicit collection boundary. Continuing after an
error should be requested there, not silently selected by every <Output>.

Product decision

<Output> is fail-fast by default.

  • It renders the output produced before a failure.
  • The document completes with a failure.
  • Later siblings, later output regions, elicitations, and other effects do not
    run.
  • Successful output selection, documentation suppression, ordering, and
    capture behavior remain unchanged.

Continuing after ordinary failures is explicit and scope-local:

<Output>
  <CaptureErrors>
    ... work whose errors should render without aborting ...
  </CaptureErrors>
</Output>

Rename the paired public vocabulary as part of the same breaking change:

  • <CollectFailures> becomes <CaptureErrors>.
  • collectFailures(fn) becomes captureErrors(fn).
  • The old tag and export are removed rather than retained as aliases.

<CaptureErrors> otherwise preserves the existing collection contract: it
accepts no props, converts ordinary function-component failures into one
structured error segment, renders/observes each error once, and permits later
work to continue. Fatal durability, replay, schema, and already-selected
documentation failures remain outside that recovery contract exactly as they
are today.

This is a breaking language and API change; its PR title uses the 💥 gitmoji.

Dependency

#307 lands first. A non-zero command must be recognized as a failure even when
it wrote stdout. This issue then governs what <Output> does with that failure
and ensures the stdout remains observable.

Required behavior

<Output>

  • A failure after visible text or command stdout preserves everything produced
    before the failure and then fails the execution.
  • The failure is reported only after the failing invocation has completed
    teardown.
  • Error identity, type, cause, aggregation, and one-observation behavior are
    preserved.
  • No later sibling or region begins after the failure.
  • A successful root <Output> retains its existing single buffered emission.
  • A failing root <Output> emits the partial selected output before its
    completion Result reports failure.
  • Component-owned <Output> follows the same policy as root <Output>.
  • Documentation remains hidden and fail-fast.
  • A root or component with no <Output> retains its separately documented
    behavior; do not broaden this issue into a redesign of text roots.

<CaptureErrors> / captureErrors(fn)

  • Both install the same explicit, scope-local recovery policy as their current
    CollectFailures counterparts.
  • Nested capture boundaries do not duplicate diagnostics.
  • Failures outside the boundary remain fail-fast.
  • A captured failure inside <Output> renders once and allows later content to
    run.
  • The structural name is reserved and receives the existing placement, prop,
    source-position, projection, and observation guarantees.

Acceptance coverage

  • Root <Output>: output before failure is emitted, completion is failed, and a
    later sibling with an observable side effect does not run.
  • Component <Output>: the same three assertions through a real invocation.
  • Non-zero exec with stdout: stdout is visible, execution fails, and a later
    <Elicit>/process probe does not run. This is the integration pin with A non-zero code block that printed to stdout raises no failure #307.
  • Multiple output regions: failure in an earlier region prevents later regions
    and documentation from running while preserving earlier selected output.
  • Explicit <CaptureErrors> inside <Output> renders the diagnostic once and
    permits a following marker to render.
  • Without <CaptureErrors>, the same fixture fails and the marker does not
    render; demonstrate this discriminating mutation before keeping the test.
  • Nested <CaptureErrors> and captureErrors(fn) preserve existing collection,
    teardown, identity/cause, and one-observation behavior.
  • Successful <Output> selection, <Content> projection, as capture, empty
    regions, and root/component parity remain covered.
  • Durable replay produces the same visible partial output and failure without
    replaying suppressed later effects.

Migration and documentation

  • Rename the structural dispatch, reservation entry, diagnostics, conformance
    rows, tests, smoke documents, README examples, and current specifications.
  • Rename every production and test import/call of collectFailures to
    captureErrors.
  • Audit every remaining CollectFailures / collectFailures occurrence into
    removed or explicitly historical text; current authoring and API references
    use only the new vocabulary.
  • Update specs/executable-mdx-spec.md in the same PR, including the current
    statements that <Output> selects the collecting policy and that buffered
    output is discarded on later failure.
  • Update PR ✨ Bootstrap npm packages as a real xmd run: elicit the OTP #308's plan after this lands: the verdict files and production
    assertions disappear, and ordinary npm stages rely on fail-fast <Output>.

Verification

Before commit, run the repository's complete required gates with the CI-pinned
runtimes, including deno task fmt, lint, check, test, check:jsr, Node
typecheck/tests, Bun tests, compiled-binary smoke, git diff --check, and the
packages/cli/src/node.ts mode check. No red gate is waived as baseline.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions