continuo-python-runtime 0.7.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.
- continuo_python_runtime-0.8.0/.claude/agents/ddd-architecture-auditor.md +117 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/.github/ISSUE_TEMPLATE/bug_report.yml +1 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/.github/dependabot.yml +4 -3
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/.github/workflows/ci.yml +22 -1
- continuo_python_runtime-0.8.0/.github/workflows/image-build.yml +188 -0
- continuo_python_runtime-0.8.0/.github/workflows/image-smoke.yml +255 -0
- continuo_python_runtime-0.8.0/.github/workflows/images.yml +89 -0
- continuo_python_runtime-0.8.0/.github/workflows/publish-pypi.yml +375 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/.github/workflows/release.yml +33 -29
- continuo_python_runtime-0.8.0/.github/workflows/verify-packages.yml +74 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/.gitignore +1 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/CHANGELOG.md +67 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/CLAUDE.md +2 -2
- continuo_python_runtime-0.8.0/CONTRIBUTING.md +180 -0
- continuo_python_runtime-0.8.0/Dockerfile.duckdb +58 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/Dockerfile.postgres +13 -1
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/Dockerfile.trino +13 -1
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/PKG-INFO +24 -19
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/README.md +23 -18
- continuo_python_runtime-0.8.0/adapters/duckdb/README.md +109 -0
- continuo_python_runtime-0.8.0/adapters/duckdb/continuo_duckdb_adapter/__init__.py +1 -0
- continuo_python_runtime-0.8.0/adapters/duckdb/continuo_duckdb_adapter/adapter.py +25 -0
- continuo_python_runtime-0.8.0/adapters/duckdb/continuo_duckdb_adapter/application/__init__.py +1 -0
- continuo_python_runtime-0.8.0/adapters/duckdb/continuo_duckdb_adapter/application/ports.py +77 -0
- continuo_python_runtime-0.8.0/adapters/duckdb/continuo_duckdb_adapter/application/warehouse.py +205 -0
- continuo_python_runtime-0.8.0/adapters/duckdb/continuo_duckdb_adapter/domain/__init__.py +1 -0
- continuo_python_runtime-0.8.0/adapters/duckdb/continuo_duckdb_adapter/domain/columns.py +46 -0
- continuo_python_runtime-0.8.0/adapters/duckdb/continuo_duckdb_adapter/domain/identifiers.py +33 -0
- continuo_python_runtime-0.8.0/adapters/duckdb/continuo_duckdb_adapter/domain/layout.py +162 -0
- continuo_python_runtime-0.8.0/adapters/duckdb/continuo_duckdb_adapter/infrastructure/__init__.py +1 -0
- continuo_python_runtime-0.8.0/adapters/duckdb/continuo_duckdb_adapter/infrastructure/ddl.py +229 -0
- continuo_python_runtime-0.8.0/adapters/duckdb/continuo_duckdb_adapter/infrastructure/extensions.py +51 -0
- continuo_python_runtime-0.8.0/adapters/duckdb/continuo_duckdb_adapter/infrastructure/passfile.py +67 -0
- continuo_python_runtime-0.8.0/adapters/duckdb/continuo_duckdb_adapter/infrastructure/session.py +281 -0
- continuo_python_runtime-0.8.0/adapters/duckdb/continuo_duckdb_adapter/infrastructure/settings.py +101 -0
- continuo_python_runtime-0.8.0/adapters/duckdb/pyproject.toml +59 -0
- continuo_python_runtime-0.8.0/adapters/duckdb/tests/conftest.py +403 -0
- continuo_python_runtime-0.8.0/adapters/duckdb/tests/test_adapter_duckdb_proxy.py +25 -0
- continuo_python_runtime-0.8.0/adapters/duckdb/tests/test_adapter_duckdb_runtime.py +106 -0
- continuo_python_runtime-0.8.0/adapters/duckdb/tests/test_adapter_duckdb_validation.py +150 -0
- continuo_python_runtime-0.8.0/adapters/duckdb/tests/test_adapter_duckdb_wiring.py +25 -0
- continuo_python_runtime-0.8.0/adapters/duckdb/tests/test_domain_duckdb_columns.py +55 -0
- continuo_python_runtime-0.8.0/adapters/duckdb/tests/test_domain_duckdb_identifiers.py +26 -0
- continuo_python_runtime-0.8.0/adapters/duckdb/tests/test_domain_duckdb_layout.py +135 -0
- continuo_python_runtime-0.8.0/adapters/duckdb/tests/test_infra_duckdb_session.py +458 -0
- continuo_python_runtime-0.8.0/adapters/duckdb/tests/test_infra_duckdb_settings_ddl.py +295 -0
- continuo_python_runtime-0.8.0/adapters/duckdb/tests/test_integration_duckdb_layout.py +269 -0
- continuo_python_runtime-0.8.0/adapters/duckdb/tests/test_integration_duckdb_runtime.py +253 -0
- continuo_python_runtime-0.8.0/adapters/duckdb/tests/test_integration_duckdb_stack.py +133 -0
- continuo_python_runtime-0.8.0/adapters/duckdb/tests/test_integration_duckdb_validation.py +226 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/docs/boundary-contract.md +13 -4
- continuo_python_runtime-0.8.0/image-requirements-duckdb.txt +3 -0
- continuo_python_runtime-0.8.0/image-requirements-postgres.txt +3 -0
- continuo_python_runtime-0.8.0/image-requirements-trino.txt +3 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/pyproject.toml +2 -2
- continuo_python_runtime-0.8.0/scripts/apply_rehearsal_version.py +145 -0
- continuo_python_runtime-0.8.0/scripts/bake_duckdb_extensions.py +37 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/scripts/check_version_bumps.py +5 -1
- continuo_python_runtime-0.8.0/scripts/verify_installed_packages.py +155 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/template/Dockerfile +1 -1
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/template/contracts/example.yml +2 -1
- continuo_python_runtime-0.8.0/tests/contract/__init__.py +0 -0
- continuo_python_runtime-0.8.0/tests/smoke/duckdb-stack/docker-compose.yml +71 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/tests/test_adapter_naming.py +2 -2
- continuo_python_runtime-0.8.0/tests/test_check_version_bumps_tags.py +49 -0
- continuo_python_runtime-0.8.0/tests/test_ci_test_coverage.py +138 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/tests/test_contract_pin_consistency.py +1 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/tests/test_image_requirements_sync.py +13 -0
- continuo_python_runtime-0.8.0/tests/test_rehearsal_version.py +136 -0
- continuo_python_runtime-0.8.0/tests/test_release_pipeline_structure.py +142 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/tests/test_template.py +1 -1
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/uv.lock +58 -1
- continuo_python_runtime-0.7.0/.github/workflows/images.yml +0 -228
- continuo_python_runtime-0.7.0/.github/workflows/publish-pypi.yml +0 -99
- continuo_python_runtime-0.7.0/CONTRIBUTING.md +0 -102
- continuo_python_runtime-0.7.0/image-requirements-postgres.txt +0 -2
- continuo_python_runtime-0.7.0/image-requirements-trino.txt +0 -2
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/.dockerignore +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/.github/CODEOWNERS +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/.github/workflows/dco.yml +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/.github/workflows/security.yml +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/.gitleaks.toml +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/CODE_OF_CONDUCT.md +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/DCO +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/LICENSE +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/NOTICE +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/SECURITY.md +0 -0
- {continuo_python_runtime-0.7.0/adapters/postgres/continuo_postgres_adapter → continuo_python_runtime-0.8.0/adapters/duckdb/tests}/__init__.py +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/adapters/postgres/README.md +0 -0
- {continuo_python_runtime-0.7.0/adapters/postgres/tests → continuo_python_runtime-0.8.0/adapters/postgres/continuo_postgres_adapter}/__init__.py +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/adapters/postgres/continuo_postgres_adapter/adapter.py +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/adapters/postgres/pyproject.toml +0 -0
- {continuo_python_runtime-0.7.0/adapters/trino/continuo_trino_adapter → continuo_python_runtime-0.8.0/adapters/postgres/tests}/__init__.py +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/adapters/postgres/tests/conftest.py +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/adapters/postgres/tests/test_adapter_postgres_validation.py +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/adapters/postgres/tests/test_adapter_runtime_postgres.py +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/adapters/postgres/tests/test_integration_postgres_validation.py +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/adapters/postgres/tests/test_integration_runtime_postgres.py +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/adapters/trino/README.md +0 -0
- {continuo_python_runtime-0.7.0/adapters/trino/tests → continuo_python_runtime-0.8.0/adapters/trino/continuo_trino_adapter}/__init__.py +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/adapters/trino/continuo_trino_adapter/adapter.py +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/adapters/trino/pyproject.toml +0 -0
- {continuo_python_runtime-0.7.0/contract → continuo_python_runtime-0.8.0/adapters/trino}/tests/__init__.py +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/adapters/trino/tests/test_adapter_runtime_trino.py +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/adapters/trino/tests/test_adapter_trino_validation.py +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/adapters/trino/tests/test_integration_runtime_trino.py +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/adapters/trino/tests/test_integration_trino_validation.py +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/continuo_python_runtime/__init__.py +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/continuo_python_runtime/cli.py +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/continuo_python_runtime/closure.py +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/continuo_python_runtime/conform.py +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/continuo_python_runtime/context.py +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/continuo_python_runtime/contract/__init__.py +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/continuo_python_runtime/contract/kinds.py +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/continuo_python_runtime/contract/loader.py +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/continuo_python_runtime/contract/merge.py +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/continuo_python_runtime/contract/model.py +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/continuo_python_runtime/contract/paths.py +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/continuo_python_runtime/contract/secret_ref.py +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/continuo_python_runtime/csv_loader.py +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/continuo_python_runtime/csv_readers/__init__.py +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/continuo_python_runtime/csv_readers/https.py +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/continuo_python_runtime/csv_readers/s3.py +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/continuo_python_runtime/csv_source.py +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/continuo_python_runtime/errors.py +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/continuo_python_runtime/harness.py +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/continuo_python_runtime/hashing.py +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/continuo_python_runtime/lint.py +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/continuo_python_runtime/types.py +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/continuo_python_runtime/validation/__init__.py +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/continuo_python_runtime/validation/runner.py +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/continuo_python_runtime/validation/s3.py +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/contract/LICENSE +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/contract/NOTICE +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/contract/README.md +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/contract/continuo_engine_contract/__init__.py +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/contract/continuo_engine_contract/config.py +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/contract/continuo_engine_contract/port.py +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/contract/continuo_engine_contract/result.py +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/contract/continuo_engine_contract/sql.py +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/contract/continuo_engine_contract/types.py +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/contract/pyproject.toml +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0/contract}/tests/__init__.py +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/contract/tests/test_config.py +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/contract/tests/test_contract_types.py +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/contract/tests/test_port.py +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/contract/tests/test_result.py +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/contract/tests/test_sql.py +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/scripts/security-scan.sh +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/template/.github/workflows/release.yml +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/template/Dockerfile.pip +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/template/README.md +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/template/contracts/example_csv.yml +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/template/requirements.lock +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/template/scripts/example.py +0 -0
- {continuo_python_runtime-0.7.0/tests/contract → continuo_python_runtime-0.8.0/tests}/__init__.py +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/tests/conftest.py +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/tests/contract/test_kinds.py +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/tests/contract/test_loader.py +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/tests/contract/test_merge.py +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/tests/contract/test_model.py +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/tests/smoke/node_smoke/contracts/smoke.yml +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/tests/smoke/node_smoke/scripts/smoke.py +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/tests/smoke/postgres-stack/docker-compose.yml +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/tests/smoke/trino-stack/catalog/iceberg.properties +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/tests/smoke/trino-stack/docker-compose.yml +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/tests/test_cli.py +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/tests/test_closure.py +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/tests/test_conform.py +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/tests/test_context.py +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/tests/test_contract_loader.py +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/tests/test_csv_loader.py +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/tests/test_csv_readers_https.py +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/tests/test_csv_readers_integration.py +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/tests/test_csv_source.py +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/tests/test_errors.py +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/tests/test_harness.py +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/tests/test_hashing.py +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/tests/test_image_smoke_validation.py +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/tests/test_kind_registries.py +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/tests/test_lint.py +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/tests/test_no_legacy_names.py +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/tests/test_package.py +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/tests/test_types.py +0 -0
- {continuo_python_runtime-0.7.0 → continuo_python_runtime-0.8.0}/tests/test_validation_runner.py +0 -0
- {continuo_python_runtime-0.7.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.
|
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
version: 2
|
|
8
8
|
|
|
9
9
|
updates:
|
|
10
|
-
# The uv workspace: this package (root), contract/, and the
|
|
11
|
-
#
|
|
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.
|
|
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
|
|
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
|