Skip to content

Value contracts ​

The frozen internal prototype is value-contracts.v2.json. That JSON file owns the policy. The TypeScript interface mirrors it and has revision 2.

Each contract names a logical value, its wire encoding, its decoded JavaScript value, and its constraints. Sequences, fixed tuples, records, and unions compose child contracts. A union has two to 32 alternatives. The compiler rejects a union if one wire value could decode two ways. Contract depth stops at 64. Named records can inline within that limit. Recursive aliases stay unsupported until a reviewed reference graph exists. The prototype covers float16 ndarrays and Torch tensors. Other Arrow types keep their existing rules. In particular, Arrow int64 may decode as bigint. Unsupported conversions carry a reason and guidance.

The wire stays at tywrap/1 and scientific codecVersion: 1. Existing Arrow envelopes, JSON fallback, and transports keep their shape. The producer rejects unsafe Python integers before it writes JSON. A generated wrapper that consumes this interface must keep number for Python int and add a safe-integer return constraint. Regenerate wrappers after that compiler change. Do not return bigint under an existing Promise<number>.

The bytes leaf keeps the existing base64 envelopes. Requests use __tywrap_bytes__; responses use __type__: bytes. Both decode to Uint8Array. The Torch float16 contract names torch.float16 on the outer tensor and float16 on its nested version 1 ndarray. The decoder rejects a different nested dtype before it decodes the data.

Migration for users with large Python integers: convert them to strings in Python with an explicit return annotation, or keep integer columns in Arrow tables. JSON fallback for ndarray and pandas values still rejects unsafe integers. This is a behavior change from silent rounding to a call error.

The fixed proof fixtures cover integer boundaries, binary16 values, unions, fixed tuples, and nested paths. The focused tests also compare every finite binary16 storage word with an independent float32 bit reference. They test signed zero with Object.is.

The Python serializer checks plain integers, NumPy scalar integers, and model dumps during its existing value walk. It also checks terminal estimator parameters and Series name metadata before JSON output. A JSON ndarray or sparse matrix checks integer data before it creates an envelope. Sparse metadata checks shapes and indices. The TypeScript decoder converts float16 Arrow storage during extraction, before it reshapes the ndarray. Torch uses that same ndarray path. These checks do not walk Arrow payloads again for schema validation.

Exact bigint extension for #337 ​

The revision-3 policy is a bounded prototype. It pins the revision-2 policy and adds one integer-exact leaf. Revision 2 remains the default safe-number policy. No default bridge supports bigint calls yet.

The internal EXACT_INTEGER_VALUE_CONVERSION selects revision 3 for a fixture module. It maps Python int to TypeScript bigint in parameters and returns. The generated validator requires bigint at nested integer nodes. Python bool and finite float keep their revision-2 rules. The compiler requires an explicit exact-integer-adapter capability. It is unavailable by default. The prototype has no public per-callable option for mixed modules.

The opted call uses a version-2 integer envelope with a canonical signed decimal string. A sample value is {"__tywrap__":"integer","codecVersion":2,"encoding":"decimal","value":"18446744073709551617"}. The companion test adapter must check the grammar and 4096-digit limit in both directions. It must use BigInt only after validation. Arrow int64 keeps its current path. Contract revision 3, envelope codecVersion: 2, per-call policy bigint-v2, and RPC protocol tywrap/1 are different version labels.

The bound test provider must see exactIntegerDecimalV2 in bridge metadata before it sends a tagged request. It sets params.valuePolicy.integer to bigint-v2. Calls without that policy keep revision-2 behavior. The production bridge does not negotiate this capability. Unknown versions and malformed decimal values must fail with a path.

The revision-3 fixtures record exact values, negative zero, and migration cost. The generated proof still needs integrated CI and independent review before #337 can close. A prototype does not enable bigint by default or authorize a release.

Dataclass return extension for #339 ​

This extension is a design sketch. Dataclass inputs remain unsupported and need a separate input contract. Users must pass a plain record through an explicitly typed function if they need that behavior now.

For an annotated dataclass return, compile a structural TypeScript record from dataclasses.fields. Include each instance field in the output, including fields with defaults or init=False. A default does not make a returned field optional. A field annotated Optional[T] permits null. Compose nested dataclasses, records, and sequences only when every field has a supported value contract. Reject ordinary class instances and unsupported fields with their result paths.

Use a version 2 dataclass envelope with a declared type identity and a fields record. For example, Point(1, 2) would carry {"__tywrap__":"dataclass","codecVersion":2,"encoding":"fields","type":"fixtures.values_torture.Point","fields":{"x":1,"y":2}}. The TypeScript decoder returns {x: 1, y: 2}. The generated validator must check the type identity, exact field names, field values, and required fields. It must reject extra fields unless the compiled contract allows them. The Python encoder must use the current bounded value walk. It must detect cycles and enforce depth, node, and payload limits without calling dataclasses.asdict as a shortcut.

The bridge must advertise dataclass value support. A generated dataclass wrapper must reject an older bridge before calling it. A new bridge must send the envelope only when the wrapper requests it. This leaves existing value RPC and ordinary class behavior unchanged.

Acceptance evidence still needed: a generated Promise<Point> wrapper must return the exact plain record and reject missing, extra, or wrong typed fields. Tests must cover optional and defaulted fields, nested values, cycles, unsupported fields, depth, and payload limits. An independent review must approve the type identity, version rule, and migration cost before #339 can close.

Released under the MIT License.