Skip to content

chore: single-source Python version + adapter-convert CI smoke#172

Merged
CiroGamboa merged 3 commits into
mainfrom
chore/consolidate-devops-hygiene
Jul 5, 2026
Merged

chore: single-source Python version + adapter-convert CI smoke#172
CiroGamboa merged 3 commits into
mainfrom
chore/consolidate-devops-hygiene

Conversation

@CiroGamboa

Copy link
Copy Markdown
Collaborator

TL;DR

Bundles two independent CI/packaging hygiene items into a single PR so one review cycle unblocks both. Each was previously carried as its own PR (both green, both had passed a full Claude-review round, both awaiting human review); consolidating trims the review overhead without merging together things that aren't cohesive.

What lands in this PR

1. Single-source the Python runtime version from .python-version

Six engine Dockerfiles, tolokaforge/docker/builder.py, and five GitHub Actions workflows previously hardcoded python:3.12-slim / python-version: "3.12" in eight separate places. This meant a Python-version bump was an eight-file coordinated edit with high drift risk.

After this change, .python-version is the single source. builder.py reads it and threads it into every Dockerfile as the PYTHON_VERSION build arg; workflows read the same file via a small setup-python step. A future 3.12 → 3.13 bump is a one-line change.

Files: 5 workflow YAMLs, 6 Dockerfiles, tolokaforge/docker/builder.py, tests/unit/test_docker_build_context.py. Total: +67/-28 LOC.

2. CI smoke test for tolokaforge adapter convert packaging

Adds three canonical tests exercising the adapter convert CLI end-to-end:

  • test_bundle_writer_ships_in_the_built_wheel — inspects the built wheel to confirm bundle_writer.py is packaged.
  • test_adapter_convert_cli_help_runs_via_subprocess — real subprocess invocation of the installed CLI.
  • test_bundle_writer_module_imports_cleanly — fast import-guard smoke.

Prevents the wheel-packaging regression class where adapter convert breaks silently because a module fails to make it into the built distribution.

Files: tests/canonical/test_adapter_convert_packaging.py (new, 123 LOC).

Why these two together

Both items are:

  • Small (net +67 and +123 LOC).
  • CI / packaging hygiene, not core engine changes.
  • Independently green on CI.
  • Touching disjoint files.
  • Reviewed clean in their prior standalone PRs.

Bundling them saves a review cycle. The other two PRs from the same batch (an orchestrator DI refactor and a typing-infra stage-1 payload change) stay standalone because they touch different layers and each warrants its own review lens.

Test plan

  • uv run ruff check clean on all modified files.
  • uv run pytest tests/canonical/test_adapter_convert_packaging.py tests/unit/test_docker_build_context.py -q — 13 pass.
  • Full unit + canonical suite — 2443 pass on the integration branch that layers this PR on top of the sibling PRs (refactor(orchestrator): collapse injection kwargs into OrchestratorDeps #134, refactor(output): typed models for run-level aggregate payloads (stage 1) #149) plus current main.
  • Empirical regression check — five public examples (native_shared_domain, coding, multi_service, multi_service_advanced, multi_service_postgres) plus two representative recent internal task packs — all produce byte-identical avg_score_micro on main vs. the branch that has this + the sibling PRs applied.
  • Automated Claude review on this PR.

Supersedes

Closes #136 and #137 — content unchanged, single review path.

…version

Read the pinned Python minor version once (in builder.py from
.python-version) and thread it through every runtime Dockerfile as a
PYTHON_VERSION build arg. CI workflows also switch from a hardcoded
'uv python install 3.12' to 'uv python install "$(cat .python-version)"'.
Bumping the runtime version now needs one edit — .python-version — and
propagates to dev, CI, and all four live Docker images (runner,
db-service, mock-web, rag-service). Orphan Dockerfiles get the same
ARG for consistency in case they're ever revived.

Fixes the drift where runner + db_service ran on 3.11 and mock_web +
rag on 3.10 while dev / CI / devcontainer were on 3.12 — undercutting
the "byte-identical engine" contract.
…ation

Add three canonical tests that catch failure modes the existing
in-process adapter-convert suite can't see:

* Wheel packaging inspection — build the wheel via ``uv build``,
  unzip, assert every module the CLI reaches at call time
  (bundle_writer, native, base, adapter_commands, cli.main) is
  physically present. This is exactly the shape of the GH #37
  regression: source file existed on disk, tests passed, PyPI
  install crashed with ImportError.
* Subprocess invocation of ``uv run tolokaforge adapter convert
  --help`` — proves the console script wires and the CLI imports
  cleanly under the real Python resolver (no monkeypatch shortcuts).
* Import-time symbol check — cheap in-process guard on
  ``bundle_writer.write_bundle``.

Runs under the ``canonical`` marker, so the existing CI test-smoke
job picks it up automatically — no workflow-file wiring needed.

Closes #37
@github-actions

github-actions Bot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Claude finished @CiroGamboa's task —— View job


PR Review: single-source Python version + adapter-convert CI smoke

Reviewed all 14 changed files against AGENTS.md. No rule violations found. The PR is clean, well-scoped, and the design is sound. Details below, plus two non-blocking observations worth a glance.

Todo:

  • Read AGENTS.md rules
  • Review builder.py changes (Python version single-sourcing)
  • Review new canonical test file
  • Review Dockerfile changes (all 6)
  • Review workflow YAML changes (all 5)
  • Review test_docker_build_context.py changes
  • Check SECRETS hard rule
  • Check testing rules (pytestmark, xfail/skip)
  • Compile findings

Rule-by-rule verdict

# Rule Verdict
1 Code quality — explicit errors, <100 lines, nesting <3 _pinned_python_version() fails loud (no fallback); Dockerfiles have a documented default not a silent fallback
2 Secrets — single abstraction (HARD) ✅ No new os.environ.get/getenv, no load_dotenv, no .env reads, no secret baked into any image/build-arg. PYTHON_VERSION/WHEEL_FILENAME build-args are non-credential
3 Testing — pytestmark, no xfail/skip ✅ New file has pytestmark = pytest.mark.canonical; skips use conditional @pytest.mark.skipif(not _uv_available()) — no bare skip/xfail
4 Architecture — harness generic ✅ Pure CI/packaging plumbing; no task-specific logic
5 Repo hygiene ✅ No root scripts/data/logs added
6 DRY / self-describing .python-version becomes the single source; eliminates 8-file drift
7 Root cleanliness allow-list ✅ No new root files
8 No temp artifacts ✅ None
9–11 Script location / organization / tools ✅ N/A — no new scripts/tools
12 No project-specific content on main ✅ None

Strengths

  • builder.py:42-56_pinned_python_version() reads .python-version with no try/except fallback: a missing/unreadable file fails loud, exactly per Core Rule 1. Threading it via _PYTHON_BUILD_ARGS and merging with ** in the dynamic factories (_runner_definition:129, _rag_definition:147) is clean and DRY.
  • Dockerfile default (ARG PYTHON_VERSION=3.12) — this is not a silent fallback: it keeps a bare docker build reproducible while the harness always passes the arg. The docstring at builder.py:48-50 documents the intent. Good.
  • Test coverage matches the changetest_docker_build_context.py:198-203, 237-242, 245-269 assert PYTHON_VERSION reaches build_args for both force/non-force paths and the no-context db-service path, importing the constant rather than hardcoding "3.12", so a future pin bump won't break the test. Exactly right.
  • New canonical test — the wheel-inspection test (test_adapter_convert_packaging.py:39) catches the exact regression class (module dropped from the built wheel) that in-process tests structurally cannot. The three-layer approach (wheel inspection → real subprocess CLI → fast import guard) is well-reasoned, and the skipif(not _uv_available()) guard degrades loudly rather than silently passing.

Non-blocking observations (not violations)

  1. json_db.Dockerfile and orchestrator.Dockerfile gained ARG PYTHON_VERSION but are not in IMAGE_DEFINITIONS (builder.py:76-105 only defines db-service, runner, rag-service, mock-web). So the builder never threads PYTHON_VERSION into these two — they only ever get the Dockerfile default. That's harmless and keeps them consistent for manual docker build, but the "single source" guarantee doesn't actually reach them. If either is dead/legacy (json_db.Dockerfile looks like an older sibling of db_service.Dockerfile), AGENTS.md rule 8 ("update or remove legacy mentions immediately") would favor deleting it rather than carrying a half-wired arg. Worth a one-line confirmation that both are still live.

  2. release-gate.yml:22 still pins astral-sh/setup-uv@v4 while every other workflow this PR touched uses @v7. Pre-existing and out of scope for this PR, but since you were already normalizing these five workflows, it's the natural place to align it. Not a blocker.

  3. release-gate.yml:67 references docker compose build json-db mock-web rag-service executor — service names (json-db, executor) that don't match the IMAGE_DEFINITIONS keys. Pre-existing, unrelated to this PR; flagging only for awareness.


Verdict

Everything checks out. No AGENTS.md violations. The secrets hard-rule is respected (build-args carry only the Python version + wheel filename, never a credential). Testing markers are correct and there are zero xfail/bare-skip usages. The two observations above are optional cleanups, not merge blockers.

Job run · chore/consolidate-devops-hygiene

… cleanly

The previous PR-#172 shape read `.python-version` from the repo root
via `_pinned_python_version()` at module import of
`tolokaforge.docker.builder`. That works from a source checkout but
crashes with `FileNotFoundError` under a wheel install — the wheel
lands in `site-packages/tolokaforge/`, and the repo-root dotfile is
not part of the package payload. Every consumer that transitively
imports the docker submodule (including `tolokaforge docker build`
and any code that touches `builder.PYTHON_VERSION`) breaks.

Fix (idiomatic hatchling + importlib.resources):

- `pyproject.toml` gains a `[tool.hatch.build.targets.wheel.force-include]`
  block that copies `.python-version` from the repo root into the
  wheel as `tolokaforge/_python_version.txt` at build time. The
  repo-root dotfile stays the single source of truth (dev, CI, docker
  build all read it); the packaged copy is a build artifact.
- `_pinned_python_version()` now tries the packaged file first via
  `importlib.resources.files("tolokaforge").joinpath("_python_version.txt")`
  and falls back to `repo_root() / ".python-version"` when the
  packaged file is not present (editable installs, direct source
  execution).

Both branches read the same value on a matching install; the
two-branch shape only exists to cover the wheel-install case.

Tests:

- `test_python_version_pin_ships_in_the_built_wheel` — builds the
  wheel via `uv build`, inspects it via `zipfile`, asserts
  `tolokaforge/_python_version.txt` is present and its content
  matches the repo-root pin.
- `test_pinned_python_version_reads_cleanly_in_process` — in-process
  guard that the resolver returns a non-empty value matching the
  repo-root pin.

Verified:

- Fresh wheel install into a Python 3.12 venv:
  `from tolokaforge.docker.builder import PYTHON_VERSION` returns 3.12.
  `tolokaforge docker build --help`, `docker status`, and unrelated
  imports all clean (previously the module-load crash cascaded).
- Source install (uv sync from a fresh clone): fallback branch
  handles it; same value.
- Full unit + canonical suite: 2433 pass.
@github-actions

github-actions Bot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Claude finished @CiroGamboa's task —— View job


PR Review: single-source Python version + adapter-convert CI smoke

Re-reviewed all 15 changed files against AGENTS.md (this synchronize added pyproject.toml + two new tests since the prior review). No rule violations found. The PR is clean and well-scoped.

Todo:

  • Read AGENTS.md rules
  • Review builder.py (Python version single-sourcing)
  • Review new canonical test file
  • Review Dockerfile changes (all 6)
  • Review workflow YAML changes (all 5)
  • Review pyproject.toml + test_docker_build_context.py
  • Check SECRETS hard rule
  • Check testing rules (pytestmark, xfail/skip)
  • Compile findings

Rule-by-rule verdict

# Rule Verdict
1 Code quality — explicit errors, <100 lines, nesting <3 _pinned_python_version() fails loud; the try/except at builder.py:68-77 narrowly catches package-discovery errors then falls through to a hard read that raises on a missing file — not a silent fallback
2 Secrets — single abstraction (HARD) ✅ No new os.environ.get/getenv, no load_dotenv, no .env reads. PYTHON_VERSION/WHEEL_FILENAME build-args are non-credential
3 Testing — pytestmark, no xfail/bare-skip ✅ New file has pytestmark = pytest.mark.canonical; skips are conditional @pytest.mark.skipif(not _uv_available())
4 Architecture — harness generic ✅ Pure CI/packaging plumbing
5 Repo hygiene ✅ No root scripts/data/logs
6 DRY / self-describing .python-version is now single source
7 Root cleanliness allow-list ✅ No new root files
8 No temp artifacts ✅ None
9–11 Script location / organization / tools ✅ N/A
12 No project-specific content on main ✅ None

Strengths

  • builder.py:43-78 — the wheel-install path is handled cleanly: read the packaged _python_version.txt first, fall through to the repo-root .python-version. The except is scoped to (ModuleNotFoundError, FileNotFoundError, OSError) around package discovery only; the final read_text() has no guard, so a genuinely missing pin still raises loud. Threading via _PYTHON_BUILD_ARGS and **-merging in the factories (builder.py:155,173) is DRY.
  • pyproject.toml:133-134[tool.hatch.build.targets.wheel.force-include] copying .python-version → tolokaforge/_python_version.txt closes the loop: the new _pinned_python_version() read path Add GitHub CI workflows and consolidate formatter on ruff #1 would otherwise FileNotFoundError on every wheel install. Good that this landed together with the reader.
  • New wheel-pin test (test_adapter_convert_packaging.py:127-172)test_python_version_pin_ships_in_the_built_wheel inspects the built wheel and asserts the packaged value equals the repo-root pin, so a stale force-include is caught, not just an absent file. Paired with the in-process test_pinned_python_version_reads_cleanly_in_process (:175-190), both the packaging and the read path are pinned. Directly guards the regression the force-include introduces.
  • test_docker_build_context.py:198-203, 237-242, 267-269 — imports PYTHON_VERSION rather than hardcoding "3.12", so a future pin bump won't break the tests. Correct.

Non-blocking observations (not violations)

  1. json_db.Dockerfile and orchestrator.Dockerfile gained ARG PYTHON_VERSION but are unwired. Neither appears in IMAGE_DEFINITIONS (builder.py:102-131), any compose file, or any workflow — grep finds them referenced only in docs/FUTURE_DEVELOPMENT.md and docs/DB_SERVICE_API.md. So the "single source" guarantee never actually reaches them; they only ever get the Dockerfile default. db_service.Dockerfile is the live sibling that IMAGE_DEFINITIONS uses. Per AGENTS.md Core Rule 8 ("Do not keep legacy mentions — update or remove them immediately"), these two look like deletion candidates rather than half-wired arg carriers. Worth a one-line confirmation they're still intended to exist.

  2. release-gate.yml:22 still pins astral-sh/setup-uv@v4 with version: "latest" while the five ci.yml jobs use @v7 + a pinned 0.9.5. Pre-existing, but since this PR normalized the Python-version step across these workflows, it's the natural spot to align the uv action too. Not a blocker.

  3. release-gate.yml:67docker compose build json-db mock-web rag-service executor references service names (json-db, executor) that don't match the IMAGE_DEFINITIONS keys (db-service, runner, ...). Pre-existing and unrelated to this PR; flagging only for awareness.


Verdict

Everything checks out. No AGENTS.md violations. The secrets hard-rule is respected (build-args carry only the Python version + wheel filename, never a credential). Testing markers are correct with zero xfail/bare-skip. The force-include ↔ reader ↔ wheel-inspection-test triangle is internally consistent. The three observations above are optional cleanups, not merge blockers — observation #1 (the two unwired Dockerfiles) is the only one that touches an actual AGENTS.md rule and is worth a quick confirm/delete.

Job run · chore/consolidate-devops-hygiene

@CiroGamboa CiroGamboa merged commit 1146b67 into main Jul 5, 2026
8 checks passed
@CiroGamboa CiroGamboa deleted the chore/consolidate-devops-hygiene branch July 5, 2026 00:26
CiroGamboa added a commit that referenced this pull request Jul 5, 2026
…kflow (#173)

* chore(docker): delete unused Dockerfiles + normalize release-gate workflow

Two of the six Dockerfiles under `tolokaforge/docker/dockerfiles/`
were never wired into `IMAGE_DEFINITIONS` in `builder.py` — they
existed only because the initial open-source release brought them
along as drafts. The most recent Claude review of PR #172 flagged
them for exactly this reason. `docs/FUTURE_DEVELOPMENT.md` had
already noted both as audit candidates. Removing them plus the
resolved audit questions and stale doc references.

**Deleted:**

- `tolokaforge/docker/dockerfiles/json_db.Dockerfile` — bare draft
  of `db_service.Dockerfile`. Both copy the same source tree
  (`tolokaforge/env/json_db_service/`) but only `db_service` is
  production-hardened and wired into `IMAGE_DEFINITIONS`. The
  `json-db` name that appears in older docs is a *network alias* on
  the `db-service` container
  (`tolokaforge/docker/stacks/core.py`), not a separate service.
- `tolokaforge/docker/dockerfiles/orchestrator.Dockerfile` —
  placeholder for a hosted-orchestrator feature that never
  materialized. `tolokaforge` ships as a CLI; the orchestrator runs
  on the host, not inside a container.

**Documentation swept:**

- `docs/DB_SERVICE_API.md` — the "Dockerfile Changes" note now
  points at `db_service.Dockerfile` (the live sibling).
- `docs/FUTURE_DEVELOPMENT.md` — the four audit-question lines
  covering these two Dockerfiles are removed (question answered by
  deletion).
- `docs/GETTING_STARTED.md`, `docs/TROUBLESHOOTING.md` — the
  `docker compose up -d json-db mock-web rag-service` command can
  never have worked (there's no `docker-compose.yml` in the repo —
  the engine generates compose manifests dynamically via
  `ServiceStack` classes). Replaced with
  `uv run tolokaforge docker build` + a note that services
  auto-start on `tolokaforge run`.

**`.github/workflows/release-gate.yml` normalization:**

- Bump `astral-sh/setup-uv@v4` (with `version: "latest"`) to `@v7`
  with `version: "0.9.5"` — matching `ci.yml`. Copied the
  pin-rationale comment verbatim so future readers see why the
  version pin matters in-place. Prevents lock-format drift on uv
  release day.
- Replaced the never-worked
  `docker compose build json-db mock-web rag-service executor` line
  with `uv run tolokaforge docker build`. `json-db` was a network
  alias, `executor` doesn't exist anywhere in the codebase, and
  there's no compose file to back either. The engine's own docker
  entry point builds every service in `IMAGE_DEFINITIONS` — the
  same set the removed command was targeting (minus the
  non-existent ones).

**Verification:**

- `make lint` clean.
- `uv run pytest tests/unit/test_docker_build_context.py -q` — 10
  pass (the test that pins which Dockerfiles ship in
  `IMAGE_DEFINITIONS`; the deletions are invisible to it because it
  only asserts on the ones that ARE wired).
- `uv run pytest tests/ -m "unit or canonical" -q` — 2445 pass.
- `make docker-build-core` clean — db-service + runner both build.
- Post-flight grep confirms zero lingering references to the deleted
  files or the broken compose invocation.

* review: fold in adjacent workflow + doc cleanup flagged by review

GitHub Claude review flagged two additional in-scope items:

- `release-gate.yml` still ran `pytest tests/functional/` and
  `pytest tests/e2e/` — both directories were deleted per
  `FUTURE_DEVELOPMENT.md` Stage 10 (tests consolidated to
  `unit / canonical / integration`). The workflow was still failing
  downstream of the Python-version and compose-command fixes because
  of these two missing paths. Collapsed the two steps into a single
  `pytest tests/ -m "unit or canonical"` invocation matching how the
  repo actually runs tests today.
- `docs/TROUBLESHOOTING.md:31` still referenced an `executor
  container` — the same non-existent name the release-gate compose
  command was excised for. Replaced with a pointer to the `runner`
  container + `tolokaforge docker status` for state inspection.

Both edits are in the same class of cleanup as the rest of the PR
(remove references to deleted / never-existed artifacts). Folding in
here so the workflow can genuinely run end-to-end after this lands
rather than trading one broken step for another.

* docs(future-dev): tick verification-gate checkboxes this PR resolves

Follow-up on the review nit — three checkboxes under
FUTURE_DEVELOPMENT.md §9.5 "Verification gate" are now satisfied:

- No orphaned Dockerfiles — the two orphans (json_db, orchestrator)
  are deleted in this PR.
- builder.py IMAGE_DEFINITIONS matches actual files — every remaining
  Dockerfile is referenced by exactly one IMAGE_DEFINITIONS entry.
- tolokaforge docker build --core succeeds — validated in this PR's
  verification (make docker-build-core builds runner + db-service
  cleanly).

The other three items in the gate (minimal-runner image, content-hash
scope, frozen_mcp_core parity) are separate scopes and stay unchecked.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant