Skip to content

feat(tii): type-directed argument encoding to the TaggedArg wire form#20

Merged
scarmuega merged 1 commit into
mainfrom
feat/tagged-arg-encoder
Jun 22, 2026
Merged

feat(tii): type-directed argument encoding to the TaggedArg wire form#20
scarmuega merged 1 commit into
mainfrom
feat/tagged-arg-encoder

Conversation

@scarmuega

Copy link
Copy Markdown
Contributor

What

Parity with the Rust SDK: encodes an argument of an aggregate type (record, List, Map, Tuple, variant) into the TRP TaggedArg wire form. New tii/encode.py, wired into into_resolve_request.

One walk, not two paths

encode(param, value) is a single recursive walk over (type, value) — scalars are the leaf cases. A scalar leaf renders bare at the top level (resolver coerces via the flat TIR type) and tagged when nested; aggregates always render to their tagged structural form. Bare top-level scalars keep their pre-existing wire shape.

Encoding rules

  • Record → positional struct fields in declared order, from the schema's required array (tx3c emits source order; properties is alphabetized).
  • Variantconstructor = the oneOf case index.
  • Map[key, value] pairs; keys are string leaves (the .tii erases the key type), pairs sorted.
  • Missing/extra field, wrong arity, unknown case → raise before sending.

Note (not breaking)

The record ParamType stays a dict[str, ParamType] — now built in required order so iteration is positional. The public type is unchanged, so existing consumers are unaffected. The encoder is wired into the synced SDK release.

Tests

Vector-driven over the shared cross-SDK oracle (tests/fixtures/wire-vectors.json, mirrored from sdk-spec): accept vectors encode equal, reject vectors raise; plus the Meta field-order regression, top-level-bare vs nested-tagged, and an end-to-end into_resolve_request check. pytest -m "not e2e" green (86); ruff clean.

🤖 Generated with Claude Code

Mirror the Rust reference: marshal every arg through one recursive walk
over (ParamType, value) in `tii/encode.py`, wired into
`into_resolve_request`. A scalar leaf renders bare at the top level (the
resolver coerces it via the flat type) and tagged when nested in an
aggregate; aggregates (list/tuple/map/record/variant) render to the
self-describing tagged form. No separate scalar/aggregate path.

Records map the user's by-name dict to positional struct fields in
declared (`required`-array) order; variants resolve the case index from
the `oneOf` ordering; map keys are `string` leaves, pairs sorted. Ill-
shaped values are rejected before sending.

The record ParamType stays a `dict[str, ParamType]` but is now built in
`required` order so iteration is positional; the public type is
unchanged.

Tests drive the shared cross-SDK oracle (accept + reject vectors).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@scarmuega scarmuega merged commit 1ffd838 into main Jun 22, 2026
3 checks passed
@scarmuega scarmuega deleted the feat/tagged-arg-encoder branch June 22, 2026 16:03
@scarmuega scarmuega mentioned this pull request Jun 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant