Context
Part of the priority-ordered NPU / IREE roadmap.
P0-2 from the roadmap is "extract a backend-neutral API module so `skainet-backend-cpu` becomes one backend among peers (IREE, Metal, NPU) rather than a load-bearing dependency of every `skainet-compile-`, `skainet-io-`, and `skainet-test-*` module."
Scoping turned up a surprise win: `TensorOps`, `TensorDataFactory`, `TensorData` already live in `skainet-lang-core` (not in `skainet-backend-cpu`). The CPU module only holds `DefaultCpuOps` + platform variants. So the extraction collapses to a purely additive first step — no file moves, no consumer migration.
This PR
- Add `skainet-backends:skainet-backend-api` to `settings.gradle.kts`.
- Create `skainet-backends/skainet-backend-api/build.gradle.kts`. It depends on `:skainet-lang:skainet-lang-core` and `api`-re-exports the already-neutral types (`TensorOps`, `TensorDataFactory`, `TensorData`). No Kotlin source files yet — it's a pure re-export module.
- Add `:skainet-backends:skainet-backend-api` as a dependency of `:skainet-backends:skainet-backend-cpu` so future backends can mirror that wiring.
Out of scope
- Migrating the ~11 existing consumers from `skainet-backend-cpu` → `skainet-backend-api`. That's the follow-up step in the P0-2 track and will be split into bite-sized PRs per consumer module.
- Moving `TensorOps` source out of `skainet-lang-core`. If/when we decide `lang-core` shouldn't own tensor ops, that's a separate large refactor.
- Any new backend implementation (IREE, Metal, NPU).
Why this is the right first step
- Pure build-graph addition. No behavior change, no broken compile for any existing consumer.
- Unblocks every subsequent backend by giving them a canonical neutral import path.
- Surfaces any `api` vs `implementation` visibility mistakes immediately (caught by `skainet-backend-cpu`'s consumers).
Context
Part of the priority-ordered NPU / IREE roadmap.
P0-2 from the roadmap is "extract a backend-neutral API module so `skainet-backend-cpu` becomes one backend among peers (IREE, Metal, NPU) rather than a load-bearing dependency of every `skainet-compile-`, `skainet-io-`, and `skainet-test-*` module."
Scoping turned up a surprise win: `TensorOps`, `TensorDataFactory`, `TensorData` already live in `skainet-lang-core` (not in `skainet-backend-cpu`). The CPU module only holds `DefaultCpuOps` + platform variants. So the extraction collapses to a purely additive first step — no file moves, no consumer migration.
This PR
Out of scope
Why this is the right first step