DEV Community

Haley
Haley

Posted on

Put a Tamper-Evident Receipt on Your AI Design Demo Before You Demo It

Last week I watched a teammate demo an AI design proposal. It looked polished. The problem arrived moments later: someone asked which model produced it, what it had rejected along the way, and what evidence we had if a stakeholder pushed back. We had none. I froze.

The past week's AI-detection chatter made me think. People are asking whether text is AI-generated, but product teams are still presenting AI work without the more boring provenance we control ourselves. A watermark alone won't tell you whether the model that produced your interface is the one your team approved, what alternative it discarded, or where a human changed the output.

I wanted a tiny, tamper-evident review receipt for an AI design demo.

MonkeyCode is an open-source project that currently includes a free 30M-token allowance and a free server option. Disclosure: This article was prepared as part of MonkeyCode's product outreach. I used its free server as a sandbox to attach the receipt below; this is not a production safety case. The token allowance matters less than the free server for this exercise, because the trick is all in the record, not the model size.

What goes on the receipt

I kept the receipt small. For every agent decision, I capture:

  • the model ID,
  • a hash of the prompt,
  • a hash of the selected output,
  • hashes of rejected alternatives,
  • the stop condition,
  • a hash of the previous receipt entry.

The prompt is stored as a hash because the full text may contain private user input. The rejected alternatives are stored as hashes too, so a reviewer can see that alternatives existed without paging through an unreadable dump.

The sandbox server

Here is the small server sketch I ran on the free server:

import crypto from 'node:crypto';
import express from 'express';
import fs from 'node:fs/promises';

const app = express();
app.use(express.json({ limit: '1mb' }));

const hash = (value) => crypto.createHash('sha256').update(JSON.stringify(value)).digest('hex');

app.post('/receipts', async (req, res) => {
  const entry = {
    at: new Date().toISOString(),
    modelId: req.body.modelId ?? 'unspecified',
    promptHash: hash(req.body.prompt ?? ''),
    selectedHash: hash(req.body.selected ?? {}),
    rejectedHashes: (req.body.rejected ?? []).map(hash),
    stopCondition: req.body.stopCondition ?? 'not-set'
  };
  const lines = await fs.readFile('receipts.jsonl', 'utf8').catch(() => '');
  const last = lines.trim().split('\n').filter(Boolean).at(-1);
  entry.previousHash = last ? JSON.parse(last).hash : 'genesis';
  entry.hash = hash(entry);
  await fs.appendFile('receipts.jsonl', JSON.stringify(entry) + '\n');
  res.status(201).json({ hash: entry.hash });
});

app.listen(process.env.PORT ?? 3000);
Enter fullscreen mode Exit fullscreen mode

This is an example, not a production security control. It appends each decision as a new line in receipts.jsonl and includes a previousHash so later changes from an earlier entry are easier to spot.

The review flow

Interaction -> model -> receipt fields -> append hash chain -> human reviewer -> approve or hand back
Enter fullscreen mode Exit fullscreen mode

A reviewer sees the current hash and the previous hash. If the chain does not match, the demo stops instead of continuing to the next step.

That is the part I care about. A broken chain should be a stop condition, not a warning hidden behind a small icon.

What stops approval

Missing evidence What I did
The model ID is unspecified stop approval
A design option was chosen but the rejected list is empty ask where the alternatives went
The previous hash cannot be verified stop and hand back to the human
The stop condition was never set stop; the agent had no defined boundary

Notice that some extra evidence adds noise. Storing a long thread of every model thought did not help. The fewer fields the reviewer must check, the more likely they will notice what is missing.

What I still need to test

The evidence I have is small. The receipt made missing fields visible in my sandbox, and the reviewer stopped when modelId was missing. I also ran a small manual edit: I changed an old line. The next receipt still acted as if it was new, but the reviewer could see previousHash did not match. That is the exact behavior I wanted.

What I still need to test is whether teams keep paying attention after the first demo. I am treating that as a design hypothesis, not a finding.

Accessibility check

The review card should announce changes to screen readers. I used a live region with text like 'Receipt updated. Four fields verified. One field missing.' I did not rely on color alone to show a missing model ID. Keyboard focus moves to the first missing field when the receipt fails.

Limitations

A receipt does not make the model safe. It only records what the agent says happened. If the agent can write its own receipt, it can also write a believable one. For a real audit, use signed events and a separate trusted write path.

A free server is also not a compliance archive. It may reboot, change quotas, or disappear between runs. I treated it as a sandbox, not as the place where I keep the only copy of the evidence.

Who should not use this approach: teams that need legal-grade audit, teams that want a zero-infrastructure demo, and teams that expect the receipt to replace model testing.

The pattern I want to remember

Show the missing evidence before asking someone to approve. Keep the discarded paths in a concrete record. Make the next step depend on that record, not on the confidence of the person presenting the demo.

If you're already running small AI design demos, give each one a receipt before you present it. MonkeyCode's free server made that cheap for me, and the exercise changed the review conversation.

MonkeyCode provides free models that can run this workflow.

Top comments (0)