Skip to content

Commit 2c2a998

Browse files
chore(docs): cleaner wording
1 parent 4f71d9b commit 2c2a998

11 files changed

Lines changed: 62 additions & 78 deletions

File tree

‎.config/mise.toml‎

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -29,40 +29,40 @@ description = "Static checks: ruff, shellcheck, and actionlint via pre-commit"
2929
run = "uv run pre-commit run --all-files"
3030

3131
[tasks."test:tools"]
32-
description = "L1: test the shared shell scripts offline (bats)"
32+
description = "Test shell scripts offline with bats"
3333
run = "bats generator/scripts/tests"
3434

3535
[tasks."test:preprocess"]
36-
description = "L2: test spec patching and the endpoint checker"
36+
description = "Test spec patching and endpoint coverage"
3737
run = "uv run python -m unittest generator.tests.test_strip_schema_titles generator.tests.test_openapi_overlay generator.tests.test_endpoint_coverage"
3838

3939
[tasks."test:unit"]
40-
description = "L4: test the generated SDK and contract-case derivation offline"
40+
description = "Run unit tests offline"
4141
run = "uv run python -m unittest tests.test_sdk_smoke tests.test_wiretap_cases"
4242

4343
[tasks."check:generated"]
44-
description = "L3: fail if regeneration changes upcloud_api/ or the patched spec"
44+
description = "Fail if regeneration changes upcloud_api/ or the patched spec"
4545
run = "bash generator/scripts/check_generated.sh upcloud_api generator/openapi/spec.patched.json -- mise run generate --force"
4646

4747
[tasks."check:endpoints"]
48-
description = "L3: check that every spec operation has generated code"
48+
description = "Check that every spec operation has generated code"
4949
run = "uv run python generator/scripts/check_endpoint_coverage.py generator/openapi/spec.json generator/openapi/spec.patched.json upcloud_api/api"
5050

5151
[tasks."test:contract"]
52-
description = "L5: run SDK calls against a local Wiretap mock; writes validation-artifacts/"
52+
description = "Run SDK calls against a local Wiretap mock (writes validation-artifacts/)"
5353
run = 'VALIDATION_ARTIFACTS="${VALIDATION_ARTIFACTS:-validation-artifacts}" bash generator/scripts/wiretap.sh generator/openapi/spec.patched.json uv run python -m unittest tests.test_wiretap'
5454

5555
[tasks.check]
5656
alias = "test"
57-
description = "Integrity gate (must be green): lint, test:tools, test:preprocess, test:unit, check:generated"
57+
description = "Run required checks: lint, test:tools, test:preprocess, test:unit, check:generated"
5858
run = "mise run --jobs 1 lint ::: test:tools ::: test:preprocess ::: test:unit ::: check:generated"
5959

6060
[tasks.findings]
61-
description = "Findings gate (red on known spec/generator defects): check:endpoints, test:contract"
61+
description = "Run check:endpoints and test:contract (fails on known spec/generator issues)"
6262
run = "mise run --continue-on-error --jobs 1 check:endpoints ::: test:contract"
6363

6464
[tasks.validate]
65-
description = "Run check and findings, even if check fails (kept for one release)"
65+
description = "Run check and findings, even if check fails"
6666
run = "mise run --continue-on-error --jobs 1 check ::: findings"
6767

6868
[tasks.build]
@@ -95,10 +95,10 @@ flag "--spec-file <file>" default="upcloud-public-openapi.json" help="Spec membe
9595
run = 'bash generator/scripts/download_spec.sh --version "$usage_version" --asset-url "$usage_asset_url" --spec-file "$usage_spec_file"'
9696

9797
[tasks.regenerate]
98-
description = "Generate, then run the check gate; stops at the first failure"
98+
description = "Generate the SDK and run checks"
9999
run = ["mise run generate", "mise run check"]
100100

101101
[tasks.examples]
102-
description = "L6: run live mdtest examples, all by default (creates real UpCloud resources; requires UPCLOUD_TOKEN)"
102+
description = "Run live mdtest examples (creates real UpCloud resources, requires UPCLOUD_TOKEN)"
103103
usage = 'arg "[name]..." var=#true help="Directory under examples/ (default: all)"'
104104
run = 'bash generator/scripts/run_examples.sh ${usage_name:-file-storage object-storage tag kubernetes}'

