refactor(ms-ai-architect): R14 — persona ut av plugin-flaten, og de seks stedene som produserte den

Nøytral arkitekt-ramme (ratifisert 15.09) i SKILL.md-ene, 23 kommandoer, 70 redaksjonelle
forekomster i ref-korpuset, README/CLAUDE.md/NOTICE og A11Y-rapportens proveniens-linje.
Hver av de 70 avgjort i kontekst: dialog fikk ny taler, proveniens ny opphavspåstand.

Ordren navnga fem produsent-steder; den live sjette manglet. `PROMPT_TEMPLATE` settes i
generate-skills.sh:27 og leses ALDRI — prompten er en heredoc i samme script. Å rette bare
prompt-template.md ville latt generatoren re-minte personaen ved neste KB-kjøring.

Ny G4-klausul i check-cosmo-gate.mjs: persona i leveranseflaten = 0, derivasjonsregisteret
pinnet per fil PÅ TALL (et filnavn-unntak er blindt for hva fila senere inneholder).
Registeret utvidet med README.md 3 — versjonstabellens rader er historikk, ikke leveranse.
Den ene genitiv-formede produktlinja adjudiseres på cosmos-db-URL-en i samme rad; regelen
er målt lukket (8 tvetydige linjer totalt, 33 URL-linjer, 2 persona-klassifiserte, begge
produkt) og hvitvasker ikke bar `Cosmo`. classifyCosmo urørt — baselinene hviler på den.
R3 godtar nå R14-utført (0) men ingenting imellom: et halvveis sveip felles fortsatt.

Bevis: gate PASSED + NETTET VALIDERT BEGGE VEIER · 1137/1137 · validate-plugin 250/0/0 ·
begge --dry-drivere 0 · Layer B 45/45 OK (kjent-positiv feller, exit 1).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-09-15 23:15:27 +02:00
commit ced0c5f46d
80 changed files with 400 additions and 152 deletions

View file

