continuo-python-runtime 0.11.0__tar.gz → 0.12.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 (234) hide show
  1. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/.github/workflows/image-build.yml +7 -1
  2. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/CHANGELOG.md +142 -0
  3. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/CLAUDE.md +5 -3
  4. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/CONTRIBUTING.md +1 -1
  5. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/Dockerfile.duckdb +8 -21
  6. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/Dockerfile.postgres +10 -33
  7. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/Dockerfile.trino +10 -33
  8. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/PKG-INFO +129 -12
  9. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/README.md +126 -10
  10. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/adapters/duckdb/continuo_duckdb_adapter/application/warehouse.py +2 -0
  11. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/adapters/duckdb/pyproject.toml +2 -2
  12. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/adapters/duckdb/tests/test_adapter_duckdb_wiring.py +4 -0
  13. continuo_python_runtime-0.12.0/adapters/duckdb/tests/test_integration_duckdb_entrypoint.py +185 -0
  14. continuo_python_runtime-0.12.0/adapters/duckdb/tests/test_integration_duckdb_seed_preflight.py +154 -0
  15. continuo_python_runtime-0.12.0/adapters/postgres/README.md +87 -0
  16. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/adapters/postgres/continuo_postgres_adapter/adapter.py +170 -1
  17. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/adapters/postgres/pyproject.toml +2 -2
  18. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/adapters/postgres/tests/test_adapter_postgres_validation.py +22 -1
  19. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/adapters/postgres/tests/test_integration_postgres_conformance.py +5 -0
  20. continuo_python_runtime-0.12.0/adapters/postgres/tests/test_integration_postgres_dependents.py +701 -0
  21. continuo_python_runtime-0.12.0/adapters/postgres/tests/test_integration_postgres_entrypoint.py +209 -0
  22. continuo_python_runtime-0.12.0/adapters/postgres/tests/test_integration_postgres_seed_preflight.py +141 -0
  23. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/adapters/trino/README.md +30 -0
  24. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/adapters/trino/continuo_trino_adapter/adapter.py +111 -1
  25. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/adapters/trino/pyproject.toml +7 -2
  26. continuo_python_runtime-0.12.0/adapters/trino/tests/test_adapter_trino_dependents.py +224 -0
  27. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/adapters/trino/tests/test_adapter_trino_validation.py +4 -0
  28. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/adapters/trino/tests/test_integration_trino_conformance.py +9 -0
  29. continuo_python_runtime-0.12.0/adapters/trino/tests/test_integration_trino_entrypoint.py +204 -0
  30. continuo_python_runtime-0.12.0/adapters/trino/tests/test_integration_trino_seed_preflight.py +105 -0
  31. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/continuo_python_runtime/cli.py +6 -0
  32. continuo_python_runtime-0.12.0/continuo_python_runtime/content_check.py +73 -0
  33. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/continuo_python_runtime/contract/kinds.py +10 -2
  34. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/continuo_python_runtime/contract/loader.py +52 -2
  35. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/continuo_python_runtime/contract/merge.py +26 -45
  36. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/continuo_python_runtime/contract/model.py +1 -0
  37. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/continuo_python_runtime/contract/paths.py +33 -0
  38. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/continuo_python_runtime/errors.py +13 -1
  39. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/continuo_python_runtime/harness.py +7 -1
  40. continuo_python_runtime-0.12.0/continuo_python_runtime/hash_inputs.py +63 -0
  41. continuo_python_runtime-0.12.0/continuo_python_runtime/hashing.py +93 -0
  42. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/continuo_python_runtime/lint.py +194 -6
  43. continuo_python_runtime-0.12.0/continuo_python_runtime/validation/preflight.py +410 -0
  44. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/continuo_python_runtime/validation/runner.py +20 -3
  45. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/contract/README.md +16 -5
  46. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/contract/continuo_engine_contract/port.py +70 -1
  47. continuo_python_runtime-0.12.0/contract/continuo_engine_contract/result.py +59 -0
  48. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/contract/continuo_engine_contract/testing/conformance.py +119 -1
  49. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/contract/pyproject.toml +1 -1
  50. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/contract/tests/test_conformance_suite.py +48 -1
  51. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/contract/tests/test_port.py +15 -0
  52. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/contract/tests/test_result.py +33 -0
  53. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/contract/tests/test_startup_conformance.py +13 -0
  54. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/docs/boundary-contract.md +63 -48
  55. continuo_python_runtime-0.12.0/image-requirements-duckdb.txt +3 -0
  56. continuo_python_runtime-0.12.0/image-requirements-postgres.txt +3 -0
  57. continuo_python_runtime-0.12.0/image-requirements-trino.txt +3 -0
  58. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/pyproject.toml +5 -2
  59. continuo_python_runtime-0.12.0/scripts/install_image_pins.py +137 -0
  60. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/scripts/verify_installed_packages.py +3 -0
  61. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/template/.github/workflows/release.yml +28 -3
  62. continuo_python_runtime-0.12.0/template/Dockerfile +17 -0
  63. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/template/Dockerfile.pip +6 -5
  64. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/template/README.md +30 -7
  65. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/tests/contract/test_loader.py +49 -0
  66. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/tests/contract/test_merge.py +172 -40
  67. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/tests/contract/test_wire_contract_write_modes.py +4 -0
  68. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/tests/fixtures/wire_contract_write_modes.yml +16 -1
  69. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/tests/smoke/trino-stack/docker-compose.yml +4 -1
  70. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/tests/test_cli.py +10 -0
  71. continuo_python_runtime-0.12.0/tests/test_entrypoint_shims.py +23 -0
  72. continuo_python_runtime-0.12.0/tests/test_harness_content_check.py +176 -0
  73. continuo_python_runtime-0.12.0/tests/test_hashing.py +196 -0
  74. continuo_python_runtime-0.12.0/tests/test_hashing_vectors.py +35 -0
  75. continuo_python_runtime-0.12.0/tests/test_install_image_pins.py +148 -0
  76. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/tests/test_kind_registries.py +2 -3
  77. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/tests/test_lint.py +77 -0
  78. continuo_python_runtime-0.12.0/tests/test_projection_vectors.py +34 -0
  79. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/tests/test_release_pipeline_structure.py +13 -3
  80. continuo_python_runtime-0.12.0/tests/test_template.py +498 -0
  81. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/tests/test_types.py +34 -0
  82. continuo_python_runtime-0.12.0/tests/test_validation_preflight.py +524 -0
  83. continuo_python_runtime-0.12.0/tests/test_verify_installed_packages.py +34 -0
  84. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/uv.lock +21 -5
  85. continuo_python_runtime-0.11.0/adapters/postgres/README.md +0 -43
  86. continuo_python_runtime-0.11.0/continuo_python_runtime/hashing.py +0 -94
  87. continuo_python_runtime-0.11.0/contract/continuo_engine_contract/result.py +0 -34
  88. continuo_python_runtime-0.11.0/image-requirements-duckdb.txt +0 -3
  89. continuo_python_runtime-0.11.0/image-requirements-postgres.txt +0 -3
  90. continuo_python_runtime-0.11.0/image-requirements-trino.txt +0 -3
  91. continuo_python_runtime-0.11.0/template/Dockerfile +0 -16
  92. continuo_python_runtime-0.11.0/tests/test_hashing.py +0 -195
  93. continuo_python_runtime-0.11.0/tests/test_template.py +0 -248
  94. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/.claude/agents/ddd-architecture-auditor.md +0 -0
  95. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/.dockerignore +0 -0
  96. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/.github/CODEOWNERS +0 -0
  97. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  98. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  99. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  100. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  101. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/.github/dependabot.yml +0 -0
  102. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/.github/workflows/ci.yml +0 -0
  103. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/.github/workflows/dco.yml +0 -0
  104. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/.github/workflows/image-smoke.yml +0 -0
  105. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/.github/workflows/images.yml +0 -0
  106. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/.github/workflows/publish-pypi.yml +0 -0
  107. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/.github/workflows/release.yml +0 -0
  108. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/.github/workflows/security.yml +0 -0
  109. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/.github/workflows/verify-packages.yml +0 -0
  110. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/.gitignore +0 -0
  111. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/.gitleaks.toml +0 -0
  112. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/.python-version +0 -0
  113. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/CODE_OF_CONDUCT.md +0 -0
  114. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/DCO +0 -0
  115. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/LICENSE +0 -0
  116. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/NOTICE +0 -0
  117. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/SECURITY.md +0 -0
  118. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/adapters/duckdb/README.md +0 -0
  119. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/adapters/duckdb/continuo_duckdb_adapter/__init__.py +0 -0
  120. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/adapters/duckdb/continuo_duckdb_adapter/adapter.py +0 -0
  121. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/adapters/duckdb/continuo_duckdb_adapter/application/__init__.py +0 -0
  122. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/adapters/duckdb/continuo_duckdb_adapter/application/ports.py +0 -0
  123. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/adapters/duckdb/continuo_duckdb_adapter/domain/__init__.py +0 -0
  124. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/adapters/duckdb/continuo_duckdb_adapter/domain/columns.py +0 -0
  125. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/adapters/duckdb/continuo_duckdb_adapter/domain/identifiers.py +0 -0
  126. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/adapters/duckdb/continuo_duckdb_adapter/domain/layout.py +0 -0
  127. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/adapters/duckdb/continuo_duckdb_adapter/infrastructure/__init__.py +0 -0
  128. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/adapters/duckdb/continuo_duckdb_adapter/infrastructure/ddl.py +0 -0
  129. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/adapters/duckdb/continuo_duckdb_adapter/infrastructure/extensions.py +0 -0
  130. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/adapters/duckdb/continuo_duckdb_adapter/infrastructure/passfile.py +0 -0
  131. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/adapters/duckdb/continuo_duckdb_adapter/infrastructure/session.py +0 -0
  132. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/adapters/duckdb/continuo_duckdb_adapter/infrastructure/settings.py +0 -0
  133. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/adapters/duckdb/tests/__init__.py +0 -0
  134. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/adapters/duckdb/tests/conftest.py +0 -0
  135. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/adapters/duckdb/tests/test_adapter_duckdb_proxy.py +0 -0
  136. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/adapters/duckdb/tests/test_adapter_duckdb_runtime.py +0 -0
  137. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/adapters/duckdb/tests/test_adapter_duckdb_validation.py +0 -0
  138. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/adapters/duckdb/tests/test_domain_duckdb_columns.py +0 -0
  139. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/adapters/duckdb/tests/test_domain_duckdb_identifiers.py +0 -0
  140. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/adapters/duckdb/tests/test_domain_duckdb_layout.py +0 -0
  141. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/adapters/duckdb/tests/test_infra_duckdb_session.py +0 -0
  142. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/adapters/duckdb/tests/test_infra_duckdb_settings_ddl.py +0 -0
  143. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/adapters/duckdb/tests/test_integration_duckdb_conformance.py +0 -0
  144. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/adapters/duckdb/tests/test_integration_duckdb_layout.py +0 -0
  145. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/adapters/duckdb/tests/test_integration_duckdb_nullability_probe.py +0 -0
  146. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/adapters/duckdb/tests/test_integration_duckdb_runtime.py +0 -0
  147. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/adapters/duckdb/tests/test_integration_duckdb_stack.py +0 -0
  148. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/adapters/duckdb/tests/test_integration_duckdb_validation.py +0 -0
  149. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/adapters/postgres/continuo_postgres_adapter/__init__.py +0 -0
  150. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/adapters/postgres/tests/__init__.py +0 -0
  151. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/adapters/postgres/tests/conftest.py +0 -0
  152. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/adapters/postgres/tests/test_adapter_postgres_connect.py +0 -0
  153. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/adapters/postgres/tests/test_adapter_runtime_postgres.py +0 -0
  154. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/adapters/postgres/tests/test_integration_postgres_validation.py +0 -0
  155. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/adapters/postgres/tests/test_integration_runtime_postgres.py +0 -0
  156. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/adapters/trino/continuo_trino_adapter/__init__.py +0 -0
  157. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/adapters/trino/tests/__init__.py +0 -0
  158. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/adapters/trino/tests/test_adapter_runtime_trino.py +0 -0
  159. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/adapters/trino/tests/test_adapter_trino_connect.py +0 -0
  160. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/adapters/trino/tests/test_adapter_trino_writes.py +0 -0
  161. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/adapters/trino/tests/test_integration_runtime_trino.py +0 -0
  162. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/adapters/trino/tests/test_integration_trino_merge_probe.py +0 -0
  163. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/adapters/trino/tests/test_integration_trino_validation.py +0 -0
  164. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/continuo_python_runtime/__init__.py +0 -0
  165. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/continuo_python_runtime/closure.py +0 -0
  166. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/continuo_python_runtime/conform.py +0 -0
  167. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/continuo_python_runtime/context.py +0 -0
  168. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/continuo_python_runtime/contract/__init__.py +0 -0
  169. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/continuo_python_runtime/contract/secret_ref.py +0 -0
  170. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/continuo_python_runtime/csv_loader.py +0 -0
  171. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/continuo_python_runtime/csv_readers/__init__.py +0 -0
  172. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/continuo_python_runtime/csv_readers/https.py +0 -0
  173. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/continuo_python_runtime/csv_readers/s3.py +0 -0
  174. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/continuo_python_runtime/csv_source.py +0 -0
  175. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/continuo_python_runtime/shape.py +0 -0
  176. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/continuo_python_runtime/types.py +0 -0
  177. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/continuo_python_runtime/validation/__init__.py +0 -0
  178. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/continuo_python_runtime/validation/s3.py +0 -0
  179. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/contract/LICENSE +0 -0
  180. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/contract/NOTICE +0 -0
  181. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/contract/continuo_engine_contract/__init__.py +0 -0
  182. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/contract/continuo_engine_contract/config.py +0 -0
  183. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/contract/continuo_engine_contract/keys.py +0 -0
  184. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/contract/continuo_engine_contract/sql.py +0 -0
  185. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/contract/continuo_engine_contract/testing/__init__.py +0 -0
  186. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/contract/continuo_engine_contract/types.py +0 -0
  187. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/contract/tests/__init__.py +0 -0
  188. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/contract/tests/test_config.py +0 -0
  189. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/contract/tests/test_contract_keys.py +0 -0
  190. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/contract/tests/test_contract_packaging.py +0 -0
  191. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/contract/tests/test_contract_types.py +0 -0
  192. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/contract/tests/test_sql.py +0 -0
  193. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/scripts/apply_rehearsal_version.py +0 -0
  194. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/scripts/bake_duckdb_extensions.py +0 -0
  195. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/scripts/check_version_bumps.py +0 -0
  196. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/scripts/security-scan.sh +0 -0
  197. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/template/contracts/example.yml +0 -0
  198. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/template/contracts/example_csv.yml +0 -0
  199. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/template/requirements.lock +0 -0
  200. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/template/scripts/example.py +0 -0
  201. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/tests/__init__.py +0 -0
  202. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/tests/conftest.py +0 -0
  203. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/tests/contract/__init__.py +0 -0
  204. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/tests/contract/test_kinds.py +0 -0
  205. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/tests/contract/test_model.py +0 -0
  206. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/tests/smoke/duckdb-stack/docker-compose.yml +0 -0
  207. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/tests/smoke/node_smoke/contracts/smoke.yml +0 -0
  208. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/tests/smoke/node_smoke/scripts/smoke.py +0 -0
  209. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/tests/smoke/postgres-stack/docker-compose.yml +0 -0
  210. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/tests/smoke/trino-stack/catalog/iceberg.properties +0 -0
  211. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/tests/test_adapter_naming.py +0 -0
  212. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/tests/test_check_version_bumps_tags.py +0 -0
  213. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/tests/test_ci_test_coverage.py +0 -0
  214. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/tests/test_closure.py +0 -0
  215. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/tests/test_conform.py +0 -0
  216. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/tests/test_context.py +0 -0
  217. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/tests/test_contract_loader.py +0 -0
  218. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/tests/test_contract_pin_consistency.py +0 -0
  219. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/tests/test_csv_loader.py +0 -0
  220. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/tests/test_csv_readers_https.py +0 -0
  221. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/tests/test_csv_readers_integration.py +0 -0
  222. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/tests/test_csv_source.py +0 -0
  223. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/tests/test_errors.py +0 -0
  224. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/tests/test_harness.py +0 -0
  225. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/tests/test_harness_write_modes.py +0 -0
  226. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/tests/test_image_requirements_sync.py +0 -0
  227. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/tests/test_image_smoke_validation.py +0 -0
  228. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/tests/test_no_legacy_names.py +0 -0
  229. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/tests/test_package.py +0 -0
  230. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/tests/test_python_version_consistency.py +0 -0
  231. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/tests/test_rehearsal_version.py +0 -0
  232. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/tests/test_shape.py +0 -0
  233. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/tests/test_validation_runner.py +0 -0
  234. {continuo_python_runtime-0.11.0 → continuo_python_runtime-0.12.0}/tests/test_validation_s3.py +0 -0
@@ -89,7 +89,13 @@ jobs:
89
89
  # once published. (The image build itself runs inside python:3.14-slim.)
90
90
  python-version: "3.14"
91
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
92
+ # publish-pypi.yml, but an index can lag its own upload, so poll. A
93
+ # probe that succeeds does not guarantee the build's own request the
94
+ # same answer (two requests seconds apart can be served different
95
+ # copies of the index page), so the image build's install
96
+ # (scripts/install_image_pins.py) also retries while a pin is not
97
+ # served yet. This probe is the fast pre-check, and in a rehearsal it
98
+ # fetches the files compared byte for byte below. The
93
99
  # probe runs with --no-cache-dir: the simple index is served with
94
100
  # `Cache-Control: max-age=600`, which pip's HTTP cache honours, so one
95
101
  # early stale fetch would otherwise be replayed for 10 minutes, exactly
@@ -5,6 +5,148 @@ follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## [0.12.0] - 2026-11-11
9
+
10
+ **TL;DR**
11
+
12
+ - Hashes now come from continuo-contracts 0.1.0, the library continuo uses.
13
+ Every Python node's `content_hash` changes once, on its first release with
14
+ this version.
15
+ - A node can list the files its script reads in `assets`, and the lint refuses
16
+ five kinds of code the hash cannot see.
17
+ - The harness checks, when a node starts, that the image holds what the
18
+ release hashed.
19
+ - The validation runner has two new read-only ops, `seed_preflight` and
20
+ `probe_views`.
21
+ - Adapter authors: every adapter must now declare `drop_semantics`.
22
+
23
+ **Upgrade**
24
+
25
+ 1. Build your domain image `FROM` the `v0.12.0` engine image and install
26
+ `continuo-python-runtime==0.12.0` in CI. The template's "Pin the runtime to
27
+ the image" step does this from the `FROM` line. A `Dockerfile.pip`
28
+ repository replaces that step as the template README says.
29
+ 2. Move each Python service with continuo's rebaseline release, not an
30
+ ordinary one: its `content_hash` changes once.
31
+ 3. `COPY` into the image every file a node lists in `assets`.
32
+ 4. Bring-your-own adapter: declare `drop_semantics`. You may also implement
33
+ `DependentsIntrospection` and run `DependentsConformance`.
34
+
35
+ Versions: runtime 0.12.0 (pins continuo-contracts 0.1.0), engine contract 0.10.0, postgres adapter 0.4.0, trino adapter 0.5.0, duckdb adapter 0.3.0.
36
+
37
+ ### Added
38
+
39
+ - Every engine adapter declares `drop_semantics`, what dropping a table does
40
+ to the objects that read it: `"cascade"` (they are destroyed) or
41
+ `"late_binding"` (they survive and fail on their next read). The postgres
42
+ adapter declares `cascade`, the trino and duckdb adapters `late_binding`.
43
+ `StartupConformance` has a new case, `test_declares_a_drop_semantics`.
44
+ - `continuo_engine_contract.port` has an opt-in capability,
45
+ `DependentsIntrospection`, with `Dependent` and `Dependents` and the
46
+ vocabularies `DROP_SEMANTICS`, `COMPLETENESS` and `DEPENDENT_KINDS`. Its one
47
+ method, `dependents(schema, table)`, lists every object a drop of the table
48
+ would destroy or leave reading a missing table, and gives none for a missing
49
+ table or schema. The conformance class `DependentsConformance` checks it,
50
+ with an overridable `create_view` hook.
51
+ - `PostgresAdapter.dependents(schema, table)` lists, with `completeness="exact"`,
52
+ every object a `DROP TABLE ... CASCADE` of the table destroys or changes: views
53
+ and materialized views to any depth, SQL-standard functions and procedures,
54
+ foreign keys and policies on other tables, rules, inheriting tables, tables
55
+ with a column of the table's row type or of an array of it, the types built
56
+ on the table (a domain, a range, a composite type) and what reads them, and
57
+ what reads a partition of the table or a sequence of it (owned or identity).
58
+ Each object is listed once, at its shallowest depth, and the walk follows at
59
+ most 32 dependency levels. It reads `pg_depend`, so it sees what
60
+ `information_schema.view_table_usage` misses, and finds the table
61
+ case-insensitively. `TestPostgresDependents` plus a guard that snapshots the
62
+ schemas around a rolled-back cascade check it. The adapter's README lists
63
+ every kind.
64
+ - `TrinoAdapter.dependents(schema, table)` lists, with
65
+ `completeness="best_effort"`, the views whose SQL reads the table. It reads
66
+ `information_schema.views` of the adapter's catalog in one query and parses
67
+ each definition with sqlglot: an unqualified name is in the view's own
68
+ schema, a name without a catalog is in the adapter's, and a CTE of the same
69
+ statement is not the table. A definition that does not parse is matched by
70
+ the table's name as a whole identifier, with a WARNING naming the view. It
71
+ misses dynamic SQL, views in other catalogs, and views that read the table
72
+ only through another view. The adapter pins `sqlglot==30.23.0`, as the
73
+ engine contract does, and `TestTrinoDependents` runs the conformance cases
74
+ against Trino and the Iceberg REST catalog.
75
+ - The validation runner has two read-only ops, `seed_preflight` and `probe_views`
76
+ (`VALIDATION_OP`), which need no `DBT_TARGET_SCHEMA`. `seed_preflight` lists the
77
+ objects a reload of a seed's production table would destroy or leave reading
78
+ a missing table, compares them with `SEED_PREFLIGHT_ALLOWED` and
79
+ `SEED_PREFLIGHT_ACKNOWLEDGED`, and in `check` mode exits 1 with `reason`
80
+ `unowned_dependents`, `engine_mismatch` or `warehouse_unreachable` when the
81
+ reload must not run; `report` mode prints the same `reason` and exits 0.
82
+ `probe_views` reads each view of `PROBE_VIEWS` with a zero-row `SELECT` and
83
+ reports which no longer bind, without failing. Both print one result block
84
+ with their own keys after `reason`; `result_block` takes them as a keyword-only
85
+ `fields` mapping and refuses a key that shadows a standard one. The README
86
+ section "Seed preflight and view probe" has the inputs, the block and the
87
+ decision table.
88
+ - `continuo-runtime --version` prints `continuo-runtime <version>` to stdout and
89
+ exits 0. The template's `release.yml` has a "Pin the runtime to the image"
90
+ step that reads the version from the Dockerfile's
91
+ `FROM ghcr.io/carolsimone/continuo-python-runtime-<engine>:vX.Y.Z` line
92
+ (failing with an `::error::` when none matches), installs exactly that
93
+ runtime, and fails when the version `uv tool list` reports differs, so CI hashes
94
+ with the runtime the image runs.
95
+ - The harness checks, before it builds the adapter, that the image holds what
96
+ the release hashed. When `CONTENT_HASH` is set it recomputes the node's
97
+ `content_hash` from the files in the image, with the helpers `merge` uses,
98
+ and a difference, or a hashed file missing from the image, fails the node
99
+ with `ImageContractMismatch` (a `ContractError`) and exit 1. The message
100
+ names both hashes, the three parts, the unit paths and the runtime version.
101
+ An unset or empty `CONTENT_HASH` logs a warning and runs. The result block
102
+ carries an optional `reason` key after `unique_id`
103
+ (`image_contract_mismatch`); a block without one keeps its bytes, and
104
+ `continuo_engine_contract.result.result_block` takes it as a keyword-only
105
+ `reason`.
106
+ - `assets:` on a `python-node` or `python-api` node lists the repository files
107
+ its script reads. `merge` folds their bytes into `shared_code_hash`, so
108
+ editing one changes the node's `content_hash`, and writes `assets` on the
109
+ wire entry when a node declares any. A missing path, a non-file or a path
110
+ that resolves outside the repository fails `merge`; `python-csv` refuses the
111
+ key. The image must `COPY` the files.
112
+ - `lint` and `merge` refuse code the content hash cannot see: `runpy`, a change
113
+ to `sys.path`, a `sitecustomize` or `usercustomize` module, `compile()` and
114
+ `types.FunctionType`, and `pickle.load`, `pickle.loads` and
115
+ `pickle.Unpickler`. `merge` also refuses a `sitecustomize.py` or
116
+ `usercustomize.py` at the repository root. A file a script reads belongs in
117
+ `assets`.
118
+
119
+ ### Changed
120
+
121
+ - **Breaking for adapter authors.** `WarehouseAdapter.drop_semantics` is a
122
+ required declaration with no default: an adapter that does not set it cannot
123
+ pass the new conformance case.
124
+ - A node's hashes come from continuo-contracts 0.1.0, the library
125
+ continuo's topology-controller uses too, so CI and continuo compute the
126
+ same values. `config_hash` hashes the node's code projection: `owner`,
127
+ `description`, `criticality` and `schedule` no longer move a node's hash,
128
+ a column type's spelling does not (`INT` and `INTEGER` hash alike), and a
129
+ read's SQL is hashed as written, apart from LF line endings and the
130
+ whitespace around it. `shared_code_hash` folds each import closure file
131
+ with its repo-relative path, so two files that swap their contents change
132
+ it. Every Python node's `content_hash` changes once, on the first release
133
+ built with this version. The wire entry keeps its keys and the fold.
134
+ - A node whose `config` holds a value the hash cannot write (an integer
135
+ beyond 2**53 - 1, NaN or an infinity, nesting deeper than 99 levels, a
136
+ lone surrogate) fails `merge` with a `ContractError` naming the node.
137
+ - The package gate no longer counts `continuo-contracts` as a first-party
138
+ distribution.
139
+
140
+ ### Fixed
141
+
142
+ - A release's image build no longer fails when PyPI still serves an index
143
+ page from before the upload. The engine images install their pins through
144
+ `scripts/install_image_pins.py`, which retries an install while pip says a
145
+ pinned version is not served yet (up to 10 minutes) and fails any other
146
+ error at once. The v0.11.0 trino amd64 build hit this 40 s after the upload,
147
+ although the runner's own probe had seen the new version. No package
148
+ changes.
149
+
8
150
  ## [0.11.0] - 2026-11-04
9
151
 
10
152
  **TL;DR**
@@ -28,11 +28,13 @@ merged PRs before adding anything new.
28
28
  conformance suite for engine adapter authors. Its dependencies are installed
29
29
  with `continuo-engine-contract[testing]`.
30
30
 
31
- - Every adapter MUST satisfy the engine contract and pass `StartupConformance`
32
- (`from_env` waits out a warehouse that refuses connections at first) and the
31
+ - Every adapter MUST satisfy the engine contract, declare a `drop_semantics`,
32
+ and pass `StartupConformance` (`from_env` waits out a warehouse that refuses
33
+ connections at first, and `drop_semantics` is declared) and the
33
34
  shared conformance classes for every capability it declares:
34
35
  `IntrospectionConformance` for `TableIntrospection`, `AppendConformance` for
35
- `AppendCapable`, and `UpsertConformance` for `UpsertCapable`. Append and
36
+ `AppendCapable`, `UpsertConformance` for `UpsertCapable`, and
37
+ `DependentsConformance` for `DependentsIntrospection`. Append and
36
38
  upsert adapters must also run the introspection class. An adapter need not
37
39
  declare capabilities its engine cannot support.
38
40
  - Each adapter's integration suite MUST subclass the applicable classes,
@@ -151,7 +151,7 @@ gh workflow run publish-pypi.yml --ref <branch> -f dry_run=true
151
151
  |---|---|---|---|---|
152
152
  | `prepare`, `verify-packages`, `preflight-*` | nothing | nothing | none | Fix, delete the tag (`git push --delete origin vX.Y.Z`), re-tag. |
153
153
  | `publish-packages` | maybe some of the five files | nothing | none | Re-run failed jobs (`skip-existing` makes it resumable), or fix forward. |
154
- | `candidate-build` | published | maybe untagged digests | none | Transient (index lag, runner): re-run failed jobs. Real defect: yank the files on pypi.org and **fix forward** with a new version. |
154
+ | `candidate-build` | published | maybe untagged digests | none | The build waits up to 10 minutes on its own for the index to serve the new pins. Transient (a runner, a longer index lag): re-run failed jobs. Real defect: yank the files on pypi.org and **fix forward** with a new version. |
155
155
  | `candidate-verify` | published | untagged candidates | none | Same: an image defect means yank and fix forward. |
156
156
  | `promote` | published | verified digests, maybe some engines tagged | none | Re-run failed jobs; tagging is idempotent and refuses to move an existing tag to different content. |
157
157
 
@@ -12,30 +12,17 @@ FROM python:3.14.8-slim
12
12
  # package names, so it is never an extra index.
13
13
  # Any other value fails the build.
14
14
  # The pinned versions live in image-requirements-duckdb.txt, kept equal to the
15
- # repo's pyproject versions by tests/test_image_requirements_sync.py. See
16
- # Dockerfile.postgres for the full reasoning behind the two-step wheelhouse
17
- # install (exact local files first, --no-index --no-deps, then PyPI for the
18
- # third-party dependencies).
15
+ # repo's pyproject versions by tests/test_image_requirements_sync.py.
16
+ # scripts/install_image_pins.py runs the installs for each WHEEL_SOURCE (and
17
+ # says why the wheelhouse wheels are installed by exact file first). It
18
+ # retries an index install while the index does not serve a just-uploaded
19
+ # pin yet: a release builds its images right after the upload.
19
20
  ARG WHEEL_SOURCE=pypi
20
21
  COPY image-requirements-duckdb.txt /tmp/req.txt
21
22
  COPY wheelhous[e] /tmp/wheelhouse
22
- RUN set -eu; \
23
- if [ "$WHEEL_SOURCE" = "wheelhouse" ]; then \
24
- pip install --no-cache-dir --no-index --no-deps \
25
- /tmp/wheelhouse/continuo_engine_contract-*.whl \
26
- /tmp/wheelhouse/continuo_python_runtime-*.whl \
27
- /tmp/wheelhouse/continuo_duckdb_adapter-*.whl; \
28
- pip install --no-cache-dir -r /tmp/req.txt; \
29
- elif [ "$WHEEL_SOURCE" = "testpypi" ]; then \
30
- pip install --no-cache-dir --no-deps --only-binary=:all: --index-url https://test.pypi.org/simple/ \
31
- -r /tmp/req.txt; \
32
- pip install --no-cache-dir -r /tmp/req.txt; \
33
- elif [ "$WHEEL_SOURCE" = "pypi" ]; then \
34
- pip install --no-cache-dir -r /tmp/req.txt; \
35
- else \
36
- echo "unknown WHEEL_SOURCE=$WHEEL_SOURCE (pypi|testpypi|wheelhouse)" >&2; exit 1; \
37
- fi; \
38
- rm -rf /tmp/req.txt /tmp/wheelhouse
23
+ COPY scripts/install_image_pins.py /tmp/install_image_pins.py
24
+ RUN python /tmp/install_image_pins.py --source "$WHEEL_SOURCE" --adapter duckdb --requirements /tmp/req.txt --wheelhouse /tmp/wheelhouse \
25
+ && rm -rf /tmp/req.txt /tmp/wheelhouse /tmp/install_image_pins.py
39
26
  # The adapter LOADs ducklake/postgres/httpfs/aws from this directory. They are
40
27
  # installed now, as root, because the runtime user below has no home directory
41
28
  # and the container may have no network. HOME=/tmp gives DuckDB a writable
@@ -15,48 +15,25 @@ FROM python:3.14.8-slim
15
15
  # Any other value fails the build.
16
16
  # The wheelhouse holds only this repo's own first-party wheels (built via
17
17
  # `uv build --all-packages`); third-party dependencies always resolve from
18
- # PyPI in both branches, so the wheelhouse branch never passes --no-index to
19
- # the requirements install below.
18
+ # PyPI in both branches, so the wheelhouse install never passes --no-index to
19
+ # the requirements install.
20
20
  # The pinned versions live in image-requirements-postgres.txt, kept equal to the
21
21
  # repo's pyproject versions by tests/test_image_requirements_sync.py.
22
22
  #
23
- # The wheelhouse branch installs its three first-party wheels (contract,
24
- # runtime, adapter) by exact local file first, with --no-index --no-deps: a
25
- # plain `--find-links=/tmp/wheelhouse -r req.txt` is not enough, because pip
26
- # does not prefer a find-links wheel over an index match at the same
27
- # name+version, and continuo-python-runtime / continuo-engine-contract are
28
- # already published on PyPI under the version this repo's HEAD currently
29
- # carries (a routine dependency bump lands between releases without a version
30
- # bump). Left unpinned-by-file, pip silently installs the last PyPI release's
31
- # transitive pins instead of the wheel this build just produced, defeating
32
- # the point of testing an unreleased change. Installing the exact local files
33
- # first (no index contacted at all for this step) is unambiguous; the second
34
- # install then only has PyPI-only third-party deps left to resolve, and finds
35
- # the three first-party packages already satisfied.
23
+ # scripts/install_image_pins.py runs the installs for each WHEEL_SOURCE (and
24
+ # says why the wheelhouse wheels are installed by exact file first). It
25
+ # retries an index install while the index does not serve a just-uploaded
26
+ # pin yet: a release builds its images right after the upload.
36
27
  ARG WHEEL_SOURCE=pypi
37
28
  COPY image-requirements-postgres.txt /tmp/req.txt
38
29
  # Optional COPY: the bracket glob matches ./wheelhouse when it exists (the CI/PR
39
30
  # WHEEL_SOURCE=wheelhouse path builds it) and no-ops when it is absent, so a
40
31
  # release build or a plain `docker build -f Dockerfile.postgres .` needs no
41
- # pre-created directory. Only the wheelhouse branch below reads its contents.
32
+ # pre-created directory. Only the wheelhouse install reads its contents.
42
33
  COPY wheelhous[e] /tmp/wheelhouse
43
- RUN set -eu; \
44
- if [ "$WHEEL_SOURCE" = "wheelhouse" ]; then \
45
- pip install --no-cache-dir --no-index --no-deps \
46
- /tmp/wheelhouse/continuo_engine_contract-*.whl \
47
- /tmp/wheelhouse/continuo_python_runtime-*.whl \
48
- /tmp/wheelhouse/continuo_postgres_adapter-*.whl; \
49
- pip install --no-cache-dir -r /tmp/req.txt; \
50
- elif [ "$WHEEL_SOURCE" = "testpypi" ]; then \
51
- pip install --no-cache-dir --no-deps --only-binary=:all: --index-url https://test.pypi.org/simple/ \
52
- -r /tmp/req.txt; \
53
- pip install --no-cache-dir -r /tmp/req.txt; \
54
- elif [ "$WHEEL_SOURCE" = "pypi" ]; then \
55
- pip install --no-cache-dir -r /tmp/req.txt; \
56
- else \
57
- echo "unknown WHEEL_SOURCE=$WHEEL_SOURCE (pypi|testpypi|wheelhouse)" >&2; exit 1; \
58
- fi; \
59
- rm -rf /tmp/req.txt /tmp/wheelhouse
34
+ COPY scripts/install_image_pins.py /tmp/install_image_pins.py
35
+ RUN python /tmp/install_image_pins.py --source "$WHEEL_SOURCE" --adapter postgres --requirements /tmp/req.txt --wheelhouse /tmp/wheelhouse \
36
+ && rm -rf /tmp/req.txt /tmp/wheelhouse /tmp/install_image_pins.py
60
37
  # PYTHONPATH is belt-and-braces: the harness also inserts APP_ROOT and the
61
38
  # node script's own directory at the front of sys.path before executing it.
62
39
  ENV CONTRACT_DIR=/app/contracts APP_ROOT=/app PYTHONPATH=/app
@@ -15,48 +15,25 @@ FROM python:3.14.8-slim
15
15
  # Any other value fails the build.
16
16
  # The wheelhouse holds only this repo's own first-party wheels (built via
17
17
  # `uv build --all-packages`); third-party dependencies always resolve from
18
- # PyPI in both branches, so the wheelhouse branch never passes --no-index to
19
- # the requirements install below.
18
+ # PyPI in both branches, so the wheelhouse install never passes --no-index to
19
+ # the requirements install.
20
20
  # The pinned versions live in image-requirements-trino.txt, kept equal to the
21
21
  # repo's pyproject versions by tests/test_image_requirements_sync.py.
22
22
  #
23
- # The wheelhouse branch installs its three first-party wheels (contract,
24
- # runtime, adapter) by exact local file first, with --no-index --no-deps: a
25
- # plain `--find-links=/tmp/wheelhouse -r req.txt` is not enough, because pip
26
- # does not prefer a find-links wheel over an index match at the same
27
- # name+version, and continuo-python-runtime / continuo-engine-contract are
28
- # already published on PyPI under the version this repo's HEAD currently
29
- # carries (a routine dependency bump lands between releases without a version
30
- # bump). Left unpinned-by-file, pip silently installs the last PyPI release's
31
- # transitive pins instead of the wheel this build just produced, defeating
32
- # the point of testing an unreleased change. Installing the exact local files
33
- # first (no index contacted at all for this step) is unambiguous; the second
34
- # install then only has PyPI-only third-party deps left to resolve, and finds
35
- # the three first-party packages already satisfied.
23
+ # scripts/install_image_pins.py runs the installs for each WHEEL_SOURCE (and
24
+ # says why the wheelhouse wheels are installed by exact file first). It
25
+ # retries an index install while the index does not serve a just-uploaded
26
+ # pin yet: a release builds its images right after the upload.
36
27
  ARG WHEEL_SOURCE=pypi
37
28
  COPY image-requirements-trino.txt /tmp/req.txt
38
29
  # Optional COPY: the bracket glob matches ./wheelhouse when it exists (the CI/PR
39
30
  # WHEEL_SOURCE=wheelhouse path builds it) and no-ops when it is absent, so a
40
31
  # release build or a plain `docker build -f Dockerfile.trino .` needs no
41
- # pre-created directory. Only the wheelhouse branch below reads its contents.
32
+ # pre-created directory. Only the wheelhouse install reads its contents.
42
33
  COPY wheelhous[e] /tmp/wheelhouse
43
- RUN set -eu; \
44
- if [ "$WHEEL_SOURCE" = "wheelhouse" ]; then \
45
- pip install --no-cache-dir --no-index --no-deps \
46
- /tmp/wheelhouse/continuo_engine_contract-*.whl \
47
- /tmp/wheelhouse/continuo_python_runtime-*.whl \
48
- /tmp/wheelhouse/continuo_trino_adapter-*.whl; \
49
- pip install --no-cache-dir -r /tmp/req.txt; \
50
- elif [ "$WHEEL_SOURCE" = "testpypi" ]; then \
51
- pip install --no-cache-dir --no-deps --only-binary=:all: --index-url https://test.pypi.org/simple/ \
52
- -r /tmp/req.txt; \
53
- pip install --no-cache-dir -r /tmp/req.txt; \
54
- elif [ "$WHEEL_SOURCE" = "pypi" ]; then \
55
- pip install --no-cache-dir -r /tmp/req.txt; \
56
- else \
57
- echo "unknown WHEEL_SOURCE=$WHEEL_SOURCE (pypi|testpypi|wheelhouse)" >&2; exit 1; \
58
- fi; \
59
- rm -rf /tmp/req.txt /tmp/wheelhouse
34
+ COPY scripts/install_image_pins.py /tmp/install_image_pins.py
35
+ RUN python /tmp/install_image_pins.py --source "$WHEEL_SOURCE" --adapter trino --requirements /tmp/req.txt --wheelhouse /tmp/wheelhouse \
36
+ && rm -rf /tmp/req.txt /tmp/wheelhouse /tmp/install_image_pins.py
60
37
  # PYTHONPATH is belt-and-braces: the harness also inserts APP_ROOT and the
61
38
  # node script's own directory at the front of sys.path before executing it.
62
39
  ENV CONTRACT_DIR=/app/contracts APP_ROOT=/app PYTHONPATH=/app
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: continuo-python-runtime
3
- Version: 0.11.0
3
+ Version: 0.12.0
4
4
  Summary: Runtime harness, contract tooling, and CI lint for Continuo python nodes.
5
5
  Author: Simone Carolini
6
6
  Maintainer: Simone Carolini
@@ -12,7 +12,8 @@ Classifier: Intended Audience :: Developers
12
12
  Classifier: Programming Language :: Python :: 3.14
13
13
  Requires-Python: >=3.14
14
14
  Requires-Dist: boto3==1.43.114
15
- Requires-Dist: continuo-engine-contract==0.9.0
15
+ Requires-Dist: continuo-contracts==0.1.0
16
+ Requires-Dist: continuo-engine-contract==0.10.0
16
17
  Requires-Dist: pyarrow==26.0.0
17
18
  Requires-Dist: pyyaml==6.0.3
18
19
  Requires-Dist: sqlglot==30.23.0
@@ -112,7 +113,12 @@ on every change.
112
113
  last line of stdout, and Continuo's Go side parses it byte-for-byte
113
114
  (`pkg/validationresult` in the `continuo` repository). That wire format — the
114
115
  sentinel markers, the framing, and the field names inside the block — is
115
- **frozen**. Reuse it; never change it from this side alone. A change here that
116
+ **frozen**. The additions are an optional `reason` key, written after
117
+ `unique_id` only when an outcome has a machine-readable code (for example
118
+ `image_contract_mismatch`), and the op's own keys that the `seed_preflight` and
119
+ `probe_views` ops write after `reason`; a block without either keeps its bytes,
120
+ and the sentinels, `schema_version` and the five standard keys do not change. Reuse
121
+ it; never change it from this side alone. A change here that
116
122
  the Go parser has not been taught is a production outage, not a refactor.
117
123
 
118
124
  ## Quickstart for domain teams
@@ -196,14 +202,37 @@ produced, never how it is checked or written.
196
202
  script reads with `os.environ[...]`. The contract never holds a secret value
197
203
  or env var name; the Secret itself is created in the cluster by the operator.
198
204
 
205
+ ### Files a script reads (`assets`)
206
+
207
+ A `python-node` or `python-api` script that reads a file from the repository
208
+ (a model, a lookup table, a JSON config) lists it under `assets:`, one path
209
+ relative to the repository root per entry, written with forward slashes and
210
+ without a `..` segment:
211
+
212
+ ```yaml
213
+ - schema: analytics
214
+ table: scored_orders
215
+ kind: python-node
216
+ script: scripts/scored_orders.py
217
+ assets: [data/rates.json, models/scorer.bin]
218
+ ```
219
+
220
+ `merge` reads each listed file and folds its bytes into `shared_code_hash`
221
+ under its path, so editing one changes the node's `content_hash` and starts a
222
+ validation. A path that is also an import of the script is one unit. `merge`
223
+ fails with a `ContractError` for a path that is missing, is not a file or
224
+ resolves outside the repository, and a path listed twice is refused. The image
225
+ must `COPY` the files, because `assets` only tells the hash about them. A
226
+ `python-csv` node has no script, so `assets` is refused on it.
227
+
199
228
  ### Task timeout
200
229
 
201
230
  Any node may set `timeout_minutes:` — an integer from 1 to 10,080 (one week) —
202
231
  to declare how long its task may run before continuo gives up on it. A value
203
232
  outside that range, or one that is not an integer, fails
204
- `continuo-runtime validate`/`merge`/`lint`. It is the one key that is not part
205
- of `config_hash`, so changing only the timeout never changes the node's
206
- `content_hash`.
233
+ `continuo-runtime validate`/`merge`/`lint`. Like `owner`, `schedule`,
234
+ `criticality` and `description`, it is outside the code projection that
235
+ `config_hash` hashes, so changing it never changes the node's `content_hash`.
207
236
 
208
237
  ## Write modes
209
238
 
@@ -282,7 +311,7 @@ def run(ctx):
282
311
  example a polars DataFrame. Returning anything else raises `ScriptError`.
283
312
  - The dataframe library is your choice. No dataframe library is baked into
284
313
  the base image — the package's own runtime dependencies are `pyarrow`,
285
- `PyYAML`, and `continuo-engine-contract`. Add whatever you script
314
+ `PyYAML`, `continuo-engine-contract` and `continuo-contracts`. Add whatever you script
286
315
  against (pandas, polars, …) as a `RUN pip install` line in your own
287
316
  `Dockerfile`, on top of the base image. The base image runs as non-root
288
317
  (uid 65532), which pip cannot install under, so switch to root for the
@@ -298,7 +327,18 @@ def run(ctx):
298
327
  read_sql as rs` then calling `rs(...)`),
299
328
  - private/protected attribute access (`obj._x`) — except on `self`/`cls`,
300
329
  so a script's own class-private helpers (`self._helper()`) aren't
301
- flagged.
330
+ flagged,
331
+ - code the content hash cannot see. The hash folds the script and the files
332
+ it imports, found by reading imports, so a node that runs anything else
333
+ can change without its `content_hash` changing. Lint refuses `runpy`, a
334
+ change to `sys.path` (`append`, `insert`, assignment, `del`, and
335
+ `site.addsitedir`), a `sitecustomize` or `usercustomize` module (and a file
336
+ of that name), `compile()` and `types.FunctionType`, and `pickle.load`,
337
+ `pickle.loads` and `pickle.Unpickler`. `merge` applies the same rules to
338
+ every file in a node's import closure, and refuses a `sitecustomize.py` or
339
+ `usercustomize.py` at the repository root, which Python imports before the
340
+ harness starts. A file a script reads, such as a model or a lookup table,
341
+ belongs in the node's `assets`, where its bytes are hashed.
302
342
 
303
343
  The SQL-literal rule is best-effort: docstrings are exempt, but other
304
344
  prose may still occasionally match. The hard guarantees enforced by lint
@@ -337,7 +377,7 @@ declared `output_columns` on the table `run()` returned, in this order:
337
377
 
338
378
  ## Error taxonomy
339
379
 
340
- Every runtime failure is one of five `HarnessError` subclasses
380
+ Every runtime failure is one of six `HarnessError` subclasses
341
381
  (`continuo_python_runtime/errors.py`). The sentinel result block's message
342
382
  is prefixed `<ErrorClass>: ` so failures can be triaged without parsing free
343
383
  text. The harness also logs the same message at ERROR on stderr, with the
@@ -348,6 +388,7 @@ the result block out of the log before storing it.
348
388
  | Class | Meaning | Typical fix target |
349
389
  | --- | --- | --- |
350
390
  | `ContractError` | Contract missing or invalid, node not found for `NODE_ID`, or the declared script is missing/unreachable. | The contract yaml or the `script:` path. |
391
+ | `ImageContractMismatch` (a `ContractError`) | The image does not hold the contract, script, imports or assets the release hashed: the `content_hash` in `CONTENT_HASH` differs from the one the image's files give, or a file the release hashed is missing from the image. The block also carries `"reason":"image_contract_mismatch"`. | Rebuild the image from the released commit, with CI and the image on the same runtime. |
351
392
  | `ReadError` | `ctx.read()` was called with an undeclared name, or a declared read failed at the warehouse. | The `reads:` map, or the upstream query/warehouse access. |
352
393
  | `ScriptError` | `run()` raised, has no callable `run`, or returned a value that isn't Arrow-convertible. | The node script. |
353
394
  | `ConformError` | Structural mismatch (extra/missing/duplicate columns), a strict-cast failure, a not-null violation, a VARCHAR/CHAR overflow, or an `upsert` run whose rows repeat a primary key. | The script's output shape, or the `output_columns` declaration. |
@@ -359,11 +400,11 @@ A domain repo picks its warehouse engine by which base image it builds
359
400
  `FROM`:
360
401
 
361
402
  ```dockerfile
362
- FROM ghcr.io/carolsimone/continuo-python-runtime-postgres:v0.11.0
403
+ FROM ghcr.io/carolsimone/continuo-python-runtime-postgres:v0.12.0
363
404
  # or
364
- FROM ghcr.io/carolsimone/continuo-python-runtime-trino:v0.11.0
405
+ FROM ghcr.io/carolsimone/continuo-python-runtime-trino:v0.12.0
365
406
  # or
366
- FROM ghcr.io/carolsimone/continuo-python-runtime-duckdb:v0.11.0
407
+ FROM ghcr.io/carolsimone/continuo-python-runtime-duckdb:v0.12.0
367
408
  ```
368
409
 
369
410
  The engine is part of the image **name**; the tag is the bare version, so
@@ -402,6 +443,82 @@ Both shapes end up running the same runtime and the same adapter version;
402
443
  which one you pick only changes who controls the base OS layer underneath
403
444
  them.
404
445
 
446
+ ## Seed preflight and view probe
447
+
448
+ Two validation ops read the warehouse and change nothing. They run through the
449
+ same runner image as the other validation ops (`VALIDATION_OP`), need no
450
+ `DBT_TARGET_SCHEMA`, and print one result block as the last line of stdout.
451
+
452
+ A seed reload drops and recreates the seed's production table. On an engine where
453
+ a drop destroys the objects that read the table (`drop_semantics` `cascade`,
454
+ postgres) that takes every view, function and constraint built on the seed with
455
+ it, including objects continuo does not own. On an engine where those objects
456
+ survive and fail on their next read (`late_binding`, trino and duckdb) they
457
+ may go stale. `seed_preflight` lists what a reload would touch and compares it
458
+ with what the operator has allowed or acknowledged; `probe_views` tries named
459
+ views after a reload and says which no longer bind.
460
+
461
+ `seed_preflight` runs in two places. `check` runs in the seed task's own pod just
462
+ before dbt and stops the reload with exit 1 when the check concludes it must.
463
+ `report` runs in the release's seed-build Job and only reports what a `check`
464
+ would conclude; it exits 0 whatever it finds.
465
+
466
+ ### `seed_preflight` inputs
467
+
468
+ | Variable | Meaning |
469
+ |---|---|
470
+ | `SEED_PREFLIGHT_MODE` | `check` or `report`. |
471
+ | `SEED_PREFLIGHT_ENGINE_FAMILY` | `schema_bound` or `late_binding`, the family the release plan expects of the engine. |
472
+ | `PROD_SCHEMA`, `TABLE_NAME` | The seed's production relation. |
473
+ | `SEED_PREFLIGHT_ALLOWED`, `SEED_PREFLIGHT_ACKNOWLEDGED` | JSON arrays of `"<schema>.<name>"`, default `[]`. An object is allowed when `<schema>.<name>`, in lower case, is in either list (entries are trimmed and lower-cased). |
474
+ | `NODE_ID` | Optional; the block's `unique_id`. Default `<PROD_SCHEMA>.<TABLE_NAME>`. |
475
+
476
+ The adapter's own variables (`POSTGRES_*`, `TRINO_*`, ...) are needed as for any
477
+ other op. An adapter's family is `schema_bound` when its `drop_semantics` is
478
+ `cascade`, and `late_binding` when it is `late_binding`.
479
+
480
+ ### `seed_preflight` block
481
+
482
+ After the standard keys (and `reason` when one applies): `op` (`seed_preflight`),
483
+ `mode`, `checked` (the warehouse was asked and its listing came back), `engine_family` (as given),
484
+ `drop_semantics` (the adapter's), `completeness` (`exact`, `best_effort`, or
485
+ `none` when nothing was listed) and `dependents`, a list of
486
+ `{schema, name, kind, owner, depth, allowed}` sorted by depth, schema and name.
487
+ `status` is `error` for exit codes 1 and 2, and `failures` is 1 then.
488
+
489
+ `reason` is what a `check` concludes. `report` prints the same `reason` on a
490
+ `success` block.
491
+
492
+ | Situation | `reason` | `check` | `report` |
493
+ |---|---|---|---|
494
+ | a missing or malformed input, no adapter, missing adapter env | none | exit 2 | exit 2 |
495
+ | the adapter's family differs from `SEED_PREFLIGHT_ENGINE_FAMILY`, or it declares none | `engine_mismatch` | exit 1 | exit 0 |
496
+ | the adapter cannot list dependents, `schema_bound` | `unowned_dependents` | exit 1 | exit 0 |
497
+ | the adapter cannot list dependents, `late_binding` | none | exit 0 | exit 0 |
498
+ | the connection fails | `warehouse_unreachable` | exit 1 | exit 0 |
499
+ | the listing fails, `schema_bound` | none | exit 1 | exit 0 |
500
+ | the listing fails, `late_binding` | none | exit 0 | exit 0 |
501
+ | listed, `schema_bound`, an object is not allowed | `unowned_dependents` | exit 1 | exit 0 |
502
+ | anything else listed (on `late_binding` the objects not allowed are external views that may go stale, named in `message`) | none | exit 0 | exit 0 |
503
+
504
+ A seed table that does not exist yet has no dependents and passes. A stop's
505
+ `message` starts with its `reason` and names each object outside the set as
506
+ `<schema>.<name> (<kind>, owner <owner>)`.
507
+
508
+ ### `probe_views`
509
+
510
+ `PROBE_VIEWS` is a JSON array of `"<schema>.<name>"`, split on the first dot;
511
+ `NODE_ID` is optional (default `probe_views`). Each view is read with
512
+ `SELECT * FROM "<schema>"."<name>" LIMIT 0`. The block has `op`
513
+ (`probe_views`), `checked` (the warehouse was reachable) and `views`, a list of
514
+ `{schema, name, ok, error}` in the order given, where `error` is the first line
515
+ of the failure, at most 500 characters, and empty when `ok`. A view that fails
516
+ its probe never fails the op: the exit code is 0, and `message` reads
517
+ `<n> of <total> views failed: <names>`. A warehouse that cannot be reached gives
518
+ exit 0, `checked` false, `views` empty and `reason` `warehouse_unreachable`.
519
+ Only a missing or malformed input, no adapter, missing adapter env or an
520
+ unexpected error is exit 2.
521
+
405
522
  ## Further reading
406
523
 
407
524
  - `docs/superpowers/specs/2026-07-31-python-runtime-design.md` — this