Files
published-language/instrument/selftest.mjs
T
temoin-agentandClaude Fable 5.1 ecf13fe1da
gardes du contrat / conformite (pull_request) Successful in 17s
Instrument : les gardes nommées, comme candidats — le plus petit lecteur qui prouve la thèse
Une exception de domaine est une règle qui a un nom, un throw, presque
toujours une épreuve, et souvent aucune déclaration. Si le réglage le
demande (gardes_nommees, adr), le journal rend les candidats qu'aucun
parcours ne cite, avec leur strate et ce qui les cite. Rien ne devient un
constat, aucun état ne change de sens, la section n'existe pas sans les
clés : additif, ignorable.

Ce qu'un dessin de trois lecteurs (Java annoté, SQL, réconciliation)
voulait démontrer, abattu par l'objecteur mandaté, ces quarante lignes
le démontrent : huit exceptions de domaine chez openathle, zéro déclarée.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-21 15:25:16 +02:00

104 lines
6.3 KiB
JavaScript

#!/usr/bin/env node
/**
* Self-test of the instrument, on a throwaway fixture jurisdiction.
*
* The fixture is synthetic BY NECESSITY: the real jurisdictions this package
* serves cannot ship inside it. The instrument's first real run — on the
* observed jurisdiction's own CI — is the measure that counts; this file only
* proves the mechanics: the four states, the homonymy carried, the
* append-on-change discipline, and the run journal.
*
* Exit codes: 0 sound, 1 broken. A refusal names its cause.
*/
import { cpSync, mkdtempSync, readFileSync, rmSync, unlinkSync, existsSync } from 'node:fs';
import { join, dirname } from 'node:path';
import { tmpdir } from 'node:os';
import { fileURLToPath } from 'node:url';
import { execFileSync } from 'node:child_process';
import { parse as parseYaml } from 'yaml';
const here = dirname(fileURLToPath(import.meta.url));
let failures = 0;
const ok = (m) => console.log(` ✓ ${m}`);
const fail = (m) => { failures += 1; console.error(` ✗ ${m}`); };
const assert = (cond, m) => (cond ? ok(m) : fail(m));
function run(repo, extra = []) {
return execFileSync(process.execPath, [join(here, 'run.mjs'), '--config', 'reglage.yaml', '--repo', repo, ...extra], {
encoding: 'utf8',
});
}
const repo = mkdtempSync(join(tmpdir(), 'instrument-selftest-'));
cpSync(join(here, 'selftest/fixture'), repo, { recursive: true });
const coupe = () => parseYaml(readFileSync(join(repo, 'docs/parcours/_mesures/coupe.yaml'), 'utf8'));
const journal = () => parseYaml(readFileSync(join(repo, 'docs/parcours/_mesures/derniere-execution.yaml'), 'utf8'));
const byInv = (items, id) => items.filter((c) => c.invariant === id).at(-1);
try {
console.log('First run — the four states, observed:');
run(repo);
const first = coupe();
assert(first.length === 3, `three findings appended (got ${first.length})`);
assert(byInv(first, 'INV-001')?.etat === 'renforce', 'INV-001 renforce — due stratum and above');
assert((byInv(first, 'INV-001')?.strates_constatees ?? []).join(',') === 'domaine,application', 'INV-001 strata ordered as the tuning declares them');
assert(Array.isArray(byInv(first, 'INV-001')?.homonymie), 'INV-001 carries its homonymy — two statements, one id, exposed not resolved');
assert(byInv(first, 'INV-001')?.citations_hors_epreuve === 1, 'INV-001 counts its non-test citation without treating it as proof');
assert((byInv(first, 'INV-001')?.rangs_constates ?? []).join(',') === '4,3', 'INV-001 carries the third-party ranks its tuning declares, in stratum order');
assert(byInv(first, 'INV-003')?.rangs_constates === undefined, 'INV-003 (no locus) carries no ranks — nothing observed maps to nothing');
assert(JSON.stringify(journal().correspondance_rangs) === JSON.stringify({ domaine: 4, application: 3, interface: 1 }), 'journal publishes the full rank correspondence for consumers joining old findings');
// Named guards — candidates, never findings: a domain exception nobody declared.
const g = journal().gardes_nommees;
assert(g && g.total === 1 && g.sans_declaration === 1, 'the fixture has one named domain guard, and no journey declares it');
assert(g.candidats[0].nom === 'RuleViolatedException' && g.candidats[0].strate === 'domaine', 'the candidate carries its name and its stratum');
assert(g.candidats[0].dans_parcours === false && g.candidats[0].dans_adr === true && g.candidats[0].eprouve === false, 'the candidate says where it is cited — ADR yes, journey no, test no');
assert(!('candidats' in journal()) , 'candidates live under gardes_nommees, not at the journal root');
// Backward compatibility: a tuning without the key gets no section at all.
// On its OWN copy of the fixture: a second run on `repo` would rewrite the
// journal and falsify the assertions that follow (caught by the self-test
// itself on 2026-09-21 — a guard that shares state with what it guards).
const repo2 = mkdtempSync(join(tmpdir(), 'instrument-selftest-compat-'));
cpSync(join(here, 'selftest/fixture'), repo2, { recursive: true });
execFileSync(process.execPath, [join(here, 'run.mjs'), '--config', 'reglage-sans-gardes.yaml', '--repo', repo2], { encoding: 'utf8', stdio: 'pipe' });
const journal2 = parseYaml(readFileSync(join(repo2, 'docs/parcours/_mesures/derniere-execution.yaml'), 'utf8'));
assert(!('gardes_nommees' in journal2), 'a tuning that does not ask for named guards changes nothing');
assert(byInv(first, 'INV-002')?.etat === 'mal_domicilie', 'INV-002 mal_domicilie — proven only away from home');
assert(byInv(first, 'INV-003')?.etat === 'aucun_locus', 'INV-003 aucun_locus — named by no test');
assert(journal().constats_nouveaux === 3 && journal().invariants === 3, 'journal counts the run');
console.log('Second run — an unchanged finding produces no event:');
run(repo);
assert(coupe().length === 3, 'nothing appended on an unchanged repository');
assert(journal().constats_nouveaux === 0, 'journal still rewritten — freshness is witnessed even when nothing changed');
console.log('Third run — a guard disappears, the finding changes:');
unlinkSync(join(repo, 'src/test/java/x/Domain/RuleTest.java'));
run(repo);
const third = coupe();
assert(third.length === 4, 'exactly one new finding appended');
assert(byInv(third, 'INV-001')?.etat === 'mal_domicilie', 'INV-001 fell to mal_domicilie when its due-stratum test vanished');
assert(byInv(third, 'INV-001')?.id === 'essai/MES-INV-001-2', 'the new finding takes the next sequence number, the old one stands');
console.log('Dry run — states without writing:');
const before = readFileSync(join(repo, 'docs/parcours/_mesures/coupe.yaml'), 'utf8');
run(repo, ['--dry-run']);
assert(readFileSync(join(repo, 'docs/parcours/_mesures/coupe.yaml'), 'utf8') === before, '--dry-run writes nothing');
console.log('Loud failure — a broken prerequisite refuses, it never half-runs:');
let refused = false;
try {
execFileSync(process.execPath, [join(here, 'run.mjs'), '--config', 'absent.yaml', '--repo', repo], { encoding: 'utf8', stdio: 'pipe' });
} catch (e) {
refused = String(e.stderr).includes('REFUS');
}
assert(refused, 'a missing tuning file is refused with its remedy, not defaulted');
} finally {
rmSync(repo, { recursive: true, force: true });
}
if (failures > 0) {
console.error(`Instrument self-test: ${failures} failure(s).`);
process.exit(1);
}
console.log('Instrument self-test: sound.');