continuo-python-runtime 0.2.0__tar.gz → 0.3.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. {continuo_python_runtime-0.2.0 → continuo_python_runtime-0.3.1}/.dockerignore +2 -1
  2. continuo_python_runtime-0.3.1/.github/CODEOWNERS +2 -0
  3. continuo_python_runtime-0.3.1/.github/ISSUE_TEMPLATE/bug_report.yml +59 -0
  4. continuo_python_runtime-0.3.1/.github/ISSUE_TEMPLATE/config.yml +5 -0
  5. continuo_python_runtime-0.3.1/.github/ISSUE_TEMPLATE/feature_request.yml +30 -0
  6. continuo_python_runtime-0.3.1/.github/PULL_REQUEST_TEMPLATE.md +20 -0
  7. continuo_python_runtime-0.3.1/.github/dependabot.yml +64 -0
  8. continuo_python_runtime-0.3.1/.github/workflows/ci.yml +61 -0
  9. continuo_python_runtime-0.3.1/.github/workflows/dco.yml +49 -0
  10. {continuo_python_runtime-0.2.0 → continuo_python_runtime-0.3.1}/.github/workflows/images.yml +45 -14
  11. {continuo_python_runtime-0.2.0 → continuo_python_runtime-0.3.1}/.github/workflows/publish-pypi.yml +28 -19
  12. continuo_python_runtime-0.3.1/.github/workflows/release.yml +141 -0
  13. continuo_python_runtime-0.3.1/.github/workflows/security.yml +130 -0
  14. {continuo_python_runtime-0.2.0 → continuo_python_runtime-0.3.1}/.gitignore +2 -0
  15. continuo_python_runtime-0.3.1/.gitleaks.toml +36 -0
  16. continuo_python_runtime-0.3.1/CHANGELOG.md +84 -0
  17. continuo_python_runtime-0.3.1/CODE_OF_CONDUCT.md +133 -0
  18. continuo_python_runtime-0.3.1/CONTRIBUTING.md +99 -0
  19. continuo_python_runtime-0.3.1/DCO +34 -0
  20. continuo_python_runtime-0.3.1/Dockerfile.postgres +22 -0
  21. continuo_python_runtime-0.3.1/Dockerfile.trino +22 -0
  22. continuo_python_runtime-0.3.1/LICENSE +201 -0
  23. continuo_python_runtime-0.3.1/NOTICE +11 -0
  24. {continuo_python_runtime-0.2.0 → continuo_python_runtime-0.3.1}/PKG-INFO +82 -53
  25. {continuo_python_runtime-0.2.0 → continuo_python_runtime-0.3.1}/README.md +75 -50
  26. continuo_python_runtime-0.3.1/SECURITY.md +80 -0
  27. continuo_python_runtime-0.3.1/adapters/postgres/README.md +36 -0
  28. continuo_python_runtime-0.3.1/adapters/postgres/continuo_python_runtime_postgres/adapter.py +700 -0
  29. {continuo_python_runtime-0.2.0/python-runtime-postgres → continuo_python_runtime-0.3.1/adapters/postgres}/pyproject.toml +13 -5
  30. continuo_python_runtime-0.3.1/adapters/postgres/tests/test_adapter_postgres_validation.py +199 -0
  31. {continuo_python_runtime-0.2.0/python-runtime-postgres → continuo_python_runtime-0.3.1/adapters/postgres}/tests/test_adapter_runtime_postgres.py +49 -41
  32. continuo_python_runtime-0.3.1/adapters/postgres/tests/test_integration_postgres_validation.py +450 -0
  33. {continuo_python_runtime-0.2.0/python-runtime-postgres → continuo_python_runtime-0.3.1/adapters/postgres}/tests/test_integration_runtime_postgres.py +34 -6
  34. {continuo_python_runtime-0.2.0/python-runtime-trino → continuo_python_runtime-0.3.1/adapters/trino}/README.md +15 -6
  35. {continuo_python_runtime-0.2.0/python-runtime-trino → continuo_python_runtime-0.3.1/adapters/trino}/continuo_python_runtime_trino/adapter.py +194 -62
  36. {continuo_python_runtime-0.2.0/python-runtime-trino → continuo_python_runtime-0.3.1/adapters/trino}/pyproject.toml +13 -5
  37. {continuo_python_runtime-0.2.0/python-runtime-trino → continuo_python_runtime-0.3.1/adapters/trino}/tests/test_adapter_runtime_trino.py +19 -19
  38. continuo_python_runtime-0.3.1/adapters/trino/tests/test_adapter_trino_validation.py +120 -0
  39. {continuo_python_runtime-0.2.0/python-runtime-trino → continuo_python_runtime-0.3.1/adapters/trino}/tests/test_integration_runtime_trino.py +18 -14
  40. continuo_python_runtime-0.3.1/adapters/trino/tests/test_integration_trino_validation.py +469 -0
  41. {continuo_python_runtime-0.2.0 → continuo_python_runtime-0.3.1}/continuo_python_runtime/cli.py +11 -0
  42. {continuo_python_runtime-0.2.0 → continuo_python_runtime-0.3.1}/continuo_python_runtime/context.py +2 -2
  43. {continuo_python_runtime-0.2.0 → continuo_python_runtime-0.3.1}/continuo_python_runtime/contract/loader.py +3 -3
  44. {continuo_python_runtime-0.2.0 → continuo_python_runtime-0.3.1}/continuo_python_runtime/harness.py +9 -10
  45. {continuo_python_runtime-0.2.0 → continuo_python_runtime-0.3.1}/continuo_python_runtime/types.py +2 -2
  46. continuo_python_runtime-0.3.1/continuo_python_runtime/validation/__init__.py +1 -0
  47. continuo_python_runtime-0.3.1/continuo_python_runtime/validation/runner.py +245 -0
  48. continuo_python_runtime-0.3.1/continuo_python_runtime/validation/s3.py +45 -0
  49. continuo_python_runtime-0.3.1/contract/LICENSE +201 -0
  50. continuo_python_runtime-0.3.1/contract/NOTICE +12 -0
  51. continuo_python_runtime-0.3.1/contract/README.md +51 -0
  52. continuo_python_runtime-0.3.1/contract/continuo_engine_contract/__init__.py +1 -0
  53. continuo_python_runtime-0.3.1/contract/continuo_engine_contract/config.py +50 -0
  54. continuo_python_runtime-0.3.1/contract/continuo_engine_contract/port.py +189 -0
  55. continuo_python_runtime-0.3.1/contract/continuo_engine_contract/result.py +34 -0
  56. continuo_python_runtime-0.3.1/contract/continuo_engine_contract/sql.py +86 -0
  57. continuo_python_runtime-0.3.1/contract/continuo_engine_contract/types.py +23 -0
  58. continuo_python_runtime-0.3.1/contract/pyproject.toml +21 -0
  59. continuo_python_runtime-0.3.1/contract/tests/test_config.py +55 -0
  60. continuo_python_runtime-0.3.1/contract/tests/test_contract_types.py +25 -0
  61. continuo_python_runtime-0.3.1/contract/tests/test_port.py +349 -0
  62. continuo_python_runtime-0.3.1/contract/tests/test_result.py +40 -0
  63. continuo_python_runtime-0.3.1/contract/tests/test_sql.py +103 -0
  64. {continuo_python_runtime-0.2.0 → continuo_python_runtime-0.3.1}/docs/boundary-contract.md +23 -22
  65. {continuo_python_runtime-0.2.0 → continuo_python_runtime-0.3.1}/pyproject.toml +28 -10
  66. continuo_python_runtime-0.3.1/scripts/security-scan.sh +178 -0
  67. {continuo_python_runtime-0.2.0 → continuo_python_runtime-0.3.1}/template/Dockerfile +7 -5
  68. {continuo_python_runtime-0.2.0 → continuo_python_runtime-0.3.1}/tests/conftest.py +7 -7
  69. continuo_python_runtime-0.3.1/tests/contract/__init__.py +0 -0
  70. {continuo_python_runtime-0.2.0 → continuo_python_runtime-0.3.1}/tests/contract/test_loader.py +1 -1
  71. {continuo_python_runtime-0.2.0 → continuo_python_runtime-0.3.1}/tests/smoke/postgres-stack/docker-compose.yml +1 -1
  72. {continuo_python_runtime-0.2.0 → continuo_python_runtime-0.3.1}/tests/smoke/trino-stack/docker-compose.yml +4 -4
  73. {continuo_python_runtime-0.2.0 → continuo_python_runtime-0.3.1}/tests/test_cli.py +15 -3
  74. continuo_python_runtime-0.3.1/tests/test_contract_pin_consistency.py +49 -0
  75. {continuo_python_runtime-0.2.0 → continuo_python_runtime-0.3.1}/tests/test_harness.py +33 -33
  76. continuo_python_runtime-0.3.1/tests/test_image_smoke_validation.py +69 -0
  77. continuo_python_runtime-0.3.1/tests/test_no_legacy_names.py +49 -0
  78. {continuo_python_runtime-0.2.0 → continuo_python_runtime-0.3.1}/tests/test_template.py +18 -5
  79. {continuo_python_runtime-0.2.0 → continuo_python_runtime-0.3.1}/tests/test_types.py +2 -2
  80. continuo_python_runtime-0.3.1/tests/test_validation_runner.py +880 -0
  81. continuo_python_runtime-0.3.1/tests/test_validation_s3.py +41 -0
  82. {continuo_python_runtime-0.2.0 → continuo_python_runtime-0.3.1}/uv.lock +79 -27
  83. continuo_python_runtime-0.2.0/.github/workflows/ci.yml +0 -25
  84. continuo_python_runtime-0.2.0/Dockerfile.postgres +0 -17
  85. continuo_python_runtime-0.2.0/Dockerfile.trino +0 -17
  86. continuo_python_runtime-0.2.0/python-runtime-postgres/README.md +0 -18
  87. continuo_python_runtime-0.2.0/python-runtime-postgres/continuo_python_runtime_postgres/adapter.py +0 -442
  88. {continuo_python_runtime-0.2.0/python-runtime-postgres → continuo_python_runtime-0.3.1/adapters/postgres}/continuo_python_runtime_postgres/__init__.py +0 -0
  89. {continuo_python_runtime-0.2.0/python-runtime-postgres → continuo_python_runtime-0.3.1/adapters/postgres}/tests/__init__.py +0 -0
  90. {continuo_python_runtime-0.2.0/python-runtime-trino → continuo_python_runtime-0.3.1/adapters/trino}/continuo_python_runtime_trino/__init__.py +0 -0
  91. {continuo_python_runtime-0.2.0/python-runtime-trino → continuo_python_runtime-0.3.1/adapters/trino}/tests/__init__.py +0 -0
  92. {continuo_python_runtime-0.2.0 → continuo_python_runtime-0.3.1}/continuo_python_runtime/__init__.py +0 -0
  93. {continuo_python_runtime-0.2.0 → continuo_python_runtime-0.3.1}/continuo_python_runtime/closure.py +0 -0
  94. {continuo_python_runtime-0.2.0 → continuo_python_runtime-0.3.1}/continuo_python_runtime/conform.py +0 -0
  95. {continuo_python_runtime-0.2.0 → continuo_python_runtime-0.3.1}/continuo_python_runtime/contract/__init__.py +0 -0
  96. {continuo_python_runtime-0.2.0 → continuo_python_runtime-0.3.1}/continuo_python_runtime/contract/merge.py +0 -0
  97. {continuo_python_runtime-0.2.0 → continuo_python_runtime-0.3.1}/continuo_python_runtime/contract/model.py +0 -0
  98. {continuo_python_runtime-0.2.0 → continuo_python_runtime-0.3.1}/continuo_python_runtime/contract/paths.py +0 -0
  99. {continuo_python_runtime-0.2.0 → continuo_python_runtime-0.3.1}/continuo_python_runtime/errors.py +0 -0
  100. {continuo_python_runtime-0.2.0 → continuo_python_runtime-0.3.1}/continuo_python_runtime/hashing.py +0 -0
  101. {continuo_python_runtime-0.2.0 → continuo_python_runtime-0.3.1}/continuo_python_runtime/lint.py +0 -0
  102. {continuo_python_runtime-0.2.0 → continuo_python_runtime-0.3.1/contract}/tests/__init__.py +0 -0
  103. {continuo_python_runtime-0.2.0 → continuo_python_runtime-0.3.1}/docs/superpowers/plans/2026-07-31-python-runtime.md +0 -0
  104. {continuo_python_runtime-0.2.0 → continuo_python_runtime-0.3.1}/docs/superpowers/plans/2026-08-07-step-3b-config-three-part-hash.md +0 -0
  105. {continuo_python_runtime-0.2.0 → continuo_python_runtime-0.3.1}/docs/superpowers/specs/2026-07-31-python-runtime-design.md +0 -0
  106. {continuo_python_runtime-0.2.0 → continuo_python_runtime-0.3.1}/template/.github/workflows/release.yml +0 -0
  107. {continuo_python_runtime-0.2.0 → continuo_python_runtime-0.3.1}/template/README.md +0 -0
  108. {continuo_python_runtime-0.2.0 → continuo_python_runtime-0.3.1}/template/contracts/example.yml +0 -0
  109. {continuo_python_runtime-0.2.0 → continuo_python_runtime-0.3.1}/template/scripts/example.py +0 -0
  110. {continuo_python_runtime-0.2.0/tests/contract → continuo_python_runtime-0.3.1/tests}/__init__.py +0 -0
  111. {continuo_python_runtime-0.2.0 → continuo_python_runtime-0.3.1}/tests/contract/test_merge.py +0 -0
  112. {continuo_python_runtime-0.2.0 → continuo_python_runtime-0.3.1}/tests/contract/test_model.py +0 -0
  113. {continuo_python_runtime-0.2.0 → continuo_python_runtime-0.3.1}/tests/smoke/node_smoke/contracts/smoke.yml +0 -0
  114. {continuo_python_runtime-0.2.0 → continuo_python_runtime-0.3.1}/tests/smoke/node_smoke/scripts/smoke.py +0 -0
  115. {continuo_python_runtime-0.2.0 → continuo_python_runtime-0.3.1}/tests/smoke/trino-stack/catalog/iceberg.properties +0 -0
  116. {continuo_python_runtime-0.2.0 → continuo_python_runtime-0.3.1}/tests/test_closure.py +0 -0
  117. {continuo_python_runtime-0.2.0 → continuo_python_runtime-0.3.1}/tests/test_conform.py +0 -0
  118. {continuo_python_runtime-0.2.0 → continuo_python_runtime-0.3.1}/tests/test_context.py +0 -0
  119. {continuo_python_runtime-0.2.0 → continuo_python_runtime-0.3.1}/tests/test_errors.py +0 -0
  120. {continuo_python_runtime-0.2.0 → continuo_python_runtime-0.3.1}/tests/test_hashing.py +0 -0
  121. {continuo_python_runtime-0.2.0 → continuo_python_runtime-0.3.1}/tests/test_lint.py +0 -0
  122. {continuo_python_runtime-0.2.0 → continuo_python_runtime-0.3.1}/tests/test_package.py +0 -0
