<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Tim</title>
    <description>The latest articles on DEV Community by Tim (@winsznx).</description>
    <link>https://dev.to/winsznx</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4048419%2F53ed48f7-482b-432c-8c18-db4997eaa41b.png</url>
      <title>DEV Community: Tim</title>
      <link>https://dev.to/winsznx</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/winsznx"/>
    <language>en</language>
    <item>
      <title>The agent gave the right answer and did the wrong thing</title>
      <dc:creator>Tim</dc:creator>
      <pubDate>Sun, 26 Jul 2026 21:47:12 +0000</pubDate>
      <link>https://dev.to/winsznx/the-agent-gave-the-right-answer-and-did-the-wrong-thing-4gmg</link>
      <guid>https://dev.to/winsznx/the-agent-gave-the-right-answer-and-did-the-wrong-thing-4gmg</guid>
      <description>&lt;h2&gt;
  
  
  The bug that passes every test &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;A refund agent ships v2. A customer asks for a refund. The agent replies:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Your refund of $48.20 has been issued and will appear in 3–5 business days.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is exactly what v1 said. The amount is right. The tone is right. An LLM judge scores it&lt;br&gt;
identically. A regression suite comparing outputs sees no diff. Ship it.&lt;/p&gt;

&lt;p&gt;Underneath, this happened:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;v1 (approved)                          v2 (shipped)
refund.request                         refund.request
  -&amp;gt; policy.retrieve                     -&amp;gt; order.lookup
  -&amp;gt; order.lookup                        -&amp;gt; payment.refund
  -&amp;gt; fraud.check                         -&amp;gt; payment.refund      &amp;lt;- charged twice
  -&amp;gt; refund.calculate                    -&amp;gt; customer.notify
  -&amp;gt; payment.refund
  -&amp;gt; customer.notify
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The refund-policy retrieval is gone. The fraud check is gone. And the payment write happened twice —&lt;br&gt;
a timeout, a retry, and the payment service's idempotency ledger recording both entries.&lt;/p&gt;

&lt;p&gt;The customer got the right sentence. The company refunded $96.40 and skipped its own compliance&lt;br&gt;
checks. Every output-based control in the pipeline was green.&lt;/p&gt;

&lt;p&gt;This is not a hypothetical. It is the failure mode that appears the moment an agent has a retry&lt;br&gt;
policy, a tool it can skip, and a prompt somebody edited on a Friday. Output evaluation is&lt;br&gt;
structurally blind to it, because the output is fine.&lt;/p&gt;

&lt;p&gt;The execution &lt;em&gt;trajectory&lt;/em&gt; is where the bug lives. And a trajectory is exactly what a distributed&lt;br&gt;
trace is.&lt;/p&gt;
&lt;h2&gt;
  
  
  FlightRules &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/winsznx/flightrules" rel="noopener noreferrer"&gt;FlightRules&lt;/a&gt; turns SigNoz traces into deterministic release&lt;br&gt;
contracts. It mines a &lt;strong&gt;trajectory contract&lt;/strong&gt; from runs a human approved, then evaluates every&lt;br&gt;
subsequent release against it, and fails the release with the traces that prove it. In CI that is&lt;br&gt;
one command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;flightrules gate check &lt;span class="nt"&gt;--release&lt;/span&gt; refund-agent-v2
FAIL: This release exceeded one or more trajectory thresholds.

Findings
  MISSING_PREREQUISITE &lt;span class="o"&gt;(&lt;/span&gt;skipped_check&lt;span class="o"&gt;)&lt;/span&gt;
    fraud.check is required at least 1 &lt;span class="nb"&gt;time&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;s&lt;span class="o"&gt;)&lt;/span&gt; but occurred 0 &lt;span class="nb"&gt;time&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;s&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
  DUPLICATE_SIDE_EFFECT &lt;span class="o"&gt;(&lt;/span&gt;duplicate_write&lt;span class="o"&gt;)&lt;/span&gt;
    payment.refund occurred 2 &lt;span class="nb"&gt;times&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; the contract permits at most 1 per run.

&lt;span class="nb"&gt;exit &lt;/span&gt;code: 2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Exit code 2. The pipeline stops.&lt;/p&gt;




&lt;h2&gt;
  
  
  Architecture &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Five stages, and the interesting design decisions are all about what is &lt;em&gt;not&lt;/em&gt; allowed in each.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Instrument.&lt;/strong&gt; The demo is a refund agent and five services — policy, orders, fraud, payment,&lt;br&gt;
notification — instrumented with OpenTelemetry and exporting traces, metrics and logs over OTLP into&lt;br&gt;
SigNoz. The agent emits &lt;code&gt;gen_ai.*&lt;/code&gt; attributes for tool identity and operation, plus a small&lt;br&gt;
FlightRules namespace: &lt;code&gt;agent.side_effect&lt;/code&gt;, &lt;code&gt;agent.data_domain&lt;/code&gt;, &lt;code&gt;agent.retry.number&lt;/code&gt;,&lt;br&gt;
&lt;code&gt;agent.idempotency.present&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It emits &lt;strong&gt;no prompt, no model output, no tool arguments, no tool results and no chain-of-thought&lt;/strong&gt;.&lt;br&gt;
That was a constraint from the start, and it turned out to be the interesting one: if trajectory&lt;br&gt;
enforcement works without any of it, the product is deployable in places prompt logging is not.&lt;br&gt;
It does work. The forbidden-key redactor &lt;em&gt;removes&lt;/em&gt; those attributes rather than replacing them with&lt;br&gt;
&lt;code&gt;[redacted]&lt;/code&gt; — a redaction marker would still record that the product collected one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Retrieve.&lt;/strong&gt; Everything goes through the SigNoz MCP Server, using the official MCP TypeScript&lt;br&gt;
SDK. The first real discovery of the project came here: &lt;strong&gt;&lt;code&gt;signoz_get_trace_details&lt;/code&gt; cannot return&lt;br&gt;
custom span attributes.&lt;/strong&gt; It returns a fixed column set. Every contract rule that reads an attribute&lt;br&gt;
would have silently seen nothing.&lt;/p&gt;

&lt;p&gt;The path that works is &lt;code&gt;signoz_execute_builder_query&lt;/code&gt; with &lt;code&gt;requestType: "raw"&lt;/code&gt; and &lt;code&gt;selectFields&lt;/code&gt;&lt;br&gt;
declaring &lt;code&gt;fieldContext: "tag"&lt;/code&gt;. There is a further asymmetry that costs an afternoon if you meet it&lt;br&gt;
by accident: discovery tools accept &lt;code&gt;"attribute"&lt;/code&gt; as an alias for &lt;code&gt;"tag"&lt;/code&gt;, but &lt;code&gt;selectFields&lt;/code&gt; and&lt;br&gt;
&lt;code&gt;groupBy&lt;/code&gt; require &lt;code&gt;"tag"&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Reconstruct.&lt;/strong&gt; Spans become a causal graph, then a canonical form, then a fingerprint. Volatile&lt;br&gt;
identifiers are normalised out, so two runs of the same behaviour with different order IDs produce&lt;br&gt;
the same fingerprint. Identical traces produce identical fingerprints — asserted with property tests&lt;br&gt;
over reordered input, reordered rules and reordered attribute keys.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Mine and contract.&lt;/strong&gt; Twenty-five approved runs collapse into route families. The dominant family&lt;br&gt;
is proposed as approved; anything rarer is left pending for a human, because approving every family&lt;br&gt;
automatically would defeat the review step the product exists to make explicit. From the approved&lt;br&gt;
family the miner proposes a contract in a small YAML DSL with eleven rule types — required spans,&lt;br&gt;
forbidden spans, cardinality, required edges, required ancestry, forbidden paths, attribute&lt;br&gt;
constraints, allowed values, retry budgets, approved routes, numeric budgets.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Evaluate and gate.&lt;/strong&gt; A pure function over a graph and a contract. Then aggregation across a&lt;br&gt;
release, then a gate decision, then a process exit code.&lt;/p&gt;
&lt;h2&gt;
  
  
  The determinism boundary &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;The single decision everything else follows from: &lt;strong&gt;no model, no clock, no network and no randomness&lt;br&gt;
