Admins can set how much an account's peer verdicts weigh
users.review_weight (1 to 3, or auto) overrides the earned weight (trust.js). Setting it re-weights the account's verdicts on functions still waiting and settles the queue: what the new weight decides is accepted or sent back at once. One reviewer still never decides alone. td-server user review-weight <name> <1|2|3|auto>, or the admin actions review-weight-1/2/3/auto. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
09d1091775
commit
804ebd15c2
6 changed files with 61 additions and 3 deletions
|
|
@ -1024,6 +1024,11 @@ export function createApp(ctx) {
|
|||
'submit-trusted': (name) => setSubmitMode(name, 'trusted'),
|
||||
'submit-paused': (name) => setSubmitMode(name, 'paused'),
|
||||
'reset-password': (name, admin) => accounts.resetPassword(name, admin.id),
|
||||
// How much the account's peer verdicts weigh: set by an admin, or earned (trust.js).
|
||||
'review-weight-1': (name) => peers.setReviewWeight(accounts.requireUser(name).id, 1),
|
||||
'review-weight-2': (name) => peers.setReviewWeight(accounts.requireUser(name).id, 2),
|
||||
'review-weight-3': (name) => peers.setReviewWeight(accounts.requireUser(name).id, 3),
|
||||
'review-weight-auto': (name) => peers.setReviewWeight(accounts.requireUser(name).id, null),
|
||||
};
|
||||
const setSubmitMode = (name, mode) => {
|
||||
const u = accounts.byName(name);
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ Usage: td-server <command> [options] (all commands accept --data <dir>, defaul
|
|||
user approve|reject|revoke|reinstate <name>
|
||||
user role <name> --role R
|
||||
user rename <name> <new-name> rename an account (sessions and tokens keep working)
|
||||
user review-weight <name> <1|2|3|auto> weight of its peer verdicts (auto: earned, trust.js)
|
||||
user password <name> [--password-stdin] reset a password (logs out browser sessions)
|
||||
user token <name> [label] create an API token for an account
|
||||
version add <id> <exe> [--current] register a target executable version
|
||||
|
|
@ -174,6 +175,12 @@ async function main() {
|
|||
return console.log(accounts.setRole(name, values.role, null));
|
||||
case 'rename':
|
||||
return console.log(accounts.rename(name, rest[1], null));
|
||||
case 'review-weight': {
|
||||
// td-server user review-weight <name> <1|2|3|auto>
|
||||
const ctx = openServer(dataDir);
|
||||
const w = rest[1] === 'auto' ? null : Number(rest[1]);
|
||||
return console.log(ctx.peers.setReviewWeight(accounts.requireUser(name).id, w));
|
||||
}
|
||||
case 'token': {
|
||||
const u = accounts.requireUser(name);
|
||||
const t = accounts.createApiToken(u.id, rest[1] ?? 'cli');
|
||||
|
|
|
|||
|
|
@ -302,6 +302,7 @@ export class Store {
|
|||
this.addColumn('users', 'peer_review_blocked', 'INTEGER NOT NULL DEFAULT 0'); // no peer reviews from this account
|
||||
this.addColumn('users', 'submit_mode', "TEXT NOT NULL DEFAULT 'auto'"); // auto (quality.js) | trusted | paused
|
||||
this.addColumn('users', 'quality_reset_at', 'INTEGER'); // the owner's daily fresh start (quality.js)
|
||||
this.addColumn('users', 'review_weight', 'INTEGER'); // set by an admin: the weight of this account's verdicts (trust.js)
|
||||
// Which agent (API token) submitted or claimed what: quality is judged per agent.
|
||||
this.addColumn('attempts', 'token_id', 'INTEGER');
|
||||
this.addColumn('batches', 'token_id', 'INTEGER');
|
||||
|
|
|
|||
|
|
@ -260,6 +260,26 @@ export class PeerReviews {
|
|||
return { state: a.state, same, different, required: this.needed(different) };
|
||||
}
|
||||
|
||||
/**
|
||||
* An admin sets how much an account's verdicts weigh (1 to 3), or null to let its record decide
|
||||
* (trust.js). A set weight also applies to its verdicts on functions still waiting, which are
|
||||
* then decided if they now are; decided functions stay as they were.
|
||||
*/
|
||||
setReviewWeight(userId, weight) {
|
||||
if (weight !== null && ![1, 2, 3].includes(weight)) throw new HttpError(400, 'weight must be 1, 2, 3 or auto');
|
||||
this.store.run('UPDATE users SET review_weight = ? WHERE id = ?', weight, userId);
|
||||
this.trust?.forget();
|
||||
let reweighted = 0;
|
||||
if (weight !== null) {
|
||||
reweighted = Number(this.store.run(
|
||||
`UPDATE peer_reviews SET weight = ? WHERE reviewer_id = ? AND attempt_id IN (SELECT id FROM attempts WHERE state IN ('peer_review', 'in_review'))`,
|
||||
weight, userId,
|
||||
).changes);
|
||||
}
|
||||
this.store.event('review_weight', null, { user: userId, weight, reweighted });
|
||||
return { weight: weight ?? 'auto', reweighted, ...this.settleAll() };
|
||||
}
|
||||
|
||||
/** Decides every waiting attempt the rule decides now (after the rule or a policy changed). */
|
||||
settleAll() {
|
||||
const waiting = this.store.all(`SELECT a.* FROM attempts a WHERE a.state IN ('peer_review', 'in_review') AND EXISTS (SELECT 1 FROM peer_reviews p WHERE p.attempt_id = a.id)`);
|
||||
|
|
|
|||
|
|
@ -133,9 +133,11 @@ export class Trust {
|
|||
}
|
||||
const sameScore = lowerBound(n.sameAgreed, n.same);
|
||||
const differentScore = lowerBound(n.differentAgreed, n.different);
|
||||
const blocked = !!this.store.get('SELECT 1 FROM users WHERE id = ? AND peer_review_blocked = 1', userId);
|
||||
const tier = blocked ? null : p.weights.find((t) => n.different >= t.minDifferent && sameScore >= t.same && differentScore >= t.different);
|
||||
return { ...n, sameScore, differentScore, weight: tier?.weight ?? 1, tiers: p.weights };
|
||||
const u = this.store.get('SELECT peer_review_blocked AS blocked, review_weight AS fixed FROM users WHERE id = ?', userId);
|
||||
const tier = u?.blocked ? null : p.weights.find((t) => n.different >= t.minDifferent && sameScore >= t.same && differentScore >= t.different);
|
||||
// An admin may set the weight for an account they trust (or distrust); it overrides the record.
|
||||
const weight = u?.fixed != null && !u.blocked ? u.fixed : tier?.weight ?? 1;
|
||||
return { ...n, sameScore, differentScore, weight, setByAdmin: u?.fixed != null, tiers: p.weights };
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1442,3 +1442,26 @@ test('an author withdraws a function waiting for review: it leaves the queue and
|
|||
assert.equal((await call('POST', `/api/attempts/${attempt}/withdraw`, {}, token)).status, 409, 'once');
|
||||
ctx.store.run('UPDATE tasks SET state = ?, lease_user = ?, lease_expires = ? WHERE id = ?', saved.state, saved.lease_user, saved.lease_expires, task);
|
||||
});
|
||||
|
||||
test('an admin sets a reviewer weight: it counts at once, also on verdicts already given', async () => {
|
||||
const author = ctx.accounts.userByApiToken(await activeUser('rwauthor'));
|
||||
const trusted = ctx.accounts.userByApiToken(await activeUser('rwtrusted'));
|
||||
const other = ctx.accounts.userByApiToken(await activeUser('rwother'));
|
||||
const saved = ctx.store.get(`SELECT id, state FROM tasks WHERE state != 'done' ORDER BY id LIMIT 1`);
|
||||
const open = () => Number(ctx.store.run(`INSERT INTO attempts (task_id, user_id, base_commit, files_json, state, created_at) VALUES (?, ?, ?, '{}', 'peer_review', ?)`, saved.id, author.id, ctx.repo.head(), now()).lastInsertRowid);
|
||||
const why = 'the branches, the call and the return value match the original';
|
||||
const waiting = open();
|
||||
ctx.peers.submit(trusted, waiting, 'same', why, null);
|
||||
ctx.peers.submit(other, waiting, 'same', why, null);
|
||||
assert.equal(ctx.store.get('SELECT state FROM attempts WHERE id = ?', waiting).state, 'peer_review', 'two to none');
|
||||
const r = ctx.peers.setReviewWeight(trusted.id, 3);
|
||||
assert.deepEqual([r.reweighted, r.approved], [1, 1]);
|
||||
assert.equal(ctx.store.get('SELECT state FROM attempts WHERE id = ?', waiting).state, 'approved', 'four to none: accepted');
|
||||
const alone = open();
|
||||
assert.equal(ctx.peers.submit(trusted, alone, 'same', why, null).weight, 3);
|
||||
assert.equal(ctx.store.get('SELECT state FROM attempts WHERE id = ?', alone).state, 'peer_review', 'one reviewer never decides');
|
||||
ctx.peers.setReviewWeight(trusted.id, null);
|
||||
assert.equal(ctx.trust.reviewer(trusted.id).weight, 1, 'auto: back to the earned weight');
|
||||
ctx.store.run(`UPDATE attempts SET state = 'superseded' WHERE id IN (?, ?)`, waiting, alone);
|
||||
ctx.store.run('UPDATE tasks SET state = ? WHERE id = ?', saved.state, saved.id);
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue