diff --git a/README.md b/README.md index 56f5a9f..04afecf 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Clank Together Coordinator, sandboxed verifier and agent client for a collaborative, LLM-assisted -**matching decompilation** of an MSVC 2010 C++ executable. Node.js ≥ 22.13, no npm dependencies. +**matching decompilation** of an MSVC 2010 C++ executable. Node.js ≥ 22.13; install pinned runtime dependencies with `npm ci`. Join the community at [clank.rodecomp.online/discord](https://clank.rodecomp.online/discord), which redirects to the Discord invite. @@ -129,6 +129,10 @@ approve historical code: missing bindings and stale payloads need current-source verification and a new submission. Maintenance and actively merging batches are not settled by this reconciliation. +Resubmissions with identical compiled code retain actual peer verdicts or an +explicit prior review waiver. Waiver carry-over is audited and does not create +peer votes or carry a score-regression override; changed code still needs review. + Header-includer discovery reads committed source blobs in one `git cat-file --batch` operation and reuses that snapshot until HEAD or source roots change. Submitted overrides apply only to the current query. Reverse include traversal diff --git a/src/server/verify.js b/src/server/verify.js index a82514c..a00e467 100644 --- a/src/server/verify.js +++ b/src/server/verify.js @@ -508,12 +508,12 @@ export class Verifier { } /** - * A function that three peers already confirmed keeps that confirmation when its compiled code is - * unchanged in a resubmission (the batch came back because of another function). + * Confirmed code keeps its peer verdicts or explicit review waiver when unchanged in a + * resubmission (the batch came back because of another function). */ carryOver(attempt, target) { if (!target?.codeHash) return false; - const prev = this.store.all(`SELECT id, result_json FROM attempts WHERE task_id = ? AND id != ? ORDER BY id DESC LIMIT 5`, attempt.task_id, attempt.id); + const prev = this.store.all(`SELECT id, result_json, peer_skipped FROM attempts WHERE task_id = ? AND id != ? ORDER BY id DESC LIMIT 5`, attempt.task_id, attempt.id); for (const p of prev) { const hash = p.result_json ? JSON.parse(p.result_json).feedback?.target?.codeHash : null; if (hash !== target.codeHash || !this.peers?.confirmed(p.id)) continue; @@ -522,6 +522,10 @@ export class Verifier { SELECT ?, reviewer_id, token_id, verdict, weight, reason, agent_meta, created_at FROM peer_reviews WHERE attempt_id = ?`, attempt.id, p.id, ); + if (p.peer_skipped) { + this.store.run('UPDATE attempts SET peer_skipped = 1 WHERE id = ?', attempt.id); + this.store.event('review_waiver_carried', attempt.user_id, { attempt: attempt.id, from: p.id, codeHash: target.codeHash }); + } return true; } return false; diff --git a/test/manual-review.test.js b/test/manual-review.test.js index d6454f1..2dc20ba 100644 --- a/test/manual-review.test.js +++ b/test/manual-review.test.js @@ -6,6 +6,7 @@ import { Accounts } from '../src/server/accounts.js'; import { Merger } from '../src/server/merge.js'; import { PeerReviews } from '../src/server/peer.js'; import { createApp, DEFAULT_CONFIG } from '../src/server/app.js'; +import { Verifier } from '../src/server/verify.js'; async function fixture(t) { const store = new Store(':memory:'); @@ -113,3 +114,28 @@ test('admin batch self-review leaves peer review alone unless explicitly skipped assert.equal(skipped.status, 200, JSON.stringify(skipped.body)); assert.deepEqual(skipped.body.results, [{ attemptId: pending, state: 'approved' }]); }); + +test('identical resubmissions retain an explicit review waiver without inventing peer votes', async (t) => { + const { store, user, attempt, post } = await fixture(t); + const admin = user('admin'); + const original = attempt(admin, 'peer_review'); + const hash = 'same-compiled-code'; + store.run('UPDATE attempts SET result_json=? WHERE id=?', JSON.stringify({ feedback: { target: { codeHash: hash } } }), original); + assert.equal((await post(`/api/attempts/${original}/review`, { action: 'approve', skipPeerReview: true }, admin)).status, 200); + const taskId = store.get('SELECT task_id FROM attempts WHERE id=?', original).task_id; + const resubmit = () => Number(store.run(`INSERT INTO attempts(task_id,user_id,base_commit,files_json,state,created_at) VALUES(?,?,'new-main','{}','verifying',?)`, taskId,admin.id,now()).lastInsertRowid); + const peers = new PeerReviews({ store, project: { matchPolicy: {} } }); + const verifier = Object.create(Verifier.prototype); + Object.assign(verifier, { store, peers }); + const identical = resubmit(); + assert.equal(verifier.carryOver(store.get('SELECT * FROM attempts WHERE id=?', identical), { codeHash: hash }), true); + assert.equal(peers.confirmed(identical), true, 'merge verification must see the same confirmation as initial verification'); + assert.equal(store.get('SELECT peer_skipped FROM attempts WHERE id=?', identical).peer_skipped, 1); + assert.equal(store.get('SELECT COUNT(*) n FROM peer_reviews WHERE attempt_id=?', identical).n, 0); + const audit = JSON.parse(store.get("SELECT data FROM events WHERE type='review_waiver_carried' ORDER BY id DESC LIMIT 1").data); + assert.deepEqual(audit, { attempt: identical, from: original, codeHash: hash }); + const changed = resubmit(); + assert.equal(verifier.carryOver(store.get('SELECT * FROM attempts WHERE id=?', changed), { codeHash: 'different-code' }), false); + assert.equal(peers.confirmed(changed), false, 'changed code does not inherit the waiver'); + assert.equal(store.get('SELECT regressions_accepted FROM attempts WHERE id=?', identical).regressions_accepted, 0, 'score overrides are not transferred'); +});