The client validates source_id with ^[a-z0-9][a-z0-9_-]{1,62}$ (plexus/client.py, _SOURCE_ID_RE), but the gateway's wire contract (gateway/validate.go) accepts ^[a-z0-9][a-z0-9._-]*$ up to 256 chars — dots allowed, and 1-char slugs are valid.
Practical impact: slugs like web.frontend (valid on the wire, used by other SDKs and documented in docs.plexus.company's hardware overview) raise ValueError from the Python client, and 1-character slugs are rejected.
Suggested fix: align _SOURCE_ID_RE with the wire rule — ^[a-z0-9][a-z0-9._-]*$, max length 256. Optionally also reject uuid-shaped slugs, which the app now rejects at source creation (they're unreachable through the app's ref resolvers).
For reference, plexus-typescript ships the wire rule + uuid-shape rejection (src/wire.ts).
Related note found in the same review (separate issue-worthy): the client gzips bodies >1KB with Content-Encoding: gzip, which the gateway historically never decompressed; the gateway is gaining transparent gzip support, after which the Python path works as intended.
The client validates
source_idwith^[a-z0-9][a-z0-9_-]{1,62}$(plexus/client.py,_SOURCE_ID_RE), but the gateway's wire contract (gateway/validate.go) accepts^[a-z0-9][a-z0-9._-]*$up to 256 chars — dots allowed, and 1-char slugs are valid.Practical impact: slugs like
web.frontend(valid on the wire, used by other SDKs and documented in docs.plexus.company's hardware overview) raiseValueErrorfrom the Python client, and 1-character slugs are rejected.Suggested fix: align
_SOURCE_ID_REwith the wire rule —^[a-z0-9][a-z0-9._-]*$, max length 256. Optionally also reject uuid-shaped slugs, which the app now rejects at source creation (they're unreachable through the app's ref resolvers).For reference,
plexus-typescriptships the wire rule + uuid-shape rejection (src/wire.ts).Related note found in the same review (separate issue-worthy): the client gzips bodies >1KB with
Content-Encoding: gzip, which the gateway historically never decompressed; the gateway is gaining transparent gzip support, after which the Python path works as intended.