Skip to content

Commit fc72c93

Browse files
committed
Merge main into simplification — bring v1.9.x main commits up to v1.10
Pulls in 12 commits from main (blog posts, SDK PRs #90/#91, v1.9.3 bump, auto-handshake feature). Conflict resolution policy: - Code (pkg/daemon, pkg/registry, plugins): simplification wins — rc1–rc7 fixes take priority over main's v1.9.x evolution. - Workflows + SDK shipping (.github/workflows/publish-*, sdk/node/): main wins — Alex's npm bundling + 1.9-* clients. - Content (CHANGELOG, blog posts): simplification wins for rc trail. Compile fixes after merge: - daemon.go: add trustedagents import (main's auto-handshake code landed via auto-merge; the import was outside the conflict hunk). - sdk/cgo/bindings.go: drop PilotManagedScore + PilotManagedRankings cgo exports (the underlying Driver methods were retired with the polo subsystem on simplification; main's binding kept them). - pkg/tasksubmit/tasksubmit.go: stays deleted (removed on simplification, main's modify-after-delete reverted).
2 parents ed93632 + a008601 commit fc72c93

37 files changed

Lines changed: 5618 additions & 1118 deletions

.github/workflows/publish-node-sdk.yml

Lines changed: 131 additions & 502 deletions
Large diffs are not rendered by default.
Lines changed: 31 additions & 247 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,42 @@
11
name: Publish Python SDK
22

3-
# Pulls pre-built binaries from the matching GitHub release (built by
4-
# release.yml) instead of rebuilding the Go suite. This guarantees the
5-
# bytes shipped on PyPI are byte-identical to the bytes shipped on GitHub
6-
# Releases, preserves the version stamping (`-X main.version=$TAG`) and
7-
# the macOS ad-hoc codesign + xattr-strip done in release.yml.
8-
#
9-
# Triggered by `release: published` (after release.yml's tarballs exist).
10-
#
11-
# Branches whose name starts with `ci_` ALSO get a dry-run on every push.
12-
# This is the permanent rehearsal path for iterating on the workflow itself
13-
# without making a real release. The push run executes everything up to but
14-
# not including `twine upload` (the `publish` and `test-install` jobs are
15-
# gated to release events). Required naming convention — only `ci_*`
16-
# branches trigger the rehearsal; other branches are ignored.
17-
# See CONTRIBUTING.md → "Testing publish workflows" for the convention.
18-
193
on:
204
release:
215
types: [published]
22-
push:
23-
branches:
24-
- 'ci_*'
6+
workflow_dispatch:
7+
inputs:
8+
version:
9+
description: 'Version to publish (without leading v, e.g. 1.9.1)'
10+
required: true
11+
type: string
12+
prerelease:
13+
description: 'Treat as prerelease (skip publish; build only)'
14+
required: false
15+
type: boolean
16+
default: false
2517

2618
permissions:
2719
contents: write
2820
id-token: write
2921

22+
env:
23+
PILOT_VERSION: ${{ inputs.version || github.event.release.tag_name }}
24+
PILOT_PRERELEASE: ${{ inputs.prerelease || github.event.release.prerelease }}
25+
3026
jobs:
3127
test-sdk:
32-
if: "!github.event.release.prerelease"
3328
runs-on: ubuntu-latest
3429
steps:
3530
- name: Checkout repository
3631
uses: actions/checkout@v4
3732

38-
- name: Set up Python
39-
uses: actions/setup-python@v5
40-
with:
41-
python-version: '3.11'
33+
- name: Set up Docker Buildx
34+
uses: docker/setup-buildx-action@v3
4235

43-
# Run the Python SDK's own unit tests. The previous `make test-sdk`
44-
# under tests/integration/ was a stale reference (the Makefile is
45-
# gone) — kept that step's spirit (gate publish on SDK tests passing)
46-
# but pointed it at the pytest suite that actually exists.
47-
- name: Run Python SDK unit tests
48-
working-directory: sdk/python
36+
- name: Run SDK integration tests
4937
run: |
50-
pip install --upgrade pip
51-
pip install -e .[dev]
52-
pytest tests/ -v
38+
cd tests/integration
39+
make test-sdk
5340
timeout-minutes: 10
5441

5542
build-wheels:
@@ -59,195 +46,29 @@ jobs:
5946
include:
6047
- os: ubuntu-latest
6148
platform: linux
62-
release_arch: amd64
6349
- os: macos-latest
6450
platform: macos
65-
release_arch: arm64
6651
runs-on: ${{ matrix.os }}
6752
steps:
6853
- name: Checkout repository
6954
uses: actions/checkout@v4
7055

71-
- name: Resolve version + tag (release event or ci_* push)
56+
- name: Set version from release tag or workflow input
7257
shell: bash
73-
env:
74-
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
7558
run: |
76-
# Release event: use the tag of the release being published.
77-
# ci_* push: rehearse against the LATEST existing release.
78-
# Either way, strip the leading "v" to get the bare PEP 440 version
79-
# used by pyproject.toml and wheel filenames; keep the full tag for
80-
# downloading release artifacts.
81-
if [ "${{ github.event_name }}" = "release" ]; then
82-
TAG="${{ github.event.release.tag_name }}"
83-
else
84-
TAG=$(gh release view --repo "${{ github.repository }}" --json tagName --jq .tagName)
85-
echo "ci_* push rehearsal: using latest release ${TAG} as test source"
86-
fi
87-
VERSION="${TAG#v}"
88-
echo "version=$VERSION" >> $GITHUB_ENV
89-
echo "release_tag=$TAG" >> $GITHUB_ENV
90-
echo "Resolved: version=$VERSION, release_tag=$TAG"
91-
92-
# Update pyproject.toml so the wheel + sdist carry the release
93-
# version. This is the single source of truth for "what version
94-
# is this SDK?" — and it matches the release tag exactly.
59+
VERSION="${PILOT_VERSION}"
60+
VERSION="${VERSION#v}"
61+
echo "version=$VERSION" >> $GITHUB_ENV
9562
cd sdk/python
9663
sed -i.bak "s/^version = .*/version = \"$VERSION\"/" pyproject.toml
9764
rm pyproject.toml.bak
65+
echo "SDK version: $VERSION"
9866
99-
# Pull pre-built binaries from the GitHub release. Using the unauthenticated
100-
# download URL avoids consuming gh API rate limits for public repos.
101-
- name: Download release tarball
102-
shell: bash
103-
env:
104-
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
105-
run: |
106-
set -euo pipefail
107-
ARCH="${{ matrix.release_arch }}"
108-
# Map matrix.platform → GOOS naming used in the tarball filename.
109-
case "${{ matrix.platform }}" in
110-
linux) GOOS=linux ;;
111-
macos) GOOS=darwin ;;
112-
*) echo "unsupported platform: ${{ matrix.platform }}"; exit 1 ;;
113-
esac
114-
ARCHIVE="pilot-${GOOS}-${ARCH}.tar.gz"
115-
mkdir -p /tmp/pilot-release
116-
echo "Downloading ${ARCHIVE} from release ${release_tag}..."
117-
gh release download "${release_tag}" \
118-
--repo "${{ github.repository }}" \
119-
--pattern "${ARCHIVE}" \
120-
--pattern "checksums.txt" \
121-
--dir /tmp/pilot-release
122-
123-
# Verify SHA-256 against the release's checksums.txt.
124-
cd /tmp/pilot-release
125-
if [ -f checksums.txt ]; then
126-
EXPECTED=$(grep " ${ARCHIVE}\$" checksums.txt | awk '{print $1}')
127-
if [ -z "$EXPECTED" ]; then
128-
echo "Warning: ${ARCHIVE} not in checksums.txt; skipping verification"
129-
else
130-
if command -v sha256sum >/dev/null 2>&1; then
131-
ACTUAL=$(sha256sum "${ARCHIVE}" | awk '{print $1}')
132-
else
133-
ACTUAL=$(shasum -a 256 "${ARCHIVE}" | awk '{print $1}')
134-
fi
135-
if [ "$EXPECTED" != "$ACTUAL" ]; then
136-
echo "Checksum mismatch for ${ARCHIVE}!" >&2
137-
echo " expected: $EXPECTED" >&2
138-
echo " actual: $ACTUAL" >&2
139-
exit 1
140-
fi
141-
echo " Verified SHA-256: $ACTUAL"
142-
fi
143-
fi
144-
145-
# The release tarball ships daemon, pilotctl, gateway, registry, beacon,
146-
# rendezvous, nameserver, updater. The Python SDK only ships the four
147-
# client-side binaries — registry/beacon/rendezvous/nameserver are
148-
# server infrastructure not part of the user-facing wheel.
149-
#
150-
# The release uses bare names (`daemon`, `gateway`, `updater`); the SDK
151-
# uses prefixed names (`pilot-daemon`, `pilot-gateway`, `pilot-updater`)
152-
# to avoid colliding with system binaries on PATH. Rename here.
153-
#
154-
# libpilot is NOT in the release tarball — it's CGO-built in the next
155-
# step using the same release tag for its version stamp.
156-
- name: Layout binaries into pilotprotocol/bin/
157-
shell: bash
158-
env:
159-
# Resolve GOOS at workflow-eval time so the run-script doesn't
160-
# need its own case statement. bash 3.2 (macOS default) chokes
161-
# on `case` inside `$(...)` command substitution.
162-
GOOS: ${{ matrix.platform == 'macos' && 'darwin' || 'linux' }}
163-
ARCH: ${{ matrix.release_arch }}
164-
run: |
165-
set -euo pipefail
166-
ARCHIVE="pilot-${GOOS}-${ARCH}.tar.gz"
167-
168-
BIN_DIR="sdk/python/pilotprotocol/bin"
169-
rm -rf "$BIN_DIR" && mkdir -p "$BIN_DIR"
170-
tar -xzf "/tmp/pilot-release/${ARCHIVE}" -C "$BIN_DIR"
171-
172-
cd "$BIN_DIR"
173-
# Drop infrastructure binaries — not shipped in the SDK.
174-
rm -f registry beacon rendezvous nameserver
175-
176-
# Rename to SDK convention.
177-
[ -f daemon ] && mv daemon pilot-daemon
178-
[ -f gateway ] && mv gateway pilot-gateway
179-
[ -f updater ] && mv updater pilot-updater
180-
181-
# .pilot-version marker — the seeder reads this to compare against
182-
# whatever's installed at ~/.pilot/bin/. Always normalize to bare
183-
# version (no leading "v") so it's symmetric with install.sh.
184-
echo "${version}" > .pilot-version
185-
186-
echo ""
187-
echo "pilotprotocol/bin/ after release-tarball extraction:"
188-
ls -lh
189-
echo ""
190-
# Sanity: confirm version stamp made it through (release.yml builds
191-
# with -X main.version=$TAG; SDK shouldn't drop it). Run against
192-
# the runner's native arch only since cross-arch tarballs cannot
193-
# be exec'd here.
194-
if [ "${{ matrix.platform }}" = "linux" ] && [ -x pilotctl ]; then
195-
STAMPED=$(./pilotctl version 2>&1 | head -1)
196-
echo "pilotctl version → ${STAMPED}"
197-
case "$STAMPED" in
198-
*"${release_tag}"*|*"${version}"*)
199-
echo " ✓ version stamp matches release tag"
200-
;;
201-
*)
202-
echo " ✗ version stamp '${STAMPED}' does not contain '${release_tag}'!" >&2
203-
echo " ✗ pilotctl version must report the release tag, not 'dev'." >&2
204-
echo " ✗ release.yml is supposed to build with -X main.version=\$GITHUB_REF_NAME." >&2
205-
exit 1
206-
;;
207-
esac
208-
fi
209-
210-
# Build libpilot.{so,dylib} — the CGO shared library used by the SDK
211-
# via ctypes. Stamped with the same release tag (`-X main.version=$TAG`)
212-
# so SDK provenance is consistent with the executables pulled from
213-
# the release tarball. Codesigned ad-hoc on darwin (mirrors release.yml
214-
# for the executables).
21567
- name: Set up Go
21668
uses: actions/setup-go@v5
21769
with:
21870
go-version-file: go.mod
21971

220-
- name: Build libpilot CGO shared library
221-
shell: bash
222-
env:
223-
GOOS: ${{ matrix.platform == 'macos' && 'darwin' || 'linux' }}
224-
GOARCH: ${{ matrix.release_arch }}
225-
CGO_ENABLED: '1'
226-
run: |
227-
set -euo pipefail
228-
case "$GOOS" in
229-
linux) EXT=so ;;
230-
darwin) EXT=dylib ;;
231-
esac
232-
BIN_DIR="sdk/python/pilotprotocol/bin"
233-
LDFLAGS="-s -w -X main.version=${release_tag}"
234-
echo "Building libpilot.$EXT for ${GOOS}/${GOARCH}, version=${release_tag}..."
235-
go build -buildmode=c-shared -ldflags "$LDFLAGS" \
236-
-o "${BIN_DIR}/libpilot.${EXT}" ./sdk/cgo
237-
# Drop the auto-generated header — SDKs load via ctypes by symbol,
238-
# not against the C header.
239-
rm -f "${BIN_DIR}/libpilot.h"
240-
241-
if [ "$GOOS" = "darwin" ]; then
242-
echo "Ad-hoc codesigning libpilot.${EXT}..."
243-
codesign --force --sign - "${BIN_DIR}/libpilot.${EXT}"
244-
xattr -cr "${BIN_DIR}/libpilot.${EXT}" || true
245-
fi
246-
247-
echo ""
248-
echo "Final pilotprotocol/bin/ contents (post-libpilot):"
249-
ls -lh "$BIN_DIR"
250-
25172
- name: Set up Python
25273
uses: actions/setup-python@v5
25374
with:
@@ -263,12 +84,9 @@ jobs:
26384
26485
- name: Build wheel
26586
shell: bash
266-
env:
267-
PILOT_SKIP_BUILD_BINARIES: '1'
268-
SKIP_TWINE_CHECK: '1'
26987
run: |
27088
cd sdk/python
271-
chmod +x scripts/build.sh
89+
chmod +x scripts/build-binaries.sh scripts/build.sh
27290
./scripts/build.sh
27391
27492
- name: Convert to manylinux wheel (Linux only)
@@ -309,11 +127,8 @@ jobs:
309127
retention-days: 7
310128

311129
publish:
312-
# Real publishes only fire on `release: published`. ci_* push rehearsals
313-
# run everything up through wheel-build + version-stamp checks, then stop
314-
# here so no test wheel reaches PyPI.
315-
if: github.event_name == 'release'
316130
needs: build-wheels
131+
if: ${{ !(inputs.prerelease || github.event.release.prerelease) }}
317132
runs-on: ubuntu-latest
318133
steps:
319134
- name: Download all artifacts
@@ -374,7 +189,6 @@ jobs:
374189
echo "**Version:** ${{ steps.extract-version.outputs.version }}" >> $GITHUB_STEP_SUMMARY
375190
echo "**Install:** \`pip install pilotprotocol\`" >> $GITHUB_STEP_SUMMARY
376191
echo "**PyPI:** https://pypi.org/project/pilotprotocol/" >> $GITHUB_STEP_SUMMARY
377-
echo "**Source:** Pulled binaries from release \`${{ github.event.release.tag_name }}\` (no rebuild)" >> $GITHUB_STEP_SUMMARY
378192
379193
test-install:
380194
needs: publish
@@ -391,38 +205,8 @@ jobs:
391205
- name: Wait for PyPI propagation
392206
run: sleep 60
393207

394-
- name: Resolve expected version from release tag
395-
shell: bash
396-
run: |
397-
TAG="${{ github.event.release.tag_name }}"
398-
VERSION="${TAG#v}"
399-
echo "version=$VERSION" >> $GITHUB_ENV
400-
echo "release_tag=$TAG" >> $GITHUB_ENV
401-
402-
- name: Install and verify version stamp matches release
403-
shell: bash
208+
- name: Install and verify
404209
run: |
405-
set -euo pipefail
406-
pip install --no-cache-dir pilotprotocol=="${version}"
407-
408-
# Smoke-load the FFI module — confirms libpilot loads on this host.
409-
python -c "from pilotprotocol import Driver; print('SDK module imports OK')"
410-
411-
# Assert pilotctl reports the actual release tag (not 'dev').
412-
# This is the regression test for the reported issue: pip-installed
413-
# pilotprotocol must run pilotctl with the release version stamped in.
414-
STAMPED=$(pilotctl version 2>&1 | head -1)
415-
echo "pilotctl version → ${STAMPED}"
416-
case "$STAMPED" in
417-
*"${release_tag}"*|*"${version}"*)
418-
echo "✓ pilotctl version reports the release tag"
419-
;;
420-
*dev*|*unknown*|"")
421-
echo "✗ pilotctl version reports '${STAMPED}' — looks unstamped." >&2
422-
exit 1
423-
;;
424-
*)
425-
echo "✗ pilotctl version reports '${STAMPED}', expected to contain '${release_tag}' or '${version}'." >&2
426-
exit 1
427-
;;
428-
esac
210+
pip install pilotprotocol
211+
pilotctl info 2>&1 | head -1 || true
212+
python -c "from pilotprotocol import Driver; print('SDK installed')"

pkg/daemon/daemon.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
"github.com/TeoSlayer/pilotprotocol/internal/account"
2525
"github.com/TeoSlayer/pilotprotocol/internal/crypto"
2626
"github.com/TeoSlayer/pilotprotocol/internal/fsutil"
27+
"github.com/TeoSlayer/pilotprotocol/internal/trustedagents"
2728
"github.com/TeoSlayer/pilotprotocol/internal/validate"
2829
"github.com/TeoSlayer/pilotprotocol/pkg/protocol"
2930
registry "github.com/TeoSlayer/pilotprotocol/pkg/registry/client"
@@ -2807,6 +2808,22 @@ func (d *Daemon) dialConnectionLocked(ctx context.Context, dstAddr protocol.Addr
28072808
return nil, err
28082809
}
28092810

2811+
// Auto-initiate handshake toward known trusted agents when we have no
2812+
// local trust entry yet. Scoped to the trusted-agents list so we don't
2813+
// spray handshakes at arbitrary peers. Fires non-blocking so the
2814+
// SYN-retry loop succeeds once the peer approves.
2815+
//
2816+
// d.handshakes is wired post-construction (see RegisterHandshakeService);
2817+
// integration tests that build a Daemon directly without the plugin set
2818+
// see it as nil — guard so this code path is a no-op there rather than
2819+
// a nil-deref. The on-wire SYN still goes out below; we just skip the
2820+
// proactive handshake when the plugin isn't loaded.
2821+
if d.handshakes != nil && !d.handshakes.IsTrusted(dstAddr.Node) {
2822+
if _, ok := trustedagents.IsTrusted(dstAddr.Node); ok {
2823+
go d.handshakes.SendRequest(dstAddr.Node, "")
2824+
}
2825+
}
2826+
28102827
localPort := d.ports.AllocEphemeralPort()
28112828
if localPort == 0 {
28122829
return nil, ErrEphemeralExhausted

0 commit comments

Comments
 (0)