continuo-python-runtime 0.4.0__tar.gz → 0.4.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (141) hide show
  1. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/.github/workflows/ci.yml +5 -5
  2. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/.github/workflows/dco.yml +1 -1
  3. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/.github/workflows/images.yml +53 -20
  4. continuo_python_runtime-0.4.1/.github/workflows/publish-pypi.yml +99 -0
  5. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/.github/workflows/release.yml +1 -1
  6. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/.github/workflows/security.yml +2 -2
  7. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/CONTRIBUTING.md +2 -2
  8. continuo_python_runtime-0.4.1/Dockerfile.postgres +56 -0
  9. continuo_python_runtime-0.4.1/Dockerfile.trino +56 -0
  10. continuo_python_runtime-0.4.0/README.md → continuo_python_runtime-0.4.1/PKG-INFO +78 -25
  11. continuo_python_runtime-0.4.0/PKG-INFO → continuo_python_runtime-0.4.1/README.md +58 -45
  12. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/adapters/postgres/README.md +1 -1
  13. {continuo_python_runtime-0.4.0/adapters/postgres/continuo_python_runtime_postgres → continuo_python_runtime-0.4.1/adapters/postgres/continuo_postgres_adapter}/adapter.py +1 -1
  14. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/adapters/postgres/pyproject.toml +13 -9
  15. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/adapters/postgres/tests/test_adapter_postgres_validation.py +1 -1
  16. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/adapters/postgres/tests/test_adapter_runtime_postgres.py +2 -2
  17. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/adapters/postgres/tests/test_integration_postgres_validation.py +1 -1
  18. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/adapters/postgres/tests/test_integration_runtime_postgres.py +1 -1
  19. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/adapters/trino/README.md +2 -2
  20. {continuo_python_runtime-0.4.0/adapters/trino/continuo_python_runtime_trino → continuo_python_runtime-0.4.1/adapters/trino/continuo_trino_adapter}/adapter.py +1 -1
  21. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/adapters/trino/pyproject.toml +12 -8
  22. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/adapters/trino/tests/test_adapter_runtime_trino.py +2 -2
  23. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/adapters/trino/tests/test_adapter_trino_validation.py +1 -1
  24. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/adapters/trino/tests/test_integration_runtime_trino.py +2 -2
  25. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/adapters/trino/tests/test_integration_trino_validation.py +1 -1
  26. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/contract/pyproject.toml +2 -2
  27. continuo_python_runtime-0.4.1/image-requirements-postgres.txt +2 -0
  28. continuo_python_runtime-0.4.1/image-requirements-trino.txt +2 -0
  29. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/pyproject.toml +17 -8
  30. continuo_python_runtime-0.4.1/scripts/check_version_bumps.py +65 -0
  31. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/template/Dockerfile +1 -1
  32. continuo_python_runtime-0.4.1/template/Dockerfile.pip +23 -0
  33. continuo_python_runtime-0.4.1/template/README.md +82 -0
  34. continuo_python_runtime-0.4.1/template/requirements.lock +22 -0
  35. continuo_python_runtime-0.4.1/tests/test_adapter_naming.py +41 -0
  36. continuo_python_runtime-0.4.1/tests/test_image_requirements_sync.py +36 -0
  37. continuo_python_runtime-0.4.1/uv.lock +827 -0
  38. continuo_python_runtime-0.4.0/.github/workflows/publish-pypi.yml +0 -74
  39. continuo_python_runtime-0.4.0/Dockerfile.postgres +0 -22
  40. continuo_python_runtime-0.4.0/Dockerfile.trino +0 -22
  41. continuo_python_runtime-0.4.0/template/README.md +0 -36
  42. continuo_python_runtime-0.4.0/uv.lock +0 -666
  43. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/.dockerignore +0 -0
  44. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/.github/CODEOWNERS +0 -0
  45. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  46. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  47. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  48. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  49. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/.github/dependabot.yml +0 -0
  50. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/.gitignore +0 -0
  51. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/.gitleaks.toml +0 -0
  52. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/CHANGELOG.md +0 -0
  53. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/CODE_OF_CONDUCT.md +0 -0
  54. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/DCO +0 -0
  55. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/LICENSE +0 -0
  56. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/NOTICE +0 -0
  57. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/SECURITY.md +0 -0
  58. {continuo_python_runtime-0.4.0/adapters/postgres/continuo_python_runtime_postgres → continuo_python_runtime-0.4.1/adapters/postgres/continuo_postgres_adapter}/__init__.py +0 -0
  59. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/adapters/postgres/tests/__init__.py +0 -0
  60. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/adapters/postgres/tests/conftest.py +0 -0
  61. {continuo_python_runtime-0.4.0/adapters/trino/continuo_python_runtime_trino → continuo_python_runtime-0.4.1/adapters/trino/continuo_trino_adapter}/__init__.py +0 -0
  62. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/adapters/trino/tests/__init__.py +0 -0
  63. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/continuo_python_runtime/__init__.py +0 -0
  64. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/continuo_python_runtime/cli.py +0 -0
  65. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/continuo_python_runtime/closure.py +0 -0
  66. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/continuo_python_runtime/conform.py +0 -0
  67. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/continuo_python_runtime/context.py +0 -0
  68. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/continuo_python_runtime/contract/__init__.py +0 -0
  69. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/continuo_python_runtime/contract/loader.py +0 -0
  70. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/continuo_python_runtime/contract/merge.py +0 -0
  71. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/continuo_python_runtime/contract/model.py +0 -0
  72. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/continuo_python_runtime/contract/paths.py +0 -0
  73. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/continuo_python_runtime/csv_loader.py +0 -0
  74. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/continuo_python_runtime/csv_readers/__init__.py +0 -0
  75. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/continuo_python_runtime/csv_readers/https.py +0 -0
  76. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/continuo_python_runtime/csv_readers/s3.py +0 -0
  77. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/continuo_python_runtime/csv_source.py +0 -0
  78. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/continuo_python_runtime/errors.py +0 -0
  79. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/continuo_python_runtime/harness.py +0 -0
  80. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/continuo_python_runtime/hashing.py +0 -0
  81. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/continuo_python_runtime/lint.py +0 -0
  82. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/continuo_python_runtime/types.py +0 -0
  83. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/continuo_python_runtime/validation/__init__.py +0 -0
  84. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/continuo_python_runtime/validation/runner.py +0 -0
  85. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/continuo_python_runtime/validation/s3.py +0 -0
  86. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/contract/LICENSE +0 -0
  87. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/contract/NOTICE +0 -0
  88. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/contract/README.md +0 -0
  89. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/contract/continuo_engine_contract/__init__.py +0 -0
  90. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/contract/continuo_engine_contract/config.py +0 -0
  91. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/contract/continuo_engine_contract/port.py +0 -0
  92. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/contract/continuo_engine_contract/result.py +0 -0
  93. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/contract/continuo_engine_contract/sql.py +0 -0
  94. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/contract/continuo_engine_contract/types.py +0 -0
  95. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/contract/tests/__init__.py +0 -0
  96. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/contract/tests/test_config.py +0 -0
  97. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/contract/tests/test_contract_types.py +0 -0
  98. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/contract/tests/test_port.py +0 -0
  99. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/contract/tests/test_result.py +0 -0
  100. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/contract/tests/test_sql.py +0 -0
  101. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/docs/boundary-contract.md +0 -0
  102. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/docs/superpowers/plans/2026-07-31-python-runtime.md +0 -0
  103. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/docs/superpowers/plans/2026-08-07-step-3b-config-three-part-hash.md +0 -0
  104. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/docs/superpowers/specs/2026-07-31-python-runtime-design.md +0 -0
  105. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/scripts/security-scan.sh +0 -0
  106. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/template/.github/workflows/release.yml +0 -0
  107. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/template/contracts/example.yml +0 -0
  108. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/template/contracts/example_csv.yml +0 -0
  109. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/template/scripts/example.py +0 -0
  110. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/tests/__init__.py +0 -0
  111. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/tests/conftest.py +0 -0
  112. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/tests/contract/__init__.py +0 -0
  113. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/tests/contract/test_loader.py +0 -0
  114. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/tests/contract/test_merge.py +0 -0
  115. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/tests/contract/test_model.py +0 -0
  116. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/tests/smoke/node_smoke/contracts/smoke.yml +0 -0
  117. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/tests/smoke/node_smoke/scripts/smoke.py +0 -0
  118. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/tests/smoke/postgres-stack/docker-compose.yml +0 -0
  119. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/tests/smoke/trino-stack/catalog/iceberg.properties +0 -0
  120. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/tests/smoke/trino-stack/docker-compose.yml +0 -0
  121. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/tests/test_cli.py +0 -0
  122. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/tests/test_closure.py +0 -0
  123. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/tests/test_conform.py +0 -0
  124. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/tests/test_context.py +0 -0
  125. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/tests/test_contract_loader.py +0 -0
  126. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/tests/test_contract_pin_consistency.py +0 -0
  127. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/tests/test_csv_loader.py +0 -0
  128. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/tests/test_csv_readers_https.py +0 -0
  129. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/tests/test_csv_readers_integration.py +0 -0
  130. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/tests/test_csv_source.py +0 -0
  131. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/tests/test_errors.py +0 -0
  132. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/tests/test_harness.py +0 -0
  133. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/tests/test_hashing.py +0 -0
  134. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/tests/test_image_smoke_validation.py +0 -0
  135. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/tests/test_lint.py +0 -0
  136. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/tests/test_no_legacy_names.py +0 -0
  137. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/tests/test_package.py +0 -0
  138. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/tests/test_template.py +0 -0
  139. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/tests/test_types.py +0 -0
  140. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/tests/test_validation_runner.py +0 -0
  141. {continuo_python_runtime-0.4.0 → continuo_python_runtime-0.4.1}/tests/test_validation_s3.py +0 -0
