fix(linkedin-studio): N24.6 — brain-restposter (slugify-translitterering + okf_version til rot-frontmatter) [skip-docs]
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
This commit is contained in:
parent
e38f71f418
commit
bc47c1842a
5 changed files with 85 additions and 15 deletions
|
|
@ -15,16 +15,46 @@ import { PROVENANCE_VALUES } from "./types.js";
|
|||
import type { Provenance } from "./types.js";
|
||||
|
||||
/**
|
||||
* Stable slug: lowercase, trim, every non-alphanumeric run → a single `-`, with
|
||||
* leading/trailing dashes stripped. Stable across case + whitespace variation of
|
||||
* the same label, so it is a safe id key.
|
||||
* Letters NFD cannot reach. Unicode decomposition turns `å`/`é`/`ñ` into a base
|
||||
* letter plus a combining mark, but `ø`, `æ`, `ß` and friends are atomic code
|
||||
* points with no decomposition — they need an explicit mapping or they fall into
|
||||
* the non-alphanumeric run and become a dash. Applied AFTER lowercasing, so only
|
||||
* the lowercase forms are listed.
|
||||
*/
|
||||
const TRANSLITERATIONS: ReadonlyArray<readonly [RegExp, string]> = [
|
||||
[/æ/g, "ae"],
|
||||
[/ø/g, "o"],
|
||||
[/œ/g, "oe"],
|
||||
[/ß/g, "ss"],
|
||||
[/[đð]/g, "d"],
|
||||
[/þ/g, "th"],
|
||||
[/ł/g, "l"],
|
||||
];
|
||||
|
||||
/**
|
||||
* Stable slug: lowercase, trim, transliterate, then every non-alphanumeric run →
|
||||
* a single `-`, with leading/trailing dashes stripped. Stable across case +
|
||||
* whitespace variation of the same label, so it is a safe id key.
|
||||
*
|
||||
* Transliteration (N24.6) exists because the plain `[^a-z0-9]` collapse was lossy
|
||||
* for every non-English label: "Løkkene" slugged to `l-kkene`, and — worse — `ø`
|
||||
* and `å` both became `-`, so "møte" and "måte" collapsed onto ONE id. It narrows
|
||||
* that collision class; it does not close it (`møte` and `mote` still meet, as
|
||||
* they must for the case/whitespace stability above to hold).
|
||||
*
|
||||
* ORDER IS LOAD-BEARING: transliteration runs BEFORE the `[^a-z0-9]+` collapse, so
|
||||
* the collapse stays the final gate and the output whitelist is unchanged. Anything
|
||||
* downstream that rests on "a slug cannot contain a %-escape, a path or a URL"
|
||||
* (the ingestion-guard exposure figures do) rests on that ordering.
|
||||
*/
|
||||
export function slugify(label: string): string {
|
||||
return label
|
||||
let s = label
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9]+/g, "-")
|
||||
.replace(/^-+|-+$/g, "");
|
||||
.normalize("NFD")
|
||||
.replace(/\p{M}+/gu, "");
|
||||
for (const [pattern, replacement] of TRANSLITERATIONS) s = s.replace(pattern, replacement);
|
||||
return s.replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -29,14 +29,24 @@ function today(): string {
|
|||
return new Date().toISOString().slice(0, 10);
|
||||
}
|
||||
|
||||
/**
|
||||
* The bundle-root index. `okf_version` sits in a FRONTMATTER block, not body text
|
||||
* (OKF-form spec §6, v0.3): upstream carves exactly one exception to "index files
|
||||
* contain no frontmatter", and it is enumerated to this single key. So nothing else
|
||||
* rides along here — no `type:`/`title:` (those belong to concept files), and
|
||||
* `okf_layout` stays in body text per §12. Non-root index files carry no block at all.
|
||||
*
|
||||
* The value stays 0.1 — this migration moves the marker, it does not bump it.
|
||||
*/
|
||||
function indexSeed(): string {
|
||||
return `# Brain — Index (MOC)
|
||||
return `---
|
||||
okf_version: 0.1
|
||||
---
|
||||
# Brain — Index (MOC)
|
||||
|
||||
> Map of Content — one screen pointing at every tributary, with a freshness flag.
|
||||
> Generated by \`brain init\`; safe to hand-edit (a re-run never clobbers it).
|
||||
|
||||
okf_version: 0.1
|
||||
|
||||
| Tributary | What it holds | Freshness |
|
||||
|-----------|---------------|-----------|
|
||||
| voice-samples | writing style | — |
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -15,8 +15,9 @@ import { parseProfile } from "../src/profile.js";
|
|||
* `okr/scripts/okf-check.mjs`, the reference checker:
|
||||
* - every CONCEPT file (`*.md` except `index.md`) carries a non-empty `type` in a
|
||||
* leading YAML frontmatter block;
|
||||
* - the bundle-root `index.md` carries an `okf_version` marker as markdown TEXT
|
||||
* (index files carry no frontmatter per the OKF spec);
|
||||
* - the bundle-root `index.md` carries an `okf_version` marker in its FRONTMATTER
|
||||
* block — upstream's single carved exception to "index files carry no
|
||||
* frontmatter" (spec §6, v0.3; this was body text until N24.6);
|
||||
* - each directory level has its own `index.md` (progressive disclosure).
|
||||
*
|
||||
* EXCLUDED by design (brief §6): the `ingest/` tributary. `ingest/published/*.md`
|
||||
|
|
@ -68,10 +69,39 @@ describe("brain/ bundle is OKF-compatible form (Stage 1)", () => {
|
|||
rmSync(root, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
test("bundle-root index.md carries an okf_version marker (markdown text, no frontmatter)", () => {
|
||||
// N24.6 (OKF 0.3 §6): the marker moved from body text INTO the root index.md's
|
||||
// frontmatter block — upstream's one carved exception to "index files contain no
|
||||
// frontmatter". The two assertions this replaced were vacuous: `/^okf_version:/m`
|
||||
// matches a frontmatter line as happily as a body line, and `frontmatterType()`
|
||||
// reads `type:`, so it returns null with or without a block. Both went green on
|
||||
// either placement, which is exactly the debt class N24.5 swept.
|
||||
test("bundle-root index.md carries okf_version INSIDE the leading frontmatter block", () => {
|
||||
const index = readFileSync(join(root, "brain/index.md"), "utf8");
|
||||
assert.match(index, /^okf_version:\s*\S+/m, "root index.md declares okf_version");
|
||||
assert.equal(frontmatterType(index), null, "index.md carries NO frontmatter (OKF reserved file)");
|
||||
const block = index.match(/^---\n([\s\S]*?)\n---\n/);
|
||||
assert.ok(block, "root index.md opens with a frontmatter block");
|
||||
assert.match(block![1], /^okf_version:\s*\S+/m, "the block declares okf_version");
|
||||
});
|
||||
|
||||
test("okf_version does NOT also sit in the body (one marker, one placement)", () => {
|
||||
const index = readFileSync(join(root, "brain/index.md"), "utf8");
|
||||
const body = index.replace(/^---\n[\s\S]*?\n---\n/, "");
|
||||
assert.doesNotMatch(body, /^okf_version:/m, "no leftover body-text marker");
|
||||
});
|
||||
|
||||
test("the root index frontmatter carries okf_version and nothing else", () => {
|
||||
// Upstream's exception is enumerated to ONE key, so `okf_layout` (this
|
||||
// convention's own extension marker, spec §12) stays in body text and no
|
||||
// concept-style `type:`/`title:` may ride along in an index file.
|
||||
const index = readFileSync(join(root, "brain/index.md"), "utf8");
|
||||
const block = index.match(/^---\n([\s\S]*?)\n---\n/)![1];
|
||||
const keys = block.split("\n").filter((l) => l.trim() !== "").map((l) => l.split(":")[0].trim());
|
||||
assert.deepEqual(keys, ["okf_version"]);
|
||||
assert.equal(frontmatterType(index), null, "an index file still carries no `type:`");
|
||||
});
|
||||
|
||||
test("a non-root index.md carries no frontmatter at all", () => {
|
||||
const journal = readFileSync(join(root, "brain/journal/index.md"), "utf8");
|
||||
assert.doesNotMatch(journal, /^---\n/, "only the BUNDLE-ROOT index may carry a block");
|
||||
});
|
||||
|
||||
test("every concept file under brain/ carries a non-empty frontmatter type", () => {
|
||||
|
|
|
|||
|
|
@ -780,7 +780,7 @@ if [ -x "$BR_DIR/node_modules/.bin/tsx" ]; then
|
|||
BR_OUT=$( set +e; (cd "$BR_DIR" && npm test) 2>&1; echo "BR_EXIT:$?" )
|
||||
BR_EXIT=$(echo "$BR_OUT" | grep -oE 'BR_EXIT:[0-9]+' | grep -oE '[0-9]+' | head -1)
|
||||
BR_TESTS=$(echo "$BR_OUT" | grep -oE 'tests [0-9]+' | grep -oE '[0-9]+' | tail -1)
|
||||
BRAIN_TESTS_FLOOR=127 # SB-S0 34 [id(11)+profile(6)+fold(12)+scaffold(5)] + SB-S1 29 [ingest(14)+publish(9)+cli(6)] + SB-S2 19 [consolidate(12)+consolidate-cli(7)] + SB-S3b 12 [consolidate(10)+consolidate-cli(2)] + SB-S3c 19 [ingest(4)+publish(3)+assemble(8)+cli(4)] + SB-S3d 1 [scaffold dated-anchor seed] + SB-S3e 13 [reconcile: parse(5)+tiers(7)+io(1)]
|
||||
BRAIN_TESTS_FLOOR=142 # 127 decomposed: SB-S0 34 [id(11)+profile(6)+fold(12)+scaffold(5)] + SB-S1 29 [ingest(14)+publish(9)+cli(6)] + SB-S2 19 [consolidate(12)+consolidate-cli(7)] + SB-S3b 12 [consolidate(10)+consolidate-cli(2)] + SB-S3c 19 [ingest(4)+publish(3)+assemble(8)+cli(4)] + SB-S3d 1 [scaffold dated-anchor seed] + SB-S3e 13 [reconcile: parse(5)+tiers(7)+io(1)]; + 7 added between S3e and N24.6 without a floor bump (measured, not decomposed) + N24.6 8 [id: transliteration(3)+whitelist(1)+ascii-goldens(1), okf-conform: frontmatter placement(3)]
|
||||
if [ "$BR_EXIT" = "0" ] && [ -n "$BR_TESTS" ] && [ "$BR_TESTS" -ge "$BRAIN_TESTS_FLOOR" ]; then
|
||||
pass "brain suite green: $BR_TESTS tests pass (floor $BRAIN_TESTS_FLOOR)"
|
||||
else
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue