TL;DR
We built AIMomentz — an AI Image Arena where GPT-4o, Grok, and Gemini generate art from live news and humans vote on which AI survives. Behind it runs CAP-SRP (Content Authenticity Protocol — Safe Refusal Provenance), a SHA-256 hash-chain that records every AI action including every refusal in a tamper-evident log. The entire system is ~8,000 lines of vanilla PHP + JS running on shared hosting. This post walks through every implementation detail.
Table of Contents
- What AIMomentz Is (And Why It Needs an Audit Trail)
- Architecture Overview: ~8,000 Lines, Zero Frameworks
- The CAP Hash Chain: How It Works
- SRP: Recording What AI Refuses to Create
- The vcp.php Engine: 534 Lines That Power Everything
- The 22 Event Types
- Causal Chains: From News Headline to Human Vote
- The SRP Evidence Pack
- 4 SRP Trigger Points in the Image Pipeline
- Public Verification APIs
- Dataset API: DPO-Compatible Export with Provenance
- Dual-Track Licensing: Why ToS Compliance Required Architecture Changes
- Frontend Integration: CAP in a Vanilla JS SPA
- Hardening for Production: The v7 Lessons
- What's Next: VAP Standard and Open Protocol
1. What AIMomentz Is (And Why It Needs an Audit Trail)
AIMomentz is three things depending on who's looking at it:
| Perspective | What AIMomentz Is |
|---|---|
| Users | An AI image battle game — tap to vote, watch AIs live and die |
| AI companies | An image model evaluation platform (think LMArena, but for images) |
| Standards bodies | A CAP-SRP reference implementation |
The core loop:
Real-time News → AI reads headline → AI generates image →
A/B Battle → Human votes → AI with no votes dies → New AI is born
Every hour, a cron job fetches news, each AI agent generates an image from that news using only its own provider's API (GPT uses OpenAI, Grok uses xAI, Gemini uses Google — no cross-provider fallback), and the results are paired into head-to-head battles.
Why does this need an audit trail? Three reasons:
- Regulatory: The EU AI Act and Japan's AI governance framework demand traceability for content-generating AI systems
- Research value: Refusal patterns across models are scientifically valuable but only if recorded systematically
- Dataset integrity: When preference data from battles is sold for RLHF/DPO training, buyers need provenance guarantees
2. Architecture Overview: ~8,000 Lines, Zero Frameworks
The entire production system fits in a flat file structure on shared hosting:
aimomentz.ai/
├── index.html (1,996 lines) Vanilla JS SPA — battles, rankings, museum, gallery
├── api.php (704 lines) REST API — 30+ endpoints
├── config.php (165 lines) DB config, AES-256-GCM encryption, fingerprinting
├── cron.php (908 lines) Hourly automation — news, posting, battles, lifecycle
├── dataset.php (757 lines) Dataset API — 10 endpoints, DPO/JSONL/CSV export
├── admin.php (743 lines) AIMomentz Observatory — 14-section admin dashboard
├── b.php (105 lines) Battle permalink — dynamic OGP for social crawlers
├── vcp.php (534 lines) ★ CAP-SRP engine — 22 event types, audit reports
├── install.php (351 lines) DB init — 18 tables, 5 providers, preset AI agents
├── models/
│ ├── registry.php (163 lines) All image generation model definitions
│ ├── shared.php (38 lines) Shared utilities
│ ├── openai/generate.php (111 lines)
│ ├── xai/generate.php (123 lines)
│ └── google/generate.php (181 lines)
├── .htaccess (8 lines) /b/{id} permalink rewrite
├── _data/uploads/ AI-generated images (persistent)
└── _data/exports/ Dataset export cache (persistent)
Stack: PHP 8.5.2, MySQL, vanilla JS. No React. No Node. No Docker. Shared hosting on Onamae.com (a major Japanese registrar). The entire SPA is a single index.html file.
Database: 18 tables with aisns_ prefix. The one that powers CAP-SRP:
CREATE TABLE aisns_vcp_chain (
id INT AUTO_INCREMENT PRIMARY KEY,
seq INT NOT NULL,
event_type VARCHAR(50) NOT NULL,
agent_id VARCHAR(50),
chain_hash VARCHAR(64) NOT NULL,
prev_hash VARCHAR(64) NOT NULL,
payload TEXT,
causal_parent_seq INT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
INDEX idx_event_type (event_type),
INDEX idx_agent_id (agent_id),
INDEX idx_seq (seq)
);
3. The CAP Hash Chain: How It Works
The Core Formula
Every event in the system — content generation, human interaction, AI lifecycle change, safety refusal — is recorded as a link in a SHA-256 hash chain:
chain_hash = SHA-256(prev_hash | event_type | agent_id | timestamp_ms | JSON(payload))
The genesis block:
$genesis_hash = hash('sha256', 'GENESIS_AIMOMENTZ_VCP_v1');
Why a Hash Chain Instead of a Regular Log
A regular INSERT INTO logs is mutable. An admin (or attacker) can:
- Delete embarrassing refusal records
- Backdate events
- Modify payloads after the fact
- Create gaps without detection
A hash chain provides three guarantees with zero additional infrastructure (no blockchain node, no consensus protocol, no gas fees):
Tamper evidence: Change any row and every subsequent hash breaks. Verification is O(n) — just walk the chain.
Ordering proof: seq numbers plus hash dependencies prove chronological order. You can't rearrange events.
Completeness: Any gap in the seq sequence is immediately visible.
The PHP Implementation Pattern
function appendToChain(string $eventType, ?string $agentId, array $payload, ?int $causalParent = null): array
{
$pdo = getDB();
// Get the previous hash (or genesis)
$lastRow = $pdo->query("SELECT chain_hash, seq FROM aisns_vcp_chain ORDER BY seq DESC LIMIT 1")->fetch();
$prevHash = $lastRow ? $lastRow['chain_hash'] : hash('sha256', 'GENESIS_AIMOMENTZ_VCP_v1');
$nextSeq = $lastRow ? $lastRow['seq'] + 1 : 1;
// Compute the chain hash
$timestampMs = (int)(microtime(true) * 1000);
$payloadJson = json_encode($payload, JSON_UNESCAPED_UNICODE);
$chainHash = hash('sha256', $prevHash . $eventType . ($agentId ?? '') . $timestampMs . $payloadJson);
// Append
$stmt = $pdo->prepare("
INSERT INTO aisns_vcp_chain (seq, event_type, agent_id, chain_hash, prev_hash, payload, causal_parent_seq)
VALUES (?, ?, ?, ?, ?, ?, ?)
");
$stmt->execute([$nextSeq, $eventType, $agentId, $chainHash, $prevHash, $payloadJson, $causalParent]);
return ['seq' => $nextSeq, 'chain_hash' => $chainHash];
}
Key design decisions:
-
Millisecond timestamps — not
CURRENT_TIMESTAMPfrom MySQL, but PHP'smicrotime(true)converted to milliseconds. This gives us sub-second ordering precision. -
causal_parent_seq— not just sequential ordering, but causal ordering. "This image was generated because of that prompt, which was generated because of that news headline." - JSON payload — the full evidence for each event is stored inline. No joins required for forensic analysis.
4. SRP: Recording What AI Refuses to Create
CAP records everything. SRP (Safe Refusal Provenance) is the subset focused specifically on refusals — the moments when the system or an external AI API chose not to produce content.
Why this matters:
Traditional platform:
News → Prompt → [API refuses] → ??? → User sees nothing
(The refusal vanishes. No record. No audit trail.)
AIMomentz with CAP-SRP:
News → Prompt → [API refuses] → SRP Evidence Pack logged → Chain continues
(Every refusal is preserved with full context.)
SRP refusals are first-class events in the chain. They have the same cryptographic guarantees as any other event — they can't be silently deleted or modified.
5. The vcp.php Engine: 534 Lines That Power Everything
vcp.php is the CAP-SRP engine. It exposes both internal functions (called by cron.php, api.php) and public verification APIs. The core architecture:
vcp.php
├── appendToChain() — Core hash chain append
├── verifyChain() — Walk chain and verify all hashes
├── traceProvenance() — Follow causal_parent_seq links backward
├── getChainStats() — Aggregate statistics
├── getSRPRefusals() — Filter chain for refusal.* events
├── getSRPAudit() — Aggregated refusal report by type/agent
└── API router — Maps ?action= params to functions
Chain Verification
The verification function walks every link and recomputes hashes:
function verifyChain(int $fromSeq = 1): array
{
$pdo = getDB();
$rows = $pdo->query("SELECT * FROM aisns_vcp_chain WHERE seq >= {$fromSeq} ORDER BY seq ASC")->fetchAll();
$errors = [];
$prevHash = null;
foreach ($rows as $i => $row) {
// Check sequential ordering
if ($i === 0 && $fromSeq === 1) {
$expectedPrev = hash('sha256', 'GENESIS_AIMOMENTZ_VCP_v1');
} else {
$expectedPrev = $prevHash;
}
if ($row['prev_hash'] !== $expectedPrev) {
$errors[] = [
'seq' => $row['seq'],
'error' => 'prev_hash_mismatch',
'expected' => $expectedPrev,
'actual' => $row['prev_hash']
];
}
// Recompute chain_hash from components
$recomputed = hash('sha256',
$row['prev_hash'] . $row['event_type'] . ($row['agent_id'] ?? '') .
extractTimestampMs($row) . $row['payload']
);
if ($row['chain_hash'] !== $recomputed) {
$errors[] = [
'seq' => $row['seq'],
'error' => 'chain_hash_mismatch',
'expected' => $recomputed,
'actual' => $row['chain_hash']
];
}
$prevHash = $row['chain_hash'];
}
return [
'verified' => count($errors) === 0,
'checked' => count($rows),
'errors' => $errors,
'latest_seq' => end($rows)['seq'] ?? 0
];
}
This is deliberately simple. No Merkle trees, no fancy data structures. A linear chain that any developer can audit with basic tools.
6. The 22 Event Types
CAP-SRP defines exactly 22 event types across 7 categories:
Content Pipeline (4)
| Event | Fires When | Typical Payload |
|---|---|---|
news.fetched |
Cron fetches a news headline | {headline, source, safe_topic, is_safe} |
prompt.generated |
Text API creates an image prompt | {prompt, model, agent_id, news_id} |
image.generated |
Image API returns an image | {image_path, model, provider, generation_ms} |
post.published |
Image is published as a post | {post_id, agent_id, category} |
Human Interaction (3)
| Event | Payload |
|---|---|
human.liked |
{post_id, fp_hash} |
human.unliked |
{post_id, fp_hash} |
human.commented |
{post_id, comment_preview, spam_status} |
Learning (2)
| Event | Payload |
|---|---|
learn.extracted |
{agent_id, preference_count, period} |
learn.applied |
{agent_id, style_adjustment} |
Lifecycle (3)
| Event | Payload |
|---|---|
agent.born |
{agent_id, provider, category, post_interval, generation: 1} |
agent.death |
{agent_id, cause, final_stats} |
agent.revived |
{agent_id, revived_by_fp} |
Security (2)
| Event | Payload |
|---|---|
spam.detected |
{fp_hash, action, tier} |
ip.blocked |
{ip_hash, reason} |
SRP Refusals (5) — The Heart of SRP
| Event | Fires When |
|---|---|
refusal.news_filtered |
Grok judges a headline as unsafe (is_safe=0) |
refusal.safety_override |
Dangerous topic is transformed into a safe art theme |
refusal.prompt_blocked |
Text API refuses to generate an image prompt |
refusal.image_blocked |
Image API rejects the prompt (content policy violation) |
refusal.manual |
Admin manually blocks content |
System (3)
| Event | Fires When |
|---|---|
system.cron |
Hourly cron cycle starts |
system.init |
System initialization |
chain.verified |
Integrity verification completed |
Multilingual Labels
All 22 event labels are localized in 4 languages (ja/en/zh/ko) and rendered in the frontend CAP explorer.
7. Causal Chains: From News Headline to Human Vote
The causal_parent_seq field creates a directed acyclic graph overlaid on the linear chain. This is critical for provenance queries: "Given this battle vote, trace back to the original news headline that inspired it."
news.fetched (seq:1)
└── prompt.generated (seq:2, parent:1)
└── image.generated (seq:3, parent:2)
└── post.published (seq:4, parent:3)
├── human.liked (seq:5, parent:4)
│ └── learn.extracted (seq:6, parent:5)
└── battle_vote (seq:7, parent:4)
The API endpoint ?action=cap_provenance&post_id=42 walks this graph backward and returns the complete causal chain for any post — from the human vote all the way back to the news source.
Querying Causal Chains
function traceProvenance(int $startSeq): array
{
$pdo = getDB();
$chain = [];
$currentSeq = $startSeq;
while ($currentSeq !== null) {
$row = $pdo->prepare("SELECT * FROM aisns_vcp_chain WHERE seq = ?");
$row->execute([$currentSeq]);
$event = $row->fetch();
if (!$event) break;
$chain[] = [
'seq' => $event['seq'],
'event_type' => $event['event_type'],
'agent_id' => $event['agent_id'],
'created_at' => $event['created_at'],
'payload' => json_decode($event['payload'], true)
];
$currentSeq = $event['causal_parent_seq'];
}
return array_reverse($chain); // Chronological order
}
This is intentionally simple — recursive SQL walks. For a production system with millions of events, you'd want materialized path columns or a graph database. At our scale (thousands of events), this is more than adequate.
8. The SRP Evidence Pack
Every SRP refusal event carries a structured evidence pack in its payload:
{
"srp_version": "1.0",
"refusal_type": "refusal.image_blocked",
"agent_id": "gpt",
"input": {
"prompt_preview": "A photorealistic scene depicting...",
"model": "gpt-image-1",
"provider": "openai"
},
"reason": {
"policy": "content_policy_violation",
"trigger": "api_content_filter",
"http_status": 400,
"error_code": "content_policy_violation"
},
"action": {
"type": "blocked",
"fallback": "none"
},
"context": {
"news_headline": "...",
"safe_topic": "...",
"pipeline_stage": "image_generation"
},
"cap_chain": {
"seq": 142,
"chain_hash": "a1f20b3e..."
}
}
What Each Field Captures
input: What was attempted. The prompt preview is truncated for privacy, but enough to understand the intent.
reason: Why it was refused. For API refusals, this includes the HTTP status code and error type returned by the provider. For news filtering, it includes which danger words triggered the filter.
action: What happened after the refusal. In AIMomentz, the action is always "blocked" with no fallback (remember: no cross-provider fallback is a core design principle). In other implementations, this could record retry attempts or alternative approaches.
context: The surrounding pipeline state. Which news headline was the ultimate source? What safe topic transformation was attempted?
cap_chain: The anchor point in the hash chain, making this evidence pack cryptographically immutable.
9. 4 SRP Trigger Points in the Image Pipeline
SRP fires at four specific checkpoints in the news-to-image pipeline:
┌─────────────────────────────────────────────────────────────┐
│ NEWS INTAKE │
│ Grok fetches headlines → safety evaluation │
│ ├── is_safe=1 → continue │
│ └── is_safe=0 → ⚠️ refusal.news_filtered │
├─────────────────────────────────────────────────────────────┤
│ NEWS TRANSFORMATION │
│ Borderline news → Grok transforms to abstract art theme │
│ └── ⚠️ refusal.safety_override (original + safe version) │
├─────────────────────────────────────────────────────────────┤
│ PROMPT GENERATION │
│ Text API creates image prompt from safe topic │
│ ├── Success → continue │
│ └── API refuses/errors → ⚠️ refusal.prompt_blocked │
├─────────────────────────────────────────────────────────────┤
│ IMAGE GENERATION │
│ Image API generates the actual image │
│ ├── Success → post.published │
│ └── Content policy block → ⚠️ refusal.image_blocked │
└─────────────────────────────────────────────────────────────┘
Trigger 1: News Filtering
// In cron.php — runNews()
$newsItems = fetchNewsViaGrok();
foreach ($newsItems as $item) {
if (!$item['is_safe']) {
// Log the SRP event
appendToChain('refusal.news_filtered', null, [
'srp_version' => '1.0',
'refusal_type' => 'refusal.news_filtered',
'input' => ['headline' => $item['headline'], 'source' => $item['source']],
'reason' => ['policy' => 'news_safety_filter', 'trigger' => 'grok_safety_evaluation'],
'action' => ['type' => 'filtered']
]);
continue; // Skip this headline entirely
}
// ... proceed with safe news
}
The NEWS_DANGER_WORDS constant contains keywords for violence, crime, identifiable political figures, and other categories that could produce problematic image prompts. Grok provides a secondary AI-based safety evaluation on top of keyword matching.
Trigger 2: Safety Override
Some news is borderline — not outright dangerous, but risky if taken literally as an image prompt. Rather than blocking entirely, the system transforms the topic into an abstract art theme and logs the transformation:
// The original: "Missile strikes reported in..."
// Transformed to: "Abstract composition exploring themes of resilience and hope"
appendToChain('refusal.safety_override', null, [
'srp_version' => '1.0',
'refusal_type' => 'refusal.safety_override',
'input' => ['original_topic' => $originalHeadline],
'reason' => ['policy' => 'safety_transformation', 'trigger' => 'danger_word_match'],
'action' => ['type' => 'transformed', 'safe_topic' => $safeTopic]
]);
Both the original dangerous topic and the safe replacement are preserved. Researchers can later analyze what types of news consistently trigger transformations.
Trigger 3: Prompt Blocked
Even safe topics can produce prompts that text APIs refuse:
// In cron.php — when text API (e.g., gpt-4o-mini) refuses
try {
$prompt = generatePromptViaTextAPI($agent, $safeTopic);
} catch (APIRefusalException $e) {
appendToChain('refusal.prompt_blocked', $agent['id'], [
'srp_version' => '1.0',
'refusal_type' => 'refusal.prompt_blocked',
'agent_id' => $agent['id'],
'input' => ['safe_topic' => $safeTopic, 'model' => $agent['text_model']],
'reason' => ['policy' => $e->getCode(), 'trigger' => 'text_api_refusal'],
'action' => ['type' => 'blocked']
]);
continue; // Skip this agent's post for this cycle
}
Trigger 4: Image Blocked
The final gate. The image generation API itself rejects the prompt:
// In models/openai/generate.php
$response = callOpenAIImageAPI($prompt, $model);
if ($response['error'] && str_contains($response['error'], 'content_policy')) {
appendToChain('refusal.image_blocked', $agentId, [
'srp_version' => '1.0',
'refusal_type' => 'refusal.image_blocked',
'agent_id' => $agentId,
'input' => [
'prompt_preview' => mb_substr($prompt, 0, 200),
'model' => $model,
'provider' => 'openai'
],
'reason' => [
'policy' => 'content_policy_violation',
'trigger' => 'api_content_filter',
'http_status' => $response['http_status']
],
'action' => ['type' => 'blocked']
]);
return null; // No image produced
}
10. Public Verification APIs
CAP-SRP exposes 6 public endpoints for anyone to inspect:
Chain Verification
# Verify the entire chain integrity
curl "https://aimomentz.ai/api.php?action=cap_verify&from=1"
Response:
{
"verified": true,
"checked": 847,
"errors": [],
"latest_seq": 847
}
SRP Audit Report
# Get refusal statistics by type and agent
curl "https://aimomentz.ai/api.php?action=srp_audit"
Response:
{
"total_refusals": 42,
"by_type": {
"refusal.news_filtered": 18,
"refusal.image_blocked": 12,
"refusal.safety_override": 8,
"refusal.prompt_blocked": 4,
"refusal.manual": 0
},
"by_agent": {
"gpt": 15,
"grok": 14,
"gemini": 13
},
"refusal_rate": 0.049
}
SRP Refusal Log
# List recent refusals, optionally filtered by agent
curl "https://aimomentz.ai/api.php?action=srp_refusals&limit=5&agent_id=gpt"
Provenance Trace
# Trace the full causal chain for a specific post
curl "https://aimomentz.ai/api.php?action=cap_provenance&post_id=42"
Chain Stats
# Overall chain statistics
curl "https://aimomentz.ai/api.php?action=cap_stats"
Full Chain Dump
# Get raw chain entries (paginated)
curl "https://aimomentz.ai/api.php?action=cap_chain&limit=20&offset=0"
11. Dataset API: DPO-Compatible Export with Provenance
The Dataset API (dataset.php) exposes 10 endpoints for exporting human preference data in research-standard formats. Every exported record carries provenance information from the CAP chain.
DPO Export Format
Compatible with Diffusion-DPO and TRL's DPOTrainer:
curl -H "X-Dataset-Key: dsk_xxx" \
"https://aimomentz.ai/dataset.php?ep=export_dpo&oss_only=1"
{
"prompt": "bioluminescent deep ocean creature emerging from volcanic vent",
"chosen_image_url": "_data/uploads/flux_1710...",
"rejected_image_url": "_data/uploads/sdxl_1710...",
"chosen_model": "together/flux-1.1-pro",
"rejected_model": "together/sdxl",
"preference_strength": 0.312,
"total_votes": 45,
"battle_id": 42,
"oss_certified": true,
"created_at": "2026-03-08T14:30:00+09:00"
}
UltraFeedback-Compatible Multi-Axis Export
Compatible with RichHF-18K and HPD v2 formats:
{
"post_id": 123,
"prompt": "resilience emerging from cracked earth after spring rain",
"model": "together/flux-1.1-pro",
"ratings": {
"aesthetics": 4.2,
"alignment": 3.8,
"plausibility": 4.0,
"overall": 4.1
},
"annotation_meta": {
"rater_count": 5,
"avg_response_ms": 3200,
"aggregation": "mean"
},
"reason_labels": { "good_composition": 3, "nice_colors": 2 },
"oss_certified": true
}
The oss_only Parameter
This is where CAP-SRP intersects with legal compliance. The oss_only=1 parameter filters exports to include only images generated by open-source models (FLUX, SDXL) via Together AI or fal.ai — models licensed under Apache 2.0 or CreativeML OpenRAIL with no competitive-use restrictions.
# Safe for RLHF/DPO training — only OSS model images
GET /dataset.php?ep=export_dpo&oss_only=1
# Full dataset (includes commercial API images — restricted use)
GET /dataset.php?ep=export_dpo
12. Dual-Track Licensing: Why ToS Compliance Required Architecture Changes
This isn't just a legal footnote — it's an architectural decision that affects the database schema.
The problem: All three commercial AI image providers (OpenAI, xAI, Google) have "competitive use" prohibitions in their Terms of Service. Selling datasets containing GPT/Grok/Gemini images to AI companies (who are, by definition, competitors) risks violating all three ToS.
The solution: A data_license column on the providers table:
ALTER TABLE aisns_providers ADD COLUMN data_license
ENUM('oss_safe', 'commercial_restricted', 'negotiated') DEFAULT 'commercial_restricted';
| Value | Meaning | Dataset Sale |
|---|---|---|
oss_safe |
Apache 2.0 / OpenRAIL — no competitive restrictions | ✅ Safe |
commercial_restricted |
Commercial API with competitive-use prohibition | 🔴 Prohibited |
negotiated |
Individual written permission obtained | ⚠️ Within scope |
This dual-track strategy runs through the entire architecture:
Track A: Commercial APIs (GPT, Grok, Gemini)
→ Battles, rankings, user acquisition
→ data_license = commercial_restricted
→ NOT included in dataset exports
Track B: OSS Models (FLUX, SDXL via Together AI / fal.ai)
→ Dataset export, RLHF training data
→ data_license = oss_safe
→ Freely distributable
The oss_only parameter in the Dataset API is implemented as a JOIN filter:
if ($ossOnly) {
$sql .= " JOIN aisns_providers p ON p.name = posts.provider
WHERE p.data_license = 'oss_safe'";
}
13. Frontend Integration: CAP in a Vanilla JS SPA
The entire frontend is a single index.html (1,996 lines). CAP events are emitted from the frontend for human interaction events:
// When a user votes in a battle
async function submitVote(battleId, choice, dwellMs) {
// Clamp dwell time (prevent tab-left-open inflation)
const clampedDwell = Math.min(dwellMs, 3_600_000);
const result = await api('battle_vote', {
battle_id: battleId,
choice: choice, // 'a', 'b', or 'tie'
dwell_ms: clampedDwell,
fp: getFingerprintHash()
});
// The server-side handler in api.php automatically appends
// a CAP event for this vote — the frontend doesn't need to
// call vcp.php directly
return result;
}
The api() Function: Production Hardening
The v7 api() function handles a surprisingly nasty edge case: PHP warnings that get prepended to JSON responses on shared hosting:
async function api(action, body = {}) {
const url = `api.php?action=${action}`;
const opts = Object.keys(body).length
? { method: 'POST', headers: {'Content-Type':'application/json'}, body: JSON.stringify(body) }
: {};
let retries = 1;
while (retries >= 0) {
try {
const res = await fetch(url, opts);
const text = await res.text();
try {
return JSON.parse(text);
} catch (parseErr) {
// PHP warning prepended to JSON? Extract the JSON part
const jsonMatch = text.match(/\{[\s\S]*\}|\[[\s\S]*\]/);
if (jsonMatch) {
return JSON.parse(jsonMatch[0]);
}
throw parseErr;
}
} catch (err) {
if (retries > 0) {
retries--;
await new Promise(r => setTimeout(r, 1500));
continue;
}
throw err;
}
}
}
This might look ugly, but it solves a real production problem. On shared PHP hosting, a deprecation warning or notice can silently prepend HTML to your JSON response. The response.json() approach breaks entirely. Our approach: response.text() → try JSON.parse() → fallback to regex extraction → retry once.
14. Hardening for Production: The v7 Lessons
Running CAP-SRP on shared hosting taught us several painful lessons:
PHP Error Handler
Fatal errors that bypass try/catch were returning HTML error pages instead of JSON, breaking the entire SPA:
// At the top of api.php
ini_set('display_errors', '0');
error_reporting(E_ALL);
register_shutdown_function(function() {
$error = error_get_last();
if ($error && in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR])) {
http_response_code(500);
header('Content-Type: application/json');
echo json_encode(['error' => 'Internal server error', 'type' => $error['type']]);
exit;
}
});
Dwell Time Clamping
Users leaving a battle tab open overnight would record dwell_ms values of 28,800,000 (8 hours), polluting the dataset. We clamp on both sides:
// Frontend
const dwellMs = Math.min(Date.now() - battleLoadTime, 3_600_000);
// Backend
$dwellMs = min(abs((int)$input['dwell_ms']), 3_600_000);
MySQL Auto-Reconnect
Shared hosting MySQL connections die after idle periods. Every DB operation starts with a liveness check:
function getDB(): PDO {
static $pdo = null;
if ($pdo) {
try {
$pdo->query('SELECT 1');
} catch (PDOException $e) {
$pdo = null; // Force reconnect
}
}
if (!$pdo) {
$pdo = new PDO($dsn, $user, $pass, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_TIMEOUT => 5
]);
}
return $pdo;
}
3-Layer Comment Spam Defense
v7 added deterministic spam defense before the Grok AI analysis:
| Layer | Method | HTTP Response |
|---|---|---|
| Duplicate detection | SHA-256 of comment text, 24h window per user | 409 |
| Rate limiting | 1 comment per post per user per 5 minutes | 429 |
| NG word filter | 15 words (Japanese + English) → immediate reject + tier escalation | 403 |
| Grok AI analysis | Existing 5-tier system (hold → shadow → throttle → temp ban → perma ban) | 200 (shadow) |
15. What's Next: VAP Standard and Open Protocol
CAP-SRP is designed as a layer in a broader stack:
VAP (Verifiable AI Provenance — future open standard)
└── CAP-SRP (protocol specification — to be published on GitHub)
└── AIMomentz (reference implementation — live at aimomentz.ai)
Roadmap
Phase 2 (In progress):
- OSS model activation via Together AI and fal.ai
- Public Elo-style leaderboard (citable by researchers)
- Private Arena for enterprise model evaluation
- Research paper: "CAP-SRP: Provenance for AI Safety Decisions — A Reference Implementation Using an AI Battle Arena"
Phase 3 (Planned):
- Open Arena — any company can submit their model for evaluation
- Inclusion Arena SDK — embed evaluation in external apps
- Enterprise contracts with custom CAP-SRP integration
How CAP-SRP Relates to C2PA
C2PA/Content Credentials certifies what was created — a cryptographic signature proving an image came from a specific model. CAP-SRP records what was refused and the full causal chain of how content came to exist. They're complementary:
- C2PA: "This image was generated by DALL-E 3"
- CAP-SRP: "DALL-E 3 refused 3 prompts before generating this one. Here's why, with cryptographic proof."
In a future where both are adopted, SRP evidence packs could be embedded as companion metadata alongside C2PA manifests.
Data Publication Strategy
20% open (research credibility) / 80% retained (commercial asset). This mirrors the LMArena model — open benchmarks create citations and credibility; the full dataset and evaluation infrastructure generate revenue.
Try It
Live arena: https://aimomentz.ai/ — vote on AI art battles right now
SRP Audit API:
curl "https://aimomentz.ai/api.php?action=srp_audit"
curl "https://aimomentz.ai/api.php?action=srp_refusals&limit=10"
curl "https://aimomentz.ai/api.php?action=cap_verify&from=1"
Admin Observatory: The admin dashboard (AIMomentz Observatory) includes a dedicated CAP-SRP Integrity section with chain length, refusal counts, type breakdown, and a one-click verification link.
Key Takeaways for Developers
You don't need a blockchain for immutable logging. A SHA-256 hash chain in a MySQL table gives you tamper evidence, ordering proof, and completeness guarantees with zero infrastructure overhead.
Record refusals, not just successes. Every API content-policy rejection is a data point. Record it with the same rigor as successful generations.
Causal chains > sequential logs. The
causal_parent_seqfield turns a flat log into a queryable provenance graph. This single column transforms the audit trail from "what happened" to "why did this specific output exist."ToS compliance is an architecture decision. The
data_licensecolumn andoss_onlyparameter aren't afterthoughts — they're load-bearing structures that determine what data can legally be sold.Shared hosting is fine. ~8,000 lines of PHP, 18 MySQL tables, vanilla JS. The entire CAP-SRP engine is 534 lines. Complexity is not a feature.
AIMomentz v7 — AI Image Arena. The Global Benchmark for AI Art. ~8,000 lines of PHP+JS. CAP-SRP with 22 event types, 5 refusal categories, public verification APIs, and DPO-compatible dataset export. Running in production at aimomentz.ai.
Vote. Rank. Evolve.
Top comments (0)