feat: cross-language symmetry — Wave A (bugs + cheap [O] omissions across all bindings) - #164
Open
tlmquintino wants to merge 11 commits into
Open
feat: cross-language symmetry — Wave A (bugs + cheap [O] omissions across all bindings)#164tlmquintino wants to merge 11 commits into
tlmquintino wants to merge 11 commits into
Conversation
…defined) Symmetry audit BUG-FFI: the header documented tgm_last_error_object_index() in the TGM_ERROR_MISSING_HASH note, but the function did not exist — the offending object index was unreachable from C. Add a thread-local object-index alongside the last-error message: to_error_code records it from HashMismatch/MissingHash, set_last_error clears it (plain-string errors carry none). New extern returns the 0-based index or -1. Header regenerated; test extended.
…on, streaming introspection Symmetry Wave A (Python lane): - BUG-PY: add object_inline_hashes(buf) (binds core data_object_inline_hashes) so v3 inline hashes are readable; fix the dangling DataObjectDescriptor.hash docstring (pointed at non-existent Message methods). - AsyncStreamingEncoder (feature async): write_object/_pre_encoded/_preceder/ finish/finish_backfilled + object_count/bytes_written. - StreamingEncoder gains write_preceder/object_count/bytes_written. - compute_common(base) binds core (was reimplemented in jupyter/01). 42 new tests; full suite 693 passed. Example 19 added.
…_order, export DecodeStreamOptions Symmetry Wave A (TS lane): - BUG-TS: TensogramFile.append now forwards the full AppendOptions (allowNan/ allowInf/*MaskMethod/smallMaskThresholdBytes) to encode, not just hash. - TensogramFile.create: empty-file factory (mirrors core file::create), creates parent dirs; append() extends it. - native_byte_order decode option (minimal wasm widen; trailing optional keeps existing decode byte-identical). - Re-export DecodeStreamOptions from index.ts. +17 tests; suite 454 passed (was 437), no regressions. Example 19 added.
…essors, pre-encoded, decode variants Symmetry Wave A (Fortran lane): bind 23 previously-omitted synchronous C-ABI functions (49->72 tgm_* bound) — TGM_WIRE_VERSION + message/metadata version; tensogram_doctor; validate/validate_file; scan; compute_hash + inline-hash accessors; object byte_order/type/filter/compression/encoding/strides; encode_pre_encoded; decode_object/range/metadata. All [O] omissions (no language-limited items). 105-assertion test; fortran ctest 26/26; f2008 clean. Example validate.f90 added.
…ange, task control, integrity object_index Symmetry Wave A (C++ lane) — wrap C-ABI functions C++ never exposed ([O]): - doctor() -> tgm_doctor_to_json (whole category was missing). - compute_simple_packing_params() typed wrapper over tgm_simple_packing_compute_params. - async_file::decode_object/decode_range on callback/coro/std_future frontends (tgm_async_file_decode_object/_range + join_multi_bytes/multi_bytes_free). - async_file::path()/async_streaming_encoder::path(); pull-model task<T> with ready()/cancel()/join() (tgm_async_task_is_ready/_cancel). - integrity_error::object_index() sourced from tgm_last_error_object_index() (MissingHash/HashMismatch offending object now reachable in C++). 31 new tests; cpp ctest 241/241.
There was a problem hiding this comment.
Pull request overview
Implements “Wave A” of the cross-language interface symmetry roadmap by fixing confirmed binding defects and exposing previously-omitted (already-supported) capabilities across C ABI, Python, TypeScript/WASM, Fortran, and C++, with accompanying tests and examples to keep surfaces aligned with the Rust core reference.
Changes:
- Adds missing/buggy binding surface area (notably C ABI last-error object index, TS append option forwarding + file factory, Python inline-hash access + compute helpers, C++ doctor/async decode helpers).
- Widens decode/encode option plumbing (e.g., TS
nativeByteOrder) and improves structured error reporting (object index for integrity failures). - Adds new cross-language examples and expanded test coverage validating the new APIs end-to-end.
Reviewed changes
Copilot reviewed 43 out of 43 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| typescript/tests/streaming.test.ts | Adds a regression test ensuring DecodeStreamOptions is nameable via index re-export. |
| typescript/tests/nativeByteOrder.test.ts | Adds tests covering DecodeOptions.nativeByteOrder behavior and forwarding through TensogramFile.message. |
| typescript/tests/create.test.ts | Adds tests for TensogramFile.create workflows (truncate, mkdir parents, URL support, invalid args). |
| typescript/tests/append.test.ts | Adds regression tests proving append() forwards full mask/NaN/Inf options to encode(). |
| typescript/src/types.ts | Adds DecodeOptions.nativeByteOrder documentation and option surface. |
| typescript/src/index.ts | Re-exports DecodeStreamOptions type. |
| typescript/src/file.ts | Implements TensogramFile.create, adds node write helpers, and fixes append() option forwarding. |
| typescript/src/decode.ts | Plumbs nativeByteOrder through to the wasm decode functions. |
| rust/tensogram-wasm/src/lib.rs | Widens wasm decode/decode_object exports to accept native_byte_order. |
| rust/tensogram-ffi/tensogram.h | Declares the new tgm_last_error_object_index() C ABI function. |
| rust/tensogram-ffi/src/lib.rs | Implements thread-local last-error object index tracking + adds a regression test. |
| python/tests/test_streaming_encoder_introspection.py | Tests new StreamingEncoder introspection + write_preceder behavior. |
| python/tests/test_object_inline_hashes.py | Tests tensogram.object_inline_hashes() (v3 inline hash accessibility). |
| python/tests/test_compute_common.py | Tests tensogram.compute_common() binding and edge cases. |
| python/tests/test_async_streaming_encoder.py | Tests the new AsyncStreamingEncoder (async writes, finish modes, introspection, errors). |
| python/bindings/src/lib.rs | Adds Python bindings for inline hashes, compute_common, streaming encoder introspection, and async streaming encoder. |
| python/bindings/python/tensogram/init.py | Adds explicit Python re-exports for compute_common / object_inline_hashes. |
| python/bindings/Cargo.toml | Extends async feature dependencies to include tokio (sync mutex). |
| fortran/test/test_symmetry.f90 | Adds a broad symmetry test exercising newly exposed Fortran procedures. |
| fortran/src/tensogram.F90 | Adds Wave-A Fortran bindings: version, doctor/validate/scan/hash, descriptor/hash accessors, pre-encoded encode, decode variants. |
| fortran/CMakeLists.txt | Wires in the new Fortran symmetry test and validate example. |
| examples/typescript/README.md | Documents new TypeScript example for create + mask options. |
| examples/typescript/package.json | Adds script for the new TS example and includes it in all. |
| examples/typescript/19_file_create_masks.ts | Demonstrates TensogramFile.create and mask-option append round-trip. |
| examples/python/README.md | Documents new Python example and newly exposed API surface items. |
| examples/python/19_async_streaming_and_common.py | Demonstrates async streaming encode, compute_common, and object_inline_hashes. |
| examples/fortran/validate.f90 | Adds Fortran example for scan/version/validate workflows. |
| examples/fortran/README.md | Documents the new Fortran validate example. |
| examples/cpp/README.md | Documents new C++ doctor and async object/range example. |
| examples/cpp/CMakeLists.txt | Adds new C++ examples to the build. |
| examples/cpp/25_async_decode_object_range.cpp | Demonstrates async decode_object/decode_range + task handle/path accessors. |
| examples/cpp/10_doctor.cpp | Demonstrates tensogram::doctor() reporting. |
| cpp/tests/test_simple_packing.cpp | Adds tests for typed compute_simple_packing_params() helper. |
| cpp/tests/test_doctor.cpp | Adds tests for tensogram::doctor() wrapper output shape/stability. |
| cpp/tests/test_decode_verify_hash.cpp | Adds tests for structured integrity-error object index accessor. |
| cpp/tests/test_async_task_control.cpp | Adds tests for async path accessors and pull-model task control (ready/cancel/join). |
| cpp/tests/test_async_decode_object_range.cpp | Adds tests for async decode_object/decode_range in callback + std::future frontends. |
| cpp/tests/test_async_coro.cpp | Adds coroutine-frontend tests for decode_object/decode_range and task/path access. |
| cpp/tests/CMakeLists.txt | Registers new C++ tests in the build. |
| cpp/include/tensogram/async/std_future.hpp | Exposes async decode_object/decode_range and pull-model tasks + path for std::future frontend. |
| cpp/include/tensogram/async/coro.hpp | Exposes async decode_object/decode_range and pull-model tasks + path for coroutine frontend. |
| cpp/include/tensogram/async/callback.hpp | Adds async range decode buffer handling + pull-model task<T> API + new decode/path methods. |
| cpp/include/tensogram.hpp | Adds doctor wrapper, typed simple packing params helper, and structured integrity error object_index plumbing. |
Comments suppressed due to low confidence (1)
cpp/include/tensogram/async/callback.hpp:695
- Same empty-
rangesissue as the callback-baseddecode_range:offsets.data()/counts.data()may be null for empty vectors, but the FFI requires non-null pointers regardless ofn_ranges. Ensure non-null pointers are passed even whenranges.size() == 0.
tensogram::detail::check(tgm_async_file_decode_range(
handle_.get(), msg_index, obj_index,
offsets.empty() ? nullptr : offsets.data(),
counts.empty() ? nullptr : counts.data(),
ranges.size(), true, 0,
token ? token->raw() : nullptr,
static_cast<uint64_t>(timeout.count()), &raw));
return task<detail::range_buffers>(raw, &detail::join_multi_bytes_result);
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+224
to
+228
| if (typeof path !== 'string' && !(path instanceof URL)) { | ||
| throw new InvalidArgumentError( | ||
| 'TensogramFile.create: path must be a string or file:// URL', | ||
| ); | ||
| } |
Comment on lines
+601
to
+611
| tgm_async_task_t* task = nullptr; | ||
| tgm_error err = tgm_async_file_decode_range( | ||
| handle_.get(), msg_index, obj_index, | ||
| offsets.empty() ? nullptr : offsets.data(), | ||
| counts.empty() ? nullptr : counts.data(), | ||
| ranges.size(), | ||
| true, // native_byte_order | ||
| 0, // threads | ||
| token ? token->raw() : nullptr, | ||
| static_cast<uint64_t>(timeout.count()), | ||
| &task); |
Comment on lines
+1806
to
+1817
| [[nodiscard]] inline simple_packing_params compute_simple_packing_params( | ||
| const double* values, std::size_t num_values, | ||
| int bits_per_value, int decimal_scale_factor = 0) { | ||
| simple_packing_params params{}; | ||
| params.decimal_scale_factor = static_cast<std::int32_t>(decimal_scale_factor); | ||
| detail::check(tgm_simple_packing_compute_params( | ||
| values, num_values, | ||
| static_cast<std::uint32_t>(bits_per_value), | ||
| static_cast<std::int32_t>(decimal_scale_factor), | ||
| ¶ms.reference_value, ¶ms.binary_scale_factor)); | ||
| return params; | ||
| } |
…arity The Wave-A native_byte_order option added a 4th param to wasm decode (and 5th to decode_object); rust/tensogram-wasm/tests/wasm_tests.rs still called the old arity, breaking make wasm-test. Pass the trailing None at all call sites. 161 wasm integration tests green.
…n family, decode_with_masks)
Symmetry Wave B (C-ABI widening; unblocks C++/Fortran Wave C):
- tgm_validate_buffer (multi-message -> JSON FileValidationReport).
- tgm_compute_common (metadata handle -> {common,remaining} JSON) +
tgm_verify_canonical_cbor.
- Scan family: tgm_scan_file/_with_options/_file_with_options, TgmScanOptions,
tgm_data_object_inline_hashes.
- tgm_decode_with_masks + tgm_object_has_masks + tgm_object_mask(kind) +
TgmMaskKind (per-object NaN/±Inf boolean masks, one byte/element).
Purely additive; 31 new tests (198->229); clippy clean; header no drift.
Deferred: wire-introspection enums, remote, typed-dtype enums.
…zy iterators, range-from-payload Symmetry Wave D (Python, binds core directly): scan_file/scan_with_options/ scan_file_with_options + ScanOptions; validate_buffer (multi-message); TensogramFile.message_layouts()/MessageLayout + open_mmap (mmap feature on); lazy objects()/objects_metadata() iterators; decode_range_from_payload. 78 new tests; suite 771 passed.
…inline hashes Symmetry Wave D (TS/wasm): decodeWithMasks (wasm masks.rs -> per-object NaN/±Inf Uint8Array masks); lazy objects()/objectsMetadata() generators; scanWithOptions; dataObjectInlineHashes. +32 tests; suite 486 passed.
…, decode_with_masks
Symmetry Wave C (C++ wraps the Wave-B C ABI):
- validate_buffer(buf,len,level,check_canonical) -> JSON.
- metadata::compute_common() -> {common,remaining} JSON; verify_canonical_cbor().
- scan_file/scan_with_options/scan_file_with_options + scan_options struct;
data_object_inline_hashes() -> vector<optional<string>>.
- decode_with_masks() + decoded_object::has_masks()/mask(mask_kind).
38 new tests; cpp ctest 279/279.
…mily, decode_with_masks
Symmetry Wave C (Fortran wraps the Wave-B C ABI):
- tensogram_validate_buffer -> JSON.
- tensogram_compute_common -> {common,remaining} JSON; verify_canonical_cbor -> logical.
- scan_file/scan_with_options/scan_file_with_options + tensogram_scan_options POD;
data_object_inline_hashes -> JSON array.
- decode_with_masks + object_has_masks/object_mask(kind) + TGM_MASK_KIND_* params
(+ encode_with_masks to produce mask frames for the round-trip test).
93-check test; fortran ctest 28/28; f2008 clean.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 69 out of 69 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (1)
typescript/src/file.ts:228
- The error message says "string or file:// URL", but this function only treats
file://as aURLobject (a"file://..."string would be passed tofs.writeFileas a literal path and fail). Consider clarifying the message to explicitly say "file:// URL object" (or alternatively add support for file-URL strings).
Comment on lines
+77
to
+86
| let defaults = core::ScanOptions::default(); | ||
| let max_message_size = match max_message_size { | ||
| Some(v) if v.is_finite() && v >= 0.0 => v as u64, | ||
| Some(v) => { | ||
| return Err(JsValue::from(js_sys::Error::new(&format!( | ||
| "max_message_size must be a non-negative finite number, got {v}" | ||
| )))); | ||
| } | ||
| None => defaults.max_message_size, | ||
| }; |
Comment on lines
+123
to
+130
| if ( | ||
| opts?.maxMessageSize !== undefined && | ||
| (!Number.isFinite(opts.maxMessageSize) || opts.maxMessageSize < 0) | ||
| ) { | ||
| throw new InvalidArgumentError( | ||
| `maxMessageSize must be a non-negative finite number, got ${String(opts.maxMessageSize)}`, | ||
| ); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
First implementation wave of the cross-language interface symmetry roadmap (
plans/INTERFACE_SYMMETRY.md§8, docs in #163). Closes the confirmed defects and the cheap [O] omissions — capabilities the backend already provides that a binding simply never exposed. Reference = Rust core. Each lane is disjoint by binding and self-verified.C ABI
tgm_last_error_object_index()(was documented in theTGM_ERROR_MISSING_HASHnote but undefined). Thread-local object index recorded alongside the last error; header regenerated. The MissingHash/HashMismatch offending object is now reachable from C (and C++ — see below). 198 ffi tests.Python
object_inline_hashes(buf)(binds coredata_object_inline_hashes) makes v3 inline hashes readable; fixed the danglingDataObjectDescriptor.hashdocstring.AsyncStreamingEncoder;StreamingEncoderintrospection (object_count/bytes_written/write_preceder);compute_common. +42 tests → 693 passed.TypeScript
TensogramFile.appendnow forwards the fullAppendOptions(mask options were silently dropped).TensogramFile.create;native_byte_orderdecode option (minimal wasm widen, existing decode byte-identical); exportDecodeStreamOptions. +17 tests → 454 passed.Fortran
TGM_WIRE_VERSION+ version getters,doctor,validate/validate_file,scan,compute_hash+ inline-hash accessors, objectbyte_order/type/filter/compression/encoding/strides,encode_pre_encoded,decode_object/range/metadata. All [O]; no language-limited items. 105-assertion test; ctest 26/26; f2008 -Werror clean.C++
doctor()(whole category was missing); typedcompute_simple_packing_params();async_file::decode_object/decode_rangeon all three async frontends; asyncpath()+ pull-modeltask<T>(ready()/cancel()/join());integrity_error::object_index()sourced from the new C ABI symbol. +31 tests → cpp ctest 241/241.Notes
decode_with_masks,scan_file/ScanOptions,validate_buffer, typed enums, wire introspection, sync remote — then Wave C C++/Fortran wrappers, Wave E convert) is tracked inplans/INTERFACE_SYMMETRY.md.Docs Preview
https://sites.ecmwf.int/docs/tensogram/pull-requests/PR-164