<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:blog="https://jonesrussell.github.io/blog/ns"><channel><title>Integrity on Web Developer Blog</title><link>https://jonesrussell.github.io/blog/tags/integrity/</link><description>Recent content in Integrity on Web Developer Blog</description><image><title>Web Developer Blog</title><url>https://jonesrussell.github.io/blog/images/og-default.png</url><link>https://jonesrussell.github.io/blog/images/og-default.png</link></image><generator>Hugo -- 0.168.0</generator><language>en-us</language><lastBuildDate>Tue, 27 Oct 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://jonesrussell.github.io/blog/tags/integrity/feed.xml" rel="self" type="application/rss+xml"/><item><title>Repair stale digest pins without weakening verification</title><link>https://jonesrussell.github.io/blog/repair-stale-digest-pins/</link><pubDate>Tue, 27 Oct 2026 00:00:00 +0000</pubDate><guid>https://jonesrussell.github.io/blog/repair-stale-digest-pins/</guid><category>php</category><blog:tag>php</blog:tag><blog:tag>testing</blog:tag><blog:tag>integrity</blog:tag><blog:tag>code-generation</blog:tag><description>Treat a stale generated-artifact digest as a fixture migration, then prove the real reference configuration still renders.</description><content:encoded><![CDATA[<p>Ahnii!</p>
<p>A strict digest check is supposed to stop old configuration from silently selecting changed generated output. When a reviewed generator changes, that protection can also make a reference fixture fail until its pinned identity is migrated.</p>
<p>This post shows how to repair that failure without weakening the integrity check, using a <a href="https://github.com/waaseyaa/framework">Waaseyaa</a> reference consumer as the example.</p>
<h2 id="understand-what-the-digest-protects">Understand what the digest protects</h2>
<p>The reference application selects a generated recipe in YAML:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">recipes</span>:
</span></span><span style="display:flex;"><span>  - <span style="color:#f92672">id</span>: <span style="color:#ae81ff">published_content</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">version</span>: <span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">capability</span>: <span style="color:#ae81ff">published_content</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">artifact_digest</span>: <span style="color:#ae81ff">cf8f1932dea7f7dc08ccf2cefbf42465d5c2838b1d0e1fbd3cf62a63da580f11</span>
</span></span></code></pre></div><p>The digest identifies the reviewed recipe artifact, not just the recipe name and version. If the recipe&rsquo;s generated files or contract change, an older selection should not claim that it still describes the installed output.</p>
<p>That refusal is useful. It turns a silent compatibility mismatch into an explicit migration decision.</p>
<h2 id="classify-the-failure-before-changing-code">Classify the failure before changing code</h2>
<p>The hosted reference consumer failed because its checked-in answers still pinned the previous recipe digest. A fresh installed consumer reproduced the same mismatch through <code>project:init --dry-run</code>.</p>
<p>There are two very different possible causes:</p>
<table>
	<thead>
			<tr>
					<th>Cause</th>
					<th>Correct response</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>Installed recipe changed without an approved contract update</td>
					<td>Stop and repair the producer or release process</td>
			</tr>
			<tr>
					<td>Reviewed recipe changed but its reference fixture kept the old digest</td>
					<td>Migrate the fixture and prove it against the installed recipe</td>
			</tr>
	</tbody>
