diff --git a/src/client/mcp.js b/src/client/mcp.js index a7d9ce3..6afc038 100644 --- a/src/client/mcp.js +++ b/src/client/mcp.js @@ -120,7 +120,7 @@ const TOOLS = [ }, { name: 'td_submit', - description: 'Submit your region as one batch: every task you hold (default) plus the functions of your shared regions that you rewrote, with the changed files they allow. The server compiles it once: byte-identical functions merge, the others go to peer review (five "same" merge them). Do not wait for the result: claim the next region right away and check results later with td_get_batch.', + description: 'Submit your region as one batch: every task you hold (default) plus the functions of your shared regions that you rewrote, with the changed files they allow. The server compiles it once: byte-identical functions merge, the others go to peer review (four "same" merge them, five once anyone said "different"). Do not wait for the result: claim the next region right away and check results later with td_get_batch.', inputSchema: { type: 'object', properties: { diff --git a/src/server/guidance.js b/src/server/guidance.js index 0fea507..b912cc6 100644 --- a/src/server/guidance.js +++ b/src/server/guidance.js @@ -11,7 +11,7 @@ export const DEFAULT_GUIDANCE = [ 'Before submitting: the region compiles, every function is annotated, and after a header change td_check with includers: true shows every unit that includes it still compiles.', 'Port, do not polish. Write faithful C++ for each function: the same logic, calls, conditions, loops and data accesses, with sensible names. Clear compile errors and structural differences (a missing branch, a wrong call, a wrong field), then move on even when it is not byte-identical.', 'Do not spend more than two or three checks per function chasing exact bytes: five other agents confirm the logic in peer review, which is the normal path. Byte-identical code merges right away.', - 'Peer reviews come in groups (up to 40 functions of one batch and file): read the original and the submission (it is known to compile; do not compile or diff), and send all verdicts in one td_submit_peer_review call. Five "same" merge a function, three "different" send it back.', + 'Peer reviews come in groups (up to 40 functions of one batch and file): read the original and the submission (it is known to compile; do not compile or diff), and send all verdicts in one td_submit_peer_review call. Four "same" merge a function (five once anyone said "different"), three "different" send it back.', 'Quality counts: an account whose functions peers keep sending back, or whose submissions keep breaking, is limited and then paused for a while automatically (the quality field of td_get_batch / /api/me says why). Fewer, checked submissions beat many careless ones.', 'Trust pays: an agent whose close matches keep passing peer review gets functions scoring 95% or more accepted without review, and a reviewer whose verdicts keep agreeing with the others (including on the differences they find) counts twice or three times. Each agent (token) earns it on its own; td_quality shows where it stands.', 'If your code (or someone else\'s) breaks a neighbouring function so a region does not compile, claim that function too (td_claim_region covers it when it is in your region) and fix it in the same submission.', diff --git a/src/server/merge.js b/src/server/merge.js index 0dffc28..5431340 100644 --- a/src/server/merge.js +++ b/src/server/merge.js @@ -353,7 +353,7 @@ export class Merger { (peers.length ? `\nPeer-reviewed-by: ${peers.join(', ')}` : '') + (reviewers.length ? `\nReviewed-by: ${reviewers.join(', ')}` : '') + (!reviewers.length && !confirmedIds.size ? '\nMerged automatically: every function is byte-identical to the original.' : '') + - (!reviewers.length && confirmedIds.size ? `\nAccepted without human review: at least ${this.peers?.autoAccept ?? 5} peers confirmed the logic.` : '') + + (!reviewers.length && confirmedIds.size ? `\nAccepted without human review: at least ${this.peers?.acceptClean ?? 4} peers confirmed the logic.` : '') + '\n'; // New source files join the build list in the same commit (with the flags of their folder). const listFile = this.project.compileCommands ?? null; diff --git a/src/server/peer.js b/src/server/peer.js index f8f572c..78117ce 100644 --- a/src/server/peer.js +++ b/src/server/peer.js @@ -4,7 +4,8 @@ // via `td disasm` / `td decompile`) and the submitted code, and decides whether the logic is the // same. No automated verdicts: the context deliberately leaves out the server's byte diff and score. // An attempt stays in peer review until one side has enough verdicts from different accounts: -// `peerAutoAccept` (default 5) "same" verdicts accept it, and it merges with no human step; +// `peerAcceptClean` (default 4) "same" verdicts accept it when nobody said "different", and +// `peerAutoAccept` (default 5) when someone did; it merges with no human step; // `peerReviews` (default 3) "different" ones send it back to its author with their reasons. The // code was already shown to compile when it was submitted, so reviewers only read. If anyone said // "different" and it was accepted all the same, it is kept in later_review for a human (merge.js). @@ -25,7 +26,7 @@ const REVIEW_GROUP = 40; export const PEER_INSTRUCTIONS = [ 'You hold every function listed under functions. Review them all, then send all verdicts in one call (td_submit_peer_review with a verdicts list).', 'A function with a claim field is not ported code but a claim that it needs no decompilation (and why). Read the original: "same" if the claim is right, "different" if it is ordinary code that should be ported.', - 'Decide whether the submitted C++ does the same thing as the original function. It compiles (the server proved that when it was submitted) but is not byte-identical; your judgment is what counts. Five "same" verdicts merge it, three "different" send it back; verdicts of reviewers who keep agreeing with the others count twice or three times, so careful reviews pay off.', + 'Decide whether the submitted C++ does the same thing as the original function. It compiles (the server proved that when it was submitted) but is not byte-identical; your judgment is what counts. Four "same" verdicts merge it (five once anyone said "different"), three "different" send it back; verdicts of reviewers who keep agreeing with the others count twice or three times, so careful reviews pay off.', 'Read only: the original assembly (td_disassemble), the original pseudocode (td_decompile) and the submitted code below. Do not compile, run td_check, diff bytes or use other automated comparison.', 'Check: every branch and condition, loop bounds, arithmetic and signedness, the functions called and their arguments, fields and globals read and written, return values, and error paths.', 'Answer "same" only if a caller could not tell the two apart. Otherwise answer "different" and name the concrete difference (where, and what the original does instead).', @@ -47,6 +48,16 @@ export class PeerReviews { return this.project.matchPolicy.peerAutoAccept ?? 5; } + // "same" verdicts that accept an attempt nobody objected to. + get acceptClean() { + return Math.min(this.project.matchPolicy.peerAcceptClean ?? 4, this.autoAccept); + } + + /** "same" verdicts this attempt needs: fewer while nobody said "different". */ + needed(different) { + return different > 0 ? this.autoAccept : this.acceptClean; + } + get leaseMs() { return (this.project.matchPolicy.peerLeaseMinutes ?? 60) * 60_000; } @@ -71,13 +82,14 @@ export class PeerReviews { AND NOT EXISTS (SELECT 1 FROM users b WHERE b.id = ? AND b.peer_review_blocked = 1) AND NOT EXISTS (SELECT 1 FROM peer_reviews p WHERE p.attempt_id = a.id AND p.reviewer_id = ?) ) - WHERE objections < ? AND confirmations + in_progress < ?`, + WHERE objections < ? AND confirmations + in_progress < CASE WHEN objections > 0 THEN ? ELSE ? END`, now(), user.id, user.id, user.id, this.required, this.autoAccept, + this.acceptClean, ); // Random by batch, so a batch stays together. const key = new Map(); @@ -160,7 +172,7 @@ export class PeerReviews { const ids = attemptIds ?? (a.batch_id ? this.store.all(`SELECT id FROM attempts WHERE batch_id = ? AND state = 'peer_review' ORDER BY id`, a.batch_id).map((r) => r.id) : [a.id]); return { attemptId: a.id, - required: this.autoAccept, // "same" verdicts that accept it + required: this.needed(this.counts(a.id).different), // "same" verdicts that accept it (more once anyone objected) rejectAt: this.required, // "different" verdicts that send it back instructions: PEER_INSTRUCTIONS, // Every function this review covers (one batch); give one verdict per attemptId. @@ -195,7 +207,7 @@ export class PeerReviews { const leased = this.store.get('SELECT 1 FROM peer_leases WHERE attempt_id = ? AND reviewer_id = ? AND expires_at > ?', attemptId, user.id, now()); const counts = this.counts(attemptId); const others = this.store.get('SELECT COUNT(*) AS n FROM peer_leases WHERE attempt_id = ? AND reviewer_id != ? AND expires_at > ?', attemptId, user.id, now()).n; - const full = counts.same + others >= this.autoAccept; + const full = counts.same + others >= this.needed(counts.different); if (!leased && full) throw new HttpError(409, 'enough reviewers are already on this attempt; claim another'); const weight = this.trust?.reviewer(user.id, user.tokenId ?? null).weight ?? 1; @@ -216,7 +228,7 @@ export class PeerReviews { this.store.run(`UPDATE attempts SET state = 'approved' WHERE id = ?`, attemptId); this.store.run(`UPDATE tasks SET state = 'approved', updated_at = ? WHERE id = ? ${notOthers}`, now(), a.task_id); this.store.event('auto_accepted', null, { attempt: attemptId, same, different }); - return { state: 'approved', same, different, weight, required: this.autoAccept }; + return { state: 'approved', same, different, weight, required: this.needed(different) }; } if (this.rejects({ different, differentReviewers })) { // Enough independent reviewers found a difference: back to the author with their reasons. @@ -232,7 +244,7 @@ export class PeerReviews { return { state: 'needs_cleanup', same, different, weight, required: this.required }; } // Undecided: it stays in review until one side has enough verdicts. - return { state: a.state, same, different, weight, required: this.autoAccept }; + return { state: a.state, same, different, weight, required: this.needed(different) }; }); } @@ -267,10 +279,10 @@ export class PeerReviews { return { same: r?.same ?? 0, different: r?.different ?? 0, sameReviewers: r?.sameReviewers ?? 0, differentReviewers: r?.differentReviewers ?? 0 }; } - // Three "different" send it back even when five said "same": accepted only without that. - // However much a verdict weighs, one reviewer alone never decides. + // Four "same" accept it while nobody objected, five once someone did; three "different" send it + // back even when five said "same". However much a verdict weighs, one reviewer never decides. accepts(c) { - return c.same >= this.autoAccept && c.different < this.required && c.sameReviewers >= 2; + return c.same >= this.needed(c.different) && c.different < this.required && c.sameReviewers >= 2; } rejects(c) { diff --git a/src/server/trust.js b/src/server/trust.js index fc7e9de..9b0b6f9 100644 --- a/src/server/trust.js +++ b/src/server/trust.js @@ -57,6 +57,10 @@ export class Trust { return this.project.matchPolicy.peerAutoAccept ?? 5; } + get acceptClean() { + return Math.min(this.project.matchPolicy.peerAcceptClean ?? 4, this.autoAccept); + } + get required() { return this.project.matchPolicy.peerReviews ?? 3; } @@ -84,11 +88,11 @@ export class Trust { const s = this.scope('a', tokenId); const w = (verdict) => `(SELECT COALESCE(SUM(r.weight), 0) FROM peer_reviews r WHERE r.attempt_id = a.id AND r.verdict = '${verdict}')`; const r = this.store.get( - `SELECT SUM(same >= ? AND different < ?) AS accepted, SUM(different >= ?) AS rejected FROM ( + `SELECT SUM(same >= CASE WHEN different > 0 THEN ? ELSE ? END AND different < ?) AS accepted, SUM(different >= ?) AS rejected FROM ( SELECT ${w('same')} AS same, ${w('different')} AS different FROM attempts a WHERE a.user_id = ? AND ${s.sql} AND a.created_at > ? AND a.kind = 'code' AND a.peer_skipped = 0 AND json_extract(a.result_json, '$.feedback.target.score') >= ?)`, - this.autoAccept, this.required, this.required, userId, ...s.args, now() - p.authorDays * DAY, p.bandAt, + this.autoAccept, this.acceptClean, this.required, this.required, userId, ...s.args, now() - p.authorDays * DAY, p.bandAt, ); const accepted = r?.accepted ?? 0; const rejected = r?.rejected ?? 0; diff --git a/src/server/verify.js b/src/server/verify.js index 7df7403..1bc1d52 100644 --- a/src/server/verify.js +++ b/src/server/verify.js @@ -645,7 +645,7 @@ export class Verifier { state = auto ? 'approved' : 'in_review'; } else if (target.status === STATUS.MISMATCH || target.status === STATUS.UNRESOLVED) { // Not byte-identical, but it compiles cleanly: other agents judge whether the logic is the same. - feedback.summary = `not byte-identical (score ${(score * 100).toFixed(1)}%), sent to peer review: other agents compare it with the original (${policy.peerAutoAccept ?? 5} "same" merge it, ${policy.peerReviews ?? 3} "different" send it back)`; + feedback.summary = `not byte-identical (score ${(score * 100).toFixed(1)}%), sent to peer review: other agents compare it with the original (${policy.peerAcceptClean ?? 4} "same" merge it, ${policy.peerAutoAccept ?? 5} once anyone said "different"; ${policy.peerReviews ?? 3} "different" send it back)`; state = 'peer_review'; } else { feedback.summary = `the annotated function was not compiled or has no address in the current version (${target.status})`; diff --git a/src/server/web/app.js b/src/server/web/app.js index 5886bc7..5140276 100644 --- a/src/server/web/app.js +++ b/src/server/web/app.js @@ -139,8 +139,8 @@ const TASK_STATES = { open: 'Not started yet. Any agent can claim it (usually with its whole source file).', leased: 'An agent claimed it (alone or with its region) and is working on it. The claim expires if the agent goes quiet.', verifying: 'Submitted. The server is compiling it once to prove it builds and to compare it.', - peer_review: 'Compiles but is not byte-identical. Other agents compare the logic with the original: five "same" merge it, three "different" send it back.', - in_review: 'From the earlier rule (three confirmations, then a human). Five "same" verdicts accept it now.', + peer_review: 'Compiles but is not byte-identical. Other agents compare the logic with the original: four "same" merge it (five once anyone said "different"), three "different" send it back.', + in_review: 'From the earlier rule (three confirmations, then a human). Four "same" verdicts accept it now (five if anyone said "different").', approved: 'Accepted (byte-identical, or five peers confirmed it). Waiting to be merged into main.', merging: 'Being merged into main right now.', done: 'Merged into main.', diff --git a/src/server/web/index.html b/src/server/web/index.html index f23e530..8288a88 100644 --- a/src/server/web/index.html +++ b/src/server/web/index.html @@ -239,7 +239,7 @@

Pick a function on the left.

-

Agents do the peer reviews: five "same" verdicts merge a function, three "different" send it back. Here you can look at any of them, and maintainers can decide one themselves.

+

Agents do the peer reviews: four "same" verdicts merge a function (five once anyone said "different"), three "different" send it back. Here you can look at any of them, and maintainers can decide one themselves.