<?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: Yusuke Shiki</title>
    <description>The latest articles on DEV Community by Yusuke Shiki (@shikiyusuke).</description>
    <link>https://dev.to/shikiyusuke</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%2F4078263%2F82f5374c-930c-4704-8462-b1fb7ba36019.png</url>
      <title>DEV Community: Yusuke Shiki</title>
      <link>https://dev.to/shikiyusuke</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/shikiyusuke"/>
    <language>en</language>
    <item>
      <title>Tracking Codex Astra costs took more than updating a price table</title>
      <dc:creator>Yusuke Shiki</dc:creator>
      <pubDate>Sun, 06 Sep 2026 01:10:55 +0000</pubDate>
      <link>https://dev.to/shikiyusuke/tracking-codex-astra-costs-took-more-than-updating-a-price-table-3p3i</link>
      <guid>https://dev.to/shikiyusuke/tracking-codex-astra-costs-took-more-than-updating-a-price-table-3p3i</guid>
      <description>&lt;p&gt;I updated &lt;a href="https://github.com/shiki-yusuke/agent-cost" rel="noopener noreferrer"&gt;agent-cost&lt;/a&gt;, a CLI I maintain, to version 0.1.1 because I wanted to measure Codex's GPT-6 Astra usage too. It reads local Claude Code and Codex CLI logs and reports token usage and estimated costs, not actual billed amounts.&lt;/p&gt;

&lt;p&gt;Fast mode was where I ran into trouble.&lt;/p&gt;

&lt;p&gt;After adding Astra to the price table, I tested it with synthetic logs. Usage marked as Fast was still being priced at the Standard rate. The multiplier was in the catalog, but the log reader wasn't recognizing the setting.&lt;/p&gt;

&lt;p&gt;The fix involved both detecting the mode and deciding what to do when the logs didn't give me enough information.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the synthetic tests showed
&lt;/h2&gt;

&lt;p&gt;I used synthetic data with two million uncached input tokens, two million cached input tokens, and two million output tokens: six million tokens in total.&lt;/p&gt;

&lt;p&gt;Here are the results for Standard, Fast, and unknown settings, using the rate catalog bundled with 0.1.1. This is a test fixture, not real usage or a list of current official prices.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Setting identified from the log&lt;/th&gt;
&lt;th&gt;Estimated USD&lt;/th&gt;
&lt;th&gt;Estimated credits&lt;/th&gt;
&lt;th&gt;Unpriced tokens&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Standard&lt;/td&gt;
&lt;td&gt;122&lt;/td&gt;
&lt;td&gt;3,050&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fast&lt;/td&gt;
&lt;td&gt;305&lt;/td&gt;
&lt;td&gt;7,625&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unknown&lt;/td&gt;
&lt;td&gt;0*&lt;/td&gt;
&lt;td&gt;0*&lt;/td&gt;
&lt;td&gt;6,000,000&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;The zero in the unknown row means none of that usage could be priced. **It does not mean the usage was free.&lt;/em&gt;*&lt;/p&gt;

&lt;p&gt;The same token counts produce an estimate of $122 at Standard rates and $305 at Fast rates. During development, the Fast case was incorrectly returning the Standard estimate.&lt;/p&gt;

&lt;p&gt;The already-published 0.1.0 release didn't have Astra rates at all, so it treated Astra usage as unpriced. This was a problem found while implementing Astra support, not a report that 0.1.0 had shown users the wrong Fast amount.&lt;/p&gt;

&lt;p&gt;The changes are in &lt;a href="https://github.com/shiki-yusuke/agent-cost/pull/8" rel="noopener noreferrer"&gt;PR #8&lt;/a&gt;. The &lt;a href="https://github.com/shiki-yusuke/agent-cost/blob/48c2731c3336401470d9d9016a98f437390abeef/tests/fixtures/synthetic-codex-astra.jsonl" rel="noopener noreferrer"&gt;synthetic fixture&lt;/a&gt; and &lt;a href="https://github.com/shiki-yusuke/agent-cost/blob/48c2731c3336401470d9d9016a98f437390abeef/tests/test_astra_pricing.py" rel="noopener noreferrer"&gt;tests&lt;/a&gt; are public too. The table above summarizes test results; it isn't a copy of the CLI's output.&lt;/p&gt;

&lt;h2&gt;
  
  
  Does "not known to be Fast" mean Standard?
&lt;/h2&gt;

&lt;p&gt;I checked the public source for Codex 0.153.4.&lt;/p&gt;

&lt;p&gt;For this Astra support, agent-cost reads &lt;code&gt;service_tier&lt;/code&gt; as well as the model name. It checks that the settings belong to the same session and match the target turn and model. An explicit &lt;code&gt;default&lt;/code&gt; selects Standard; &lt;code&gt;priority&lt;/code&gt; selects Fast.&lt;/p&gt;

&lt;p&gt;But a setting can be missing. It can also conflict with the information recorded for a turn. In those cases, the mode isn't clear.&lt;/p&gt;

&lt;p&gt;Treating everything that isn't recognized as Fast as Standard would still produce a number. It would also assign Standard prices to usage whose settings the reader simply couldn't establish.&lt;/p&gt;

&lt;p&gt;Instead, &lt;strong&gt;usage stays &lt;code&gt;unpriced&lt;/code&gt; when the reader can't determine which rate applies&lt;/strong&gt;. Knowing Astra's rates doesn't establish whether a particular piece of usage was Standard or Fast.&lt;/p&gt;

&lt;p&gt;This is based on the request settings recorded in the log. It doesn't confirm the processing mode actually used by the server or what was billed. The source references and attribution rules are in the &lt;a href="https://github.com/shiki-yusuke/agent-cost/blob/48c2731c3336401470d9d9016a98f437390abeef/docs/astra-pricing.md#standardfast-attribution-request-based-estimate" rel="noopener noreferrer"&gt;implementation notes&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Keep the tokens even when the price is unknown
&lt;/h3&gt;

&lt;p&gt;Dropping an unpriced row would hide the fact that the usage happened. Keeping only a zero amount would make it look free.&lt;/p&gt;

&lt;p&gt;So the output carries &lt;code&gt;pricing_status&lt;/code&gt;, which describes the pricing state, and &lt;code&gt;unpriced_tokens&lt;/code&gt;, which counts tokens that couldn't be priced, separately from the amount.&lt;/p&gt;

&lt;p&gt;Checking those fields alongside the total shows whether any usage was left out of the calculation. The unknown row above doesn't just say $0: it preserves all six million tokens as unpriced.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;priced&lt;/code&gt; doesn't mean a confirmed charge either. It means the usage could be calculated using the selected rate catalog.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which usage gets the new setting when Fast is switched on?
&lt;/h2&gt;

&lt;p&gt;Settings can also change during a session.&lt;/p&gt;

&lt;p&gt;Suppose a session starts in Standard, uses Fast for the next turn, then switches back to Standard. Pricing the entire session with its final setting would treat the Fast portion as Standard too.&lt;/p&gt;

&lt;p&gt;When settings change between completed turns, the reader applies the new settings only to subsequent usage. The log contains cumulative token counts, so it takes the increase since the previous count and prices that increase using the turn's settings.&lt;/p&gt;

&lt;p&gt;An update while a turn is still running leaves an ambiguous interval: the reader can't safely assign that usage to the old or new settings. Those tokens stay &lt;code&gt;unpriced&lt;/code&gt; as well.&lt;/p&gt;

&lt;p&gt;This behavior was added in 0.1.1 for logs containing Astra. It doesn't fully reconstruct missing history, concurrent updates, or alternative storage formats. The supported scope is limited to the &lt;a href="https://github.com/shiki-yusuke/agent-cost/blob/48c2731c3336401470d9d9016a98f437390abeef/agent_cost/readers/codex.py" rel="noopener noreferrer"&gt;reader implementation&lt;/a&gt; and the cases checked with synthetic data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trying it locally
&lt;/h2&gt;

&lt;p&gt;With &lt;code&gt;uvx&lt;/code&gt; available, you can run the published 0.1.1 package with the commands below.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Astra coverage in this version:&lt;/strong&gt; the model ID must be exactly &lt;code&gt;gpt-6-astra&lt;/code&gt;, and usage must be at or after &lt;strong&gt;September 5, 2026, 17:18:23 UTC&lt;/strong&gt; (September 6, 02:18:23 JST). Earlier usage remains &lt;code&gt;unpriced&lt;/code&gt;, even when Standard or Fast can be identified.&lt;/p&gt;

&lt;p&gt;This is a catalog boundary based on when the rates were checked, not an official launch or tariff start time. This release also does not update every model's rates: known discrepancies in the GPT-5.6 family remain.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uvx &lt;span class="nt"&gt;--from&lt;/span&gt; coding-agent-cost&lt;span class="o"&gt;==&lt;/span&gt;0.1.1 agent-cost doctor
uvx &lt;span class="nt"&gt;--from&lt;/span&gt; coding-agent-cost&lt;span class="o"&gt;==&lt;/span&gt;0.1.1 agent-cost report
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;doctor&lt;/code&gt; checks things such as the configured input locations. &lt;code&gt;report&lt;/code&gt; actually reads the local logs and aggregates usage. Running &lt;code&gt;doctor&lt;/code&gt; alone doesn't verify that every log is valid.&lt;/p&gt;

&lt;p&gt;The CLI doesn't send the logs anywhere. The &lt;code&gt;uvx&lt;/code&gt; package runner may use the network to download the package. To see output before reading your own logs, use the README's &lt;a href="https://github.com/shiki-yusuke/agent-cost#synthetic-output-example" rel="noopener noreferrer"&gt;synthetic output example&lt;/a&gt;. That example remains pinned to 0.1.0 as a record of an earlier verification.&lt;/p&gt;

&lt;p&gt;For use from another program, there is JSON output and the &lt;code&gt;measure/v1&lt;/code&gt; interface. The output includes the rate catalog's version and SHA-256, so a result can be traced back to the catalog used to calculate it. The &lt;a href="https://github.com/shiki-yusuke/agent-cost" rel="noopener noreferrer"&gt;README&lt;/a&gt; covers installation requirements and the timezone-data caveat on Windows.&lt;/p&gt;

&lt;p&gt;The additional Astra checks used synthetic data. I haven't validated compatibility against real Astra logs or measured a cost reduction from using the tool. API-specific tariffs and actual charges under individual contracts are outside this calculation. The &lt;a href="https://github.com/shiki-yusuke/agent-cost/releases/tag/v0.1.1" rel="noopener noreferrer"&gt;0.1.1 release notes&lt;/a&gt; record the scope and verification results at publication.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where I landed
&lt;/h2&gt;

&lt;p&gt;An unknown price isn't a particularly satisfying result when you ran the tool to find out the cost.&lt;/p&gt;

&lt;p&gt;But assigning a Standard rate when the settings couldn't be read would hide that uncertainty from the person using the result. For this Astra update, I kept the tokens even when they couldn't be priced, so the output shows how much usage the calculation actually covers.&lt;/p&gt;




&lt;p&gt;This is the English version of my &lt;a href="https://zenn.dev/yusuke_shiki/articles/d7209850cb7a03" rel="noopener noreferrer"&gt;article on Zenn&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>opensource</category>
      <category>codex</category>
    </item>
    <item>
      <title>Before Asking an AI Coding Agent to Fix a Bug, Verify the Bug Exists</title>
      <dc:creator>Yusuke Shiki</dc:creator>
      <pubDate>Thu, 03 Sep 2026 02:25:58 +0000</pubDate>
      <link>https://dev.to/shikiyusuke/before-asking-an-ai-coding-agent-to-fix-a-bug-verify-the-bug-exists-5and</link>
      <guid>https://dev.to/shikiyusuke/before-asking-an-ai-coding-agent-to-fix-a-bug-verify-the-bug-exists-5and</guid>
      <description>&lt;p&gt;Before asking an AI coding agent to “fix this bug,” there is one question worth asking first:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does the bug actually exist?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;An issue says it does. A review points it out. Someone reports seeing it in another environment.&lt;/p&gt;

&lt;p&gt;Any of those is a good reason to investigate. None of them, by itself, proves that the problem currently exists exactly as described.&lt;/p&gt;

&lt;p&gt;It may already be fixed. It may only happen in a particular version or environment. The reported behavior may even be expected.&lt;/p&gt;

&lt;p&gt;As I have started relying more on AI coding agents, I have become more deliberate about checking this step.&lt;/p&gt;

&lt;h2&gt;
  
  
  A wrong premise can still produce a perfectly reasonable implementation
&lt;/h2&gt;

&lt;p&gt;Suppose you give an agent this task:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Problem X is happening. Find the cause and fix it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The agent can inspect the code, reason under the assumption that X exists, and produce a plausible fix.&lt;/p&gt;

&lt;p&gt;That is not necessarily an AI capability problem.&lt;/p&gt;

&lt;p&gt;If the input says “X is happening,” solving the problem under that premise is a reasonable thing to do.&lt;/p&gt;

&lt;p&gt;The part I want to question happens one step earlier.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Bug report
 ↓
Fix
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Bug report
 ↓
Does the reported problem actually occur?
 ↓
Fix
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before implementation, verify the premise the implementation depends on.&lt;/p&gt;

&lt;h2&gt;
  
  
  I had a review finding that changed after I measured it
&lt;/h2&gt;

&lt;p&gt;This happened publicly while I was working on &lt;a href="https://github.com/shiki-yusuke/spec-lane/pull/28" rel="noopener noreferrer"&gt;PR #28&lt;/a&gt; in my OSS project, &lt;code&gt;spec-lane&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The PR added a gate that can execute an external verification command. During review, a strong claim came up around timeout behavior:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Even with SIGKILL, the CLI timeout itself may hang.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If I had accepted that statement as the premise, the next task would have been straightforward: implement a fix for a hanging timeout.&lt;/p&gt;

&lt;p&gt;Instead, I measured it first.&lt;/p&gt;

&lt;p&gt;Under the three conditions I tested, I could not reproduce a call exceeding the configured deadline.&lt;/p&gt;

&lt;p&gt;That did not mean there was no problem.&lt;/p&gt;

&lt;p&gt;I found a different boundary.&lt;/p&gt;

&lt;p&gt;Even after the direct child process exits, a descendant process that still holds the inherited stdout/stderr pipes can affect how long &lt;code&gt;spawnSync&lt;/code&gt; waits.&lt;/p&gt;

&lt;p&gt;In one measurement, the direct child exited immediately while a grandchild remained alive for four seconds. The call took 3112 ms.&lt;/p&gt;

&lt;p&gt;But when the grandchild remained alive for ten seconds, a two-second timeout returned in 2035 ms, and a 0.5-second timeout returned in 501 ms.&lt;/p&gt;

&lt;p&gt;In the conditions I measured, the configured deadline still held.&lt;/p&gt;

&lt;p&gt;So these were two different statements:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Original strong claim:
SIGKILL can allow the timeout itself to hang past its deadline.

What I could actually observe:
Descendants can influence waiting and timeout reporting within the deadline.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;They sound similar, but they lead to different implementation work.&lt;/p&gt;

&lt;p&gt;I did not add a new “hang fix.”&lt;/p&gt;

&lt;p&gt;Instead, I changed the specification and tests to describe the boundary I could actually measure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Measuring the premise changed the problem I was about to implement.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;One important qualification: this incident was not automatically stopped by &lt;code&gt;spec-lane&lt;/code&gt;'s &lt;code&gt;premise_evidence&lt;/code&gt; gate. It was a manual review-and-measurement decision made while developing the project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Put “verify” between “reported” and “fix”
&lt;/h2&gt;

&lt;p&gt;I now think about the flow like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Issue / review finding
          ↓
Verify the premise
          ↓
      Reproduced?
       ↙       ↘
     No         Yes
     ↓           ↓
Stop or        Spec
re-scope         ↓
              Implement
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the issue reproduces, proceed with the specification and implementation.&lt;/p&gt;

&lt;p&gt;If it does not, do not force yourself—or an agent—to find a fix for the problem you expected to see.&lt;/p&gt;

&lt;p&gt;Sometimes the right answer is to stop.&lt;/p&gt;

&lt;p&gt;Sometimes, as in PR #28, the investigation reveals a different, measurable problem. In that case, change the scope to the problem that actually exists.&lt;/p&gt;

&lt;p&gt;This extra step matters more as implementation becomes cheaper.&lt;/p&gt;

&lt;h2&gt;
  
  
  spec-lane can record that decision before implementation
&lt;/h2&gt;

&lt;p&gt;I wanted this decision to exist in the development workflow rather than only in someone's memory.&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;spec-lane&lt;/code&gt; 0.9.0, an intent can contain an optional &lt;code&gt;premise_evidence&lt;/code&gt; record.&lt;/p&gt;

&lt;p&gt;For example, when you decide that a change requires premise verification but fail to reproduce the reported problem:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;premise_evidence&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;live&lt;/span&gt;
  &lt;span class="na"&gt;reproduced&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="na"&gt;evidence&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Reported&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;steps&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;were&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;executed&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;against&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;current&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;build,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;but&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;reported&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;behavior&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;was&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;not&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;observed."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, &lt;code&gt;required: true&lt;/code&gt; records the decision that premise verification applies to this change.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;reproduced: false&lt;/code&gt; records that the premise was not confirmed.&lt;/p&gt;

&lt;p&gt;With that current record, &lt;code&gt;spec-lane&lt;/code&gt; refuses the &lt;code&gt;1_intent -&amp;gt; 2_spec&lt;/code&gt; transition. A failed &lt;code&gt;lane advance&lt;/code&gt; does not modify &lt;code&gt;lane-state.json&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The purpose is not to have a CLI magically detect whether an AI is wrong.&lt;/p&gt;

&lt;p&gt;The purpose is simpler.&lt;/p&gt;

&lt;p&gt;Once the workflow has explicitly recorded “we could not confirm this problem,” do not silently continue into specification and implementation as if it had been confirmed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recorded evidence is not the same thing as truth
&lt;/h2&gt;

&lt;p&gt;There is an important limit to this mechanism.&lt;/p&gt;

&lt;p&gt;Suppose the file says:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;reproduced&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The CLI can verify that &lt;code&gt;true&lt;/code&gt; was recorded.&lt;/p&gt;

&lt;p&gt;It cannot verify that someone honestly reproduced the bug.&lt;/p&gt;

&lt;p&gt;The same applies to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;live&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The CLI can verify the field and its value. It cannot know whether somebody actually inspected a live system.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;spec-lane&lt;/code&gt; also enforces a minimum length for the evidence text. That is a structural threshold, not a quality score.&lt;/p&gt;

&lt;p&gt;A longer evidence string is not automatically better evidence.&lt;/p&gt;

&lt;p&gt;The boundary I use is:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;CLI proves recorded shape and transition behavior, not real-world truth.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The system can mechanically inspect the record and decide whether a transition is allowed.&lt;/p&gt;

&lt;p&gt;The truth of the real-world observation remains outside that guarantee.&lt;/p&gt;

&lt;h2&gt;
  
  
  Missing premise evidence does not automatically block a lane
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;premise_evidence&lt;/code&gt; itself is optional.&lt;/p&gt;

&lt;p&gt;If it is absent, the current CLI emits a warning rather than a hard error. A warning alone does not prevent the transition to the next phase.&lt;/p&gt;

&lt;p&gt;That is deliberate.&lt;/p&gt;

&lt;p&gt;The CLI cannot determine whether every change actually requires premise verification.&lt;/p&gt;

&lt;p&gt;A bug already observed directly by a human is different from an unverified report. A feature addition may not have a “does this bug exist?” premise at all.&lt;/p&gt;

&lt;p&gt;Making the CLI decide applicability on its own would introduce another form of false certainty.&lt;/p&gt;

&lt;p&gt;So &lt;code&gt;spec-lane&lt;/code&gt; starts enforcing after a human or agent has decided that premise verification applies and recorded the result.&lt;/p&gt;

&lt;p&gt;It is a mechanical backstop for an explicit decision, not an oracle.&lt;/p&gt;

&lt;h2&gt;
  
  
  Faster implementation makes the question before implementation more important
&lt;/h2&gt;

&lt;p&gt;AI coding agents have made the implementation step dramatically faster.&lt;/p&gt;

&lt;p&gt;That makes the decision immediately before implementation more consequential.&lt;/p&gt;

&lt;p&gt;What are we changing?&lt;/p&gt;

&lt;p&gt;Why are we changing it?&lt;/p&gt;

&lt;p&gt;Does the problem this change depends on actually occur?&lt;/p&gt;

&lt;p&gt;If that premise is wrong, faster implementation only lets us move in the wrong direction faster.&lt;/p&gt;

&lt;p&gt;So before handing an issue or review finding to an agent and saying “fix it,” I now want one more step:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does this problem actually exist?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If it reproduces, proceed.&lt;/p&gt;

&lt;p&gt;If it does not, stop.&lt;/p&gt;

&lt;p&gt;If the investigation reveals a different problem, change the scope to what you can actually observe.&lt;/p&gt;

&lt;p&gt;That is the role premise evidence plays in &lt;code&gt;spec-lane&lt;/code&gt;: preserve that decision before implementation and provide a mechanical backstop against ignoring an explicitly failed premise.&lt;/p&gt;

&lt;p&gt;Project: &lt;a href="https://github.com/shiki-yusuke/spec-lane" rel="noopener noreferrer"&gt;shiki-yusuke/spec-lane&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Japanese version: &lt;a href="https://zenn.dev/yusuke_shiki/articles/2c3bb57fe6f801" rel="noopener noreferrer"&gt;Zenn&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When you use AI coding agents, where in your workflow do you verify that the problem they are about to fix is real?&lt;/p&gt;

</description>
      <category>ai</category>
      <category>testing</category>
      <category>opensource</category>
      <category>programming</category>
    </item>
    <item>
      <title>A Real Deployment Exposed Two Contract Defects Before I Froze the Contract</title>
      <dc:creator>Yusuke Shiki</dc:creator>
      <pubDate>Wed, 26 Aug 2026 10:14:59 +0000</pubDate>
      <link>https://dev.to/shikiyusuke/a-real-deployment-exposed-two-contract-defects-before-i-froze-the-contract-2pao</link>
      <guid>https://dev.to/shikiyusuke/a-real-deployment-exposed-two-contract-defects-before-i-froze-the-contract-2pao</guid>
      <description>&lt;p&gt;A green CI run does not prove that the artifact built from reviewed source matches what production later exposes.&lt;/p&gt;

&lt;p&gt;The build job may have produced one directory while the deploy job selected another. A file may have been added after review. The live manifest and representative file bytes may match the digests sealed at build time—or they may not.&lt;/p&gt;

&lt;p&gt;I built &lt;a href="https://github.com/shiki-yusuke/release-evidence" rel="noopener noreferrer"&gt;release-evidence&lt;/a&gt; and its &lt;a href="https://github.com/shiki-yusuke/ai-agent-skills-playbook/tree/main/contracts/release-evidence/v0" rel="noopener noreferrer"&gt;v0 contract&lt;/a&gt; to connect those claims. It records source and build provenance, seals artifact digests, appends deployment events, and checks a bounded part of the live production target.&lt;/p&gt;

&lt;p&gt;But I did not freeze the first reviewed version of the contract.&lt;/p&gt;

&lt;p&gt;Deployment contracts are unusually good at lying while every synthetic fixture stays green. A&lt;br&gt;
schema can be internally consistent and still describe a topology that does not exist. Two digest&lt;br&gt;
fields can both look reasonable until an adapter has to decide which one to compute first.&lt;/p&gt;

&lt;p&gt;Keeping the contract in draft until a real adapter exercised it exposed two defects.&lt;/p&gt;
&lt;h2&gt;
  
  
  The goal was not another “build succeeded” record
&lt;/h2&gt;

&lt;p&gt;The first target was a static dashboard deployed through GitHub Pages.&lt;/p&gt;

&lt;p&gt;The evidence chain needed to support a narrower statement:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;reviewed source
  -&amp;gt; one built artifact
  -&amp;gt; that artifact deployed to production
  -&amp;gt; live manifest read back
     + representative file bytes spot-checked
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The draft contract used a sealed release bundle and an append-only event ledger. The bundle held&lt;br&gt;
the source commit and tree, toolchain evidence, and artifact digests. The ledger appended events&lt;br&gt;
such as &lt;code&gt;prepared&lt;/code&gt;, &lt;code&gt;deployed&lt;/code&gt;, and &lt;code&gt;verified&lt;/code&gt;; current state was derived by folding those events,&lt;br&gt;
not by editing a mutable status row.&lt;/p&gt;

&lt;p&gt;There were schemas, semantic checks, and accepted and rejected fixtures. The draft passed review.&lt;br&gt;
The pull request still said that v0 would remain unfrozen until a real adapter had exercised it.&lt;/p&gt;
&lt;h2&gt;
  
  
  Defect 1: the contract expected a preview that did not exist
&lt;/h2&gt;

&lt;p&gt;The first transition graph expected a preview environment before production.&lt;/p&gt;

&lt;p&gt;This scheduled GitHub Pages deployment has no real preview tier. The workflow builds the static&lt;br&gt;
dashboard and deploys it directly to the production Pages site.&lt;/p&gt;

&lt;p&gt;I could have labeled a CI workspace “preview” and made the ledger satisfy the graph. That would&lt;br&gt;
have been worse than an explicit gap: an evidence system would be changing the description of&lt;br&gt;
reality to pass its own validator.&lt;/p&gt;

&lt;p&gt;The contract changed instead.&lt;/p&gt;

&lt;p&gt;A direct &lt;code&gt;prepared -&amp;gt; deployed|production&lt;/code&gt; transition is now valid only when it carries&lt;br&gt;
&lt;code&gt;preview_skipped: true&lt;/code&gt; and a closed reason code. The same flag is rejected on the normal preview&lt;br&gt;
path. The absence of a preview is an explicit property of the target, not a silently missing event.&lt;/p&gt;

&lt;p&gt;GitHub Pages was not defective. The contract's assumption that every target had a preview was.&lt;/p&gt;
&lt;h2&gt;
  
  
  Defect 2: the manifest and bundle could not be finalized
&lt;/h2&gt;

&lt;p&gt;The static site exposes a &lt;code&gt;release-manifest.json&lt;/code&gt; containing a digest for each deployed path. A&lt;br&gt;
read-back can fetch the manifest and compare a live file with the value it records.&lt;/p&gt;

&lt;p&gt;The first design also made the release bundle carry the byte digest of that manifest, while the&lt;br&gt;
manifest carried the digest of the bundle:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;bundle
  contains sha256(release-manifest.json bytes)

release-manifest.json
  contains sha256(bundle)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The bundle required the finished manifest. The manifest required the finished bundle. Neither&lt;br&gt;
value could be computed first.&lt;/p&gt;

&lt;p&gt;The cycle looks obvious when written as two arrows. It was much less obvious while reviewing the&lt;br&gt;
two record shapes separately. It surfaced when the real adapter had to generate files in an actual&lt;br&gt;
order.&lt;/p&gt;

&lt;p&gt;The deployed manifest now contains only its schema version and content map:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"schema_version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"release-evidence/v0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"index.html"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sha256:..."&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It does not repeat the enclosing bundle digest. During production read-back, the adapter&lt;br&gt;
canonicalizes the fetched &lt;code&gt;content&lt;/code&gt;, computes its SHA-256 digest, compares that value with the&lt;br&gt;
static-site artifact in the sealed bundle, and spot-checks live file bytes against the manifest.&lt;/p&gt;

&lt;p&gt;Removing the mutual reference made the evidence flow one-directional.&lt;/p&gt;
&lt;h2&gt;
  
  
  I fixed both defects, then exercised production
&lt;/h2&gt;

&lt;p&gt;On August 22, 2026, the corrected adapter ran in the real workflow.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/shiki-yusuke/agent-metrics-harvester/actions/runs/32572501427" rel="noopener noreferrer"&gt;Workflow run 32572501427&lt;/a&gt;&lt;br&gt;
built the dashboard, deployed it to GitHub Pages, fetched the live manifest, compared its content&lt;br&gt;
map, spot-checked a representative live file, and persisted the evidence ledger.&lt;/p&gt;

&lt;p&gt;The ledger contains this sequence:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;prepared
  -&amp;gt; deployed | production | preview_skipped=true
  -&amp;gt; verified | production
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Recomputing the digest from the live manifest's canonicalized content produced the same value as&lt;br&gt;
the artifact sealed in the bundle:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sha256:35e4b7ee5a17ca64f0851e54d83385f2c478b4d04e3210a2e08639fecd3b6d0c
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Only after that receipt existed did the contract replace its synthetic dashboard fixture with the&lt;br&gt;
real deployed bundle and freeze &lt;code&gt;release-evidence/v0&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The order mattered:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;review the draft
  -&amp;gt; let a real adapter break it
  -&amp;gt; repair the contract
  -&amp;gt; verify a production read-back
  -&amp;gt; freeze
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Freezing first would have forced the adapter either to invent a preview or immediately violate the&lt;br&gt;
contract I had just declared stable.&lt;/p&gt;
&lt;h2&gt;
  
  
  The second success verified the repeat-run link
&lt;/h2&gt;

&lt;p&gt;After the first production exercise succeeded, I found another issue in the adapter. A later&lt;br&gt;
release should derive &lt;code&gt;previous_release_id&lt;/code&gt; from the last verified production event. The first&lt;br&gt;
implementation still needed that repeat-run chain logic.&lt;/p&gt;

&lt;p&gt;I keep this separate from the two contract defects. It did not change the transition model or the&lt;br&gt;
digest definition. It was an adapter defect revealed by asking what the second release must do.&lt;/p&gt;

&lt;p&gt;After that fix reached main, scheduled&lt;br&gt;
&lt;a href="https://github.com/shiki-yusuke/agent-metrics-harvester/actions/runs/32615972524" rel="noopener noreferrer"&gt;run 32615972524&lt;/a&gt;&lt;br&gt;
created a second production release from commit &lt;code&gt;6f0b5c8&lt;/code&gt;, which includes&lt;br&gt;
&lt;a href="https://github.com/shiki-yusuke/agent-metrics-harvester/pull/7" rel="noopener noreferrer"&gt;PR #7&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The new bundle records the link explicitly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;release_id:
  agent-metrics-dashboard@32615972524-1

previous_release_id:
  agent-metrics-dashboard@32572501427-1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first release was the preceding verified production release; its sealed bundle digest was&lt;br&gt;
&lt;code&gt;sha256:63392d1a635e68f670f07dcb717ad7e1437f8282be82a1dd8c0ab2c6a664c9e1&lt;/code&gt;.&lt;br&gt;
The second release recorded &lt;code&gt;prepared&lt;/code&gt; at &lt;code&gt;2026-08-23T03:41:48.031Z&lt;/code&gt;, &lt;code&gt;deployed | production&lt;/code&gt;&lt;br&gt;
at &lt;code&gt;2026-08-23T03:42:20.467Z&lt;/code&gt;, and &lt;code&gt;verified | production&lt;/code&gt; at&lt;br&gt;
&lt;code&gt;2026-08-23T03:42:20.659Z&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Its ledger persisted the same three-event sequence under one sealed bundle digest:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;prepared
  -&amp;gt; deployed | production | preview_skipped=true
  -&amp;gt; verified | production
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The sealed bundle digest is&lt;br&gt;
&lt;code&gt;sha256:7dabd0a36be4308cb1c70387d1097dcb37c7fde3be3aee991f8287371bc661df&lt;/code&gt;.&lt;br&gt;
Recomputing the static-site digest from the live manifest content produced&lt;br&gt;
&lt;code&gt;sha256:ac69214db9909b1f1d97bd1f806214034172e0dac780e1c1470434f158b70d49&lt;/code&gt;,&lt;br&gt;
and the live &lt;code&gt;index.html&lt;/code&gt; bytes matched the manifest entry&lt;br&gt;
&lt;code&gt;sha256:ce99db76efd55bace6b50bd9ba88caa76e1dbef841c76e87dcb07b7d88380c9e&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Auditing the persisted collection against the frozen v0 contract reported two bundles, six&lt;br&gt;
events, and no problems. The repeat-run link is now observed in production. The order still&lt;br&gt;
matters: the first success did not prove that future linkage until a second release exercised it.&lt;/p&gt;
&lt;h2&gt;
  
  
  Two later runs preserved the verified chain
&lt;/h2&gt;

&lt;p&gt;Two subsequent scheduled runs created production releases from the same PR #7 merge commit.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Run&lt;/th&gt;
&lt;th&gt;Release ID&lt;/th&gt;
&lt;th&gt;Previous verified release&lt;/th&gt;
&lt;th&gt;Sealed bundle digest&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/shiki-yusuke/agent-metrics-harvester/actions/runs/32687508604" rel="noopener noreferrer"&gt;32687508604&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;agent-metrics-dashboard@32687508604-1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;agent-metrics-dashboard@32615972524-1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;sha256:fd2ee5b9ef6d531b1c377257a60ced576a949964be186fd9b1c6337278b2a8a4&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/shiki-yusuke/agent-metrics-harvester/actions/runs/32806015116" rel="noopener noreferrer"&gt;32806015116&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;agent-metrics-dashboard@32806015116-1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;agent-metrics-dashboard@32687508604-1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;sha256:94d9f3ee15e2238eb84dcae6dbe26bb4f69afd434ce8a1491be636e24d40b8e0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The first follow-up chain recorded &lt;code&gt;prepared&lt;/code&gt; at &lt;code&gt;2026-08-24T03:45:20.910Z&lt;/code&gt;, &lt;code&gt;deployed |&lt;br&gt;
production&lt;/code&gt; at &lt;code&gt;2026-08-24T03:45:54.808Z&lt;/code&gt;, and &lt;code&gt;verified | production&lt;/code&gt; at&lt;br&gt;
&lt;code&gt;2026-08-24T03:45:55.135Z&lt;/code&gt;. The second recorded the same sequence at&lt;br&gt;
&lt;code&gt;2026-08-25T03:40:22.482Z&lt;/code&gt;, &lt;code&gt;2026-08-25T03:40:53.785Z&lt;/code&gt;, and&lt;br&gt;
&lt;code&gt;2026-08-25T03:40:53.929Z&lt;/code&gt;. Within each chain, all three events carry the same sealed bundle&lt;br&gt;
digest shown in the table. The Actions logs also record &lt;code&gt;read-back verified&lt;/code&gt; against the&lt;br&gt;
corresponding static-site artifact digest.&lt;/p&gt;

&lt;p&gt;These runs add evidence that the &lt;code&gt;previous_release_id&lt;/code&gt; chain and read-back continued to operate.&lt;br&gt;
They do not prove that every future deployment will succeed, that every deployment is safe, or&lt;br&gt;
that an external user adopted the tool.&lt;/p&gt;
&lt;h2&gt;
  
  
  Matching digests do not prove everything
&lt;/h2&gt;

&lt;p&gt;The integrity level of &lt;code&gt;release-evidence/v0&lt;/code&gt; is &lt;code&gt;digest_only&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;There is no signature, SLSA, or SBOM claim. GitHub Actions credentials, pinned actions, the tool&lt;br&gt;
checkout, and the production host remain inside the trust boundary.&lt;/p&gt;

&lt;p&gt;A successful read-back shows that the live manifest's content map matched the static-site&lt;br&gt;
artifact digest sealed in the bundle, and that a representative live file matched its manifest&lt;br&gt;
entry at verification time.&lt;/p&gt;

&lt;p&gt;It does not show that every production file was fetched and compared byte for byte, or that the&lt;br&gt;
host cannot change afterward.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;content integrity
!= semantic correctness

digest match
!= complete supply-chain security

successful read-back
!= future correctness
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The v0 ledger assumes one writer. The first adapter is specific to a GitHub Pages/static-site&lt;br&gt;
deployment. It is not a universal release database or an automatic promotion system.&lt;/p&gt;

&lt;p&gt;Stating those limits does not weaken the receipt. It prevents a content-digest proof from silently&lt;br&gt;
turning into a broader supply-chain claim.&lt;/p&gt;

&lt;h2&gt;
  
  
  Make reality answer before the contract becomes permanent
&lt;/h2&gt;

&lt;p&gt;Schema review and synthetic fixtures were necessary. They remain valuable regression guards after&lt;br&gt;
the fixes.&lt;/p&gt;

&lt;p&gt;They were not enough to justify freezing a deployment contract.&lt;/p&gt;

&lt;p&gt;Real targets have topology that diagrams omit. Artifacts have generation order that isolated&lt;br&gt;
schemas hide. Production read-back crosses boundaries that a local build never touches.&lt;/p&gt;

&lt;p&gt;The point was not to delay stability. It was to keep the contract breakable until the last cheap&lt;br&gt;
moment to discover that it described something impossible.&lt;/p&gt;

&lt;p&gt;The real adapter found a preview environment that did not exist and a digest cycle that could not&lt;br&gt;
be computed. Both were contract problems, not cosmetic implementation details.&lt;/p&gt;

&lt;p&gt;Before freezing a deployment-evidence schema, I now want one answer from the production path:&lt;br&gt;
can an honest adapter actually satisfy this contract without inventing state?&lt;/p&gt;

&lt;p&gt;For &lt;code&gt;release-evidence/v0&lt;/code&gt;, asking that question before the freeze is what found the two defects.&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;AI-assistance disclosure&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I used ChatGPT and Codex to help structure, edit, and translate this article. I reviewed the release IDs, digests, timestamps, implementation details, and claim boundaries against the recorded evidence before publication.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>devops</category>
      <category>githubactions</category>
      <category>opensource</category>
      <category>testing</category>
    </item>
    <item>
      <title>I Could Measure Claude and Codex Usage. I Still Couldn't Honestly Assign It to a Task.</title>
      <dc:creator>Yusuke Shiki</dc:creator>
      <pubDate>Sat, 22 Aug 2026 21:20:35 +0000</pubDate>
      <link>https://dev.to/shikiyusuke/i-could-measure-claude-and-codex-usage-i-still-couldnt-honestly-assign-it-to-a-task-2ghj</link>
      <guid>https://dev.to/shikiyusuke/i-could-measure-claude-and-codex-usage-i-still-couldnt-honestly-assign-it-to-a-task-2ghj</guid>
      <description>&lt;p&gt;Once you use Claude Code or Codex for real work, a total usage number stops being enough. You want to know which change consumed it.&lt;/p&gt;

&lt;p&gt;I did not build &lt;code&gt;agent-cost&lt;/code&gt; because I had missed the existing token and cost trackers. I knew about multi-agent reporting CLIs, local dashboards, and OpenTelemetry-style observability stacks. I had even built a similar view in Notion before.&lt;/p&gt;

&lt;p&gt;The problem appeared when I tried to use that kind of reporting in an operational workflow. I needed agent logs to stay on the machine. I wanted a small runtime dependency surface, custom metrics I could audit, and a machine-readable result that another tool could consume. Most importantly, I needed session measurement and task attribution to remain two different claims.&lt;/p&gt;

&lt;p&gt;I did not need another universal dashboard. I needed a boundary underneath the dashboard that could answer: is this number supported well enough to enter task accounting?&lt;/p&gt;

&lt;h2&gt;
  
  
  A measurement layer below the UI
&lt;/h2&gt;

&lt;p&gt;Different tools optimize for different jobs. A broad CLI such as &lt;code&gt;ccusage&lt;/code&gt; is useful when coverage across agents matters. Local interfaces such as &lt;code&gt;token-tracker&lt;/code&gt; or &lt;code&gt;AgentMeter&lt;/code&gt; are a better fit for visual exploration of projects, sessions, subagents, and tools. An OpenTelemetry stack is the natural choice for fleet-level metrics, logs, and traces.&lt;/p&gt;

&lt;p&gt;Those are not inferior versions of &lt;code&gt;agent-cost&lt;/code&gt;. They serve different use cases and trust models.&lt;/p&gt;

&lt;p&gt;The layer I wanted looked like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;local observations
  -&amp;gt; auditable normalized facts
  -&amp;gt; explicit pricing status
  -&amp;gt; caller-selected sessions
  -&amp;gt; task-attribution policy
  -&amp;gt; optional dashboard / Notion / spec-lane
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;agent-cost&lt;/code&gt; reads logs that Claude Code and Codex CLI have already written locally. It normalizes each usage event into a fact with a model, token kind, timestamp, and count. At runtime it makes no network calls and declares no Python runtime dependencies. Its price catalog has a version and SHA-256 digest, both carried into machine-readable output.&lt;/p&gt;

&lt;p&gt;That “zero-network” claim is deliberately limited to runtime behavior. Installing from PyPI still means trusting a registry, installer, build backend, Python runtime, and operating system. The tool also needs access to the source logs. The design narrows runtime data egress and dependency surface; it does not make the supply chain disappear.&lt;/p&gt;

&lt;h2&gt;
  
  
  A session is observable. A task is another claim.
&lt;/h2&gt;

&lt;p&gt;There is an attractive shortcut when building task-level cost reports:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Measure usage in a time window.&lt;/li&gt;
&lt;li&gt;Find the issues or branches active during that window.&lt;/li&gt;
&lt;li&gt;Apportion the total by working time or commit count.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This always produces numbers that add up. But the agreement comes from the allocation rule, not from an observation.&lt;/p&gt;

&lt;p&gt;One session can cover several tasks. One task can span several sessions. A branch can stay unchanged while the operator investigates a different issue or reviews someone else's work. Elapsed time does not describe the computational weight of prompts and tool calls.&lt;/p&gt;

