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:
@@ -33,6 +33,19 @@ complète : `juridiction`, `cadence_heures`, `parcours`, `strates` (la première
|
|||||||
— ou celle marquée `due: true` — est l'étage dû), `epreuves`, `sortie`,
|
— ou celle marquée `due: true` — est l'étage dû), `epreuves`, `sortie`,
|
||||||
`journal_execution`.
|
`journal_execution`.
|
||||||
|
|
||||||
|
## La correspondance vers une échelle tierce (`rang`, optionnel)
|
||||||
|
|
||||||
|
Une juridiction peut faire correspondre chacune de ses strates à une échelle
|
||||||
|
tierce (p. ex. les rangs 1–5 de Percursus) en déclarant `rang: N` sur la
|
||||||
|
strate, dans son réglage. L'instrument émet alors `rangs_constates` dans
|
||||||
|
chaque constat — **seulement si toutes les strates constatées portent un
|
||||||
|
rang** : une correspondance partielle laisserait deviner, et deviner n'est
|
||||||
|
pas constater — et publie la table complète (`correspondance_rangs`) dans le
|
||||||
|
journal d'exécution, pour joindre les constats anciens, qui ne se réécrivent
|
||||||
|
jamais. La grossièreté se dit dans le réglage (en commentaire) : un rang
|
||||||
|
absent de la correspondance est un rang que la juridiction ne sait pas
|
||||||
|
instrumenter, pas un rang qui va de soi.
|
||||||
|
|
||||||
## Ce qu'il écrit — chez la juridiction, jamais ailleurs
|
## Ce qu'il écrit — chez la juridiction, jamais ailleurs
|
||||||
|
|
||||||
- **`sortie`** : les constats, en **ajout seul**, et **seulement quand l'état
|
- **`sortie`** : les constats, en **ajout seul**, et **seulement quand l'état
|
||||||
|
|||||||
+20
-1
@@ -71,9 +71,12 @@ function loadConfig(repo, path) {
|
|||||||
if (cfg?.[key] === undefined) refuse(`le réglage « ${path} » ne déclare pas « ${key} »`);
|
if (cfg?.[key] === undefined) refuse(`le réglage « ${path} » ne déclare pas « ${key} »`);
|
||||||
if (!Array.isArray(cfg.strates) || cfg.strates.length === 0)
|
if (!Array.isArray(cfg.strates) || cfg.strates.length === 0)
|
||||||
refuse(`le réglage « ${path} » doit déclarer au moins une strate`);
|
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))
|
if (typeof s?.nom !== 'string' || !Array.isArray(s?.chemins))
|
||||||
refuse(`chaque strate du réglage porte « nom » et « chemins » — l'une des deux manque`);
|
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];
|
const due = cfg.strates.find((s) => s.due === true) ?? cfg.strates[0];
|
||||||
return { ...cfg, due: due.nom, raw: readFileSync(full, 'utf8'), path };
|
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,
|
etat: verdict.etat,
|
||||||
constat: sentence(inv.id, verdict, cfg.due),
|
constat: sentence(inv.id, verdict, cfg.due),
|
||||||
strates_constatees: verdict.strates,
|
strates_constatees: verdict.strates,
|
||||||
|
...rangsConstates(cfg, verdict.strates),
|
||||||
pieces: kept,
|
pieces: kept,
|
||||||
...(omitted > 0 ? { pieces_omises: omitted } : {}),
|
...(omitted > 0 ? { pieces_omises: omitted } : {}),
|
||||||
citations_hors_epreuve: hits.filter((h) => !h.isTest).length,
|
citations_hors_epreuve: hits.filter((h) => !h.isTest).length,
|
||||||
@@ -247,6 +251,18 @@ function constatItem(cfg, inv, verdict, hits, seq, meta) {
|
|||||||
return item;
|
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) {
|
function gitRevision(repo) {
|
||||||
try {
|
try {
|
||||||
return execFileSync('git', ['-C', repo, 'rev-parse', 'HEAD'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
|
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 } : {}),
|
...(typeof cfg.cadence_heures === 'number' ? { cadence_heures: cfg.cadence_heures } : {}),
|
||||||
parcours_lus: r.journeys,
|
parcours_lus: r.journeys,
|
||||||
invariants: r.invariants,
|
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,
|
etats: r.tally,
|
||||||
constats_nouveaux: r.fresh.length,
|
constats_nouveaux: r.fresh.length,
|
||||||
reglage_empreinte: r.meta.reglageHash,
|
reglage_empreinte: r.meta.reglageHash,
|
||||||
|
|||||||
@@ -44,6 +44,9 @@ try {
|
|||||||
assert((byInv(first, 'INV-001')?.strates_constatees ?? []).join(',') === 'domaine,application', 'INV-001 strata ordered as the tuning declares them');
|
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(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')?.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-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(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');
|
assert(journal().constats_nouveaux === 3 && journal().invariants === 3, 'journal counts the run');
|
||||||
|
|||||||
@@ -5,10 +5,13 @@ parcours: docs/parcours
|
|||||||
strates:
|
strates:
|
||||||
- nom: domaine
|
- nom: domaine
|
||||||
due: true
|
due: true
|
||||||
|
rang: 4
|
||||||
chemins: ["src/main/java/**/Domain/**", "src/test/java/**/Domain/**"]
|
chemins: ["src/main/java/**/Domain/**", "src/test/java/**/Domain/**"]
|
||||||
- nom: application
|
- nom: application
|
||||||
|
rang: 3
|
||||||
chemins: ["src/main/java/**/Application/**", "src/test/java/**/Application/**"]
|
chemins: ["src/main/java/**/Application/**", "src/test/java/**/Application/**"]
|
||||||
- nom: interface
|
- nom: interface
|
||||||
|
rang: 1
|
||||||
chemins: ["app/src/**"]
|
chemins: ["app/src/**"]
|
||||||
epreuves: ["src/test/**", "app/src/**/*.essai.*"]
|
epreuves: ["src/test/**", "app/src/**/*.essai.*"]
|
||||||
sortie: docs/parcours/_mesures/coupe.yaml
|
sortie: docs/parcours/_mesures/coupe.yaml
|
||||||
|
|||||||
Reference in New Issue
Block a user