continuo-python-runtime 0.6.0__tar.gz → 0.8.0__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 (189) hide show
  1. continuo_python_runtime-0.8.0/.claude/agents/ddd-architecture-auditor.md +117 -0
  2. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/.github/ISSUE_TEMPLATE/bug_report.yml +1 -0
  3. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/.github/dependabot.yml +4 -3
  4. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/.github/workflows/ci.yml +22 -1
  5. continuo_python_runtime-0.8.0/.github/workflows/image-build.yml +188 -0
  6. continuo_python_runtime-0.8.0/.github/workflows/image-smoke.yml +255 -0
  7. continuo_python_runtime-0.8.0/.github/workflows/images.yml +89 -0
  8. continuo_python_runtime-0.8.0/.github/workflows/publish-pypi.yml +375 -0
  9. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/.github/workflows/release.yml +33 -29
  10. continuo_python_runtime-0.8.0/.github/workflows/verify-packages.yml +74 -0
  11. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/.gitignore +1 -0
  12. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/CHANGELOG.md +76 -0
  13. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/CLAUDE.md +2 -2
  14. continuo_python_runtime-0.8.0/CONTRIBUTING.md +180 -0
  15. continuo_python_runtime-0.8.0/Dockerfile.duckdb +58 -0
  16. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/Dockerfile.postgres +13 -1
  17. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/Dockerfile.trino +13 -1
  18. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/PKG-INFO +30 -19
  19. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/README.md +29 -18
  20. continuo_python_runtime-0.8.0/adapters/duckdb/README.md +109 -0
  21. continuo_python_runtime-0.8.0/adapters/duckdb/continuo_duckdb_adapter/__init__.py +1 -0
  22. continuo_python_runtime-0.8.0/adapters/duckdb/continuo_duckdb_adapter/adapter.py +25 -0
  23. continuo_python_runtime-0.8.0/adapters/duckdb/continuo_duckdb_adapter/application/__init__.py +1 -0
  24. continuo_python_runtime-0.8.0/adapters/duckdb/continuo_duckdb_adapter/application/ports.py +77 -0
  25. continuo_python_runtime-0.8.0/adapters/duckdb/continuo_duckdb_adapter/application/warehouse.py +205 -0
  26. continuo_python_runtime-0.8.0/adapters/duckdb/continuo_duckdb_adapter/domain/__init__.py +1 -0
  27. continuo_python_runtime-0.8.0/adapters/duckdb/continuo_duckdb_adapter/domain/columns.py +46 -0
  28. continuo_python_runtime-0.8.0/adapters/duckdb/continuo_duckdb_adapter/domain/identifiers.py +33 -0
  29. continuo_python_runtime-0.8.0/adapters/duckdb/continuo_duckdb_adapter/domain/layout.py +162 -0
  30. continuo_python_runtime-0.8.0/adapters/duckdb/continuo_duckdb_adapter/infrastructure/__init__.py +1 -0
  31. continuo_python_runtime-0.8.0/adapters/duckdb/continuo_duckdb_adapter/infrastructure/ddl.py +229 -0
  32. continuo_python_runtime-0.8.0/adapters/duckdb/continuo_duckdb_adapter/infrastructure/extensions.py +51 -0
  33. continuo_python_runtime-0.8.0/adapters/duckdb/continuo_duckdb_adapter/infrastructure/passfile.py +67 -0
  34. continuo_python_runtime-0.8.0/adapters/duckdb/continuo_duckdb_adapter/infrastructure/session.py +281 -0
  35. continuo_python_runtime-0.8.0/adapters/duckdb/continuo_duckdb_adapter/infrastructure/settings.py +101 -0
  36. continuo_python_runtime-0.8.0/adapters/duckdb/pyproject.toml +59 -0
  37. continuo_python_runtime-0.8.0/adapters/duckdb/tests/conftest.py +403 -0
  38. continuo_python_runtime-0.8.0/adapters/duckdb/tests/test_adapter_duckdb_proxy.py +25 -0
  39. continuo_python_runtime-0.8.0/adapters/duckdb/tests/test_adapter_duckdb_runtime.py +106 -0
  40. continuo_python_runtime-0.8.0/adapters/duckdb/tests/test_adapter_duckdb_validation.py +150 -0
  41. continuo_python_runtime-0.8.0/adapters/duckdb/tests/test_adapter_duckdb_wiring.py +25 -0
  42. continuo_python_runtime-0.8.0/adapters/duckdb/tests/test_domain_duckdb_columns.py +55 -0
  43. continuo_python_runtime-0.8.0/adapters/duckdb/tests/test_domain_duckdb_identifiers.py +26 -0
  44. continuo_python_runtime-0.8.0/adapters/duckdb/tests/test_domain_duckdb_layout.py +135 -0
  45. continuo_python_runtime-0.8.0/adapters/duckdb/tests/test_infra_duckdb_session.py +458 -0
  46. continuo_python_runtime-0.8.0/adapters/duckdb/tests/test_infra_duckdb_settings_ddl.py +295 -0
  47. continuo_python_runtime-0.8.0/adapters/duckdb/tests/test_integration_duckdb_layout.py +269 -0
  48. continuo_python_runtime-0.8.0/adapters/duckdb/tests/test_integration_duckdb_runtime.py +253 -0
  49. continuo_python_runtime-0.8.0/adapters/duckdb/tests/test_integration_duckdb_stack.py +133 -0
  50. continuo_python_runtime-0.8.0/adapters/duckdb/tests/test_integration_duckdb_validation.py +226 -0
  51. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/continuo_python_runtime/contract/kinds.py +18 -3
  52. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/continuo_python_runtime/contract/loader.py +9 -0
  53. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/continuo_python_runtime/contract/merge.py +7 -1
  54. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/continuo_python_runtime/contract/model.py +2 -1
  55. continuo_python_runtime-0.8.0/continuo_python_runtime/contract/secret_ref.py +31 -0
  56. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/continuo_python_runtime/harness.py +2 -1
  57. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/docs/boundary-contract.md +22 -4
  58. continuo_python_runtime-0.8.0/image-requirements-duckdb.txt +3 -0
  59. continuo_python_runtime-0.8.0/image-requirements-postgres.txt +3 -0
  60. continuo_python_runtime-0.8.0/image-requirements-trino.txt +3 -0
  61. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/pyproject.toml +2 -2
  62. continuo_python_runtime-0.8.0/scripts/apply_rehearsal_version.py +145 -0
  63. continuo_python_runtime-0.8.0/scripts/bake_duckdb_extensions.py +37 -0
  64. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/scripts/check_version_bumps.py +5 -1
  65. continuo_python_runtime-0.8.0/scripts/verify_installed_packages.py +155 -0
  66. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/template/Dockerfile +1 -1
  67. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/template/contracts/example.yml +2 -1
  68. continuo_python_runtime-0.8.0/tests/contract/__init__.py +0 -0
  69. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/tests/contract/test_kinds.py +50 -1
  70. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/tests/contract/test_loader.py +45 -0
  71. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/tests/contract/test_merge.py +49 -0
  72. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/tests/contract/test_model.py +2 -2
  73. continuo_python_runtime-0.8.0/tests/smoke/duckdb-stack/docker-compose.yml +71 -0
  74. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/tests/test_adapter_naming.py +2 -2
  75. continuo_python_runtime-0.8.0/tests/test_check_version_bumps_tags.py +49 -0
  76. continuo_python_runtime-0.8.0/tests/test_ci_test_coverage.py +138 -0
  77. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/tests/test_contract_loader.py +1 -1
  78. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/tests/test_contract_pin_consistency.py +1 -0
  79. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/tests/test_harness.py +49 -0
  80. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/tests/test_image_requirements_sync.py +13 -0
  81. continuo_python_runtime-0.8.0/tests/test_rehearsal_version.py +136 -0
  82. continuo_python_runtime-0.8.0/tests/test_release_pipeline_structure.py +142 -0
  83. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/tests/test_template.py +1 -1
  84. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/uv.lock +58 -1
  85. continuo_python_runtime-0.6.0/.github/workflows/images.yml +0 -228
  86. continuo_python_runtime-0.6.0/.github/workflows/publish-pypi.yml +0 -99
  87. continuo_python_runtime-0.6.0/CONTRIBUTING.md +0 -102
  88. continuo_python_runtime-0.6.0/image-requirements-postgres.txt +0 -2
  89. continuo_python_runtime-0.6.0/image-requirements-trino.txt +0 -2
  90. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/.dockerignore +0 -0
  91. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/.github/CODEOWNERS +0 -0
  92. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  93. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  94. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  95. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/.github/workflows/dco.yml +0 -0
  96. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/.github/workflows/security.yml +0 -0
  97. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/.gitleaks.toml +0 -0
  98. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/CODE_OF_CONDUCT.md +0 -0
  99. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/DCO +0 -0
  100. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/LICENSE +0 -0
  101. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/NOTICE +0 -0
  102. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/SECURITY.md +0 -0
  103. {continuo_python_runtime-0.6.0/adapters/postgres/continuo_postgres_adapter → continuo_python_runtime-0.8.0/adapters/duckdb/tests}/__init__.py +0 -0
  104. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/adapters/postgres/README.md +0 -0
  105. {continuo_python_runtime-0.6.0/adapters/postgres/tests → continuo_python_runtime-0.8.0/adapters/postgres/continuo_postgres_adapter}/__init__.py +0 -0
  106. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/adapters/postgres/continuo_postgres_adapter/adapter.py +0 -0
  107. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/adapters/postgres/pyproject.toml +0 -0
  108. {continuo_python_runtime-0.6.0/adapters/trino/continuo_trino_adapter → continuo_python_runtime-0.8.0/adapters/postgres/tests}/__init__.py +0 -0
  109. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/adapters/postgres/tests/conftest.py +0 -0
  110. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/adapters/postgres/tests/test_adapter_postgres_validation.py +0 -0
  111. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/adapters/postgres/tests/test_adapter_runtime_postgres.py +0 -0
  112. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/adapters/postgres/tests/test_integration_postgres_validation.py +0 -0
  113. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/adapters/postgres/tests/test_integration_runtime_postgres.py +0 -0
  114. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/adapters/trino/README.md +0 -0
  115. {continuo_python_runtime-0.6.0/adapters/trino/tests → continuo_python_runtime-0.8.0/adapters/trino/continuo_trino_adapter}/__init__.py +0 -0
  116. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/adapters/trino/continuo_trino_adapter/adapter.py +0 -0
  117. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/adapters/trino/pyproject.toml +0 -0
  118. {continuo_python_runtime-0.6.0/contract → continuo_python_runtime-0.8.0/adapters/trino}/tests/__init__.py +0 -0
  119. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/adapters/trino/tests/test_adapter_runtime_trino.py +0 -0
  120. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/adapters/trino/tests/test_adapter_trino_validation.py +0 -0
  121. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/adapters/trino/tests/test_integration_runtime_trino.py +0 -0
  122. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/adapters/trino/tests/test_integration_trino_validation.py +0 -0
  123. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/continuo_python_runtime/__init__.py +0 -0
  124. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/continuo_python_runtime/cli.py +0 -0
  125. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/continuo_python_runtime/closure.py +0 -0
  126. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/continuo_python_runtime/conform.py +0 -0
  127. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/continuo_python_runtime/context.py +0 -0
  128. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/continuo_python_runtime/contract/__init__.py +0 -0
  129. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/continuo_python_runtime/contract/paths.py +0 -0
  130. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/continuo_python_runtime/csv_loader.py +0 -0
  131. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/continuo_python_runtime/csv_readers/__init__.py +0 -0
  132. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/continuo_python_runtime/csv_readers/https.py +0 -0
  133. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/continuo_python_runtime/csv_readers/s3.py +0 -0
  134. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/continuo_python_runtime/csv_source.py +0 -0
  135. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/continuo_python_runtime/errors.py +0 -0
  136. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/continuo_python_runtime/hashing.py +0 -0
  137. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/continuo_python_runtime/lint.py +0 -0
  138. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/continuo_python_runtime/types.py +0 -0
  139. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/continuo_python_runtime/validation/__init__.py +0 -0
  140. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/continuo_python_runtime/validation/runner.py +0 -0
  141. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/continuo_python_runtime/validation/s3.py +0 -0
  142. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/contract/LICENSE +0 -0
  143. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/contract/NOTICE +0 -0
  144. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/contract/README.md +0 -0
  145. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/contract/continuo_engine_contract/__init__.py +0 -0
  146. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/contract/continuo_engine_contract/config.py +0 -0
  147. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/contract/continuo_engine_contract/port.py +0 -0
  148. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/contract/continuo_engine_contract/result.py +0 -0
  149. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/contract/continuo_engine_contract/sql.py +0 -0
  150. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/contract/continuo_engine_contract/types.py +0 -0
  151. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/contract/pyproject.toml +0 -0
  152. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0/contract}/tests/__init__.py +0 -0
  153. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/contract/tests/test_config.py +0 -0
  154. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/contract/tests/test_contract_types.py +0 -0
  155. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/contract/tests/test_port.py +0 -0
  156. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/contract/tests/test_result.py +0 -0
  157. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/contract/tests/test_sql.py +0 -0
  158. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/scripts/security-scan.sh +0 -0
  159. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/template/.github/workflows/release.yml +0 -0
  160. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/template/Dockerfile.pip +0 -0
  161. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/template/README.md +0 -0
  162. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/template/contracts/example_csv.yml +0 -0
  163. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/template/requirements.lock +0 -0
  164. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/template/scripts/example.py +0 -0
  165. {continuo_python_runtime-0.6.0/tests/contract → continuo_python_runtime-0.8.0/tests}/__init__.py +0 -0
  166. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/tests/conftest.py +0 -0
  167. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/tests/smoke/node_smoke/contracts/smoke.yml +0 -0
  168. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/tests/smoke/node_smoke/scripts/smoke.py +0 -0
  169. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/tests/smoke/postgres-stack/docker-compose.yml +0 -0
  170. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/tests/smoke/trino-stack/catalog/iceberg.properties +0 -0
  171. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/tests/smoke/trino-stack/docker-compose.yml +0 -0
  172. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/tests/test_cli.py +0 -0
  173. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/tests/test_closure.py +0 -0
  174. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/tests/test_conform.py +0 -0
  175. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/tests/test_context.py +0 -0
  176. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/tests/test_csv_loader.py +0 -0
  177. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/tests/test_csv_readers_https.py +0 -0
  178. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/tests/test_csv_readers_integration.py +0 -0
  179. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/tests/test_csv_source.py +0 -0
  180. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/tests/test_errors.py +0 -0
  181. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/tests/test_hashing.py +0 -0
  182. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/tests/test_image_smoke_validation.py +0 -0
  183. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/tests/test_kind_registries.py +0 -0
  184. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/tests/test_lint.py +0 -0
  185. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/tests/test_no_legacy_names.py +0 -0
  186. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/tests/test_package.py +0 -0
  187. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/tests/test_types.py +0 -0
  188. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/tests/test_validation_runner.py +0 -0
  189. {continuo_python_runtime-0.6.0 → continuo_python_runtime-0.8.0}/tests/test_validation_s3.py +0 -0
