To restposter fra N24.5s FUNN-liste, begge i scripts/brain, i én berøring.
1. slugify() var tapsgivende for alt annet enn engelsk: hver ikke-ASCII-bokstav
falt i [^a-z0-9]-kjøringen og ble en bindestrek. «Løkkene» ble «l-kkene», og
verre: ø og å ble BEGGE «-», så «møte» og «måte» kollapset til én id.
Nå: NFD + strip av kombinerende tegn (å, é, ñ), så en eksplisitt tabell for de
ikke-dekomponerbare (æ→ae, ø→o, œ→oe, ß→ss, đ/ð→d, þ→th, ł→l).
REKKEFØLGEN: translitterering kjører FØR kollapsen, så kollapsen forblir siste
gate. Presist om hva det beviser — utdata-whitelisten [a-z0-9-] bæres av
kollapsen selv, ikke av rekkefølgen; dagens tabell produserer bare [a-z], så
rekkefølgen ville ikke brutt whitelisten i dag. Den er en invariant for
FREMTIDIGE tabelloppføringer, og den er nå pinnet av test. Det er whitelisten
ingestion-guardens tall hviler på (0/81 %-escapes, 0/81 bilde-URL-er,
rapportert til guard-eieren som strukturelt); repo-vid grep bekrefter at
scripts/brain/src/id.ts er den ENESTE slugify-implementasjonen i repoet, så
attribusjonen holder.
ID-STABILITET (den åpne beslutningen, avgjort på bevis, ikke skjønn):
endre in-place — ingen versjonering av slugifieren, ingen migrering.
- Ingen mintede ider finnes på disk: $DATA/brain og $DATA/ingest inneholder
bare tomme kataloger (opprettet 23.06, null filer — initBrain lager kataloger
OG filer i ett kall, så brain-en er aldri blitt genuint initialisert her).
- Alle 27 profile-field-labels den shippede malen minter er ASCII (målt ved å
kjøre extractFields' faktiske regler mot malen) ⇒ endringen er et BEVIST
no-op for profil-laget. Fem golden-ider pinner det.
- observed-ider (consolidate) mintes fra brukerskrevne nøkler og ville endret
seg — men ingenting er persistert, så migrasjonsflaten er null rader.
Ærlig avgrensning: dette KRYMPER kollisjonsklassen, det lukker den ikke —
«møte» og «mote» møtes fortsatt, som de må for at stabiliteten over case og
whitespace skal holde. Og en label uten latinske tegn i det hele tatt
(«日本語») slugger fortsatt til tom streng, så to slike minter samme id —
pre-eksisterende, ikke innført her, men det hører hjemme ved siden av
«krymper, lukker ikke» framfor å stå uskrevet. Adopter-forbehold: repoet er offentlig, men brain init
er ikke session-start-wiret (SB-S2 eier det), så en persistert brain krever en
eksplisitt invokasjon.
2. okf_version: 0.1 lå som BRØDTEKST i rot-index.md. Kanonisk plassering er
frontmatter-blokken (OKF-form spec §6, v0.3) — upstreams ene utskårne unntak
fra «index-filer har ingen frontmatter», og unntaket er oppregnet til ÉN
nøkkel, så ingenting annet blir med (okf_layout blir i brødtekst per §12).
Verdien flytter, den bumpes ikke: vi blir på 0.1.
VERIFISERING
- TDD: 6 røde først, så grønt. Brain 134 → 142 (+8), floor 127 → 142.
- Mutasjonstestet mot de FAKTISKE kildefilene, ikke bare self-tester:
okf_version tilbake til brødtekst → 3 røde · fjern translitterering → 1 rød ·
flytt translitterering til ETTER kollapsen → 1 rød · restaurert → 142/0.
- De to okf-testene dette erstattet var VAKUØSE: /^okf_version:/m matcher en
frontmatter-linje like gjerne som en brødtekstlinje, og frontmatterType() leser
type:, så den ga null med eller uten blokk. Begge gikk grønt på begge
plasseringer — nøyaktig gjeldsklassen N24.5 feide. Meldingen «index.md carries
NO frontmatter» ble usann og er skrevet om.
- Delt gate (katalogen, read-only): node catalog/scripts/okf-check.mjs <bundle>
→ exit 0, «OK: valid OKF bundle», okf_version 0.1. Bevist at verdien leses FRA
frontmatter: fjern blokken → «MISSING». check-okf-parity-signaturen er
conceptCount|untyped|okfVersion|okfVersionAccepted — plassering inngår ikke, så
det å migrere først splitter ingen paritet (okr skriver fortsatt brødtekst).
- Ti suiter grønne: test-runner 303/0 · trends 300 · analytics 202 · hooks 191 ·
brain 142 · editions 72 · render 63 · specifics-bank 45 · tests 35 ·
contract-gate 33.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EgUSPs7vDejiHr7iZw8xUx
119 lines
4.9 KiB
TypeScript
119 lines
4.9 KiB
TypeScript
import { describe, test } from "node:test";
|
||
import assert from "node:assert/strict";
|
||
|
||
import { slugify, mintEntityId, normalizeProvenance } from "../src/id.js";
|
||
|
||
describe("id module (SC4)", () => {
|
||
describe("slugify", () => {
|
||
test("lowercases, trims, and dashes non-alphanumeric runs", () => {
|
||
assert.equal(slugify("Current Role"), "current-role");
|
||
});
|
||
|
||
test("collapses repeated separators and strips edge dashes", () => {
|
||
assert.equal(slugify(" Core Expertise Areas (5 topics) "), "core-expertise-areas-5-topics");
|
||
});
|
||
|
||
test("is stable across case + whitespace variation of the same label", () => {
|
||
assert.equal(slugify("Current Role"), slugify(" current ROLE "));
|
||
});
|
||
|
||
// N24.6 — transliteration. Before this, every non-ASCII letter fell into the
|
||
// `[^a-z0-9]+` run and became a dash, so a Norwegian label was mangled
|
||
// ("Løkkene" → "l-kkene") and distinct labels collapsed onto one slug.
|
||
test("transliterates the non-decomposable Nordic/German letters", () => {
|
||
assert.equal(slugify("Løkkene"), "lokkene");
|
||
assert.equal(slugify("Æresmedlem"), "aeresmedlem");
|
||
assert.equal(slugify("Straße"), "strasse");
|
||
});
|
||
|
||
test("strips combining diacritics rather than dashing them", () => {
|
||
assert.equal(slugify("Måte"), "mate");
|
||
assert.equal(slugify("Café résumé"), "cafe-resume");
|
||
assert.equal(slugify("Señor"), "senor");
|
||
});
|
||
|
||
test("keeps ø-labels and å-labels distinct (they both collapsed to `-` before)", () => {
|
||
assert.notEqual(slugify("Møte"), slugify("Måte"));
|
||
});
|
||
|
||
test("output stays whitelisted to [a-z0-9-] for any input", () => {
|
||
// Load-bearing beyond this repo: the ingestion-guard exposure numbers reported
|
||
// to the guard owner rest on this whitelist being STRUCTURAL (no %-escape and no
|
||
// image URL can survive a slug). Transliteration runs BEFORE the collapse so the
|
||
// collapse stays the final gate.
|
||
for (const raw of ["%2Fetc%2Fpasswd", "https://x.tld/a b.png?q=1#f", "Løkke%C3%B8", "日本語", "a |