<?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: stephen-gabriel</title>
    <description>The latest articles on DEV Community by stephen-gabriel (@onyx_dev).</description>
    <link>https://dev.to/onyx_dev</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%2F4191448%2Ffe9e6004-f87a-419a-98de-710735d9ce19.jpeg</url>
      <title>DEV Community: stephen-gabriel</title>
      <link>https://dev.to/onyx_dev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/onyx_dev"/>
    <language>en</language>
    <item>
      <title>An Evaluation Is Not an Approval: Building AFTERIMAGE with FalkorDB</title>
      <dc:creator>stephen-gabriel</dc:creator>
      <pubDate>Sun, 18 Oct 2026 07:21:50 +0000</pubDate>
      <link>https://dev.to/onyx_dev/an-evaluation-is-not-an-approval-building-afterimage-with-falkordb-1134</link>
      <guid>https://dev.to/onyx_dev/an-evaluation-is-not-an-approval-building-afterimage-with-falkordb-1134</guid>
      <description>&lt;p&gt;A saved investigation in my hackathon project ended with &lt;code&gt;NO_ACTION_NEEDED&lt;/code&gt;. The policy evaluator could see passing evidence. The current release revision still had no recorded decision.&lt;/p&gt;

&lt;p&gt;Those facts should not have been allowed to collapse into the same conclusion.&lt;/p&gt;

&lt;p&gt;That bug became the clearest explanation of why I built &lt;strong&gt;AFTERIMAGE&lt;/strong&gt;: an approval needs a record of the exact facts it depended on, and a way to recognise when those facts no longer apply.&lt;/p&gt;

&lt;p&gt;I built the prototype for &lt;strong&gt;Graph Hacks by WeMakeDevs and FalkorDB&lt;/strong&gt;, for developers and release reviewers who need to revisit decisions after dependencies change.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/VEsePKOzgSI" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  The approval that outlived its context
&lt;/h2&gt;

&lt;p&gt;Imagine a checkout release approved against &lt;code&gt;payment-api&lt;/code&gt; version &lt;code&gt;2.0.0&lt;/code&gt;. Its integration evidence passes, its requirement is satisfied, and the decision is recorded.&lt;/p&gt;

&lt;p&gt;Now the dependency changes to &lt;code&gt;3.0.0&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The old test result has not become false. It still describes the earlier configuration. What has changed is whether that result supports the release we are reviewing now.&lt;/p&gt;

&lt;p&gt;AFTERIMAGE creates a new release revision and marks it &lt;strong&gt;Needs review&lt;/strong&gt;. The old approval remains visible as history. A separate documentation release, which does not depend on that API, stays approved.&lt;/p&gt;

&lt;p&gt;The demonstration follows this sequence:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What happens&lt;/th&gt;
&lt;th&gt;What AFTERIMAGE records or shows&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Checkout is approved against payment-api v2&lt;/td&gt;
&lt;td&gt;A decision supported by revision-specific evidence&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The dependency changes to v3&lt;/td&gt;
&lt;td&gt;A new revision; the earlier decision is no longer current&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The agent investigates&lt;/td&gt;
&lt;td&gt;Findings with record references and proposed review actions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;I apply an evidence-request proposal&lt;/td&gt;
&lt;td&gt;A persistent request for the missing evidence&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Matching synthetic PASS evidence is recorded&lt;/td&gt;
&lt;td&gt;Evidence for the new revision; approval still requires a decision&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;I apply a reassessment&lt;/td&gt;
&lt;td&gt;A new recorded decision, with its supporting evidence&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;“Needs review” does not claim the dependency is broken. It identifies a gap between an earlier decision and the evidence available for the current configuration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the graph is part of the mechanism
&lt;/h2&gt;

&lt;p&gt;FalkorDB stores the application’s authoritative records. Release revisions, dependency versions, requirements, evidence, sources, and decisions are connected through directed relationships.&lt;/p&gt;

&lt;p&gt;Some of the relationships that matter most are:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Relationship&lt;/th&gt;
&lt;th&gt;Question it answers&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ReleaseRevision —DEPENDS_ON→ DependencyVersion&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Which dependency version belongs to this configuration?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Evidence —FOR_REVISION→ ReleaseRevision&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Which revision does this evidence describe?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Evidence —SATISFIES_CHECK→ Requirement&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Which requirement is the evidence attached to?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Evidence —FROM_SOURCE→ Source&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Where did it come from?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Decision —ASSESSES→ ReleaseRevision&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;What was actually reviewed?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Decision —SUPPORTED_BY→ Evidence&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;What supported the recorded outcome?&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Attaching evidence to a requirement is not enough to approve it. The policy still checks the outcome, revision, dependency scope, source authority, and withdrawal or supersession state.&lt;/p&gt;

&lt;p&gt;Impact lookup follows the graph backwards from a changed entity. This excerpt from the application’s &lt;code&gt;findAffectedReleases&lt;/code&gt; query follows a dependency to the release revisions that use it, then selects releases for which those revisions are current:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cypher"&gt;&lt;code&gt;&lt;span class="k"&gt;MATCH&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entity&lt;/span&gt; &lt;span class="ss"&gt;{&lt;/span&gt;&lt;span class="py"&gt;id:&lt;/span&gt; &lt;span class="n"&gt;$entityId&lt;/span&gt;&lt;span class="ss"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;entity.workspaceId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;$wsId&lt;/span&gt;
&lt;span class="k"&gt;OPTIONAL&lt;/span&gt; &lt;span class="k"&gt;MATCH&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entity&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="ss"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;:DEPENDS_ON&lt;/span&gt;&lt;span class="ss"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="py"&gt;depRev:&lt;/span&gt;&lt;span class="n"&gt;ReleaseRevision&lt;/span&gt; &lt;span class="ss"&gt;{&lt;/span&gt;&lt;span class="py"&gt;workspaceId:&lt;/span&gt; &lt;span class="n"&gt;$wsId&lt;/span&gt;&lt;span class="ss"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;OPTIONAL&lt;/span&gt; &lt;span class="k"&gt;MATCH&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;depRev&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="ss"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;:CURRENT_REVISION&lt;/span&gt;&lt;span class="ss"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="py"&gt;depRel:&lt;/span&gt;&lt;span class="n"&gt;Release&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is an excerpt, not a standalone query: the &lt;a href="https://github.com/stephen-gabriel/afterimage/blob/main/backend/src/query/index.ts" rel="noopener noreferrer"&gt;full implementation&lt;/a&gt; includes additional paths and returns the affected releases.&lt;/p&gt;

&lt;p&gt;The same graph supplies context to the agent and records to the policy evaluator. The Connections tab makes those paths readable, but the database also determines which records the application can investigate and evaluate. Removing it breaks the core reads and writes.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the agent can do
&lt;/h2&gt;

&lt;p&gt;The live agent uses an OpenRouter model through six narrow tools:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;get_release_context&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;find_affected_releases&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;inspect_evidence&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;get_decision_history&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;evaluate_release&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;propose_review_action&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These tools expose validated operations over the application’s query layer. The model does not receive unrestricted database access.&lt;/p&gt;

&lt;p&gt;An investigation can propose creating an evidence request, resolving a satisfied request, or recording a reassessment. The user applies the proposal, and the backend checks the current revision and the action’s requirements again.&lt;/p&gt;

&lt;p&gt;Runs, proposals, requests, and decision history persist in FalkorDB. A later session can recover the earlier work rather than starting from an empty conversation.&lt;/p&gt;

&lt;p&gt;That separation matters: the model helps investigate and choose the next action, while deterministic application code evaluates the evidence and guards writes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bug that clarified the design
&lt;/h2&gt;

&lt;p&gt;The most useful failure was the one in the opening paragraph.&lt;/p&gt;

&lt;p&gt;An earlier completion rule effectively treated an &lt;code&gt;APPROVED&lt;/code&gt; evaluation as sufficient reason to stop. It did not verify that a decision had actually been recorded for the investigated revision. Another part of the code classified completion from the model’s findings text, which could omit actions the run had submitted.&lt;/p&gt;

&lt;p&gt;The fix needed more than a stronger prompt.&lt;/p&gt;

&lt;p&gt;The backend now checks the release’s current decision pointer and verifies that the referenced decision assesses the investigated revision. The relationship check uses this Cypher pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cypher"&gt;&lt;code&gt;&lt;span class="k"&gt;MATCH&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="py"&gt;d:&lt;/span&gt;&lt;span class="n"&gt;Decision&lt;/span&gt; &lt;span class="ss"&gt;{&lt;/span&gt;&lt;span class="py"&gt;id:&lt;/span&gt; &lt;span class="n"&gt;$decisionId&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="py"&gt;workspaceId:&lt;/span&gt; &lt;span class="n"&gt;$wsId&lt;/span&gt;&lt;span class="ss"&gt;})&lt;/span&gt;
      &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="ss"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;:ASSESSES&lt;/span&gt;&lt;span class="ss"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;
      &lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="py"&gt;rev:&lt;/span&gt;&lt;span class="n"&gt;ReleaseRevision&lt;/span&gt; &lt;span class="ss"&gt;{&lt;/span&gt;&lt;span class="py"&gt;id:&lt;/span&gt; &lt;span class="n"&gt;$revisionId&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="py"&gt;workspaceId:&lt;/span&gt; &lt;span class="n"&gt;$wsId&lt;/span&gt;&lt;span class="ss"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;RETURN&lt;/span&gt; &lt;span class="n"&gt;d.id&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When evaluation is complete but the relevant decision is missing, the validator requires a reassessment proposal unless one is already pending. It gives the model bounded corrective feedback. If the required action is still missing when that allowance runs out, the run fails validation. The application does not invent a proposal or silently write an approval.&lt;/p&gt;

&lt;p&gt;Completion classification also uses the run’s submitted proposals, rather than relying on whether its prose mentions them.&lt;/p&gt;

&lt;p&gt;The resulting distinction is precise:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;An evaluation computes what the policy says about the evidence now. A recorded decision preserves an explicitly applied assessment of a particular revision.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/stephen-gabriel/afterimage/blob/main/backend/src/agent/run.ts" rel="noopener noreferrer"&gt;validation and orchestration code&lt;/a&gt; makes that distinction enforceable.&lt;/p&gt;

&lt;h2&gt;
  
  
  A valid proposal can become stale
&lt;/h2&gt;

&lt;p&gt;A second lesson appeared when an investigation proposed two actions based on the same workspace state.&lt;/p&gt;

&lt;p&gt;Applying the first action advances the workspace revision. The other proposal was prepared against the earlier state. Letting both apply without checking would make the second action depend on assumptions that might no longer hold.&lt;/p&gt;

&lt;p&gt;AFTERIMAGE stores the revision each proposal expects. The backend compares it with the current workspace revision at application time. Outdated proposals become stale, and the interface offers another investigation against the refreshed state.&lt;/p&gt;

&lt;p&gt;Release revisions and workspace revisions serve different purposes: one identifies a release configuration; the other tracks changes to the shared application state.&lt;/p&gt;

&lt;p&gt;The prototype serializes mutations in one backend process. These guards are not a claim of distributed, multi-instance concurrency support.&lt;/p&gt;

&lt;h2&gt;
  
  
  What testing—and a free model—taught me
&lt;/h2&gt;

&lt;p&gt;I used FalkorDB in Docker and a free OpenRouter model for the live demo. Runtime spending stayed at $0, but model availability was a real constraint: a later investigation hit the provider’s daily quota.&lt;/p&gt;

&lt;p&gt;That made it important to separate three kinds of evidence:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Deterministic policy tests&lt;/strong&gt; check applicability, conflicts, withdrawals, supersession, and revision handling.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A clearly labelled test model&lt;/strong&gt; exercises action flows repeatably without consuming live-provider requests.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Live-provider runs&lt;/strong&gt; check that a real model uses the tools and produces usable proposals.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A passing test double does not prove that a live model followed the same path. Similarly, a quota failure does not prove that a release failed its checks. The interface reports it as an investigation failure.&lt;/p&gt;

&lt;p&gt;Focused regression tests cover stale proposal application and the missing-decision case, including a deliberately uncooperative test model. The important outcome is that failure remains visible without fabricating successful work.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the prototype does—and what comes next
&lt;/h2&gt;

&lt;p&gt;AFTERIMAGE currently uses synthetic evidence and a small demonstration policy. It does not run CI, deploy software, or certify that a release is safe. A resolved evidence request closes an internal task; approval still requires a recorded decision.&lt;/p&gt;

&lt;p&gt;The next useful extension would be ingesting real CI results while preserving their source and exact revision scope. A production version would also need authentication, stronger operational controls, and a durable execution design before running across multiple backend instances.&lt;/p&gt;

&lt;p&gt;For this build, the useful result is already concrete: I can change a dependency, see which approval needs review, investigate the evidence gap, and record a new decision without erasing the old one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The record of why we approved something should remain useful after the facts change.&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Explore the project&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/stephen-gabriel/afterimage" rel="noopener noreferrer"&gt;Source code and complete local setup&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/stephen-gabriel/afterimage/blob/main/docs/GRAPH_MODEL.md" rel="noopener noreferrer"&gt;Implemented graph model&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/stephen-gabriel/afterimage/blob/main/backend/src/evaluation/policy.ts" rel="noopener noreferrer"&gt;Deterministic policy evaluator&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;AI assistance: I directed the product scope, reviewed the running behaviour, and used AI coding assistants extensively for implementation, debugging, and test development. AI assistance also helped draft this article from the repository and build records. The repository includes a fuller disclosure.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>falkordb</category>
      <category>ai</category>
      <category>typescript</category>
      <category>hackathon</category>
    </item>
  </channel>
</rss>
