Skip to content

Records a neutral discriminator for exit reasons - #76

Merged
johnnyt merged 1 commit into
mainfrom
sui-2s4-terminated-reason
Sep 2, 2026
Merged

Records a neutral discriminator for exit reasons#76
johnnyt merged 1 commit into
mainfrom
sui-2s4-terminated-reason

Conversation

@johnnyt

@johnnyt johnnyt commented Sep 2, 2026

Copy link
Copy Markdown
Member

Records ADR-0015 at Status proposed. sui-2s4 asked whether
session.terminated's reason should stop being bare inspect/1 output.
This PR answers it as a decision record and ships nothing else: no code, no
docs/wire-format.md edit, no changelog fragment, no implementing bead.

Provenance

  • Cut from origin/main at 2071776, the squash-merge of sui PR Records a wire vocabulary for error reasons #74
    (sui-4lr, ADR-0014). ADR-0014 and its README index row were already on
    main when this branch was created
    , so the ADR-0014 row is inherited
    rather than merged around, and no rebase-over-sibling resolution was
    needed. This record is ADR-0015; ls docs/adr and the open-PR list
    were both checked for a number collision.
  • A sibling worker is active on lib/, assets/, and mix.exs for
    sui-wqr. This branch touches none of them.

Touched files

File Change
docs/adr/0015-a-neutral-discriminator-for-exit-reasons.md new, 503 lines
docs/adr/README.md +1 index row, after ADR-0014's

git diff origin/main -- docs/adr/ shows zero removed lines.

What the record decides

Add one field, kind, carrying a language-neutral lowercase token for the
exit reason's class, and leave reason byte-identical as the human overflow.

  • Purely additive: every existing message stays byte-for-byte the same, the
    format version stays 1, and the projection position table does not move.
    The rejected alternative - mapping :normal / :shutdown /
    {:shutdown, _} / :killed to bare strings in place - is a changed
    value under an existing key, which ADR-0005's must-ignore rule does not
    cover; the record says so explicitly rather than waving at "additive".
  • The golden fixture does not move: test/support/trace/two_state.jsonl
    contains zero session.terminated messages (verified, not assumed).
  • kind is never projected; reason keeps its existing unconditional
    redaction. A projected stream therefore gains an outcome it cannot be
    told today.

Consistency with ADR-0014

Stated as its own decision section, with a side-by-side table. There is no
divergence to declare
: ADR-0014 kept inspect/1 as a fallback field on the
explicit condition that a neutral token sit beside it, and cited this bead's
third acceptance criterion as the shape it was satisfying. ADR-0015 adopts
the same kind derivation rule verbatim and the same
inspect/1-beside-a-token split for the field ADR-0014 took the precedent
from, which closes that record's open coupling rather than opening a new one.
The only asymmetry is ADR-0014's class key, absent here because
session.terminated has one payload shape rather than two.

Two things flagged rather than buried

  • The OTP crash shape {exception, stacktrace} classifies as "unknown"
    under the shared derivation rule, so the most informative failure gets the
    least specific token. Recorded as the record's weakest point, with the
    additive escape hatch named.
  • The bead's first acceptance criterion selects the option this record
    rejects.
    sui-2s4 was written in 2026-08 as an implementation bead and
    asks for bare lowercase strings under reason itself. Flipping this
    record's Status also re-scopes that criterion, and the record's Notes say
    so.

A named alternative the operator may prefer (a curated OTP-reason allowlist
with an "other" token, so {:bad, 1} does not read as a recognised reason)
is recorded in the Notes and argued against rather than omitted.

Verification

  • /wurk:verify --unattended found an empty backlog (no plan stage ran), so
    the substitute pass applies: every claim was machine-checked by a script.
  • All 27 file:line cites in the record resolve against this branch's HEAD;
    two were wrong on the first draft and were corrected.
  • Status line reads proposed; the record contains no Status: accepted.
  • ADR number unique against docs/adr/ and against the open-PR list (empty).
  • Working tree carries only the two files above.
  • The bead's own acceptance criteria are implementation criteria and stay
    deferred to the implementing bead, which is filed on acceptance and not
    before.

Gate: not run - docs-only. gate.rb reports applicable: false
("no changes under lib/, test/, config/, mix.exs, mix.lock,
assets/, docs/wire-format.md"). CI replays the full gate on the PR.

Status: proposed - the flip is the operator's, and follows as its own
docs-only PR under the same review gate.

Refs: sui-2s4

ADR-0015, at Status proposed. session.terminated's reason is inspect/1
of the Elixir exit reason (subscriber.ex:598), so ":killed" and
"{:bad, 1}" put Elixir syntax on a wire ADR-0005 calls language-neutral
- the one place that happens, with session.halted's closed lowercase
set produced directly beside it.

The record decides the shape rather than shipping it: one additive
kind field carrying a structurally derived lowercase token, with
reason left byte-identical as the human overflow. That keeps the
change purely additive, where mapping the closed set in place would
be a changed value under an existing key, and it answers the bead's
ask for a way to tell a known reason from an opaque one without
string-sniffing.

Consistent with ADR-0014 by construction: the same kind derivation and
the same inspect/1-beside-a-token split, closing the coupling that
record left open. kind is never projected and reason keeps its
existing unconditional redaction, so a projected stream gains an
outcome it could not previously be told.

Two findings are recorded in it. The OTP crash shape {exception,
stacktrace} classifies as "unknown", so the most interesting failure
gets the least specific token. And the bead's first acceptance
criterion selects the option this record rejects, so flipping the
Status also re-scopes it.

No code, no docs/wire-format.md edit, no changelog fragment, and no
implementing bead: all four follow the operator's flip. sui-4lr and
sui-v8o are cross-referenced as sibling wire decisions.

Refs: sui-2s4
@johnnyt

johnnyt commented Sep 2, 2026

Copy link
Copy Markdown
Member Author

Direction-agent verdict (campaign 027): UNQUALIFIED, 0 findings - merges AS PROPOSED; the accept flip is the operator's. Scope 503 added / 0 removed plus one index row; shape matches ADR-0014; Status proposed; the ADR-0005 and ADR-0014 consistency claims verified; every cite resolves; terminology scan exit 1 with a positive control. For the flip: the record openly rejects the bead's first acceptance criterion (Option A, mapping in place) in favour of an additive kind key (Option B), so accepting it also re-scopes that criterion. Non-qualifying tidy-ups: the 'eight lines below' cite gap is sixteen; 'verbatim' should read 'identical in effect'.

@johnnyt
johnnyt merged commit 1a03952 into main Sep 2, 2026
1 check passed
@johnnyt
johnnyt deleted the sui-2s4-terminated-reason branch September 2, 2026 14:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant