Skip to content

feat(daemon): machine-readable reason on unknown verdicts (k8s phase+reason pattern)#474

Merged
proboscis merged 1 commit into
mainfrom
feat/unknown-status-reason
Jun 12, 2026
Merged

feat(daemon): machine-readable reason on unknown verdicts (k8s phase+reason pattern)#474
proboscis merged 1 commit into
mainfrom
feat/unknown-status-reason

Conversation

@proboscis

Copy link
Copy Markdown
Owner

Implements the owner-approved design from #467 review comment: a bare unknown is uninformative — the verdict now carries a machine-readable reason attribute, while the status vocabulary itself stays closed (it is the coupling core; discrimination is payload, exactly as k8s keeps phase tiny and puts reason beside it).

What

reason emitted by operator response
never_alive L3' grace expiry, both planes infra problem (binary/auth/mux env); fix host before retry
session_lost opencode session not found after dead checks backend observability triage
agent_exited capture verdict: exited, shell prompt showing check transcript/worktree; retry plausible
  • runEffect.Reason threads from the step() arms to the sanctioned writer; updateStatus records it via the new model.NewStatusEventWithReason (empty reason degrades to the plain event — no attrs entry)
  • StatusChangeEvent.Reason exposes it to listeners (Slack notifications become self-explanatory)
  • orch ps renders unknown(never_alive) inline; orch show's event list already prints reason=… via the generic attr format
  • The decision sites already logged the reason in prose — this wires the same information into the event log (SSOT) instead of throwing it away

Fence self-extension

run-status-write-surface and no-ignored-status-append now also ban NewStatusEventWithReason outside the sanctioned writer — adding the constructor without extending the fence would have opened a bypass. Fixture case added; negative-tested with a synthetic writer in internal/cli (1 finding, reverted).

Law doc

run-state-machine.md §5 gains a "Status reasons" table tied to L3'.

Verification

  • go build ./... clean; go test ./internal/...: daemon/model/cli green incl. 2 new tests (TestStepUnknownVerdictsCarryReason covering all four arms, TestUpdateStatusPersistsReason)
  • make lint: 106 rules, 0 findings; semgrep fixtures pass (10 guardrails matched)
  • Pre-existing, unrelated failures on clean main too: internal/monitor TestSessionNameForProject, test/integration tmux-dependent tests

🤖 Generated with Claude Code

…reason pattern)

The bare 'unknown' status conflates operationally distinct situations.
Every unknown verdict emitted by step() now carries a reason attribute
on the status event (model.AttrStatusReason):

- never_alive: boot grace expired with no life observed (both planes,
  L3') — infrastructure problem, retry futile until the host is fixed
- session_lost: opencode lost session observability after dead checks
- agent_exited: process exited with shell prompt showing — check the
  transcript/worktree, retry plausible

The status vocabulary itself stays closed (it is the coupling core);
discrimination travels as payload, the way Kubernetes keeps phase tiny
and puts reason beside it. The decision sites already logged the reason
in prose — this wires the same information into the event log (SSOT),
StatusChangeEvent.Reason for listeners, and orch ps, which now renders
unknown(never_alive) for direct triage.

Fence self-extension: run-status-write-surface and
no-ignored-status-append now also ban the new NewStatusEventWithReason
constructor outside the sanctioned writer (fixture case added;
negative-tested with a synthetic writer in internal/cli, reverted).

Law doc: run-state-machine.md §5 gains a 'Status reasons' table.

Tests: TestStepUnknownVerdictsCarryReason (all four arms),
TestUpdateStatusPersistsReason (attr persistence + empty-reason
degradation).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@proboscis proboscis merged commit f1d13fb into main Jun 12, 2026
3 checks passed
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