@@ -0,0 +1,117 @@
1
+ ---
2
+ name: ddd-architecture-auditor
3
+ description: >-
4
+ Use to audit code for DDD and Clean Architecture compliance in this repo's
5
+ engine adapters: layer dependency direction, domain purity, port placement,
6
+ thin use cases, SQL/identifier discipline, and repo conventions (changelog,
7
+ pins, test naming). Dispatch before merging a branch or whenever asked to
8
+ review architecture. Read-only: it writes a findings document the main agent
9
+ can act on; it never edits source.
10
+ tools: Read, Grep, Glob, Bash, Write
11
+ model: inherit
12
+ ---
13
+
14
+ # Role
15
+
16
+ You are a Domain-Driven Design and Clean Architecture auditor for the
17
+ `continuo-python-runtime` repo. You inspect code, find architecture violations,
18
+ and write a structured report so the main agent can fix them. You do not modify
19
+ source code. Your only output artifact is the report.
20
+
21
+ # Scope
22
+
23
+ Default to the current branch's diff against `main`:
24
+
25
+ ```
26
+ git diff main...HEAD --name-only
27
+ git diff main...HEAD
28
+ ```
29
+
30
+ Audit the whole tree only when the caller asks for a full sweep. State the
31
+ scope you chose at the top of the report.
32
+
33
+ # What to check
34
+
35
+ Report a finding only when you can point at a real `file:line` you have opened
36
+ and confirmed. Do not flag hypotheticals.
37
+
38
+ ## Layer 1: generic DDD / Clean Architecture
39
+
40
+ - **Dependency direction.** The arrow runs infrastructure -> application ->
41
+ domain. Flag any inward-pointing import.
42
+ - **Domain purity.** Domain code has no infrastructure concerns: no database,
43
+ object-store or dataframe clients, no serialization or framework types.
44
+ - **Ports and adapters.** A port is declared by the layer that consumes it;
45
+ implementations live outside it. Flag an implementation that leaks
46
+ engine-specific types through the port.
47
+ - **Thin use cases.** Use cases validate, then orchestrate through ports. Flag
48
+ business rules hiding in infrastructure, or SQL/engine detail in use cases.
49
+ - **SOLID.** Flag a class with more than one reason to change, a use case that
50
+ switches on engine type instead of depending on an abstraction, and an
51
+ interface too wide for its consumers.
52
+
53
+ ## Layer 2: rules for `adapters/duckdb` (and any new adapter)
54
+
55
+ 1. **Layering by import.** `domain/` imports none of `duckdb`, `psycopg2`,
56
+ `pyarrow`, `boto3`, `application`, `infrastructure`. `application/` imports
57
+ neither `infrastructure` nor `duckdb`. `infrastructure/` imports only
58
+ `domain` and `application.ports`. Only the top-level `adapter.py`
59
+ (composition root) may import every layer. Verify with grep over each
60
+ layer's files.
61
+ 2. **Port placement.** The `LakeGateway` port and `LakeConflictError` live in
62
+ `application/ports.py`. `infrastructure` only implements them.
63
+ 3. **Validate before acting.** Every public use case validates its inputs
64
+ (types, layout, single-read gate) before the first gateway call.
65
+ 4. **SQL discipline.** Identifiers go through `quote_identifier`, strings
66
+ through `sql_literal`, in `infrastructure/ddl.py` only. Flag any SQL built
67
+ by string formatting elsewhere, and any author-supplied text that reaches
68
+ SQL unquoted.
69
+ 5. **Logging.** Standard `logging` only, never `print`; no SQL that can carry
70
+ a secret is logged (the ATTACH and secret statements).
71
+ 6. **Repo conventions (CLAUDE.md).** `CHANGELOG.md` has an `[Unreleased]`
72
+ entry for user-facing change; in-repo and third-party pins are exact; the
73
+ package is registered in the workspace, `scripts/check_version_bumps.py`,
74
+ `tests/test_image_requirements_sync.py` and the CI/image workflows; test
75
+ module basenames are unique across the repo.
76
+ 7. **Doc currency.** `docs/boundary-contract.md` documents every `config` key
77
+ the adapter accepts.
78
+
79
+ # Report
80
+
81
+ Write to `docs/ddd-violations/<YYYY-MM-DD-HHMM>-ddd-violations.md` (create the
82
+ directory; timestamp from `date +%Y-%m-%d-%H%M`).
83
+
84
+ ```markdown
85
+ # DDD / Clean Architecture Audit: <YYYY-MM-DD HH:MM>
86
+
87
+ **Scope:** <branch diff vs main | full tree>
88
+ **Branch / commit:** <branch> @ <short-sha>
89
+ **Summary:** <N> blockers, <N> should-fix, <N> nits
90
+
91
+ ## Findings
92
+
93
+ ### [BLOCKER] <short title>
94
+ - **Where:** `path/to/file.py:123`
95
+ - **Rule:** <Layer 1 principle or Layer 2 rule number>
96
+ - **Problem:** <what is wrong and why>
97
+ - **Suggested fix:** <concrete change>
98
+
99
+ ### [SHOULD-FIX] ...
100
+ ### [NIT] ...
101
+
102
+ ## Clean
103
+ - <checks that passed, so their absence above is explicit>
104
+ ```
105
+
106
+ Severity: **BLOCKER** = breaks the dependency arrow, domain purity, or a
107
+ CI-enforced rule; **SHOULD-FIX** = boundary erosion or convention drift that
108
+ will bite later; **NIT** = stylistic.
109
+
110
+ # Hard constraints
111
+
112
+ - Never edit, create, or scaffold source code. The report is your only write.
113
+ - Never claim a violation is fixed; you only diagnose.
114
+ - Every finding cites a real `file:line` you have opened and confirmed.
115
+ - If you find nothing, still write the report with an empty Findings section and
116
+ a populated Clean list.
117
+ - End your reply with the report's path and the summary counts.
@@ -36,6 +36,7 @@ body:
36
36
  - contract (continuo-engine-contract)
37
37
  - adapters/postgres
38
38
  - adapters/trino
39
+ - adapters/duckdb
39
40
  - template
40
41
  - CI (.github/workflows)
41
42
  - Not sure
@@ -7,8 +7,8 @@
7
7
  version: 2
8
8
 
9
9
  updates:
10
- # The uv workspace: this package (root), contract/, and the two adapters. All
11
- # four share one lockfile, but Dependabot still needs one entry per pyproject.toml
10
+ # The uv workspace: this package (root), contract/, and the three adapters. All
11
+ # five share one lockfile, but Dependabot still needs one entry per pyproject.toml
12
12
  # directory to see each package's own dependency declarations.
13
13
  - package-ecosystem: uv
14
14
  directories:
@@ -16,6 +16,7 @@ updates:
16
16
  - /contract
17
17
  - /adapters/postgres
18
18
  - /adapters/trino
19
+ - /adapters/duckdb
19
20
  schedule:
20
21
  interval: weekly
21
22
  open-pull-requests-limit: 5
@@ -46,7 +47,7 @@ updates:
46
47
  commit-message:
47
48
  prefix: chore(ci)
48
49
 
49
- # Dockerfile.postgres and Dockerfile.trino, the published engine images. Most
50
+ # Dockerfile.postgres, Dockerfile.trino and Dockerfile.duckdb, the published engine images. Most
50
51
  # base-image CVEs are fixed by moving to a newer tag.
51
52
  - package-ecosystem: docker
52
53
  directory: /
@@ -22,6 +22,8 @@ jobs:
22
22
  run: uv run --package continuo-postgres-adapter mypy adapters/postgres/continuo_postgres_adapter
23
23
  - name: Types (trino adapter)
24
24
  run: uv run --package continuo-trino-adapter mypy adapters/trino/continuo_trino_adapter
25
+ - name: Types (duckdb adapter)
26
+ run: uv run --package continuo-duckdb-adapter mypy adapters/duckdb/continuo_duckdb_adapter
25
27
  # `-m "not image"` deselects tests/test_image_smoke_validation.py, which
26
28
  # needs a built engine image and the env naming it. Those tests run in
27
29
  # images.yml's smoke jobs, where an image actually exists. `and not
@@ -29,14 +31,19 @@ jobs:
29
31
  # need a real minio backend (started via `docker run` by the
30
32
  # `minio_container` fixture, not docker-compose) -- those run in the
31
33
  # dedicated step below, on the same runner, where docker is available.
34
+ # That step selects BY MARKER, never by file name: an explicit file list
35
+ # would silently drop every new integration test added elsewhere in
36
+ # tests/ (tests/test_ci_test_coverage.py enforces this).
32
37
  - name: Tests (runtime)
33
38
  run: uv run pytest --cov=continuo_python_runtime -m "not image and not integration" -v
34
39
  - name: Tests (runtime, integration)
35
- run: uv run pytest tests/test_csv_readers_integration.py tests/test_validation_runner.py -m integration -v
40
+ run: uv run pytest tests -m integration -v
36
41
  - name: Tests (contract)
37
42
  run: uv run pytest contract/tests -v
38
43
  - name: Tests (adapter units)
39
44
  run: uv run pytest adapters/postgres/tests adapters/trino/tests -m "not integration" -v
45
+ - name: Tests (duckdb adapter units)
46
+ run: uv run pytest adapters/duckdb/tests -m "not integration" -v
40
47
 
41
48
  integration-postgres:
42
49
  runs-on: ubuntu-latest
@@ -65,3 +72,17 @@ jobs:
65
72
  - name: Tear down trino stack
66
73
  if: always()
