Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
14 changes: 14 additions & 0 deletions .changeset/single-binding-product.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
"@germ-network/two-mls-pq": minor
---

**Breaking:** the `TwoMLSPQMigrate` product is removed. Its module now ships in the `TwoMLSPQ`
product, so `import TwoMLSPQMigrate` (and `import TwoMLSPQBinding`) keep working for any target
that depends on `TwoMLSPQ`. On every target that declared it, swap
`.product(name: "TwoMLSPQMigrate", package: "TwoMLSPQ")` for
`.product(name: "TwoMLSPQ", package: "TwoMLSPQ")`. Imports are unchanged.

With the binding in two products, an Xcode build could link a second copy into one process, for
example the `TwoMLSPQ` product as a shared framework plus `TwoMLSPQMigrate` linked statically into a
test bundle. The first callback through the other copy then aborted with "Callback interface
failure" (`unexpectedStaleHandle`). The binding now lives in exactly one product.
30 changes: 14 additions & 16 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,18 @@ let package = Package(
platforms: [.iOS(.v18), .macOS(.v15)],
products: [
// The forward-looking PUBLIC product: the concrete PQ types (`PQSession`,
// `PQInvitation`, `PQClient`, …), their value/currency types, and the UniFFI
// binding. The backward-compat shim PROTOCOLS live in the separate
// `AbstractTwoMLS` package (which depends on and re-exports this), keeping this
// product's surface clear of the legacy-shim abstraction.
// `PQInvitation`, `PQClient`, …), their value/currency types, the UniFFI
// binding, and the migrators (`import TwoMLSPQMigrate`). The backward-compat shim
// PROTOCOLS live in the separate `AbstractTwoMLS` package (which depends on and
// re-exports this), keeping this product's surface clear of the legacy-shim
// abstraction.
// Every target that depends on `TwoMLSPQBinding` must ship in THIS product and no
// other. Xcode dedups per product, so a second product carrying the binding links a
// second copy into the same process. Each copy keeps its own callback handle map
// while Rust keeps one vtable, and a callback then aborts on a stale handle.
.library(
name: "TwoMLSPQ",
targets: ["TwoMLSPQ"]
targets: ["TwoMLSPQ", "TwoMLSPQMigrate"]
),
// The Rust-free slice: the pure-Swift currency types, no binding/xcframework.
// For all-Swift consumers (e.g. Android builds, where the xcframework has no
Expand All @@ -58,15 +63,9 @@ let package = Package(
name: "TwoMLSPQTypes",
targets: ["TwoMLSPQTypes"]
),
// The invitation migrator library (GER-2372 R3) — consumes the Rust migration
// export and mints a native twomlspq-swift invitation archive.
.library(
name: "TwoMLSPQMigrate",
targets: ["TwoMLSPQMigrate"]
)
],
dependencies: [
// TEST-ONLY. The public product has no external Swift dependencies: digests and
// TEST-ONLY. The `TwoMLSPQ` module has no external Swift dependencies: digests and
// routing ids cross its surface as self-describing `Data` this package owns (see
// PQDigest.swift), so a suite change ships from here without a CommProtocol
// release. The test target still mints client ids with `AgentPrivateKey` the way
Expand Down Expand Up @@ -130,10 +129,9 @@ let package = Package(
// `TwoMlsPqSession.migrationExport`, GER-2433 C1) onto twomlspq-swift's
// `InvitationMigration.mintArchive` / `SessionMigration.mintArchive`, MINTING
// native `SecretArchive`s from legacy Rust state (dual-read / single-write:
// the Rust engine stays a read-only legacy decoder). Separate target so its
// twomlspq-swift dependency — and that package's `Invitation`/`ClientID` type
// names, which collide with this package's — stay out of the public
// `TwoMLSPQ` product.
// the Rust engine stays a read-only legacy decoder). A separate module, shipped in
// the `TwoMLSPQ` product, so twomlspq-swift's `Invitation`/`ClientID` type names,
// which collide with this package's, stay out of the `TwoMLSPQ` module.
.target(
name: "TwoMLSPQMigrate",
dependencies: [
Expand Down
6 changes: 4 additions & 2 deletions book/src/introduction.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@ App

```

The Swift package has **no external Swift dependencies**. CommProtocol is a sibling under the
app, not a layer beneath: the two meet in app code, which carries values between them.
The `TwoMLSPQ` module has **no external Swift dependencies**. The product's migrator module
(`TwoMLSPQMigrate`) depends on twomlspq-swift, swift-mls and swift-secret-bytes, the native engine
it mints into. CommProtocol is a sibling under the app, not a layer beneath: the two meet in app
code, which carries values between them.

Digests cross the Swift API as self-describing tagged `Data` — `[kind][digest]` — that this
package derives (`PQDigest.over(_:)`) and compares. The kind tag matters: the digest algorithm
Expand Down
Loading