Against real-world OTP 26 peers the v6 handshake "succeeded" but the
first REG_SEND silently dropped on the peer side — peer accepted the
connection then closed the link with no bytes when we tried to call a
registered process. Hit during MPS↔portal wallet RPC smoke test.
Root cause: OTP 25+ requires DFLAG_MANDATORY_25_DIGEST to be present
in our advertised flag set. The digest is the hash of the OTP-25
mandatory flag set; without it the peer's dist driver loses confidence
in the negotiation and drops messages from us without surfacing an
error.
Adds DFLAG_MANDATORY_25_DIGEST to DEFAULT_FLAGS. Also extends
_decode_message to accept both legacy pass-through (0x70 ...) and
dist-header framing (0x83 0x44 0x00 ...) on receive — modern OTP may
send dist-headed messages even when we didn't negotiate
DFLAG_DIST_HDR_ATOM_CACHE. Fragments (0x83 0x45 / 0x83 0x46) still
TODO; we surface a clear ChannelError instead of silent corruption.
Send side still uses pass-through framing — we don't yet implement
the atom-cache encode/decode that DFLAG_DIST_HDR_ATOM_CACHE would
require. Peer routes our pass-through sends without issue.
122/122 tests pass including live integration against a local Erlang
node and the TLS dist suite.
After protecting the * tag pattern on the python/ group, the group-
scoped Protected TWINE_USERNAME/TWINE_PASSWORD vars inject into tag
pipelines. Twine 6+ sees them set and uses classic auth directly,
skipping Trusted Publishing (which can't work for self-hosted
git.unturf.com — see docs/PYPI-TRUSTED-PUBLISHING.md).
What this commit changes:
- .gitlab-ci.yml drops the --trusted-publishing flag (unrecognized
by the runner's twine anyway) — twine sees env vars and is happy
- pyproject.toml stays on the latest setuptools (no <77 cap needed
since we're not pinning twine<6 anymore)
- Diagnostic for TWINE_USERNAME/TWINE_PASSWORD presence stays so
future failures surface fast
Pipeline #40358 (v0.1.4) failed with the same OIDC error as 0.1.0:
TrustedPublishingFailure: Unable to retrieve an OIDC token from
the CI platform for trusted publishing GitLab: Environment
variable PYPI_ID_TOKEN not found
Twine 6's default is --trusted-publishing automatic, which ALWAYS
tries OIDC first when it detects GitLab CI env vars, regardless of
whether TWINE_USERNAME/TWINE_PASSWORD are set. Adding
'--trusted-publishing never' forces classic API token auth via env
vars (or .pypirc) and skips the OIDC dance entirely.
Plus a sanity-check that the group-scoped TWINE_USERNAME and
TWINE_PASSWORD vars actually landed in the env on this pipeline
('set' or 'MISSING' — never echoes the value). If they're MISSING
despite being configured at the python/ group level, the most likely
cause is the vars being marked Protected while the tag isn't a
protected ref (Settings -> Repository -> Protected tags).
Pending publisher registered on PyPI for python/erldistpy with
workflow filepath .gitlab-ci.yml — GitLab now mints a short-lived
OIDC ID token (audience=pypi) per pipeline, twine 6+ exchanges it
for a scoped upload token. No long-lived secret on the runner.
What this commit changes:
- .gitlab-ci.yml pypi-twine stage gains id_tokens: PYPI_ID_TOKEN
- pip install twine (no <6 pin) — twine 6 is OIDC-native
- pyproject.toml build-system requires drops the setuptools<77 cap
(latest setuptools emits Metadata-Version 2.4 which twine 6 reads
fine; locally verified)
After this tag publishes successfully, the same pattern goes to:
- ago (python/ago)
- make-post-sell (engineering/make-post-sell)
- remarkbox (engineering/remarkbox)
each gets a Trusted Publisher entry added on the PyPI project page,
then the id_tokens block lands in their CI.
Recipe lives at docs/PYPI-TRUSTED-PUBLISHING.md (updated to reflect
the new auth model is now live for erldistpy).
Pipeline #40349 (v0.1.2) failed because the test stage ran on the tag
push and failed (env mismatch on the runner), which skipped the
pypi-twine stage downstream. MPS uses 'except: - tags' on test so
only pypi-twine runs for tag pipelines; mirror that.
Test stage still runs on every branch push, just not on tag pushes.
Pipeline #40344 (v0.1.1) failed at twine check with 'Metadata is
missing required fields: Name, Version' — twine 5.x doesn't recognize
Metadata-Version 2.4 wheels (it caps at 2.3). setuptools 77.0 (March
2025) bumped wheels to 2.4 for PEP 639 license-expression support.
Capping the build env's setuptools at <77 keeps wheels on Metadata-
Version 2.2 (verified locally — twine 5 PASSED).
Long-term fix is Trusted Publishing OIDC, which lets us drop the
twine<6 pin AND the setuptools cap. See the migration plan in our
next commit / pipeline discussion.
twine 6 (Sep 2025) auto-detects GitLab CI envvars and refuses to
fall back to ~/.pypirc on the runner, requiring PYPI_ID_TOKEN
(Trusted Publishing OIDC) instead. Pipeline #40338 (v0.1.0 tag) hit
this and failed with TrustedPublishingFailure.
Pinning twine<6 restores classic ~/.pypirc auth that ago /
make_post_sell / remarkbox already use on the same runner. When we
migrate to Trusted Publishing as a coordinated change across all four
python/* repos, we'll drop this pin and add an id_tokens block + PyPI
trusted-publisher config.
7 phases shipped: ETF codec, EPMD client, v6 handshake (MD5 cookie),
data channel (4-byte framing), gen_call protocol, TLS dist
(inet_tls_dist), unfeed integration. Maps + cross-stack validation
through wallet.unsandbox.com's test pyramid.
122 tests across unit / handshake / channel / node / tls / etf-maps.
Tag this commit with vX.Y.Z and push the tag — CI's pypi-twine stage
takes over from there.
pyproject.toml gains the metadata PyPI expects:
- Real author name + email
- Keywords (erlang, elixir, distribution, gen_server, ...)
- Classifiers (status, license, Python versions, topic)
- Project URLs (Homepage / Repository / Bug Tracker)
- readme content-type = text/markdown so PyPI renders our README
- build + twine added to the [dev] extra
.gitlab-ci.yml mirrors ago's two-stage pattern:
- test stage on every push (venv + pip install -e .[dev] + pytest)
- pypi-twine stage only on tags: python -m build, twine check,
twine upload. Credentials come from GitLab CI variables
TWINE_USERNAME (typically __token__) and TWINE_PASSWORD.
Makefile gains build / dist-check / publish-test / publish targets so
the release flow is also runnable locally if a dry run is needed.
Release flow:
1. Bump version in pyproject.toml
2. Commit + push
3. git tag -a vX.Y.Z -m 'release vX.Y.Z' && git push --tags
4. CI's pypi-twine stage picks up the tag and uploads
README.md adds an Install section + Quick start (plain dist + TLS dist)
so PyPI's project page shows usable copy on first visit. dist/ artifacts
build cleanly and both pass twine check.