67
74
  run: docker compose -f tests/smoke/trino-stack/docker-compose.yml down -v
75
+
76
+ integration-duckdb:
77
+ runs-on: ubuntu-latest
78
+ steps:
79
+ - uses: actions/checkout@v7
80
+ - uses: astral-sh/setup-uv@v7
81
+ - run: uv sync --all-packages --all-groups
82
+ - name: Start duckdb stack
83
+ run: docker compose -f tests/smoke/duckdb-stack/docker-compose.yml up -d --wait
84
+ - name: Tests (duckdb integration)
85
+ run: uv run pytest adapters/duckdb/tests -m integration -v
86
+ - name: Tear down duckdb stack
87
+ if: always()
88
+ run: docker compose -f tests/smoke/duckdb-stack/docker-compose.yml down -v
@@ -0,0 +1,188 @@
1
+ name: image-build
2
+
3
+ # Reusable: build ONE engine image for ONE architecture on a native runner (no
4
+ # QEMU), in one of two shapes:
5
+ #
6
+ # push: false `docker save` tarball uploaded as artifact
7
+ # `<prefix>-<engine>-<arch>` (file image.tar). Nothing leaves
8
+ # the run. PR smoke and dry runs.
9
+ # push: true pushed to ghcr BY DIGEST ONLY: no tag of any kind, so the
10
+ # candidate cannot be mistaken for a release. The digest is
11
+ # uploaded as artifact `<prefix>-<engine>-<arch>` (file
12
+ # `digest`, content `sha256:...`) for the verify and promote
13
+ # jobs. Candidates that never get promoted are garbage.
14
+ #
15
+ # wheel_source feeds the Dockerfile's WHEEL_SOURCE:
16
+ # wheelhouse the `dist` artifact's wheels (PR, dry run, pre-upload gate)
17
+ # pypi the pins from real PyPI (a real release)
18
+ # testpypi the pins from TestPyPI, first-party only (a -test rehearsal)
19
+ #
20
+ # version_suffix re-applies the rehearsal version rewrite to this job's own
21
+ # checkout so image-requirements-<engine>.txt names the versions in `dist`
22
+ # (see scripts/apply_rehearsal_version.py). Empty for a real release.
23
+ on:
24
+ workflow_call:
25
+ inputs:
26
+ engine:
27
+ type: string
28
+ required: true
29
+ arch:
30
+ description: amd64 | arm64
31
+ type: string
32
+ required: true
33
+ wheel_source:
34
+ description: wheelhouse | pypi | testpypi
35
+ type: string
36
+ required: true
37
+ push:
38
+ type: boolean
39
+ default: false
40
+ version_suffix:
41
+ type: string
42
+ default: ""
43
+ artifact_prefix:
44
+ description: artifact name prefix, e.g. pr, preflight, candidate
45
+ type: string
46
+ required: true
47
+ dist_artifact:
48
+ type: string
49
+ default: dist
50
+ outputs:
51
+ artifact:
52
+ description: name of the artifact holding image.tar or digest
53
+ value: ${{ jobs.build.outputs.artifact }}
54
+
55
+ # No `permissions:` here on purpose: a called workflow can only NARROW the
56
+ # caller's token, so the caller grants `contents: read` (+ `packages: write`
57
+ # when push is true).
58
+
59
+ jobs:
60
+ build:
61
+ name: image ${{ inputs.engine }} ${{ inputs.arch }}
62
+ runs-on: ${{ inputs.arch == 'arm64' && 'ubuntu-24.04-arm' || 'ubuntu-latest' }}
63
+ outputs:
64
+ artifact: ${{ inputs.artifact_prefix }}-${{ inputs.engine }}-${{ inputs.arch }}
65
+ env:
66
+ ARTIFACT: ${{ inputs.artifact_prefix }}-${{ inputs.engine }}-${{ inputs.arch }}
67
+ IMAGE_NAME: ghcr.io/${{ github.repository_owner }}/continuo-python-runtime-${{ inputs.engine }}
68
+ REQ: image-requirements-${{ inputs.engine }}.txt
69
+ steps:
70
+ - uses: actions/checkout@v7
71
+ - name: Assert the runner architecture
72
+ run: |
73
+ want="${{ inputs.arch == 'arm64' && 'aarch64' || 'x86_64' }}"
74
+ test "$(uname -m)" = "$want" || { echo "::error::runner is $(uname -m), expected $want"; exit 1; }
75
+ - name: Apply the rehearsal version
76
+ if: inputs.version_suffix != ''
77
+ run: python3 scripts/apply_rehearsal_version.py --suffix "${{ inputs.version_suffix }}"
78
+ - name: Fetch the built wheels into the wheelhouse
79
+ if: inputs.wheel_source == 'wheelhouse'
80
+ uses: actions/download-artifact@v8
81
+ with:
82
+ name: ${{ inputs.dist_artifact }}
83
+ path: wheelhouse
84
+ - uses: actions/setup-python@v7
85
+ if: inputs.wheel_source != 'wheelhouse'
86
+ with:
87
+ # The pinned distributions declare Requires-Python >= 3.14, so the
88
+ # host-side probe below must run under 3.14 or pip rejects them even
89
+ # once published. (The image build itself runs inside python:3.14-slim.)
90
+ python-version: "3.14"
91
+ # Ordering between the upload and this build is a `needs:` edge in
92
+ # publish-pypi.yml, but an index can lag its own upload, so poll. The
93
+ # probe runs with --no-cache-dir: the simple index is served with
94
+ # `Cache-Control: max-age=600`, which pip's HTTP cache honours, so one
95
+ # early stale fetch would otherwise be replayed for 10 minutes, exactly
96
+ # this loop's whole budget (seen in the first TestPyPI rehearsal).
97
+ - name: Wait until the pinned versions are installable
98
+ if: inputs.wheel_source != 'wheelhouse'
99
+ run: |
100
+ set -eu
101
+ if [ "${{ inputs.wheel_source }}" = "testpypi" ]; then
102
+ # First-party only, from TestPyPI: it hosts junk copies of real names.
103
+ pull() { python -m pip download --no-cache-dir --no-deps --only-binary=:all: --index-url https://test.pypi.org/simple/ \
104
+ --dest /tmp/probe -r "$REQ"; }
105
+ else
106
+ pull() { python -m pip download --no-cache-dir --dest /tmp/probe -r "$REQ"; }
107
+ fi
108
+ for i in $(seq 1 30); do
109
+ if pull >/tmp/probe.log 2>&1; then
110
+ echo "installable"; exit 0
111
+ fi
112
+ echo "attempt $i: not yet on the index; sleeping"; sleep 20
113
+ done
114
+ echo "pinned versions never became installable"; cat /tmp/probe.log; exit 1
115
+ # TestPyPI versions are unique per run, so the files served must be the
116
+ # files this run built. Compare them byte for byte.
117
+ - name: Fetch dist to compare with the index
118
+ if: inputs.wheel_source == 'testpypi'
119
+ uses: actions/download-artifact@v8
120
+ with:
121
+ name: ${{ inputs.dist_artifact }}
122
+ path: dist
123
+ - name: Index bytes equal the bytes built in this run
124
+ if: inputs.wheel_source == 'testpypi'
125
+ run: |
126
+ set -eu
127
+ n=0
128
+ for f in /tmp/probe/*.whl; do
129
+ b="$(basename "$f")"
130
+ test -f "dist/$b" || { echo "::error::$b is on the index but was not built in this run"; exit 1; }
131
+ cmp "$f" "dist/$b" || { echo "::error::$b on the index differs from this run's build"; exit 1; }
132
+ n=$((n + 1))
133
+ done
134
+ test "$n" -eq 3 || { echo "::error::expected 3 first-party wheels, compared $n"; exit 1; }
135
+ echo "compared $n wheels: identical"
136
+ - uses: docker/setup-buildx-action@v4
137
+ - name: Log in to ghcr.io
138
+ if: inputs.push
139
+ uses: docker/login-action@v4
140
+ with:
141
+ registry: ghcr.io
142
+ username: ${{ github.actor }}
143
+ password: ${{ secrets.GITHUB_TOKEN }}
144
+ - name: Build the image (tarball)
145
+ if: "!inputs.push"
146
+ uses: docker/build-push-action@v7
147
+ with:
148
+ context: .
149
+ file: Dockerfile.${{ inputs.engine }}
150
+ platforms: linux/${{ inputs.arch }}
151
+ build-args: WHEEL_SOURCE=${{ inputs.wheel_source }}
152
+ load: true
153
+ tags: cpr-smoke-${{ inputs.engine }}
154
+ - name: Save the image
155
+ if: "!inputs.push"
156
+ run: |
157
+ mkdir -p out
158
+ docker save cpr-smoke-${{ inputs.engine }} -o out/image.tar
159
+ # provenance/sbom off: with them on, a pushed digest is an index that
160
+ # also holds an attestation manifest, and merging two of those into one
161
+ # multi-arch tag nests indexes. Off, the digest IS the single-platform
162
+ # image manifest, which is exactly what the verify job pulls.
163
+ - name: Build and push by digest
164
+ id: push
165
+ if: inputs.push
166
+ uses: docker/build-push-action@v7
167
+ with:
168
+ context: .
169
+ file: Dockerfile.${{ inputs.engine }}
170
+ platforms: linux/${{ inputs.arch }}
171
+ build-args: WHEEL_SOURCE=${{ inputs.wheel_source }}
172
+ provenance: false
173
+ sbom: false
174
+ outputs: type=image,name=${{ env.IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true
175
+ - name: Record the digest
176
+ if: inputs.push
177
+ run: |
178
+ mkdir -p out
179
+ digest="${{ steps.push.outputs.digest }}"
180
+ case "$digest" in sha256:*) ;; *) echo "::error::no digest from the push"; exit 1;; esac
181
+ printf '%s' "$digest" > out/digest
182
+ echo "pushed ${IMAGE_NAME}@${digest} (candidate, untagged)" >> "$GITHUB_STEP_SUMMARY"
183
+ - uses: actions/upload-artifact@v7
184
+ with:
185
+ name: ${{ env.ARTIFACT }}
186
+ path: out/
187
+ retention-days: 3
188
+ overwrite: true
@@ -0,0 +1,255 @@
1
+ name: image-smoke
2
+
3
+ # Reusable: run the engine image smoke checks against ONE image on a native
4
+ # runner of the matching architecture. This is the single definition of "the
5
+ # image works", used by images.yml (PR, tarball built from the commit) and by
6
+ # publish-pypi.yml (pre-upload gate, and the verification of the candidate
7
+ # digests actually pushed to ghcr), so the PR path and the release path cannot
8
+ # drift. Per engine it runs:
9
+ #
10
+ # 1. tests/test_image_smoke_validation.py -m image (3 tests)
11
+ # 2. the smoke node tests/smoke/node_smoke against a REAL warehouse stack
12
+ # (tests/smoke/<engine>-stack), asserting the success sentinel and 3 rows
13
+ # 3. duckdb only: the baked extensions load offline as uid 65532
14
+ #
15
+ # mode=tar the image is `image.tar` in artifact `image_artifact`
16
+ # mode=digest the image is `<registry_image>@<digest>`, the digest being the
17
+ # content of file `digest` in artifact `image_artifact`; it is
18
+ # pulled from ghcr, so what is tested is what was pushed.
19
+ #
20
+ # A cell that cannot run must fail loudly: the image's architecture is asserted
21
+ # against the runner's before anything else is tested.
22
+ #
23
+ # No `permissions:` here on purpose (see image-build.yml): the caller grants
24
+ # `contents: read`, plus `packages: read` for mode=digest.
25
+ on:
26
+ workflow_call:
27
+ inputs:
28
+ engine:
29
+ description: postgres | trino | duckdb
30
+ type: string
31
+ required: true
32
+ arch:
33
+ description: amd64 | arm64
34
+ type: string
35
+ required: true
36
+ mode:
37
+ description: tar | digest
38
+ type: string
39
+ required: true
40
+ image_artifact:
41
+ type: string
42
+ required: true
43
+ registry_image:
44
+ description: ghcr.io/<owner>/continuo-python-runtime-<engine> (mode=digest)
45
+ type: string
46
+ default: ""
47
+
48
+ jobs:
49
+ smoke:
50
+ name: smoke ${{ inputs.engine }} ${{ inputs.arch }}
51
+ runs-on: ${{ inputs.arch == 'arm64' && 'ubuntu-24.04-arm' || 'ubuntu-latest' }}
52
+ env:
53
+ DUCKDB_CATALOG_HOST: localhost
54
+ DUCKDB_CATALOG_PORT: "15599"
55
+ DUCKDB_CATALOG_DB: catalog
56
+ DUCKDB_CATALOG_USER: continuo
57
+ DUCKDB_CATALOG_PASSWORD: continuo
58
+ DUCKDB_DATA_PATH: s3://warehouse/lake/
59
+ DUCKDB_S3_ENDPOINT: localhost:19100
60
+ DUCKDB_S3_ACCESS_KEY_ID: minioadmin
61
+ DUCKDB_S3_SECRET_ACCESS_KEY: minioadmin
62
+ DUCKDB_S3_URL_STYLE: path
63
+ DUCKDB_S3_USE_SSL: "false"
64
+ steps:
65
+ - uses: actions/checkout@v7
66
+ - name: Assert the runner architecture
67
+ run: |
68
+ want="${{ inputs.arch == 'arm64' && 'aarch64' || 'x86_64' }}"
69
+ test "$(uname -m)" = "$want" || { echo "::error::runner is $(uname -m), expected $want"; exit 1; }
70
+ - uses: actions/download-artifact@v8
71
+ with:
72
+ name: ${{ inputs.image_artifact }}
73
+ path: ${{ runner.temp }}/img
74
+ - name: Log in to ghcr.io
75
+ if: inputs.mode == 'digest'
76
+ uses: docker/login-action@v4
77
+ with:
78
+ registry: ghcr.io
79
+ username: ${{ github.actor }}
80
+ password: ${{ secrets.GITHUB_TOKEN }}
81
+ - name: Load or pull the image under test
82
+ run: |
83
+ set -eu
84
+ case "${{ inputs.mode }}" in
85
+ tar)
86
+ docker load -i "$RUNNER_TEMP/img/image.tar"
87
+ image="cpr-smoke-${{ inputs.engine }}"
88
+ ;;
89
+ digest)
90
+ digest="$(cat "$RUNNER_TEMP/img/digest")"
91
+ case "$digest" in sha256:*) ;; *) echo "::error::bad digest file"; exit 1;; esac
92
+ image="${{ inputs.registry_image }}@${digest}"
93
+ docker pull "$image"
94
+ ;;
95
+ *) echo "::error::mode must be tar or digest"; exit 1;;
96
+ esac
97
+ echo "IMAGE=$image" >> "$GITHUB_ENV"
98
+ echo "testing $image" >> "$GITHUB_STEP_SUMMARY"
99
+ - name: Assert the image architecture
100
+ run: |
101
+ got="$(docker image inspect "$IMAGE" --format '{{.Os}}/{{.Architecture}}')"
102
+ test "$got" = "linux/${{ inputs.arch }}" || { echo "::error::image is $got, expected linux/${{ inputs.arch }}"; exit 1; }
103
+ - uses: astral-sh/setup-uv@v7
104
+ - name: Sync
105
+ run: uv sync --all-packages --all-groups
106
+
107
+ # ---- postgres -------------------------------------------------------
108
+ - name: Image smoke tests (postgres)
109
+ if: inputs.engine == 'postgres'
110
+ env:
111
+ VALIDATION_IMAGE_UNDER_TEST: ${{ env.IMAGE }}
112
+ VALIDATION_IMAGE_ENGINE: postgres
113
+ # PostgresAdapter.required_env()[0]
114
+ VALIDATION_IMAGE_REQUIRED_ENV: POSTGRES_HOST
115
+ run: uv run pytest tests/test_image_smoke_validation.py -m image -v
116
+ - name: Start postgres stack
117
+ if: inputs.engine == 'postgres'
118
+ run: docker compose -f tests/smoke/postgres-stack/docker-compose.yml up -d --wait
119
+ - name: Seed source table (postgres)
120
+ if: inputs.engine == 'postgres'
121
+ run: |
122
+ docker compose -f tests/smoke/postgres-stack/docker-compose.yml exec -T postgres \
123
+ psql -U continuo -d warehouse -c \
124
+ "CREATE SCHEMA analytics; CREATE TABLE analytics.src(id int); INSERT INTO analytics.src VALUES (1),(2),(3);"
125
+ - name: Run smoke node (postgres)
126
+ if: inputs.engine == 'postgres'
127
+ run: |
128
+ docker run --rm --network host \
129
+ -v "$PWD/tests/smoke/node_smoke:/app" \
130
+ -e NODE_ID=python-node.smoke.analytics.smoke \
131
+ -e TABLE_NAME=smoke \
132
+ -e TARGET_SCHEMA=analytics \
133
+ -e POSTGRES_HOST=localhost \
134
+ -e POSTGRES_PORT=15499 \
135
+ -e POSTGRES_DB=warehouse \
136
+ -e POSTGRES_USER=continuo \
137
+ -e POSTGRES_PASSWORD=continuo \
138
+ "$IMAGE" | tee "$RUNNER_TEMP/smoke.out"
139
+ test "${PIPESTATUS[0]}" -eq 0
140
+ shell: bash
141
+ - name: Assert success sentinel and row count (postgres)
142
+ if: inputs.engine == 'postgres'
143
+ run: |
144
+ grep -q '"status":"success"' "$RUNNER_TEMP/smoke.out"
145
+ count=$(docker compose -f tests/smoke/postgres-stack/docker-compose.yml exec -T postgres \
146
+ psql -U continuo -d warehouse -t -c "select count(*) from analytics.smoke" | tr -d '[:space:]')
147
+ test "$count" = "3"
148
+ - name: Tear down postgres stack
149
+ if: inputs.engine == 'postgres' && always()
150
+ run: docker compose -f tests/smoke/postgres-stack/docker-compose.yml down -v
151
+
152
+ # ---- trino ----------------------------------------------------------
153
+ - name: Image smoke tests (trino)
154
+ if: inputs.engine == 'trino'
155
+ env:
156
+ VALIDATION_IMAGE_UNDER_TEST: ${{ env.IMAGE }}
157
+ VALIDATION_IMAGE_ENGINE: trino
158
+ # TrinoAdapter.required_env()[0]
159
+ VALIDATION_IMAGE_REQUIRED_ENV: TRINO_HOST
160
+ run: uv run pytest tests/test_image_smoke_validation.py -m image -v
161
+ - name: Start trino stack
162
+ if: inputs.engine == 'trino'
163
+ run: docker compose -f tests/smoke/trino-stack/docker-compose.yml up -d --wait
164
+ - name: Seed source table (trino)
165
+ if: inputs.engine == 'trino'
166
+ run: |
167
+ docker exec "$(docker compose -f tests/smoke/trino-stack/docker-compose.yml ps -q trino)" \
168
+ trino --execute "CREATE SCHEMA IF NOT EXISTS iceberg.analytics; CREATE TABLE iceberg.analytics.src(id int); INSERT INTO iceberg.analytics.src VALUES 1,2,3"
169
+ - name: Run smoke node (trino)
170
+ if: inputs.engine == 'trino'
171
+ run: |
172
+ docker run --rm \
173
+ -v "$PWD/tests/smoke/node_smoke:/app" \
174
+ -e NODE_ID=python-node.smoke.analytics.smoke \
175
+ -e TABLE_NAME=smoke \
176
+ -e TARGET_SCHEMA=analytics \
177
+ -e TRINO_HOST=host.docker.internal \
178
+ -e TRINO_PORT=18080 \
179
+ -e TRINO_CATALOG=iceberg \
180
+ -e TRINO_USER=continuo \
181
+ --add-host=host.docker.internal:host-gateway \
182
+ "$IMAGE" | tee "$RUNNER_TEMP/smoke.out"
183
+ test "${PIPESTATUS[0]}" -eq 0
184
+ shell: bash
185
+ - name: Assert success sentinel and row count (trino)
186
+ if: inputs.engine == 'trino'
187
+ run: |
188
+ grep -q '"status":"success"' "$RUNNER_TEMP/smoke.out"
189
+ count=$(docker exec "$(docker compose -f tests/smoke/trino-stack/docker-compose.yml ps -q trino)" \
190
+ trino --execute "select count(*) from iceberg.analytics.smoke" | tr -d '[:space:]"')
191
+ test "$count" = "3"
192
+ - name: Tear down trino stack
193
+ if: inputs.engine == 'trino' && always()
194
+ run: docker compose -f tests/smoke/trino-stack/docker-compose.yml down -v
195
+
196
+ # ---- duckdb ---------------------------------------------------------
197
+ - name: Image smoke tests (duckdb)
198
+ if: inputs.engine == 'duckdb'
199
+ env:
200
+ VALIDATION_IMAGE_UNDER_TEST: ${{ env.IMAGE }}
201
+ VALIDATION_IMAGE_ENGINE: duckdb
202
+ # DuckDBAdapter.required_env()[0]
203
+ VALIDATION_IMAGE_REQUIRED_ENV: DUCKDB_CATALOG_HOST
204
+ run: uv run pytest tests/test_image_smoke_validation.py -m image -v
205
+ # The extensions are native binaries per architecture, baked at build
206
+ # time. No network, the non-root user: exactly how the executor runs it.
207
+ - name: Extensions load offline as the non-root user (duckdb)
208
+ if: inputs.engine == 'duckdb'
209
+ run: |
210
+ docker run --rm --network none --user 65532:65532 --entrypoint python "$IMAGE" -c \
211
+ "import os; from continuo_duckdb_adapter.infrastructure.extensions import check_offline; check_offline(os.environ['DUCKDB_EXTENSION_DIRECTORY'])"
212
+ - name: Start duckdb stack
213
+ if: inputs.engine == 'duckdb'
214
+ run: docker compose -f tests/smoke/duckdb-stack/docker-compose.yml up -d --wait
215
+ - name: Seed source table (duckdb)
216
+ if: inputs.engine == 'duckdb'
217
+ run: |
218
+ uv run python - <<'PY'
219
+ import pyarrow as pa
220
+ from continuo_duckdb_adapter.adapter import DuckDBAdapter
221
+
222
+ a = DuckDBAdapter.from_env()
223
+ a.ensure_table("analytics", "src", [{"name": "id", "type": "INTEGER", "nullable": True}], config={})
224
+ a.load("analytics", "src", pa.table({"id": pa.array([1, 2, 3], pa.int32())}))
225
+ a.close()
226
+ PY
227
+ - name: Run smoke node (duckdb)
228
+ if: inputs.engine == 'duckdb'
229
+ run: |
230
+ docker run --rm --network host \
231
+ -v "$PWD/tests/smoke/node_smoke:/app" \
232
+ -e NODE_ID=python-node.smoke.analytics.smoke \
233
+ -e TABLE_NAME=smoke \
234
+ -e TARGET_SCHEMA=analytics \
235
+ -e DUCKDB_CATALOG_HOST -e DUCKDB_CATALOG_PORT -e DUCKDB_CATALOG_DB \
236
+ -e DUCKDB_CATALOG_USER -e DUCKDB_CATALOG_PASSWORD -e DUCKDB_DATA_PATH \
237
+ -e DUCKDB_S3_ENDPOINT -e DUCKDB_S3_ACCESS_KEY_ID -e DUCKDB_S3_SECRET_ACCESS_KEY \
238
+ -e DUCKDB_S3_URL_STYLE -e DUCKDB_S3_USE_SSL \
239
+ "$IMAGE" | tee "$RUNNER_TEMP/smoke.out"
240
+ test "${PIPESTATUS[0]}" -eq 0
241
+ shell: bash
242
+ - name: Assert success sentinel and row count (duckdb)
243
+ if: inputs.engine == 'duckdb'
244
+ run: |
245
+ grep -q '"status":"success"' "$RUNNER_TEMP/smoke.out"
246
+ uv run python - <<'PY'
247
+ from continuo_duckdb_adapter.adapter import DuckDBAdapter
248
+
249
+ a = DuckDBAdapter.from_env()
250
+ assert a.fetch("SELECT count(*) AS n FROM analytics.smoke").to_pylist() == [{"n": 3}]
251
+ a.close()
252
+ PY
253
+ - name: Tear down duckdb stack
254
+ if: inputs.engine == 'duckdb' && always()
255
+ run: docker compose -f tests/smoke/duckdb-stack/docker-compose.yml down -v