‎.github/workflows/main.yml‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ jobs:
2626
uses: jdx/mise-action@dad1bfd3df957f44999b559dd69dc1671cb4e9ea # v4.2.1
2727
with:
2828
version: 2026.10.3
29-
- name: Integrity gate (must be green)
29+
- name: Run checks
3030
run: mise run check
3131
unit:
3232
runs-on: ubuntu-latest
@@ -59,9 +59,9 @@ jobs:
5959
uses: jdx/mise-action@dad1bfd3df957f44999b559dd69dc1671cb4e9ea # v4.2.1
6060
with:
6161
version: 2026.10.3
62-
- name: Findings gate (red on known spec or generator defects; does not block release)
62+
- name: Run endpoint and contract checks
6363
run: mise run findings
64-
- name: Upload contract diagnostics
64+
- name: Upload Wiretap artifacts
6565
if: always()
6666
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
6767
with:

‎.github/workflows/regenerate.yml‎

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -52,23 +52,22 @@ jobs:
5252
SPECS_ACCESS_PAT: ${{ secrets.SPECS_ACCESS_PAT }}
5353
run: mise run spec:download "$SPEC_VERSION" "$ASSET_URL" --spec-file "$SPEC_FILE"
5454

55-
# Integrity gate: if generation or `check` fails, no PR is opened.
56-
- name: Generate, then run the check gate
55+
- name: Generate and run checks
5756
id: regenerate
5857
run: mise run regenerate
5958

60-
- name: Run the findings gate (reported in the PR; does not block it)
59+
- name: Run endpoint and contract checks
6160
id: findings
6261
if: ${{ !cancelled() && (steps.regenerate.outcome == 'success' || steps.regenerate.outcome == 'failure') }}
6362
continue-on-error: true
6463
run: mise run findings
6564

66-
- name: Save the generated changes as a patch
65+
- name: Save generated changes as a patch
6766
run: |
6867
git add -A -- generator/openapi upcloud_api
6968
git diff --cached --binary > regenerate.patch
7069
71-
- name: Upload generated changes and contract diagnostics
70+
- name: Upload generated changes and Wiretap artifacts
7271
if: always()
7372
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
7473
with:
@@ -124,8 +123,8 @@ jobs:
124123
body: |
125124
Automated regeneration for spec version `${{ env.SPEC_VERSION }}`.
126125
127-
The integrity gate (`mise run check`) passed.
128-
Findings gate (`mise run findings`): **${{ needs.generate.outputs.findings }}**.
129-
The PR is a draft while findings fail; see the `regenerated-sdk` artifact
126+
`mise run check` passed.
127+
`mise run findings`: **${{ needs.generate.outputs.findings }}**.
128+
The PR is opened as a draft when `findings` fails. See the `regenerated-sdk` artifact
130129
of ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
131130
for the Wiretap and endpoint reports.

‎README.md‎

Lines changed: 27 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ The [mise](https://mise.jdx.dev/) configuration pins `openapi-python-client`,
6767
The `generate` task runs `generator/scripts/generate.sh` on the checked-in
6868
`generator/openapi/spec.json` (no download). It refuses to overwrite uncommitted
6969
changes in `upcloud_api/` unless you pass `mise run generate --force`.
70-
`mise run regenerate` runs `generate`, then the `check` gate, and stops at the
70+
`mise run regenerate` runs `generate`, then `check`, and stops at the
7171
first failure with a non-zero exit. The generated changes stay in place for
7272
investigation. Neither task changes `pyproject.toml` or `uv.lock`. A stale lock
7373
fails the `uv` commands, and only `mise run lock` and `mise run version:set` update it.
@@ -111,66 +111,51 @@ UV_PYTHON=pypy3.11 mise run test:unit
111111
| --- | --- |
112112
| `setup` | Install locked development dependencies. |
113113
| `lint` | Run pre-commit: ruff, shellcheck, and actionlint. |
114-
| `test:tools` | L1: test the shared shell scripts offline with bats. |
115-
| `test:preprocess` | L2: test schema-title filtering (`test_strip_schema_titles.py`), overlay fixes, and the endpoint checker. |
116-
| `test:unit` | L4: test the generated SDK and the contract-case derivation offline. |
117-
| `check:generated` | L3: regenerate and fail if `upcloud_api/` or `spec.patched.json` changes. |
118-
| `check:endpoints` | L3: check that every spec operation has a generated method/path contract. |
119-
| `test:contract` | L5: run generated sync/async SDK calls against a local Wiretap mock. |
120-
| `check` | Integrity gate: `lint`, `test:tools`, `test:preprocess`, `test:unit`, `check:generated`. Must be green. Alias: `test`. |
121-
| `findings` | Findings gate: `check:endpoints` and `test:contract`, run even if one fails. Red on known defects. |
122-
| `validate` | Run `check` and `findings`, even if `check` fails. Kept for one release. |
114+
| `test:tools` | Test the shell scripts offline with bats. |
115+
| `test:preprocess` | Test schema-title filtering, overlay fixes, and the endpoint checker. |
116+
| `test:unit` | Run offline unit tests for the SDK and Wiretap test cases. |
117+
| `check:generated` | Regenerate and fail if `upcloud_api/` or `spec.patched.json` changes. |
118+
| `check:endpoints` | Check that every spec operation has generated code. |
119+
| `test:contract` | Run generated sync/async SDK calls against a local Wiretap mock. |
120+
| `check` | Run required checks: `lint`, `test:tools`, `test:preprocess`, `test:unit`, `check:generated`. Alias: `test`. |
121+
| `findings` | Run `check:endpoints` and `test:contract` (fails on known spec and generator issues). |
122+
| `validate` | Run `check` and `findings`, even if `check` fails. |
123123
| `build` | Build a wheel and source distribution into `dist/`, then run `twine check`. |
124124
| `generate [--force]` | Regenerate the SDK from the checked-in spec. |
125125
| `regenerate` | Run `generate`, then `check`, and stop at the first failure. |
126126
| `lock` | Update `uv.lock`. |
127-
| `version:set <version>` | Explicitly change `[project].version` in `pyproject.toml` and `uv.lock` with `uv version --no-sync`. |
127+
| `version:set <version>` | Set `[project].version` in `pyproject.toml` and `uv.lock` with `uv version --no-sync`. |
128128
| `spec:download <version> <asset_url>` | Download a released spec using `SPECS_ACCESS_PAT` (used by the regeneration workflow). |
129-
| `examples [name...]` | L6: run live mdtest documents, all four by default. |
129+
| `examples [name...]` | Run live mdtest examples, all four by default. |
130130

131-
### Test layers and gates
131+
### Checks and contract tests
132132

133-
Every check belongs to one layer, and every defect fails exactly one check.
133+
`check` runs the required static, unit, and generation checks. It blocks builds,
134+
releases, and regeneration PRs.
134135

135-
| Layer | Guards | Members |
136-
| --- | --- | --- |
137-
| L1 tools | The shared shell scripts behave safely. | `generator/scripts/tests/*.bats` |
138-
| L2 preprocess | Overlay fixes and title filtering are correct and idempotent. | `generator/tests/test_strip_schema_titles.py`, `test_openapi_overlay.py`, `test_endpoint_coverage.py` |
139-
| L3 generation | Committed output equals generator output, and every spec operation is generated. | `check:generated`, `check:endpoints` |
140-
| L4 SDK unit | Generated imports, enums, `client.py.jinja` behavior, contract-case derivation. | `tests/test_sdk_smoke.py`, `tests/test_wiretap_cases.py` |
141-
| L5 contract | SDK calls match the spec on the wire. | `tests/test_wiretap.py` via `wiretap.sh` |
142-
| L6 live | Real API behavior. | `examples/**/*.md` via mdtest |
143-
144-
There are two gates. `check` (integrity) must always be green: a red `check`
145-
means the pipeline itself is broken. It blocks builds, releases, and regeneration
146-
PRs. `findings` covers the known spec and generator defects: a red `findings`
147-
means the spec or generator still has a known defect. It always runs and uploads
148-
its diagnostics, but it does not block releases. Regeneration PRs are opened as
149-
drafts while it fails.
136+
`findings` runs `check:endpoints` and `test:contract` to report known spec and
137+
generator issues without blocking releases. Regeneration PRs are opened as
138+
drafts when `findings` fails.
150139

151140
`mise install` installs every pinned tool, including `wiretap`, `bats`,
152141
`shellcheck`, `actionlint`, `twine`, and `mdtest` (built with the pinned Go).
153142
Wiretap runs as a local binary in mock mode and doesn't need Docker or Podman.
154143
No task except `examples` needs UpCloud credentials or makes real UpCloud requests,
155144
but the first run needs network access to install tools and dependencies.
156145

157-
The existing endpoint and Wiretap findings remain strict failures, not exemptions.
158-
`generator/scripts/wiretap.sh` starts Wiretap, sends a deliberately invalid canary
159-
request, runs the Python tests, and then fails if the report is missing, lacks the
160-
canary, or contains any other violation. It writes `wiretap.log` and
161-
`wiretap-report.json`. The Python tests add the `coverage.json` case inventory,
162-
which lists operations without generated code under `missing` (`check:endpoints`
163-
reports them as failures). All three go to `validation-artifacts/` (override with
164-
`VALIDATION_ARTIFACTS`). CI uploads these diagnostics.
146+
`generator/scripts/wiretap.sh` starts Wiretap, sends an invalid probe request to
147+
verify validation is active, runs the Python tests, and fails if the report is
148+
missing or contains any other violation. It writes `wiretap.log`,
149+
`wiretap-report.json`, and `coverage.json` to `validation-artifacts/` (override
150+
with `VALIDATION_ARTIFACTS`).
165151

166-
### Task names across SDK repositories
152+
### Shared scripts across SDK repositories
167153

168-
The task names, their inputs, and their exit semantics are the portable interface:
154+
The task names are shared across the Python, Go, and PHP SDK repositories:
169155
`setup`, `lint`, `generate`, `test:tools`, `test:preprocess`, `test:unit`,
170156
`check:generated`, `check:endpoints`, `test:contract`, `check`, `findings`,
171-
`build`, `spec:download`, `regenerate`, `examples`, and `version:set`. Go and PHP
172-
SDK repositories can use the same names. Only the `run` bodies are
173-
language-specific.
157+
`build`, `spec:download`, `regenerate`, `examples`, and `version:set`. Only the
158+
`run` commands are language-specific.
174159

175160
Scripts with the header line `Copied unchanged into each UpCloud SDK repository`
176161
don't depend on the SDK language. They need only `bash`, `git`, `curl`, `tar`, and

‎generator/scripts/check_endpoint_coverage.py‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from pathlib import Path
1313

1414
HTTP_METHODS = {"get", "put", "post", "delete", "patch", "head", "options", "trace"}
15-
SURFACES = {"sync_detailed", "asyncio_detailed"}
15+
FUNCTIONS = {"sync_detailed", "asyncio_detailed"}
1616

1717

1818
def normalize_path(path: str) -> str:
@@ -46,13 +46,13 @@ def endpoint_contract(path: Path) -> tuple[str, str]:
4646
for node in tree.body
4747
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef))
4848
}
49-
missing = SURFACES - functions.keys()
49+
missing = FUNCTIONS - functions.keys()
5050
if missing:
51-
raise ValueError(f"{path}: missing call surfaces {sorted(missing)}")
51+
raise ValueError(f"{path}: missing functions {sorted(missing)}")
5252
if not isinstance(functions["asyncio_detailed"], ast.AsyncFunctionDef):
5353
raise ValueError(f"{path}: asyncio_detailed is not async")
5454
if ("sync" in functions) != ("asyncio" in functions):
55-
raise ValueError(f"{path}: inconsistent parsed sync/async surfaces")
55+
raise ValueError(f"{path}: inconsistent sync/async functions")
5656
builder = functions.get("_get_kwargs")
5757
if builder is None:
5858
raise ValueError(f"{path}: missing _get_kwargs")
@@ -115,7 +115,7 @@ def check_coverage(spec: dict, directory: Path, patched: dict | None = None) ->
115115
ids = Counter(operation["operationId"] for operation in expected.values())
116116
errors.extend(f"Duplicate operationId: {name}" for name, count in ids.items() if count > 1)
117117
if patched is not None and expected.keys() != operations(patched).keys():
118-
errors.append("Preprocessing changed the method/path inventory")
118+
errors.append("Preprocessing changed the operations")
119119
return errors
120120

