Prove the error-span contract across the real failure paths #94
No reviewers
Labels
No labels
autonomy
async-consult
autonomy
epic
autonomy
headless
autonomy
live-collab
coherence-core
priority
P0
priority
P1
priority
P2
priority
P3
priority
P4
qa-fixture
role/ai
role/creator
role/design
role/director
role/engineer
role/exec
role/human
role/ops
role/qa
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
coilyco-flight-deck/agent-proxy!94
Loading…
Reference in a new issue
No description provided.
Delete branch "otel-error-span-path-coverage"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #71.
The finding
The issue's central code ask — every manual
record_exceptionalso marks its span as an error — was already satisfied when I picked this up. The catch-all baseline (d6e9d10, referenced by #74) centralized both operations intoapp.obs.record_error, so no call site can set one without the other.app/contains no otherrecord_exceptioncall. The issue's evidence section describesapp/resilience.pyandapp/upstream.pycallingrecord_exceptionon manually-created spans without setting status; that is no longer how the code is shaped.What was genuinely missing was the evidence.
tests/test_obs.pyexercised the helper in isolation, which cannot show whether the real dispatch paths route their failures through it — nor answer the criterion that actually constrains the design: "retried attempts remain individually visible without incorrectly marking a later successful request as failed."What changed
New
tests/test_error_spans.py, six tests over an in-memory span exporter:StatusCode.ERRORresponse_validation_failed, not a silent rerollupstream.chat_streamagainst a failing transportRecording turns out to be at attempt granularity, which is the right answer for #71's criterion: a transient blip stays visible in Error Management without turning a request that ultimately succeeded into a false alert.
docs/proxy.mdnow states that contract.Two things worth the reviewer's attention
The streaming test does not stub
chat_stream. Therecord_errorcall lives inside that function, so stubbing it would delete the code under test and leave the assertion passing vacuously. My first draft did exactly that and I caught it — it now drives a failing transport through the real function.My first draft of two tests failed, and the tests were wrong, not the code. A fake
UpstreamResultwithprompt_eval_count=1tripped the delivered-context detector (#33), which correctly recordedcontext_truncatedand marked an otherwise-clean span as an error. The_ok()helper now derivesprompt_eval_countfrom the proxy's own token count of the messages actually sent. Worth knowing because any future test that fabricates anUpstreamResultwill hit the same edge.Verification
pytest— 264 passedruff/black/mypy— cleanset_statuscall inapp/obs.pykills 5 of the 6 new tests. The redaction test correctly survives, since it does not depend on status. Reverted after the check.Human checkpoint — not claimed here
This issue is labelled
interactive, and its last acceptance criterion is an Ops verification: one synthetic transport failure appearing underservice.name=agent-proxyin SigNoz Error Management, linked to the expected trace. That needs a deployed build and a person looking at the tenant, so I have not marked it done. Everything else in the acceptance list is covered above.