operationalize_design.py is a py_binary the merge gate imports as a library — extract the shared logic nf-2og ← Beads

open priority 2 task unassigned

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

Deferred from #1018 by @aedanpope (chat, 2026-08-18): *"lets save more changes for later on figuring out a better way for py deps"* — filed so the shape of the fix isn't lost.

The smell. nflib.channels._load_operationalize() imports design_docs/tools/operationalize_design.py by file path via importlib, because the design-doc-followup channel needs the tool's projected_text()/_fill_doc() and there is no importable library that owns them. In Bazel terms: the tool is a py_binary (owns main(), argparse, its own sys.path bootstrap) and the gate is depending on it as if it were a py_library — a dep shape Bazel refuses outright, hand-worked-around here with the loader.

Why not just import: design_docs/tools/ is on nobody's sys.path; putting it there from library code is a process-wide side effect (status.py etc. become importable/shadowable top-level names); and the lazy by-path load is what lets a broken tool module degrade to "no grant" inside channels.grants() instead of crashing authority — and with it merge_pr and the required check — for every PR at import time.

The fix. The standard binary/library split:

  1. Move the shared, read-only logic (projected_text, _fill_doc, PreviewImpossible, and the parse/stamp/cell helpers they need) into an importable package. tools/github_live is the natural home — already the tool's only real dependency, already GOVERNANCE (the gate executes it, #1018), and the moved code keeps its zero-clone-dependency rule (it needs only github_live's own Gh + parser, no nflib).
  2. Shrink operationalize_design.py to the binary: argparse + calls into the library. It stays beside the docs it operates on (design 008's deliberate siting).
  3. nflib.channels replaces _load_operationalize() with a plain lazy import inside the channel function — same fail-closed property, no path arithmetic.
  4. DesignDocFollowupMatchesTheTool-style pinning survives unchanged: the end-to-end test in test_nflib_channels.py already runs the real projector against github_live's fakes.

Worth doing together with any broader "better way for py deps" decision (whether that's a repo-root package layout, a build system, or just a stated convention for what may import what) rather than as a one-off move.

Dependencies

None.

Comments

No comments.

Add a comment