121121

‎generator/scripts/tests/wiretap.bats‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#!/usr/bin/env bats
2-
# wiretap.sh report gate, with fake wiretap and curl binaries; jq is real.
2+
# Test wiretap.sh with fake wiretap and curl binaries (jq is real).
33

44
bats_require_minimum_version 1.5.0
55

‎generator/tests/test_endpoint_coverage.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ def test_formatted_path_and_matching_contract(self):
3838
self.assertTrue(any("Missing" in error for error in errors))
3939
self.assertTrue(any("Unexpected" in error for error in errors))
4040

41-
def test_duplicates_and_missing_surfaces_fail(self):
41+
def test_duplicates_and_missing_functions_fail(self):
4242
with tempfile.TemporaryDirectory() as directory:
4343
api = Path(directory)
4444
(api / "server").mkdir()
@@ -48,7 +48,7 @@ def test_duplicates_and_missing_surfaces_fail(self):
4848
second.write_text(SOURCE)
4949
self.assertTrue(any("Duplicate generated" in e for e in check_coverage(SPEC, api)))
5050
second.write_text(SOURCE.replace("async def asyncio_detailed(): pass", ""))
51-
self.assertTrue(any("missing call surfaces" in e for e in check_coverage(SPEC, api)))
51+
self.assertTrue(any("missing functions" in e for e in check_coverage(SPEC, api)))
5252

