diff --git a/Dockerfile b/Dockerfile index c1a21e2..62d2529 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,14 +1,24 @@ -FROM python:3.12-slim AS runtime +FROM ghcr.io/astral-sh/uv:0.11.28@sha256:0f36cb9361a3346885ca3677e3767016687b5a170c1a6b88465ec14aefec90aa AS uv + +FROM python:3.12.14-slim-bookworm@sha256:782412e85d0f0984994c290652577d4018aff08145c85b262bb63dc0c7522254 AS runtime ENV PYTHONDONTWRITEBYTECODE=1 \ - PYTHONUNBUFFERED=1 + PYTHONUNBUFFERED=1 \ + UV_COMPILE_BYTECODE=1 \ + UV_LINK_MODE=copy \ + VIRTUAL_ENV=/app/.venv \ + PATH="/app/.venv/bin:$PATH" WORKDIR /app -COPY pyproject.toml README.md ./ -COPY testql/ ./testql/ +COPY --from=uv /uv /uvx /bin/ +COPY pyproject.toml uv.lock README.md ./ +RUN uv lock --check --no-sources && \ + uv sync --frozen --no-dev --extra nlp2env \ + --no-editable --no-install-project -RUN pip install --no-cache-dir . +COPY testql/ ./testql/ +RUN uv sync --frozen --no-dev --extra nlp2env --no-editable ENV PYTHONPATH=/app diff --git a/Dockerfile.e2e b/Dockerfile.e2e index e91b837..34a564b 100644 --- a/Dockerfile.e2e +++ b/Dockerfile.e2e @@ -1,29 +1,42 @@ -FROM python:3.12-slim AS runtime +FROM ghcr.io/astral-sh/uv:0.11.28@sha256:0f36cb9361a3346885ca3677e3767016687b5a170c1a6b88465ec14aefec90aa AS uv + +FROM python:3.12.14-slim-bookworm@sha256:782412e85d0f0984994c290652577d4018aff08145c85b262bb63dc0c7522254 AS runtime ENV PYTHONDONTWRITEBYTECODE=1 \ - PYTHONUNBUFFERED=1 + PYTHONUNBUFFERED=1 \ + UV_COMPILE_BYTECODE=1 \ + UV_LINK_MODE=copy \ + VIRTUAL_ENV=/app/.venv \ + PATH="/app/.venv/bin:$PATH" WORKDIR /app -COPY pyproject.toml README.md ./ -COPY testql/ ./testql/ +COPY --from=uv /uv /uvx /bin/ +COPY pyproject.toml uv.lock README.md ./ +RUN uv lock --check --no-sources && \ + uv sync --frozen --no-dev --extra nlp2env \ + --no-editable --no-install-project -RUN pip install --no-cache-dir . +COPY testql/ ./testql/ +RUN uv sync --frozen --no-dev --extra nlp2env --no-editable ENV PYTHONPATH=/app FROM runtime AS e2e +RUN uv sync --frozen --extra dev --extra nlp2env --no-editable + COPY openapi.yaml ./ COPY packages/ ./packages/ +RUN uv pip install --python /app/.venv/bin/python \ + --no-build-isolation --no-deps \ + --editable packages/graphql2testql \ + --editable packages/proto2testql \ + --editable packages/sql2testql \ + --editable packages/desktop2testql + COPY tests/ ./tests/ COPY testql-scenarios/ ./testql-scenarios/ COPY .testql/ ./.testql/ -RUN pip install --no-cache-dir --no-deps \ - -e packages/graphql2testql \ - -e packages/proto2testql \ - -e packages/sql2testql \ - -e packages/desktop2testql - CMD ["pytest", "-q", "--no-cov"] diff --git a/TODO.md b/TODO.md index bb73832..2d4d553 100644 --- a/TODO.md +++ b/TODO.md @@ -50,7 +50,7 @@ Last updated: 2026-09-01 (1.2.67 source — Docker and binary HTTP evidence) ## 📋 Technical Debt & Code Quality -- [ ] Deliver the portable lockfile and immutable Docker build chain tracked by +- [x] Deliver the portable lockfile and immutable Docker build chain tracked by tickets `003`-`005` and TestQL issue #10 for external consumers such as Koru. - [x] Repair Docker build inputs after the package moved from `src/` to `testql/`, and make the E2E image install the extracted local plugins. diff --git a/project/TICKETS.md b/project/TICKETS.md index 5b9c2ec..c12e31b 100644 --- a/project/TICKETS.md +++ b/project/TICKETS.md @@ -10,5 +10,6 @@ This file indexes governance tickets without taking ownership of | **ticket-002** | [`README.md`](./ticket-002/README.md) | [`preprompt.md`](./ticket-002/preprompt.md) | - | [`ai-codex.md`](./ticket-002/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-002/ai-codex-logs.txt) | [`changelog.md`](./ticket-002/changelog.md) | | **ticket-003** | [`README.md`](./ticket-003/README.md) | [`preprompt.md`](./ticket-003/preprompt.md) | - | [`ai-codex.md`](./ticket-003/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-003/ai-codex-logs.txt) | [`changelog.md`](./ticket-003/changelog.md) | | **ticket-004** | [`README.md`](./ticket-004/README.md) | [`preprompt.md`](./ticket-004/preprompt.md) | - | [`ai-codex.md`](./ticket-004/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-004/ai-codex-logs.txt) | [`changelog.md`](./ticket-004/changelog.md) | +| **ticket-005** | [`README.md`](./ticket-005/README.md) | [`preprompt.md`](./ticket-005/preprompt.md) | - | [`ai-codex.md`](./ticket-005/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-005/ai-codex-logs.txt) | [`changelog.md`](./ticket-005/changelog.md) | | **ticket-006** | [`README.md`](./ticket-006/README.md) | [`preprompt.md`](./ticket-006/preprompt.md) | - | [`ai-codex.md`](./ticket-006/ai-codex.md) | [`ai-codex-logs.txt`](./ticket-006/ai-codex-logs.txt) | [`changelog.md`](./ticket-006/changelog.md) | diff --git a/project/ticket-005/README.md b/project/ticket-005/README.md new file mode 100644 index 0000000..6681271 --- /dev/null +++ b/project/ticket-005/README.md @@ -0,0 +1,66 @@ +# Ticket 005: Harden external Docker build + +- **ID**: ticket-005 +- **Owner**: unresolved:human +- **Status**: IN_PROGRESS +- **Workflow state**: PUBLICATION +- **Created**: 2026-09-01 + +## Goal and scope + +Harden both TestQL Docker build paths consumed by external repositories. Pin +the uv tool and Python runtime stages by immutable manifest digest, verify the +portable graph in no-sources mode, and install the project exclusively from the +committed lock in frozen, non-editable mode. + +The E2E image also installs its declared `dev` extra from that lock and creates +distribution metadata for the four local plugins without invoking a second +dependency resolver or isolated build environment. + +## Acceptance criteria + +- [x] AC-01: The user's autonomous sequential-completion request is recorded as + `SESSION_EXECUTION_AUTHORIZATION`. +- [x] AC-02: Every uv/Python Docker stage is selected by an immutable sha256 + digest. +- [x] AC-03: Production first checks the portable graph with `uv lock --check + --no-sources`, then installs through `uv sync --frozen --no-editable` with + the required `nlp2env` extra. +- [x] AC-04: E2E dependencies come from the same lock and local plugin sources + require no package resolver. +- [x] AC-05: Both images build; production CLI, package tests, Compose, + governance and Docker checks pass. +- [ ] AC-06: Exact-head protected publication closes TestQL issue #10 and + provides an immutable revision for Koru. + +## Authorization + +The user's request to continue and close all tasks sequentially authorizes this +bounded TestQL issue #10 implementation and protected publication. It does not +authorize secrets, self-approval, direct merge or unrelated changes. + +## Resolved prerequisite + +The no-cache runtime image builds and runs TestQL 1.2.67, but the first E2E run +reported `1650 passed, 53 skipped, 10 failed`: local plugin imports worked via +`PYTHONPATH`, while entry-point discovery correctly failed because the packages +were not installed distributions. Ticket 006 locked `setuptools` and `wheel` in +the dev graph and proved resolver-free local metadata installation, so this +ticket resumes on merged revision `2528dcfcfe95b1b786016bf9956900f6f1797843`. + +## Validation evidence + +- No-cache production image `sha256:a78e6ba5e881b196eac6a12839fc93aca299664413260127433d6f68c2cf1fd1` + reports TestQL 1.2.67, uv 0.11.28 and `nlp2env` 0.1.6. +- No-cache E2E image `sha256:a68a4fbe88dfc505771f9aebb2f2e3ae09c83e37651aa9208d31ac74a26f4744` + installed all four plugins without build isolation or dependency resolution. +- Its `testql.plugins` catalog resolves `graphql`, `proto` and `sql` to the + expected local distributions. +- Full container suite: `1701 passed, 12 skipped, 1 warning` in 17.82 seconds; + all 1713 previously collected cases completed without failure. +- Both Docker build checks and all three root Compose configurations pass. + +## Participants + +- Human participant: unresolved; no user-* file was created by this script. +- Agent participant: [ai-codex.md](ai-codex.md) diff --git a/project/ticket-005/ai-codex-logs.txt b/project/ticket-005/ai-codex-logs.txt new file mode 100644 index 0000000..fcf49d7 --- /dev/null +++ b/project/ticket-005/ai-codex-logs.txt @@ -0,0 +1,8 @@ +2026-09-01T22:29:28Z SESSION_EXECUTION_AUTHORIZATION recorded for TestQL issue #10 and semcod/koru#64. +2026-09-01T22:34:00Z EDIT base=ab9e899b916a1933c6219fc05cf39949d557141c workstream=governance dockerfiles=2. +2026-09-01T22:36:00Z BUILD rejected reason="uv 0.11.28 forbids sync --frozen with --no-sources" remediation="no-sources lock check followed by frozen sync". +2026-09-01T22:39:00Z BUILD runtime=pass cli=1.2.67 uv=0.11.28 nlp2env=0.1.6 e2e_image=pass. +2026-09-01T22:40:00Z BLOCKED e2e="1650 passed, 53 skipped, 10 failed" reason="plugin entry-point metadata absent" prerequisite="lock setuptools and wheel in dev extra". +2026-09-01T23:07:26Z RESUME prerequisite=ticket-006 main=2528dcfcfe95b1b786016bf9956900f6f1797843 status=IN_PROGRESS workflow=EDIT. +2026-09-01T23:10:41Z VALIDATION runtime_image=sha256:a78e6ba5e881b196eac6a12839fc93aca299664413260127433d6f68c2cf1fd1 cli=1.2.67 uv=0.11.28 nlp2env=0.1.6. +2026-09-01T23:10:41Z VALIDATION e2e_image=sha256:a68a4fbe88dfc505771f9aebb2f2e3ae09c83e37651aa9208d31ac74a26f4744 entrypoints=graphql,proto,sql tests="1701 passed, 12 skipped, 1 warning" workflow=PUBLICATION. diff --git a/project/ticket-005/ai-codex.md b/project/ticket-005/ai-codex.md new file mode 100644 index 0000000..fbafe28 --- /dev/null +++ b/project/ticket-005/ai-codex.md @@ -0,0 +1,40 @@ +--- +participant-id: agent:codex +participant: codex +role: agent +ticket: ticket-005 +--- +# Participant: codex (AI agent) + +## Understanding + +TestQL's externally consumed Dockerfile still used a mutable Python tag and +resolved dependencies with pip. The E2E image repeated both behaviors and then +invoked four additional editable package builds. The merged portable lock and +published plugin sources allow both paths to be resolver-free. + +## Execution plan + +1. Pin uv 0.11.28 and Python 3.12.14 stages by manifest digest. +2. Check the portable graph in no-sources mode, then replace pip resolution + with frozen/non-editable uv sync. +3. Install E2E plugin metadata without build isolation or dependency resolution. +4. Build and execute both images, then run governance and Compose checks. +5. Publish through exact-head Validator review and close issue #10. + +## Actual changes + +- Recorded the user's session execution authorization and implemented the two + bounded Docker build paths on the exact merged `ticket-004` base. +- Split portable-lock validation from frozen installation because uv 0.11.28 + correctly rejects simultaneous `sync --frozen --no-sources`: frozen performs + no source resolution, while the preceding no-sources check binds the graph. +- Resumed after ticket 006 locked the dev build backend, and installed all four + local plugin distributions with `--no-build-isolation --no-deps`. +- Built both images from scratch on immutable base digests. Production reports + TestQL 1.2.67, uv 0.11.28 and `nlp2env` 0.1.6; the E2E image exposes all + required plugin entry points and passes 1701 tests with 12 skips. + +## Blockers + +- None. Ticket 006 supplied and validated the frozen build-tool prerequisite. diff --git a/project/ticket-005/changelog.md b/project/ticket-005/changelog.md new file mode 100644 index 0000000..9bce5bc --- /dev/null +++ b/project/ticket-005/changelog.md @@ -0,0 +1,11 @@ +# Ticket Changelog (ticket-005) + +## [0.1.0] - 2026-09-01 + +- Initial governance scaffold created. +- No human participant identity or content was generated. +- Pinned uv and Python stages by immutable manifest digest. +- Replaced pip resolution with portable-lock validation and frozen uv sync. +- Installed local E2E plugin metadata without build isolation or dependency + resolution and validated the full 1713-case container suite. +- Planned digest-pinned, frozen-lock Docker builds for runtime and E2E. diff --git a/project/ticket-005/intent.json b/project/ticket-005/intent.json new file mode 100644 index 0000000..3ac9a7f --- /dev/null +++ b/project/ticket-005/intent.json @@ -0,0 +1,23 @@ +{ + "schema": "new-project.intent/v3", + "ticket": "ticket-005", + "summary": "Harden external Docker build", + "workstream": "governance", + "classification": { + "kind": "SERVICE", + "priority": "P1", + "origin": "health" + }, + "allowedPaths": [ + "project/ticket-005/**", + "TODO.md", + "project/TICKETS.md", + "Dockerfile", + "Dockerfile.e2e" + ], + "forbiddenPaths": ["project/ticket-*/user-*.md"], + "stacks": ["docker"], + "dependsOn": ["ticket-003", "ticket-004", "ticket-006"], + "conflictsWith": [], + "integrationTicket": null +} diff --git a/project/ticket-005/preprompt.md b/project/ticket-005/preprompt.md new file mode 100644 index 0000000..ca899e7 --- /dev/null +++ b/project/ticket-005/preprompt.md @@ -0,0 +1,10 @@ +# Ticket preprompt + +- **Task ID**: ticket-005 +- **Task title**: Harden external Docker build +- **Created**: 2026-09-01T22:29:28Z + +Keep executable implementation outside this governance/evidence directory. +Read a human-owned user-*.md file only when one exists. +Use only digest-pinned tool/runtime stages and the committed `uv.lock`; do not +change dependencies, executable source or integration-owned paths.