Build a Claim-Contract Verification Engine

Deploy a browser-native tool that verifies AI claims with SHA-256 and 6 states.

Introduction

30 Second Summary

AI tools produce answers packed with dates, version numbers, and links that look like evidence. A sentence full of specific details feels verified. But specificity is not proof.

In this project, you will build a browser-based Claim-Contract Verification Engine that decomposes any text into individual claims, generates a formal contract for each one, and assigns a verification state. You will also deploy it to a live URL where anyone can test it.

What You'll Build

Paste AI-generated text into your live tool, watch every claim get decomposed into a falsifiable contract, then upload a file and verify its integrity with real cryptographic hashing.

By the end of this project, you'll have:

  • A live web tool where you paste any AI output and see each claim labelled as STRUCTURED, CHECKABLE, VERIFIED, or FAILED, so you never confuse confidence for proof.
  • A SHA-256 file verifier that performs real cryptographic verification in the browser using the Web Crypto API, proving a claim with mathematical certainty at zero cost.
  • 8 frozen adversarial tests that run visibly on page load, proving the engine catches fake specificity, unreachable evidence, and contradictions before you trust it.
  • Secret Mission: Build a Near-Truth Detector using Euler's identity to prove that domain-aware equality catches what naive tolerance misses.

Are there any prerequisites?

You need basic JavaScript familiarity, a GitHub account, and a Vercel account (free tier). No npm packages or external dependencies are used.

Before We Start

Before we dive in, let's lock in what you're about to build and why it matters. You're building a tool that distinguishes between claims that LOOK verified and claims that ARE verified, because AI produces confident-sounding output filled with structured tokens that humans mistake for evidence.

Set Up the Verification Workshop

A verification engine that lives only on your machine proves nothing to anyone else. For the tool to be credible, it must be publicly accessible so others can test it and replicate the experiment.

In this step, you will create a public GitHub repository, scaffold the project files, and connect the repo to Vercel for automatic deployment. By the end, you will have a live URL that auto-deploys every time you push code.

In this step, get ready to:
  • Create and clone a public GitHub repository.
  • Scaffold the four project files in VS Code.
  • Connect the repo to Vercel and verify the live deployment.
Create and clone the GitHub repository
  • Open your browser and go to github.com/new to create a new repository.
  • Enter chain-verify as the repository name.
  • Set the visibility to Public.
  • Check the box next to Add a README file.
  • Click Create repository.

Now clone the repo to your local machine.

  • Open your terminal by pressing Cmd+Space (macOS) or the Windows key (Windows), then type Terminal and press Enter.
  • Move to your Desktop by running this command:
cd ~/Desktop
  • Clone the repository by running this command:
git clone https://github.com/[[GITHUB_USERNAME="your GitHub username"]]/chain-verify.git
  • Move into the cloned folder by running:
cd chain-verify
  • Confirm the clone worked by running:
git status

You should see On branch main, nothing to commit, working tree clean.

Seeing 'repository not found'?

Make sure the repository name matches chain-verify exactly and that you set it to Public. Double check that your GitHub username in the clone URL matches your account.

help me troubleshoot my git clone

Scaffold the project files

The project needs exactly four files: one for layout, one for the verification engine logic, one for the test suite, and one for styling.

  • Open VS Code by pressing Cmd+Space (macOS) or the Windows key (Windows), typing VS Code, and pressing Enter.
  • Click File in the top menu bar.
  • Click Open Folder.
  • Select the chain-verify folder on your Desktop, then click Open.

Now create the four empty scaffold files from the VS Code terminal.

  • Open the integrated terminal by pressing Ctrl+` (backtick key, same shortcut on macOS and Windows).
  • Create the four project files by running this command:
touch index.html engine.js tests.js style.css

Windows without touch?

If you are using PowerShell and touch is not recognized, run this instead:

New-Item index.html, engine.js, tests.js, style.css -ItemType File
  • Confirm the files exist by running:
ls

You should see index.html, engine.js, tests.js, and style.css listed alongside the README.md that GitHub created.

  • Stage and commit the empty files by running:
git add . && git commit -m "scaffold: empty project structure"
  • Push the commit to GitHub by running:
git push
  • Verify the push succeeded by running:
git status

You should see On branch main and nothing to commit, working tree clean. Your scaffold files are now live on GitHub.

Push rejected or authentication error?

GitHub requires either an SSH key or a personal access token for pushing. If you see a password prompt or authentication failure, you may need to set up a personal access token in your GitHub settings under Developer settings > Personal access tokens.

help me fix my git push authentication error

Connect Vercel for automatic deployment

Vercel will auto-deploy your site every time you push to the main branch. Since this is a static site with no build step, Vercel serves your files as-is.

  • In your browser, go to vercel.com/new.
  • Find the chain-verify repository in the list and click Import.
  • Set Framework Preset to Other.
  • Leave the Build Command field blank.
  • Set Output Directory to . (a single dot, meaning the root of the repo).
  • Click Deploy.

Why 'Other' with no build command?

This project is plain HTML, CSS, and JavaScript with no bundler or framework. Vercel does not need to compile anything. Setting Framework Preset to Other and leaving the build command blank tells Vercel to serve your files directly.

Vercel will take 30 to 60 seconds to deploy. Once complete, it will display your live URL.

  • Before you click the URL, predict what you will see. The index.html file is empty. What does an empty HTML file look like in a browser?
  • Click the live URL that Vercel shows (something like chain-verify.vercel.app).

You should see a blank white page. That is correct. An empty index.html renders as nothing. Your deployment pipeline is working.

Don't see your repo in the Vercel import list?

Make sure your Vercel account is connected to your GitHub account. On the import page, click Adjust GitHub App Permissions and grant Vercel access to the chain-verify repository.

help me connect my GitHub repo to Vercel

Your workshop is ready. You have a public repo, four scaffold files, and a live deployment pipeline. Next up, you will build the state machine that distinguishes structured tokens from verified claims.

Build the Claim-Contract State Machine

AI generates text full of dates, version numbers, and URLs. Humans see those structured tokens and assume the text is verified. It is not. A sentence containing 2026-08-12 has structure, but structure is not evidence.

This step builds the engine that enforces that distinction. You will implement a 6-state system where claims can only advance from STRUCTURED to CHECKABLE to VERIFIED through explicit evidence. No shortcuts, no conflation.

In this step, get ready to:
  • Build the page shell with sections for text analysis, file verification, and a correction ledger.
  • Implement claim extraction that detects structured tokens (dates, URLs, versions) and generates formal claim contracts.
  • Wire the 6-state transition logic so claims can only advance through evidence, never by appearance alone.
Build the page shell

The engine needs a visible interface before any logic can produce results. You will build the HTML structure and dark-mode styling first so you can see each piece of logic take effect as you write it.

  • In VS Code, open index.html and paste the page structure by copying the following code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CHAIN Verify — Claim-Contract Engine</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<header>
<h1>CHAIN VERIFY</h1>
<p class="subtitle">Claim-Contract Verification Engine v2.0</p>
<p class="subtitle">Paste text. See claims. Know what is STRUCTURED, CHECKABLE, or VERIFIED. Never confuse them.</p>
</header>

<section id="test-panel">
<h2>Frozen Adversarial Tests</h2>
<p class="test-info">These run on load. Any failure is reported before you trust the tool.</p>
<div id="test-results"></div>
</section>

<section id="verify-section">
<h2>Text Verification</h2>
<textarea id="input" placeholder="Paste any AI-generated text here..."></textarea>
<button id="analyze-btn">ANALYZE CLAIMS</button>
<div id="claims-output"></div>
</section>

What does this code do?

This is the top half of your page. The header sets the tool's identity. The test-panel section will display adversarial test results (built in Step 4). The verify-section gives users a textarea to paste text and a button to trigger analysis.

  • Directly below the code you just pasted, add the remaining sections and close the page:

<section id="file-section">
<h2>File Hash Verification (SHA-256)</h2>
<input type="file" id="file-input">
<input type="text" id="expected-hash" placeholder="Expected SHA-256 hash (64 hex characters)">
<button id="hash-btn">VERIFY FILE</button>
<div id="hash-output"></div>
</section>

<section id="ledger-section">
<h2>Correction Ledger</h2>
<div id="ledger"></div>
</section>

<script src="engine.js"></script>
<script src="tests.js"></script>
</body>
</html>

What does this code do?

The file-section provides a file upload and hash input for cryptographic verification (wired in the next step). The ledger-section displays every state transition the engine makes. The two script tags load your engine logic and test suite.

  • Save index.html.
  • Open index.html in your browser by double-clicking it in your file explorer, or right-clicking in VS Code and selecting Open with Live Server if you have that extension.
  • You should see the page headings (CHAIN VERIFY, Frozen Adversarial Tests, Text Verification, File Hash Verification, Correction Ledger) rendered as plain unstyled text on a white background.
  • Open style.css and paste the base styles:
* { margin: 0; padding: 0; box-sizing: border-box; }
body { background: #0a0a0a; color: #e0e0e0; font-family: 'Courier New', monospace; padding: 2rem; max-width: 900px; margin: 0 auto; }
h1 { color: #00ff88; margin-bottom: 0.25rem; font-size: 1.8rem; }
h2 { color: #00ff88; margin: 2rem 0 0.5rem 0; font-size: 1.2rem; }
.subtitle { color: #888; margin-bottom: 0.5rem; font-size: 0.9rem; }
textarea { width: 100%; height: 150px; background: #1a1a1a; color: #fff; border: 1px solid #333; padding: 1rem; font-family: inherit; font-size: 0.85rem; resize: vertical; }
input[type="text"] { width: 100%; background: #1a1a1a; color: #fff; border: 1px solid #333; padding: 0.5rem; font-family: inherit; margin-top: 0.5rem; }
input[type="file"] { margin: 0.5rem 0; }
button { background: #00ff88; color: #0a0a0a; border: none; padding: 0.6rem 1.5rem; font-family: inherit; font-size: 0.9rem; font-weight: bold; cursor: pointer; margin-top: 0.75rem; }
button:hover { background: #00cc6a; }
  • Below the base styles, add the panel and claim-card styles:
#test-panel { background: #111; border: 1px solid #333; padding: 1rem; margin-bottom: 2rem; }
.test-info { color: #666; font-size: 0.8rem; margin-bottom: 0.5rem; }
.test-pass { color: #00ff88; }
.test-fail { color: #ff4444; font-weight: bold; }
.claim-card { background: #1a1a1a; border-left: 3px solid #444; padding: 0.75rem; margin: 0.5rem 0; }
.claim-card.structured { border-color: #ffaa00; }
.claim-card.checkable { border-color: #00aaff; }
.claim-card.verified { border-color: #00ff88; }
.claim-card.failed { border-color: #ff4444; }
.claim-card.unknown { border-color: #888; }
.claim-card.error { border-color: #ff00ff; }
.claim-card.unstructured { border-color: #333; }
.status-badge { display: inline-block; padding: 0.15rem 0.5rem; font-size: 0.75rem; font-weight: bold; margin-bottom: 0.25rem; }
.status-badge.structured { background: #ffaa0033; color: #ffaa00; }
.status-badge.checkable { background: #00aaff33; color: #00aaff; }
.status-badge.verified { background: #00ff8833; color: #00ff88; }
.status-badge.failed { background: #ff444433; color: #ff4444; }
.status-badge.unknown { background: #88888833; color: #888; }
.status-badge.error { background: #ff00ff33; color: #ff00ff; }
.status-badge.unstructured { background: #33333333; color: #666; }
.field { font-size: 0.8rem; color: #aaa; margin: 0.15rem 0; }
.field-label { color: #666; }
#ledger { font-size: 0.75rem; color: #666; max-height: 200px; overflow-y: auto; }
#hash-output { margin-top: 0.5rem; }

What do these styles do?

Each verification state gets its own colour. STRUCTURED claims show an amber border, CHECKABLE gets blue, VERIFIED is green, FAILED is red. This colour-coding makes state transitions immediately visible when you test the engine.

  • Save style.css and refresh your browser.
  • You should see a dark background with green headings, a dark textarea, and a green ANALYZE CLAIMS button. The page looks like a terminal-style tool.

Page still looks unstyled?

Confirm your <link rel="stylesheet" href="style.css"> tag in index.html points to style.css (same folder, same spelling). Open DevTools with F12 and check the Console tab for any 404 errors on the stylesheet.

Help me debug my CSS not loading.

Implement claim extraction and token detection

The engine's first job is splitting text into individual claims and scanning each one for structured tokens. A token (a date, URL, or version number) gives a claim structure, but structure alone means nothing about truth.

  • Open engine.js and paste the state definitions and token patterns:
// All possible verification states a claim can hold
const STATES = {
  UNSTRUCTURED: 'unstructured',
  STRUCTURED: 'structured',
  CHECKABLE: 'checkable',
  VERIFIED: 'verified',
  FAILED: 'failed',
  UNKNOWN: 'unknown',
  ERROR: 'error'
};

// Regex patterns that detect structured tokens in text
const TOKEN_PATTERNS = [
  { type: 'date', regex: /\d{4}[-/]\d{2}[-/]\d{2}/g },
  { type: 'version', regex: /v?\d+\.\d+\.\d+/g },
  { type: 'url', regex: /https?:\/\/\S+/g },
  { type: 'percentage', regex: /\d+(\.\d+)?%/g },
  { type: 'filepath', regex: /[A-Z]:\\\S+/g },
  { type: 'measurement', regex: /\d+(\.\d+)?\s*(GB|MB|KB|TB|ms|sec|min|hr|hours|bytes)\b/gi },
  { type: 'hash', regex: /\b[a-f0-9]{64}\b/g },
  { type: 'identifier', regex: /@\w+/g }
];

What does this code do?

The STATES object defines the 6 verification states plus UNSTRUCTURED. Every claim starts in one of these and can only move forward through explicit logic.

TOKEN_PATTERNS is the set of regex detectors. If a sentence contains a date like 2026-08-12 or a URL, the engine flags it as having structure. Having structure does NOT mean having evidence.

  • Below the TOKEN_PATTERNS array, add the ledger and extraction functions:
// Correction ledger tracks every state change
const ledger = [];

function logToLedger(entry) {
  const timestamp = new Date().toISOString();
  ledger.push({ timestamp, ...entry });
  renderLedger();
}

function renderLedger() {
  const el = document.getElementById('ledger');
  if (!el) return;
  el.innerHTML = ledger.map(e =>
    `<div>${e.timestamp} | ${e.action} | ${e.detail}</div>`
  ).join('');
}

// Split text into individual claim sentences
function extractClaims(text) {
  return text.split(/(?<=[.!?])\s+/).filter(s => s.trim().length > 0);
}

// Scan a sentence for structured tokens
function detectTokens(sentence) {
  const found = [];
  for (const pattern of TOKEN_PATTERNS) {
    const matches = sentence.match(pattern.regex);
    if (matches) {
      found.push(...matches.map(m => ({ type: pattern.type, value: m })));
    }
  }
  return found;
}

What does this code do?

The ledger records every state transition with a timestamp so you can audit what the engine decided and when. The extractClaims function splits text on sentence boundaries. Then detectTokens runs every regex pattern against each sentence, collecting matched tokens.

  • Below detectTokens, add the function that generates a formal claim contract:
// Generate a formal contract for each claim
function createContract(claimText, index) {
  const tokens = detectTokens(claimText);
  const status = tokens.length > 0 ? STATES.STRUCTURED : STATES.UNSTRUCTURED;

  return {
    claim_id: `claim_${index}`,
    claim_text: claimText,
    tokens: tokens,
    subject: '',
    predicate: '',
    object_or_expected_state: '',
    evidence_type: '',
    evidence_locator: '',
    falsifier: '',
    comparison_rule: '',
    authority_required: '',
    status: status
  };
}

What does this code do?

Each claim gets a contract with fields for subject, predicate, evidence locator, falsifier, and comparison rule. The initial status is STRUCTURED if tokens are found, UNSTRUCTURED if not. A claim NEVER jumps to CHECKABLE here because it has no evidence path yet.

  • Save engine.js.

Seeing a syntax error in the console?

Check that each regex in TOKEN_PATTERNS ends with /g and that the array items are separated by commas. Missing a comma between objects is the most common cause.

Help me fix a syntax error in my TOKEN_PATTERNS array.

Wire the state machine and make it interactive

The contract generator tags claims with their initial state. Now you need the logic that governs how claims move between states, and the rendering code that makes everything visible in the browser.

  • Below createContract, add the state transition function:
// Governs all state transitions based on evidence
function transitionState(contract, evidence) {
  try {
    if (!contract.evidence_locator && !contract.falsifier) {
      return contract;
    }
    if (contract.evidence_locator && contract.falsifier) {
      contract.status = STATES.CHECKABLE;
      logToLedger({ action: 'STATE_TRANSITION', detail: `${contract.claim_id}: STRUCTURED -> CHECKABLE` });
    }
    if (evidence === undefined || evidence === null) {
      if (contract.status === STATES.CHECKABLE) {
        contract.status = STATES.UNKNOWN;
        logToLedger({ action: 'STATE_TRANSITION', detail: `${contract.claim_id}: CHECKABLE -> UNKNOWN (no evidence provided)` });
      }
      return contract;
    }
    if (evidence.error) {
      contract.status = STATES.ERROR;
      logToLedger({ action: 'STATE_TRANSITION', detail: `${contract.claim_id}: -> ERROR (${evidence.error})` });
      return contract;
    }
    if (evidence.match) {
      contract.status = STATES.VERIFIED;
      logToLedger({ action: 'STATE_TRANSITION', detail: `${contract.claim_id}: CHECKABLE -> VERIFIED` });
    } else {
      contract.status = STATES.FAILED;
      logToLedger({ action: 'STATE_TRANSITION', detail: `${contract.claim_id}: CHECKABLE -> FAILED` });
    }
  } catch (e) {
    contract.status = STATES.ERROR;
    logToLedger({ action: 'STATE_TRANSITION', detail: `${contract.claim_id}: -> ERROR (${e.message})` });
  }
  return contract;
}

What does this code do?

This is the heart of the verification engine. A claim can only become CHECKABLE if it has BOTH an evidence_locator and a falsifier. Without both, it stays STRUCTURED forever.

From CHECKABLE, the claim moves to VERIFIED only if actual evidence matches. It moves to FAILED if evidence contradicts. UNKNOWN means the evidence path exists but cannot be reached. ERROR catches any processing crash. Every transition is logged to the ledger.

  • Below transitionState, add the hash utility functions (used by the file verifier in the next step):
// Compute SHA-256 hash of a file using Web Crypto API
async function hashFile(file) {
  const arrayBuffer = await file.arrayBuffer();
  const hashBuffer = await crypto.subtle.digest("SHA-256", arrayBuffer);
  const hashArray = Array.from(new Uint8Array(hashBuffer));
  const hashHex = hashArray.map(b => b.toString(16).padStart(2, "0")).join("");
  return hashHex;
}

// Compute SHA-256 hash of a string (for tests)
async function hashString(str) {
  const encoder = new TextEncoder();
  const data = encoder.encode(str);
  const hashBuffer = await crypto.subtle.digest("SHA-256", data);
  const hashArray = Array.from(new Uint8Array(hashBuffer));
  return hashArray.map(b => b.toString(16).padStart(2, "0")).join("");
}

What does this code do?

These two functions use the browser's built-in Web Crypto API to compute SHA-256 hashes. hashFile reads a file into an ArrayBuffer, hashes it, and returns 64 hex characters. hashString does the same for plain text (used by the test suite). You will wire these to the UI in the next step.

  • Below the hash functions, add the text analysis and rendering logic:
// Orchestrate claim analysis on input text
function analyzeText(text) {
  const sentences = extractClaims(text);
  return sentences.map((s, i) => createContract(s, i));
}

// Render claim cards to the page
function renderContracts(contracts) {
  const el = document.getElementById('claims-output');
  el.innerHTML = contracts.map(c => `
    <div class="claim-card ${c.status}">
      <span class="status-badge ${c.status}">${c.status.toUpperCase()}</span>
      <div class="field">${c.claim_text}</div>
      <div class="field"><span class="field-label">Tokens:</span> ${c.tokens.length > 0 ? c.tokens.map(t => t.type + ':' + t.value).join(', ') : 'none'}</div>
      <div class="field"><span class="field-label">Evidence locator:</span> ${c.evidence_locator || '(not specified)'}</div>
      <div class="field"><span class="field-label">Falsifier:</span> ${c.falsifier || '(not specified)'}</div>
      <div class="field"><span class="field-label">Comparison rule:</span> ${c.comparison_rule || '(not declared)'}</div>
    </div>
  `).join('');
}

What does this code do?

The analyzeText function is the entry point: split text into sentences, generate a contract for each. The renderContracts function builds an HTML card for each claim showing its status badge, detected tokens, and empty contract fields. The card's CSS class matches the status, so the colour-coded border appears automatically.

  • Finally, add the event listener that wires the Analyze button to your engine:
// Wire UI buttons to engine functions
document.addEventListener('DOMContentLoaded', () => {
  document.getElementById('analyze-btn').addEventListener('click', () => {
    const text = document.getElementById('input').value;
    if (!text.trim()) return;
    const contracts = analyzeText(text);
    renderContracts(contracts);
    logToLedger({ action: 'ANALYZE', detail: `${contracts.length} claims extracted` });
  });
});
  • Save engine.js.
  • Refresh index.html in your browser.

Before you test it, predict: if you paste a sentence with a date and a URL, what state should the engine assign? Will it say VERIFIED?

  • Paste the following text into the textarea and click ANALYZE CLAIMS:
On 2026-08-12 Project X v9.9.9 improved 99%. See https://fake.example/proof.

You should see a claim card with an amber left border and the status badge STRUCTURED. The tokens list shows date:2026-08-12, version:9.9.9, percentage:99%, and a URL. The evidence locator and falsifier both say "(not specified)". The claim is STRUCTURED, not VERIFIED.

Why is it STRUCTURED and not VERIFIED?

The sentence contains real-looking tokens (dates, versions, percentages, URLs). A naive system would call this "verified" because it looks official. Your engine correctly identifies these as structure only. Without an evidence locator AND a falsifier, no claim can advance past STRUCTURED. That is the core principle this tool enforces.

Not seeing any claim cards?

Open DevTools (F12) and check the Console for errors. The most common issue is a typo in an element ID. Confirm analyze-btn in your JavaScript matches id="analyze-btn" in your HTML exactly.

Help me debug why clicking Analyze Claims does nothing.

✔️ Awesome, I've got everything!

Great. Confirm you have saved both index.html, style.css, and engine.js. All three files should be in the root of your chain-verify folder.

ⓧ I'd like to double check the full code

Scroll up and confirm each code block is present in the correct file and in order. Your engine.js should contain these sections from top to bottom:

  • STATES constant and TOKEN_PATTERNS array.
  • ledger array, logToLedger(), renderLedger(), extractClaims(), detectTokens().
  • createContract().
  • transitionState().
  • hashFile() and hashString().
  • analyzeText() and renderContracts().
  • DOMContentLoaded listener with the analyze-btn handler.

Your index.html should contain: header, test-panel section, verify-section with textarea and button, file-section with file input and hash fields, ledger-section, and both script tags. Your style.css should contain all selectors from the reset through #hash-output.

Your claim-contract state machine is live. It correctly identifies structure without conflating it with truth. Next, you will build real cryptographic verification using SHA-256 so the engine can actually transition claims to VERIFIED with mathematical certainty.

Build the SHA-256 File Verifier

Your state machine can label claims as STRUCTURED, CHECKABLE, and everything in between. But so far the engine has no path to VERIFIED.

Without a real verification mechanism, the engine's claim that it can verify is itself unverified. In this step you will add cryptographic file hashing using the browser's Web Crypto API, giving the tool a zero-dependency path from CHECKABLE to VERIFIED or FAILED with mathematical certainty.

In this step, get ready to:
  • Implement two SHA-256 hash functions using the browser-native Web Crypto API.
  • Wire a file-upload handler that compares actual vs. expected hashes with exact string equality.
  • Handle edge cases (no file, no hash, read errors) so every code path produces a visible state transition.
Implement the SHA-256 hash functions

The Web Crypto API is built into every modern browser. It provides crypto.subtle.digest which computes cryptographic hashes without any external libraries.

  • In VS Code, open engine.js.
  • Find the closing brace of the transitionState function (the line that says return contract; followed by the final }).
  • Add the following two hash functions directly below it, before the analyzeText function:
async function hashFile(file) {
  // Read the file contents into a binary buffer
  const arrayBuffer = await file.arrayBuffer();
  // Compute the SHA-256 digest using browser-native crypto
  const hashBuffer = await crypto.subtle.digest("SHA-256", arrayBuffer);
  // Convert the binary hash to a readable 64-character hex string
  const hashArray = Array.from(new Uint8Array(hashBuffer));
  const hashHex = hashArray.map(b => b.toString(16).padStart(2, "0")).join("");
  return hashHex;
}

async function hashString(str) {
  // Encode the string as UTF-8 bytes for hashing
  const encoder = new TextEncoder();
  const data = encoder.encode(str);
  // Hash the bytes with SHA-256 (same API, different input)
  const hashBuffer = await crypto.subtle.digest("SHA-256", data);
  const hashArray = Array.from(new Uint8Array(hashBuffer));
  return hashArray.map(b => b.toString(16).padStart(2, "0")).join("");
}

What does this code do?

Both functions follow the same three-step pattern:

  • Convert the input to raw bytes (file.arrayBuffer() for files, TextEncoder for strings).
  • Pass those bytes to crypto.subtle.digest("SHA-256", data) which returns a binary hash buffer.
  • Convert each byte of the buffer to a two-character hex value, producing a 64-character string.

The hashString function exists so the test suite (next step) can generate known hashes without needing a file.

  • Save engine.js.
  • Open index.html in your browser (right-click the file in VS Code and select Open with Live Server, or double-click the file in your file explorer).
  • Open the browser console with Cmd+Option+J (macOS) or Ctrl+Shift+J (Windows).
  • Test the hash function by running:
hashString("test").then(console.log)

You should see a 64-character hex string appear in the console. This confirms the Web Crypto API is working in your browser.

Not seeing any output?

  • Make sure you saved engine.js and refreshed the browser page before testing in the console.
  • If you see a crypto is not defined message, ensure you opened the HTML file via http:// (Live Server) rather than file://. The Web Crypto API requires a secure context.

Need more help? Ask: Why is crypto.subtle undefined in my browser?

Wire up the edge-case guards

Your index.html already has the File Hash Verification section with a file input, an expected-hash text field, and a VERIFY FILE button. Now you need JavaScript that responds when the learner clicks that button.

  • In engine.js, scroll to the bottom and find the DOMContentLoaded event listener.
  • Find the closing }); of the analyze-btn handler (the line after logToLedger({ action: 'ANALYZE').
  • Add the following hash-button handler directly below that closing, still inside the DOMContentLoaded callback:
  document.getElementById('hash-btn').addEventListener('click', async () => {
    const fileInput = document.getElementById('file-input');
    const expectedHash = document.getElementById('expected-hash').value.trim().toLowerCase();
    const output = document.getElementById('hash-output');
    // Guard: no file selected
    if (!fileInput.files[0]) {
      output.innerHTML = '<div class="claim-card error"><span class="status-badge error">ERROR</span> No file selected.</div>';
      return;
    }
    // Guard: no expected hash means verification is impossible
    if (!expectedHash) {
      output.innerHTML = '<div class="claim-card unknown"><span class="status-badge unknown">UNKNOWN</span> No expected hash provided. Cannot verify without expected value.</div>';
      logToLedger({ action: 'HASH_CHECK', detail: 'UNKNOWN: no expected hash' });
      return;
    }
  });

What do the guards do?

  • No file selected: the tool cannot hash nothing, so it shows ERROR immediately.
  • No expected hash: the tool has nothing to compare against, so it shows UNKNOWN. This is the correct state because verification requires both an actual value AND an expected value.
  • Every transition is logged to the correction ledger with a timestamp, making the tool's decisions auditable.
  • Save engine.js and refresh the browser.
  • Click the VERIFY FILE button without selecting a file.

You should see a purple-bordered ERROR card that says "No file selected."

  • Now select any file from your computer, leave the expected hash field empty, and click VERIFY FILE again.

You should see a grey-bordered UNKNOWN card explaining it cannot verify without an expected value.

Button does nothing when clicked?

  • Check that your new handler is inside the DOMContentLoaded callback (between its opening () => { and its final });), not outside it.
  • Make sure the element ID matches exactly: hash-btn in your JavaScript must match id="hash-btn" in your HTML.

Still stuck? Why isn't my hash-btn click handler firing?

Add the hash comparison logic

The guards catch bad input. Now you need the core logic that computes the file's actual hash and compares it to the expected value.

  • In the handler you just added, find the closing }); of the hash-btn listener.
  • Replace that entire }); line with the following try/catch block plus the new closing }); at the end:
    try {
      // Compute actual file hash and compare with exact string equality
      const actualHash = await hashFile(fileInput.files[0]);
      const match = actualHash === expectedHash;
      const status = match ? 'verified' : 'failed';
      output.innerHTML = `
        <div class="claim-card ${status}">
          <span class="status-badge ${status}">${status.toUpperCase()}</span>
          <div class="field"><span class="field-label">Expected:</span> ${expectedHash}</div>
          <div class="field"><span class="field-label">Actual:</span> ${actualHash}</div>
          <div class="field"><span class="field-label">Comparison:</span> exact string equality (discrete value)</div>
          <div class="field"><span class="field-label">Verdict:</span> ${match ? 'Hashes match. File integrity confirmed.' : 'MISMATCH. File does not match expected hash.'}</div>
        </div>`;
      logToLedger({ action: 'HASH_CHECK', detail: `${status.toUpperCase()}: ${fileInput.files[0].name}` });
    } catch (e) {
      // File read error transitions to ERROR state
      output.innerHTML = `<div class="claim-card error"><span class="status-badge error">ERROR</span> ${e.message}</div>`;
      logToLedger({ action: 'HASH_CHECK', detail: `ERROR: ${e.message}` });
    }
  });

What does this code do?

  • It calls hashFile() to compute the SHA-256 hash of the uploaded file.
  • It compares the actual hash to the expected hash using exact string equality. No tolerance, no "close enough." The hash either matches character-for-character or it does not.
  • It displays BOTH hashes so the user can see the evidence for themselves, not just a pass/fail label.
  • If the file cannot be read (corrupted, permission error), the catch block transitions to ERROR and logs the reason.

This is the only path in the engine that reaches VERIFIED. It requires real cryptographic evidence, not tokens or structure.

  • Save engine.js.

Now test the full verification flow. You need a file and its known SHA-256 hash.

  • Open the browser console with Cmd+Option+J (macOS) or Ctrl+Shift+J (Windows).
  • Create a test file by running this in your terminal:
echo -n "hello verification" > test-payload.txt
  • Get the known hash of that file by running this in the browser console:
hashString("hello verification").then(h => console.log(h))
  • Copy the 64-character hex string that appears in the console.
  • Refresh the page.
  • In the File Hash Verification section, click the file input and select test-payload.txt from your Desktop (inside the chain-verify folder).
  • Paste the copied hash into the Expected SHA-256 hash field.

Before you click VERIFY FILE, predict: will this show VERIFIED or FAILED?

  • Click VERIFY FILE.

You should see a green-bordered VERIFIED card showing both the expected and actual hashes matching. The comparison rule reads "exact string equality (discrete value)."

  • Now change one character in the expected hash field (replace any digit with a 0) and click VERIFY FILE again.

You should see a red-bordered FAILED card with "MISMATCH" in the verdict. Both hashes are displayed so you can see exactly where they differ.

Seeing ERROR instead of VERIFIED?

  • Make sure you created test-payload.txt with echo -n (the -n flag prevents adding a trailing newline). A newline changes the hash completely.
  • Confirm that you are selecting the correct file in the file picker. The hash you computed was for the exact string "hello verification" with no extra characters.

Still stuck? Why is my file hash not matching the expected hash?

Why exact string equality?

SHA-256 produces a discrete 64-character hex value. There is no concept of "close enough" for a hash. Either every character matches, or the file has been modified.

This is the comparison rule that the engine declares for this verification path. A different claim type (like a measured temperature) might declare a tolerance. The key principle: every claim must declare its comparison rule explicitly.

  • Scroll down to the Correction Ledger section on the page.

You should see timestamped log entries for each action: UNKNOWN (if you tested the guard earlier), VERIFIED for the matching hash, and FAILED for the mismatched one. Every state transition is auditable.

✔️ Awesome, I've got everything!

Great. Double check you have saved engine.js with both hash functions and the complete hash-btn handler.

ⓧ I'd like to double check the full code

Your engine.js should now contain all of the following chunks in order. Scroll through and confirm each is present:

  • STATES object and TOKEN_PATTERNS array (from Step 2).
  • ledger array, logToLedger(), and renderLedger() (from Step 2).
  • extractClaims(), detectTokens(), createContract(), and transitionState() (from Step 2).
  • hashFile() and hashString() (added this step).
  • analyzeText() and renderContracts() (from Step 2).
  • DOMContentLoaded listener with both the analyze-btn handler (Step 2) and the hash-btn handler (this step).

Your engine now has a real verification path. A claim can go from CHECKABLE to VERIFIED only when cryptographic evidence proves it. Next up, you will write 8 frozen adversarial tests that prove this engine catches fake specificity before anyone trusts it.

Wire the Frozen Adversarial Tests

Your engine can extract claims, assign states, and verify file hashes. But how does anyone know it actually works?

An untested verification engine is itself an unverified claim. In this step, you will write 8 frozen adversarial tests that run on page load BEFORE anyone trusts the tool. Any failure gets reported prominently. The engine must prove itself before it earns your trust.

In this step, get ready to:
  • Write 8 adversarial tests that exercise every state transition path.
  • Build a test runner that displays results visibly on page load.
  • Push to GitHub and verify all 8 tests pass on your live Vercel URL.
Write the first detection tests (T1-T2)

The first two tests validate the most basic boundary: does the engine correctly sort claims with no structured tokens from claims loaded with fake specificity?

  • Open tests.js in VS Code. It is currently empty.
  • Start building the test suite by pasting this code:
async function runFrozenTests() {
  const results = [];

  const t1 = createContract("wrong", 0);
  results.push({
    id: 'T1',
    desc: '"wrong" (6 bytes) must be UNSTRUCTURED',
    expected: 'unstructured',
    actual: t1.status,
    pass: t1.status === STATES.UNSTRUCTURED
  });

  const t2 = createContract("On 2026-08-12 Project X v9.9.9 improved 99%. See https://fake.example/proof.", 1);
  results.push({
    id: 'T2',
    desc: 'Fake dates/URLs must be at most STRUCTURED, never VERIFIED',
    expected: 'structured (not verified)',
    actual: t2.status,
    pass: t2.status === STATES.STRUCTURED
  });

What do T1 and T2 prove?

T1 feeds the word "wrong" into the engine. It has no dates, URLs, versions, or measurements. The engine must classify it as UNSTRUCTURED.

T2 is the critical test. The sentence is packed with impressive-looking tokens (a date, a version, a percentage, a URL). A naive system might call this VERIFIED because it looks so specific. Your engine correctly stops at STRUCTURED because having tokens is not evidence.

Add the state transition tests (T3-T4)

T3 proves that falsifiability comes from having a procedure, not from containing numbers. T4 proves the engine can reach VERIFIED when real cryptographic evidence confirms a claim.

  • In tests.js, paste the following directly below the T2 results.push closing:

  const t3 = createContract("Every swan in this enclosure is white.", 2);
  t3.evidence_locator = "visual inspection of enclosure";
  t3.falsifier = "one non-white swan falsifies";
  t3.comparison_rule = "universal quantifier: all must satisfy predicate";
  transitionState(t3, undefined);
  results.push({
    id: 'T3',
    desc: 'Claim with falsifier + evidence procedure = CHECKABLE (no numbers needed)',
    expected: 'unknown',
    actual: t3.status,
    pass: t3.status === STATES.UNKNOWN || t3.status === STATES.CHECKABLE
  });

  const testString = "chain-verify-test-payload";
  const knownHash = await hashString(testString);
  const t4Contract = createContract(`File contains "${testString}"`, 3);
  t4Contract.evidence_locator = "SHA-256 hash comparison";
  t4Contract.falsifier = "hash mismatch";
  t4Contract.comparison_rule = "exact string equality (64-char hex)";
  transitionState(t4Contract, { match: true });
  results.push({
    id: 'T4',
    desc: 'Correct SHA-256 hash = VERIFIED',
    expected: 'verified',
    actual: t4Contract.status,
    pass: t4Contract.status === STATES.VERIFIED
  });

What do T3 and T4 prove?

T3 is "Every swan in this enclosure is white." It contains zero numbers. But it has both an evidence locator (visual inspection) and a falsifier (one non-white swan). That makes it CHECKABLE. Since the evidence is unreachable (no one physically inspected), it transitions to UNKNOWN.

T4 creates a known string, hashes it with SHA-256, then passes the correct hash as evidence. The engine transitions to VERIFIED because the cryptographic proof matches.

Add the failure and edge case tests (T5-T8)

The remaining four tests cover what happens when evidence is unreachable, when processing crashes, when floating-point math lies, and when sources contradict each other.

  • Paste the following below the T4 results.push closing:

  const t5 = createContract("Data at https://unreachable.internal/api/data confirms 42.", 4);
  t5.evidence_locator = "https://unreachable.internal/api/data";
  t5.falsifier = "response != 42";
  t5.comparison_rule = "exact integer equality";
  transitionState(t5, undefined);
  results.push({
    id: 'T5',
    desc: 'Valid locator but no access = UNKNOWN',
    expected: 'unknown',
    actual: t5.status,
    pass: t5.status === STATES.UNKNOWN
  });

  const t6 = createContract("This will error.", 5);
  t6.evidence_locator = "local";
  t6.falsifier = "any";
  transitionState(t6, { error: "Simulated reader crash" });
  results.push({
    id: 'T6',
    desc: 'Evidence reader crash = ERROR',
    expected: 'error',
    actual: t6.status,
    pass: t6.status === STATES.ERROR
  });

What do T5 and T6 prove?

T5 has a syntactically valid evidence locator (a URL) that the browser cannot reach. The engine must not guess. It reports UNKNOWN.

T6 forces a processing error during evidence evaluation. The engine catches it and transitions to ERROR rather than crashing or silently proceeding.

  • Now paste the final two tests and close the function. Add this directly below T6:

  const t7sum = 0.1 + 0.2;
  const t7expected = 0.3;
  const t7exactMatch = t7sum === t7expected;
  results.push({
    id: 'T7',
    desc: '0.1 + 0.2 vs 0.3 under zero tolerance must REJECT (not exactly equal in binary)',
    expected: 'reject (false)',
    actual: t7exactMatch ? 'accept (true)' : 'reject (false)',
    pass: t7exactMatch === false
  });

  const t8a = createContract("The server has 99.99% uptime.", 6);
  const t8b = createContract("The server experienced 12 hours of downtime this week.", 7);
  t8a.evidence_locator = "monitoring dashboard";
  t8a.falsifier = "uptime < 99.99%";
  t8b.evidence_locator = "incident log";
  t8b.falsifier = "no downtime recorded";
  results.push({
    id: 'T8',
    desc: 'Contradictory claims must not both be VERIFIED',
    expected: 'neither verified',
    actual: `a:${t8a.status}, b:${t8b.status}`,
    pass: t8a.status !== STATES.VERIFIED && t8b.status !== STATES.VERIFIED
  });

  renderTests(results);
  return results;
}

What do T7 and T8 prove?

T7 exploits IEEE 754 floating-point arithmetic. In JavaScript, 0.1 + 0.2 does not equal 0.3 in binary. Exact equality correctly rejects this. A loose tolerance would let it through.

T8 presents two contradictory claims about the same server (99.99% uptime AND 12 hours downtime). The engine must never give both VERIFIED status, because that would mean the system verified a contradiction.

Wire the test runner and see results

The 8 tests are defined but have no way to display. The runner renders each result into the test panel and fires automatically on page load.

  • Below the closing } of runFrozenTests, paste the display function and bootstrap listener:

function renderTests(results) {
  const el = document.getElementById('test-results');
  const allPass = results.every(r => r.pass);
  el.innerHTML = results.map(r => `
    <div class="${r.pass ? 'test-pass' : 'test-fail'}">
      ${r.pass ? 'PASS' : 'FAIL'} ${r.id}: ${r.desc}
      ${!r.pass ? `<br>&nbsp;&nbsp;Expected: ${r.expected} | Got: ${r.actual}` : ''}
    </div>
  `).join('') + `<div style="margin-top:0.5rem;color:${allPass ? '#00ff88' : '#ff4444'}">
    ${allPass ? 'ALL 8 TESTS PASS. Engine verified.' : 'TEST FAILURES DETECTED. Review above.'}
  </div>`;
}

document.addEventListener('DOMContentLoaded', () => {
  runFrozenTests();
});

What does this code do?

The renderTests function iterates over the results array and injects PASS/FAIL lines into the test-results div. Failures show in red with expected vs actual values.

The DOMContentLoaded listener calls runFrozenTests() the moment the page loads. The tests run before the user touches anything.

  • Save tests.js.

Before you open the page, predict: will all 8 tests pass on the first try, or will one fail?

  • Open index.html in your browser (double-click the file, or use Live Server in VS Code).

You should see the Frozen Adversarial Tests panel at the top of the page with 8 green PASS lines and the message "ALL 8 TESTS PASS. Engine verified."

Seeing FAIL on any test?

Check that engine.js loads before tests.js in your index.html. The tests call createContract and transitionState which are defined in engine.js.

If you see a console error about hashString not being defined, confirm that the hashString function exists in engine.js from the previous step.

help me debug my frozen tests

Commit, push, and verify on Vercel

The tests pass locally. Now push to GitHub so Vercel auto-deploys the self-proving version.

  • Commit and push your test suite by running these commands in your terminal:
git add . && git commit -m "feat: frozen adversarial test suite (T1-T8)" && git push

What does this command do?

This stages all changes, creates a commit with the message describing the feature, and pushes to your main branch on GitHub. Vercel detects the push and auto-deploys within 30-60 seconds.

  • Wait about 30 seconds for the deployment to complete.
  • Open your live Vercel URL in the browser.

You should see the same 8 PASS results at your live URL, proving the engine works in production over HTTPS.

Tests pass locally but not on Vercel?

Make sure your git push succeeded. Check the Vercel dashboard for a new deployment. If the deployment shows an error, confirm all four files (index.html, engine.js, tests.js, style.css) are committed.

help me fix my Vercel deployment

✔️ Awesome, I've got everything!

Great. Double check you have saved tests.js and that all 8 tests pass both locally and on your live Vercel URL.

ⓧ I'd like to double check the full code

Here is the complete tests.js file. Confirm your code matches:

Your file should contain these sections in order:

  • The runFrozenTests() function opening with const results = [].
  • Tests T1 and T2 (UNSTRUCTURED and STRUCTURED checks).
  • Tests T3 and T4 (CHECKABLE/UNKNOWN and VERIFIED via SHA-256).
  • Tests T5 and T6 (UNKNOWN for unreachable evidence, ERROR for crashes).
  • Tests T7 and T8 (float rejection and contradiction detection).
  • The function closing with renderTests(results); return results; }.
  • The renderTests() function.
  • The DOMContentLoaded listener that calls runFrozenTests().

Your engine now proves itself on every page load. Next up, you will deploy the final production version and verify the complete system end-to-end.

Deploy and Verify the Live System

Your engine passes all 8 adversarial tests and handles every state transition locally. That proves the logic works on your machine, but it does not prove the tool works for anyone else.

The tool must prove itself in production, over HTTPS, in a browser you do not control. Vercel already auto-deployed your last push. This step tags your v1.0.0 release and runs a comprehensive verification in production to prove the tool survives the real world.

In this step, get ready to:
  • Confirm zero external dependencies in your project files.
  • Push the v1.0.0 release commit.
  • Verify all features work in the live production deployment.
Run a final code review

The Web Crypto API only works in secure contexts (HTTPS). If your page loaded a CDN script or an npm-bundled dependency that broke under HTTPS, your SHA-256 verification would silently fail. A final code review confirms the tool stands alone.

  • Switch back to VS Code with your chain-verify folder open.
  • Open index.html and confirm the three <script> and <link> references: engine.js, tests.js, and style.css.
  • Confirm there are zero CDN links (no <script src="https://..."> tags pointing to external servers).
  • Confirm there is no package.json or node_modules folder. The tool uses only browser-native APIs.
  • Open engine.js and confirm the crypto.subtle.digest call uses no polyfill or import. It relies on the browser's built-in Web Crypto API.
  • Verify your folder only contains the expected files by running this command in your terminal:
ls

You should see exactly: index.html, engine.js, tests.js, style.css, and a README.md. Nothing else.

Why zero external dependencies?

Every external dependency is an unverified claim. A CDN link is a promise that a third-party server will respond correctly, forever. If that server goes down, your verification tool stops working.

Browser-native APIs (Web Crypto, File API, DOM) ship with the browser itself. They require no network fetch, no version compatibility, and no trust in a third party. The tool verifies claims. It must not itself depend on unverifiable claims.

Push the v1.0.0 release
  • Save all open files in VS Code with Cmd+S (macOS) or Ctrl+S (Windows).
  • Tag the release by running these commands in your terminal:
git add . && git commit -m "v1.0.0: claim-contract verification engine" && git push

What does this command do?

  • git add . stages any changes made during your code review.
  • git commit -m "v1.0.0: ..." marks this as the official release version.
  • git push sends the commit to GitHub, which triggers Vercel to auto-deploy.

Wait 30-60 seconds for Vercel to finish deploying. You can check the status in your Vercel dashboard, but the deployment usually completes quickly.

Seeing "nothing to commit"?

If git reports that there is nothing to commit, everything was already saved in your previous push. That is fine. Your last deployment is already live. Run the version tag as an empty commit instead:

git commit --allow-empty -m "v1.0.0: claim-contract verification engine" && git push

This creates a commit with no file changes, purely to mark the release point in your history.

Why is git saying nothing to commit when I expected changes?

Verify the deployed version in production

The deployment itself is the final verification. A tool that tests claims must survive being tested in the real world, not just on localhost.

  • Open your live Vercel URL in your browser.

Before you look at the test panel, predict: do you think all 8 tests will pass in production exactly as they did locally? Why or why not?

Now look at the test panel at the top of the page. You should see all 8 tests pass with the green message: "ALL 8 TESTS PASS. Engine verified."

Why do the tests run on every page load?

The tests are not a developer convenience you hide from users. They are the engine proving its own claims BEFORE the user trusts it. If any test ever fails, the failure is displayed prominently. The engine eats its own dogfood.

Now run through each verification scenario to confirm the full tool works in production.

  • Paste this text into the text area and click ANALYZE CLAIMS:
On 2026-08-12 Project X v9.9.9 improved 99%. See https://fake.example/proof.

Each claim should show a yellow STRUCTURED badge. Tokens are detected (dates, versions, URLs, percentages). But the status never reaches VERIFIED because no evidence was actually interrogated.

  • Scroll down to the File Hash Verification section.
  • Create a simple test file on your computer. Open any text editor, type hello, and save it as test.txt on your Desktop.
  • Get the SHA-256 hash of your file by running this in your terminal:
shasum -a 256 ~/Desktop/test.txt
  • Copy the 64-character hex string from the output.
  • In the live tool, click the file input and select your test.txt file.
  • Paste the correct hash into the Expected SHA-256 hash field.
  • Click VERIFY FILE.

You should see a green VERIFIED badge. Both hashes are displayed side by side, proving they match via exact string equality.

  • Now change one character in the expected hash field (e.g. change the last character to 0).
  • Click VERIFY FILE again.

You should see a red FAILED badge. The mismatch is undeniable because both hashes are shown. One altered character is enough to flip VERIFIED to FAILED. That is what cryptographic verification means.

  • Check the Correction Ledger at the bottom of the page. You should see timestamped entries for each state transition and analysis you performed.
  • Copy your live URL and send it to another person. Ask them to paste some text and verify a file. Confirm the tool works on their device too.

What about the swan claim (T3)?

T3 in the test panel already proves that a claim with an evidence_locator and falsifier transitions to CHECKABLE, then to UNKNOWN when evidence cannot be provided. You can see T3 passing in the test results at the top of the page.

The text analyzer creates contracts without falsifiers because determining what falsifies an arbitrary claim requires human judgment. That is the gap between STRUCTURED (tokens detected) and CHECKABLE (falsification procedure declared).

File hash showing ERROR?

The Web Crypto API requires HTTPS. If you are testing on http://localhost instead of your Vercel URL, crypto.subtle may be undefined. Use your live Vercel URL which serves over HTTPS automatically.

If the file input is not responding, make sure you actually selected a file before clicking VERIFY FILE.

Help me debug why my file hash verification shows ERROR on the live Vercel URL

Your Claim-Contract Verification Engine is live, self-testing, and provably correct. Anyone with the URL can paste AI output and see the difference between looking verified and being verified. Next up: the Secret Mission challenges you to prove that even mathematically close values can fail verification when the comparison rule demands it.

Secret mission

Build the Near-Truth Detector

Your engine distinguishes STRUCTURED from VERIFIED. But can it catch a value that LOOKS like -1 but isn't? Euler's identity gives you a mathematically certain -1. The complex near-miss pi^(ie) gives you approximately -0.9995 + 0.0299i. Same ingredients, different sequence, different truth. Build the demo that proves comparison rules must be declared explicitly, or the architecture fails silently.

Clean Up Your Resources

Clean Up Your Resources

Decide whether to keep your resources running, pause them to come back later, or delete them entirely. This project runs entirely on free services with no ongoing costs.

Resources you used:

  • GitHub repository: chain-verify (public, free tier).
  • Vercel deployment (free tier, auto-deploys from GitHub).
  • Local project folder: chain-verify on your Desktop.

Keep everything running

No action needed. Choose this if you want to keep your live Verification Engine accessible and continue experimenting with it.

  • Your Vercel deployment stays live indefinitely on the free tier. No maintenance required.
  • Your GitHub repo remains public. Anyone with the link can view your source code.
  • Your local chain-verify folder stays on your Desktop for future edits.

Pause - I'll come back to this later

Since everything is free, pausing looks the same as keeping. The only action is freeing up local disk space if you need it.

  • Your Vercel deployment and GitHub repo cost nothing to keep running. Leave them as-is.
  • If you want to free local disk space, delete the local folder. You can re-clone from GitHub any time by running:
cd ~/Desktop
git clone https://github.com/gamertotrader/chain-verify.git

Everything else stays live and ready for when you return.

Delete - I don't want to use this again

Remove all project resources completely. This cannot be undone.

  • Delete the local project folder by running this in your terminal:
cd ~/Desktop
rm -rf chain-verify
  • Delete the Vercel deployment: go to vercel.com, open your chain-verify project, click Settings, scroll to the bottom, and click Delete Project.
  • Delete the GitHub repository: go to github.com/gamertotrader/chain-verify, click Settings, scroll to the bottom under Danger Zone, and click Delete this repository.

Nice Work!

Nice Work!

You just built and deployed a zero-cost Claim-Contract Verification Engine that proves "close enough" is not verification. Anyone with your live URL can paste AI-generated text and see exactly where confident-sounding claims fall short of actual proof.

You've learned how to:

  • Build a 6-state claim-contract verification engine that distinguishes STRUCTURED from CHECKABLE from VERIFIED, deployed live on Vercel where anyone can paste AI output and see claims decomposed into falsifiable contracts.
  • Implement real SHA-256 file verification using the browser-native Web Crypto API at zero cost, transitioning claims to VERIFIED or FAILED with cryptographic certainty.
  • Wire 8 frozen adversarial tests that run on page load and prove the engine catches fake specificity, unreachable evidence, processing errors, and contradictions before you trust it.
  • Secret Mission: Proved mathematically that pi^(ie) passes casual inspection but fails domain-aware equality, demonstrating why comparison rules must be declared explicitly with every verification.

Ready to quiz yourself?