5353
def test_unsupported_expressions_and_removed_operations_fail(self):
5454
with tempfile.TemporaryDirectory() as directory:

‎tests/test_sdk_smoke.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ async def call_gateway_plan_async(client):
7070

7171

7272
class SDKSmokeTests(unittest.TestCase):
73-
"""Check generated surfaces without making any network requests."""
73+
"""Test generated client and models without network requests."""
7474

7575
def test_every_generated_api_and_model_module_imports(self):
7676
"""Generator output and spec patches: every `api/` and `models/` module imports."""

‎tests/test_wiretap.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ def recover_documented_parse_failure(case, mode, error, responses, requests):
4848

4949

5050
class WiretapTests(unittest.IsolatedAsyncioTestCase):
51-
"""Exercise the SDK; generator/scripts/wiretap.sh owns the server and the report check."""
51+
"""Run SDK calls against Wiretap (wiretap.sh starts the server and checks the report)."""
5252

5353
@classmethod
5454
def setUpClass(cls):

‎tests/test_wiretap_cases.py‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -115,8 +115,8 @@ def test_scalar_conversions(self):
115115
with self.assertRaises(ValueError):
116116
convert("invalid", Literal["yes", "no"])
117117

118-
def test_operation_without_generated_code_is_inventoried_as_missing(self):
119-
"""A later derivable operation must not erase the missing-operation inventory."""
118+
def test_operation_without_generated_code_is_recorded_as_missing(self):
119+
"""A later derivable operation must not clear the missing operations list."""
120120
ok = {"responses": {"200": {"description": "ok"}}}
121121
spec = {
122122
"paths": {
@@ -318,7 +318,7 @@ def test_known_response_does_not_hide_other_failures(self):
318318
)
319319
changed_title = json.loads(documented_body())
320320
changed_title["title"] = "Other"
321-
# Parse failures before classification surface as the genuine model KeyError, not as
321+
# Parse failures before classification raise the model KeyError, not
322322
# an AssertionError, because only the exact documented response is adapted.
323323
scenarios = {
324324
"wrong status": (self.serve(status=500), label, KeyError),

0 commit comments

Comments
 (0)