feat(published-language): l'instrument émet la correspondance de rangs déclarée

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>
This commit is contained in:
2026-09-10 13:20:11 +02:00
co-authored by Claude Opus 5
parent 92d402444e
commit 7f03f37ef7
4 changed files with 39 additions and 1 deletions
+20 -1
View File
@@ -71,9 +71,12 @@ function loadConfig(repo, path) {
if (cfg?.[key] === undefined) refuse(`le réglage « ${path} » ne déclare pas « ${key} »`);
if (!Array.isArray(cfg.strates) || cfg.strates.length === 0)
refuse(`le réglage « ${path} » doit déclarer au moins une strate`);
for (const s of cfg.strates)
for (const s of cfg.strates) {
if (typeof s?.nom !== 'string' || !Array.isArray(s?.chemins))
refuse(`chaque strate du réglage porte « nom » et « chemins » — l'une des deux manque`);
if (s.rang !== undefined && typeof s.rang !== 'number')
refuse(`la strate « ${s.nom} » déclare un rang qui n'est pas un nombre — le rang est une correspondance vers une échelle tierce, ou rien`);
}
const due = cfg.strates.find((s) => s.due === true) ?? cfg.strates[0];
return { ...cfg, due: due.nom, raw: readFileSync(full, 'utf8'), path };
}
@@ -232,6 +235,7 @@ function constatItem(cfg, inv, verdict, hits, seq, meta) {
etat: verdict.etat,
constat: sentence(inv.id, verdict, cfg.due),
strates_constatees: verdict.strates,
...rangsConstates(cfg, verdict.strates),
pieces: kept,
...(omitted > 0 ? { pieces_omises: omitted } : {}),
citations_hors_epreuve: hits.filter((h) => !h.isTest).length,
@@ -247,6 +251,18 @@ function constatItem(cfg, inv, verdict, hits, seq, meta) {
return item;
}
/**
* La correspondance vers une échelle tierce (p. ex. les rangs de Percursus),
* si la juridiction la déclare dans son réglage. Émise SEULEMENT quand toutes
* les strates constatées portent un rang : une correspondance partielle
* laisserait le consommateur deviner — deviner n'est pas constater.
*/
function rangsConstates(cfg, strates) {
const rang = new Map(cfg.strates.filter((s) => typeof s.rang === 'number').map((s) => [s.nom, s.rang]));
if (strates.length === 0 || !strates.every((n) => rang.has(n))) return {};
return { rangs_constates: strates.map((n) => rang.get(n)) };
}
function gitRevision(repo) {
try {
return execFileSync('git', ['-C', repo, 'rev-parse', 'HEAD'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
@@ -292,6 +308,9 @@ function emit(args, cfg, r) {
...(typeof cfg.cadence_heures === 'number' ? { cadence_heures: cfg.cadence_heures } : {}),
parcours_lus: r.journeys,
invariants: r.invariants,
...(cfg.strates.some((s) => typeof s.rang === 'number')
? { correspondance_rangs: Object.fromEntries(cfg.strates.filter((s) => typeof s.rang === 'number').map((s) => [s.nom, s.rang])) }
: {}),
etats: r.tally,
constats_nouveaux: r.fresh.length,
reglage_empreinte: r.meta.reglageHash,