Implement `design_docs/001-no-logic-in-shell.md` — tooling tier moves to Python nf-80rk ← Beads

closed priority 2 task unassigned activesession:a

Twinned from https://github.com/cfsh/ninefold/issues/433 by tools/beads/import_github.py's reconcile pass.

Filed unclaimed and unlabelled by the /design round that produced it, for a future /triage to activate. Do not start this piecemeal — the work breakdown lives in the doc, attached to the argument that produced it.

Doc: [design_docs/001-no-logic-in-shell.md](https://github.com/cfsh/ninefold/blob/main/design_docs/001-no-logic-in-shell.md) · design PR #429

The recommendation, in two lines

No logic in checked-in shell. The Tier-2 tooling tier (~2,478 lines across 15 scripts) moves to Python — directly executable ./tools/pr/poll.py with #!/usr/bin/env python, subprocess wrappers (nflib) with hand-written fakes, and a single polyglot ./presubmit.py that lands before any script migrates. No .sh wrappers, no Bazel, no compiled artifact.

Why: 92% of the tier's code lines sit in scripts past the Google Shell Style Guide's own rewrite threshold, and the failure mode is documented in this repo — #352 killed the Tier-0 reviewer for ~38 hours from a set -euo pipefail wrinkle while the log kept looking healthy; #382 reintroduced the same class *in the fix for it*; the gojq anchor bug shipped twice in one file. Cost is zero new dependencies: stdlib unittest, and python is already on the critical path because this box has no jq.

Shape of the work

~11 PRs, honestly 2 chunks, not 1. The doc marks the split after piece 5.

Migration is characterization-first: a throwaway bash harness pins the existing script's golden output, port, diff byte-identical, delete both.

Two things the implementing session must not rediscover the hard way
  1. nflib/__init__.py must sys.stdout.reconfigure(encoding="utf-8") on day one. Python's stdout here is cp1252 and nine of the characters this tier prints are outside it — it passes interactively and crashes under redirection, which is every cron tick. probe_gate.sh:161's sentinel is eight U+2500, so a probe_all.py literally cannot emit the string the UI gate matches on. §2.4.
  2. os.environ.get(X, "0") == "1", never bare truthiness. Every escape hatch in the tier is an exact-string test; Python truthiness inverts three of them, and REVIEWER_SELF_SYNC=0 would run git reset --hard in a working checkout. §6.
Supersedes

#414's prescription (a permanent bash test harness). Its *diagnosis* is excellent, reproduced and credited throughout the doc, and its mkrepo/stub/assert_unchanged harness is adopted wholesale — but as a throwaway differential oracle for the migration, not as the destination. #414 should close when piece 1 lands.

Also absorbs the Shell tooling backlog chunk (#414, #411, #412) and answers part of CI / presubmit (#416, #417, #409) — triage should probably collapse those rather than run three chunks.

Dependencies

None.

Comments

No comments.

Add a comment