032-stdout-closed-reader

A closed reader is not an error: stdout writes stop panicking the CLI

Lifecycle approvedImplementation completeVerification unknownQualification unknown

Exit codes are a stable contract: 0 ok, 1 validation failure, 2 stale, 3 I/O. The CLI could exit 101. `println!` unwraps its write, so a reader that stops early made the process panic: `spec-spine registry list --json | head` printed a Rust backtrace and exited 101, and under `set -o pipefail` that failed the surrounding script. Piping into `head`, `less` or `grep -q` is ordinary use, not an error condition. This spec routes every CLI stdout write through one helper that classifies the write instead of unwrapping it: a closed reader ends the process 0, because the consumer got what it asked for, and a genuine I/O failure reports and exits 3, which is the code the contract already assigns to I/O. Stderr keeps `eprintln!`: diagnostics are small, and a broken stderr has nowhere left to report itself. The fix is not `unsafe` SIGPIPE restoration, which `forbid(unsafe_code)` rules out workspace-wide, and not a panic hook, which would match on a message string the standard library is free to change.

Read the full pinned specification ↗

Relationships

Claimed units

  • establishes
    {
      "kind": "file",
      "path": "crates/spec-spine-cli/src/out.rs"
    }
  • extends
    {
      "nature": "additive",
      "spec": "001-compile-registry",
      "unit": {
        "kind": "file",
        "path": "crates/spec-spine-cli/src/main.rs"
      }
    }
  • extends
    {
      "nature": "additive",
      "spec": "001-compile-registry",
      "unit": {
        "kind": "file",
        "path": "crates/spec-spine-cli/src/cmd_attest.rs"
      }
    }
  • extends
    {
      "nature": "additive",
      "spec": "001-compile-registry",
      "unit": {
        "kind": "file",
        "path": "crates/spec-spine-cli/src/cmd_compile.rs"
      }
    }
  • extends
    {
      "nature": "additive",
      "spec": "001-compile-registry",
      "unit": {
        "kind": "file",
        "path": "crates/spec-spine-cli/src/cmd_couple.rs"
      }
    }
  • extends
    {
      "nature": "additive",
      "spec": "001-compile-registry",
      "unit": {
        "kind": "file",
        "path": "crates/spec-spine-cli/src/cmd_index.rs"
      }
    }
  • extends
    {
      "nature": "additive",
      "spec": "001-compile-registry",
      "unit": {
        "kind": "file",
        "path": "crates/spec-spine-cli/src/cmd_lint.rs"
      }
    }
  • extends
    {
      "nature": "additive",
      "spec": "001-compile-registry",
      "unit": {
        "kind": "file",
        "path": "crates/spec-spine-cli/src/cmd_registry.rs"
      }
    }
  • extends
    {
      "nature": "additive",
      "spec": "001-compile-registry",
      "unit": {
        "kind": "file",
        "path": "crates/spec-spine-cli/src/verify_attestation.rs"
      }
    }
  • extends
    {
      "nature": "additive",
      "spec": "001-compile-registry",
      "unit": {
        "kind": "file",
        "path": "crates/spec-spine-cli/tests/cli.rs"
      }
    }

Source headings

  1. 032: A closed reader is not an error
  2. 1. Purpose
  3. 2. Territory
  4. 3. Behavior
  5. 3.1 Two entry points, one classifier, three outcomes
  6. 3.2 Why not the two shorter fixes
  7. 3.3 Stderr is deliberately unchanged
  8. 3.4 Determinism and output are unaffected
  9. 3.5 Tests (minimum)
  10. 4. Out of scope

Frozen anchors

No frozen anchors declared.