Percursus lit les strates en rangs (1–5, échelle fermée par ses ADR-003/004) ; openathle les nomme (domaine, application, infrastructure, interface). La clause loci de Percursus, déclarée à la main, est devenue inférable depuis que la coupe existe — et l'humain ne saisit pas l'inférable (P4). Le réglage de la juridiction peut désormais porter rang: N par strate. Le constat émet rangs_constates — seulement si TOUTES les strates constatées en déclarent un : une correspondance partielle laisserait deviner, et deviner n'est pas constater. Le journal publie la table complète (correspondance_rangs) pour joindre les constats anciens, qui ne se réécrivent jamais. Champ optionnel, additif : aucun réglage existant ne change de comportement. Auto-épreuve : dix-huit vérifications, dont l'ordre des rangs, l'absence de rangs sur l'invariant sans locus, et la table du journal. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
89 lines
4.8 KiB
JavaScript
89 lines
4.8 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');
|
|
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.');
|