&lt;p&gt;The invariant I wanted was:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Session usage is observable. Session-to-task attribution is a separate claim.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;code&gt;agent-cost measure&lt;/code&gt; accepts only session IDs selected by its caller:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;agent-cost measure &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--session-id&lt;/span&gt; &amp;lt;session-a&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--session-id&lt;/span&gt; &amp;lt;session-b&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--format&lt;/span&gt; json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It does not infer a task from a branch, pull request, or timestamp. A workflow that already owns the task-to-session binding passes the corresponding session set.&lt;/p&gt;

&lt;p&gt;For example, the &lt;code&gt;spec-lane&lt;/code&gt; adapter invokes &lt;code&gt;agent-cost&lt;/code&gt; as a subprocess and checks the JSON, the &lt;code&gt;measure/v1&lt;/code&gt; protocol version, the schema, and forbidden personal dimensions. &lt;code&gt;agent-cost&lt;/code&gt; does not learn what the task is. The caller that knows the task selects the sessions.&lt;/p&gt;

&lt;p&gt;If a session crosses tasks and there is no defensible way to split it, I would rather leave that usage unattributed than manufacture a precise-looking allocation. Unknown is pending evidence, not zero.&lt;/p&gt;

&lt;h2&gt;
  
  
  The same rule applies to prices
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;agent-cost&lt;/code&gt; carries uncertainty instead of smoothing it away.&lt;/p&gt;

&lt;p&gt;An unknown model is &lt;code&gt;unpriced&lt;/code&gt;. A Claude cache write without a TTL breakdown is priced at the cheaper five-minute rate and labeled &lt;code&gt;lower_bound&lt;/code&gt;. Codex logs do not expose cache-write tokens, so the tool does not invent a zero-valued cache-write row. Malformed events, unreadable files, and decreasing cumulative counters remain visible in &lt;code&gt;data_quality&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;“Fail closed” does not mean every imperfect input crashes the command. It means unsupported pricing or attribution does not quietly become a confirmed value downstream.&lt;/p&gt;

&lt;p&gt;On August 23, 2026, I reran the published &lt;code&gt;coding-agent-cost 0.1.0&lt;/code&gt; package in temporary &lt;code&gt;uvx&lt;/code&gt; directories. Its doctor command found the local sources and loaded catalog version &lt;code&gt;2026-07-29&lt;/code&gt;. The explicit unknown-model path still rejected a made-up model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;uvx &lt;span class="nt"&gt;--refresh&lt;/span&gt; &lt;span class="nt"&gt;--from&lt;/span&gt; coding-agent-cost &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="go"&gt;    agent-cost rates show --model model-not-in-catalog
[unpriced] no rate entry for 'model-not-in-catalog'
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A numeric zero next to an unpriced row is not a claim that the usage was free. Consumers must inspect &lt;code&gt;pricing_status&lt;/code&gt; and &lt;code&gt;unpriced_tokens&lt;/code&gt;, then choose a policy: exclude the value from a headline, stop the workflow, or supply a verified catalog.&lt;/p&gt;

&lt;p&gt;The output field is &lt;code&gt;estimated_cost_usd&lt;/code&gt;, not a bill. Allowances, contracts, credits, and batch usage are not fully recoverable from local logs. The number is a list-price estimate attached to observed tokens.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the smaller boundary buys
&lt;/h2&gt;

&lt;p&gt;There are intentional limits. &lt;code&gt;agent-cost&lt;/code&gt; alone will not label a session as belonging to an issue. Local execution does not remove installation-time supply-chain risk or the need to trust local log access.&lt;/p&gt;

&lt;p&gt;In exchange, each layer has a narrower claim:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Local logs support session usage facts.&lt;/li&gt;
&lt;li&gt;A versioned catalog supports an estimated price.&lt;/li&gt;
&lt;li&gt;Unsupported prices remain &lt;code&gt;unpriced&lt;/code&gt; or &lt;code&gt;lower_bound&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The caller owns task binding as separate evidence.&lt;/li&gt;
&lt;li&gt;Ambiguous usage is not silently apportioned just to complete a total.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is not an argument against dashboards. Use a dashboard when visual exploration is the job. Use OpenTelemetry when fleet observability is the job. Use a small accounting primitive when you need a subprocess contract and want measurement to remain separate from attribution policy.&lt;/p&gt;

&lt;p&gt;Keeping an unknown visible is not a failure to measure. It is how the next layer avoids false confidence.&lt;/p&gt;

&lt;p&gt;Start with the 60-second path in &lt;a href="https://github.com/shiki-yusuke/agent-cost" rel="noopener noreferrer"&gt;&lt;code&gt;agent-cost&lt;/code&gt;&lt;/a&gt;. If you also need a workflow to own task attribution, see &lt;a href="https://github.com/shiki-yusuke/spec-lane" rel="noopener noreferrer"&gt;&lt;code&gt;spec-lane&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>claudecode</category>
      <category>codex</category>
      <category>ai</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Writing evidence-linked docs exposed two missing regression tests</title>
      <dc:creator>Yusuke Shiki</dc:creator>
      <pubDate>Fri, 14 Aug 2026 23:15:18 +0000</pubDate>
      <link>https://dev.to/shikiyusuke/writing-evidence-linked-docs-exposed-two-missing-regression-tests-3524</link>
      <guid>https://dev.to/shikiyusuke/writing-evidence-linked-docs-exposed-two-missing-regression-tests-3524</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a crosspost of the &lt;a href="https://github.com/shiki-yusuke/evidence-docs/blob/main/docs/articles/writing-evidence-linked-docs-exposed-two-missing-regression-tests.md" rel="noopener noreferrer"&gt;canonical version on GitHub&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Problem
&lt;/h2&gt;

&lt;p&gt;I run &lt;a href="https://github.com/shiki-yusuke/agent-cost" rel="noopener noreferrer"&gt;&lt;code&gt;agent-cost&lt;/code&gt;&lt;/a&gt;, a small open-source CLI that reads local Claude Code / Codex CLI usage logs and estimates token cost. I wanted documentation for it more trustworthy than a hand-written README: not prose claiming "the reader handles the TTL cache-write breakdown correctly," but claims that each point at the exact test or source line backing them, checked against real git history so the pointer can't silently go stale.&lt;/p&gt;

&lt;p&gt;To do that I used a second tool I've been building, &lt;a href="https://github.com/shiki-yusuke/evidence-docs" rel="noopener noreferrer"&gt;&lt;code&gt;evidence-docs&lt;/code&gt;&lt;/a&gt;, to build a &lt;strong&gt;claim corpus&lt;/strong&gt; against &lt;code&gt;agent-cost&lt;/code&gt;: 17 individual &lt;code&gt;observation&lt;/code&gt;s grouped into 5 &lt;code&gt;topic&lt;/code&gt;s (pricing-catalog validation rules, the cache-write lower-bound behavior, unpriced-fact handling, decimal arithmetic for money, and the &lt;code&gt;measure&lt;/code&gt; command's v1 contract). Each observation is one statement — a behavior, an invariant, a decision record — with a &lt;code&gt;claim_kind&lt;/code&gt;, an &lt;code&gt;epistemic_status&lt;/code&gt;, and one or more &lt;code&gt;provenance&lt;/code&gt; entries naming the exact file, test, or spec section it's backed by, plus a &lt;code&gt;content_digest&lt;/code&gt; of that source at a specific commit.&lt;/p&gt;

&lt;p&gt;Building that corpus is what surfaced this post's actual finding. To write an honest &lt;code&gt;provenance&lt;/code&gt; entry for a behavior claim, I had to point at the test that exercises it — reading the test, not just the source, for every claim. Doing that for the cache-write TTL breakdown logic and the pricing-status aggregation logic turned up two places where the claim I wanted to make ("the code does X in this case") was true by reading the code, but had &lt;strong&gt;no test asserting it&lt;/strong&gt;. Both went into &lt;code&gt;docs/claims/gaps.yaml&lt;/code&gt; as &lt;code&gt;GAP-01&lt;/code&gt; and &lt;code&gt;GAP-02&lt;/code&gt;, and both became small, scoped pull requests that added regression tests with zero implementation changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Why it mattered
&lt;/h2&gt;

