Ctrl K
ComunidadGuías…

__tests__/job-verdict.test.js

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
/*
 * THE VERDICT IS TAKEN WHEN THE WORK FINISHES, NOT WHEN SOMEBODY BUILDS A TRAINING SET.
 *
 * Labelling the whole store afterwards works — it is how the first 1,147 gold jobs were found — but
 * evidence ages. A captured file is cleaned up, a recording is deleted, a workflow run rolls out of
 * its window, and the label quietly gets worse the longer it waits.
 *
 * The other half is a product fix that stands on its own: an agent that reports an export it never
 * produced is worse than one that admits it failed, because nobody finds out until they go looking
 * for the file. So when a check contradicts the report, the job says so in its own record.
 */
import { describe, it, expect, beforeEach, afterAll } from 'vitest';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';

const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'gb-verdict-'));
process.env.PROFILE_DIR = dir;
const jobs = await import('../src/jobs.js');
const { outcomeOf } = await import('../src/verify.js');

const newJob = (over = {}) => {
  const j = jobs.create({ owner: 'o', goal: 'do the thing', role: 'research.web', ...over });
  return j;
};

beforeEach(() => { jobs.setVerifier(null); });
afterAll(() => { try { fs.rmSync(dir, { recursive: true, force: true }); } catch {} });

describe('finishing a job takes its verdict', () => {
  it('lands the tier on the job record', () => {
    jobs.setVerifier((job) => outcomeOf(job, {}));
    const j = newJob();
    jobs.setReport(j, 'Had a look around.');
    jobs.finish(j, 'idle', 'done');
    expect(j.verdict).toBeTruthy();
    expect(['gold', 'silver', 'bronze', 'void']).toContain(j.verdict.tier);
    expect(j.verdict.at).toBeTruthy();
  });

  it('sees the END step, which is how a stop becomes void rather than a failure', () => {
    /* The verdict is taken AFTER the end step is written, on purpose: "stopped by you" is what
       makes voidOf answer correctly, and it does not exist a moment earlier. */
    jobs.setVerifier((job) => outcomeOf(job, {}));
    const j = newJob();
    jobs.stop(j, 'stopped by you');
    expect(j.verdict.tier).toBe('void');
    expect(j.verdict.voidReason).toMatch(/owner stopped/);
  });

  it('writes the contradiction into the job when a check fails the report', () => {
    /* The product half: the claim is wrong and the record now says which claim and why, instead of
       the failure sitting invisible until somebody looks for the file. */
    jobs.setVerifier((job) => outcomeOf(job, { files: () => [] }));
    const j = newJob();
    jobs.step(j, 'tool', 'download_url()', { tool: 'download_url', args: { url: 'https://x/v.mp4' } });
    jobs.setReport(j, 'Exported the video successfully.');
    jobs.finish(j, 'idle', 'done');
    expect(j.verdict.tier).toBe('bronze');
    expect(j.verdict.failures).toContain('fileWasProduced');
    const said = j.steps.filter((s) => s.kind === 'blocked').map((s) => s.text).join(' ');
    expect(said).toMatch(/checked:/);
    expect(said).toMatch(/fileWasProduced/);
  });

  it('says nothing extra when every check passes', () => {
    jobs.setVerifier((job) => outcomeOf(job, {}));
    const j = newJob();
    jobs.step(j, 'tool', 'type()', { tool: 'type', args: { text: 'a confirmed sentence of some length' } });
    jobs.step(j, 'type', 'typed into [3] "field": a confirmed sentence of some length');
    jobs.setReport(j, 'Typed it.');
    jobs.finish(j, 'idle', 'done');
    expect(j.verdict.tier).toBe('gold');
    expect(j.steps.some((s) => s.kind === 'blocked')).toBe(false);
  });
});

describe('the checker is injected, and nothing depends on it being there', () => {
  it('finishes normally with no verifier wired at all', () => {
    const j = newJob();
    jobs.finish(j, 'idle', 'done');
    expect(j.status).toBe('idle');
    expect(j.verdict).toBeUndefined();
  });

  it('a verifier that throws does not take the job down with it', () => {
    /* A label is never worth failing the work for. */
    jobs.setVerifier(() => { throw new Error('the file store is offline'); });
    const j = newJob();
    expect(() => jobs.finish(j, 'idle', 'done')).not.toThrow();
    expect(j.status).toBe('idle');
    expect(j.verdict).toBeUndefined();
  });

  it('a verifier returning nothing is simply no verdict', () => {
    jobs.setVerifier(() => null);
    const j = newJob();
    jobs.finish(j, 'idle', 'done');
    expect(j.verdict).toBeUndefined();
  });

  it('judging twice does not double up the record', () => {
    jobs.setVerifier((job) => outcomeOf(job, {}));
    const j = newJob();
    jobs.setReport(j, 'Looked around.');
    jobs.finish(j, 'idle', 'done');
    const first = j.verdict.at;
    /* finish() on an already-over job is a no-op; idle is not over, so this re-judges rather than
       skipping — either way there is exactly one verdict object. */
    jobs.finish(j, 'stopped', 'stopped by you');
    expect(typeof j.verdict.at).toBe(typeof first);
    expect(Array.isArray(j.verdict.why)).toBe(true);
  });
});

describe('the verdict has to survive being written to disk', () => {
  /*
   * THE GAP EVERY TEST ABOVE LEFT OPEN.
   *
   * They all assert on `j.verdict` — the object in memory — and they all passed while all 2,228
   * jobs on the cluster carried `verdict: null`. judge() set the field, the bus carried it, and the
   * persisted shape did not name it, so nothing ever reached the file.
   *
   * Nothing raised. The only symptom was a training set labelled from whatever evidence still
   * existed weeks later, which means gold decaying into silver as the files that proved it are
   * cleaned up. A test that never leaves memory cannot see that.
   */
  it('writes the verdict into the job file, not just onto the object', () => {
    jobs.setVerifier((job) => outcomeOf(job, {}));
    const j = newJob();
    jobs.setReport(j, 'Read the page and reported what it said.');
    jobs.finish(j, 'idle', 'done');
    expect(j.verdict).toBeTruthy();

    const onDisk = JSON.parse(fs.readFileSync(path.join(jobs.DIR, `${j.id}.json`), 'utf8'));
    expect(onDisk.verdict).toBeTruthy();
    expect(onDisk.verdict.tier).toBe(j.verdict.tier);
    /* The reasoning travels with it, so a tier can be argued with later rather than merely trusted. */
    expect(Array.isArray(onDisk.verdict.why)).toBe(true);
  });

  it('writes null rather than omitting the field when nothing judged it', () => {
    /* Absent and "judged as nothing" must not look the same to a reader: one means the verifier had
       no opinion, the other means this job predates verdicts entirely. */
    jobs.setVerifier(null);
    const j = newJob();
    jobs.finish(j, 'idle', 'done');
    const onDisk = JSON.parse(fs.readFileSync(path.join(jobs.DIR, `${j.id}.json`), 'utf8'));
    expect(onDisk).toHaveProperty('verdict');
    expect(onDisk.verdict).toBe(null);
  });
});