Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 66 additions & 0 deletions .github/workflows/pages.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
name: Deploy MachineOutcome live demo

on:
workflow_dispatch:

permissions:
contents: read

concurrency:
group: pages
cancel-in-progress: false

jobs:
verify-and-build:
runs-on: ubuntu-24.04
timeout-minutes: 8
steps:
- name: Checkout
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
with:
persist-credentials: false

- name: Setup Node
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
with:
node-version: "24"

- name: Verify reference and live demo
run: npm test

- name: Build minimal static artifact
run: |
set -euo pipefail
mkdir -p _site/src
cp index.html app.js styles.css _site/
cp src/reference-outcome-verifier.mjs src/demo-model.mjs _site/src/
test -s _site/index.html
test -s _site/app.js
test -s _site/styles.css
test -s _site/src/reference-outcome-verifier.mjs
test -s _site/src/demo-model.mjs

- name: Upload Pages artifact
uses: actions/upload-pages-artifact@7b1f4a764d45c48632c6b24a0339c27f5614fb0b
with:
path: _site

deploy:
needs: verify-and-build
permissions:
pages: write
id-token: write
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-24.04
timeout-minutes: 8
steps:
- name: Configure Pages
uses: actions/configure-pages@983d7736d9b0ae728b81ab479565c72886d7745b
with:
enablement: true

- name: Deploy Pages
id: deployment
uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# MachineOutcome - verify what actually happened before retrying

[**Browser lab for this release →**](https://samct86.github.io/machineoutcome-case-study/)

After the Pages deployment for this release completes, no install, account, credential or real external write is required. The browser lab uses the same bounded verifier exported by this repository and runs entirely on synthetic state.

[![verify-reference](https://github.com/SamCT86/machineoutcome-case-study/actions/workflows/verify-reference.yml/badge.svg)](https://github.com/SamCT86/machineoutcome-case-study/actions/workflows/verify-reference.yml)

MachineOutcome is about a simple but important failure mode in automation: an API call can time out even though the external system already changed.
Expand Down Expand Up @@ -36,6 +40,10 @@ Start with **2–3 sentences** describing what is slow, expensive or unreliable.

## Try it

**Fastest path after the Pages deployment completes:** open the [browser lab](https://samct86.github.io/machineoutcome-case-study/) and run **Timeout, but change happened**. Then switch to **No readback** and **Wrong-attempt evidence** to see the safe action change.

**Technical verification from a clean clone:**

```bash
git clone https://github.com/SamCT86/machineoutcome-case-study.git
cd machineoutcome-case-study
Expand Down
84 changes: 84 additions & 0 deletions app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { verifyMutationOutcome } from "./src/reference-outcome-verifier.mjs";
import { SCENARIOS, escapeHtml, evaluateControls } from "./src/demo-model.mjs";

const form = document.querySelector("#lab-form");
const presets = document.querySelector("#presets");
const result = document.querySelector("#result");
const validation = document.querySelector("#validation");
const resetButton = document.querySelector("[data-reset]");
const guidedButton = document.querySelector("[data-run-guided]");
let selected = "ambiguousVerified";

function setControls(values) {
for (const [key, value] of Object.entries(values)) form.elements[key].value = value;
}

function readControls() {
return Object.fromEntries(["ack", "prestate", "readback", "poststate", "identity"].map((key) => [key, form.elements[key].value]));
}

function renderPresets() {
presets.innerHTML = "";
for (const [key, scenario] of Object.entries(SCENARIOS)) {
const button = document.createElement("button");
button.type = "button";
button.className = "preset";
button.dataset.scenario = key;
button.setAttribute("aria-pressed", String(key === selected));
button.innerHTML = '<b>' + escapeHtml(scenario.title) + '</b><span>' + escapeHtml(scenario.hint) + '</span>';
button.addEventListener("click", () => {
selected = key;
setControls(scenario.controls);
validation.textContent = "";
renderPresets();
evaluate(scenario.controls);
});
presets.append(button);
}
}

function evaluate(controls) {
validation.textContent = "";
try {
const evaluation = evaluateControls(controls, verifyMutationOutcome);
result.innerHTML = evaluation.markup;
return evaluation.output;
} catch (error) {
validation.textContent = "VERIFIER_REFUSED_INPUT: " + (error instanceof Error ? error.message : String(error));
result.innerHTML = '<div class="empty-state"><p class="eyebrow">FAIL CLOSED</p><h3>Invalid evidence contract.</h3><p>The public verifier rejected the input instead of guessing.</p></div>';
return null;
}
}

form.addEventListener("change", (event) => {
if (!(event.target instanceof HTMLSelectElement)) return;
selected = "custom";
renderPresets();
evaluate(readControls());
});

form.addEventListener("submit", (event) => {
event.preventDefault();
selected = "custom";
renderPresets();
evaluate(readControls());
});

resetButton.addEventListener("click", () => {
selected = "ambiguousVerified";
setControls(SCENARIOS.ambiguousVerified.controls);
renderPresets();
validation.textContent = "";
result.innerHTML = '<div class="empty-state"><p class="eyebrow">READY</p><h3>Run an ambiguous action.</h3><p>You will get an outcome status, reason, safe retry decision, evidence trace and the exact verifier input/output.</p></div>';
});

guidedButton.addEventListener("click", () => {
selected = "ambiguousVerified";
setControls(SCENARIOS.ambiguousVerified.controls);
renderPresets();
evaluate(SCENARIOS.ambiguousVerified.controls);
document.querySelector("#lab").scrollIntoView({ behavior: "smooth", block: "start" });
});

setControls(SCENARIOS.ambiguousVerified.controls);
renderPresets();
115 changes: 115 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<meta name="theme-color" content="#111827" />
<meta name="description" content="Interactive synthetic MachineOutcome lab: verify what actually happened before an agent retries an external action." />
<title>MachineOutcome — Outcome Verification Lab</title>
<link rel="stylesheet" href="./styles.css" />
</head>
<body>
<a class="skip-link" href="#lab">Skip to lab</a>
<header class="topbar">
<a class="brand" href="./">MachineOutcome</a>
<nav aria-label="Primary">
<a href="#why">Why it matters</a>
<a href="#lab">Lab</a>
<a href="#proof">Proof boundary</a>
</nav>
<span class="status-pill">PUBLIC · SYNTHETIC · NO WRITES</span>
</header>

<main>
<section class="hero shell">
<div class="hero-copy">
<p class="eyebrow">OUTCOME VERIFICATION FOR AGENTIC SYSTEMS</p>
<h1>A timeout is not a failure. Prove what happened before retrying.</h1>
<p class="lede">MachineOutcome demonstrates one reliability rule: when an external action has an ambiguous result, <strong>read back the real state before success or retry is trusted.</strong></p>
<div class="hero-actions">
<button class="button primary" type="button" data-run-guided>Run the 30-second failure</button>
<a class="button secondary" href="#lab">Break it yourself</a>
</div>
<div class="trust-row" aria-label="Demo boundaries">
<span>✓ no credentials</span>
<span>✓ no network calls</span>
<span>✓ no real writes</span>
<span>✓ canonical public verifier</span>
</div>
</div>
<aside class="preview" aria-label="Ambiguous mutation preview">
<div class="preview-head"><span>attempt / repo-change-001</span><span class="dot">synthetic</span></div>
<ol>
<li><span>01</span><b>MUTATION</b><em>attempted</em></li>
<li><span>02</span><b>ACK</b><em class="warn">timeout</em></li>
<li><span>03</span><b>READBACK</b><em>expected state</em></li>
<li><span>04</span><b>OUTCOME</b><em class="good">verified</em></li>
<li><span>05</span><b>RETRY</b><em class="blocked">blocked</em></li>
</ol>
<p>transport certainty: <strong>low</strong><br />outcome certainty: <strong>verified by state</strong></p>
</aside>
</section>

<section id="why" class="why-band">
<div class="shell">
<p class="eyebrow light">THE FAILURE MODE</p>
<div class="why-grid">
<article><span>1</span><h2>Action leaves your process</h2><p>An agent asks another system to mutate something.</p></article>
<article><span>2</span><h2>Acknowledgement disappears</h2><p>The request times out after the external system may already have changed.</p></article>
<article><span>3</span><h2>Naive retry duplicates work</h2><p>Treating silence as failure can create a second write, deploy, payment or message.</p></article>
<article><span>4</span><h2>Read reality first</h2><p>Observed post-state decides whether success or retry is safe.</p></article>
</div>
</div>
</section>

<section id="lab" class="lab shell">
<div class="section-heading">
<div>
<p class="eyebrow">TRY IT WITHOUT INSTALLING ANYTHING</p>
<h2>Change the evidence. Watch the safe action change.</h2>
</div>
<p>This lab calls the same bounded verifier exported by this public repository. It never contacts a provider and never performs a mutation.</p>
</div>

<div class="presets" id="presets" role="group" aria-label="Scenario presets"></div>

<div class="lab-grid">
<form id="lab-form" class="controls">
<div class="panel-title">SIMULATED EVIDENCE</div>
<label><span>Provider acknowledgement</span><small>Did the transport say the action was accepted?</small><select name="ack"><option value="accepted">Accepted</option><option value="ambiguous">Timeout / ambiguous</option></select></label>
<label><span>Starting state</span><small>Did reality still match the state the action was allowed to start from?</small><select name="prestate"><option value="match">Matches expected pre-state</option><option value="stale">Changed before the action</option></select></label>
<label><span>Provider readback</span><small>Were we able to read the external state after the attempt?</small><select name="readback"><option value="complete">Complete</option><option value="incomplete">Incomplete / unavailable</option></select></label>
<label><span>Observed post-state</span><small>What did the external system actually show?</small><select name="poststate"><option value="expected">Expected new state</option><option value="unchanged">Still at the old state</option><option value="unexpected">Different unexpected state</option></select></label>
<label><span>Evidence identity</span><small>Does the readback belong to this exact task and attempt?</small><select name="identity"><option value="exact">Exact task + exact attempt</option><option value="wrong-attempt">Wrong attempt</option><option value="wrong-task">Wrong task</option></select></label>
<div class="actions"><button class="button primary" type="submit">Verify outcome</button><button class="button quiet" type="button" data-reset>Reset</button></div>
<p id="validation" class="validation" role="alert" aria-live="polite"></p>
</form>

<section id="result" class="result" aria-live="polite">
<div class="empty-state"><p class="eyebrow">READY</p><h3>Run an ambiguous action.</h3><p>You will get an outcome status, reason, safe retry decision, evidence trace and the exact verifier input/output.</p></div>
</section>
</div>
</section>

<section id="proof" class="proof shell">
<div class="section-heading">
<div><p class="eyebrow">WHAT THIS PROVES</p><h2>Small enough to inspect. Strict enough to fail closed.</h2></div>
<p>The public lab demonstrates one invariant. It is not the private MachineOutcome runtime and it does not claim customer adoption, ROI or universal agent reliability.</p>
</div>
<div class="proof-grid">
<article><span>01</span><h3>Bind evidence</h3><p>Wrong-task and wrong-attempt evidence cannot prove the action.</p></article>
<article><span>02</span><h3>Bind pre-state</h3><p>A stale starting state blocks the mutation boundary.</p></article>
<article><span>03</span><h3>Read after ambiguity</h3><p>Transport timeout does not decide the real outcome.</p></article>
<article><span>04</span><h3>Keep UNKNOWN</h3><p>Missing or ambiguous reality stays unresolved instead of being guessed into success or failure.</p></article>
</div>
<div class="proof-actions">
<a class="button secondary" href="https://github.com/SamCT86/machineoutcome-case-study">Inspect the source and tests</a>
<a class="button secondary" href="https://github.com/SamCT86/machineoutcome-case-study/blob/main/PUBLIC_BOUNDARY.md">Read the public/private boundary</a>
</div>
</section>
</main>

<footer><div class="shell footer-inner"><div><b>MachineOutcome</b><span>Public synthetic verification lab</span></div><a href="https://github.com/SamCT86/machineoutcome-case-study">GitHub repository</a></div></footer>
<script type="module" src="./app.js"></script>
</body>
</html>
Loading
Loading