@@ -11,4 +11,5 @@ dist/
11
11
  docs/
12
12
  template/
13
13
  tests/
14
- python-runtime-*/tests/
14
+ adapters/*/tests/
15
+ contract/tests/
@@ -0,0 +1,2 @@
1
+ # Every change is reviewed by the maintainer.
2
+ * @carolsimone
@@ -0,0 +1,59 @@
1
+ name: Bug report
2
+ description: Something in the Continuo Python runtime behaves incorrectly.
3
+ labels: ["bug"]
4
+ body:
5
+ - type: markdown
6
+ attributes:
7
+ value: |
8
+ Please do not report security vulnerabilities here — see SECURITY.md.
9
+
10
+ - type: textarea
11
+ id: what-happened
12
+ attributes:
13
+ label: What happened
14
+ description: What did you observe, and what did you expect instead?
15
+ validations:
16
+ required: true
17
+
18
+ - type: textarea
19
+ id: reproduce
20
+ attributes:
21
+ label: Steps to reproduce
22
+ placeholder: |
23
+ 1. Write a contract with ...
24
+ 2. Run `continuo-runtime validate ...`
25
+ 3. Observe ...
26
+ validations:
27
+ required: true
28
+
29
+ - type: dropdown
30
+ id: component
31
+ attributes:
32
+ label: Which component
33
+ multiple: true
34
+ options:
35
+ - continuo_python_runtime (harness / CLI)
36
+ - contract (continuo-engine-contract)
37
+ - adapters/postgres
38
+ - adapters/trino
39
+ - template
40
+ - CI (.github/workflows)
41
+ - Not sure
42
+ validations:
43
+ required: true
44
+
45
+ - type: input
46
+ id: version
47
+ attributes:
48
+ label: Version
49
+ description: Package version (PyPI) or commit SHA.
50
+ placeholder: "v0.3.0"
51
+ validations:
52
+ required: true
53
+
54
+ - type: textarea
55
+ id: logs
56
+ attributes:
57
+ label: Relevant logs
58
+ description: Please redact any credentials before pasting.
59
+ render: shell
@@ -0,0 +1,5 @@
1
+ blank_issues_enabled: false
2
+ contact_links:
3
+ - name: Security vulnerability
4
+ url: https://github.com/carolsimone/continuo-python-runtime/security/advisories/new
5
+ about: Report privately. Please do not open a public issue for security problems.
@@ -0,0 +1,30 @@
1
+ name: Feature request
2
+ description: Propose a change or addition to the Continuo Python runtime.
3
+ labels: ["enhancement"]
4
+ body:
5
+ - type: textarea
6
+ id: problem
7
+ attributes:
8
+ label: What problem does this solve
9
+ description: Describe the situation you are in, not the solution you have in mind.
10
+ validations:
11
+ required: true
12
+
13
+ - type: textarea
14
+ id: proposal
15
+ attributes:
16
+ label: What you would like to happen
17
+ validations:
18
+ required: true
19
+
20
+ - type: textarea
21
+ id: alternatives
22
+ attributes:
23
+ label: Alternatives you considered
24
+
25
+ - type: checkboxes
26
+ id: contribute
27
+ attributes:
28
+ label: Contribution
29
+ options:
30
+ - label: I am willing to open a pull request for this.
@@ -0,0 +1,20 @@
1
+ ## What this changes
2
+
3
+ <!-- One or two sentences. Link the issue this addresses. -->
4
+
5
+ Closes #
6
+
7
+ ## Why
8
+
9
+ <!-- What problem does this solve? -->
10
+
11
+ ## How it was verified
12
+
13
+ <!-- The commands you ran and what they printed. "Tests pass" on its own is not enough. -->
14
+
15
+ ## Checklist
16
+
17
+ - [ ] Every commit is signed off (`git commit -s`) — the `dco` check enforces this
18
+ - [ ] Tests added or updated for the behavior changed
19
+ - [ ] `uv run ruff check .` and `uv run mypy` (relevant packages) pass
20
+ - [ ] `scripts/security-scan.sh` passes (dependency or credential-adjacent changes)
@@ -0,0 +1,64 @@
1
+ # Dependabot keeps dependencies current so that known CVEs are fixed by an
2
+ # upgrade before a scanner has to report them.
3
+ #
4
+ # Every ecosystem groups its patch and minor updates into a single pull request
5
+ # per week to keep the noise down. Major updates stay ungrouped because they
6
+ # carry breaking changes and need to be reviewed one at a time.
7
+ version: 2
8
+
9
+ updates:
10
+ # The uv workspace: this package (root), contract/, and the two adapters. All
11
+ # four share one lockfile, but Dependabot still needs one entry per pyproject.toml
12
+ # directory to see each package's own dependency declarations.
13
+ - package-ecosystem: uv
14
+ directories:
15
+ - /
16
+ - /contract
17
+ - /adapters/postgres
18
+ - /adapters/trino
19
+ schedule:
20
+ interval: weekly
21
+ open-pull-requests-limit: 5
22
+ groups:
23
+ python-minor-patch:
24
+ applies-to: version-updates
25
+ update-types: [minor, patch]
26
+ python-security:
27
+ applies-to: security-updates
28
+ patterns: ["*"]
29
+ commit-message:
30
+ prefix: chore(deps)
31
+
32
+ # Actions are a supply-chain surface in their own right: a compromised or
33
+ # abandoned action runs with access to the workflow token.
34
+ - package-ecosystem: github-actions
35
+ directory: /
36
+ schedule:
37
+ interval: weekly
38
+ open-pull-requests-limit: 5
39
+ groups:
40
+ actions:
41
+ applies-to: version-updates
42
+ update-types: [minor, patch]
43
+ actions-security:
44
+ applies-to: security-updates
45
+ patterns: ["*"]
46
+ commit-message:
47
+ prefix: chore(ci)
48
+
49
+ # Dockerfile.postgres and Dockerfile.trino, the published engine images. Most
50
+ # base-image CVEs are fixed by moving to a newer tag.
51
+ - package-ecosystem: docker
52
+ directory: /
53
+ schedule:
54
+ interval: weekly
55
+ open-pull-requests-limit: 5
56
+ groups:
57
+ docker:
58
+ applies-to: version-updates
59
+ update-types: [minor, patch]
60
+ docker-security:
61
+ applies-to: security-updates
62
+ patterns: ["*"]
63
+ commit-message:
64
+ prefix: chore(deps)
@@ -0,0 +1,61 @@
1
+ name: ci
2
+ on:
3
+ push: { branches: [main] }
4
+ pull_request:
5
+ jobs:
6
+ test:
7
+ runs-on: ubuntu-latest
8
+ steps:
9
+ - uses: actions/checkout@v4
10
+ - uses: astral-sh/setup-uv@v7
11
+ - name: Sync
12
+ run: uv sync --all-packages --all-groups
13
+ - name: Lint
14
+ run: uv run ruff check .
15
+ - name: Lint (contract)
16
+ run: uv run ruff check contract
17
+ - name: Types (runtime)
18
+ run: uv run mypy continuo_python_runtime
19
+ - name: Types (contract)
20
+ run: uv run mypy contract/continuo_engine_contract
21
+ - name: Types (postgres adapter)
22
+ run: uv run --package continuo-python-runtime-postgres mypy adapters/postgres/continuo_python_runtime_postgres
23
+ - name: Types (trino adapter)
24
+ run: uv run --package continuo-python-runtime-trino mypy adapters/trino/continuo_python_runtime_trino
25
+ # `-m "not image"` deselects tests/test_image_smoke_validation.py, which
26
+ # needs a built engine image and the env naming it. Those tests run in
27
+ # images.yml's smoke jobs, where an image actually exists.
28
+ - name: Tests (runtime)
29
+ run: uv run pytest --cov=continuo_python_runtime -m "not image" -v
30
+ - name: Tests (contract)
31
+ run: uv run pytest contract/tests -v
32
+ - name: Tests (adapter units)
33
+ run: uv run pytest adapters/postgres/tests adapters/trino/tests -m "not integration" -v
34
+
35
+ integration-postgres:
36
+ runs-on: ubuntu-latest
37
+ steps:
38
+ - uses: actions/checkout@v4
39
+ - uses: astral-sh/setup-uv@v7
40
+ - run: uv sync --all-packages --all-groups
41
+ - name: Start postgres stack
42
+ run: docker compose -f tests/smoke/postgres-stack/docker-compose.yml up -d --wait
43
+ - name: Tests (postgres integration)
44
+ run: uv run pytest adapters/postgres/tests -m integration -v
45
+ - name: Tear down postgres stack
46
+ if: always()
47
+ run: docker compose -f tests/smoke/postgres-stack/docker-compose.yml down -v
48
+
49
+ integration-trino:
50
+ runs-on: ubuntu-latest
51
+ steps:
52
+ - uses: actions/checkout@v4
53
+ - uses: astral-sh/setup-uv@v7
54
+ - run: uv sync --all-packages --all-groups
55
+ - name: Start trino stack
56
+ run: docker compose -f tests/smoke/trino-stack/docker-compose.yml up -d --wait
57
+ - name: Tests (trino integration)
58
+ run: uv run pytest adapters/trino/tests -m integration -v
59
+ - name: Tear down trino stack
60
+ if: always()
61
+ run: docker compose -f tests/smoke/trino-stack/docker-compose.yml down -v
@@ -0,0 +1,49 @@
1
+ name: dco
2
+
3
+ on:
4
+ pull_request:
5
+ branches:
6
+ - main
7
+
8
+ permissions:
9
+ contents: read
10
+
11
+ jobs:
12
+ # Job name becomes the check-run name, which is what a branch ruleset requires
13
+ # by string. Keep it "dco" so the required check is unambiguous.
14
+ dco:
15
+ runs-on: ubuntu-latest
16
+ steps:
17
+ - uses: actions/checkout@v4
18
+ with:
19
+ # The whole PR commit range has to be walkable, not just the merge commit.
20
+ fetch-depth: 0
21
+
22
+ - name: Verify every commit is signed off
23
+ env:
24
+ BASE_SHA: ${{ github.event.pull_request.base.sha }}
25
+ HEAD_SHA: ${{ github.event.pull_request.head.sha }}
26
+ run: |
27
+ set -euo pipefail
28
+ missing=0
29
+ while read -r sha; do
30
+ [ -z "$sha" ] && continue
31
+ # Merge commits are generated by GitHub and carry no sign-off.
32
+ if [ "$(git rev-list --no-walk --count --merges "$sha")" -eq 1 ]; then
33
+ continue
34
+ fi
35
+ if ! git show -s --format='%B' "$sha" | grep -qiE '^Signed-off-by: .+ <.+@.+>$'; then
36
+ echo "::error::Commit $sha ($(git show -s --format='%an' "$sha")) has no Signed-off-by line."
37
+ missing=1
38
+ fi
39
+ done < <(git rev-list "$BASE_SHA".."$HEAD_SHA")
40
+
41
+ if [ "$missing" -ne 0 ]; then
42
+ echo ""
43
+ echo "Every commit needs a Developer Certificate of Origin sign-off (see DCO)."
44
+ echo "Fix an existing branch with:"
45
+ echo " git rebase --signoff origin/main && git push --force-with-lease"
46
+ echo "Sign off future commits automatically with 'git commit -s'."
47
+ exit 1
48
+ fi
49
+ echo "All commits signed off."
@@ -5,8 +5,8 @@ on:
5
5
  - "Dockerfile.*"
6
6
  - "tests/smoke/**"
7
7
  - "continuo_python_runtime/**"
8
- - "python-runtime-postgres/**"
9
- - "python-runtime-trino/**"
8
+ - "contract/**"
9
+ - "adapters/**"
10
10
  - "pyproject.toml"
11
11
  - ".github/workflows/images.yml"
12
12
  push:
@@ -56,6 +56,16 @@ jobs:
56
56
  path: /tmp
57
57
  - name: Load image
58
58
  run: docker load -i /tmp/cpr-smoke-postgres.tar
59
+ - uses: astral-sh/setup-uv@v7
60
+ - name: Sync
61
+ run: uv sync --all-packages --all-groups
62
+ - name: Image smoke tests (postgres)
63
+ env:
64
+ VALIDATION_IMAGE_UNDER_TEST: cpr-smoke-postgres
65
+ VALIDATION_IMAGE_ENGINE: postgres
66
+ # PostgresAdapter.required_env()[0]
67
+ VALIDATION_IMAGE_REQUIRED_ENV: POSTGRES_HOST
68
+ run: uv run pytest tests/test_image_smoke_validation.py -m image -v
59
69
  - name: Install psql client
60
70
  run: |
61
71
  sudo apt-get update
@@ -101,6 +111,16 @@ jobs:
101
111
  path: /tmp
102
112
  - name: Load image
103
113
  run: docker load -i /tmp/cpr-smoke-trino.tar
114
+ - uses: astral-sh/setup-uv@v7
115
+ - name: Sync
116
+ run: uv sync --all-packages --all-groups
117
+ - name: Image smoke tests (trino)
118
+ env:
119
+ VALIDATION_IMAGE_UNDER_TEST: cpr-smoke-trino
120
+ VALIDATION_IMAGE_ENGINE: trino
121
+ # TrinoAdapter.required_env()[0]
122
+ VALIDATION_IMAGE_REQUIRED_ENV: TRINO_HOST
123
+ run: uv run pytest tests/test_image_smoke_validation.py -m image -v
104
124
  - name: Start trino stack
105
125
  run: docker compose -f tests/smoke/trino-stack/docker-compose.yml up -d --wait
106
126
  - name: Seed source table
@@ -133,13 +153,20 @@ jobs:
133
153
  if: always()
134
154
  run: docker compose -f tests/smoke/trino-stack/docker-compose.yml down -v
135
155
 
136
- # On tag push: log in to ghcr.io and push ghcr.io/${{ github.repository }}:<tag>-postgres
137
- # and :<tag>-trino. continuo-validation-contract, continuo-python-runtime-postgres,
138
- # and continuo-python-runtime-trino are all installed from the build context /
139
- # PyPI already (see Dockerfile.postgres / Dockerfile.trino and
140
- # publish-pypi.yml) — nothing pending here.
156
+ # On tag push: build both engine images for linux/amd64 and linux/arm64 and
157
+ # push them to ghcr.io/<owner>/continuo-python-runtime-<engine>:<tag>. The
158
+ # engine belongs in the image NAME, not the tag: Continuo's Helm chart pins an
159
+ # image as `<name>:vX.Y.Z@sha256:<digest>`, so the tag must be the bare
160
+ # version. Both platforms are required: domain repos build their own images
161
+ # FROM these, and Continuo's e2e runs them in a kind cluster on aarch64
162
+ # developer machines as well as on amd64 CI runners, so a single-arch publish
163
+ # is unusable off amd64. The smoke jobs above stay single-arch — they load a
164
+ # `docker save` tarball, which has no multi-platform equivalent.
165
+ # continuo-engine-contract and the two engine adapters are installed from the
166
+ # build context (see Dockerfile.postgres / Dockerfile.trino) — nothing pending
167
+ # here.
141
168
  publish:
142
- if: startsWith(github.ref, 'refs/tags/v')
169
+ if: startsWith(github.ref, 'refs/tags/v') && !contains(github.ref_name, '-test')
143
170
  needs: [smoke-postgres, smoke-trino]
144
171
  strategy:
145
172
  matrix:
@@ -150,15 +177,19 @@ jobs:
150
177
  packages: write
151
178
  steps:
152
179
  - uses: actions/checkout@v4
180
+ - uses: docker/setup-qemu-action@v3
181
+ - uses: docker/setup-buildx-action@v4
153
182
  - name: Log in to ghcr.io
154
- uses: docker/login-action@v3
183
+ uses: docker/login-action@v4
155
184
  with:
156
185
  registry: ghcr.io
157
186
  username: ${{ github.actor }}
158
187
  password: ${{ secrets.GITHUB_TOKEN }}
159
188
  - name: Build and push ${{ matrix.engine }} image
160
- run: |
161
- tag="${GITHUB_REF_NAME}"
162
- image="ghcr.io/${{ github.repository }}:${tag}-${{ matrix.engine }}"
163
- docker build -f Dockerfile.${{ matrix.engine }} -t "$image" .
164
- docker push "$image"
189
+ uses: docker/build-push-action@v7
190
+ with:
191
+ context: .
192
+ file: Dockerfile.${{ matrix.engine }}
193
+ platforms: linux/amd64,linux/arm64
194
+ push: true
195
+ tags: ghcr.io/${{ github.repository_owner }}/continuo-python-runtime-${{ matrix.engine }}:${{ github.ref_name }}
@@ -1,37 +1,42 @@
1
1
  name: publish-pypi
2
2
 
3
- # Publishes the continuo-python-runtime package via PyPI Trusted Publishing
4
- # (OIDC, no stored token).
3
+ # Publishes both PyPI distributions this repo owns — continuo-python-runtime
4
+ # (the harness) and continuo-engine-contract (the port, result-block format,
5
+ # and shared guards) — via PyPI Trusted Publishing (OIDC, no stored token).
5
6
  #
6
7
  # The two engine adapters (continuo-python-runtime-postgres /
7
8
  # continuo-python-runtime-trino) are deliberately NOT published here. Nothing
8
- # installs them from an index: the runtime images build them from the build
9
+ # installs them from an index: the engine images build them from the build
9
10
  # context (see Dockerfile.postgres / Dockerfile.trino and images.yml), and the
10
11
  # harness package does not depend on them — adapters are found at run time
11
- # through the `continuo_runtime.adapters` entry-point group, inside the image
12
+ # through the `continuo_engine.adapters` entry-point group, inside the image
12
13
  # that installed exactly one. Publishing them would only add releases with no
13
14
  # consumer. They remain uv workspace members and are still built, typed, and
14
15
  # tested by ci.yml.
15
16
  #
16
- # Tag glob note: "python-runtime-v*" requires a literal "v" immediately after
17
- # the dash, so it matches only the harness tag (python-runtime-v1.2.3[-testN]).
18
- # Tag `<pkg>-v<ver>-test<n>` publishes to TestPyPI; `<pkg>-v<ver>` publishes to
19
- # real PyPI. The GitHub environment name is what the PyPI "pending publisher"
20
- # is registered against.
17
+ # Tag glob note: `v*` is the repository's single release pattern — the same tag
18
+ # that images.yml builds and pushes both engine images from, so one tag ships
19
+ # the whole release. GitHub Actions tag globs anchor at character 1, so `v*`
20
+ # claims every tag beginning with "v"; no other pattern may be introduced.
21
+ # Tag `v<ver>-test<n>` publishes to TestPyPI; `v<ver>` publishes to real PyPI.
22
+ # The GitHub environment name is what the PyPI "pending publisher" is
23
+ # registered against — both project names need one on each index.
21
24
  #
22
- # Ordering: continuo-validation-contract==0.4.0 must exist on the target index
23
- # before publishing here — consumers of the wheel resolve it from that index
24
- # ([tool.uv.sources] is dev-only and not embedded in the wheel).
25
+ # Both distributions are built into a single `dist/` and uploaded in one
26
+ # publish call, so there is no ordering constraint between them. The runtime
27
+ # wheel declares continuo-engine-contract as a dependency and resolves it from
28
+ # the index at install time ([tool.uv.sources] is dev-only and is not embedded
29
+ # in the wheel).
25
30
  on:
26
31
  push:
27
32
  tags:
28
- - "python-runtime-v*"
33
+ - "v*"
29
34
 
30
35
  permissions: {}
31
36
 
32
37
  jobs:
33
- publish-runtime:
34
- if: startsWith(github.ref_name, 'python-runtime-v')
38
+ publish-packages:
39
+ if: startsWith(github.ref_name, 'v')
35
40
  runs-on: ubuntu-latest
36
41
  environment: ${{ contains(github.ref_name, '-test') && 'testpypi' || 'pypi' }}
37
42
  permissions:
@@ -39,12 +44,16 @@ jobs:
39
44
  contents: read # actions/checkout needs read access to the repo
40
45
  steps:
41
46
  - uses: actions/checkout@v4
42
- - uses: astral-sh/setup-uv@v5
47
+ - uses: astral-sh/setup-uv@v7
43
48
  - name: Test before publishing
49
+ # `-m "not image"` deselects the image smoke tests; images.yml runs
50
+ # those against a built engine image.
44
51
  run: |
45
- uv sync --all-groups
46
- uv run pytest -q
47
- - name: Build the sdist + wheel
52
+ uv sync --all-packages --all-groups
53
+ uv run pytest tests contract/tests -m "not image" -q
54
+ - name: Build the contract sdist + wheel
55
+ run: uv build --package continuo-engine-contract -o dist
56
+ - name: Build the runtime sdist + wheel
48
57
  run: uv build -o dist
49
58
  - name: Publish to TestPyPI
50
59
  if: contains(github.ref_name, '-test')
@@ -0,0 +1,141 @@
1
+ name: release
2
+
3
+ # Creates a GitHub Release once BOTH tag-triggered publish workflows
4
+ # (publish-pypi.yml, images.yml) have finished successfully for this exact
5
+ # tag. A Release exists here beyond what PyPI already shows because a tag
6
+ # also ships two ghcr.io images that PyPI knows nothing about — the Release
7
+ # is the one place that says "this tag = these packages + these images."
8
+ #
9
+ # Triggered by the same tag push as its two siblings, rather than by
10
+ # workflow_run watching them complete: `needs:` cannot cross workflow files,
11
+ # and this avoids depending on workflow_run's head_branch semantics for a
12
+ # tag-triggered source run, which nothing else in this repo relies on.
13
+ # Instead the job below polls the Actions API for the sibling runs at this
14
+ # exact commit until both report a conclusion — that only depends on the
15
+ # same push:tags trigger every other release workflow here already uses.
16
+ on:
17
+ push:
18
+ tags:
19
+ # Digit after v: keeps this to release tags, same reasoning continuo's
20
+ # release.yml uses. publish-pypi.yml and images.yml use the looser `v*`
21
+ # and instead guard internally with startsWith(..., 'v') checks, so
22
+ # this is deliberately the stricter of the three triggers.
23
+ - "v[0-9]*"
24
+
25
+ concurrency:
26
+ group: release-${{ github.ref }}
27
+ cancel-in-progress: false
28
+
29
+ permissions:
30
+ contents: read
31
+
32
+ jobs:
33
+ github-release:
34
+ # -test tags are a TestPyPI dry run (see publish-pypi.yml) and skip
35
+ # images.yml's publish job by design (its own if: guards on this same
36
+ # condition) — no public Release for them.
37
+ if: "!contains(github.ref_name, '-test')"
38
+ runs-on: ubuntu-latest
39
+ # Each wait_for call below can itself run up to 30 minutes (its own
40
+ # internal timeout), called twice, sequentially — so this job's ceiling
41
+ # has to clear 60 minutes on its own polling math alone, before even
42
+ # accounting for how long the siblings actually take to run.
43
+ timeout-minutes: 70
44
+ permissions:
45
+ contents: write # gh release create/edit
46
+ actions: read # poll sibling workflow runs
47
+ env:
48
+ TAG: ${{ github.ref_name }}
49
+ SHA: ${{ github.sha }}
50
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
51
+ steps:
52
+ - name: Wait for publish-pypi.yml and images.yml to finish on this commit
53
+ run: |
54
+ set -euo pipefail
55
+
56
+ wait_for() {
57
+ local workflow_file="$1" elapsed=0 interval=20 timeout=1800
58
+ while :; do
59
+ # event=push, not just head_sha: images.yml also triggers on
60
+ # pull_request, and a coincidentally-identical head_sha between
61
+ # an open PR and this tag would otherwise pick up the wrong run.
62
+ # Query params go in the URL, not via -f: gh api switches a
63
+ # request to POST the moment any -f/-F flag is present, and this
64
+ # listing endpoint only accepts GET — it 404s on POST, which
65
+ # under `set -e` would abort this script on the very first poll,
66
+ # every time. Verified against this repo's real API: -f flags
67
+ # 404 here, the URL query string form returns real run data.
68
+ conclusion="$(gh api "repos/${GITHUB_REPOSITORY}/actions/workflows/${workflow_file}/runs?head_sha=${SHA}&event=push" \
69
+ --jq '.workflow_runs[0].conclusion // "pending"')"
70
+ case "${conclusion}" in
71
+ success)
72
+ echo "${workflow_file}: success"
73
+ return 0
74
+ ;;
75
+ pending)
76
+ # Covers both "no matching run yet" (API eventual
77
+ # consistency right after the push) and "run exists but
78
+ # still queued/in_progress" — conclusion is JSON null in
79
+ # both cases until the run completes.
80
+ ;;
81
+ *)
82
+ echo "::error::${workflow_file} concluded '${conclusion}' for ${SHA}; not creating a Release."
83
+ exit 1
84
+ ;;
85
+ esac
86
+ elapsed=$((elapsed + interval))
87
+ if [ "${elapsed}" -ge "${timeout}" ]; then
88
+ echo "::error::timed out after ${timeout}s waiting for ${workflow_file} to finish on ${SHA}."
89
+ exit 1
90
+ fi
91
+ sleep "${interval}"
92
+ done
93
+ }
94
+
95
+ wait_for "publish-pypi.yml"
96
+ wait_for "images.yml"
97
+
98
+ - name: Extract this version's CHANGELOG section
99
+ id: notes
100
+ run: |
101
+ set -euo pipefail
102
+ VERSION="${TAG#v}"
103
+ # Print the body between "## [X.Y.Z]" and the next "## [" heading.
104
+ if grep -qE "^## \[${VERSION}\]" CHANGELOG.md; then
105
+ awk -v ver="## [${VERSION}]" '
106
+ index($0, ver) == 1 { found = 1; next }
107
+ found && /^## \[/ { exit }
108
+ found { print }
109
+ ' CHANGELOG.md > /tmp/release-body.md
110
+ fi
111
+
112
+ if [ -s /tmp/release-body.md ]; then
113
+ echo "generate=false" >> "$GITHUB_OUTPUT"
114
+ else
115
+ # A tag with no matching CHANGELOG.md section (e.g. someone
116
+ # forgot to add one before tagging) still gets a Release — just
117
+ # with GitHub's generated notes instead of failing the job over
118
+ # a missing doc update.
119
+ echo "::warning::no '## [${VERSION}]' section in CHANGELOG.md; falling back to generated notes."
120
+ echo "generate=true" >> "$GITHUB_OUTPUT"
121
+ fi
122
+
123
+ - name: Create the Release
124
+ run: |
125
+ set -euo pipefail
126
+ # Idempotent: re-running this workflow for an existing tag updates
127
+ # the Release rather than failing.
128
+ if gh release view "${TAG}" --repo "${GITHUB_REPOSITORY}" >/dev/null 2>&1; then
129
+ if [ "${{ steps.notes.outputs.generate }}" = "true" ]; then
130
+ gh release edit "${TAG}" --repo "${GITHUB_REPOSITORY}" --latest
131
+ else
132
+ gh release edit "${TAG}" --repo "${GITHUB_REPOSITORY}" \
133
+ --notes-file /tmp/release-body.md --latest
134
+ fi
135
+ elif [ "${{ steps.notes.outputs.generate }}" = "true" ]; then
136
+ gh release create "${TAG}" --repo "${GITHUB_REPOSITORY}" \
137
+ --title "${TAG}" --generate-notes --latest
138
+ else
139
+ gh release create "${TAG}" --repo "${GITHUB_REPOSITORY}" \
140
+ --title "${TAG}" --notes-file /tmp/release-body.md --latest
141
+ fi