feat(linkedin-studio): N9 — randsone-gates (krok/carousel/tittel) + deterministisk fakta-recheck + WebFetch [skip-docs]

C-4/C-9: Step 9 (post-lock) gets a scoped fact-reviewer (F1/F3) + language-reviewer
(L1/L2) pass over the four feed-facing surfaces — delingstekst hook, samle post,
carousel slide text, edition title — plus a title-strength gate, before the hook
conversion binary. Placed at Step 9 (first phase where all four exist; operates on
distribution copy only, never the locked body — body/canonical-title defect = pivot).

C-6: deterministic post-factcheck fact-invariant after Step 5 — any later fold-in
whose diff touches a sentence with numbers, names, or quotes fires a mandatory scoped
fact-reviewer mini-recheck (not a judgment call), closing the pivot-heuristic blind
spot; enforced at the 6.5 + 7 fold-in sites.

C-7: WebFetch added to fact-checker + fact-reviewer (verbatim source text for quote
precision / exact-figure attribution).

TDD RED→GREEN: test-runner Section 16p (8 unconditional greps + non-vacuity
self-test), anti-erosion floor 129 → 137. Suite 146 → 154/0. Domain-general.

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 01:45:45 +02:00
commit 34c6c854c0
5 changed files with 239 additions and 41 deletions

View file

@ -64,7 +64,10 @@
# the trends-brief-history/diff wiring guard (RE-R3e: scripts/trends/src/brief.ts has
# 'export function diffSurfaced' AND 'parseSurfacedFrontmatter' AND the 'Nytt siden sist' section AND
# the 'surfaced: ' frontmatter emit, scripts/trends/src/cli.ts wires 'selectPriorBriefFile', with a
# non-vacuity self-test) in Section 16n; the assertion-count anti-erosion floor (SC6) in Section 18. All
# 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).
#
# Usage: bash scripts/test-runner.sh
@ -1594,6 +1597,100 @@ fi
echo ""
# --- Section 16p: Randsone-Gates - feed-facing surfaces + deterministic fact-recheck + WebFetch (N9 / C-4,C-6,C-7,C-9) ---
echo "--- Randsone Gates (N9) ---"
# N9 closes the border-zone gap: the hook (delingstekst), the samle post, the carousel slide text and the
# edition title reach the feed today with NO fact/language gate (Step 5/6.5 run on the article BODY;
# carousel is authored in Step 7.5 AFTER the last content gate; Step 9 judged only the hook's click-worth).
# Four invariants must hold in the .md surfaces:
# (C-4/C-9) Step 9's post-lock gate is extended with a scoped fact-reviewer + language-reviewer pass over
# all four distribution surfaces (delingstekst hook, samle post, carousel slide text, edition
# title candidates). Step 9 is the first phase where all four exist, and it already operates on
# post-lock distribution copy the gate may revise without reopening the locked body.
# (C-6) any post-factcheck fold-in whose diff touches a sentence with numbers, names or quotes fires a
# mandatory scoped fact-reviewer mini-recheck - a DETERMINISTIC trigger ("not a judgment call"),
# which also covers the pivot-heuristic blind spot (a swapped number no >20% rule would catch).
# (C-7) both fact agents (fact-checker, fact-reviewer) carry WebFetch - quote precision needs the
# verbatim source text, not just a search snippet.
# The randsone predicate is compound (both reviewers AND both new surfaces), so a non-vacuity self-test
# guards it (mirrors Section 16o). All checks are UNCONDITIONAL .md greps -> they raise the deps-absent floor.
NL_N9="commands/newsletter.md"
# Slice the Step 9 block (## Step 9: up to ## Step 10:) so the gate literals are scoped to that phase.
STEP9_BLOCK=$(awk '/^## Step 9:/{f=1} /^## Step 10:/{f=0} f' "$NL_N9")
randsone_gated() { # $1 = text; wired iff BOTH reviewers AND BOTH new surfaces are named
echo "$1" | grep -qF "fact-reviewer" \
&& echo "$1" | grep -qF "language-reviewer" \
&& echo "$1" | grep -qF "carousel slide text" \
&& echo "$1" | grep -qF "edition title"
}
RZ_SELFTEST_OK=1
if ! randsone_gated "scoped fact-reviewer + language-reviewer over the carousel slide text and edition title candidates"; then
RZ_SELFTEST_OK=0; echo " non-vacuity FAIL: a fully-wired randsone-gate probe was not detected"
fi
while IFS= read -r probe; do
[ -z "$probe" ] && continue
if randsone_gated "$probe"; then
RZ_SELFTEST_OK=0; echo " false-positive FAIL: under-wired randsone-gate probe accepted -> $probe"
fi
done <<'NEGATIVE16P'
language-reviewer over the carousel slide text and edition title but no fact reviewer here
fact-reviewer + language-reviewer over the edition title only, no carousel surface
fact-reviewer + language-reviewer over the carousel slide text only, no title surface
NEGATIVE16P
if [ "$RZ_SELFTEST_OK" -eq 1 ]; then
pass "randsone-gate self-test: predicate needs BOTH fact+language reviewers AND both new surfaces (1 accepted, 3 under-wired rejected)"
else
fail "randsone-gate self-test failed - the randsone lint is vacuous or over-eager"
fi
# (C-4/C-9) Step 9 gates all four distribution surfaces through fact + language reviewers
if randsone_gated "$STEP9_BLOCK"; then
pass "newsletter.md Step 9 runs the scoped fact-reviewer + language-reviewer randsone gate over carousel + title (C-4/C-9)"
else
fail "newsletter.md Step 9 missing the randsone fact+language gate - hook/samle/carousel/title still reach the feed ungated (C-4/C-9)"
fi
# (C-9) the edition's own title now faces a strength gate at Step 9 (today only the hook's two lines were judged)
if echo "$STEP9_BLOCK" | grep -qF "title-strength"; then
pass "newsletter.md Step 9 gates the edition title-strength, not only the hook (C-9)"
else
fail "newsletter.md Step 9 missing the title-strength gate - the edition title still ships unjudged (C-9)"
fi
# (C-6) deterministic post-factcheck fact-recheck trigger (nouns + mechanism + determinism marker)
if grep -qF "numbers, names, or quotes" "$NL_N9"; then
pass "newsletter.md declares the deterministic recheck trigger nouns ('numbers, names, or quotes', C-6)"
else
fail "newsletter.md missing the recheck trigger nouns 'numbers, names, or quotes' (C-6)"
fi
if grep -qF "scoped fact-reviewer" "$NL_N9"; then
pass "newsletter.md routes the post-factcheck recheck to a scoped fact-reviewer (C-6)"
else
fail "newsletter.md missing 'scoped fact-reviewer' - the recheck has no mechanism (C-6)"
fi
if grep -qF "not a judgment call" "$NL_N9"; then
pass "newsletter.md marks the recheck trigger deterministic ('not a judgment call', C-6)"
else
fail "newsletter.md missing the determinism marker 'not a judgment call' - the trigger is still discretionary (C-6)"
fi
# (C-7) both fact agents carry WebFetch (verbatim source text for quote precision)
if grep -qF "WebFetch" agents/fact-checker.md; then
pass "fact-checker.md carries WebFetch (verbatim source text for quote precision, C-7)"
else
fail "fact-checker.md missing WebFetch in tools - quote precision has no verbatim-source fetch (C-7)"
fi
if grep -qF "WebFetch" agents/fact-reviewer.md; then
pass "fact-reviewer.md carries WebFetch (verbatim source text for quote precision, C-7)"
else
fail "fact-reviewer.md missing WebFetch in tools - cold re-verification has no verbatim-source fetch (C-7)"
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
@ -1625,12 +1722,15 @@ echo ""
# 'surfaced: ' frontmatter grep) = 123; +6 for N8's six UNCONDITIONAL Section-16o checks (voice-chain
# self-test + voice-trainer chronicle-voice-profile grep + newsletter chronicle-voice-profile grep +
# newsletter chronicle-voice-drift-log read grep + newsletter Step-1 language-resolve grep + newsletter
# Step-8 auto-gull compound grep) = 129.
# 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.
# 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=129
ASSERT_BASELINE_FLOOR=137
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"