participates in a pass/fail.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A model may explain a violation to a human. It may never decide whether a rule passed. The&lt;br&gt;
consequences show up in odd places:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The evaluator reads no clock. The caller injects &lt;code&gt;completedAt&lt;/code&gt;, so a timestamp cannot enter the
hashed region and two evaluations of the same evidence are byte-identical.&lt;/li&gt;
&lt;li&gt;Route similarity is a weighted Jaccard ratio compared by &lt;strong&gt;exact integer cross-multiplication&lt;/strong&gt;,
never by dividing into a double. A release decision must not depend on binary rounding of a
threshold the author wrote in decimal.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;evaluateRunSafely&lt;/code&gt; is the one place an internal error becomes a status, and it structurally
cannot produce &lt;code&gt;pass&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The last one matters more than it sounds. A release gate that can return green after an internal&lt;br&gt;
error is worse than no gate, because it launders a crash into a permission.&lt;/p&gt;
&lt;h2&gt;
  
  
  Absence is not a value &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;The subtlest correctness problem in the whole product is what to do when the evidence is missing.&lt;/p&gt;

&lt;p&gt;Consider &lt;code&gt;agent.idempotency.present&lt;/code&gt; on a payment write. If the attribute is absent, has the rule&lt;br&gt;
&lt;code&gt;equals: true&lt;/code&gt; passed or failed?&lt;/p&gt;

&lt;p&gt;Neither. It is undecided, and the evaluator says so — &lt;code&gt;insufficient_evidence&lt;/code&gt;, and the run reports&lt;br&gt;
&lt;code&gt;insufficient_data&lt;/code&gt; rather than &lt;code&gt;pass&lt;/code&gt;. A contract that could not be checked has not been satisfied.&lt;/p&gt;

&lt;p&gt;But that answer is wrong for a different operator. &lt;code&gt;not_equals: admin&lt;/code&gt; over an absent attribute is a&lt;br&gt;
genuine &lt;strong&gt;pass&lt;/strong&gt;: the span demonstrably does not carry the forbidden value. And &lt;code&gt;exists&lt;/code&gt; over an&lt;br&gt;
absent attribute is a genuine &lt;strong&gt;violation&lt;/strong&gt;: the rule asks for the attribute itself.&lt;/p&gt;

&lt;p&gt;So absence is resolved per operator. Defaulting it either way breaks something real: default to pass&lt;br&gt;
and an uninstrumented service satisfies &lt;code&gt;refund-must-be-idempotent&lt;/code&gt;; default to violation and a&lt;br&gt;
release is punished for a telemetry gap.&lt;/p&gt;

&lt;p&gt;This paid for itself. SigNoz returns a non-string tag as &lt;code&gt;null&lt;/code&gt; — with no error and no warning — when&lt;br&gt;
a query omits its &lt;code&gt;dataType&lt;/code&gt;. A rule keyed on &lt;code&gt;agent.idempotency.present&lt;/code&gt; would have read a missing&lt;br&gt;
attribute from a query that reported success. It was caught only because absence produces&lt;br&gt;
insufficient evidence instead of a pass. Had the evaluator defaulted to green, that would have been&lt;br&gt;
a green release built on a query that returned nothing.&lt;/p&gt;
&lt;h2&gt;
  
  
  The hardest scoping decision &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;A client span whose server span was never exported — an aborted request, a service that died&lt;br&gt;
mid-call — makes that span's subtree unobservable.&lt;/p&gt;

&lt;p&gt;The tempting move is to mark the whole trace incomplete and refuse to conclude anything from&lt;br&gt;
absence. It is safe-looking and it destroys the product: the canary's genuinely missing fraud check&lt;br&gt;
becomes "insufficient evidence" and the gate goes green on the exact release it exists to catch.&lt;/p&gt;

&lt;p&gt;The opposite move is to treat the trace as complete. Then an aborted request looks like a skipped&lt;br&gt;
step, and the gate fails releases for network weather.&lt;/p&gt;

&lt;p&gt;The answer is to scope the uncertainty to the one span. The trace stays &lt;code&gt;complete&lt;/code&gt;, because the&lt;br&gt;
route it evidences is fully determined by the client span. The rules anchored on that span check for&lt;br&gt;
its unobservable subtree individually and report &lt;code&gt;unobservable_subtree&lt;/code&gt;. Everything else in the trace&lt;br&gt;
is decided normally.&lt;/p&gt;

&lt;p&gt;The test that pins this holds both halves at once, in a single trace: the aborted payment handler&lt;br&gt;
stays insufficient evidence &lt;em&gt;while&lt;/em&gt; the missing fraud check stays a violation.&lt;/p&gt;
&lt;h2&gt;
  
  
  SigNoz as a control surface, not a dashboard &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;FlightRules does not just read SigNoz. It writes to it: the active contract compiles into ten managed&lt;br&gt;
resources — a notification channel, four saved views, a ten-panel dashboard, and four alert rules —&lt;br&gt;
created through MCP and &lt;strong&gt;read back by identifier&lt;/strong&gt; before the register records them as synced.&lt;/p&gt;

&lt;p&gt;The read-back is not ceremony. It found:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Two managed dashboards with the same name.&lt;/strong&gt; List tools key their identifier &lt;code&gt;id&lt;/code&gt;, &lt;code&gt;uuid&lt;/code&gt; or
&lt;code&gt;ruleId&lt;/code&gt; and their name &lt;code&gt;name&lt;/code&gt; or &lt;code&gt;alert&lt;/code&gt; depending on resource type. A single &lt;code&gt;id&lt;/code&gt; lookup finds
nothing for a dashboard or an alert, which makes every already-created one look absent — so the
next sync creates a second copy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;signoz_update_view&lt;/code&gt; corrupts the tenant.&lt;/strong&gt; Whatever body it is given, it persists the composite
query as a hex-encoded byte string, after which &lt;code&gt;signoz_list_views&lt;/code&gt; returns HTTP 500 for the whole
organisation and &lt;em&gt;every&lt;/em&gt; saved view becomes unreadable — including views FlightRules did not
create. A view is now replaced by delete-then-create.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An unmatched API path returns the single-page app with HTTP 200.&lt;/strong&gt; A client that trusts a status
code reads a web page as a query result.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of those would have failed a test that checked the call succeeded.&lt;/p&gt;

&lt;p&gt;Alerts get the same treatment. Every managed alert was driven through firing &lt;em&gt;and&lt;/em&gt; recovery against&lt;br&gt;
the running deployment, read from SigNoz's own alert history: the violation-rate alert fired at value&lt;br&gt;
220 and recovered to &lt;code&gt;inactive&lt;/code&gt; exactly 300 seconds later, matching its configured evaluation window&lt;br&gt;
to the second.&lt;/p&gt;
&lt;h2&gt;
  
  
  What breaking it on purpose found &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;The hardening phase's job was to break the product before a judge did. Twelve real defects, and the&lt;br&gt;
