mirror of
https://github.com/zap-proto/py.git
synced 2026-10-30 15:04:09 +00:00
Differential fuzzing against Go's zapgen turned up three names the emitter could not carry. A field called wrap or build was refused outright; a field called from generated a keyword; two fields answering to one name generated a duplicate argument, which is a SyntaxError rather than a diagnosis. Now one rule covers all of them: a name Python or the view already claims takes the trailing underscore PEP 8 prescribes, so `None` — a real field name in the fleet — reads back as None_. Two fields that land on the same name are refused by name, the one place this reader is stricter than the reference, which accepts that schema and writes Go that will not compile.
6.4 KiB
6.4 KiB
zap-proto (Python) — ZAP zero-copy app proto
Python ZAP implementation. Full docs in README.md. PyPI dist name zap-proto,
import zap. The core is pure stdlib — import zap pulls no third-party dep.
src/zap/:
wire— the zero-copy codec. Pure-stdlib port of the canonical Go runtime (zap-proto/go: zap.go + builder.go + accessors.go + list_cursor.go).Message/Object/Listreads +Builder/ObjectBuilder/ListBuilderwrites; 16-byteZAP\x00header; Version1+2 accept; reader hardening (reject backward pointers into the header, clamp list length). Byte-faithful to Go — pinned by the shared golden vector intests/test_wire.py.rpc— real TCP request/response transport.Serverdispatches theZapinterface method ordinals (Methodenum, @0..@8 fromzap.zap);Clientcalls them. JSON body, magic-framed envelope (reusesprotocol).pipeline— promise pipelining (the ONE canonical ZAP model), the faithful Python peer of Go'szap-proto/go/rpc(Session+Pipeliner) and TS@zap-proto/zappromise.ts.Session(client: monotonicpromise_idallocator starting at 1 +origin/pipelineCallbuilders that settargettoNO_TARGETvs a prior promise's id) +Pipeliner(server: per-connection promise table that records each OK answer's Body under itspromise_id, substitutes a resolved target's Body for a dependent's Payload before dispatch, QUEUES a dependent whose target is unresolved until it resolves, REFUSES —STATUS_BAD_REQUEST— a dependent whose target answered non-OK or was finished; never hangs).finish(id)bounds the table with a terminal "finished" state that refuses + wakes parked dependents. Operates on the binaryCall/Responseenvelope built withzap.wire(build_request/build_response/parse_request/parse_response,target@8) — byte-for-byte identical to Gorpc.BuildRequest/BuildResponseand TSbuildRequest/buildResponse(pinned bytests/test_pipeline.py, which asserts equality with golden Go hexes and which were verified to round-trip through the real GoParseRequest/ParseResponse). NOTE: this is the SAME wire as Go/TS pipelining and is DISTINCT fromrpc.py's JSON{id,method,params}transport envelope — pipelining shares the byte-level wire with the other runtimes; therpc.Server/ClientJSON transport does not. Pure stdlib.frame— thezapdrouter-envelope codec, byte-compatible withzap-proto/zapd/src/frame.rs+ZapClient(connect / hello / providers.list / route). Used byhanzo-tools-browser'szapd_consumer.protocol— magic-framed JSON framing (ZAP\x01+ type + length + JSON);decode_streamdemuxes back-to-back frames.app— FastMCP-style decorator app (ZAP);run()serves realrpc. Needs the[app]extra (pydantic). Lazy-imported fromzap/__init__.py.client—Client(sync RPC overrpc) +ZapClient(router UDS).crypto— real ML-KEM-768 / ML-DSA-65 (pqcrypto) + X25519 (cryptography), hybrid HKDF-SHA256 KEX. Needs the[crypto]extra. Hard-fails withCryptoErrorwhen a backend is missing — never fabricates bytes.cap— the capability runtime (signed, attenuable authority tokens). Faithful port ofzap-proto/go/capover thecapabilities.zapv1.1 schema.wrap,Capview,canonical_bytes(SPEC §3 signed scope — byte-identical to Go),id=SHA-256(canonical_bytes ‖ Sig)(SPEC §4).Issuance/issue/attenuateenforce the SPEC §2.3 delegation gate at MINT time (parent needsPERM_ATTENUATEorCapKind.DELEGATE), monotonic permission narrowing, holder→issuer linkage, and downward-only expiry.Verifier.verify/verify_chainenforce the full §2.3 invariants with FAIL-CLOSED scheme dispatch (reserved tag0x00and unknown tags refused — never downgraded).Revocation/revoke/verify_revocation(scheme-aware). Schemes: Ed25519 (mandatory bootstrap), ML-DSA-65 (FIPS 204), secp256k1 ECDSA — all real and verified; a missing backend raisesSchemeUnavailable(anUnhandledSchemeError, so fail-closed). Wire/canonical/CapID paths are pure stdlib; signing needs the[crypto]extra. Cross-language interop is pinned bytests/testdata/cap_go_kat.{hex,json}— a Go-signed cap that decodes, recomputes the same canonical bytes + CapID, and verifies in Python (and a deterministic Python re-issue is byte-identical to Go).identity— W3C DID (did:zap/did:key/did:web).consensus— voting.schema— native.zapschema support: read a schema, generate Python bindings.python -m zap.schema SCHEMA.zapwrites one<schema>_zap.py. The schema is whitespace-significant (indentation, no braces), and reading it is the SAME two steps aszap-proto/go'scmd/zapgen:desugarrewrites the whitespace form into the canonical brace form — a near-identity that only ADDS the{/}and@offsettokens the brace grammar requires, so a pure-brace file round-trips byte-for-byte — then the brace parser runs unchanged. Two styles, one grammar, and both runtimes read a schema the same way. Emitted bindings are zero-copy views overzap.wire(wrapparses, properties read fields IN PLACE at their schema offsets,buildwrites) plus, per interface, an ordinalIntEnum, a handlerProtocol, adispatch_*, and a typed client overzap.pipeline(Channel/CallFailedlive there, so a generated module declares only the schema's own names). Pinned against Go two ways intests/test_schema.py: the desugar golden corpus iscmd/zapgen/desugar_test.goverbatim, andPing.build/BaseTx.buildmust reproduce the bytes the GO generator's own output writes. Measured on the 263 unique.zapfiles in the estate: the two generators AGREE on accept-or-reject for all 263 (135 accept, 128 reject, same message), and every accepted schema's generated module imports, isruff-clean and passesmypy --strict. A name Python or the view already claims (from,None,wrap,build) takes the trailing underscore PEP 8 prescribes; two fields answering to one name is refused, the one place this reader is stricter than Go, which accepts that schema and writes Go that does not compile. Pure stdlib.
Gates (CI in .github/workflows/ci.yml): ruff check, ruff format --check,
mypy --strict src/zap/, pytest — all green. Use uv for the venv.