Forgejo rate-limits rapid port-22 handshakes. The bare per-target
`git push --all` + `git push --tags` opened ~22 SSH connections in rapid
succession; the server refused around the 6th (voyage's tag push) with
"port 22: Connection refused" (TCP-level, not auth), deterministically, on
every run. Export GIT_SSH_COMMAND with ControlMaster/ControlPath/ControlPersist
so all git-over-SSH reuses ONE persistent master connection (one TCP handshake
for the whole rollout).
Verified: 8 rapid multiplexed connections all succeed where the 6th bare
connection is refused; the full 11-target rollout then completed clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The operator window's step [a] called 40-validate-standalone.sh directly (strict
exit-code), so it STOPped on the first target carrying pre-existing in-repo test
red — voyage (2 doc-consistency drifts re phase_models/phase_signals, content moved
to docs/operations.md) and ai-psychosis (1). But the migration's ratified contract,
the one the Step-11 dry-run validated as PASS 11/11, is 'introduce no regression':
pre-existing in-repo red is the plugin's own concern, not a migration regression.
The window enforced a STRICTER gate than the contract the dry-run signed off.
Fix: new 41-validate-or-regression.sh — the single per-target gate the window calls
in [a]. It runs 40 strict, then on failure passes iff the standalone failing-test
NAME set is a SUBSET of the live in-repo set (the exact decision 99-dryrun.sh makes),
reusing sc2-regression.sh. A genuine extraction-introduced regression still STOPs the
window; a structure-validator fail and the config-audit gate stay strict.
Single-source the failing-name capture: extract capture_fails into capture-fails.sh
(mirrors the sc2-regression.sh extraction) so the live gate and the dry-run agree on
what 'failing' means; 99-dryrun.sh now delegates to it (behaviour identical).
Verified end-to-end on the real extracts: 40 strict fails voyage+ai-psychosis while
41 passes them 'N pre-existing, regression-relative'; clean targets (llm-security,
graceful-handoff) still pass via the strict path. New hermetic tests: capture-fails
3/3, 41 6/6 (strict-pass, regression-relative-pass, genuine-regression-fail,
structure-not-eligible, gate pass/fail). RUNBOOK per-repo step updated to 41.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The pilot died at line 93 with 'key<byte>: unbound variable': $key glued to
the U+2026 ellipsis let bash 5.3.9 read the multibyte byte as part of the name.
Braced to ${key}…; proven on /usr/local/bin/bash (repro + fixed form + bash -n).
Nothing was mutated on Forgejo — it failed before the [b] repo-create POST.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Review of the rollout script found 2 deterministic blockers on target #2,
playground-design-system — a shared component, not a marketplace plugin:
- step (d): DS has no .claude-plugin/plugin.json (it ships tokens.css/base.css/
schemas). The plugin.json assertion would die. Now DS-aware: assert tokens.css
for DS_KEY, plugin.json otherwise.
- step (e): 60-rewrite --only playground-design-system errors (--only name not in
marketplace.json) since DS has no catalog entry. New entry_present() gate skips
the flip when the key has no marketplace entry, mirroring 60-rewrite --all
(which only touches the 10 real plugins).
Net: pilot + 9 plugins flip (count_local to 0); DS is stood up as a repo but
never flipped. Verified: bash -n OK, entry_present + 60-rewrite behaviour tested.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>