Show compiler-generated claims with original evidence instead of missing source

This commit is contained in:
ragnarok-clanker 2026-10-27 01:10:09 +01:00
commit 70b8378109
5 changed files with 64 additions and 15 deletions

View file

@ -113,6 +113,11 @@ separately shows approved or merging attempts. `GET /api/batches` supports `stat
`attemptState` (comma-separated attempt states), `author`, `before` and `limit`.
Filters apply before pagination.
Skip-claim reviews show the claim category and reason beside the original
instructions/pseudocode. They intentionally contain no submitted C++ or byte
comparison. Attempt details expose the original symbol identity independently of
compiled results so these reviews can load evidence without inventing a target.
The merge worker settles abandoned open code batches when they contain failed
attempts and no attempt remains live. It records the reason and an audit event,
preserving attempt feedback, task states, review votes and scores. This does not

View file

@ -803,10 +803,16 @@ export function createApp(ctx) {
const result = a.result_json ? JSON.parse(a.result_json) : null;
const batch = a.batch_id ? store.get('SELECT result_json FROM batches WHERE id = ?', a.batch_id) : null;
const batchResult = batch?.result_json ? JSON.parse(batch.result_json) : {};
const symbol = store.get(
`SELECT s.id, s.name, ad.addr, ad.size FROM tasks t JOIN symbols s ON s.id = t.symbol_id
LEFT JOIN symbol_addresses ad ON ad.symbol_id = s.id AND ad.version_id = ? WHERE t.id = ?`,
store.currentVersion().id, a.task_id,
);
const isReviewer = hasRole(user, 'reviewer');
return {
id: a.id,
taskId: a.task_id,
symbol: symbol ? { ...symbol, addr: symbol.addr === null ? null : '0x' + hex(symbol.addr) } : null,
author: a.author,
state: a.state,
score: a.score,
@ -822,7 +828,7 @@ export function createApp(ctx) {
mergedFiles: isReviewer ? batchResult.mergedFiles ?? result?.mergedFiles ?? null : undefined,
reviews: store.all('SELECT r.action, r.comment, r.created_at, u.name AS reviewer FROM reviews r JOIN users u ON u.id = r.reviewer_id WHERE r.attempt_id = ?', a.id),
kind: a.kind,
claim: a.kind === 'skip' ? { category: a.skip_category, reason: a.notes } : undefined,
claim: a.kind === 'skip' ? { category: a.skip_category, reason: a.notes, noSourceExpected: true } : undefined,
peerReviews: peers.verdicts(a.id),
peerRequired: peers.needed(peers.counts(a.id).different), // "same" verdicts (by weight) that accept it as it stands
targetSource: targetSource(result?.feedback?.target?.id, batchResult.mergedFiles ?? store.attemptFiles(a)),

View file

@ -1241,8 +1241,11 @@ async function openAttempt(id) {
if (!location.hash.startsWith(`#/review/${id}`)) history.replaceState(null, '', `#/review/${id}`);
const f = a.feedback || {};
const t = f.target;
$('attempt-title').textContent = `${t ? t.demangled || t.id : `Attempt #${a.id}`}`;
$('attempt-meta').textContent = `#${a.id} by ${a.author} · ${t ? `${t.id} · ` : ''}${a.state.replace('_', ' ')} · ${((a.score || 0) * 100).toFixed(1)}% of bytes · size ${t?.size?.ours ?? '?'} / ${t?.size?.original ?? '?'} · base ${a.baseCommit.slice(0, 10)}`;
const identity = a.symbol ?? t;
const claimed = a.kind === 'skip';
$('attempt-title').textContent = `${t?.demangled || identity?.name || identity?.id || `Attempt #${a.id}`}`;
const measurement = claimed ? 'no handwritten source submitted; byte matching does not apply' : `${a.score === null ? 'unmeasured' : `${(a.score * 100).toFixed(1)}% of bytes`} · size ${t?.size?.ours ?? '?'} / ${t?.size?.original ?? '?'}`;
$('attempt-meta').textContent = `#${a.id} by ${a.author} · ${identity?.id ? `${identity.id} · ` : ''}${a.state.replace('_', ' ')} · ${measurement} · base ${a.baseCommit.slice(0, 10)}`;
const n = (v) => (a.peerReviews ?? []).filter((p) => p.verdict === v).length;
const vm = $('attempt-verdicts');
vm.replaceChildren(el('span', 'same'), pips(n('same'), 0, a.peerRequired ?? 5), el('span', 'different'), pips(0, n('different'), 0));
@ -1258,12 +1261,15 @@ async function openAttempt(id) {
r.append(d);
}
compare = { source: a.targetSource, ourAsm: t?.asm ?? null, original: null, error: null };
showCompare(compareView);
if (t?.id) {
api(`/api/original/${t.id}`)
.then((o) => (compare.original = o))
.catch((e) => (compare.error = e.message))
compare = { source: a.targetSource, ourAsm: t?.asm ?? null, original: null, error: null, claim: a.claim ?? null };
const selectedCompare = compare;
document.querySelector('#compare-tabs [data-view="code"]').textContent = claimed ? 'Claim and original pseudocode' : 'C++ vs pseudocode';
document.querySelector('#compare-tabs [data-view="asm"]').textContent = claimed ? 'Original assembly' : 'Assembly vs assembly';
showCompare(claimed ? 'asm' : compareView);
if (identity?.id) {
api(`/api/original/${identity.id}`)
.then((o) => { if (currentAttempt === a) selectedCompare.original = o; })
.catch((e) => { if (currentAttempt === a) selectedCompare.error = e.message; })
.finally(() => currentAttempt === a && showCompare(compareView));
}
@ -1281,11 +1287,15 @@ async function openAttempt(id) {
fold(`Other functions in the file (${f.otherFunctions.length}, ${exact} byte-identical)`, table(['Function', 'Status', 'Score'], f.otherFunctions.map((x) => [x.id, x.status, `${(x.score * 100).toFixed(1)}%`])));
}
const files = Object.entries(a.mergedFiles || a.files || {});
fold(`Submitted files (${files.length})`, ...files.map(([p, content]) => {
const d = el('details');
d.append(el('summary', p), el('pre', content, 'code'));
return d;
}));
if (claimed) {
fold('No source files expected', el('p', 'This is a claim about how the original function was produced, not a C++ implementation. Review the stated evidence against the original instructions.'));
} else {
fold(`Submitted files (${files.length})`, ...files.map(([p, content]) => {
const d = el('details');
d.append(el('summary', p), el('pre', content, 'code'));
return d;
}));
}
fold(a.notes ? 'Notes from the author (untrusted)' : 'Notes from the author (none)', el('pre', a.notes || '(none)', 'notes'));
// Deciding a function still in peer review is for maintainers.
@ -1309,6 +1319,14 @@ function showCompare(view) {
if (!compare) return;
const o = compare.original;
const pending = !o && !compare.error ? 'Loading the original…' : `Original not available${compare.error ? `: ${compare.error}` : ''}`;
if (compare.claim) {
const claim = compare.claim;
const explanation = `No C++ implementation is expected for this claim (${claim.category}).\n\nReason (untrusted):\n${claim.reason}\n\nApprove only if the original instructions support the claim; otherwise request an implementation.`;
const original = view === 'asm' ? o?.asm : o?.pseudocode;
const title = view === 'asm' ? 'Original assembly' : 'Original pseudocode (reading aid)';
body.append(twoPanes(['No handwritten source required — claim', explanation], [o ? `${title} · ${o.addr}` : title, original ?? (o ? 'No original text is available in this view.' : pending)]));
return;
}
if (view === 'code') {
body.append(
twoPanes(

View file

@ -413,6 +413,6 @@
<div class="hazard thin" aria-hidden="true"></div>
<p>Clank Together · source is open: <code id="clone-cmd">git clone https://clank.rodecomp.online/git/clank/game-decomp.git</code></p>
</footer>
<script src="/app.js?v=batch-attempts-20261027"></script>
<script src="/app.js?v=claim-review-20261027"></script>
</body>
</html>

View file

@ -1976,3 +1976,23 @@ test('the merger settles abandoned open batches without changing attempts, tasks
}
}
});
test('skip attempt details expose original identity without inventing a compiled target or source', async () => {
const token = await activeUser('skipdetail');
const user = ctx.accounts.byName('skipdetail');
const task = ctx.store.get(`SELECT t.id, t.symbol_id, s.name, a.addr, a.size FROM tasks t JOIN symbols s ON s.id=t.symbol_id JOIN symbol_addresses a ON a.symbol_id=s.id WHERE a.version_id=? LIMIT 1`, ctx.store.currentVersion().id);
const reason = 'Calls the ordinary destructor, tests flags bit 1, conditionally deletes storage and returns this.';
const id = Number(ctx.store.run(`INSERT INTO attempts (task_id,user_id,base_commit,files_json,state,kind,skip_category,notes,result_json,created_at) VALUES (?,?,?,'{}','peer_review','skip','compiler_generated',?,'{}',?)`, task.id,user.id,ctx.repo.head(),reason,now()).lastInsertRowid);
try {
const response = await call('GET', `/api/attempts/${id}`, null, token);
assert.equal(response.status, 200);
assert.deepEqual(response.body.symbol, { id: task.symbol_id, name: task.name, addr: '0x' + task.addr.toString(16).padStart(8, '0'), size: task.size });
assert.deepEqual(response.body.claim, { category: 'compiler_generated', reason, noSourceExpected: true });
assert.deepEqual(response.body.files, {});
assert.equal(response.body.score, null);
assert.equal(response.body.targetSource, null);
assert.equal(response.body.feedback, null);
} finally {
ctx.store.run('DELETE FROM attempts WHERE id = ?', id);
}
});