Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
ae6436d
docs(site): surface the C/C++ git channel on entry surfaces
auto-wood Sep 18, 2026
7202dd5
docs(connect): unify native-binding channel classification across SSO…
auto-wood Sep 18, 2026
dfb880c
docs(site): name the Windows import library in the quick-start C/C++ …
auto-wood Sep 18, 2026
c722084
feat(connect-cpp): add the C++17 convenience value layer, core helper…
auto-wood Sep 18, 2026
382742a
Merge branch 'feat/cpp-consumer-surface-channels' into feat/cpp-consu…
auto-wood Sep 18, 2026
d9f63d1
fix(connect-cpp): pass /D_HAS_EXCEPTIONS=0 to the MSVC smoke argv
auto-wood Sep 18, 2026
f9a5d42
Merge branch 'feat/cpp-consumer-surface-convenience' into feat/cpp-co…
auto-wood Sep 18, 2026
c12967d
Merge branch 'feat/cpp-consumer-surface-entry-docs' into feat/cpp-con…
auto-wood Sep 18, 2026
131b5e6
feat(connect-cpp): add the callback bridges and the session wrappers
auto-wood Sep 18, 2026
7112fa5
fix(connect-cpp): require each smoke configuration's exact banner list
auto-wood Sep 18, 2026
60e05c5
Merge branch 'feat/cpp-consumer-surface-convenience' into feat/cpp-co…
auto-wood Sep 18, 2026
aafe1a5
feat(connect-cpp): add the multi-peer router wrapper and close out C+…
auto-wood Sep 18, 2026
dc640c5
Merge branch 'feat/cpp-consumer-surface-convenience' into feat/cpp-co…
auto-wood Sep 18, 2026
55ada66
docs(connect-cpp): state one C++ counterpart invariant in the parity …
auto-wood Sep 18, 2026
69802b3
Merge branch 'feat/cpp-consumer-surface-convenience' into feat/cpp-co…
auto-wood Sep 18, 2026
dfdf232
docs(connect-cpp): document the C++17 convenience layer with a runnab…
auto-wood Sep 18, 2026
bf2c135
docs(connect-cpp): point consumer READMEs at public destinations
auto-wood Sep 18, 2026
d7851e1
Merge branch 'feat/cpp-consumer-surface-cpp-docs' into feat/cpp-consu…
auto-wood Sep 18, 2026
121b1f5
ci(connect-cpp): run the symbol-check self-test on the Windows lane
auto-wood Sep 18, 2026
5682eea
docs(connect-cpp): route walkthrough failures through the ordered clo…
auto-wood Sep 18, 2026
1409b64
Merge branch 'feat/cpp-consumer-surface-walkthrough-fix' into feat/cp…
auto-wood Sep 18, 2026
c85f37a
Merge branch 'feat/cpp-consumer-surface-windows-lane' into feat/cpp-c…
auto-wood Sep 18, 2026
9a0f01f
docs(knowledge): record the C++ convenience-layer pattern
auto-wood Sep 18, 2026
bd72ca7
fix(connect-cpp): split smoke banners on CRLF as well as LF
auto-wood Sep 18, 2026
dea5658
Merge branch 'feat/cpp-consumer-surface-crlf-fix' into feat/cpp-consu…
auto-wood Sep 18, 2026
4beb3d6
Potential fix for pull request finding 'CodeQL / Uncontrolled data us…
btspoony Sep 18, 2026
57cccc9
ci(connect-cpp): add macOS smoke lane
auto-wood Sep 18, 2026
75ba930
Merge branch 'feat/cpp-consumer-surface-macos-lane' into feat/cpp-con…
auto-wood Sep 18, 2026
6e139e3
Merge remote-tracking branch 'origin/feat/cpp-consumer-surface' into …
auto-wood Sep 18, 2026
47ae30c
fix(connect-cpp): import kFixtureLabel into the smoke's fixture-path …
auto-wood Sep 18, 2026
c1b9de4
Merge branch 'feat/cpp-consumer-surface-autofix-fix' into feat/cpp-co…
auto-wood Sep 18, 2026
113fc62
ci(connect-cpp): watch native carrier paths
auto-wood Sep 18, 2026
be15141
Merge branch 'feat/cpp-consumer-surface-native-paths' into feat/cpp-c…
auto-wood Sep 18, 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
69 changes: 65 additions & 4 deletions .github/workflows/cpp-connect.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
name: C++ connect (Windows)
name: C++ connect (Windows + macOS)

# Dedicated Windows carrier lane: build the C ABI with MSVC, prove the
# exported symbols, then compile, link, and run the shared C++ smoke.
# Two dedicated carrier lanes: Windows builds the C ABI and produces the
# refresh artifact; macOS validates the committed osx-arm64 carrier, then both
# lanes prove exported symbols and run the shared C++ smoke.
on:
pull_request:
paths:
Expand All @@ -12,6 +13,7 @@ on:
- crates/spoke-connect/Cargo.toml
- crates/spoke-connect/bindings/cpp/include/**
- crates/spoke-connect/bindings/cpp/Smoke/**
- crates/spoke-connect/bindings/cpp/native/**
- tooling/connect/cpp-build.mjs
- tooling/connect/cpp-symbol-check.mjs
- tooling/connect/cpp-smoke.mjs
Expand All @@ -27,6 +29,7 @@ on:
- crates/spoke-connect/Cargo.toml
- crates/spoke-connect/bindings/cpp/include/**
- crates/spoke-connect/bindings/cpp/Smoke/**
- crates/spoke-connect/bindings/cpp/native/**
- tooling/connect/cpp-build.mjs
- tooling/connect/cpp-symbol-check.mjs
- tooling/connect/cpp-smoke.mjs
Expand Down Expand Up @@ -105,7 +108,7 @@ jobs:
shell: pwsh
run: Get-Content crates/spoke-connect/bindings/cpp/native/provenance.json
- name: Check C ABI symbols
run: node tooling/connect/cpp-symbol-check.mjs --header crates/spoke-connect/bindings/cpp/include/spoke_connect.h --library crates/spoke-connect/bindings/cpp/native/win-x64/spoke_connect_capi.dll
run: node tooling/connect/cpp-symbol-check.mjs --header crates/spoke-connect/bindings/cpp/include/spoke_connect.h --library crates/spoke-connect/bindings/cpp/native/win-x64/spoke_connect_capi.dll --self-test

- name: Run C++ smoke
run: node tooling/connect/cpp-smoke.mjs --rid win-x64
Expand All @@ -127,3 +130,61 @@ jobs:
path: ${{ runner.temp }}/spoke-connect-win-x64
if-no-files-found: error
retention-days: 5

macos-smoke:
name: macos-smoke
runs-on: macos-14
timeout-minutes: 30
env:
# Keep the higher-precedence global Rust flag channels empty so the
# committed carrier's recorded build recipe remains authoritative.
RUSTFLAGS: ""
CARGO_ENCODED_RUSTFLAGS: ""
steps:
- name: Checkout
# actions/checkout@v4.2.2
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683

- name: Setup Node.js
# actions/setup-node@v4.4.0 — Node 24
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
with:
node-version: 24

- name: Setup Rust toolchain
# dtolnay/rust-toolchain@stable (2026-07-16) — exact stable pin for
# the macOS carrier's aarch64-apple-darwin target.
uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4
with:
toolchain: 1.96.0
targets: aarch64-apple-darwin

- name: Check Rust flag precedence
shell: bash
run: |
if [[ -n "${RUSTFLAGS}" ]]; then
echo "RUSTFLAGS must be empty; it would override the committed carrier recipe" >&2
exit 1
fi
if [[ -n "${CARGO_ENCODED_RUSTFLAGS}" ]]; then
echo "CARGO_ENCODED_RUSTFLAGS must be empty; it would override the committed carrier recipe" >&2
exit 1
fi
echo "RUSTFLAGS and CARGO_ENCODED_RUSTFLAGS are controlled empty."

- name: Report compiler and toolchain
shell: bash
run: |
node -v
clang++ --version
rustc +1.96.0 -Vv

- name: Show native provenance
shell: bash
run: cat crates/spoke-connect/bindings/cpp/native/provenance.json

- name: Check C ABI symbols
run: node tooling/connect/cpp-symbol-check.mjs --header crates/spoke-connect/bindings/cpp/include/spoke_connect.h --library crates/spoke-connect/bindings/cpp/native/osx-arm64/libspoke_connect_capi.dylib --self-test
Comment thread
greptile-apps[bot] marked this conversation as resolved.
Resolved

- name: Run C++ smoke
run: node tooling/connect/cpp-smoke.mjs --rid osx-arm64
4 changes: 2 additions & 2 deletions .mstar/knowledge/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ Durable protocol and tooling facts. Normative specs live in [`specs/`](../specs/
| [`conventions/docs-i18n-glossary.md`](conventions/docs-i18n-glossary.md) | conventions | compound 2026-08-03 | CN integrator-docs terminology glossary: authority order (README_CN → CONCEPTS → glossary); wire-term EN+gloss table; VitePress zh nav labels; SSOT stays English; twin consistency rules |
| [`architecture-patterns/connect-capability-token-auth.md`](architecture-patterns/connect-capability-token-auth.md) | architecture-patterns | compound 2026-08-01 (capability-token); 2026-08-04 issuance ergonomics + canonical-sig rule | capability-token auth method: claims/proof design (deny-unknown-fields, sig over JCS(claims)); trusted_issuers trust root; sub/aud/exp checks; membership capability matching; challenge/response + per-invoke auth; `auth_failed` vocabulary; base58btc inverse peer-id decode; offline validation, jti reserved for revocation; **issuance-side fail-fast** (empty caps / exp / iat guards with required 3rd positional `now: u64` on Rust `issue_capability_token`); **canonical base64url signature rule** (RFC 4648 round-trip check on both sides; see `base64-canonical-sig-ts-rust-parity.md`) |
| [`testing-patterns/connect-identity-parity-proof.md`](testing-patterns/connect-identity-parity-proof.md) | testing-patterns | compound 2026-08-01 (identity proof); 2026-08-04 CI gate | Cross-language identity-byte parity: golden vectors as contract; standalone proof-script pattern (`tooling/connect-identity-proof/`), CI-gated via the `connect-identity` job (Node 24, fail-open path filter); port gotchas (protobuf PublicKey, identity multihash, JCS optional-field omission, base64url, WebCrypto DER) |
| [`tooling-decisions/connect-publish-staging.md`](tooling-decisions/connect-publish-staging.md) | tooling-decisions | compound 2026-08-03 (publish strategy) | Registry split: npm/crates.io for primary TS/Rust connect; four binding channels (GitHub Packages NuGet/Maven, SPM git, Go modules git, PyPI OIDC); lockstep SemVer on the same tag gate |
| [`tooling-decisions/connect-publish-staging.md`](tooling-decisions/connect-publish-staging.md) | tooling-decisions | compound 2026-08-03 (publish strategy) | Registry split: npm/crates.io for primary TS/Rust connect; binding channels grouped registry-backed (GitHub Packages NuGet/Maven, PyPI OIDC) vs git-based (SPM, Go modules, committed C/C++ headers and platform carriers); lockstep SemVer on the same tag gate |
| [`architecture-patterns/integrator-docs-site-ssot-links.md`](architecture-patterns/integrator-docs-site-ssot-links.md) | architecture-patterns | compound 2026-08-02 (docs site); 2026-08-04 Diátaxis four-quadrant + zero-spec-links restructure | Integrator docs-site pattern: VitePress `docs/` with **Diátaxis four-quadrant layout** (Tutorials / How-to / Reference / Explanation); **audience boundary — zero `.mstar/specs/` links on integrator pages** (consolidated Reference carries field tables inline; external canonicals + root CONCEPTS.md for vocabulary); GitHub Pages deploy via SHA-pinned Actions; concurrency lesson (ref-scoped workflow group so PR runs never cancel main deploys); EN↔CN twin-parity CI gate; **wire-fact fidelity** (5 at-risk fact categories to land explicitly during consolidation) |
| [`architecture-patterns/base64-canonical-sig-ts-rust-parity.md`](architecture-patterns/base64-canonical-sig-ts-rust-parity.md) | architecture-patterns | compound 2026-08-04 (canonical-sig round) | Canonical base64url signature round-trip across TS (slack-lenient `atob`) and Rust (`base64` 0.22 strict `URL_SAFE_NO_PAD`): keep the round-trip check on both sides even when one side makes it unreachable — source-level parity invariant + defense-in-depth against decoder relaxation; slack-bit mutation formula for the cross-language test fixture; new-language-port checklist (verify decoder strictness first) |
| [`architecture-patterns/connect-ts-noise-xx-stack.md`](architecture-patterns/connect-ts-noise-xx-stack.md) | architecture-patterns | compound 2026-08-04 (ts-noise-xx) | Pure-TS `Noise_XX_25519_ChaChaPoly_SHA256` stack: snow-engine recorder (dev-only example, engine behind libp2p-noise) for genuine rust-libp2p golden-transcript interop; u16-BE framing + `NoiseHandshakePayload` Ed25519 static-key binding with verify-before-use; opt-in `./noise` subpath isolation (tsup multi-entry + esbuild metafile bidirectional trace + dist-shape smoke that caught the canonicalize ESM-only CJS defect) |
Expand All @@ -56,6 +56,6 @@ Durable protocol and tooling facts. Normative specs live in [`specs/`](../specs/
| [`architecture-patterns/ke-capability-flag-naming.md`](architecture-patterns/ke-capability-flag-naming.md) | architecture-patterns | compound 2026-09-17 (ke axes) | Capability flags anchor to the concern's actual carrier: `l<N>-<concern>` only for genuine single-layer concerns, named `ke-<concern>` family for cross-layer ones; independent flags, no umbrella; viewpoint on shared `Scope`; owner references holder KE `entry_id` |
| [`architecture-patterns/extract-op-injected-extraction.md`](architecture-patterns/extract-op-injected-extraction.md) | architecture-patterns | compound 2026-09-17 (ke axes + remote/FFI exposure) | `ke-extraction` extract op: standalone `ExtractionPort` + `orchestrateExtract` with injected async extractor (never composed into BaselinePorts); correlation `run_id` without durable run objects; provisional-only candidates through unchanged promote gates; null/absent `coverage_hint` equivalence across TS/Rust; **remote/FFI exposure**: service-shaped op over connect (loader never on wire), explicit service composition so a blanket probe cannot mask it, three distinguishable unavailability paths, paired-observation parity proof, and the host-side `Send` bridge constraint |
| [`conventions/parallel-plan-shared-doc-conflicts.md`](conventions/parallel-plan-shared-doc-conflicts.md) | conventions | compound 2026-09-17 (ke axes) | Parallel plans over shared spec docs: pre-declared conflict face, tail-append discipline, single-owner count constants (zero-delta plans never reset integrated counts), semantic enumeration merges |
| [`architecture-patterns/hand-written-c-abi-beside-uniffi.md`](architecture-patterns/hand-written-c-abi-beside-uniffi.md) | architecture-patterns | compound 2026-09-18 (C++ carrier for UE) | Hand-written C ABI beside a generated binding surface: choose C ABI when the consumer is an engine/toolchain (C++ ABI is not stable across compilers); isolate the carrier in a private crate so the primary crate's artifacts and timings do not change; wrap the public facade rather than re-deriving dispatch/crypto/selection; fix a mechanical boundary vocabulary (opaque owned handles, borrowed pointer+length valid for the call, Rust-allocated owned buffers with explicit free, `int32_t` status + error record, callback tables with mandatory `destroy`); gate the header on both symbols and representation (`sizeof`/`_Alignof`/`offsetof` static assertions) and prove the gate can fail; give two export faces a neutral internal seam instead of mutual `pub(crate)` imports; state engine-side verification as unverified until an engine runs it |
| [`architecture-patterns/hand-written-c-abi-beside-uniffi.md`](architecture-patterns/hand-written-c-abi-beside-uniffi.md) | architecture-patterns | compound 2026-09-18 (C++ carrier for UE); 2026-09-18 (C++ convenience layer) | Hand-written C ABI beside a generated binding surface: choose C ABI when the consumer is an engine/toolchain (C++ ABI is not stable across compilers); isolate the carrier in a private crate so the primary crate's artifacts and timings do not change; wrap the public facade rather than re-deriving dispatch/crypto/selection; fix a mechanical boundary vocabulary (opaque owned handles, borrowed pointer+length valid for the call, Rust-allocated owned buffers with explicit free, `int32_t` status + error record, callback tables with mandatory `destroy`); gate the header on both symbols and representation (`sizeof`/`_Alignof`/`offsetof` static assertions) and prove the gate can fail; give two export faces a neutral internal seam instead of mutual `pub(crate)` imports; state engine-side verification as unverified until an engine runs it |
| [`testing-patterns/connect-handshake-gates-invoke-timeout.md`](testing-patterns/connect-handshake-gates-invoke-timeout.md) | testing-patterns | compound 2026-09-18 (connect handshake budgets) | A remote-adapter test's `invoke_timeout_ms` also caps the dial handshake, so the budget must sit above handshake latency (100 ms) with the host delay above the budget (200 ms), aligned across the TS and Rust twins |

Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ The connect crate already exported a uniffi surface (proc-macro, one cdylib, fiv

**Give two export faces a neutral seam, not mutual imports.** When an adapter face and a responder face start importing each other's `pub(crate)` items for shared callback handles or output writers, extract those into one internal module and re-point both. Verify the move is behaviour-neutral: empty symbol diff, byte-identical header, unchanged check conclusions, and an import search that shows no production cross-face reference remains.

**Ship the convenience layer as a header-only wrapper, not a second library.** Once the C ABI exists, the host-language ergonomics belong in one hand-written header that includes it: move-only owning types for the C resources (the output buffer, the opaque handles), the language's optional type for the C optional-value convention, a `Result`-style return for every status, borrowed length-authoritative text views with an explicit owned copy, and callback records built from function objects and transferred through an owning pointer only after the C constructor succeeded. Two rules stay absolute: **destruction frees but never closes a live session** (a router or another handle may still reference it), and neither side of the wrapper may change an exported symbol, a record layout or the ABI revision. A header-only layer adds no artifact and no version-bearing manifest, so it stays inside the existing tag-based distribution and needs no new publish channel.

**Verify the wrapper under both exception configurations, and make the runner's assertions exact.** The interesting failure here is silent. With exceptions disabled, nothing may `throw` across the boundary and the trampoline must preprocess to plain status returns; with them enabled, the trampoline must contain a throwing host callback and map it to the documented status without reclassifying a normal return. Run the same smoke under both flag sets — they are two real code paths, not a platform matrix. Then require each configuration's printed assertion banners to equal its expected list **exactly**: a lenient "every expected banner appeared, in order" check happily accepts a banner leaked from the other configuration, which is precisely the build-guard failure the two-configuration run exists to catch.

**Keep the foreign-boundary's awkward cases distinct.** A zero-length foreign buffer is not an empty buffer — if the carrier does not release a zero-length record, allocating a holder for it leaks, so emit the zero record instead. Likewise, a present-but-empty optional field and an absent one are different values on this boundary and must survive the wrapper unmapped.

**State engine-side verification as unverified until an engine runs it.** A module skeleton (`ModuleType.External`, include paths, `PublicAdditionalLibraries`, `RuntimeDependencies` staging per platform directory) plus a standalone C++ smoke is the honest maximum without the engine; the smoke proves the ABI, not the engine.

## Why This Matters
Expand Down
11 changes: 6 additions & 5 deletions .mstar/knowledge/tooling-decisions/connect-publish-staging.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,19 @@ date: 2026-08-03
problem_type: tooling_decision
category: tooling-decisions
severity: medium
applies_when: ["publishing a new connect surface from the protocol repo", "staging a registry publish of a connect artifact", "choosing npm/crates.io vs GitHub Packages vs SPM vs Go modules vs PyPI for a connect binding"]
tags: [spoke-connect, publish, npm, nuget, maven, github-packages, spm, go-modules, pypi, staging, lockstep-semver, trusted-publishing, bindings]
applies_when: ["publishing a new connect surface from the protocol repo", "staging a registry publish of a connect artifact", "choosing npm/crates.io vs GitHub Packages vs SPM vs Go modules vs PyPI vs committed git carriers for a connect binding"]
tags: [spoke-connect, publish, npm, nuget, maven, github-packages, spm, go-modules, pypi, cpp, git-based, staging, lockstep-semver, trusted-publishing, bindings]
---

# Connect publish staging (registry split)

## Context

The connect embedding model ships several surfaces with different publishing costs: a pure-TS client, a Rust crate (libp2p + optional `ffi` cdylib), per-language uniffi bindings, and an integrator docs site. The connect publish strategy (`.mstar/specs/connect-publish-strategy.md`) stages registries so primary TS/Rust surfaces use npm/crates.io Trusted Publishing while Path B bindings use **four channel types** across five languages (packaging contract: `.mstar/specs/connect-binding-channels.md`).
The connect embedding model ships several surfaces with different publishing costs: a pure-TS client, a Rust crate (libp2p + optional `ffi` cdylib), per-language uniffi bindings, a hand-written C/C++ carrier, and an integrator docs site. The connect publish strategy (`.mstar/specs/connect-publish-strategy.md`) stages registries so primary TS/Rust surfaces use npm/crates.io Trusted Publishing while Path B bindings split into registry-backed channels (GitHub Packages NuGet, GitHub Packages Maven, PyPI) for C#/Kotlin/Python and git-based channels (Swift Package Manager, Go modules, committed C/C++ headers and platform carriers) for Swift/Go/C and C++ (packaging contract: `.mstar/specs/connect-binding-channels.md`).

## Guidance

### Registry split: npm/crates.io for primary; four channels for bindings
### Registry split: npm/crates.io for primary; registry-backed and git-based channels for bindings

| Surface | Registry / mechanism | Rule |
|---|---|---|
Expand All @@ -27,6 +27,7 @@ The connect embedding model ships several surfaces with different publishing cos
| Swift binding | **GitHub repo + SPM** | Root `Package.swift` + `vX.Y.Z` tags; consumers `.package(url:from:)` |
| Go binding | **GitHub repo + Go modules** | Root `go.mod` + `vX.Y.Z` tags; consumers `go get …@vX.Y.Z` |
| Python binding | **PyPI** | `publish-pypi` via Trusted Publishing OIDC; `pip install spoke-connect` |
| C/C++ binding | **git — committed headers + platform carriers** | `include/spoke_connect.h` + `include/spoke_connect.hpp` + `native/<rid>/` taken from the `vX.Y.Z` tag; workspace-private carrier, no publish job |
| Docs site | **GitHub Pages** | Companion site on main |

### Lockstep SemVer continuation
Expand All @@ -51,7 +52,7 @@ C# bindings hit a toolchain gap (`uniffi-bindgen-cs` targets uniffi 0.31 vs the

## Why This Matters

Registry choice is a cost and trust decision: npm/crates.io OIDC Trusted Publishing covers the primary TS/Rust surfaces; each binding language uses its ecosystem-native channel (GitHub Packages for C#/Kotlin, SPM/Go git tags, PyPI OIDC) without forcing public mirrors before demand exists. Lockstep SemVer keeps integrators on one pin across wire + connect + bindings.
Registry choice is a cost and trust decision: npm/crates.io OIDC Trusted Publishing covers the primary TS/Rust surfaces; each binding language uses its ecosystem-native channel (GitHub Packages for C#/Kotlin, SPM/Go git tags, PyPI OIDC, committed headers and platform carriers for C/C++) without forcing public mirrors before demand exists. Lockstep SemVer keeps integrators on one pin across wire + connect + bindings.

## When to Apply

Expand Down
Loading
Loading