Skip to content

feat: cross-language symmetry — Wave A (bugs + cheap [O] omissions across all bindings) - #164

Open
tlmquintino wants to merge 11 commits into
mainfrom
feat/symmetry-wave-a
Open

feat: cross-language symmetry — Wave A (bugs + cheap [O] omissions across all bindings)#164
tlmquintino wants to merge 11 commits into
mainfrom
feat/symmetry-wave-a

Conversation

@tlmquintino

@tlmquintino tlmquintino commented Jul 24, 2026

Copy link
Copy Markdown
Member

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

  • BUG-FFI — implement tgm_last_error_object_index() (was documented in the TGM_ERROR_MISSING_HASH note 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

  • BUG-PYobject_inline_hashes(buf) (binds core data_object_inline_hashes) makes v3 inline hashes readable; fixed the dangling DataObjectDescriptor.hash docstring.
  • AsyncStreamingEncoder; StreamingEncoder introspection (object_count/bytes_written/write_preceder); compute_common. +42 tests → 693 passed.

TypeScript

  • BUG-TSTensogramFile.append now forwards the full AppendOptions (mask options were silently dropped).
  • TensogramFile.create; native_byte_order decode option (minimal wasm widen, existing decode byte-identical); export DecodeStreamOptions. +17 tests → 454 passed.

Fortran

  • Bind 23 previously-omitted synchronous C-ABI functions (49→72): TGM_WIRE_VERSION + version getters, 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]; no language-limited items. 105-assertion test; ctest 26/26; f2008 -Werror clean.

C++

  • doctor() (whole category was missing); typed compute_simple_packing_params(); async_file::decode_object/decode_range on all three async frontends; async path() + pull-model task<T> (ready()/cancel()/join()); integrity_error::object_index() sourced from the new C ABI symbol. +31 tests → cpp ctest 241/241.

Notes

  • Every new capability has tests and at least one example in its language (symmetry Examples contract).
  • Remaining roadmap (Wave B C-ABI widening for the [B] backend gaps — 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 in plans/INTERFACE_SYMMETRY.md.

Docs Preview
https://sites.ecmwf.int/docs/tensogram/pull-requests/PR-164

…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.
Copilot AI review requested due to automatic review settings July 24, 2026 18:53
@tlmquintino
tlmquintino requested review from sametd and tmi as code owners July 24, 2026 18:53

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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-ranges issue as the callback-based decode_range: offsets.data() / counts.data() may be null for empty vectors, but the FFI requires non-null pointers regardless of n_ranges. Ensure non-null pointers are passed even when ranges.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 thread typescript/src/file.ts
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 thread cpp/include/tensogram.hpp
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),
&params.reference_value, &params.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.
Copilot AI review requested due to automatic review settings July 24, 2026 21:15

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 a URL object (a "file://..." string would be passed to fs.writeFile as 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 thread typescript/src/decode.ts
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)}`,
);
}
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.

2 participants