Skip to content

feat: parity with @dodomain/node 0.6.0 — TLS-issuance advisories and the ConnectSessionSummary rename (0.4.0) - #4

Merged
BSalaeddin merged 1 commit into
mainfrom
parity/node-0.6.0
Sep 8, 2026
Merged

BSalaeddin merged 1 commit into
mainfrom
parity/node-0.6.0

Conversation

@BSalaeddin

Copy link
Copy Markdown
Collaborator

What

Brings the Python SDK back to the parity rule in the monorepo's RUNBOOK.md §8.4 — "the Python SDK tracks @dodomain/node's public surface — when an API contract changes here, BOTH SDKs move." PyPI dodomain-sdk was at 0.3.0 (parity with @dodomain/node 0.4.0) while node has since shipped 0.5.0 and 0.6.0. This is dodomain-sdk 0.4.0.

Additive apart from one rename that keeps a working alias, so upgrading from 0.3.0 is a drop-in.

The surface diff this mirrors

Diffed packages/node/src between 23b01ea (node 0.4.0, 2026-08-20) and 587e633 (node 0.6.0, 2026-09-03), and the committed apps/docs/public/openapi.json over the same window. The /v1 contract delta is exactly six items — every one of them additive except the rename:

# Source Change Mirrored as
1 node 0.5.0 (1400d94) SessionConnectSessionSummary, deprecated Session alias kept, CreateSessionResponse alias re-pointed ConnectSessionSummary + Session = ConnectSessionSummary (same class object)
2 node 0.6.0 (587e633) new TlsIssuanceAdvisory type TlsIssuanceAdvisory + TlsIssuanceAdvisoryCode / TlsIssuanceAdvisorySeverity
3 IntegratorSession.tlsIssuanceAdvisories (required) IntegratorSession.tls_issuance_advisories
4 App.tlsIssuerCa: string | null (required) App.tls_issuer_ca
5 VerifySessionResponse.advisories (required) VerifyResult.advisories
6 f092cb3 (2026-08-27) VerifySessionResponse.records[].authoritativeFound + publicFound (required) VerifyRecord.authoritative_found / .public_found

Item 6 has no @dodomain/node twin and is easy to miss: @dodomain/node exposes no verify method (POST /v1/sessions/{token}/verify is token-public), so that contract change never moved a node version. This SDK does wrap it, so a node-only diff would have left the Python verify result silently two fields short. Found via the OpenAPI diff, not the node diff.

Webhook side: connection.verified and session.completed now carry tlsIssuanceAdvisories when non-empty (absent, not empty, when there is nothing to say). This SDK ships no typed event parser on purpose, so that is a documentation change here, not a code one.

Deprecation policy

Matches node's exactly: Session stays as an alias bound to the same class objectisinstance, equality and existing from dodomain import Session imports are unaffected — is marked deprecated in the docstring, README and changelog, and goes away in the next major. tests/test_models.py::test_the_deprecated_session_alias_is_the_same_class_not_a_copy pins that it never forks into a second class, which is the Python analogue of node's MutuallyAssignable<Session, ConnectSessionSummary> type pin.

Mechanical parity guard (new)

tests/fixtures/openapi_v1_shapes.json is a verbatim extract of the published OpenAPI component schemas for POST /v1/sessions, GET /v1/sessions/{token} (both arms), POST /v1/sessions/{token}/verify and GET /v1/apps, taken from the monorepo's committed apps/docs/public/openapi.json — which is itself generated from the same zod schemas the handlers validate with. tests/test_openapi_contract.py then asserts two things per schema:

  1. the hand-written wire-key → model-attribute map equals the schema's required list (a newly-required API field fails the suite the moment the fixture is regenerated), and
  2. a payload synthesized from that schema round-trips into the model with every mapped attribute holding the synthesized value (so a mapping entry cannot be satisfied by a field that quietly parses to its default).

Same job tests/fixtures/webhook_vectors.json already does for the signer. Both directions were proven red before being left green:

  • deleting tls_issuer_ca=_opt_str(data, "tlsIssuerCa") from App._from_apiAssertionError: ListAppsResponse.apps.items.tlsIssuerCa did not reach .tls_issuer_ca / assert None == 'sample-tlsIssuerCa'
  • deleting "tlsIssuerCa" from the map (simulating a new required field nobody modelled) → AssertionError ... Extra items in the left set: 'tlsIssuerCa'

The regeneration recipe is in the test module's docstring.

Tolerance

Every new field follows the module's existing rule (_opt_list / _opt_str): a body recorded before the field existed still parses (as () / None), while a field that is present with the wrong type is still fatal. Covered by test_a_verify_response_recorded_before_advisories_existed_still_parses, its two siblings, and test_an_advisories_field_that_is_present_but_not_an_array_is_still_fatal.

Gates (run locally, verbatim from .github/workflows/ci.yml)

$ ruff check .
All checks passed!
$ ruff format --check .
37 files already formatted
$ ty check
All checks passed!
$ pytest tests --ignore=tests/e2e --cov=dodomain --cov-fail-under=90
TOTAL                                          1157      9    99%
Required test coverage of 90% reached. Total coverage: 99.22%
383 passed

383 tests (was 359). Only Python 3.13 is installed on the authoring host, so the matrix's 3.10 leg was not run locally — CI covers it, and ty type-checks against 3.10 ([tool.ty.environment] python-version = "3.10") and passes. No 3.11+ syntax was introduced.

The e2e-test job needs the DODOMAIN_SECRET_KEY secret and hits prod; unchanged by this PR and not run locally.

Release order and the exact dispatch step — DO NOT run these from this PR

Publishing is operator-only (monorepo CLAUDE.md, hard boundaries). Nothing here has been tagged and nothing has touched PyPI. @dodomain/node 0.6.0 must ship first; Python tracks it.

  1. npm first. In the monorepo, node scripts/sdk/sync-mirror.mjs from the repo root, then in DevinoSolutions/dodomain-sdk: Actions → Release (npm) → Run workflow → mode publish → package node. Or:
    gh workflow run release-npm.yml --repo DevinoSolutions/dodomain-sdk -f mode=publish -f package=node
    
    Verify with npm view @dodomain/node version0.6.0, with provenance.
  2. Merge this PR to main here.
  3. Then PyPI. From this repo's main, dispatch its own publish.yml (workflow_dispatch, no inputs) — it ships whatever version is in src/dodomain/__init__.py, which this PR sets to 0.4.0, over PyPI trusted publishing (OIDC, no token):
    gh workflow run publish.yml --repo DevinoSolutions/dodomain-python --ref main
    
  4. Verify: pip index versions dodomain-sdk (or the PyPI project page) shows 0.4.0. Remember the distribution name is dodomain-sdk and the import name is dodomain.

Known-working quirk, do not "fix" it: the registered PyPI publisher stores environment: null while publish.yml declares environment: pypi. That mismatch is deliberate and publishes fine — the workflow header says so.

Finding: dev-dependency pins are behind

Checked PyPI today. httpx, pytest, pytest-asyncio, pytest-cov and respx are all pinned at their current latest. Two are not:

  • ruff==0.16.1 → latest 0.16.6
  • ty==0.0.66 → latest 0.0.79

Deliberately not bumped here: a linter/type-checker bump introduces churn and new diagnostics unrelated to a parity release, and would make this diff harder to review. It wants its own PR.

@BSalaeddin
BSalaeddin merged commit f964797 into main Sep 8, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant