fix(linkedin-studio): N10 — konfigurerbart målnivå-spenn [skip-docs]

C-8: gjør «praktisk anvendbar på alle nivå» til en konfigurerbar gate-akse og
fjerner den hardkodede primær-personaen. 6-fils scope (KTG-valgt) — gjør
targetLevel til en EKTE ende-til-ende-dimensjon i stedet for et fantomfelt:

- references/longform-quality-rules.md: «non-technical line manager» → brukerens
  konfigurerte target-level span (domene-generelt).
- config/personas.template.md: «primær trumfer» krever nå obligatorisk sekundær i
  MOTSATT ende av spennet; sekundær-NEI kan kun avskrives som «signal, ikke svikt»
  med eksplisitt ceiling-begrunnelse. Ny tekniker-ende-seed (Persona 4,
  Løsningsutvikler/AI-ingeniør) — tidligere kun 3 leder-personas.
- config/edition-state.template.json: additivt articles.NN.targetLevel-slot
  (default null, INGEN schemaVersion-bump — samme mønster som sourceTrendId/language).
- commands/newsletter.md: persister resolvert targetLevel ved Step 1.5-checkpointet.
- agents/persona-reviewer.md: leser articles.NN.targetLevel og plasserer personaene
  på spennet (fallback til persona-ekspertise om feltet er null).

Premiss-korreksjon: planens 4-fils scope antok targetLevel fantes i edition-state
via N7-broen; det stemte ikke (fantes kun i trend-store + fritekst-mapping). +2 filer
(edition-state-slot + newsletter-persist) gjør konsumet reelt.

TDD: ny Section 16q i test-runner (9 UNCOND. .md/.json-greps + non-vacuity self-test);
en vacuity-bug i egen edition-state-check fanget og skjerpet (grep slot-literal
"targetLevel": null, ikke bare ordet — sourceTrendId-docstringen inneholder allerede
"targetLevel"). test-runner 154→163/0, anti-erosion floor 137→146. Øvrige fem suiter
uendret grønne (trends 300 · brain 134 · hooks 140 · tests 35 · render 60).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014bE7VbkmR3cqHFEeGfzgwb
This commit is contained in:
Kjell Tore Guttormsen 2026-07-24 20:16:41 +02:00
commit 08106317db
6 changed files with 186 additions and 17 deletions

View file