@ -95,11 +95,15 @@ test('the editorial sets are the measured dialogue and provenance classes', () =
// ------------------------------------------------------------------ KLAUSUL 2: reach
test('the corpus carries ZERO mechanical sites, and the editorial ones are all still there', () => {
// Asserts the state AFTER the transform. The pre-transform reference point — 62 mechanical
// in 50 files — lives in cosmo-labels-baseline.json and is read from there rather than
// restated here: a number asserted in two places drifts, and the baseline is the artefact
// the gate compares against. The test below pins that the two agree.
test('the corpus carries ZERO mechanical sites, and R14 has emptied the editorial ones', () => {
// Asserts the state AFTER the transform. The pre-transform reference points — 62 mechanical
// in 50 files, 16 editorial labels + 10 cells — live in cosmo-labels-baseline.json and are
// read from there rather than restated here: a number asserted in two places drifts, and the
// baseline is the artefact the gate compares against. The test below pins that the two agree.
//
// R13b held the editorial sites at 16+10 so that R14 could adjudicate each in context. R14
// (2026-09-15) did exactly that, so 0 is now the correct state, not a breach. What must NOT
// happen is a value in between: a half-swept corpus is the failure this still catches.
const t = { mechLabel: 0, mechCell: 0, edLabel: 0, edCell: 0, unmapped: [] };
for (const rel of refFiles()) {
const s = classifyPersonaSites(readFileSync(join(PLUGIN_ROOT, rel), 'utf8'));
@ -111,8 +115,8 @@ test('the corpus carries ZERO mechanical sites, and the editorial ones are all s
}
assert.deepEqual(t.unmapped, [], 'an unmeasured persona site exists — the net has a hole');
assert.equal(t.mechLabel + t.mechCell, 0, 'R13b is executed: no mechanical site remains');
assert.equal(t.edLabel, 16, 'dialogue (14) + the order\u2019s own 2 sentence forms, held for R14');
assert.equal(t.edCell, 10, 'provenance cells held back for R14');
assert.equal(t.edLabel, 0, 'R14 is executed: the 16 dialogue/sentence labels are neutralised');
assert.equal(t.edCell, 0, 'R14 is executed: the 10 provenance cells are neutralised');
});
test('the ratified tables and the gate baseline describe the SAME 62 occurrences', () => {

View file

@ -325,3 +325,62 @@ test('a neutralized document has no dead anchors', () => {
assert.deepEqual(findDeadAnchors(content), []);
assert.match(content, /^- \[Quick Reference Card\]\(#quick-reference-card\)$/m);
});
// ================================== R14: the delivery-surface clause ======================
//
// G4 persona occurrences in the DELIVERY SURFACE = 0.
//
// Delivery surface = tracked `*.md`, minus the reference corpus (G1/G3 already own it) and
// minus the derivation register — CHANGELOG rows, docs records and the measurement document
// itself, all TRUE statements about the past that CLAUDE.md forbids rewriting.
//
// The register is pinned on PER-FILE OCCURRENCE COUNTS, never on filenames. Measured: inject
// one new persona line into an exempt file and the count moves, so a number-pinned exemption
// FELLS it while a filename-pinned one is blind to it. That is the difference between an
// exemption and a hole.
//
// The one adjudicated product line: `docs/ref-kb-gold-reconciliation-2026-06.md:93` reads
// "Cosmos multi-region writes (active-active)", which the R13 Norwegian-genitive rule scores
// as persona because the enumerated PRODUCT_FOLLOWERS list was built over a corpus with no
// English product prose. The verdict was settled by the `/azure/cosmos-db/` URL in the same
// table row, so G4 applies THAT discriminator rather than exempting the file. Measured over
// all tracked `*.md`: 8 `Cosmos <lowercase>` lines exist in total and 33 lines carry a
// cosmos-db URL — of those, 2 classify as persona and BOTH are genuinely the product, so the
// rule is closed, not open-ended. The classifier itself is untouched: G1/G3/R1/R3 baselines
// rest on it.
import { personaInDeliveryContent, DERIVATION_REGISTER } from '../../scripts/kb-update/lib/cosmo-persona.mjs';
test('G4 known-negative — an Azure Cosmos DB product line carries no delivery persona', () => {
assert.equal(personaInDeliveryContent('| Azure Cosmos DB | 8000 NOK |\n'), 0);
assert.equal(personaInDeliveryContent('Bruk `CosmosClient` mot cosmos_ru-kvoten.\n'), 0);
// The adjudicated line: genitive-shaped, but the row's own URL settles it as product.
assert.equal(
personaInDeliveryContent(
'| Cosmos multi-region writes (active-active) | learn.microsoft.com/azure/cosmos-db/distribute-data-globally |\n',
),
0,
);
});
test('G4 known-positive — persona in the delivery surface is seen, in every form it takes', () => {
// Prose, the form 62 of the 62 measured occurrences actually took.
assert.equal(personaInDeliveryContent('Du er Cosmo Skyberg i en anskaffelsesrolle.\n'), 1);
// Norwegian genitive — the case the letter-s heuristic gets wrong in BOTH directions.
assert.equal(personaInDeliveryContent('Legg til Cosmos helhetsvurdering:\n'), 1);
// A heading, and a persona line that ALSO carries a cosmos-db URL: the URL rule must not
// become a laundering channel for a genuine persona mention.
assert.equal(personaInDeliveryContent('# Cosmo Skyberg - Microsoft AI Solution Architect\n'), 1);
assert.equal(
personaInDeliveryContent('Cosmo anbefaler learn.microsoft.com/azure/cosmos-db/ som kilde.\n'),
1,
);
});
test('G4 register is pinned on counts, so a new persona line in an exempt file still fells it', () => {
// The exemption must be a number, not a filename. A filename-pinned exemption is blind to
// what the file later comes to contain; this asserts the blindness is absent.
assert.ok(DERIVATION_REGISTER['CHANGELOG.md'] > 0, 'CHANGELOG is registered with a count');
for (const [file, count] of Object.entries(DERIVATION_REGISTER)) {
assert.equal(typeof count, 'number', `${file} is pinned on a count, not a filename`);
}
});