docs(governance): Q-124 — Typer costs the cheap option down, and moves the target

Jeroen raised Typer as the Python-CLI option. Costing it changed what the
question is actually about.

The repo is already most of the way there: pyproject.toml exists, `make
setup-venv` already does `pip install -e ".[dev]"`, and 22 tooling files
already use argparse. What is missing is a single line — there is no
[project.scripts] entry at all, so no console entrypoint exists. This is
consolidation, not authorship, and it resolves the largest friction (per-script
permission prompts) for one allowlist entry.

But the framework is the second decision, not the first. A [project.scripts]
entrypoint lands in .venv/bin/, which is on PATH only when the venv is
activated — and agents and git hooks never activate it. That is the same split
VENV_PY already papers over in the Makefile, and precisely the failure recorded
for tea: an absolute path breaks the Bash(tea *) rule and prompts every time,
fixed only by a bare name on PATH. So the deliverable is "one bare command
reliably on PATH" (uv tool / pipx into ~/.local/bin, or a symlink), and a Typer
app behind an absolute venv path would solve nothing.

Two honest costs recorded against it: Typer and Click are further venv
dependencies, so it does not help the venv friction at all; and a single
entrypoint importing every subcommand eagerly would pay all 123 modules'
import cost on every invocation, four times per push. Lazy subcommand
registration is therefore mandatory rather than an optimisation, and must be
measured before and after.

Net: this looks like the answer for the check/gate family and the day-to-day
scripts, and it leaves the numpy/scipy/PIL planet-gen path alone — the part a
Rust port would have had to prove numerical equivalence for. T-1246 updated to
start here.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-20 02:00:39 +02:00
co-authored by Claude Opus 5
parent 6949f800dc
commit 3a640f91f7
3 changed files with 50 additions and 0 deletions
+25
View File
@@ -1218,3 +1218,28 @@ the same root cause — measuring VALUES without reading the CODE that produced
them. First correction: tectonics is derived. Second: the whole continuous/
categorical split is an artefact of the resolution tiers. Both were available in
a docstring the original pass grepped but never read.', NULL, '2026-08-19 22:52:46', '2026-08-19 22:52:46.636', '2026-08-19 22:52:46.636', NULL, '7fcf06ee54c5fbc71568e6b5e30760ff', 2) ON CONFLICT(hash) DO NOTHING;
INSERT INTO ticket_history (ticket_record_id, field, old_value, new_value, changed_by, changed_at, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06G1RWR7M1H1PC3TY2RT7AC174', 'description', 'Answer Q-124. Investigation and costing FIRST — do not open with a rewrite.
Price the cheap alternative before recommending Rust: a single Python dispatcher entrypoint (one command, many subcommands) would resolve the permission-prompt friction and most of the startup cost without touching the numerics. If that is enough, say so and close Q-124 with it.
Scope order if Rust is chosen: port the check/gate family first (check-client-version, check-canvas-version, check-systems-db-stamp, check-dataflow-graph) — small, pure, no numerics, highest prompt-friction, runs on every push. Leave planet-gen numerics (numpy/scipy/PIL) in Python behind a stable interface.
Hard constraint: import_economics is stamped by source SHA via tooling/generator_sources.py. Any port must keep the stamp/pre-push contract intact through the transition, not disabled during it.
Outcome: a recommendation with costs, then a D-record and Jeroen''s ruling. Epics only after that.', 'Answer Q-124. Investigation and costing FIRST — do not open with a rewrite.
Price the cheap alternative before recommending Rust: a single Python dispatcher entrypoint (one command, many subcommands) would resolve the permission-prompt friction and most of the startup cost without touching the numerics. If that is enough, say so and close Q-124 with it.
Scope order if Rust is chosen: port the check/gate family first (check-client-version, check-canvas-version, check-systems-db-stamp, check-dataflow-graph) — small, pure, no numerics, highest prompt-friction, runs on every push. Leave planet-gen numerics (numpy/scipy/PIL) in Python behind a stable interface.
Hard constraint: import_economics is stamped by source SHA via tooling/generator_sources.py. Any port must keep the stamp/pre-push contract intact through the transition, not disabled during it.
Outcome: a recommendation with costs, then a D-record and Jeroen''s ruling. Epics only after that.
2026-08-20 — Jeroen raised Typer (typer.tiangolo.com) as the Python-CLI option. Q-124 amended with the costing.
Start here, not with Rust. Measured state: pyproject.toml already exists and make setup-venv already does pip install -e ''.[dev]'', but there is NO [project.scripts] entry — no console entrypoint exists at all. 22 tooling files already use argparse. So this is consolidation, not authorship.
The real deliverable is ''one bare command reliably on PATH'', not ''a CLI framework''. A [project.scripts] entrypoint lands in .venv/bin/, which is on PATH only when activated — and agents and git hooks never activate it. That is the same split VENV_PY papers over in the Makefile, and the same failure recorded for tea (absolute path breaks the Bash(tea *) rule and prompts every time). Install via uv tool/pipx into ~/.local/bin, or symlink. A Typer app behind an absolute venv path solves nothing.
Watch startup cost: a single entrypoint importing every subcommand eagerly pays all 123 modules on every invocation, four times per push. Lazy subcommand registration is mandatory, not an optimisation — measure before and after.', NULL, '2026-08-20 00:00:11', '2026-08-20 00:00:11.592', '2026-08-20 00:00:11.592', NULL, '45f6dd41a5c643a730209c654bd57c76', 2) ON CONFLICT(hash) DO NOTHING;
+17
View File
@@ -1185,3 +1185,20 @@ Scope order if Rust is chosen: port the check/gate family first (check-client-ve
Hard constraint: import_economics is stamped by source SHA via tooling/generator_sources.py. Any port must keep the stamp/pre-push contract intact through the transition, not disabled during it.
Outcome: a recommendation with costs, then a D-record and Jeroen''s ruling. Epics only after that.', 'backlog', 'low', NULL, 'tooling', 'Q-124', '2026-08-19 23:54:55.776', '2026-08-19 23:54:55.776', NULL, 'cfb08832c72e06f85b075a46cd3223f2', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at >= tickets.updated_at;
INSERT INTO tickets (record_id, type, parent_record_id, title, description, status, priority, assigned_to, team, decision_ref, created_at, updated_at, deleted_at, hash, canonical_version) VALUES ('06G1RWR7M1H1PC3TY2RT7AC174', 'story', '06FBPPMZNNEV052DBYYY3A897C', 'Investigate retooling Python tooling into a Rust CLI (Q-124)', 'Answer Q-124. Investigation and costing FIRST — do not open with a rewrite.
Price the cheap alternative before recommending Rust: a single Python dispatcher entrypoint (one command, many subcommands) would resolve the permission-prompt friction and most of the startup cost without touching the numerics. If that is enough, say so and close Q-124 with it.
Scope order if Rust is chosen: port the check/gate family first (check-client-version, check-canvas-version, check-systems-db-stamp, check-dataflow-graph) — small, pure, no numerics, highest prompt-friction, runs on every push. Leave planet-gen numerics (numpy/scipy/PIL) in Python behind a stable interface.
Hard constraint: import_economics is stamped by source SHA via tooling/generator_sources.py. Any port must keep the stamp/pre-push contract intact through the transition, not disabled during it.
Outcome: a recommendation with costs, then a D-record and Jeroen''s ruling. Epics only after that.
2026-08-20 — Jeroen raised Typer (typer.tiangolo.com) as the Python-CLI option. Q-124 amended with the costing.
Start here, not with Rust. Measured state: pyproject.toml already exists and make setup-venv already does pip install -e ''.[dev]'', but there is NO [project.scripts] entry — no console entrypoint exists at all. 22 tooling files already use argparse. So this is consolidation, not authorship.
The real deliverable is ''one bare command reliably on PATH'', not ''a CLI framework''. A [project.scripts] entrypoint lands in .venv/bin/, which is on PATH only when activated — and agents and git hooks never activate it. That is the same split VENV_PY papers over in the Makefile, and the same failure recorded for tea (absolute path breaks the Bash(tea *) rule and prompts every time). Install via uv tool/pipx into ~/.local/bin, or symlink. A Typer app behind an absolute venv path solves nothing.
Watch startup cost: a single entrypoint importing every subcommand eagerly pays all 123 modules on every invocation, four times per push. Lazy subcommand registration is mandatory, not an optimisation — measure before and after.', 'backlog', 'low', NULL, 'tooling', 'Q-124', '2026-08-19 23:54:55.776', '2026-08-20 00:00:11.592', NULL, 'b48a1523bdfbddb038c083b365414e9f', 2) ON CONFLICT(record_id) DO UPDATE SET type=excluded.type, parent_record_id=excluded.parent_record_id, title=excluded.title, description=excluded.description, status=excluded.status, priority=excluded.priority, assigned_to=excluded.assigned_to, team=excluded.team, decision_ref=excluded.decision_ref, updated_at=excluded.updated_at, deleted_at=excluded.deleted_at, hash=excluded.hash, canonical_version=excluded.canonical_version WHERE excluded.updated_at >= tickets.updated_at;
+8
View File
@@ -506,6 +506,14 @@ Technical foundation questions: engine, protocols, data structures, performance,
- Python is where one-off analysis gets written. A Rust CLI must not make ad-hoc investigation expensive, or it trades a small recurring cost for a large occasional one.
- **Therefore the likely shape, if pursued:** not a big-bang rewrite. Port the **check/gate family first** (small, pure, no numerics, highest prompt-friction, most push-frequent), leave the numerics in Python behind a stable interface, and judge from there. A partial answer is a legitimate outcome of this question.
- **Also worth asking:** whether the fix is a CLI at all, or a shared `tooling/` entrypoint dispatcher in Python — one command, many subcommands — which would resolve friction (1) and most of (3) at a fraction of the cost, without touching the numerics. **This is the cheap alternative the investigation must price before recommending Rust**, or the record will have argued for the expensive option by never costing the other one.
- **Amendment 2026-08-20 — the cheap alternative has a name, and the repo is already most of the way to it.** Jeroen raised [Typer](https://typer.tiangolo.com/) (Click-based, type-hint-driven subcommands, by the FastAPI author — an idiom already in the house via tatlock). Measured against the current state:
- `pyproject.toml` **already exists** at the repo root, and `make setup-venv` already does `pip install -e ".[dev]"`. It has **no `[project.scripts]`** — no console entrypoint is defined at all. Adding one plus a Typer app is the whole delta.
- **22 tooling files already use `argparse`.** The CLI structure exists; it is fragmented across files rather than absent. This is consolidation, not authorship.
- **This resolves friction (1) completely and cheaply.** `Bash(sr *)` as one allowlist entry replaces per-script prompting, which is the single largest recurring cost.
- **But the PATH problem is the actual requirement, and it is separate from the framework.** A `[project.scripts]` entrypoint lands in `.venv/bin/`, which is on PATH only when the venv is activated — and non-interactive shells (agents, git hooks) never activate it. That is the *same* split `VENV_PY` already papers over in the Makefile (`test -x .venv/bin/python || python3`). It is also exactly the failure recorded for `tea`: an absolute path breaks the `Bash(tea *)` rule and prompts every time, and the fix was a bare name on PATH. **So the deliverable is "one bare command reliably on PATH", not "a CLI framework"** — via `uv tool install` / `pipx` into `~/.local/bin`, or a symlink. Choose the framework second; a Typer app behind an absolute venv path solves nothing.
- **It does not help friction (2), and may worsen (3).** Typer + Click are two more venv dependencies. And a single entrypoint that imports every subcommand eagerly pays all 123 modules' import cost on every invocation — including four times per push. Lazy subcommand registration is then mandatory, not an optimisation; measure it before and after.
- **Net:** this looks like the answer for the check/gate family and the day-to-day scripts. It leaves the numpy/scipy/PIL planet-gen path untouched, which is the part a Rust port would have had to prove numerical equivalence for. Price this first.
- **Raised by:** Jeroen, 2026-08-20: *"those python files are a pain. they throw permission prompts all over the place and the venv dependencies are annoying... We should ticket a Q record to retool that to rust probably. maybe make it into an actual cli of the quality level of pql. one can dream"*
- **Cross-reference:** [D-223](../decisions/architecture.md#d-223) and `.claude/rules/asset-pipeline.md` (the stamp contract a rewrite must preserve), [D-262](../decisions/architecture.md#d-262) (`check-dataflow-graph`, the most recent addition to the per-push Python set), `.claude/rules/ticket-cli.md` (`pql` as the quality bar being referenced).