@ -67,8 +67,13 @@
# non-vacuity self-test) in Section 16n; the randsone-gates guard (N9: newsletter.md Step 9 runs a scoped
# fact-reviewer + language-reviewer pass over the carousel slide text AND edition title, gates title-strength,
# declares the deterministic 'numbers, names, or quotes' recheck trigger, and both fact agents carry WebFetch,
# with a non-vacuity self-test) in Section 16p; the assertion-count anti-erosion floor (SC6) in Section 18. All
# are live below (Sections 818).
# with a non-vacuity self-test) in Section 16p; the configurable target-level span guard (N10: the hardcoded
# 'non-technical line manager' is gone from references/ + config/, references/longform-quality-rules.md names
# the 'target-level span', config/personas.template.md requires a mandatory secondary at the opposite end +
# an explicit ceiling justification + seeds a technical-end persona, config/edition-state.template.json carries
# the additive articles.NN.targetLevel slot, commands/newsletter.md persists it, agents/persona-reviewer.md
# consumes it onto the span, with a non-vacuity self-test) in Section 16q; the assertion-count anti-erosion
# floor (SC6) in Section 18. All are live below (Sections 818).
#
# Usage: bash scripts/test-runner.sh
# bash 3.2-safe: plain arrays only, no `declare -A`, no `mapfile`/`readarray`.
@ -1691,6 +1696,114 @@ fi
echo ""
# --- Section 16q: Configurable Target-Level Span (N10 / C-8) ---
echo "--- Configurable Target-Level Span (N10) ---"
# N10 turns "practically usable at all levels" into a CONFIGURABLE gate axis and removes the
# hardcoded primary persona. Six .md/.json surfaces must hold the invariants (all UNCONDITIONAL
# greps -> they raise the deps-absent floor):
# (C-8 absence) the hardcoded primary "non-technical line manager" is GONE from references/ + config/
# — the quality-rule now points at the edition's CONFIGURED target-level span instead.
# (C-8 span-ref) references/longform-quality-rules.md names the "target-level span" (domain-general),
# not a single baked-in reader role.
# (C-8 obligatorisk-sekundær) config/personas.template.md's «primær trumfer» rule now REQUIRES a
# mandatory secondary at the opposite end of the span; a secondary NO may be waived
# as «signal, not failure» ONLY with an explicit ceiling justification.
# (C-8 tekniker-seed) the seed library gains a technical-end persona (today all three seeds are leaders).
# (C-8 edition-state) config/edition-state.template.json carries an additive per-article targetLevel slot.
# (C-8 persist) commands/newsletter.md persists the resolved targetLevel to articles.NN.targetLevel.
# (C-8 consume) agents/persona-reviewer.md READS articles.NN.targetLevel and places personas on the span
# — a compound predicate (targetLevel field + edition-state read + span placement), so a
# non-vacuity self-test guards it (mirrors Section 16p).
PR_N10="agents/persona-reviewer.md"
targetlevel_consumed() { # $1 = text; wired iff it reads targetLevel FROM edition-state AND places on the span
echo "$1" | grep -qF "targetLevel" \
&& echo "$1" | grep -qF "articles." \
&& echo "$1" | grep -qF "target-level span"
}
TL_SELFTEST_OK=1
if ! targetlevel_consumed "read articles.NN.targetLevel and place the personas on the target-level span"; then
TL_SELFTEST_OK=0; echo " non-vacuity FAIL: a fully-wired targetLevel-consumption probe was not detected"
fi
while IFS= read -r probe; do
[ -z "$probe" ] && continue
if targetlevel_consumed "$probe"; then
TL_SELFTEST_OK=0; echo " false-positive FAIL: under-wired targetLevel probe accepted -> $probe"
fi
done <<'NEGATIVE16Q'
place the personas on the target-level span but never read the field
read articles.NN.targetLevel but do not mention the span placement here
the targetLevel matters for the target-level span without an edition-state read
NEGATIVE16Q
if [ "$TL_SELFTEST_OK" -eq 1 ]; then
pass "targetLevel-consumption self-test: predicate needs targetLevel + edition-state read (articles.) + span placement (1 accepted, 3 under-wired rejected)"
else
fail "targetLevel-consumption self-test failed - the N10 consumption lint is vacuous or over-eager"
fi
# (C-8 absence) the hardcoded primary persona is gone from the shippable surfaces
if grep -rn "non-technical line manager" references/ config/ >/dev/null 2>&1; then
fail "N10: hardcoded 'non-technical line manager' still present in references/ or config/ — the primary persona is not de-hardcoded (C-8)"
else
pass "N10: no hardcoded 'non-technical line manager' in references/ or config/ (C-8 absence)"
fi
# (C-8 span-ref) the quality rule points at the configured span, not a baked-in role
if grep -qF "target-level span" references/longform-quality-rules.md; then
pass "references/longform-quality-rules.md names the configured 'target-level span' (C-8 span-ref)"
else
fail "references/longform-quality-rules.md missing 'target-level span' — the served-reader rule is still role-hardcoded (C-8)"
fi
# (C-8 obligatorisk-sekundær) the «primær trumfer» rule requires a mandatory secondary at the opposite end
if grep -qF "opposite end of the target-level span" config/personas.template.md; then
pass "config/personas.template.md requires a mandatory secondary at the opposite end of the span (C-8 obligatorisk-sekundær)"
else
fail "config/personas.template.md missing the 'opposite end of the target-level span' requirement — one reader can still self-certify all levels (C-8)"
fi
# (C-8 ceiling-begrunnelse) a secondary NO may only be waived with an explicit ceiling justification
if grep -qF "explicit ceiling justification" config/personas.template.md; then
pass "config/personas.template.md gates the secondary-NO waiver behind an explicit ceiling justification (C-8 ceiling-begrunnelse)"
else
fail "config/personas.template.md missing 'explicit ceiling justification' — a secondary NO can still be waved away as 'signal, not failure' unproven (C-8)"
fi
# (C-8 tekniker-seed) the seed library gains a technical-end persona (today all three seeds are leaders)
if grep -qF "Technical end of the target-level span" config/personas.template.md; then
pass "config/personas.template.md seeds a technical-end persona opposite the leader personas (C-8 tekniker-seed)"
else
fail "config/personas.template.md missing a 'Technical end of the target-level span' seed — the library is still leader-only (C-8)"
fi
# (C-8 edition-state) the per-article targetLevel SLOT exists (additive, no schemaVersion bump).
# Grep the slot's default literal `"targetLevel": null`, NOT the bare word — the latter already occurs
# inside the sourceTrendId doc-string ("angle / targetLevel / key-points"), which would match vacuously.
if grep -qF '"targetLevel": null' config/edition-state.template.json; then
pass "config/edition-state.template.json carries the additive articles.NN.targetLevel slot (default null, C-8 edition-state)"
else
fail "config/edition-state.template.json missing the '\"targetLevel\": null' slot — persona-reviewer would consume a phantom field (C-8)"
fi
# (C-8 persist) newsletter.md persists the resolved targetLevel to the per-article slot
if grep -qF "articles.NN.targetLevel" commands/newsletter.md; then
pass "commands/newsletter.md persists the resolved targetLevel to articles.NN.targetLevel (C-8 persist)"
else
fail "commands/newsletter.md never persists articles.NN.targetLevel — the slot stays null and consumption is vacuous (C-8)"
fi
# (C-8 consume) persona-reviewer reads targetLevel from edition-state and places personas on the span
PR_CONTENT=$(cat "$PR_N10")
if targetlevel_consumed "$PR_CONTENT"; then
pass "agents/persona-reviewer.md reads articles.NN.targetLevel and places personas on the target-level span (C-8 consume)"
else
fail "agents/persona-reviewer.md does not consume articles.NN.targetLevel onto the span — targetLevel is set but never read (C-8)"
fi
echo ""
# --- Section 18: Assertion-Count Anti-Erosion (SC6) ---
# The lint self-modifies its own checks, so a green run could mask a silently dropped
# assertion. Pin the total pass()+fail() invocations as a monotonic floor; the count
@ -1725,12 +1838,16 @@ echo ""
# Step-8 auto-gull compound grep) = 129; +8 for N9's eight UNCONDITIONAL Section-16p checks (randsone-gate
# self-test + Step-9 randsone_gated compound grep + Step-9 title-strength grep + newsletter C-6 trigger-nouns
# grep + newsletter C-6 scoped-fact-reviewer grep + newsletter C-6 determinism-marker grep + fact-checker
# WebFetch grep + fact-reviewer WebFetch grep) = 137.
# WebFetch grep + fact-reviewer WebFetch grep) = 137; +9 for N10's nine UNCONDITIONAL Section-16q checks
# (targetLevel-consumption self-test + no-hardcoded-line-manager absence grep + references target-level-span
# grep + personas opposite-end obligatorisk-sekundær grep + personas explicit-ceiling-justification grep +
# personas technical-end seed grep + edition-state targetLevel-slot grep + newsletter articles.NN.targetLevel
# persist grep + persona-reviewer targetLevel-consumption compound grep) = 146.
# NB: the floor tracks the deps-absent MINIMUM (conditional TS suites warn-skip and drop
# the count), so it is bumped only by UNCONDITIONAL new checks — NOT pinned to the
# deps-present TOTAL_CHECKS (that would zero the warn-skip margin and false-fail a fresh
# clone). Runs last so TOTAL_CHECKS sees every prior check.
ASSERT_BASELINE_FLOOR=137
ASSERT_BASELINE_FLOOR=146
TOTAL_CHECKS=$((PASS + FAIL))
if [ "$TOTAL_CHECKS" -ge "$ASSERT_BASELINE_FLOOR" ]; then
pass "assertion-count anti-erosion: $TOTAL_CHECKS checks >= baseline floor $ASSERT_BASELINE_FLOOR"