feat(published-language): l'instrument de coupe — où chaque garde vit réellement
La juridiction se mesure elle-même (temoin/ADR-018, mandat temoin/DEC-012 ; ADR-061 §2.6/§2.8 : le plan de mesure remplit la coupe). L'instrument court dans la CI de la juridiction observée, lit les invariants déclarés par ses parcours, et constate pour chacun l'étage où sa vérification est nommée : domicilié, renforcé, mal domicilié, aucun locus. Il constate la nomination, pas l'exécution ; il n'échoue jamais sur un écart — l'écart est le produit. Constats en ajout seul et sur changement d'état seulement ; journal de fraîcheur réécrit à chaque exécution ; réglage tenu par la juridiction et épinglé dans chaque constat ; homonymie exposée, jamais résolue. Auto-épreuve sur juridiction d'essai (quinze vérifications). Essai à blanc sur openathle réel : 21 invariants, 3 mal domiciliés, 18 sans locus — la coupe mord. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
#!/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-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.');
|
||||
Reference in New Issue
Block a user