@@ -6,7 +6,7 @@ jobs:
6
6
  test:
7
7
  runs-on: ubuntu-latest
8
8
  steps:
9
- - uses: actions/checkout@v4
9
+ - uses: actions/checkout@v7
10
10
  - uses: astral-sh/setup-uv@v7
11
11
  - name: Sync
12
12
  run: uv sync --all-packages --all-groups
@@ -19,9 +19,9 @@ jobs:
19
19
  - name: Types (contract)
20
20
  run: uv run mypy contract/continuo_engine_contract
21
21
  - name: Types (postgres adapter)
22
- run: uv run --package continuo-python-runtime-postgres mypy adapters/postgres/continuo_python_runtime_postgres
22
+ run: uv run --package continuo-postgres-adapter mypy adapters/postgres/continuo_postgres_adapter
23
23
  - name: Types (trino adapter)
24
- run: uv run --package continuo-python-runtime-trino mypy adapters/trino/continuo_python_runtime_trino
24
+ run: uv run --package continuo-trino-adapter mypy adapters/trino/continuo_trino_adapter
25
25
  # `-m "not image"` deselects tests/test_image_smoke_validation.py, which
26
26
  # needs a built engine image and the env naming it. Those tests run in
27
27
  # images.yml's smoke jobs, where an image actually exists. `and not
@@ -41,7 +41,7 @@ jobs:
41
41
  integration-postgres:
42
42
  runs-on: ubuntu-latest
43
43
  steps:
44
- - uses: actions/checkout@v4
44
+ - uses: actions/checkout@v7
45
45
  - uses: astral-sh/setup-uv@v7
46
46
  - run: uv sync --all-packages --all-groups
47
47
  - name: Start postgres stack
@@ -55,7 +55,7 @@ jobs:
55
55
  integration-trino:
56
56
  runs-on: ubuntu-latest
57
57
  steps:
58
- - uses: actions/checkout@v4
58
+ - uses: actions/checkout@v7
59
59
  - uses: astral-sh/setup-uv@v7
60
60
  - run: uv sync --all-packages --all-groups
61
61
  - name: Start trino stack
@@ -14,7 +14,7 @@ jobs:
14
14
  dco:
15
15
  runs-on: ubuntu-latest
16
16
  steps:
17
- - uses: actions/checkout@v4
17
+ - uses: actions/checkout@v7
18
18
  with:
19
19
  # The whole PR commit range has to be walkable, not just the merge commit.
20
20
  fetch-depth: 0
@@ -20,12 +20,15 @@ jobs:
20
20
  engine: [postgres, trino]
21
21
  runs-on: ubuntu-latest
22
22
  steps:
23
- - uses: actions/checkout@v4
23
+ - uses: actions/checkout@v7
24
+ - uses: astral-sh/setup-uv@v7
25
+ - name: Build wheels into a wheelhouse
26
+ run: uv build --all-packages -o wheelhouse
24
27
  - name: Build ${{ matrix.engine }} image
25
- run: docker build -f Dockerfile.${{ matrix.engine }} -t cpr-smoke-${{ matrix.engine }} .
28
+ run: docker build -f Dockerfile.${{ matrix.engine }} --build-arg WHEEL_SOURCE=wheelhouse -t cpr-smoke-${{ matrix.engine }} .
26
29
  - name: Save image
27
30
  run: docker save cpr-smoke-${{ matrix.engine }} -o /tmp/cpr-smoke-${{ matrix.engine }}.tar
28
- - uses: actions/upload-artifact@v4
31
+ - uses: actions/upload-artifact@v7
29
32
  with:
30
33
  name: cpr-smoke-${{ matrix.engine }}-image
31
34
  path: /tmp/cpr-smoke-${{ matrix.engine }}.tar
@@ -49,8 +52,8 @@ jobs:
49
52
  --health-timeout 5s
50
53
  --health-retries 10
51
54
  steps:
52
- - uses: actions/checkout@v4
53
- - uses: actions/download-artifact@v4
55
+ - uses: actions/checkout@v7
56
+ - uses: actions/download-artifact@v8
54
57
  with:
55
58
  name: cpr-smoke-postgres-image
56
59
  path: /tmp
@@ -104,8 +107,8 @@ jobs:
104
107
  needs: build
105
108
  runs-on: ubuntu-latest
106
109
  steps:
107
- - uses: actions/checkout@v4
108
- - uses: actions/download-artifact@v4
110
+ - uses: actions/checkout@v7
111
+ - uses: actions/download-artifact@v8
109
112
  with:
110
113
  name: cpr-smoke-trino-image
111
114
  path: /tmp
@@ -153,21 +156,31 @@ jobs:
153
156
  if: always()
154
157
  run: docker compose -f tests/smoke/trino-stack/docker-compose.yml down -v
155
158
 
156
- # On tag push: build both engine images for linux/amd64 and linux/arm64 and
157
- # push them to ghcr.io/<owner>/continuo-python-runtime-<engine>:<tag>. The
158
- # engine belongs in the image NAME, not the tag: Continuo's Helm chart pins an
159
- # image as `<name>:vX.Y.Z@sha256:<digest>`, so the tag must be the bare
159
+ # Runs on this same tag push, after the smoke jobs above pass. It builds both
160
+ # engine images for linux/amd64 and linux/arm64 installing the pinned
161
+ # versions FROM PyPI (WHEEL_SOURCE=pypi, the Dockerfile default), then
162
+ # pushes them to ghcr.io/<owner>/continuo-python-runtime-<engine>:<tag>. The
163
+ # engine belongs in the image NAME, not the tag: Continuo's Helm chart pins
164
+ # an image as `<name>:vX.Y.Z@sha256:<digest>`, so the tag must be the bare
160
165
  # version. Both platforms are required: domain repos build their own images
161
166
  # FROM these, and Continuo's e2e runs them in a kind cluster on aarch64
162
- # developer machines as well as on amd64 CI runners, so a single-arch publish
163
- # is unusable off amd64. The smoke jobs above stay single-arch — they load a
164
- # `docker save` tarball, which has no multi-platform equivalent.
165
- # continuo-engine-contract and the two engine adapters are installed from the
166
- # build context (see Dockerfile.postgres / Dockerfile.trino) — nothing pending
167
- # here.
167
+ # developer machines as well as on amd64 CI runners, so a single-arch
168
+ # publish is unusable off amd64. The smoke jobs above stay single-arch —
169
+ # they load a `docker save` tarball, which has no multi-platform
170
+ # equivalent.
171
+ #
172
+ # publish-pypi.yml runs in parallel on this same tag push, not before this
173
+ # job: there is no cross-workflow `needs:`, so this job cannot wait on that
174
+ # workflow directly. Instead the wait-until-installable step below polls
175
+ # PyPI until the pinned versions this tag just published are actually
176
+ # installable, which is what orders this build after publish-pypi.yml
177
+ # completes without a cross-workflow dependency.
178
+ # -test tags publish to TestPyPI only (see publish-pypi.yml), so this job
179
+ # skips them: there is no real-PyPI installable version to build an image
180
+ # from for those.
168
181
  publish:
