Wave 46 / 47.A — jpcite env-bridge full callsite migration (89)¶
Status: READY
Branch: feat/jpcite_2026_05_12_wave46_rename_47a_aliaschoices_full
Worktree: /tmp/jpcite-w46-rename-47a (lane mutex /tmp/jpcite-w46-rename-47a.lane)
Companion to: Wave 46.E (PR #132) — Settings AliasChoices conversion (31 fields)
Predecessor reference: PR #120 (introduces the _jpcite_env_bridge.get_flag helper concept; this PR lands the helper in tree alongside the 89 callsite migration).
1. Goal¶
Wave 46.E only converted Settings-bound env reads. 89 additional ad-hoc
callsites in MCP tools / REST / cron / ETL / ops / self_improve loops still
called os.environ.get("AUTONOMATH_X", ...) or os.getenv("JPINTEL_X", ...)
directly, which means they would have continued to read the legacy name even
when the canonical JPCITE_X was set in prod / CI / Fly secrets.
This PR completes the bridge by routing every selected callsite through
jpintel_mcp._jpcite_env_bridge.get_flag(primary, legacy, default):
JPCITE_*(new canonical) checked first- legacy name (
AUTONOMATH_*/JPINTEL_*) checked second - hard-coded default last
- destruction-free: nothing is deleted; legacy env continues to work
(per
feedback_destruction_free_organization)
2. Callsite breakdown — 6 categories, 89 calls¶
| Category | Files | Calls |
|---|---|---|
src/jpintel_mcp/mcp/ (tools + http + auth + cohort_resources) |
46 | 64 |
scripts/cron/ |
14 | 15 |
scripts/etl/ |
6 | 7 |
scripts/ops/ |
1 | 3 |
| (split out) | ||
src/jpintel_mcp/self_improve/ (subset of src/) |
2 | 2 |
src/jpintel_mcp/mcp/_http_fallback.py (api lane) |
1 | 3 |
Total: 89 callsites across 66 unique files.
Snapshot list: /tmp/w47a_callsites_all.txt (input), /tmp/w47a_apply.py
(EDITS = [...] literal — 87 single-line + 2 multi-line) + the 2 manual
edits in eligibility_tools.py.
Wire pattern examples:
# Before
_ENABLED = os.environ.get("AUTONOMATH_ELIGIBILITY_CHECK_ENABLED", "1") == "1"
# After
from jpintel_mcp._jpcite_env_bridge import get_flag
_ENABLED = get_flag(
"JPCITE_ELIGIBILITY_CHECK_ENABLED",
"AUTONOMATH_ELIGIBILITY_CHECK_ENABLED",
"1",
) == "1"
3. New helper module — src/jpintel_mcp/_jpcite_env_bridge.py¶
Pure stdlib (no LLM/SDK imports per
feedback_autonomath_no_api_use / feedback_no_operator_llm_api).
Exposes:
get_flag(primary, legacy, default=None) -> str | Noneget_bool_flag(primary, legacy, default: bool) -> boolget_int_flag(primary, legacy, default: int) -> int
Empty-string env values are treated as "unset" so a stray export JPCITE_X=
in CI does not pin the canonical key when the legacy key carries the real
value.
4. Test — tests/test_w47a_env_bridge_callsites.py (~175 LOC)¶
Parametrised on 20 representative (primary, legacy, default) triplets drawn from the 89 migrated callsites (6 src/tools _ENABLED + 4 DB-path src/tools + 3 src/api + 4 scripts/cron + 1 scripts/etl + 1 scripts/ops + 1 self_improve).
Test matrix: 20 callsites × 3 modes (default / new-primary / legacy-fallback) = 60 cases + 5 anchor tests (precedence-primary-wins / empty-string-as-unset / get_bool_flag truthy-falsy / get_int_flag valid-invalid / public_api_surface_stable) = 65 cases total.
Result (python3.13 + pytest 9.0.3, run with
PYTHONPATH=src --noconftest because the repo conftest.py needs fastapi
which is heavy to install for a unit-test of a stdlib-only helper):
5. Verification — verdicts¶
| Check | Result |
|---|---|
py_compile on all 67 modified files (66 callsite files + helper) |
OK 67/67 |
pytest tests/test_w47a_env_bridge_callsites.py |
65 passed |
| ruff on helper + new test | OK |
ruff on all 66 touched callsite files (post --fix) |
13 errors remain — all pre-existing (E701/E702 multi-statement, SIM105 try/except/pass) on lines this PR did not touch |
| New-injected import block sort (I001) | 0 (ruff auto-fixed) |
# noqa: E402 added for 5 files where helper import lands after sys.path.insert(...) (CI runner shim) |
OK |
grep direct os.environ.get(.AUTONOMATH_/.JPINTEL_) in 89 targets |
0 callsites still raw (verified by re-scanning original target list) |
grep direct os.environ.get(.AUTONOMATH_/.JPINTEL_) in src+scripts whole |
181 remain (started 269; Wave 47.B/.C/... will continue) |
6. Anti-rule checks (memory: feedback_destruction_free_organization, feedback_autonomath_no_api_use, feedback_no_operator_llm_api)¶
- ❌ NO env var deleted
- ❌ NO behavioural change (defaults preserved verbatim, every triplet honours legacy fallback)
- ❌ NO
anthropic/ SDK import added in src or scripts (stdlib only) - ❌ NO main worktree mutation (used
/tmp/jpcite-w46-rename-47aworktree, atomic mkdir lane mutex/tmp/jpcite-w46-rename-47a.lane) - ❌ NO legacy brand surface change (banner/docs untouched per
feedback_legacy_brand_marker)
7. Files changed (PR diff)¶
| File | Lines |
|---|---|
NEW src/jpintel_mcp/_jpcite_env_bridge.py |
+118 |
NEW tests/test_w47a_env_bridge_callsites.py |
+175 |
NEW docs/research/wave46/STATE_w47a_pr.md (this file) |
+~140 |
66 callsite files (89 line edits + 66 helper import injections, 5 with # noqa: E402, 2 import os removed where it became unused) |
~190 +/- |
8. Followups (NOT in this PR)¶
- 47.B: convert remaining ~180 callsites (e.g.
intel_wave32.py,corporate_form_tools.py,invoice_risk_tools.py, additional cron jobs) — same mechanical pattern, will reuse/tmp/w47a_apply.pyas a template. - 47.C: scripts/cron/aggregate_program_agriculture_weekly.py multi-statement lines (
; sh = …; sh.set… ; root.addHandler(sh)) deserve a one-off ruff cleanup outside this scope. - post-47:
mypy --strictaudit on the helper (currently passespy_compile; mypy not run because the repo's mypy config requires the full venv with all deps).
9. Pytest-editable-install gotcha (re-noted from 46.E)¶
Running pytest needs PYTHONPATH=src because the repo is not installed
editable in the local shell — there is no .venv at the repo root. Once
landed, CI's existing setup-env step handles this automatically. Local
contributors should either run pip install -e . once, or prefix
PYTHONPATH=src as we did above.