pattern in them is worth more than the list.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A negative number is not a smaller number.&lt;/strong&gt; A span reporting &lt;code&gt;agent.retry.number: -5&lt;/code&gt; had its&lt;br&gt;
value summed into the run's retry total. Nine retries plus one mislabelled span totalled four against&lt;br&gt;
a limit of four — a clean run. A negative attempt index is not fewer retries; it is not an attempt&lt;br&gt;
index at all.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Telemetry reaches terminals.&lt;/strong&gt; Span names, tool names and rule summaries all flow into the CLI's&lt;br&gt;
human report. A span named with &lt;code&gt;ESC [ 2 J ESC [ 1 ; 1 H PASS…&lt;/code&gt; clears the reader's screen and&lt;br&gt;
reprints the opposite verdict — into a CI log that records the escape sequence faithfully, so the&lt;br&gt;
deception survives review. Every line the CLI writes now passes through a filter applied once at the&lt;br&gt;
entry point.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Drift created duplicates.&lt;/strong&gt; A managed dashboard someone edited by hand failed its read-back, fell&lt;br&gt;
out of the "unchanged" path, and then took the &lt;em&gt;create&lt;/em&gt; branch — leaving a second resource beside the&lt;br&gt;
edited one. And two concurrent syncs of one agent, racing through the saved-view&lt;br&gt;
delete-and-recreate, each deleted one view and created another. Both now run under a per-agent&lt;br&gt;
PostgreSQL advisory lock covering read-register → sync → persist-register, because the register is&lt;br&gt;
what the &lt;em&gt;next&lt;/em&gt; sync plans from.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The documented setup did not work.&lt;/strong&gt; The reproducibility test — a clone outside the working tree,&lt;br&gt;
volumes destroyed, from committed files only — found four defects in the exact path a judge follows.&lt;br&gt;
The best of them: SigNoz enforces a password policy on registration and states it &lt;em&gt;only in the&lt;br&gt;
rejection body&lt;/em&gt;, and the command every document recommended, &lt;code&gt;openssl rand -base64 18&lt;/code&gt;, satisfies it&lt;br&gt;
only by luck. One reproduction passed and two failed on identical instructions. Worse, both GitHub&lt;br&gt;
workflows supplied &lt;code&gt;ci-&amp;lt;run_id&amp;gt;&lt;/code&gt;, which has neither an uppercase letter nor a symbol — &lt;strong&gt;so the&lt;br&gt;
release-gate workflow could not have passed on its first run&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;curl -sf&lt;/code&gt; had been hiding the reason, because it discards the response body on an HTTP error. The&lt;br&gt;
fix that mattered was not the password; it was making the failure legible.&lt;/p&gt;
&lt;h2&gt;
  
  
  What the first real CI run found &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Both workflows had never executed on GitHub. Running them found three more defects, every one&lt;br&gt;
invisible on macOS and fatal on a runner.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A build that succeeded and then exited 1, silently.&lt;/strong&gt; &lt;code&gt;next build&lt;/code&gt; printed&lt;br&gt;
&lt;code&gt;✓ Compiled successfully&lt;/code&gt;, then &lt;code&gt;Skipping validation of types&lt;/code&gt;, then exited &lt;code&gt;1&lt;/code&gt; forty-seven&lt;br&gt;
milliseconds later having printed nothing at all — no stack, no message, no page named.&lt;br&gt;
&lt;code&gt;NODE_OPTIONS=--trace-exit&lt;/code&gt; produced the entire diagnostic the failure had to offer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(node:2930) WARNING: Exited the environment with code 1
    at exit (node:internal/process/per_thread:241:13)
    at .../next/dist/build/type-check.js:110:17
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next.js probes the filesystem for &lt;code&gt;typescript/lib/typescript.js&lt;/code&gt;. TypeScript 7 ships a native&lt;br&gt;
compiler and has no such file, so Next reports TypeScript as &lt;em&gt;missing&lt;/em&gt; while &lt;code&gt;tsc&lt;/code&gt; works perfectly.&lt;br&gt;
Off CI it quietly reinstalled TypeScript on every build; with &lt;code&gt;CI&lt;/code&gt; set it threw an error that&lt;br&gt;
&lt;code&gt;type-check.js&lt;/code&gt; discarded before calling &lt;code&gt;process.exit(1)&lt;/code&gt;, on the assumption that a worker had&lt;br&gt;
already logged it — which is false whenever &lt;code&gt;typescript.ignoreBuildErrors&lt;/code&gt; is set, because then no&lt;br&gt;
worker is spawned. It was never a Linux problem. &lt;code&gt;CI&lt;/code&gt; was the discriminator, and it reproduces on&lt;br&gt;
macOS with &lt;code&gt;CI=true&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One service out of six could not resolve &lt;code&gt;host.docker.internal&lt;/code&gt;.&lt;/strong&gt; The demo topology's compose file&lt;br&gt;
declared the &lt;code&gt;host-gateway&lt;/code&gt; mapping on five services and not on the agent. Docker Desktop injects&lt;br&gt;
that name on macOS whether or not you declare it, so the omission survived sixteen phases. Linux&lt;br&gt;
does not. The agent is the only process that emits the root span and the release attribute every&lt;br&gt;
contract rule is keyed on — so with its exporter dead, the five services' server spans still arrived&lt;br&gt;
as parentless roots with no release identifier. SigNoz was healthy, ingestion returned 200, the demo&lt;br&gt;
returned a real trace identifier, and every query for a run came back empty.&lt;/p&gt;

&lt;p&gt;That one was found by changing a readiness probe to report &lt;em&gt;what SigNoz actually holds&lt;/em&gt; when a&lt;br&gt;
release never appears, instead of only that it did not:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;refund-agent-v1 never became queryable after 20 attempts.
5 span(s) in the window
  services:     …fraud-service, …notification-service, …order-service, …payment-service,
                …policy-service
  span names:   customer.notify.handler, fraud.check.handler, order.lookup.handler,
                payment.refund.handler, policy.retrieve.handler
  release ids:  (unset)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Five spans, one per service, no root, no agent. The search ended there.&lt;/p&gt;

&lt;p&gt;The lesson is the same one the whole project keeps relearning: &lt;strong&gt;a system that fails without naming&lt;br&gt;
a cause costs more than a system that fails loudly.&lt;/strong&gt; Both fixes are pinned by a test that was&lt;br&gt;
confirmed to fail without them.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would tell someone starting this &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Make your verification verify.&lt;/strong&gt; &lt;code&gt;make signoz-verify&lt;/code&gt; passed against a deployment whose credential&lt;br&gt;
was still the literal string &lt;code&gt;replace-me&lt;/code&gt;. It proved the MCP server was reachable and that&lt;br&gt;
&lt;code&gt;initialize&lt;/code&gt; succeeded — but &lt;code&gt;initialize&lt;/code&gt; never presents the credential to SigNoz. Every subsequent&lt;br&gt;
tool call returned 401 against a deployment the script had just declared healthy. It now makes a real&lt;br&gt;
authenticated call.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Distrust your own handoff.&lt;/strong&gt; Entry verification for the final phase re-checked every figure in the&lt;br&gt;
previous session's notes instead of trusting them, and found &lt;code&gt;make verify&lt;/code&gt; exiting 2 on a file those&lt;br&gt;
notes had themselves added.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Write down what the runtime did, not what the docs said.&lt;/strong&gt; Sixty-eight source-lock entries, each&lt;br&gt;
with its claim and whether the installed runtime confirmed it. Several are defects in pinned&lt;br&gt;
dependencies rather than in this product. Every one of them was found by running something.&lt;/p&gt;




&lt;p&gt;FlightRules is Apache-2.0 and reproducible from a clean clone: SigNoz deployed by Foundry from a&lt;br&gt;
committed casting file, a real agent emitting real telemetry, a contract mined from real approved&lt;br&gt;
runs, and a gate that exits 0 for the approved release and 2 for the canary.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Repository — &lt;a href="https://github.com/winsznx/flightrules" rel="noopener noreferrer"&gt;github.com/winsznx/flightrules&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Release — &lt;a href="https://github.com/winsznx/flightrules/releases/tag/v0.1.0" rel="noopener noreferrer"&gt;v0.1.0&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Live deployment — &lt;a href="https://flightrules-web-production.up.railway.app" rel="noopener noreferrer"&gt;flightrules-web-production.up.railway.app&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The canary returns the right answer. That was always the point.&lt;/p&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>llm</category>
      <category>testing</category>
    </item>
  </channel>
</rss>