</table>
<p>Do not make digest comparison optional just because the failure appeared in a test fixture. That would also allow genuinely stale applications to accept changed generated output.</p>
<p>In this case, the reviewed recipe already owned the new digest. The missing change was in the reference consumer&rsquo;s selection.</p>
<h2 id="update-the-selection-not-the-verifier">Update the selection, not the verifier</h2>
<p>The repair changed one value in the reference answers file: the previous digest was replaced with the digest of the reviewed installed recipe.</p>
<p>The validation rules stayed strict. Runtime boot, installation authority, persistence behavior, and normal refusal controls did not need to change.</p>
<p>This is an important boundary for code generation:</p>
<ul>
<li>the producer defines the artifact identity;</li>
<li>the consumer pins the identity it reviewed;</li>
<li>a changed identity requires an explicit consumer migration;</li>
<li>the verifier continues refusing mismatches.</li>
</ul>
<p>If you instead loosen the verifier, you erase the evidence that the consumer has reviewed the new generated contract.</p>
<h2 id="test-the-real-reference-fixture">Test the real reference fixture</h2>
<p>Updating a hash by hand can make the immediate error disappear without proving that the complete reference configuration works. Add a contract test that loads the actual fixture and sends it through the installed recipe:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-php" data-lang="php"><span style="display:flex;"><span>#[<span style="color:#a6e22e">Test</span>]
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">public</span> <span style="color:#66d9ef">function</span> <span style="color:#a6e22e">theReferenceSelectionRendersWithTheInstalledPublishedContentRecipe</span>()<span style="color:#f92672">:</span> <span style="color:#a6e22e">void</span>
</span></span><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>    $path <span style="color:#f92672">=</span> <span style="color:#a6e22e">dirname</span>(<span style="color:#66d9ef">__DIR__</span>) <span style="color:#f92672">.</span> <span style="color:#e6db74">&#39;/ReferenceConsumer/site.answers.yaml&#39;</span>;
</span></span><span style="display:flex;"><span>    $manifest <span style="color:#f92672">=</span> <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">SiteManifestParser</span>()
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">parse</span>((<span style="color:#a6e22e">string</span>) <span style="color:#a6e22e">file_get_contents</span>($path), $path);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">self</span><span style="color:#f92672">::</span><span style="color:#a6e22e">assertNotEmpty</span>(
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">new</span> <span style="color:#a6e22e">PublishedContentRecipe</span>()<span style="color:#f92672">-&gt;</span><span style="color:#a6e22e">render</span>($manifest),
</span></span><span style="display:flex;"><span>    );
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>This test checks the real answers file rather than a smaller duplicate fixture. It exercises three things together:</p>
<ul>
<li>the reference YAML parses as a site manifest;</li>
<li>its pinned recipe identity matches the installed recipe;</li>
<li>the recipe can render output for that manifest.</li>
</ul>
<p>The test was captured failing with the stale digest before the fixture was updated. That proves the regression test detects the original mismatch.</p>
<h2 id="keep-the-negative-test">Keep the negative test</h2>
<p>A successful real-fixture test is only half of the contract. Keep a separate test that substitutes the wrong digest and expects refusal.</p>
<p>The two tests answer different questions:</p>
<table>
	<thead>
			<tr>
					<th>Test</th>
					<th>Evidence</th>
			</tr>
	</thead>
	<tbody>
			<tr>
					<td>Actual reference selection renders</td>
					<td>The maintained fixture matches the installed reviewed recipe</td>
			</tr>
			<tr>
					<td>Substituted digest is refused</td>
					<td>Integrity checking still rejects an unreviewed identity</td>
			</tr>
	</tbody>
</table>
<p>Without the first test, fixture drift can reach hosted qualification. Without the second, a permissive implementation could make every fixture pass.</p>
<h2 id="requalify-the-new-identity">Requalify the new identity</h2>
<p>Once the fixture and regression test change, you have a new candidate. Run the focused contract tests, the broader governed preflight, and the hosted consumers again against the exact new commit.</p>
<p>Do not reuse the old green results. They describe the old candidate. Do not repair a failed installed consumer in place either. A fresh consumer gives you evidence about the new fixture without mixing it with state from the failed attempt.</p>
<p>The final checklist is short:</p>
<ol>
<li>Reproduce the digest mismatch from a clean consumer.</li>
<li>Confirm whether the producer or the pinned fixture is stale.</li>
<li>Update only the reviewed consumer selection when it is a fixture migration.</li>
<li>Add a test that renders the actual reference manifest.</li>
<li>Retain a negative test for a substituted digest.</li>
<li>Requalify the new commit through fresh consumers.</li>
</ol>
<p>Strict digest checks create migration work on purpose. Preserve that boundary, migrate the fixture explicitly, and test both acceptance and refusal.</p>
<p>Baamaapii</p>
]]></content:encoded></item></channel></rss>