feat(linkedin-studio): N14 — foldIns-fangst + Step 11 retro + background-headless + språkregel-akkumulering [skip-docs]

Lukker sløyfen pluginen manglet: en rettelse operatøren gjør i utgave N
håndheves i N+1 i stedet for å bli gjenoppdaget. Maskineriet fantes
(foldIns-skjema + promote→ratify siden fix #1); det som manglet var wiring —
ingenting skrev til køen og ingen fase tømte den.

- Fangst (A2-F7): Steps 2.5/3a/5.5/6.5 appender rettelsen ordrett med trigger,
  decision "pending". Ubetinget og bevisst dum — å avgjøre ved fangst om noe
  "fortjener" en regel er nettopp slik køen holder seg tom og sløyfen dør.
- Step 11 retro (A2-F8), ≤5 min ETTER scheduling: promoter køen med eksplisitt
  JA/NEI (mekanisk → atomisk contract-gate-promotering, teller kun med grønn
  --ratify; dømmekraft → operatørens språkregelfil; NEI → rejected, beholdes),
  effort-oppsummering fra MÅLT phaseLog (aldri re-estimert), og ÉN
  friksjons-spørring som besvares tilbake til operatøren. Faser 18 → 19;
  resumption-tabellen ruter scheduling → Step 11, retro → complete.
- articles.NN.retro: additiv-valgfri (default null), schemaVersion forblir 1.
- Background-headless (A2-F6): --background kjører pakken i en bakgrunnsagent
  som skriver rapporten til disk; drafting-sesjonen leser fila. Samme isolasjon
  som fersk sesjon, uten copy-paste-sømmen. Inline fan-out = eksplisitt fallback.
- Språkregler (C-10): ${DATA}/language-rules/<lang>.md (opt-in, template).
  Leses TO ganger — av language-reviewer (fanger) og av Step 4 (forebygger).
  Shippet banliste forblir baseline; brukerfila utvider.
- references/fold-in-loop.md (A2-F9): loopen dokumentert domene-generelt in-tree,
  så en adopter uten ekstern skrivekontrakt har hele sløyfen. Refs 28 → 29.

Suiter (alle grønne): test-runner 197 → 217 (Section 16u: 18 ubetingede greps +
non-vacuity self-test; fase-sveip 16 → 17 faser; floor 179 → 198) · hooks 174 ·
trends 300 · brain 134 · editions 72 · specifics-bank 45 · contract-gate 33 ·
tests 35 · render 60.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QxvWAjte7vPcF79QeSRvRJ
This commit is contained in:
Kjell Tore Guttormsen 2026-07-25 12:39:16 +02:00
commit f0532dce3f
9 changed files with 711 additions and 30 deletions

View file

@ -100,7 +100,7 @@ warn() { echo -e "${YELLOW}⚠${NC} $1"; WARN=$((WARN + 1)); }
# with the files when adding/removing an agent, command, reference, or skill.
EXPECT_AGENTS=20
EXPECT_COMMANDS=30
EXPECT_REFS=28
EXPECT_REFS=29
EXPECT_SKILLS=6
# Pre-M0 references/ baseline was 25. Every ref doc added since is NAMED below, so the
# count bump always maps to an intended, named addition — never an incidental doc masked
@ -112,6 +112,7 @@ M0_REF="references/data-path-convention.md"
POSTM0_REFS=(
"references/trend-scoring-modes.md" # research-engine slice 2a (scoring SSOT)
"references/figure-design-guidelines.md" # MR-F4 N3 (coded-figure design rules + token convention)
"references/fold-in-loop.md" # N14 A2-F9 (capture->classify->promote->enforce, domain-general)
)
echo "================================================"
@ -1996,13 +1997,14 @@ NL_FLAT=$(tr '\n' ' ' < "$NL_N12" | tr -s ' ')
MISSING_PHASES=""
for phase in lived-specifics research skeleton-pitch spine-prose draft consistency-quality \
contract-gate factcheck-sweep editorial-review persona-sweep-prelock \
headless-review annotation visual-assets lock-delivery hook-conversion-gate scheduling; do
headless-review annotation visual-assets lock-delivery hook-conversion-gate scheduling \
retro; do
if ! printf '%s' "$NL_FLAT" | grep -qF "run the protocol (Step 0) for ${BT}${phase}${BT}"; then
MISSING_PHASES="$MISSING_PHASES $phase"
fi
done
if [ -z "$MISSING_PHASES" ]; then
pass "all 16 canonical phases carry a transition call - the lead-time log has no holes (A1-12)"
pass "all 17 canonical phases carry a transition call - the lead-time log has no holes (A1-12)"
else
fail "newsletter phases with no transition call:$MISSING_PHASES - lead time silently undercounts them (A1-12)"
fi
@ -2194,6 +2196,202 @@ fi
echo ""
# --- Section 16u: Fold-In Loop + Retro Step (N14 / A2-F6,F7,F8,F9, C-10) ---
echo "--- Fold-In Loop + Retro (N14) ---"
# N14 closes the loop the whole pipeline was missing: a correction the operator makes in
# edition N is ENFORCED in edition N+1 instead of being re-discovered. The machinery already
# existed (foldIns schema + promote->ratify); what was absent was the wiring - nothing wrote
# to the queue and no step ever emptied it. So the bindings worth linting are:
# (A2-F7 capture) every step where the operator corrects prose appends to the queue - a
# compound predicate (queue named + left open + routed), so a non-vacuity
# self-test guards it (mirrors Sections 16p/16q/16r/16s/16t).
# (A2-F8 retro) Step 11 exists and does all three jobs: promote the queue, summarize the
# measured effort (phaseLog, N12), ask ONE friction question.
# (A2-F6 headless) the cold package runs as a background agent writing to a file - the
# maximum-independence path with no manual copy-paste seam.
# (C-10 language) confirmed language corrections accumulate in a USER file the reviewer
# and the drafting step both read - the shipped ban-list stays the baseline.
# (A2-F9 adopter) the fang->klassifiser->promoter->handhev loop is documented in-tree, so
# an adopter without the author's contract still has the whole loop.
NL_N14="commands/newsletter.md"
HR_N14="commands/headless-review.md"
LR_N14="agents/language-reviewer.md"
EST_N14="config/edition-state.template.json"
LANG_TPL="config/language-rules.template.md"
FOLDIN_REF="references/fold-in-loop.md"
foldin_capture_wired() { # $1 = text; wired iff it NAMES the queue, leaves it OPEN, and ROUTES it
echo "$1" | grep -qF "foldIns" \
&& echo "$1" | grep -qF "pending" \
&& echo "$1" | grep -qF "Step 11"
}
FI_SELFTEST_OK=1
if ! foldin_capture_wired "append the correction to articles.NN.foldIns with decision pending; Step 11 routes it"; then
FI_SELFTEST_OK=0; echo " non-vacuity FAIL: a fully-wired fold-in-capture probe was not detected"
fi
while IFS= read -r probe; do
[ -z "$probe" ] && continue
if foldin_capture_wired "$probe"; then
FI_SELFTEST_OK=0; echo " false-positive FAIL: under-wired fold-in-capture probe accepted -> $probe"
fi
done <<'NEGATIVE16U'
append the correction to articles.NN.foldIns with decision pending, routed by nobody
capture the correction as pending and let Step 11 route it, queue left unnamed
append it to articles.NN.foldIns and Step 11 promotes it, never marked open
NEGATIVE16U
if [ "$FI_SELFTEST_OK" -eq 1 ]; then
pass "fold-in-capture self-test: predicate needs foldIns + pending + Step 11 (1 accepted, 3 under-wired rejected)"
else
fail "fold-in-capture self-test failed - the N14 wiring lint is vacuous or over-eager"
fi
# (A2-F7 capture) each of the four fold-in steps feeds the queue - a step that folds a
# correction in without capturing it is exactly how a rule gets re-discovered next edition
STEP25_N14=$(awk '/^## Step 2.5:/{f=1} /^## Step 3a:/{f=0} f' "$NL_N14")
STEP3A_N14=$(awk '/^## Step 3a:/{f=1} /^## Step 3b:/{f=0} f' "$NL_N14")
STEP55_N14=$(awk '/^## Step 5.5:/{f=1} /^## Step 6:/{f=0} f' "$NL_N14")
STEP65_N14=$(awk '/^## Step 6.5:/{f=1} /^## Step 7:/{f=0} f' "$NL_N14")
STEP11_N14=$(awk '/^## Step 11:/{f=1} /^## Distribution channel/{f=0} f' "$NL_N14")
if foldin_capture_wired "$STEP25_N14"; then
pass "Step 2.5 captures operator corrections into the fold-in queue (A2-F7)"
else
fail "newsletter Step 2.5 folds annotations in without capturing them - the rule is lost (A2-F7)"
fi
if foldin_capture_wired "$STEP3A_N14"; then
pass "Step 3a captures operator corrections into the fold-in queue (A2-F7)"
else
fail "newsletter Step 3a folds annotations in without capturing them - the rule is lost (A2-F7)"
fi
if foldin_capture_wired "$STEP55_N14"; then
pass "Step 5.5 captures approved editorial flags into the fold-in queue (A2-F7)"
else
fail "newsletter Step 5.5 folds flags in without capturing them - the rule is lost (A2-F7)"
fi
if foldin_capture_wired "$STEP65_N14"; then
pass "Step 6.5 captures approved cold-review flags into the fold-in queue (A2-F7)"
else
fail "newsletter Step 6.5 folds flags in without capturing them - the rule is lost (A2-F7)"
fi
# (A2-F8 retro) the step exists and empties the queue with an explicit operator verdict
if echo "$STEP11_N14" | grep -qF "foldIns" && echo "$STEP11_N14" | grep -qF "promoted"; then
pass "Step 11 promotes the fold-in queue with an operator verdict (A2-F8)"
else
fail "newsletter has no Step 11 fold-in promotion - the queue fills up and is never emptied (A2-F8)"
fi
# (A2-F8 effort) the effort summary reads the MEASURED log (N12), not the model's memory
if echo "$STEP11_N14" | grep -qF "phaseLog"; then
pass "Step 11 summarizes effort from the measured phaseLog - no new measuring apparatus (A2-F8/A1-12)"
else
fail "newsletter Step 11 does not read phaseLog - effort is re-estimated from memory (A2-F8)"
fi
# (A2-F8 friction) exactly one friction question, and the operator's own register stays theirs
if echo "$STEP11_N14" | grep -qF "process friction" && echo "$STEP11_N14" | grep -qF "never write"; then
pass "Step 11 asks one friction question and never writes the operator's register (A2-F8)"
else
fail "newsletter Step 11 lacks the single friction question or claims write access to the operator's register (A2-F8)"
fi
# (A2-F8 transition) retro is a real phase - logged like every other, and it re-closes the row
if echo "$STEP11_N14" | grep -qF "register-complete" && echo "$STEP11_N14" | grep -qF "${BT}retro${BT}"; then
pass "Step 11 runs the retro transition and re-closes the register row (A1-11/A2-F8)"
else
fail "newsletter Step 11 leaves the edition re-opened in the register or logs no retro phase (A1-11/A2-F8)"
fi
# (A2-F8 schema) edition-state documents AND carries the retro slot
if grep -qF '"retro": "Per-article retro record' "$EST_N14" && grep -qF '"retro": null' "$EST_N14"; then
pass "edition-state documents and carries the per-article retro slot (A2-F8 schema)"
else
fail "$EST_N14 has no documented retro slot - the retro leaves no durable record (A2-F8)"
fi
# (A2-F8 resumption) an edition aborted after scheduling resumes INTO the retro, not past it
RESUME_N14=$(awk '/^### Deterministic resumption/{f=1} /^### Phase-transition protocol/{f=0} f' "$NL_N14")
if echo "$RESUME_N14" | grep -qF "Step 11" && echo "$RESUME_N14" | grep -qF "${BT}retro${BT}"; then
pass "resumption table routes scheduling -> Step 11 and retro -> complete (A2-F8)"
else
fail "newsletter resumption table skips the retro - an aborted session drops the fold-in queue (A2-F8)"
fi
# (A2-F6 headless) the cold package can run as a background agent writing its report to a file
if grep -qF "run_in_background" "$HR_N14" && grep -qF -- "--background" "$HR_N14"; then
pass "headless-review offers a background run - cold isolation without a fresh session (A2-F6)"
else
fail "$HR_N14 has no background mode - maximum independence still costs a manual session switch (A2-F6)"
fi
if grep -qF "no copy-paste" "$HR_N14"; then
pass "headless-review states the seam it removes - the report is read from disk, not pasted (A2-F6)"
else
fail "$HR_N14 still relies on pasting the report back into the drafting session (A2-F6)"
fi
# (A2-F6 wiring) Step 6.5 actually offers that path instead of the paste-back one
if echo "$STEP65_N14" | grep -qF -- "--background"; then
pass "newsletter Step 6.5 routes maximum-independence through the background run (A2-F6)"
else
fail "newsletter Step 6.5 still sends the operator to a fresh session with a paste-back (A2-F6)"
fi
# (C-10 language) the accumulating user file is READ by the reviewer and by the drafting step
if grep -qF "language-rules/" "$LR_N14"; then
pass "language-reviewer reads the accumulated user language rules (C-10)"
else
fail "$LR_N14 grades only against the shipped list - confirmed corrections never bind (C-10)"
fi
STEP4_N14=$(awk '/^## Step 4: /{f=1} /^## Step 4.5:/{f=0} f' "$NL_N14")
if echo "$STEP4_N14" | grep -qF "language-rules/"; then
pass "newsletter Step 4 reads the accumulated user language rules while drafting (C-10)"
else
fail "newsletter Step 4 ignores the accumulated language rules - they only catch, never prevent (C-10)"
fi
if echo "$STEP11_N14" | grep -qF "language-rules/"; then
pass "Step 11 appends confirmed language corrections to the user rules file (C-10)"
else
fail "newsletter Step 11 never writes the language-rules file - judgment corrections evaporate (C-10)"
fi
# (C-10 opt-in) the file is offered as a template, never written for the user (slots pattern)
if [ -f "$LANG_TPL" ] && grep -qF "OPT-IN" "$LANG_TPL"; then
pass "language-rules template ships with an explicit opt-in - nothing is imposed (C-10/de-niche)"
else
fail "$LANG_TPL missing or does not state the opt-in (C-10/de-niche)"
fi
# (A2-F9 adopter) the whole loop is documented in-tree, all four stages named
FOLDIN_LOOP_OK=1
for stage in "Capture" "Classify" "Promote" "Enforce"; do
grep -qF "$stage" "$FOLDIN_REF" 2>/dev/null || FOLDIN_LOOP_OK=0
done
if [ -f "$FOLDIN_REF" ] && [ "$FOLDIN_LOOP_OK" -eq 1 ]; then
pass "fold-in loop documented in-tree with all four stages (A2-F9)"
else
fail "$FOLDIN_REF missing or does not name capture/classify/promote/enforce (A2-F9)"
fi
# (A2-F9 self-contained) an adopter without the author's private contract still has the loop,
# and the four accumulation silos are named as one collection point
if grep -qF "not a gap" "$FOLDIN_REF" 2>/dev/null \
&& grep -qF "specifics-bank" "$FOLDIN_REF" 2>/dev/null \
&& grep -qF "ab-test" "$FOLDIN_REF" 2>/dev/null; then
pass "fold-in loop is adopter-self-contained and names the accumulation silos (A2-F9)"
else
fail "$FOLDIN_REF reads as requiring the author's private contract, or omits the silos (A2-F9)"
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
@ -2245,12 +2443,19 @@ echo ""
# wiring grep + calendar opt-in grep + session-start two-block compound grep + coverage
# queue+register grep + data-dir placement grep + Step-10 nextFreeSlot grep + Step-10 --slot
# grep + scheduling-ref directional grep + scheduling-ref operator-grid grep + slot-template
# schema/opt-in grep + de-niche no-hardcoded-times/no-committed-grid check) = 179.
# schema/opt-in grep + de-niche no-hardcoded-times/no-committed-grid check) = 179; +19 for
# N14's nineteen UNCONDITIONAL Section-16u checks (fold-in-capture self-test + four capture
# greps for Steps 2.5/3a/5.5/6.5 + Step-11 promotion grep + Step-11 phaseLog grep + Step-11
# friction/never-write grep + Step-11 retro-transition/re-close grep + edition-state retro
# doc+slot grep + resumption retro-routing grep + headless background-mode grep + headless
# no-copy-paste grep + Step-6.5 background-routing grep + language-reviewer rules-file grep +
# Step-4 rules-file grep + Step-11 rules-file append grep + language-rules template opt-in
# check + fold-in-loop four-stage check + fold-in-loop adopter/silos grep) = 198.
# 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=179
ASSERT_BASELINE_FLOOR=198
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"