169
- if: startsWith(github.ref, 'refs/tags/v') && !contains(github.ref_name, '-test')
170
182
  needs: [smoke-postgres, smoke-trino]
183
+ if: startsWith(github.ref, 'refs/tags/v') && !contains(github.ref_name, '-test')
171
184
  strategy:
172
185
  matrix:
173
186
  engine: [postgres, trino]
@@ -176,8 +189,8 @@ jobs:
176
189
  contents: read
177
190
  packages: write
178
191
  steps:
179
- - uses: actions/checkout@v4
180
- - uses: docker/setup-qemu-action@v3
192
+ - uses: actions/checkout@v7
193
+ - uses: docker/setup-qemu-action@v4
181
194
  - uses: docker/setup-buildx-action@v4
182
195
  - name: Log in to ghcr.io
183
196
  uses: docker/login-action@v4
@@ -185,6 +198,26 @@ jobs:
185
198
  registry: ghcr.io
186
199
  username: ${{ github.actor }}
187
200
  password: ${{ secrets.GITHUB_TOKEN }}
201
+ # The pinned distributions declare Requires-Python >= 3.14, so the
202
+ # availability probe must resolve them under a 3.14 interpreter — the
203
+ # runner's default python is older and pip would reject them even after
204
+ # they are published, spinning out every retry. (The image build itself
205
+ # installs inside python:3.14-slim, so it is unaffected; this is only for
206
+ # the host-side probe below.)
207
+ - uses: actions/setup-python@v5
208
+ with:
209
+ python-version: '3.14'
210
+ - name: Wait until the pinned versions are installable from PyPI
211
+ run: |
212
+ set -eu
213
+ req="image-requirements-${{ matrix.engine }}.txt"
214
+ for i in $(seq 1 30); do
215
+ if python -m pip download --dest /tmp/probe -r "$req" >/tmp/probe.log 2>&1; then
216
+ echo "installable"; exit 0
217
+ fi
218
+ echo "attempt $i: not yet on index; sleeping"; sleep 20
219
+ done
220
+ echo "pinned versions never became installable"; cat /tmp/probe.log; exit 1
188
221
  - name: Build and push ${{ matrix.engine }} image
189
222
  uses: docker/build-push-action@v7
190
223
  with:
@@ -0,0 +1,99 @@
1
+ name: publish-pypi
2
+
3
+ # Publishes all four PyPI distributions this repo owns — continuo-python-runtime
4
+ # (the harness), continuo-engine-contract (the port, result-block format, and
5
+ # shared guards), and the two engine adapters (continuo-postgres-adapter,
6
+ # continuo-trino-adapter) — via PyPI Trusted Publishing (OIDC, no stored
7
+ # token). They publish together on a single v* tag, each at its own
8
+ # pyproject version, so a tag that only bumps the runtime still carries an
9
+ # unchanged adapter version along for the ride; skip-existing (below) makes
10
+ # that a no-op rather than a duplicate-upload failure. A release-time guard
11
+ # (scripts/check_version_bumps.py) refuses a tag that changed a package's
12
+ # source without bumping its version, so skip-existing can never silently
13
+ # ship stale adapter code under an unchanged version number.
14
+ #
15
+ # Tag glob note: `v*` is the repository's single release pattern — the same tag
16
+ # that images.yml builds and pushes both engine images from, so one tag ships
17
+ # the whole release. GitHub Actions tag globs anchor at character 1, so `v*`
18
+ # claims every tag beginning with "v"; no other pattern may be introduced.
19
+ # Tag `v<ver>-test<n>` publishes to TestPyPI; `v<ver>` publishes to real PyPI.
20
+ # The GitHub environment name is what the PyPI "pending publisher" is
21
+ # registered against — all four project names need one on each index.
22
+ #
23
+ # All four distributions are built into a single `dist/` and uploaded in one
24
+ # publish call, so there is no ordering constraint between them. The runtime
25
+ # wheel declares continuo-engine-contract as a dependency and resolves it from
26
+ # the index at install time ([tool.uv.sources] is dev-only and is not embedded
27
+ # in the wheel).
28
+ on:
29
+ push:
30
+ tags:
31
+ - "v*"
32
+
33
+ permissions: {}
34
+
35
+ jobs:
36
+ publish-packages:
37
+ if: startsWith(github.ref_name, 'v')
38
+ runs-on: ubuntu-latest
39
+ environment: ${{ contains(github.ref_name, '-test') && 'testpypi' || 'pypi' }}
40
+ permissions:
41
+ id-token: write # OIDC token for Trusted Publishing
42
+ contents: read # actions/checkout needs read access to the repo
43
+ steps:
44
+ - uses: actions/checkout@v7
45
+ with:
46
+ fetch-depth: 0
47
+ - uses: astral-sh/setup-uv@v7
48
+ - name: Refuse a tag that changed a package without bumping its version
49
+ run: python scripts/check_version_bumps.py
50
+ - name: Test before publishing
51
+ # Gate all four published projects, including both adapter suites — an
52
+ # adapter regression must block its own immutable PyPI upload, and
53
+ # images.yml runs in parallel so its smoke failure cannot. Each suite is
54
+ # a SEPARATE pytest invocation: every workspace member's tests/ is its
55
+ # own `tests` package (with __init__.py + conftest.py), so running two
56
+ # of them in one process collides on the `tests.conftest` module name
57
+ # (see the [tool.pytest.ini_options] note in pyproject.toml). `not image
58
+ # and not integration` deselects the tests needing a built engine image
59
+ # or a live warehouse (images.yml / the smoke jobs cover those). The
60
+ # step's default `set -e` fails the gate if any invocation fails.
61
+ run: |
62
+ uv sync --all-packages --all-groups
63
+ uv run pytest tests contract/tests -m "not image and not integration" -q
64
+ uv run pytest adapters/postgres/tests -m "not image and not integration" -q
65
+ uv run pytest adapters/trino/tests -m "not image and not integration" -q
66
+ - name: Build the contract sdist + wheel
67
+ run: uv build --package continuo-engine-contract -o dist
68
+ - name: Build the runtime sdist + wheel
69
+ run: uv build -o dist
70
+ - name: Build the postgres adapter sdist + wheel
71
+ run: uv build --package continuo-postgres-adapter -o dist
72
+ - name: Build the trino adapter sdist + wheel
73
+ run: uv build --package continuo-trino-adapter -o dist
74
+ # Adapters change rarely, so most v* tags carry an unchanged adapter
75
+ # version alongside a bumped runtime/contract version. Without
76
+ # skip-existing, re-uploading that unchanged version 400s and fails the
77
+ # whole publish. Trade-off: a changed package published without a
78
+ # version bump is then silently skipped too — the
79
+ # check_version_bumps.py guard above is what catches that case, before
80
+ # this job's build/publish steps ever run.
81
+ - name: Publish to TestPyPI
82
+ if: contains(github.ref_name, '-test')
83
+ # Pinned to a commit SHA. release/v1 is a moving branch, and this job
84
+ # holds id-token: write for PyPI Trusted Publishing — a compromised
85
+ # revision could mint a token and publish under this project's name.
86
+ uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # release/v1
87
+ with:
88
+ repository-url: https://test.pypi.org/legacy/
89
+ packages-dir: dist
90
+ skip-existing: true
91
+ - name: Publish to PyPI
92
+ if: ${{ !contains(github.ref_name, '-test') }}
93
+ # Pinned to a commit SHA. release/v1 is a moving branch, and this job
94
+ # holds id-token: write for PyPI Trusted Publishing — a compromised
95
+ # revision could mint a token and publish under this project's name.
96
+ uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # release/v1
97
+ with:
98
+ packages-dir: dist
99
+ skip-existing: true
@@ -53,7 +53,7 @@ jobs:
53
53
  # other source of the repo's files. Its absence went unnoticed through
54
54
  # review because this job only triggers on push:tags, never on
55
55
  # pull_request, so no PR's CI ever actually executes it.
56
- - uses: actions/checkout@v4
56
+ - uses: actions/checkout@v7
57
57
 
58
58
  - name: Wait for publish-pypi.yml and images.yml to finish on this commit
59
59
  run: |
@@ -47,7 +47,7 @@ jobs:
47
47
  # range (below), and this repository is small enough (a few hundred KB,
48
48
  # well under a second to scan) that fetch-depth: 0 costs nothing on every
49
49
  # trigger rather than only on the schedule.
50
- - uses: actions/checkout@v4
50
+ - uses: actions/checkout@v7
51
51
  with:
52
52
  fetch-depth: 0
53
53
 
@@ -106,7 +106,7 @@ jobs:
106
106
  runs-on: ubuntu-latest
107
107
  timeout-minutes: 20
108
108
  steps:
109
- - uses: actions/checkout@v4
109
+ - uses: actions/checkout@v7
110
110
 
111
111
  # Advisory: reports HIGH/CRITICAL and always exits 0. Base-image and
112
112
  # transitive CVEs are often unfixable upstream, so gating on them would
@@ -57,8 +57,8 @@ uv run ruff check .
57
57
  uv run ruff check contract
58
58
  uv run mypy continuo_python_runtime
59
59
  uv run mypy contract/continuo_engine_contract
60
- uv run --package continuo-python-runtime-postgres mypy adapters/postgres/continuo_python_runtime_postgres
61
- uv run --package continuo-python-runtime-trino mypy adapters/trino/continuo_python_runtime_trino
60
+ uv run --package continuo-postgres-adapter mypy adapters/postgres/continuo_postgres_adapter
61
+ uv run --package continuo-trino-adapter mypy adapters/trino/continuo_trino_adapter
62
62
  uv run pytest --cov=continuo_python_runtime -m "not image and not integration" -v
63
63
  uv run pytest tests/test_csv_readers_integration.py tests/test_validation_runner.py -m integration -v
64
64
  uv run pytest contract/tests -v
@@ -0,0 +1,56 @@
1
+ FROM python:3.14-slim
2
+ # One dual-role image per engine — the python-node runtime harness (default
3
+ # command `run`) and the blue/green validation runner (`validation-op`). It
4
+ # installs the PUBLISHED, versioned libraries, never the repo source:
5
+ # WHEEL_SOURCE=pypi (release) installs the pins from PyPI. PyPI versions
6
+ # are immutable and the image is consumed
7
+ # digest-pinned, so no hash lock is needed here.
8
+ # WHEEL_SOURCE=wheelhouse (CI/PR) installs the same pins from ./wheelhouse
9
+ # (built by CI) so an unreleased change is testable.
10
+ # The wheelhouse holds only this repo's own first-party wheels (built via
11
+ # `uv build --all-packages`); third-party dependencies always resolve from
12
+ # PyPI in both branches, so the wheelhouse branch never passes --no-index to
13
+ # the requirements install below.
14
+ # The pinned versions live in image-requirements-postgres.txt, kept equal to the
15
+ # repo's pyproject versions by tests/test_image_requirements_sync.py.
16
+ #
17
+ # The wheelhouse branch installs its three first-party wheels (contract,
18
+ # runtime, adapter) by exact local file first, with --no-index --no-deps: a
19
+ # plain `--find-links=/tmp/wheelhouse -r req.txt` is not enough, because pip
20
+ # does not prefer a find-links wheel over an index match at the same
21
+ # name+version, and continuo-python-runtime / continuo-engine-contract are
22
+ # already published on PyPI under the version this repo's HEAD currently
23
+ # carries (a routine dependency bump lands between releases without a version
24
+ # bump). Left unpinned-by-file, pip silently installs the last PyPI release's
25
+ # transitive pins instead of the wheel this build just produced, defeating
26
+ # the point of testing an unreleased change. Installing the exact local files
27
+ # first (no index contacted at all for this step) is unambiguous; the second
28
+ # install then only has PyPI-only third-party deps left to resolve, and finds
29
+ # the three first-party packages already satisfied.
30
+ ARG WHEEL_SOURCE=pypi
31
+ COPY image-requirements-postgres.txt /tmp/req.txt
32
+ # Optional COPY: the bracket glob matches ./wheelhouse when it exists (the CI/PR
33
+ # WHEEL_SOURCE=wheelhouse path builds it) and no-ops when it is absent, so a
34
+ # release build or a plain `docker build -f Dockerfile.postgres .` needs no
35
+ # pre-created directory. Only the wheelhouse branch below reads its contents.
36
+ COPY wheelhous[e] /tmp/wheelhouse
37
+ RUN set -eu; \
38
+ if [ "$WHEEL_SOURCE" = "wheelhouse" ]; then \
39
+ pip install --no-cache-dir --no-index --no-deps \
40
+ /tmp/wheelhouse/continuo_engine_contract-*.whl \
41
+ /tmp/wheelhouse/continuo_python_runtime-*.whl \
42
+ /tmp/wheelhouse/continuo_postgres_adapter-*.whl; \
43
+ pip install --no-cache-dir -r /tmp/req.txt; \
44
+ else \
45
+ pip install --no-cache-dir -r /tmp/req.txt; \
46
+ fi; \
47
+ rm -rf /tmp/req.txt /tmp/wheelhouse
48
+ # PYTHONPATH is belt-and-braces: the harness also inserts APP_ROOT and the
49
+ # node script's own directory at the front of sys.path before executing it.
50
+ ENV CONTRACT_DIR=/app/contracts APP_ROOT=/app PYTHONPATH=/app
51
+ WORKDIR /app
52
+ # uid 65532 matches continuo's executor securityContext expectation.
53
+ RUN useradd --uid 65532 --no-create-home --shell /usr/sbin/nologin nonroot
54
+ USER 65532:65532
55
+ ENTRYPOINT ["continuo-runtime"]
56
+ CMD ["run"]
@@ -0,0 +1,56 @@
1
+ FROM python:3.14-slim
2
+ # One dual-role image per engine — the python-node runtime harness (default
3
+ # command `run`) and the blue/green validation runner (`validation-op`). It
4
+ # installs the PUBLISHED, versioned libraries, never the repo source:
5
+ # WHEEL_SOURCE=pypi (release) installs the pins from PyPI. PyPI versions
6
+ # are immutable and the image is consumed
7
+ # digest-pinned, so no hash lock is needed here.
8
+ # WHEEL_SOURCE=wheelhouse (CI/PR) installs the same pins from ./wheelhouse
9
+ # (built by CI) so an unreleased change is testable.
10
+ # The wheelhouse holds only this repo's own first-party wheels (built via
11
+ # `uv build --all-packages`); third-party dependencies always resolve from
12
+ # PyPI in both branches, so the wheelhouse branch never passes --no-index to
13
+ # the requirements install below.
14
+ # The pinned versions live in image-requirements-trino.txt, kept equal to the
15
+ # repo's pyproject versions by tests/test_image_requirements_sync.py.
16
+ #
17
+ # The wheelhouse branch installs its three first-party wheels (contract,
18
+ # runtime, adapter) by exact local file first, with --no-index --no-deps: a
19
+ # plain `--find-links=/tmp/wheelhouse -r req.txt` is not enough, because pip
20
+ # does not prefer a find-links wheel over an index match at the same
21
+ # name+version, and continuo-python-runtime / continuo-engine-contract are
22
+ # already published on PyPI under the version this repo's HEAD currently
23
+ # carries (a routine dependency bump lands between releases without a version
24
+ # bump). Left unpinned-by-file, pip silently installs the last PyPI release's
25
+ # transitive pins instead of the wheel this build just produced, defeating
26
+ # the point of testing an unreleased change. Installing the exact local files
27
+ # first (no index contacted at all for this step) is unambiguous; the second
28
+ # install then only has PyPI-only third-party deps left to resolve, and finds
29
+ # the three first-party packages already satisfied.
30
+ ARG WHEEL_SOURCE=pypi
31
+ COPY image-requirements-trino.txt /tmp/req.txt
32
+ # Optional COPY: the bracket glob matches ./wheelhouse when it exists (the CI/PR
33
+ # WHEEL_SOURCE=wheelhouse path builds it) and no-ops when it is absent, so a
34
+ # release build or a plain `docker build -f Dockerfile.trino .` needs no
35
+ # pre-created directory. Only the wheelhouse branch below reads its contents.
36
+ COPY wheelhous[e] /tmp/wheelhouse
37
+ RUN set -eu; \
38
+ if [ "$WHEEL_SOURCE" = "wheelhouse" ]; then \
39
+ pip install --no-cache-dir --no-index --no-deps \
40
+ /tmp/wheelhouse/continuo_engine_contract-*.whl \
41
+ /tmp/wheelhouse/continuo_python_runtime-*.whl \
42
+ /tmp/wheelhouse/continuo_trino_adapter-*.whl; \
43
+ pip install --no-cache-dir -r /tmp/req.txt; \
44
+ else \
45
+ pip install --no-cache-dir -r /tmp/req.txt; \
46
+ fi; \
47
+ rm -rf /tmp/req.txt /tmp/wheelhouse
48
+ # PYTHONPATH is belt-and-braces: the harness also inserts APP_ROOT and the
49
+ # node script's own directory at the front of sys.path before executing it.
50
+ ENV CONTRACT_DIR=/app/contracts APP_ROOT=/app PYTHONPATH=/app
51
+ WORKDIR /app
52
+ # uid 65532 matches continuo's executor securityContext expectation.
53
+ RUN useradd --uid 65532 --no-create-home --shell /usr/sbin/nologin nonroot
54
+ USER 65532:65532
55
+ ENTRYPOINT ["continuo-runtime"]
56
+ CMD ["run"]
@@ -1,3 +1,23 @@
1
+ Metadata-Version: 2.5
2
+ Name: continuo-python-runtime
3
+ Version: 0.4.1
4
+ Summary: Runtime harness, contract tooling, and CI lint for Continuo python nodes.
5
+ Author: Simone Carolini
6
+ Maintainer: Simone Carolini
7
+ License-Expression: Apache-2.0
8
+ License-File: LICENSE
9
+ License-File: NOTICE
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Programming Language :: Python :: 3.14
13
+ Requires-Python: >=3.14
14
+ Requires-Dist: boto3==1.43.74
15
+ Requires-Dist: continuo-engine-contract==0.7.3
16
+ Requires-Dist: pyarrow==25.0.1
17
+ Requires-Dist: pyyaml==6.0.3
18
+ Requires-Dist: sqlglot==30.17.0
19
+ Description-Content-Type: text/markdown
20
+
1
21
  # Continuo Python Runtime
2
22
 
3
23
  [![CI](https://github.com/carolsimone/continuo-python-runtime/actions/workflows/ci.yml/badge.svg)](https://github.com/carolsimone/continuo-python-runtime/actions/workflows/ci.yml)
@@ -12,7 +32,7 @@ and register the release with Continuo.
12
32
 
13
33
  ## What this repo is
14
34
 
15
- Four artifacts come out of this repository:
35
+ Five artifacts come out of this repository:
16
36
 
17
37
  - **The `continuo-python-runtime` PyPI package** — the `continuo-runtime` CLI
18
38
  (`validate` / `merge` / `hash` / `lint` / `run` / `validation-op`) and the
@@ -21,6 +41,13 @@ Four artifacts come out of this repository:
21
41
  - **The `continuo-engine-contract` PyPI package** — the `WarehouseAdapter`
22
42
  port, the contract schema, the shared SQL/type/config guards, and the
23
43
  sentinel result-block format. Adapter authors outside this repo pin it.
44
+ - **The two engine-adapter PyPI packages** (`continuo-postgres-adapter`,
45
+ `continuo-trino-adapter`) — one `WarehouseAdapter` implementation per
46
+ warehouse engine, each published independently under the same tag. A
47
+ domain repo normally never installs these directly (the engine image
48
+ already has the matching one baked in); they exist as standalone PyPI
49
+ packages for the "build your own container" shape (see below) and for
50
+ third-party adapter authors to reference.
24
51
  - **Per-engine base images**, one per warehouse engine
25
52
  (`continuo-python-runtime-postgres`, `continuo-python-runtime-trino`), that
26
53
  domain repos build `FROM`. Each image bakes in the runtime and a single
@@ -30,10 +57,11 @@ Four artifacts come out of this repository:
30
57
  - **`template/`** — a copy-ready domain repo: `Dockerfile`, `contracts/`,
31
58
  `scripts/`, and the `release.yml` CI/CD workflow.
32
59
 
33
- One `vX.Y.Z` git tag releases all of it: `publish-pypi.yml` builds both
34
- distributions into a single `dist/` and publishes them together, and
35
- `images.yml` builds and pushes both engine images multi-arch under the same
36
- tag.
60
+ One `vX.Y.Z` git tag releases all of it: `publish-pypi.yml` builds all four
61
+ PyPI distributions into a single `dist/` and publishes them together, and
62
+ `images.yml` builds and pushes both engine images — each installing its
63
+ matching pinned adapter version from that same release — multi-arch under
64
+ the same tag.
37
65
 
38
66
  ### What this repo owns
39
67
 
@@ -50,22 +78,27 @@ validation-side port, adapter class, entry-point group, or image. One
50
78
  | --- | --- | --- | --- |
51
79
  | `continuo-python-runtime` | `continuo_python_runtime` | this repo (root) | Harness (CLI, `conform()`, `RunContext`, error taxonomy) **and** the validation runner (`continuo-runtime validation-op`). Published to PyPI. |
52
80
  | `continuo-engine-contract` | `continuo_engine_contract` | this repo, `contract/` | The `WarehouseAdapter` port, contract schema, the SQL/type/config guards adapters must run, and the result-block format. Published to PyPI. |
53
- | `continuo-python-runtime-postgres` | `continuo_python_runtime_postgres` | this repo, `adapters/postgres/` | `PostgresAdapter` — one class, both roles. **Not published to PyPI** — built from source into the image. |
54
- | `continuo-python-runtime-trino` | `continuo_python_runtime_trino` | this repo, `adapters/trino/` | `TrinoAdapter` — one class, both roles, for Trino/Iceberg. **Not published to PyPI** — built from source into the image. |
81
+ | `continuo-postgres-adapter` | `continuo_postgres_adapter` | this repo, `adapters/postgres/` | `PostgresAdapter` — one class, both roles. Published to PyPI. |
82
+ | `continuo-trino-adapter` | `continuo_trino_adapter` | this repo, `adapters/trino/` | `TrinoAdapter` — one class, both roles, for Trino/Iceberg. Published to PyPI. |
55
83
 
56
84
  All four are uv workspace members (`[tool.uv.workspace]` in the root
57
85
  `pyproject.toml`), so `uv sync --all-packages --all-groups` at the repo root
58
86
  installs everything for local development.
59
87
 
60
- **Only `continuo-python-runtime` and `continuo-engine-contract` are published
61
- to PyPI.** The two engine adapters are built **from source into the engine
62
- images**: `Dockerfile.postgres` and `Dockerfile.trino` install them out of the
63
- build context, so each image ships exactly one adapter and the runtime
64
- discovers it through the `continuo_engine.adapters` entry-point group at run
65
- time. Nothing installs them from an index — the harness package does not
66
- depend on them, and domain repos get their adapter by building `FROM` a
67
- published base image. They are still built, type-checked, and tested by CI on
68
- every change.
88
+ **All four packages in the table above are published to PyPI**, under the
89
+ same `vX.Y.Z` tag. The two engine images then **install the matching pinned
90
+ adapter version from PyPI** — `Dockerfile.postgres` installs
91
+ `continuo-postgres-adapter==X.Y.Z`, `Dockerfile.trino` installs
92
+ `continuo-trino-adapter==X.Y.Z` — rather than building it from this repo's
93
+ source tree, so each image still ships exactly one adapter and the runtime
94
+ still discovers it through the `continuo_engine.adapters` entry-point group
95
+ at run time. The image **name** (`continuo-python-runtime-<engine>`) and the
96
+ adapter's pip **distribution** name (`continuo-<engine>-adapter`) are two
97
+ different artifacts of the same adapter — same engine, same version, same
98
+ runtime behavior, different packaging; see "Build your own container" below
99
+ for a build shape that installs the pip package directly instead of `FROM`
100
+ the image. All four packages are still built, type-checked, and tested by CI
101
+ on every change.
69
102
 
70
103
  ### The result block is a frozen wire contract
71
104
 
@@ -242,26 +275,46 @@ A domain repo picks its warehouse engine by which base image it builds
242
275
  `FROM`:
243
276
 
244
277
  ```dockerfile
245
- FROM ghcr.io/carolsimone/continuo-python-runtime-postgres:v0.4.0
278
+ FROM ghcr.io/carolsimone/continuo-python-runtime-postgres:v0.4.1
246
279
  # or
247
- FROM ghcr.io/carolsimone/continuo-python-runtime-trino:v0.4.0
280
+ FROM ghcr.io/carolsimone/continuo-python-runtime-trino:v0.4.1
248
281
  ```
249
282
 
250
283
  The engine is part of the image **name**; the tag is the bare version, so
251
284
  Continuo's Helm chart can pin an image as `<name>:vX.Y.Z@sha256:<digest>`.
252
285
 
253
- Each image bakes in exactly one `WarehouseAdapter` for that engine — installed
254
- from this repo's `adapters/postgres/` or `adapters/trino/` package (see the
255
- table above) — registered under the `continuo_engine.adapters` entry-point
256
- group (entry names `postgres` / `trino`). The runtime discovers it via
257
- `discover_adapter()` at run time, so a single image serves every node in the
258
- service and the release-time validation Job for it. The executor injects the
259
- warehouse connection as environment variables (engine-native, e.g.
286
+ Each image bakes in exactly one `WarehouseAdapter` for that engine — the
287
+ pinned PyPI version of the `continuo-postgres-adapter` or
288
+ `continuo-trino-adapter` package built from this repo's `adapters/postgres/`
289
+ or `adapters/trino/` source (see the table above) — registered under the
290
+ `continuo_engine.adapters` entry-point group (entry names `postgres` /
291
+ `trino`). The runtime discovers it via `discover_adapter()` at run time, so a
292
+ single image serves every node in the service and the release-time
293
+ validation Job for it. The executor injects the warehouse connection as
294
+ environment variables (engine-native, e.g.
260
295
  `POSTGRES_HOST`/`POSTGRES_DB`/`POSTGRES_USER`) plus the node-selection
261
296
  environment (`NODE_ID`, `TABLE_NAME`, `TARGET_SCHEMA`, and optionally
262
297
  `CONTRACT_DIR`/`APP_ROOT`) that `continuo-runtime run` reads to dispatch the
263
298
  right node's script.
264
299
 
300
+ ### Build your own container
301
+
302
+ A domain repo does not have to build `FROM` the published engine image.
303
+ `template/` ships two Dockerfiles for the two build shapes (see
304
+ `template/README.md` § "Choosing a base" for the full comparison):
305
+
306
+ - **Shape 1 — `template/Dockerfile`** — `FROM` the published engine image
307
+ (`continuo-python-runtime-<engine>`), as shown above. Simplest; the image
308
+ already has the runtime and adapter installed and pinned.
309
+ - **Shape 2 — `template/Dockerfile.pip`** — your own base image, installing
310
+ `continuo-python-runtime` and one `continuo-<engine>-adapter` from PyPI
311
+ via a hash-locked `requirements.lock` (`template/requirements.lock`). Use
312
+ this when you must control the base image yourself.
313
+
314
+ Both shapes end up running the same runtime and the same adapter version;
315
+ which one you pick only changes who controls the base OS layer underneath
316
+ them.
317
+
265
318
  ## Further reading
266
319
 
267
320
  - `docs/superpowers/specs/2026-07-31-python-runtime-design.md` — this