&lt;p&gt;Neither gap was a live bug — that's the whole point of this post. &lt;code&gt;agent-cost&lt;/code&gt;'s existing test suite already covered the two extremes of each piece of logic; what was missing was the &lt;em&gt;middle&lt;/em&gt; case, the one that's easy to reason yourself past while staring at the code.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GAP-01&lt;/strong&gt; (&lt;code&gt;agent_cost/readers/claude.py&lt;/code&gt;, &lt;code&gt;parse_session_facts&lt;/code&gt;): when a Claude Code usage event's &lt;code&gt;cache_creation&lt;/code&gt; field is a dict, the reader computes &lt;code&gt;leftover = cache_creation_input_tokens - (ephemeral_5m + ephemeral_1h)&lt;/code&gt; and emits it as a &lt;code&gt;cache_write_unknown&lt;/code&gt; fact if positive. Existing tests covered a breakdown that exactly accounts for the total, and no breakdown at all — but not a breakdown dict that's &lt;em&gt;present and partial&lt;/em&gt;, where the two counters sum to less than the total. That's a real, reachable path with no assertion on what it produces.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GAP-02&lt;/strong&gt; (&lt;code&gt;agent_cost/aggregate.py&lt;/code&gt;, &lt;code&gt;_STATUS_RANK&lt;/code&gt; / &lt;code&gt;build_rows&lt;/code&gt;): a row's &lt;code&gt;pricing_status&lt;/code&gt; should be the worst status among its facts, ordered &lt;code&gt;unpriced&lt;/code&gt; (0) &amp;lt; &lt;code&gt;lower_bound&lt;/code&gt; (1) &amp;lt; &lt;code&gt;priced&lt;/code&gt; (2). The existing test only mixed an &lt;code&gt;unpriced&lt;/code&gt; fact with a &lt;code&gt;priced&lt;/code&gt; fact. No test built a row from a &lt;code&gt;lower_bound&lt;/code&gt; fact (e.g. a &lt;code&gt;cache_write_unknown&lt;/code&gt; token, priced as an explicit floor) mixed with a plain &lt;code&gt;priced&lt;/code&gt; fact to confirm the row lands on &lt;code&gt;lower_bound&lt;/code&gt;, not &lt;code&gt;priced&lt;/code&gt; — the half of the ordering that isn't "obviously" covered by the unpriced case.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If either had silently broken in a later refactor — reordering &lt;code&gt;_STATUS_RANK&lt;/code&gt;'s values, or changing the leftover math — nothing in CI would have caught it. The cost-estimation output would have quietly started under- or over-reporting cost floors, without a single red test.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Why the obvious fix was insufficient
&lt;/h2&gt;

&lt;p&gt;The obvious response to "I want good docs for this repo" is: write a good README, or write good docstrings, and be careful. I already had both — &lt;code&gt;agent-cost&lt;/code&gt;'s README has a "What this measures, and what it doesn't" section, and &lt;code&gt;price_fact()&lt;/code&gt; has a docstring explaining the lower-bound design decision. Careful prose is necessary but not sufficient, for two reasons that showed up directly here:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Prose doesn't force you to check the thing it describes against a test.&lt;/strong&gt; I could (and did, informally) describe the TTL-breakdown behavior accurately in a docstring without ever asking "is there a test where the breakdown dict is present but incomplete?" Writing prose doesn't create that question; writing a &lt;code&gt;provenance&lt;/code&gt; entry that must name a specific test does.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A doc that "looks read" and a doc that's actually checked are indistinguishable from the outside.&lt;/strong&gt; Nothing in a normal README tells a reader which sentences were verified by running something versus which were a best guess — every claim carries the same visual weight.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So the fix isn't "write better docs" in the abstract — it's structural: every behavior claim needs a machine-checkable pointer to what backs it, narrow enough that "no test covers this case" becomes visible while writing it, not something discovered later.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Invariant
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;evidence-docs&lt;/code&gt; enforces this as a schema-level invariant on every &lt;code&gt;observation&lt;/code&gt;, not a style guideline. &lt;strong&gt;&lt;code&gt;epistemic_status&lt;/code&gt;&lt;/strong&gt; must be one of a fixed vocabulary (from &lt;code&gt;execution_verified&lt;/code&gt; down to &lt;code&gt;model_inference&lt;/code&gt; / &lt;code&gt;single_source_observation&lt;/code&gt; / &lt;code&gt;recorded_decision&lt;/code&gt;) — you have to say how strongly the claim was checked, not just assert it. &lt;strong&gt;&lt;code&gt;provenance&lt;/code&gt;&lt;/strong&gt; is a list of entries, each with a &lt;code&gt;source_kind&lt;/code&gt; (&lt;code&gt;source&lt;/code&gt;, &lt;code&gt;test&lt;/code&gt;, &lt;code&gt;spec&lt;/code&gt;, or &lt;code&gt;review_memory&lt;/code&gt;), a repo-relative &lt;code&gt;uri&lt;/code&gt;, a &lt;code&gt;selector&lt;/code&gt; (which test/function/section), a &lt;code&gt;content_digest&lt;/code&gt;, and the &lt;code&gt;repo_commit&lt;/code&gt; the digest was taken against.&lt;/p&gt;

&lt;p&gt;Neither field is optional, and both are validated structurally: unknown &lt;code&gt;source_kind&lt;/code&gt; values are rejected outright, and &lt;code&gt;content_digest&lt;/code&gt; is checked against the actual git blob at the declared commit, not the working tree. Two of &lt;code&gt;agent-cost&lt;/code&gt;'s own observations (OBS-004, the cache-write-TTL claim, and OBS-010, the pricing-status-ranking claim) exist because writing their &lt;code&gt;provenance&lt;/code&gt; forced me to go find "the test that proves this," and in both cases the honest answer was "there isn't one for this specific sub-case" — which is what &lt;code&gt;gaps.yaml&lt;/code&gt; records.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Design (evidence-docs, minimally)
&lt;/h2&gt;

&lt;p&gt;An &lt;code&gt;evidence-docs&lt;/code&gt; corpus is a directory (conventionally &lt;code&gt;docs/claims/&lt;/code&gt;) with: &lt;code&gt;id-registry.yaml&lt;/code&gt; (every &lt;code&gt;topic_id&lt;/code&gt;/&lt;code&gt;observation_id&lt;/code&gt; used anywhere must be pre-registered here, closing typo/duplicate ID bugs at generate time); &lt;code&gt;topics/*.yaml&lt;/code&gt; and &lt;code&gt;observations/*.yaml&lt;/code&gt; (one file per topic by convention, not enforced); and &lt;code&gt;gaps.yaml&lt;/code&gt;, a ledger of gaps found (&lt;code&gt;gap_found: true&lt;/code&gt;) &lt;em&gt;or&lt;/em&gt; explicitly searched for and not found (&lt;code&gt;gap_found: false&lt;/code&gt;), each tagged with a &lt;code&gt;taxonomy&lt;/code&gt; — &lt;code&gt;test_missing&lt;/code&gt; for both GAP-01 and GAP-02 here, &lt;code&gt;independent_re_search_no_drift&lt;/code&gt; for a third entry, GAP-03, where I re-checked three README pricing claims against the current &lt;code&gt;rates.json&lt;/code&gt; and found no drift, recorded as a negative result rather than omitted.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;evidence-docs validate docs/claims --repo-commit &amp;lt;sha&amp;gt;&lt;/code&gt; runs full structural + provenance verification with no output written (CI-friendly); &lt;code&gt;evidence-docs generate&lt;/code&gt; does the same and then deterministically writes a human-readable &lt;code&gt;site/index.md&lt;/code&gt; and an AI-facing &lt;code&gt;bundle/*.jsonl&lt;/code&gt; + &lt;code&gt;manifest.json&lt;/code&gt;. &lt;code&gt;evidence-docs context docs/claims --query '{"seeds": {"paths": [...]}, "token_budget": ...}'&lt;/code&gt; selects a relevant subset of claims from the bundle for a given set of source paths, for feeding into an LLM context window without shipping the whole corpus. &lt;code&gt;--generated-at&lt;/code&gt; and &lt;code&gt;--repo-commit&lt;/code&gt; are always explicit CLI arguments, never derived from &lt;code&gt;datetime.now()&lt;/code&gt; or &lt;code&gt;git rev-parse&lt;/code&gt; at run time, so the same corpus and arguments always produce byte-identical output.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Fail behavior
&lt;/h2&gt;

&lt;p&gt;The parts of &lt;code&gt;evidence-docs&lt;/code&gt; that made this finding possible are the parts designed to fail loudly on drift or forgery, not pass silently:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Digest verification targets the declared commit's git blob&lt;/strong&gt; (&lt;code&gt;git show &amp;lt;repo_commit&amp;gt;:&amp;lt;uri&amp;gt;&lt;/code&gt;, hashed with sha256), not the current worktree file. This closes a real bypass: checking only the worktree's current hash would still let someone edit the file, recompute the digest, and leave &lt;code&gt;repo_commit&lt;/code&gt; on the old SHA. A worktree/declared-commit mismatch is a warning (repos move on after a snapshot); a mismatch against the &lt;em&gt;declared commit's own&lt;/em&gt; blob is always fatal.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unknown &lt;code&gt;source_kind&lt;/code&gt; is a hard rejection, not a skip.&lt;/strong&gt; An earlier design silently skipped unrecognized kinds — exactly what a forged or typo'd entry would want.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;--repo-commit&lt;/code&gt; must equal every &lt;code&gt;valid_at_commit&lt;/code&gt; and &lt;code&gt;provenance[].repo_commit&lt;/code&gt; corpus-wide&lt;/strong&gt;, with no exceptions, since a corpus is a snapshot of one commit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Two independent path-traversal guards&lt;/strong&gt; sit in front of the digest check, since &lt;code&gt;git show&lt;/code&gt; never touches the filesystem and needs its own check separate from worktree path resolution.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of this checks whether a &lt;em&gt;statement&lt;/em&gt; is true (see Boundary, below) — but it does mean a fake or lazy &lt;code&gt;provenance&lt;/code&gt; entry gets caught at &lt;code&gt;validate&lt;/code&gt; time rather than trusted forever.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Verification: gap to merged test, with receipts
&lt;/h2&gt;

&lt;p&gt;Both gaps followed the same path: recorded in &lt;code&gt;gaps.yaml&lt;/code&gt;, then closed by a small PR that added a test and changed no implementation code.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GAP-01 → &lt;a href="https://github.com/shiki-yusuke/agent-cost/pull/2" rel="noopener noreferrer"&gt;&lt;code&gt;shiki-yusuke/agent-cost#2&lt;/code&gt;&lt;/a&gt;&lt;/strong&gt; ("test: cover the partial TTL-breakdown leftover branch in the Claude reader"), merged 2026-08-14. Adds &lt;code&gt;test_cache_creation_partial_ttl_breakdown_leftover_is_unknown&lt;/code&gt; to &lt;code&gt;tests/test_reader_claude.py&lt;/code&gt;: a &lt;code&gt;cache_creation&lt;/code&gt; dict with &lt;code&gt;ephemeral_5m_input_tokens=150&lt;/code&gt; and &lt;code&gt;ephemeral_1h_input_tokens=100&lt;/code&gt; against a &lt;code&gt;cache_creation_input_tokens&lt;/code&gt; total of 300, asserting the reader emits &lt;code&gt;cache_write_5m=150&lt;/code&gt;, &lt;code&gt;cache_write_1h=100&lt;/code&gt;, &lt;strong&gt;and&lt;/strong&gt; &lt;code&gt;cache_write_unknown=50&lt;/code&gt; for the 50-token leftover. No implementation changes — the leftover math already behaved this way; the PR's own description says so.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GAP-02 → &lt;a href="https://github.com/shiki-yusuke/agent-cost/pull/3" rel="noopener noreferrer"&gt;&lt;code&gt;shiki-yusuke/agent-cost#3&lt;/code&gt;&lt;/a&gt;&lt;/strong&gt; ("test: pin the worst-status row aggregation order for pricing_status"), merged 2026-08-14. Adds four tests to &lt;code&gt;tests/test_aggregate.py&lt;/code&gt;: &lt;code&gt;test_build_rows_all_priced_facts_mark_row_priced&lt;/code&gt; (baseline), &lt;code&gt;test_build_rows_mixed_priced_and_lower_bound_marks_row_lower_bound&lt;/code&gt; (the previously-untested half — a &lt;code&gt;lower_bound&lt;/code&gt; fact plus a &lt;code&gt;priced&lt;/code&gt; fact should mark the row &lt;code&gt;lower_bound&lt;/code&gt;), &lt;code&gt;test_build_rows_mixed_lower_bound_and_unpriced_marks_row_unpriced&lt;/code&gt;, and &lt;code&gt;test_build_rows_worst_status_ranking_is_order_independent&lt;/code&gt; (same three facts constructed in three different orders, same resulting status). Again, no implementation changes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both PRs are public and merged; the diffs and CI runs are the actual evidence for this post, not my summary of them.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Boundary — what this does not show
&lt;/h2&gt;

&lt;p&gt;To be precise about scope, since it's easy to over-read a post like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;This is not a bug report.&lt;/strong&gt; Both gaps were missing regression tests for code that already behaved correctly — nothing shipped broken, nothing was fixed except test coverage.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;evidence-docs&lt;/code&gt; does not check whether a claim is true.&lt;/strong&gt; Its own schema doc says so plainly: "the prose claim itself is never checked against the code... validation checks structure and provenance shape, not truth." What forced these two gaps into the open was &lt;em&gt;my own&lt;/em&gt; process of trying to write an honest provenance pointer for each claim, not an automated truth-checker. A careless author could still write &lt;code&gt;execution_verified&lt;/code&gt; next to a claim backed only by reading the source; the schema's &lt;code&gt;negation_check&lt;/code&gt; field helps catch that but is optional and unenforced.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;gaps.yaml&lt;/code&gt; completeness is not verified either.&lt;/strong&gt; A corpus with zero recorded gaps is syntactically valid; whether the author actually looked for contradictions isn't something the tool can check. GAP-03 in this corpus (a re-check of three README pricing claims against &lt;code&gt;rates.json&lt;/code&gt; that found no drift) exists because I chose to record a negative result, not because anything required it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;This is an n=1 experience report on one small repo, not an evaluation.&lt;/strong&gt; No general effect size, no hit rate across repos, no claim that repeating this will surface N more gaps. It found two missing regression tests in one corpus-authoring pass over one repo I maintain. Trying the same process elsewhere might find zero, or something entirely different — the claim here is about what happened and how to reproduce the &lt;em&gt;process&lt;/em&gt;, not what you should expect to get out of it.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  9. Try it yourself
&lt;/h2&gt;

&lt;p&gt;The minimal path that reproduces the "write a claim, discover you can't honestly back it" moment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;evidence-docs

evidence-docs init docs/claims
&lt;span class="c"&gt;# scaffolds topics/, observations/, id-registry.yaml, gaps.yaml, README.md&lt;/span&gt;

&lt;span class="c"&gt;# ... author one observation for a behavior you believe is true, with a&lt;/span&gt;
&lt;span class="c"&gt;#     provenance entry naming the exact test that proves it. If you can't&lt;/span&gt;
&lt;span class="c"&gt;#     name one, that's the gap. ...&lt;/span&gt;

evidence-docs validate docs/claims &lt;span class="nt"&gt;--repo-commit&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;git rev-parse HEAD&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;validate&lt;/code&gt; will reject an unregistered ID, an unknown &lt;code&gt;source_kind&lt;/code&gt;, or a digest that doesn't match the git blob at the commit you passed — all useful on their own — but the actual gap-finding step is upstream of the tool: it happens while you're trying to fill in the &lt;code&gt;provenance&lt;/code&gt; field honestly, before you ever run &lt;code&gt;validate&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/shiki-yusuke/agent-cost" rel="noopener noreferrer"&gt;&lt;code&gt;shiki-yusuke/agent-cost&lt;/code&gt;&lt;/a&gt; — the repo the corpus was written against&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/shiki-yusuke/evidence-docs" rel="noopener noreferrer"&gt;&lt;code&gt;shiki-yusuke/evidence-docs&lt;/code&gt;&lt;/a&gt; — the claim-corpus tool&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/shiki-yusuke/agent-cost/pull/2" rel="noopener noreferrer"&gt;&lt;code&gt;agent-cost#2&lt;/code&gt;&lt;/a&gt; — GAP-01 fix (merged)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/shiki-yusuke/agent-cost/pull/3" rel="noopener noreferrer"&gt;&lt;code&gt;agent-cost#3&lt;/code&gt;&lt;/a&gt; — GAP-02 fix (merged)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;docs/claims/gaps.yaml&lt;/code&gt; in &lt;code&gt;agent-cost&lt;/code&gt; — the gap ledger (GAP-01/02/03)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;docs/schema.md&lt;/code&gt; in &lt;code&gt;evidence-docs&lt;/code&gt; — the trust-boundary write-up referenced throughout&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>opensource</category>
      <category>python</category>
      <category>testing</category>
      <category>documentation</category>
    </item>
  </channel>
</rss>
