Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
e99b4ac
:tada: feat(sdk): migrate configuration to xtm-oaev-sdk with PEP 562 …
Kakudou Oct 14, 2026
acbcbd6
:sparkles: feat(sdk): migrate common foundation to xtm-oaev-sdk with …
Kakudou Oct 14, 2026
382a403
:sparkles: feat(sdk): migrate signatures to xtm-oaev-sdk with version…
Kakudou Oct 14, 2026
b220f9e
:sparkles: feat(sdk): migrate contracts to xtm-oaev-sdk with PEP 562 …
Kakudou Oct 14, 2026
ca1b4a0
:sparkles: feat(sdk): add the shared daemon runtime to xtm-oaev-sdk
Kakudou Oct 14, 2026
937a4b4
:recycle: refactor(pyoaev): shim the daemon helpers and expectations …
Kakudou Oct 14, 2026
78e7454
:white_check_mark: test(pyoaev): guard the legacy public surface of p…
Kakudou Oct 14, 2026
4ece485
:recycle: refactor(sdk): enhancing port-adapter public boundaries for…
Kakudou Oct 14, 2026
ffdfac2
:recycle: refactor(sdk): enhancing port-adapter public boundaries for…
Kakudou Oct 14, 2026
9128fb2
:recycle: refactor(sdk): enhancing port-adapter public boundaries for…
Kakudou Oct 14, 2026
6725c1d
:recycle: refactor(sdk): enhancing port-adapter public boundaries for…
Kakudou Oct 14, 2026
4da1739
:recycle: refactor(sdk): enhancing port-adapter public boundaries for…
Kakudou Oct 14, 2026
de38ada
:recycle: refactor(sdk): enhancing port-adapter public boundaries for…
Kakudou Oct 14, 2026
aefca59
:wrench: chore(pyoaev): read the legacy _is_base64_encoded from the c…
Kakudou Oct 14, 2026
7481943
:memo: docs(sdk): adding the migration guide, contributing guide, cha…
Kakudou Oct 14, 2026
4125af8
:memo: docs(sdk): documenting the credential references migrated from…
Kakudou Oct 14, 2026
9f35088
:recycle: refactor(pyoaev): bring the REST client helpers back from x…
Kakudou Oct 14, 2026
11135ec
:recycle: refactor(pyoaev): bring copy_dict and remove_none_from_dict…
Kakudou Oct 14, 2026
e818566
:recycle: refactor(pyoaev): delegate the expectation models to collec…
Kakudou Oct 14, 2026
b68d1d6
:package: build(pyoaev): package only pyoaev in the wheel
Kakudou Oct 14, 2026
f5e7ce3
:white_check_mark: test(sdk): fingerprinting every seam object in one…
Kakudou Oct 15, 2026
71c3b56
:memo: docs(sdk): extending the seam golden TDR to collectors-sdk and…
Kakudou Oct 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ jobs:
- checkout
- run:
name: install dependencies
command: pip3 install --user .[dev]
command: pip3 install --user ./libs/xtm-oaev-sdk -r libs/sdk-requirements.txt .[dev]
- run:
name: confirm black version
command: black --version
Expand All @@ -33,7 +33,7 @@ jobs:
- checkout
- run:
name: install dependencies
command: pip3 install --user '.[dev]'
command: pip3 install --user ./libs/xtm-oaev-sdk -r libs/sdk-requirements.txt '.[dev]'
- run:
name: run tests
command: python -m pytest
Expand All @@ -60,7 +60,7 @@ jobs:
- run:
name: install dependencies
command: >
pip3 install --user .[dev]
pip3 install --user ./libs/xtm-oaev-sdk -r libs/sdk-requirements.txt .[dev]
- run:
name: check version
command: |
Expand Down Expand Up @@ -92,7 +92,7 @@ jobs:
at: ~/openaev-client
- run:
name: install dependencies
command: pip3 install --user .
command: pip3 install --user ./libs/xtm-oaev-sdk -r libs/sdk-requirements.txt .
- run:
name: install twine
command: pip3 install twine
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/coverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:

- name: Install dependencies
run: |
uv venv && uv pip install -e '.[dev]' coverage
uv venv && uv pip install -e ./libs/xtm-oaev-sdk -r libs/sdk-requirements.txt -e '.[dev]' coverage

- name: Run tests with coverage
run: uv run coverage run -m pytest --junitxml="junit.xml"
Expand All @@ -34,7 +34,7 @@ jobs:

- name: Generate coverage XML
run: uv run coverage xml -o coverage.xml

- name: Upload coverage to Codecov
if: ${{ !cancelled() && hashFiles('coverage.xml') != '' }}
uses: codecov/codecov-action@v7
Expand Down
7 changes: 7 additions & 0 deletions libs/sdk-requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# SDKs hosted in sibling repositories, required by pyoaev while it keeps the
# deprecation shims of its former daemon helpers. Install together with the
# local xtm-oaev-sdk, e.g.:
# pip install ./libs/xtm-oaev-sdk -r libs/sdk-requirements.txt '.[dev]'
# Switch to version pins once the SDKs are published on PyPI.
collectors-sdk @ git+https://github.com/OpenAEV-Platform/collectors.git@feat/303-sdk-init#subdirectory=libs/collectors-sdk
injectors-sdk @ git+https://github.com/OpenAEV-Platform/injectors.git@feat/303-sdk-init#subdirectory=libs/injectors-sdk
38 changes: 38 additions & 0 deletions libs/xtm-oaev-sdk/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Changelog

Every notable change to xtm-oaev-sdk lands here. Every seam change too, with the new
version of its seam
([seam-changes-are-versioned-contract-changes](docs/tdr/seam-changes-are-versioned-contract-changes.tdr.md)).

## [0.1.0] - Unreleased

First version. The shared, client-free code of `pyoaev` 3.261014.1 moves out and into
this SDK. The former `pyoaev` paths keep working, each one firing a
`DeprecationWarning` ([migration guide](docs/migration_guide.md)).

### Added

- `common`: the `OpenAEVError` hierarchy, `AppLogger`, logging setup, JSON encoding,
SSL helpers, asset and security-domain enums, and the `BaseClient`,
`DaemonProtocol` and `AppLoggerProtocol` protocols. Seam `1.0.0`.
- `configuration`: `Configuration`, `ConfigurationHint`, `SettingsLoader`, the settings
models, sources and schema generator. Seam `1.0.0`.
- `contracts`: `Contract`, `ContractBuilder`, `VariableHelper`, `prepare_contracts`,
the contract elements and enums, and the credential references (`CredentialType`,
`ContractReferencedCredential`, `build_single_referenced_credential_element`, from
`pyoaev.credential`). Seam `1.0.0`.
- `signatures`: `SignatureManager`, `build_network_configs`, `build_marker`, the
matching types and the inbound I/O models. Seam `1.0.0`.
- `port_io`: the wire models sent to the platform. `PORT_IO_CONTRACT_VERSION` `1.0.0`.
- `daemons`: `BaseDaemon` and `PingAlive`. Seam `1.0.0`.
- `deprecation` (new, not from `pyoaev`): `make_deprecated_getattr`, the PEP 562
deprecation primitive. Seam `1.0.0`.

### Changed (compared with pyoaev)

- `ExpectationType` splits in two: `SignatureExpectationType` and
`ContractExpectationType`.
- `BaseDaemon` and `SignatureManager` stop building the API client. Pass
`api_client=` / `transport=` yourself.
- `requires-python` moves from `>=3.11` to `>=3.12`, the version `typing.override`
actually needs. Released `pyoaev` already failed to import on 3.11.
89 changes: 89 additions & 0 deletions libs/xtm-oaev-sdk/CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# xtm-oaev-sdk: agent instructions

Applies to work under `libs/xtm-oaev-sdk/`, and to the `pyoaev/` deprecation shims when
code moves out of `pyoaev`. Paths below are relative to the repository root.

xtm-oaev-sdk is the shared, client-free runtime of the OpenAEV connectors
(configuration, contracts, signatures, daemons, errors). `pyoaev` is the HTTP client
and serves the former names through deprecation shims. collectors-sdk and injectors-sdk
build on this SDK from their own repositories.

## Hard rules

1. NEVER break legacy `pyoaev` code. Every public name of the latest released `pyoaev`
(recorded in `test/fixtures/legacy_surface.json`) MUST keep resolving from its legacy path, returning the SDK object and emitting a
`DeprecationWarning` that names the new import. Why: connectors pin `pyoaev`, import
its old paths and patch them in their tests.
2. Respect the layering of every capability:
- `seam/<capability>/` imports neither `public/` nor `_core/`;
- `_core/<capability>/` NEVER imports `public/` (type-only imports go under `TYPE_CHECKING`);
- `public/<capability>.py` imports `_core/` ONLY to assign module-level defaults typed by
their port (`_resolver: ConfigurationResolverPort = ConfigurationResolver(...)`);
- every exported name is an adapter defined in `public/` or a contract defined in
`seam/`, NEVER a `_core` object.

Why: the core can then be rewritten freely while the public API keeps working.
3. Every piece of behaviour goes through a seam `Protocol` port, one `_core`
implementation and a `public` adapter, one-liners included. No exceptions.
4. A public adapter MUST keep the signatures, docstrings, attributes, overridable hooks
and framework base class of the code it replaces. When the old code called a method on
`self`, pass that bound method to the core as a callable, so overrides and test
patches keep applying.
5. NEVER import `pyoaev`, `pika`, `thefuzz` or `requests` in the SDK. The API client is
injected (`BaseClient`, `SignatureTransportProtocol`).
6. Users import from the package root (`from xtm_oaev_sdk import X`). A new public name
goes in its `public/<capability>.py` `__all__` and in `public/__init__.py`.
7. A seam change (field, type, enum member, error, port signature) is a contract change:
bump that capability's `SEAM_VERSION` (`PORT_IO_CONTRACT_VERSION` for `seam/port_io`)
and add an entry to `libs/xtm-oaev-sdk/CHANGELOG.md`. Then regenerate the seam golden
in the same change: `python libs/xtm-oaev-sdk/test/seam/generate_seam_golden.py`.
NEVER regenerate it to make a failing `test/seam/test_seam_golden.py` pass without a
deliberate seam change; follow `libs/xtm-oaev-sdk/CONTRIBUTING.md`, "Seam golden".
8. NEVER regenerate `test/fixtures/legacy_surface.json` from the working tree: it records
the released `pyoaev`. NEVER rewrite the legacy tests moved under
`libs/xtm-oaev-sdk/test/<capability>/shim/`.

## Scope

Only make the changes directly requested. Do not add capabilities, abstractions, files,
dependencies or refactors beyond the task.

## Stop and ask before

- changing a seam contract or a `SEAM_VERSION`;
- renaming or removing a public name, or removing a `pyoaev` shim;
- adding or changing a dependency in any `pyproject.toml` or in `libs/sdk-requirements.txt`;
- editing `test/fixtures/legacy_surface.json`, the seam golden
`libs/xtm-oaev-sdk/test/seam/fixtures/seam_golden.json`, or CI configuration;
- deleting any file.

## Done when

Run from the repository root, and report the real output:

```bash
isort --check-only . && black --check . && flake8 --ignore=E,W --extend-exclude=.venv .
python -m pytest libs/xtm-oaev-sdk/test test/test_legacy_surface.py test/test_sdk_split_shims.py
python -m pytest
```

All three pass. If a check fails, say which one and why. NEVER report a check as passing
without running it.

## Conventions

- Tests live in `libs/xtm-oaev-sdk/test/<capability>/`, exercise behaviour through the
public API, and are written as `_given_*` / `_when_*` / `_then_*` helpers with a
one-line `"""It ..."""` docstring. Copy the style of the neighbouring tests.
- Adding a capability: follow the checklist at the end of `libs/xtm-oaev-sdk/docs/seam_strategy.md`.
- Recording a decision: add `libs/xtm-oaev-sdk/docs/tdr/<slug>.tdr.md` with the header of
an existing record and `status: draft`, plus a row in `docs/tdr/TDR_ToC.md`.
- Commit messages, when asked for: gitmoji + Conventional Commits with the `sdk` scope,
e.g. `:recycle: refactor(sdk): enhancing port-adapter public boundaries for configuration`.

## Read when relevant

- `libs/xtm-oaev-sdk/docs/seam_strategy.md`: layering patterns and capability map.
- `libs/xtm-oaev-sdk/docs/shim_strategy.md`: moving code out of `pyoaev` without breaking it.
- `libs/xtm-oaev-sdk/docs/tdr/TDR_ToC.md`: the decision behind each rule above.
- `libs/xtm-oaev-sdk/CONTRIBUTING.md`: setup and repository conventions.
172 changes: 172 additions & 0 deletions libs/xtm-oaev-sdk/CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
# Contributing to xtm-oaev-sdk

This guide is about what's specific to the SDKs. Everything repository-wide
(Conventional Commits titles with an issue reference, signed commits, labels, coverage)
lives in the repository's [CONTRIBUTING.md](../../CONTRIBUTING.md). `collectors-sdk`
and `injectors-sdk` run on this guide too, plus a short one of their own.

## Set up

From the repository root, on Python 3.12 or later, since the code uses `typing.override`:

```bash
python -m venv .venv && . .venv/bin/activate
pip install -e ./libs/xtm-oaev-sdk -r libs/sdk-requirements.txt -e '.[dev]'
```

`libs/sdk-requirements.txt` pulls `collectors-sdk` and `injectors-sdk` from git,
because `pyoaev` depends on them.

## Run the checks

```bash
isort --check-only .
black --check .
flake8 --ignore=E,W --extend-exclude=.venv .
python -m pytest # pyoaev and xtm-oaev-sdk
python -m pytest libs/xtm-oaev-sdk/test # the SDK alone
```

CI runs the exact same four tools. Locally, `pre-commit run --files <files>` takes
care of isort, black and the file hygiene hooks.

## Where code goes

Every capability is cut in three: `public/` (adapters), `seam/` (contracts), `_core/`
(implementations). [docs/seam_strategy.md](docs/seam_strategy.md) lays out the layering
and the patterns that hold it together. The rules, each one nailed down as a TDR in
[docs/tdr](docs/tdr/TDR_ToC.md):

- the seam imports neither `public` nor `_core`, `_core` never imports `public`, and
`public` touches `_core` only to wire default implementations
([public-seam-core-dependency-rule](docs/tdr/public-seam-core-dependency-rule.tdr.md));
- every piece of behaviour goes behind a port, one-liners included
([every-behaviour-behind-a-port](docs/tdr/every-behaviour-behind-a-port.tdr.md));
- adapters keep the signatures, attributes, hooks and base classes of what they replace
([adapters-keep-the-user-facing-shape](docs/tdr/adapters-keep-the-user-facing-shape.tdr.md));
- users import from the package root, nowhere else
([import-from-the-package-root](docs/tdr/import-from-the-package-root.tdr.md));
- the SDK never imports `pyoaev`, `pika`, `thefuzz` or `requests`
([sdks-never-import-pyoaev](docs/tdr/sdks-never-import-pyoaev.tdr.md),
[xtm-oaev-sdk-stays-connector-agnostic](docs/tdr/xtm-oaev-sdk-stays-connector-agnostic.tdr.md)).

`test/test_boundaries.py` catches the forbidden imports.

Adding a capability? The checklist sits at the end of
[docs/seam_strategy.md](docs/seam_strategy.md). Follow it.

## Changing a seam

A seam change is a contract change. Treat it like one
([seam-changes-are-versioned-contract-changes](docs/tdr/seam-changes-are-versioned-contract-changes.tdr.md)):

1. bump the capability's `SEAM_VERSION` (`PORT_IO_CONTRACT_VERSION` for `seam/port_io`);
2. regenerate the seam golden in the same change, not in a follow-up (see
[Seam golden](#seam-golden-testseam));
3. add an entry to [CHANGELOG.md](CHANGELOG.md);
4. if a public name changes or disappears, the old one keeps working through a
deprecation shim (`make_deprecated_getattr`) first.

## Seam golden (`test/seam`)

The seam is the contract between the public API and the core, and part of it rides the
wire: payloads sent to the platform, data coming back, the connector manifest. Every
change to it must be deliberate. `libs/xtm-oaev-sdk/test/seam/` fingerprints every
public object of every seam module, so any change fails a test until someone signs off
on it
([every-seam-object-is-fingerprinted](docs/tdr/every-seam-object-is-fingerprinted.tdr.md)).

### What is in there

| File | Role |
|---|---|
| `generate_seam_golden.py` | Finds the seam on its own: every `*.py` file under `xtm_oaev_sdk/seam/`, every public top-level definition (class, function, constant, type alias). It fingerprints each one, then rewrites the golden: changed fingerprints updated, new objects added, deleted objects removed. It prints what moved, and warns when a capability changed but its version did not. |
| `fixtures/seam_golden.json` | The golden: one entry per object, `"<module>.<name>": {"kind", "sha256"}`. |
| `test_seam_golden.py` | Recomputes the fingerprints and fails on any object added, removed or changed. It also checks that the package root re-exports the seam's own objects. |
| `test_seam_closed.py` | Checks that `port_io` imports only the signatures seam, and that the signatures seam imports nothing from the SDK. |

No list to maintain. The next run picks up any new seam object.

collectors-sdk and injectors-sdk carry the same golden under their own `test/seam/`. There,
run the generator from `libs/<sdk>`: `python test/seam/generate_seam_golden.py`.

### What a fingerprint covers

The sha256 of the definition's normalized AST. Formatting, comments and docstrings do
not count. So it moves with fields, defaults, enum members, method signatures,
validators and constant values. Pydantic models add their validation and serialization
JSON schemas on top, model and field descriptions included. Those reach the wire, so
rewording a pydantic model's docstring is a change.

### Updating it on purpose

From the repository root:

1. make the seam change;
2. bump the capability's `SEAM_VERSION` (`PORT_IO_CONTRACT_VERSION` for `seam/port_io`);
3. regenerate the golden:

```bash
python libs/xtm-oaev-sdk/test/seam/generate_seam_golden.py
```

4. run `python -m pytest libs/xtm-oaev-sdk/test/seam`;
5. commit the seam change, the version bump and the golden in one go, with a
[CHANGELOG.md](CHANGELOG.md) entry.

### Seeing what changed

A failure names every object that moved, as `<module>.<name>`. `git diff` that
definition: the code diff is the change. A pydantic model can move with no code change
at all (a pydantic upgrade, a referenced type). Then compare its schema on both commits:

```bash
python -c "import json; from xtm_oaev_sdk.seam.port_io import ExecutionDetails as M; print(json.dumps(M.model_json_schema(), indent=2, sort_keys=True))"
```

NEVER regenerate the golden just to turn a red test green. NEVER edit a hash by hand. If
a fingerprint moved with no seam change, find out why before you decide whether the
contract really changed.

## Moving code out of pyoaev

Legacy code keeps working. Non-negotiable
([no-breaking-change-for-legacy-pyoaev-code](docs/tdr/no-breaking-change-for-legacy-pyoaev-code.tdr.md)):

- serve the old path with a shim, by the rules of [docs/shim_strategy.md](docs/shim_strategy.md);
- move the old tests verbatim into `libs/xtm-oaev-sdk/test/<capability>/shim/`;
- `test/test_legacy_surface.py` stays green. Never regenerate its fixture from the
working tree: it records the released `pyoaev`, and that is the whole point.

## Tests

Tests live in `libs/xtm-oaev-sdk/test/<capability>/` and go through the public API to
hit behaviour. Write them as `_given_` / `_when_` / `_then_` helpers, topped with a
one-line "It ..." docstring:

```python
def test_configuration_resolves_with_override_priority(tmp_path, monkeypatch) -> None:
"""It prioritizes override over env, file, and default."""
module = _given_public_module()
...
_when_set_configuration_value(configuration, "target", "override-value")
```

## Commits

Repository conventions first. On top of them, the SDK history prefixes a gitmoji and
uses the `sdk` scope, one capability or concern per commit:

```
:recycle: refactor(sdk): enhancing port-adapter public boundaries for configuration
```

## Documentation

- [README.md](README.md): the intent, and a quickstart;
- [docs/shim_strategy.md](docs/shim_strategy.md) and
[docs/seam_strategy.md](docs/seam_strategy.md): the how and the why;
- [docs/migration_guide.md](docs/migration_guide.md): getting connectors onto the SDKs;
- [docs/tdr](docs/tdr/TDR_ToC.md): the technical decision records. A new decision gets
its own `<slug>.tdr.md`, same header as the others, `draft` status, and a line in
`TDR_ToC.md`.
Loading
Loading