From 74fb05e74bc6001d6432ab6bf6ac146bb3bfa8a4 Mon Sep 17 00:00:00 2001 From: Philipp Grosswiler Date: Tue, 1 Sep 2026 20:38:54 +0200 Subject: [PATCH 01/51] chore: cleaned up --- AGENTS.md | 14 -- CONTEXT.md | 41 ------ PROMPT.md | 8 +- README.md | 59 -------- .../0001-bitstream-and-scalar-wire-format.md | 36 ----- ...02-envelope-identity-and-layout-version.md | 50 ------- ...03-fixed-aggregate-and-optional-layouts.md | 42 ------ ...uthoring-and-generated-kotlin-interface.md | 53 ------- .../0005-generated-c99-header-interface.md | 48 ------- ...006-validation-diagnostics-and-mutation.md | 119 --------------- ...ormance-vectors-and-compatibility-gates.md | 110 -------------- ...0008-performance-budgets-and-benchmarks.md | 102 ------------- ...adle-modules-generation-and-publication.md | 129 ----------------- ...-descriptors-and-registry-compatibility.md | 136 ------------------ docs/agents/domain.md | 51 ------- docs/agents/issue-tracker.md | 45 ------ docs/agents/triage-labels.md | 15 -- 17 files changed, 2 insertions(+), 1056 deletions(-) delete mode 100644 CONTEXT.md delete mode 100644 README.md delete mode 100644 docs/adr/0001-bitstream-and-scalar-wire-format.md delete mode 100644 docs/adr/0002-envelope-identity-and-layout-version.md delete mode 100644 docs/adr/0003-fixed-aggregate-and-optional-layouts.md delete mode 100644 docs/adr/0004-schema-authoring-and-generated-kotlin-interface.md delete mode 100644 docs/adr/0005-generated-c99-header-interface.md delete mode 100644 docs/adr/0006-validation-diagnostics-and-mutation.md delete mode 100644 docs/adr/0007-conformance-vectors-and-compatibility-gates.md delete mode 100644 docs/adr/0008-performance-budgets-and-benchmarks.md delete mode 100644 docs/adr/0009-gradle-modules-generation-and-publication.md delete mode 100644 docs/adr/0010-canonical-descriptors-and-registry-compatibility.md delete mode 100644 docs/agents/domain.md delete mode 100644 docs/agents/issue-tracker.md delete mode 100644 docs/agents/triage-labels.md diff --git a/AGENTS.md b/AGENTS.md index bd4f7d3..c2d1d33 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -76,17 +76,3 @@ Yield only if all true: - Constitution compliant. Final report R `{changed files+behavior, exact commands+observed results, docs/API/compat/security/performance impact, blocker/unverified state, specialized instructions/skills used}`. X claim unobserved command/test/review/runtime behavior. - -## Agent skills - -### Issue tracker - -Issues are tracked in this repository's GitHub Issues. See `docs/agents/issue-tracker.md`. - -### Triage labels - -Triage uses the canonical `needs-triage`, `needs-info`, `ready-for-agent`, `ready-for-human`, and `wontfix` labels. See `docs/agents/triage-labels.md`. - -### Domain docs - -Domain documentation uses a single-context layout. See `docs/agents/domain.md`. diff --git a/CONTEXT.md b/CONTEXT.md deleted file mode 100644 index 65271d1..0000000 --- a/CONTEXT.md +++ /dev/null @@ -1,41 +0,0 @@ -# Kompact - -Kompact defines versioned, bit-packed BLE payload contracts shared by Kotlin applications and C firmware. - -## Language - -**Kompact schema**: -A versioned contract that assigns each field a precise representation and location in a bit-packed payload. -_Avoid_: Model, data class - -**Kompact envelope**: -The leading bits that identify the Kompact schema and its layout version before a payload is decoded. -_Avoid_: Header, discriminator - -**Protocol namespace**: -The enclosing BLE service or application protocol that selects one Kompact schema registry and gives its schema IDs meaning. -_Avoid_: Global registry, repository namespace - -**Layout version**: -An immutable numbered representation of one Kompact schema within a protocol namespace. -_Avoid_: Revision, format version - -**Fixed aggregate**: -A positive, fixed-count composition whose complete bit size and every element position are known from its Kompact schema. -_Avoid_: Collection, variable array - -**Nested schema**: -The body of one exact Kompact schema version embedded inside another schema without a second envelope. -_Avoid_: Embedded packet, child message - -**Reserved range**: -A named span of payload bits that must remain zero until a new layout version assigns them meaning. -_Avoid_: Padding, unused gap - -**Kompact view**: -A live, typed, non-owning interpretation of caller-owned bytes according to one Kompact schema. -_Avoid_: Model, wrapper, snapshot - -**Kompact writer**: -A typed interface that exclusively updates caller-owned bytes according to one Kompact schema while it is in use. -_Avoid_: Builder, serializer, shared mutator diff --git a/PROMPT.md b/PROMPT.md index 1e8038c..5d31599 100644 --- a/PROMPT.md +++ b/PROMPT.md @@ -5,7 +5,6 @@ The primary objective of Kompact is to serialize structured data for transmissio 1. **Microscopic Payload Sizes** (like Protobuf's bit efficiency, with zero byte padding). 2. **Zero-Copy, Zero-Allocation Reads** (like FlatBuffers, utilizing native Kotlin Multiplatform inline/value classes to wrap raw byte arrays). 3. **Pure Kotlin Ergonomics** (designed to be generated via an annotation processor or compiler plugin). -4. **Firmware Compatibility** (ability to export matching C-header bitmask definitions). --- @@ -22,9 +21,9 @@ The primary objective of Kompact is to serialize structured data for transmissio The developer should be able to define a data model in the `commonMain` source set like this: ```kotlin -package com.kompact.generated +package ch.trancee.kompact.generated -import com.kompact.runtime.* +import ch.trancee.kompact.runtime.* @KompactModel value class VehicleTelemetry(val raw: ByteArray) { @@ -65,6 +64,3 @@ Define the core common library annotations: Provide a complete, working example of a `Kompact` value class using the `KompactRuntime` to demonstrate how the boilerplate will eventually look when automated. Include: 1. The manual bit-shifting implementation of a model containing an Enum (4 bits), an Integer (10 bits), and a Boolean (1 bit)—packed into a 2-byte array (`ByteArray`). 2. A cross-platform test using `kotlin.test` showing serialization (writing values into the array) and deserialization (instantiating the value class wrapper and instantly reading values). - -#### Phase 4: C Header Exporter Specification -Draft a basic Kotlin utility function that can parse a Kompact data class declaration and print out a standard C `#define` macro header. This ensures our embedded/C firmware engineers can read the exact same BLE payload by applying the same bitmasks. diff --git a/README.md b/README.md deleted file mode 100644 index 08adb1e..0000000 --- a/README.md +++ /dev/null @@ -1,59 +0,0 @@ -# Kompact - -Bit-packed Kotlin Multiplatform serialization for Bluetooth Low Energy, designed for allocation-free reads and interoperable C firmware. - -> [!IMPORTANT] -> The Kompact v1 architecture and implementation specification are complete. Production implementation has not started, so the runtime, generator, and published artifacts do not exist yet. The closed [Kompact v1 implementation-ready specification](https://github.com/trancee/kompact/issues/1) map records every decision. - -## Why Kompact - -BLE payloads are small, and byte-aligned formats can spend more space on padding and metadata than the values require. Kompact defines each field at bit precision. A 5-bit value occupies 5 bits, including when it crosses a byte boundary. - -The project has four goals: - -- Pack fixed-size payloads without byte padding. -- Read scalar fields directly from caller-owned `ByteArray` storage without copying. -- Generate a typed Kotlin interface for shared Android and iOS code. -- Generate matching C99 constants and helpers for firmware. - -## Current design direction - -Kompact v1 is specified with these constraints: - -- Schemas have fixed, versioned layouts and an explicit envelope. -- Bit offset zero is the least-significant bit of byte zero. -- Kotlin runtime and generated code live in `commonMain` and target Android/JVM, `iosArm64`, and `iosSimulatorArm64`. -- A checked factory validates the envelope, version, and payload length before creating a view. -- Scalar properties read bits directly from the underlying buffer. -- Writers update caller-owned buffers in place and reject invalid values before mutation. -- KSP processes schemas and generates Kotlin code. -- Build-time JVM tooling generates portable C99 masks and byte-array helpers. It does not generate packed structs or C bitfields. -- Performance claims require measurements for reads, writes, allocations, encoded size, and code size. - -Variable-length fields, direct Swift export, compiler-plugin generation, and non-iOS Apple targets are outside the v1 scope. - -## Example layout - -A 16-bit telemetry payload can assign every bit without alignment padding: - -```text -bits 0..3 battery status 4-bit enum -bits 4..13 speed 10-bit unsigned integer -bit 14 engine malfunction 1-bit boolean -bit 15 reserved 1 bit -``` - -The same schema will drive generated Kotlin accessors and C99 extraction helpers. Developers author annotated schema interfaces; generated checked facades expose value-class views and writers in Kotlin and header-only typed handles in C99. - -## Specification status - -The closed [Kompact v1 implementation-ready specification](https://github.com/trancee/kompact/issues/1) is the canonical decision map. Its child issues record: - -- wire and envelope semantics; -- KSP and Gradle integration; -- generated Kotlin and C99 interfaces; -- validation and compatibility; -- cross-platform conformance; and -- performance budgets. - -Project-specific terminology lives in [`CONTEXT.md`](CONTEXT.md). diff --git a/docs/adr/0001-bitstream-and-scalar-wire-format.md b/docs/adr/0001-bitstream-and-scalar-wire-format.md deleted file mode 100644 index 16087d9..0000000 --- a/docs/adr/0001-bitstream-and-scalar-wire-format.md +++ /dev/null @@ -1,36 +0,0 @@ -# ADR-0001: Bitstream and scalar wire format - -Status: accepted - -## Context - -Kompact must encode fixed-size BLE payloads without alignment padding and produce identical results from common Kotlin on Android and iOS and from generated C99 helpers. Host byte order, Kotlin `Byte` signedness, C signed-shift behavior, enum declaration order, and NaN payload differences cannot define the wire format. - -## Decision - -A bit position `p` addresses byte `p / 8` and bit `p % 8` within that byte. Bit zero is the least-significant bit of byte zero. Field bit `i` maps to stream bit `bitOffset + i`, so a field's first bit is its value's least-significant bit. Fields may begin at any bit and cross byte boundaries. The format adds no alignment or implicit padding. - -Scalar representations are: - -- Boolean fields have width 1. Zero is false and one is true. -- Unsigned integer fields have widths from 1 through 64 and represent `0..2^width-1`. -- Signed integer fields have widths from 2 through 64 and use exact-width two's complement. Readers sign-extend into the selected Kotlin or C carrier. -- Enum fields have an explicit width from 1 through 32. Every entry has a unique, explicit, non-negative code that fits the width. Code gaps are valid. Checked wrapping rejects undeclared codes. -- Floating fields have width 32 or 64 and encode IEEE-754 binary32 or binary64 raw bits at any bit offset. Readers accept every bit pattern. Writers map every NaN to positive quiet NaN `0x7FC00000` or `0x7FF8000000000000`; infinities and signed zero retain their raw representations. -- Reserved ranges contain zero. New writers clear them, and checked wrapping rejects nonzero reserved bits. - -Width zero, out-of-bounds ranges, and values outside the declared representation are invalid. A writer validates the complete operation before mutation, changes only the target field, and preserves every other bit. Checked wrapping validates enum and reserved-bit constraints once. Scalar getters then use only deterministic byte, mask, shift, combine, and sign-extension operations. - -Kotlin converts each source byte with `toInt() and 0xFF` before shifting. C helpers use unsigned exact-width operations. Neither implementation may rely on host byte order or signed right shifts. - -## Alternatives - -MSB-first numbering was rejected because it complicates the direct mask-and-shift mapping without improving this protocol. Byte alignment was rejected because it violates the payload-size objective. Kotlin enum ordinals and inferred widths were rejected because source edits could silently change the wire contract. Tolerating unknown enum codes or nonzero reserved bits was rejected because each fixed layout has an explicit version. Preserving arbitrary NaN payloads on write was rejected because it permits multiple emitted encodings for the same semantic NaN. - -## Risks - -Canonical NaN writes discard NaN sign and payload information. Strict enum and reserved-bit validation requires a new layout version for extensions that use those codes or bits. A caller can mutate a shared `ByteArray` after checked wrapping and violate validated invariants; the generated-interface decision must define aliasing and trust rules. Allocation and latency claims for 64-bit and cross-byte operations still require target-specific measurements. - -## Migration - -No released wire format exists. Once a layout version ships, these scalar rules are immutable for that version. Any incompatible change creates a new layout version, and decoders must dispatch versions explicitly rather than reinterpret existing payloads. diff --git a/docs/adr/0002-envelope-identity-and-layout-version.md b/docs/adr/0002-envelope-identity-and-layout-version.md deleted file mode 100644 index 20f375f..0000000 --- a/docs/adr/0002-envelope-identity-and-layout-version.md +++ /dev/null @@ -1,50 +0,0 @@ -# ADR-0002: Envelope identity and layout versions - -Status: accepted - -## Context - -A decoder must identify a Kompact schema and its fixed layout before reading the body. Kotlin and C firmware need constant-time dispatch without field tags or a duplicated length value. Numeric identities must survive source renames and deletion, and deployed layouts must never be silently reinterpreted. - -## Decision - -Every top-level Kompact packet begins with a fixed 16-bit envelope governed by the LSB-first bitstream in ADR-0001: - -- Bits 0 through 11 contain a schema ID from 1 through 4095. -- Bits 12 through 15 contain a layout version from 0 through 15. -- Schema ID zero is reserved as an escape for a future envelope format and is invalid for v1 schemas. - -The two envelope bytes form `raw = byte0 | (byte1 << 8)`. The schema ID is `raw & 0x0FFF`, and the layout version is `raw >> 12`. Schema field offsets are body-relative, so body bit zero is packet bit 16. - -Each enclosing BLE service or application protocol selects one Kompact protocol namespace out of band. Schema IDs are unique within that namespace. Combining namespaces on one channel requires an explicit gateway or a merged registry. - -Each namespace owns a checked-in `kompact-registry.json`. The registry is an identity ledger, not a second editable schema definition. It records: - -- registry format version and protocol namespace; -- schema ID and stable schema name; -- layout version and exact body bit size; -- support status; -- a lowercase SHA-256 fingerprint of a versioned canonical schema descriptor; -- permanent tombstones for retired IDs and versions. - -Schema declarations own field definitions. Generation recomputes the canonical descriptor and fingerprint. A mismatch under an existing schema ID and layout version fails the build. Tooling never allocates identities implicitly and never reuses a retired numeric identity. ADR-0010 defines descriptor fields, JSON Schemas, normalization, comparison, lifecycle, and diagnostics. - -Version zero is the first layout. Versions increase monotonically through 15. After version 15, a changed layout receives a new schema ID, layout version zero, and a new stable schema name while the old identity remains in the registry; the new entry may link to the old identity through `supersedes`. Any wire or semantic change requires a new version, including a field addition or removal, offset, width, encoding, enum code, optionality, nested layout version, unit, range, or meaning. A Kotlin source rename may retain the version only when stable registry names and semantics remain unchanged. - -A decoder supports only versions explicitly marked supported. It rejects reserved schema ID zero, unknown schema IDs, unsupported versions, incorrect packet lengths, and nonzero transport-tail bits before reading the body. Removing a supported decoder is a breaking public and protocol change. Retirement changes registry status but never deletes history. - -The registry supplies the exact body bit size. The required packet length is `ceil((16 + bodyBitSize) / 8)` bytes. The envelope carries no length field. Truncated packets, extra bytes, and nonzero unused high bits after the final declared packet bit are invalid. - -The Kompact envelope contains no checksum, authentication tag, sequence number, or replay counter. The enclosing BLE or application protocol owns outer framing, integrity, authentication, sequencing, and replay protection. - -## Alternatives - -An 8-bit envelope was rejected because either schema or version space becomes too small for long-lived protocol namespaces. An 8-bit schema ID plus 8-bit version was rejected because 256 versions per schema are less useful than a larger schema registry. A variable-length envelope was rejected because it makes body offsets and firmware dispatch variable. Hash-derived IDs and annotation-only identity were rejected because collisions, renames, and deleted declarations can change or reuse wire identities. A per-packet length was rejected because fixed schema versions already define exact size. Automatic migration was rejected because bit layouts do not contain the semantic conversion rules it requires. - -## Risks - -The fixed envelope spends two bytes on every packet. Four version bits limit one schema ID to 16 layouts, so long-lived schemas may need a new ID. Namespace selection is out of band; decoding under the wrong enclosing protocol can map the same numeric ID to a different schema, so callers must bind the correct registry before accepting packets. Strict length and tail-bit checks reject concatenated or extended data. SHA-256 detects descriptor drift but does not authenticate a registry or payload. - -## Migration - -No released envelope exists. Once v1 ships, the 16-bit envelope mapping and numeric identities are immutable. Layout changes create a new version or, after version 15, a new schema ID and stable name beginning at version zero. Decoders retain explicitly supported old versions during staged application and firmware rollouts. A future envelope format begins with reserved schema ID zero and must define an explicit transition; v1 decoders fail closed when they encounter it. diff --git a/docs/adr/0003-fixed-aggregate-and-optional-layouts.md b/docs/adr/0003-fixed-aggregate-and-optional-layouts.md deleted file mode 100644 index 69cddae..0000000 --- a/docs/adr/0003-fixed-aggregate-and-optional-layouts.md +++ /dev/null @@ -1,42 +0,0 @@ -# ADR-0003: Fixed aggregate and optional layouts - -Status: accepted - -## Context - -Kompact v1 needs fixed byte sequences, arrays, optional values, and reusable nested schemas without losing compile-time offsets or adding alignment padding. Kotlin and C must derive the same element positions, absence encoding, total body size, and validation behavior. Variable-size values remain outside v1. - -## Decision - -Kompact v1 supports this recursive, fixed-size type grammar: - -- `Bytes` has positive length and bit size `8 * N`. Logical byte `j` occupies bits `offset + 8 * j` through `offset + 8 * j + 7` under ADR-0001's LSB-first rule. -- `Array` has positive count and bit size `N * bitSize(T)`. Element `i` begins at `offset + i * bitSize(T)`. -- `Optional` has bit size `1 + bitSize(T)`. Its presence bit comes first, followed immediately by the fixed value slot. `Optional>` is invalid. -- `Nested` embeds only the exact child body. It carries no child envelope. Its size comes from the protocol registry, and the parent descriptor fixes the child schema ID and layout version. - -Arrays may contain any fixed-size scalar, byte sequence, array, optional value, or nested schema. Schema references must be acyclic. A child wire or semantic change requires a new child version and a new parent layout version. - -Every value may begin at any bit offset. Aggregates, elements, and nested bodies have no alignment padding. Array indices increase toward higher stream offsets. Every body bit belongs to a field or an explicit reserved range; generation rejects overlaps and implicit gaps. - -An absent optional value has presence zero and an all-zero value slot. Setting a field to absent clears the entire slot. Checked wrapping rejects an absent field whose slot contains any nonzero bit. Presence one validates and decodes the slot under the wrapped type's normal rules. - -Checked wrapping validates the entire fixed body once, including enum codes, reserved ranges, optional slots, every array element, and every nested body. Direct getters and indexed reads do not repeat semantic validation. Array indices, size multiplication, offset addition, and packet-size calculations are checked for overflow before generation or access. - -The wire format permits finite bodies addressable by non-negative Kotlin `Int` bit offsets. Each protocol registry records a lower maximum packet byte size, and generation rejects schemas whose envelope plus body exceeds it. A zero-bit top-level body is valid and produces an envelope-only packet. - -An unaligned fixed byte sequence remains a non-owning value. Its generated Kotlin interface must expose indexed computed access rather than allocate a shifted `ByteArray` copy. The generated Kotlin and C interface decisions will define accessor names and representations without changing this layout. - -Variable-length arrays, variable-length byte sequences, strings, and recursive schema cycles are not supported in v1. - -## Alternatives - -Byte-aligning aggregates was rejected because it introduces up to seven padding bits before each value. Byte-stride array elements were rejected because narrow elements would consume more bits than declared. Nested envelopes were rejected because the parent already fixes the child identity and version. A schema-wide optional bitmap was rejected because it couples local fields to a global order without saving bits. Sentinel absence values were rejected because they remove valid values and do not apply uniformly. Ignored optional slots and implicit gaps were rejected because they permit multiple encodings of one semantic payload. - -## Risks - -Validating every element and nested body makes checked wrapping proportional to schema size even though later reads are direct. Unaligned byte-sequence access requires shift and combine operations for each logical byte. Body-only nesting couples a parent version to each child version. Deep but acyclic aggregate composition can increase generated code size and validation depth; performance and code-size budgets must cover representative nested schemas. The non-owning buffer can still be mutated after validation, so the generated-interface contract must define aliasing and concurrency limits. - -## Migration - -No released aggregate layout exists. After release, changing count, element type, optionality, child version, offset, reserved coverage, or aggregate composition creates a new parent layout version. Variable-size values require a future wire-format decision and cannot be introduced by reinterpreting a v1 fixed aggregate. diff --git a/docs/adr/0004-schema-authoring-and-generated-kotlin-interface.md b/docs/adr/0004-schema-authoring-and-generated-kotlin-interface.md deleted file mode 100644 index a9ba6c8..0000000 --- a/docs/adr/0004-schema-authoring-and-generated-kotlin-interface.md +++ /dev/null @@ -1,53 +0,0 @@ -# ADR-0004: Schema authoring and generated Kotlin interface - -Status: accepted - -## Context - -Kompact must let developers describe explicit wire layouts without writing bit arithmetic while keeping direct reads and valid writes allocation-free in the supported call shape. KSP can generate declarations but cannot add members to a user-authored value class. Generic, interface, and nullable value-class use can box, and a value class cannot carry both a `ByteArray` and a dynamic slice offset without another object. - -## Decision - -Developers author a declaration-only annotated interface. For example, `VehicleTelemetrySchema` carries `@KompactSchema`, abstract field properties, and explicit reserved-range annotations. It cannot be instantiated and contains no runtime behavior. - -`@KompactSchema` repeats the stable registry name, schema ID, and layout version. KSP fails generation if any value disagrees with the selected protocol registry. Each property declares `@KompactField(bitOffset, bitWidth)`. The property's Kotlin type selects its scalar carrier and signedness; KSP verifies that the width and encoding fit that type. Fields and reserved ranges must satisfy ADR-0001 through ADR-0003. - -KSP generates three declarations in the schema package with reserved, collision-checked names: - -- A stateless facade such as `VehicleTelemetry`, containing stable schema identity and packet-size constants plus checked construction functions. -- A read-only `VehicleTelemetryView`. -- A mutable `VehicleTelemetryWriter`. - -Generated visibility matches schema-interface visibility. Generated public signatures never expose the declaration interface. Generated files live only in the Gradle-owned build output. - -The facade provides: - -- `wrap(packet)`, which validates an existing exact-length packet and returns `KompactDecodeResult`. -- `initialize(packet)`, which requires the exact packet size, writes the envelope, clears the body, and returns `KompactDecodeResult`. -- `edit(packet)`, which validates an existing packet and returns `KompactDecodeResult`. - -A generic factory result may allocate or box once. Callers extract a concrete view or writer before entering a measured hot path. Exact error variants remain owned by the validation decision. - -The generated view and writer are common `@JvmInline value class` declarations. Each has an internal constructor and one internal `ByteArray` property. `@JvmInline` is required for the Android/JVM backend and is available as a common expected annotation. The backing array is not exposed publicly; the caller already owns the array it supplied. - -Scalar fields are direct `val` properties that preserve the declared Kotlin carrier. Fixed byte sequences and arrays use direct indexed methods. Nested arrays flatten index parameters so no dynamic slice object is required. A static nested field may return another `ByteArray`-backed value-class view because its bit offset is compile-time constant. - -Optional scalar fields expose `hasX: Boolean` and `xOr(defaultValue): T`. Writers expose `writeX(value)` and `clearX()`. A field write returns `KompactWriteError?`: null means success, and a typed error means validation rejected the operation without mutation. `writer.view()` reuses the same canonical packet without another validation pass. - -Views and writers accept only an exact packet array beginning at byte zero. They are live, non-owning interpretations: successful writes and any external mutation are immediately observable. Callers provide exclusive mutation and synchronization while a view or writer is in use. Generated views and writers are not thread-safe snapshots. - -Default value-class equality follows the backing `ByteArray` identity rather than packet contents. Generated `contentEquals` and `contentHashCode` methods provide explicit packet comparison. Generation does not add a field-dumping `toString`. - -The generated public interface contains no reflection, platform APIs, generic hot-path helpers, nullable view values, or Swift/Objective-C bridge types. Allocation-free claims apply only to direct, concrete, non-null view and writer calls under the measurement contract recorded by the allocation research. - -## Alternatives - -An annotated data class was rejected because it creates a second allocated representation and invites copy-based decoding. A user-authored value-class shell was rejected because KSP cannot inject members, checked constructors, or property bodies, leaving manual arithmetic and bypassable validation. A single mutable view was rejected because all readers would receive mutation capability. Throwing factories were rejected because malformed BLE input is an expected typed failure. Nullable optional properties and generic write results were rejected because they can box or add hot-path wrappers. Allocated slice views and copied arrays were rejected for v1 aggregate access. - -## Risks - -Generic checked-construction results allocate or box outside the scalar hot path. Requiring exact packet arrays prevents a value-class view over a slice of a larger receive buffer. Flattened indexed methods can expand generated names and code for deeply nested arrays. A caller can mutate the shared array after checked wrapping and violate previously validated invariants. Reference equality may surprise callers who expect structural packet equality. Compiler lowering can still introduce boxing when callers erase, generalize, or null the generated type, so benchmarks must retain positive boxing controls. - -## Migration - -No generated Kotlin interface has been released. After release, changing generated names, visibility, factory results, property carriers, optional access, write results, equality meaning, or buffer ownership is a public compatibility change. Wire-compatible source renames preserve registry identity but require generated API migration and compatibility review. Future slice or Swift adapters must be separate interfaces and cannot weaken the direct value-class contract. diff --git a/docs/adr/0005-generated-c99-header-interface.md b/docs/adr/0005-generated-c99-header-interface.md deleted file mode 100644 index 13026cf..0000000 --- a/docs/adr/0005-generated-c99-header-interface.md +++ /dev/null @@ -1,48 +0,0 @@ -# ADR-0005: Generated C99 header interface - -Status: accepted - -## Context - -C firmware must consume the same Kompact schema as Kotlin without implementation-defined C bitfields, packed structs, unaligned loads, host-endian casts, duplicated validation, or heap allocation. Firmware projects also need deterministic generated artifacts that require no additional object file or link step. - -## Decision - -Kompact generates one versioned `kompact_runtime.h` and one `_v.h` for every schema version. Schema headers are C99 header-only, include the runtime header, and include ``, ``, ``, ``, and `` when their declarations require them. - -`kompact_runtime.h` defines `KOMPACT_RUNTIME_INTERFACE_VERSION`. Each schema header checks the supported runtime interface version at preprocessing time and exposes generator-version and canonical-descriptor SHA-256 macros. Standard include guards, public symbols, typedefs, and constant names contain a sanitized stable registry name and layout version. Generation fails if sanitization creates a collision. - -Public schema and status domains use exact-width integer typedefs plus named `UINT*_C` constant macros. The generated public interface does not use native C enum types because their size and signedness are implementation-defined. Exact status meanings and numeric assignments remain owned by the validation decision. - -A generated View is a struct containing one `const uint8_t *`. A Writer is a struct containing one `uint8_t *`. Handles own no memory. The schema header provides static inline functions equivalent to the Kotlin facade: - -- `wrap` validates an existing exact-length packet and assigns an output View only on success. -- `initialize` requires exact packet size, writes the envelope, clears the body, and assigns an output Writer only on success. -- `edit` validates an existing packet and assigns an output Writer only on success. -- Writer-to-View conversion reuses the packet pointer without another validation pass. - -Direct scalar getters accept a successfully created View and return the declared exact-width C carrier. They repeat no pointer, length, envelope, or semantic checks. C cannot prevent a caller from forging a handle, so forged or manually modified handles are outside the supported contract. - -Field writes return `kompact_status_t`. They validate every fallible precondition before mutation and preserve unrelated bits. Fixed-byte and array reads validate the index, return status, and assign caller output storage only on success. Array writes validate both index and value before mutation. Optional fields generate `has_`, `_or(default_value)`, `write_`, and `clear_` functions. - -Nested and nested-array fields generate parent-prefixed flattened accessors with every required index parameter. They do not create dynamic slice handles, so View and Writer remain pointer-only. - -Schema headers expose constant macros for schema ID, layout version, body bit count, packet byte count, field offsets and widths, fixed counts, enum codes, and numeric bounds. They do not generate function-like field macros. - -`kompact_runtime.h` exposes reserved `kompact_internal_*` static inline bit helpers with documented preconditions. Generated checked schema functions are the supported public entry points. Runtime helpers load `uint8_t`, widen before shifting, and use unsigned operations. They never cast packet storage to wider pointers, perform unaligned loads, depend on host byte order, or right-shift signed values. - -Float helpers require exact-width integers, radix-2 binary32 and binary64 characteristics, and four-byte `float` and eight-byte `double` storage through C99-compatible compile-time checks. Integer bit patterns move to and from floating carriers with `memcpy`, never pointer punning. - -A failed factory, validation, indexed read, or write leaves packet bits and all caller output storage unchanged. Successful multi-byte writes are not atomic against concurrent access; firmware provides exclusive mutation and synchronization. Generated headers allocate no heap memory. - -## Alternatives - -A generated header plus `.c` implementation was rejected because it adds object compilation, linking, public ABI symbols, and small-call overhead unless link-time optimization removes it. Constants and expression macros alone were rejected because each firmware caller would recreate envelope checks, cross-byte operations, and write failure behavior; function-like macros also risk repeated argument evaluation. Packed structs and native C bitfields were rejected because their layout is implementation-defined. Checking every direct getter was rejected because a validated View already establishes packet invariants. Dynamic nested slice handles were rejected to keep the C and Kotlin aggregate interfaces aligned. - -## Risks - -Static inline schema functions can duplicate machine code across translation units. Pointer-only handles cannot enforce checked construction or retain packet length. Flattened nested accessors can create long symbol names and increase generated code size. Requiring IEEE binary32 and binary64 excludes unusual C99 targets at compile time. Visible `kompact_internal_*` helpers can be called despite being unsupported. Successful writes can be observed partially without external synchronization. - -## Migration - -No C header interface has been released. After release, changing public names, typedef widths, function signatures, status values, handle layout, runtime helper preconditions, or `KOMPACT_RUNTIME_INTERFACE_VERSION` is a compatibility change. Schema versions remain simultaneously includable because their symbols contain layout versions. Generator and descriptor fingerprints accompany released headers so build tooling can reject stale Kotlin, runtime, or firmware artifacts. diff --git a/docs/adr/0006-validation-diagnostics-and-mutation.md b/docs/adr/0006-validation-diagnostics-and-mutation.md deleted file mode 100644 index 8d8ca22..0000000 --- a/docs/adr/0006-validation-diagnostics-and-mutation.md +++ /dev/null @@ -1,119 +0,0 @@ -# ADR-0006: Validation, diagnostics, and mutation - -Status: accepted - -## Context - -Kompact accepts untrusted BLE bytes and generates public Kotlin and C interfaces from schema declarations. Invalid schemas must not leave partial generated artifacts, malformed packets must fail identically across platforms, diagnostics must not leak payload data, and rejected writes must not partially mutate caller-owned buffers. - -## Decision - -### Runtime status codes - -Kotlin and C share this one-byte public status table: - -| Value | Name | -| --- | --- | -| `0x00` | `OK` | -| `0x01` | `NULL_ARGUMENT` | -| `0x02` | `INVALID_PACKET_LENGTH` | -| `0x03` | `RESERVED_SCHEMA_ID` | -| `0x04` | `UNKNOWN_SCHEMA_ID` | -| `0x05` | `UNSUPPORTED_LAYOUT_VERSION` | -| `0x06` | `NONZERO_TAIL_BITS` | -| `0x07` | `UNKNOWN_ENUM_CODE` | -| `0x08` | `NONZERO_RESERVED_BITS` | -| `0x09` | `NONZERO_ABSENT_OPTIONAL` | -| `0x0A` | `VALUE_OUT_OF_RANGE` | -| `0x0B` | `INDEX_OUT_OF_RANGE` | -| `0x0C` | `INTERNAL_INVARIANT_FAILURE` | - -Values `0x0D` through `0xFF` are reserved. Released values are never reinterpreted or reused. - -Kotlin exposes sealed `KompactDecodeError` and `KompactWriteError` hierarchies. Every variant carries the shared status code. Failure objects may contain only redacted metadata: schema ID and version, stable field path or field ID, bit offset, expected and actual lengths, and array index where relevant. They never contain packet bytes, decoded values, attempted write values, secrets, or PII. - -C exposes matching exact-width `kompact_status_t` constants. A non-`OK` schema function leaves packet bits and caller output storage unchanged. Kotlin's type system excludes null packet references; C uses `NULL_ARGUMENT` for required null pointers. - -The library does not log runtime failures. Callers decide whether to log the stable code and redacted metadata. - -Kotlin indexed reads check the index and throw `IndexOutOfBoundsException` before packet access, matching Kotlin array behavior without adding a result wrapper to the hot path. Kotlin writes and all C indexed operations report `INDEX_OUT_OF_RANGE` without mutation. - -### Runtime validation ownership and order - -Registry dispatch and schema-specific factories return one deterministic first failure. They validate in this order: - -1. Required C pointers. -2. The minimum two-byte envelope length. -3. Reserved or unknown schema ID, followed by unsupported layout version. -4. Exact packet length for the selected schema version. -5. Final transport-tail bits. -6. Body fields in increasing bit-offset order, with nested values depth-first and arrays in ascending index order. - -A schema-specific factory follows the same order while comparing the packet envelope with its expected ID and version. Checked wrapping validates the complete body. Direct View getters repeat no pointer, length, envelope, or semantic validation. Writer operations validate their fallible value and index inputs because a successfully created Writer already establishes packet invariants. - -External mutation after wrapping is an undetectable contract violation. Callers provide exclusive mutation and synchronization; Kompact does not hash, lock, copy, or revalidate the packet on direct access. - -### Mutation - -A generated operation validates every fallible condition and every C output pointer before its first packet store. After mutation begins, it executes only non-failing stores. Output handles are assigned last. - -Rejected initialization, edits, indexed operations, and field writes leave the packet and caller output storage byte-for-byte unchanged. Rollback is unnecessary because no failure can occur after the first store. Successful multi-byte writes are not atomic against concurrent readers or writers and require caller-provided exclusive access. - -### KSP validation and diagnostics - -The processor builds and validates the complete canonical descriptor before emitting Kotlin or C output. Any error suppresses all generated output for that task. It collects independent errors across schemas, suppresses dependent cascades, and sorts diagnostics by repository-relative path, line, column, diagnostic code, and stable field path. - -All v1 schema, registry, portability, and generation invariant violations are errors. Accepted schemas are silent. Advisory warnings are not emitted. Deprecation warnings may be introduced only with a future explicit deprecation system. - -Stable public KSP diagnostic assignments are: - -| Code | Name | -| --- | --- | -| `KOMPACT-KSP-1001` | `INVALID_SCHEMA_DECLARATION` | -| `KOMPACT-KSP-1002` | `REGISTRY_NOT_FOUND` | -| `KOMPACT-KSP-1003` | `REGISTRY_IDENTITY_MISMATCH` | -| `KOMPACT-KSP-1004` | `TOMBSTONED_IDENTITY_REUSE` | -| `KOMPACT-KSP-1005` | `DUPLICATE_SCHEMA_ID_VERSION` | -| `KOMPACT-KSP-1006` | `DESCRIPTOR_FINGERPRINT_MISMATCH` | -| `KOMPACT-KSP-1007` | `REGISTRY_HISTORY_REMOVED` | -| `KOMPACT-KSP-1008` | `UNSUPPORTED_REGISTRY_FORMAT` | -| `KOMPACT-KSP-1009` | `COMPATIBILITY_BASELINE_REQUIRED` | -| `KOMPACT-KSP-1010` | `ILLEGAL_LIFECYCLE_TRANSITION` | -| `KOMPACT-KSP-1011` | `NONSEQUENTIAL_LAYOUT_VERSION` | -| `KOMPACT-KSP-1012` | `SUPPORTED_DECODER_MISSING` | -| `KOMPACT-KSP-1101` | `UNSUPPORTED_FIELD_TYPE` | -| `KOMPACT-KSP-1102` | `INVALID_BIT_OFFSET` | -| `KOMPACT-KSP-1103` | `INVALID_BIT_WIDTH` | -| `KOMPACT-KSP-1104` | `FIELD_TYPE_WIDTH_MISMATCH` | -| `KOMPACT-KSP-1105` | `FIELD_OVERLAP` | -| `KOMPACT-KSP-1106` | `IMPLICIT_LAYOUT_GAP` | -| `KOMPACT-KSP-1107` | `RESERVED_RANGE_CONFLICT` | -| `KOMPACT-KSP-1108` | `DUPLICATE_ENUM_CODE` | -| `KOMPACT-KSP-1109` | `ENUM_CODE_OUT_OF_RANGE` | -| `KOMPACT-KSP-1201` | `INVALID_ARRAY_COUNT` | -| `KOMPACT-KSP-1202` | `NESTED_OPTIONAL` | -| `KOMPACT-KSP-1203` | `UNKNOWN_NESTED_SCHEMA` | -| `KOMPACT-KSP-1204` | `UNSUPPORTED_NESTED_VERSION` | -| `KOMPACT-KSP-1205` | `SCHEMA_NESTING_CYCLE` | -| `KOMPACT-KSP-1206` | `SIZE_ARITHMETIC_OVERFLOW` | -| `KOMPACT-KSP-1207` | `PACKET_SIZE_LIMIT_EXCEEDED` | -| `KOMPACT-KSP-1301` | `GENERATED_KOTLIN_NAME_COLLISION` | -| `KOMPACT-KSP-1302` | `GENERATED_C_SYMBOL_COLLISION` | -| `KOMPACT-KSP-1303` | `GENERATED_OUTPUT_PATH_COLLISION` | -| `KOMPACT-KSP-1304` | `GENERATED_VISIBILITY_CONFLICT` | - -Each diagnostic exposes its code, `ERROR` severity, source symbol and location, stable schema and field metadata, offending schema metadata, and expected constraint. Code, severity, and structured payload shape are compatibility contracts. Human-readable prose may improve without changing the code. Expected validation failures use `KSPLogger.error(message, symbol)` rather than processor exceptions. - -Unassigned numbers within each family remain reserved: `1001..1099` for declarations and registry identity, `1101..1199` for fields and scalar layout, `1201..1299` for aggregates and size, and `1301..1399` for generated output and visibility. Assigned numbers are never reused. - -## Alternatives - -Failing on the first schema error was rejected because it forces one fix per build and makes traversal order visible. Emitting only valid schemas was rejected because it can package partial Kotlin, C, and registry artifacts. Runtime lists of all failures were rejected because they allocate, scan beyond the first invalid structure, and diverge from C's fixed status interface. Platform-specific status tables and string-only failures were rejected because cross-language conformance could not compare them. Rollback after partial writes was rejected because all fallible checks can run before mutation. Hashing or copying live buffers was rejected because it defeats direct zero-copy access without solving concurrent races. - -## Risks - -Returning one runtime failure hides later problems until the first is corrected. Public numeric codes and diagnostic payloads constrain future changes. Rich Kotlin failure objects allocate on failure paths. `INTERNAL_INVARIANT_FAILURE` cannot explain implementation details without risking sensitive diagnostics. C callers can forge trusted handles, and external aliases can invalidate a packet after checked wrapping. Kotlin and C differ for invalid read indices, so conformance tests must assert the documented exception-versus-status distinction. - -## Migration - -No error contract has been released. After release, removing or renumbering a status or KSP diagnostic, changing severity, or incompatibly changing structured metadata is a public compatibility break. New codes use previously unassigned values and require corresponding Kotlin, C, documentation, and conformance-vector updates. Implementations must migrate to validate-then-emit and validate-before-mutate before any generated artifact is published. diff --git a/docs/adr/0007-conformance-vectors-and-compatibility-gates.md b/docs/adr/0007-conformance-vectors-and-compatibility-gates.md deleted file mode 100644 index fd6fdc0..0000000 --- a/docs/adr/0007-conformance-vectors-and-compatibility-gates.md +++ /dev/null @@ -1,110 +0,0 @@ -# ADR-0007: Conformance vectors and compatibility gates - -Status: accepted - -## Context - -Kompact will generate Kotlin and C implementations of one bit-level protocol. Tests derived from either production encoder can reproduce the same defect in expected bytes. Releases need independent, reviewable packet vectors and target-specific execution proving identical behavior on Android/JVM, Kotlin/Native iOS, GCC, Clang, and a big-endian C target. Wire compatibility and generated public interfaces also need retained machine-readable baselines. - -## Decision - -### Authoritative vector corpus - -Each released schema version has one append-only, synthetic, secret-free JSON manifest at: - -```text -conformance///v.json -``` - -A manifest records its format version, protocol namespace, stable schema name, schema ID, layout version, canonical descriptor SHA-256, packet byte size, and named test cases. - -Packet bytes use lowercase, even-length hexadecimal without separators. Signed and unsigned integers use decimal strings so JSON number precision cannot change a value. Floating values record their exact raw hexadecimal bits plus a semantic label. Byte sequences use hexadecimal. Enum values record stable code and name. Arrays, optionals, and nested values follow the schema structure. - -Every case records logical values, exact packet bytes, expected shared status code from ADR-0006, and only the permitted redacted error metadata. Expected bytes are reviewed protocol data. Production Kotlin and C encoders never generate or update expected bytes. - -Released cases and expected results are immutable and are never removed. New cases may append. Retired schema versions retain their manifests for as long as any decoder remains supported. - -### Required valid cases - -Every supported schema version covers: - -- canonical all-zero values where the schema permits them; -- minimum and maximum values for every scalar width; -- negative signed values and exact sign-extension boundaries; -- aligned and unaligned offsets crossing every relevant byte boundary; -- every declared enum code and representative gaps around declared codes; -- finite floats, positive and negative zero, infinities, canonical written NaNs, and accepted noncanonical NaN reads; -- each optional field present and absent; -- first and last elements of every fixed array; -- representative nested values at every nesting level; -- every possible final transport-tail-bit count exercised by the canonical schema suite. - -### Required invalid and mutation cases - -Malformed cases change one invariant at a time and preserve every earlier validation stage so ADR-0006 precedence is testable. They cover: - -- every packet length shorter than expected, including zero and one byte; -- at least one extra byte; -- reserved schema ID zero, an unknown schema ID, and every unsupported version boundary; -- every transport-tail and reserved bit set independently; -- every undeclared enum code that fits the field when the code space is tractable, otherwise every gap boundary plus fixed-seed property coverage; -- nonzero value-slot bits under each absent optional; -- first invalid index below and above every array range where the language can express it; -- values immediately outside each writable scalar range; -- nested failures with their expected stable field path, bit offset, and array index. - -Rejected write and initialization cases snapshot the packet and caller output storage before the operation and require byte-for-byte equality afterward. - -### Cross-language execution - -Every execution target reads all relevant manifests, decodes packet hex, compares logical values, encodes valid logical values, and compares the resulting packet byte-for-byte with the reviewed hex. - -A standalone C harness also reads packet files emitted by Kotlin, and a Kotlin integration harness reads packet files emitted by C. Both compare those files with the reviewed manifest so neither implementation becomes the expected-byte authority. - -Normal merge gates execute: - -- common correctness tests on JVM; -- Android instrumented conformance on a pinned emulator image; -- `iosSimulatorArm64` conformance on a pinned macOS and Xcode image; -- `iosArm64` compile and link on macOS; -- strict C99 consumer compilation and vector execution under GCC and Clang; -- C vector execution using a pinned big-endian QEMU target; -- ASan and UBSan C execution where the selected compiler and target support them. - -Physical Android and iPhone conformance smoke runs are release gates and share the dedicated performance-device jobs. Each selected production firmware compiler becomes a release gate when a firmware toolchain is adopted. - -C builds use warnings as errors with strict C99 and conversion diagnostics. Generated headers must compile in more than one translation unit to expose linkage mistakes. - -### Determinism and generated artifacts - -A compact canonical schema suite checks in reviewable generated Kotlin, C header, canonical descriptor, registry, and diagnostic snapshots. Consumer-generated files outside this suite remain build outputs and are not checked in. - -All schema generation must produce byte-identical outputs and hashes across repeated, parallel, clean, and relocated builds. The test matrix covers schema addition, change, rename, removal, tombstone retention, and stale-output cleanup. - -Fixed-seed property tests supplement reviewed vectors on every merge. They cover round trips, offset and width combinations, canonicalization, deterministic failure precedence, and rejected-operation immutability. Longer randomized Kotlin runs and sanitizer-backed C fuzzing run on a schedule. Randomized evidence never replaces reviewed vectors. - -### Compatibility gates - -Kotlin public ABI tracking covers the runtime, annotations, Gradle plugin, and canonical generated interfaces. Compatibility checks compare protocol registries and canonical descriptors for ID or version reuse, fingerprint drift, tombstone deletion, semantic change without a new version, and removal of a supported decoder. - -Retained old and new C consumer fixtures compile against current versioned schema and runtime headers. Header-only C has a source-compatibility contract rather than a linked binary ABI. - -A released vector change requires a new layout version. An incompatible Kotlin or C public-interface change requires the documented SemVer impact, migration instructions, updated compatibility artifacts, and retained old-version proof where support continues. - -### Evidence retention - -CI retains manifests, canonical snapshots, hashes, test reports, failing case names, C compiler commands, sanitizer and fuzzer artifacts, emulator and runtime versions, compiler options, and target build metadata. Failures identify synthetic cases and ADR-0006 redacted metadata only; they never include packet values from production traffic. - -A candidate may claim Android, iOS, and C conformance only when all required target gates pass on that exact commit. A skipped, unavailable, or host-incompatible target remains explicitly unverified. - -## Alternatives - -Generated binary fixtures were rejected because reviewers cannot inspect field meaning and expected bytes easily. Kotlin-authored expectations were rejected because they privilege one production implementation. Decode-only vectors were rejected because writer divergence remains hidden. Random or fuzz input as primary proof was rejected because release evidence and failure precedence become nondeterministic. Snapshotting every generated consumer file was rejected because it duplicates build output and creates noisy reviews. JVM-only execution and host-GCC-only C tests were rejected because they do not exercise ART, Kotlin/Native, Clang, or endian assumptions. Physical devices on every merge were rejected because the default suite must remain deterministic and independent of retained hardware. - -## Risks - -The required matrix has meaningful CI cost and needs macOS, Android emulator, and big-endian emulation capacity. Human-reviewed expected bytes can still contain mistakes, so bidirectional implementations and property tests remain necessary. Strict append-only vectors and compatibility baselines increase repository size. Emulators do not prove physical performance or every device behavior. Sanitizer and fuzz results vary by toolchain and need pinned environments. Supporting old versions increases generated code size. - -## Migration - -No conformance corpus has been released. Before the first runtime release, implementation must add the manifest schema, canonical fixtures, target harnesses, ABI baselines, registry comparison, deterministic generation checks, and CI jobs described here. Later vector corrections that change released expected bytes create a new layout version; old manifests remain intact. New target or firmware compiler support adds gates without weakening existing ones. diff --git a/docs/adr/0008-performance-budgets-and-benchmarks.md b/docs/adr/0008-performance-budgets-and-benchmarks.md deleted file mode 100644 index 83d8f1b..0000000 --- a/docs/adr/0008-performance-budgets-and-benchmarks.md +++ /dev/null @@ -1,102 +0,0 @@ -# ADR-0008: Performance budgets and benchmark matrix - -Status: accepted - -## Context - -Kompact's payload-size, allocation, and latency claims need numeric thresholds tied to representative BLE workloads and controlled target environments. Universal nanosecond limits would describe one processor rather than the library, while measurements without thresholds cannot block regressions. The selected Kotlin interface also permits bounded allocation during checked construction but requires allocation-free direct access afterward. - -## Decision - -### Retained workloads - -The benchmark suite retains three exact, reviewed canonical schema descriptors and deterministic value corpora: - -- Small: the four-byte VehicleTelemetry packet with a 4-bit enum, cross-byte 10-bit integer, Boolean, and reserved bit. -- Medium: a fixed 32-byte packet covering every scalar carrier, aligned and unaligned fields, float32 and float64, enum gaps, optional presence and absence, fixed bytes, arrays, and one nested schema. -- Large: a fixed 244-byte packet dominated by nested arrays, unaligned byte sequences, optionals, and whole-body validation. - -These sizes are benchmark workloads, not global protocol limits. Exact descriptors and values are checked in and may change only through baseline-change review. - -### Operation boundaries - -Separate benchmarks measure: - -- low-level aligned and unaligned bit reads and writes; -- generated direct scalar reads; -- fixed-byte and array indexed reads; -- optional `hasX` and `xOr(defaultValue)` access; -- valid generated scalar and indexed writes; -- Writer-to-View conversion; -- complete valid `wrap`, `edit`, and `initialize` operations; -- complete packet read and write throughput. - -Packet allocation, initialization, expected-value construction, and checksum validation remain outside the timed operation. Each measured operation contributes to a primitive checksum so the optimizer cannot remove it. Inputs cycle deterministically through minimum, maximum, signed, float-special, aligned, unaligned, first, middle, last-index, optional, and nested cases. - -Each generated benchmark has a reviewed hand-written reference with the same operation boundary, validation behavior, carrier type, compiler options, and input sequence. The reference uses direct bit code and no generated call path. - -### Numeric latency budgets - -On each reference runtime and device: - -- Generated direct scalar and indexed reads, optional access, valid writes, and Writer-to-View conversion have median time no greater than `1.10` times the equivalent hand-written reference. -- Valid `wrap`, `edit`, and `initialize` have median time no greater than `1.25` times equivalent hand-written validation. -- Any candidate operation more than `1.10` times its previous committed generated baseline blocks merge, even when its hand-written-reference ratio still passes. - -### Allocation budgets - -Direct scalar and indexed reads, optional access, valid writes, and Writer-to-View conversion allocate exactly zero managed objects and zero managed bytes per operation on HotSpot and ART. On iOS, the same operations produce no differential allocation and no relevant allocation stack in the measured Instruments interval. - -Each measurement artifact contains generic, interface, nullable, and intentional-allocation positive controls. A zero-allocation result is invalid if the same run does not detect its controls. - -Successful `wrap`, `edit`, and `initialize` allocate at most two managed objects. Failure allocations are reported but not part of the hot-path ceiling. Generated C headers and operations perform no heap allocation. - -### Code-size budgets - -The release runtime contributes at most 16 KiB of code and read-only constant data per target after subtracting an empty harness. Measurement uses the target's stable representation: - -- JVM classfile method bytecode and constant data for JVM publication; -- DEX code and constant data for Android publication; -- linked text and read-only data for Kotlin/Native and C artifacts. - -Code attributable to each canonical generated schema is no greater than `1.25` times its reviewed hand-written equivalent and no greater than `1.10` times its previous committed baseline. Each generated C schema/version header is at most 64 KiB in source bytes. - -### Encoded-size budget - -A packet contains exactly the 16-bit envelope plus declared field and reserved bits. Byte transport adds only the ceiling to the next byte, whose unused tail bits are zero. Packets contain no hidden tags, offsets, lengths, alignment, or generator metadata. - -### Reference environments - -Repository metadata pins one dedicated physical Android device and one dedicated iPhone as blocking reference environments. It records device model, CPU, memory, OS build, power source and battery state, thermal state, clock-lock or sustained-performance state where available, toolchain versions, compiler options, GC and allocator options, and benchmark-harness version. Additional devices report nonblocking results. - -Controlled HotSpot/JMH measurements are diagnostic and blocking for the JVM publication. AndroidX Microbenchmark measures ART on the physical Android device. Release `iosArm64` loops measure time on the iPhone without Instruments; a separate run captures allocations with Instruments. Simulator, emulator, timing, and allocation runs are never compared as interchangeable environments. - -### Statistical gate - -A budget session runs seven randomized or alternating baseline/candidate process pairs and gates on the median paired ratio. A ratio above its ceiling triggers one complete repeat in reversed order. Two failing sessions block the change. - -Baseline and candidate use the same worker, device, power and thermal state, toolchain, build flags, workload, units, and profiler-attachment state. Thermal throttling, environment drift, missing positive controls, invalid checksums, tool failure, or incomparable metadata makes the session unverified. - -### CI and release enforcement - -Shared CI runs short discovery, execution, parameter, checksum, and report-generation smoke profiles. Any change to a budgeted runtime, generated interface, generator, compiler option, dependency, or canonical workload requires successful controlled JVM, Android, and iPhone jobs on the candidate commit before merge. Scheduled unchanged-baseline sessions detect worker drift. - -Physical benchmark jobs build artifacts before the timed session and retain raw per-run data. Timing and allocation profiling run separately. Performance results from shared hosted runners are informational only. - -Reference implementations, raw baseline data, workload descriptors, and environment identities are versioned. A baseline or reference change requires a separate pull request with rationale, previous and replacement raw evidence, environment identity, and approval. A feature change cannot reset its own baseline. - -### Evidence retention - -Each retained result includes repository commit, generated-source hash, raw Android and JMH-compatible JSON, Instruments trace or export, linker maps or binary-section reports, JVM classfile or Android DEX size reports, compiler arguments, checksums, run order, sample values, units, thermal and clock state, tool versions, comparison summary, and every disclosed limitation. - -## Alternatives - -Universal absolute nanosecond limits were rejected because Android and iPhone processors are not comparable. Measurements without thresholds were rejected because they cannot enforce the product claim. Exact `1.00` parity was rejected because measurement noise would fail equivalent code. Zero allocation during generic checked construction was rejected because the selected result and value-class interface may allocate or box before the hot path. One four-byte workload was rejected because it does not exercise validation scaling, nesting, arrays, or code-size growth. Hosted-runner timing gates were rejected because infrastructure variation dominates small bit-operation measurements. Feature-owned baseline regeneration was rejected because it normalizes regressions. - -## Risks - -Relative budgets can pass when both generated and hand-written implementations are slow, so reference code requires review and retained absolute measurements. Seven paired sessions and two physical devices add merge latency and infrastructure cost. The 16 KiB runtime and 64 KiB header ceilings may require revision after measured implementation evidence; changing them requires explicit baseline governance. Instruments does not expose the same normalized allocation metric as HotSpot or ART. Compiler upgrades can change code size and timing independently of source and therefore require a baseline-change review. - -## Migration - -No performance baseline exists. Before merging the first budgeted implementation, the project must commit canonical descriptors, hand-written references, benchmark harnesses, environment metadata, smoke profiles, raw baseline evidence, and comparison tooling. Later hardware or toolchain replacement establishes a separately reviewed baseline without deleting old data. Any approved budget change records rationale, measurement impact, and migration in this ADR and its benchmark metadata. diff --git a/docs/adr/0009-gradle-modules-generation-and-publication.md b/docs/adr/0009-gradle-modules-generation-and-publication.md deleted file mode 100644 index 5ef3de7..0000000 --- a/docs/adr/0009-gradle-modules-generation-and-publication.md +++ /dev/null @@ -1,129 +0,0 @@ -# ADR-0009: Gradle modules, generation, and publication - -Status: accepted - -## Context - -Kompact must process each common schema once, generate Kotlin and C from the same descriptor, make generated declarations visible to every KMP target and IDE import, restore outputs safely from the Gradle build cache, and publish complete KMP and firmware artifacts. Standard target-specific KSP tasks repeat common processing, while raw generated-directory paths create implicit task dependencies and stale-output risks. - -## Decision - -### Modules and coordinates - -Kompact uses four focused Gradle modules: - -- `kompact-runtime` is a public KMP library published as `ch.trancee.kompact:kompact-runtime`. -- `kompact-annotations` is a public KMP library published as `ch.trancee.kompact:kompact-annotations`. -- `kompact-processor` is an internal JVM module containing symbol analysis, descriptor construction, validation, and Kotlin/C generation. -- `kompact-gradle-plugin` is a public JVM Gradle plugin implementation published as `ch.trancee.kompact:kompact-gradle-plugin` with plugin ID `ch.trancee.kompact`. - -The processor ships as an undocumented implementation dependency of the Gradle plugin. It remains separately testable but is not a supported direct integration interface. Processor and KSP2 types never appear in runtime, annotation, generated, or consumer public interfaces. - -Runtime and annotation modules publish Kotlin Multiplatform root metadata plus JVM, explicit Android, `iosArm64`, and `iosSimulatorArm64` variants. Schema annotations use source retention where KSP processing permits and add no runtime dependency. - -Conformance fixtures, Gradle TestKit fixtures, publication consumers, and benchmarks remain internal test source sets or internal projects. They do not enter production publications. - -### Plugin interface and ownership - -The plugin requires an existing Kotlin Multiplatform project and `commonMain`. It does not apply Kotlin, Android, target, Maven Publish, repository, or dependency plugins and does not declare consumer dependencies. - -Consumers explicitly declare `kompact-runtime` and `kompact-annotations`. The plugin validates that its version, generator version, annotation version, runtime version, and runtime interface version align exactly. A mismatch fails before generation. - -One plugin application owns one protocol namespace, one registry, one packet limit, one descriptor set, one generation task, and one C-header archive. A project needing another protocol namespace uses another schema-owning KMP module. - -The typed `kompact` extension exposes: - -- required protocol namespace; -- required maximum packet byte size; -- registry file, defaulting to project-root `kompact-registry.json`; -- C-header generation and publication settings, including the default `c-headers` classifier. -- optional `compatibilityBaseline` registry file; -- Boolean `requireCompatibilityBaseline`, defaulting to false for local checks and enabled by CI and release. - -Configured namespace and packet limit must equal their registry values. Required-baseline mode fails when the baseline file is absent. Compatibility checks consume only the supplied local file and never perform Git, network, or credential operations. Generated directories and task implementation details are not configurable public interface. - -### Generation task - -`generateKompactSchemas` is a cacheable task that submits KSP2 common processing to a process-isolated Gradle worker exactly once. The worker classpath contains the internal processor and KSP2 embeddable implementation without placing them on consumer runtime or compilation classpaths. - -Declared normalized inputs include: - -- common schema source roots; -- protocol registry; -- schema compile classpath; -- processor and KSP classpaths; -- plugin, generator, annotation, runtime, and runtime-interface versions; -- namespace, packet limit, language/API versions, and generator options. - -Complete output directories contain generated common Kotlin, C headers, canonical descriptors, and machine-readable reports. KSP caches are Gradle local state and are never published or restored as output artifacts. - -Gradle `InputChanges` provide added, modified, and removed schema sources to KSP2. Per-schema output dependencies remain isolating where possible; registries and aggregate indexes are aggregating. Whole output directories remain declared for correct clean and build-cache restoration. - -Generation occurs in a task-owned staging workspace. Only complete successful output replaces published output directories. Any validation or generation failure removes published outputs and fails the task, leaving no partial Kotlin, C, descriptor, registry, or report files. - -Plugin-owned paths are rooted under: - -```text -build/generated/kompact// -build/kompact// -``` - -The first root contains publishable generated output. The second contains staging and local state. Namespace path segments use the same deterministic sanitization and collision validation as generated public symbols. - -### Stable task interface and wiring - -The plugin exposes three stable tasks: - -- `generateKompactSchemas` creates all generated outputs from one validated descriptor pass. -- `checkKompactSchemas` runs generation and schema, registry, descriptor, proposed-registry, and optional historical-baseline compatibility checks and participates in project `check`. -- `packageKompactCHeaders` creates a deterministic ZIP from generated headers. - -`commonMain` receives the generated Kotlin directory through the generation task's output provider. This provider carries task dependencies into every target compile and the IDE model. Source archive tasks consume the same provider and include generated public declarations. - -`packageKompactCHeaders` consumes the generated-header provider. The plugin exposes its deterministic ZIP through a consumable `kompactCHeaders` variant. When the project already applies Maven Publish and explicitly enables C publication, the plugin attaches the same ZIP to the KMP root publication with classifier `c-headers`. The plugin never applies Maven Publish itself. - -Compilation, checking, source archives, C packaging, and publication consume task providers rather than raw build-directory strings. Generated consumer files remain build outputs and are never checked into source control. - -### Publication - -One macOS release job publishes every coordinate once: - -- runtime and annotation root metadata, JVM, explicit Android, `iosArm64`, and `iosSimulatorArm64` artifacts; -- source and documentation artifacts; -- Gradle plugin marker and implementation artifacts; -- the internal processor implementation dependency; -- each explicitly enabled C-header classifier. - -Publication first targets a disposable Maven repository. Real JVM, Android, iOS, Gradle-plugin, and C-header consumers resolve and exercise those artifacts before external publication. Android publication is configured explicitly. One host owns all root and target publications to prevent duplicate coordinates. - -### Required gates - -Gradle TestKit fixtures prove: - -- clean generation and a second `UP-TO-DATE` run; -- added, modified, renamed, and removed schema incrementality; -- stale-output cleanup; -- parallel task execution; -- configuration-cache reuse; -- Gradle isolated-project compatibility; -- relocated `FROM-CACHE` restoration; -- deterministic repeated and relocated outputs; -- validation failures with stable diagnostics and no published output. - -Target fixtures compile generated code for JVM, Android, `iosArm64`, and `iosSimulatorArm64`. macOS runs iOS simulator tests and links device artifacts. Gradle IDE import resolves generated `commonMain` declarations without manual path configuration. - -Publication fixtures inspect root and target metadata, generated source archives, plugin dependency isolation, C ZIP contents, classifier and variant resolution, checksums, and reproducibility. They resolve real disposable-repository consumers for every supported target and artifact. - -Missing KMP or `commonMain`, version mismatch, namespace mismatch, packet-limit mismatch, missing registry, missing required compatibility baseline, registry history removal, output collision, and unsupported target wiring fail closed with stable diagnostics. - -## Alternatives - -Combining annotations with runtime was rejected because schema authoring and runtime release cycles would be coupled. Embedding processor code directly in the plugin was rejected because symbol processing needs an independently testable owner. Publishing the processor as a supported public interface was rejected because it creates a second path that bypasses Gradle ownership. Multiple namespaces per module were rejected because source selection, output ownership, and publication become ambiguous. Configurable output directories and checked-in generated code were rejected because they expand cache and cleanup behavior and duplicate the schema source of truth. In-daemon KSP execution was rejected because processor classloaders and memory would share the Gradle daemon. Multi-host publication was rejected because root and target coordinates can race or diverge. - -## Risks - -Four modules and an internal published processor dependency increase release plumbing. Process-isolated workers add startup time. Exact version alignment requires coordinated releases of runtime, annotations, processor, and plugin. One namespace per module may create more modules in applications serving several BLE protocols. Removing outputs on validation failure can temporarily remove IDE symbols until the schema is corrected. Attaching consumer-generated C headers to KMP publications requires careful publication ordering and reproducibility checks. - -## Migration - -No Gradle or Maven interface has been released. Implementation must introduce all four modules, the typed extension, stable tasks, provider-based wiring, staged outputs, local-state caches, variants, publications, and TestKit fixtures together. After release, plugin ID, Maven coordinates, extension properties, task names, consumable variant, classifier, output ownership, and version-alignment rules are public compatibility contracts. Later module consolidation or direct processor support requires a documented migration and SemVer impact. diff --git a/docs/adr/0010-canonical-descriptors-and-registry-compatibility.md b/docs/adr/0010-canonical-descriptors-and-registry-compatibility.md deleted file mode 100644 index 3993ce6..0000000 --- a/docs/adr/0010-canonical-descriptors-and-registry-compatibility.md +++ /dev/null @@ -1,136 +0,0 @@ -# ADR-0010: Canonical descriptors and registry compatibility - -Status: accepted - -## Context - -Kotlin and C generation need one schema representation whose fingerprint changes for every wire or semantic change but remains stable across source-only Kotlin renames and generator upgrades. The checked-in registry must preserve identity history, while compatibility checks need an external historical baseline because a current file cannot prove that its own tombstones were deleted. Descriptor and registry bytes must remain deterministic across machines and relocated builds. - -## Decision - -### Canonical descriptor - -Each schema version has one JSON descriptor with: - -- `format` equal to `kompact-schema`; -- `formatVersion` equal to `1`; -- protocol namespace; -- stable schema name, schema ID, layout version, and body bit size; -- fields; -- reserved ranges. - -Each field contains a stable name, bit offset, bit width, recursively tagged logical type, and structured semantics. Reserved ranges contain stable name, bit offset, and bit width. - -The logical type is a closed tagged union covering Boolean, signed integer, unsigned integer, enum, IEEE binary32, IEEE binary64, fixed bytes, fixed array, optional, and same-namespace nested schema/version. Arrays contain positive fixed counts and a nested element type. Nested descriptors identify stable schema name, schema ID, and exact layout version. Cross-namespace nesting is invalid. - -Structured field semantics include: - -- required stable `semanticType`; -- optional case-sensitive unit; -- optional exact rational scale and offset, reduced to coprime numerator and positive denominator; -- optional numeric minimum and maximum; -- stable enum entry names and explicit codes; -- optionality, fixed counts, and nested identity/version as part of the logical type. - -Descriptions, comments, Kotlin identifiers, Kotlin carrier types, lifecycle status, generator version, and generated symbol names are excluded. Kotlin ABI and generated-artifact compatibility checks own those concerns. - -Optional JSON properties are omitted rather than encoded as null. Signed and unsigned 64-bit values, rational numerators and denominators, and numeric domain boundaries use canonical decimal strings matching `-?(0|[1-9][0-9]*)`. They contain no leading plus sign or redundant leading zero. Floating bit patterns use fixed-width lowercase hexadecimal strings. Duplicate JSON object keys are invalid. - -Protocol namespace, schema, field, reserved-range, enum-entry, and semantic-type names match `[a-z][a-z0-9_]*`. They are independent of Kotlin identifiers. A Kotlin-only rename keeps the stable names and descriptor fingerprint; Kotlin ABI checks report its generated interface impact separately. - -Before canonical serialization: - -- fields and reserved ranges sort by bit offset, then stable name; -- enum entries sort by numeric code, then stable name; -- schemas sort by schema ID; -- versions sort ascending; -- duplicate semantic sort keys fail validation. - -The descriptor is serialized as UTF-8 using the JSON Canonicalization Scheme in RFC 8785. SHA-256 over those exact bytes is stored as a lowercase, 64-character `descriptorSha256`. Descriptor format version participates in the hash; generator version does not. SHA-256 detects drift and is not an authentication mechanism. - -KSP builds and validates one immutable descriptor model. That same instance feeds Kotlin generation, C generation, canonical serialization, hashing, and reports. Backends do not derive separate models or reparse emitted JSON. Tests independently parse emitted JSON and require an equal model and identical canonical bytes. - -### Registry - -The checked-in `kompact-registry.json` uses two-space-indented UTF-8 JSON, LF endings, a terminal newline, schemas sorted by ID, and versions ascending. Its top level contains: - -- `$schema` pointing to the versioned registry JSON Schema; -- `formatVersion` equal to `1`; -- protocol namespace; -- maximum packet byte size; -- schema entries. - -Each schema entry contains stable name, schema ID, optional `supersedes` stable schema identity, and versions. Each version contains layout version, lifecycle status, body bit size, and descriptor SHA-256. - -Lifecycle status is one of: - -- `active`: source and descriptor are present; generation emits encoder and decoder. -- `decode-only`: retained source and descriptor are present; generation emits only a decoder. -- `retired`: source and generated code may be removed, but registry entry, fingerprint, conformance vectors, and compatibility fixtures remain permanently. - -A version moves only `active` to `decode-only` to `retired`. Retired is terminal. At most one active version exists for a schema ID. New versions use exactly the next numeric value. After version 15, evolution uses a new schema ID, layout version zero, and a new stable schema name such as `vehicle_telemetry_gen2`; the new entry may identify the previous stable schema through `supersedes`. The previous ID and name remain in history. Entries, versions, tombstones, and assigned numeric identities are never deleted or reused. - -### Reviewed registry updates - -Developers explicitly add stable names, schema IDs, layout versions, and lifecycle states. Generation writes a complete proposed registry to `reports/kompact-registry.proposed.json` under the plugin-owned output root. `checkKompactSchemas` compares it with the checked-in registry, prints a deterministic structured diff, and fails until the source registry matches the reviewed proposal. - -No generation or check task mutates source files and no tool allocates an ID, version, stable name, or lifecycle transition implicitly. - -Generated Kotlin, C headers, descriptors, conformance manifests, reports, and registry entries expose the same descriptor SHA-256. A mismatch is a build failure. - -### Baseline comparison - -The `kompact` extension adds optional `compatibilityBaseline` and Boolean `requireCompatibilityBaseline` properties. Local checks may omit a baseline and then prove current schema and registry internal consistency only. CI and release set required mode; a missing baseline fails. - -Pull-request CI supplies the merge-base registry as a local input file. Release CI supplies the previous published registry artifact. Compatibility tasks perform no Git operation, network request, or credential lookup. - -Comparison rejects: - -- namespace change; -- registry history, tombstone, schema, or version removal; -- schema ID, stable name, or retired identity reuse; -- descriptor fingerprint or body-size drift under an existing ID/version; -- a new version that is not exactly the next value; -- version rollover without a new ID and new stable name; -- lifecycle reversal or more than one active version per schema ID; -- missing source/descriptor for active or decode-only status; -- missing encoder for active status or missing decoder for active/decode-only status; -- removal of a previously supported decoder without a legal lifecycle transition; -- a lower packet limit that excludes an active or decode-only version. - -A new fingerprint is accepted only under the next legal version or a new legal schema ID and stable name. Raising the packet limit is compatible. Lowering it is compatible only when every active and decode-only version still fits. - -### Diagnostics and schemas - -ADR-0006 gains these stable identity-family diagnostics: - -| Code | Name | -| --- | --- | -| `KOMPACT-KSP-1007` | `REGISTRY_HISTORY_REMOVED` | -| `KOMPACT-KSP-1008` | `UNSUPPORTED_REGISTRY_FORMAT` | -| `KOMPACT-KSP-1009` | `COMPATIBILITY_BASELINE_REQUIRED` | -| `KOMPACT-KSP-1010` | `ILLEGAL_LIFECYCLE_TRANSITION` | -| `KOMPACT-KSP-1011` | `NONSEQUENTIAL_LAYOUT_VERSION` | -| `KOMPACT-KSP-1012` | `SUPPORTED_DECODER_MISSING` | - -The Gradle plugin publishes versioned JSON Schemas for descriptor, registry, and conformance-manifest validation with its documentation artifacts. Unknown registry or descriptor format versions fail closed. JSON Schema validation runs before canonicalization and semantic validation. - -### Required gates - -Tests cover RFC 8785 and SHA-256 known-answer vectors, every descriptor type, rational and numeric-string normalization, duplicate keys, semantic ordering, stable-name validation, same-namespace nesting, and Kotlin-only renames. - -Compatibility fixtures cover every legal and illegal lifecycle transition, new version, version rollover, new schema ID, history removal, identity reuse, fingerprint drift, semantic and wire mutation, source removal, decoder removal, packet-limit change, missing and malformed baseline, unsupported format, and proposed-registry diff. - -Integration tests require Kotlin, C, descriptor, registry, manifest, report, and header fingerprints to agree. Repeated, parallel, clean, incremental, and relocated-cache builds produce identical descriptor bytes, fingerprints, proposals, and diagnostics. - -## Alternatives - -Custom canonical JSON and a binary descriptor were rejected because Kompact would own another normalization format and make review harder. Source declaration order was rejected because harmless reordering would change fingerprints. Kotlin identifiers were rejected as stable identity because source-only renames should not change wire meaning. Hashing descriptions or generator versions was rejected because typo fixes and tool upgrades are not layout versions. Bit-only fingerprints were rejected because unit, range, scale, enum meaning, and nested semantic changes can break consumers without moving bits. Current-file-only checks were rejected because deleted history becomes invisible. Git, Maven, or network lookup inside the task was rejected because compatibility must remain offline and reproducible. Automatic registry mutation was rejected because it can approve identity and lifecycle changes without review. - -## Risks - -RFC 8785 and JSON Schema implementations become build-tool dependencies and require retained known-answer tests. Structured semantics increase annotation verbosity and still cannot encode every domain meaning. Excluding Kotlin carriers from the fingerprint means ABI checks are required to catch carrier changes. Human review can approve an incorrect proposed registry. Baseline provisioning adds CI plumbing. Terminal retirement prevents reactivating an old decoder under the same lifecycle record. SHA-256 detects accidental drift but cannot establish registry provenance or payload integrity. - -## Migration - -No descriptor or registry format has been released. Implementation must add versioned JSON Schemas, canonical model and serializer, RFC 8785 and SHA-256 tests, stable-name annotations, proposed-registry output, offline baseline inputs, compatibility comparison, and diagnostics `1007` through `1012` before publishing schemas. Later descriptor or registry format changes require a new format version and migration tooling; they cannot rewrite existing descriptor fingerprints or registry history. diff --git a/docs/agents/domain.md b/docs/agents/domain.md deleted file mode 100644 index 066da3b..0000000 --- a/docs/agents/domain.md +++ /dev/null @@ -1,51 +0,0 @@ -# Domain Docs - -How the engineering skills should consume this repo's domain documentation when exploring the codebase. - -## Before exploring, read these - -- **`CONTEXT.md`** at the repo root, or -- **`CONTEXT-MAP.md`** at the repo root if it exists: it points at one `CONTEXT.md` per context. Read each one relevant to the topic. -- **`docs/adr/`**: read ADRs that touch the area you're about to work in. In multi-context repos, also check `src//docs/adr/` for context-scoped decisions. - -If any of these files don't exist, **proceed silently**. Don't flag their absence; don't suggest creating them upfront. The `/domain-modeling` skill (reached via `/grill-with-docs` and `/improve-codebase-architecture`) creates them lazily when terms or decisions actually get resolved. - -## File structure - -Single-context repo (most repos): - -```text -/ -├── CONTEXT.md -├── docs/adr/ -│ ├── 0001-event-sourced-orders.md -│ └── 0002-postgres-for-write-model.md -└── src/ -``` - -Multi-context repo (presence of `CONTEXT-MAP.md` at the root): - -```text -/ -├── CONTEXT-MAP.md -├── docs/adr/ ← system-wide decisions -└── src/ - ├── ordering/ - │ ├── CONTEXT.md - │ └── docs/adr/ ← context-specific decisions - └── billing/ - ├── CONTEXT.md - └── docs/adr/ -``` - -## Use the glossary's vocabulary - -When your output names a domain concept (in an issue title, a refactor proposal, a hypothesis, a test name), use the term as defined in `CONTEXT.md`. Don't drift to synonyms the glossary explicitly avoids. - -If the concept you need isn't in the glossary yet, that's a signal: either you're inventing language the project doesn't use (reconsider) or there's a real gap (note it for `/domain-modeling`). - -## Flag ADR conflicts - -If your output contradicts an existing ADR, surface it explicitly rather than silently overriding: - -> _Contradicts ADR-0007 (event-sourced orders), but worth reopening because…_ diff --git a/docs/agents/issue-tracker.md b/docs/agents/issue-tracker.md deleted file mode 100644 index b258aeb..0000000 --- a/docs/agents/issue-tracker.md +++ /dev/null @@ -1,45 +0,0 @@ -# Issue tracker: GitHub - -Issues and specs for this repo live as GitHub issues. Use the `gh` CLI for all operations. - -## Conventions - -- **Create an issue**: `gh issue create --title "..." --body "..."`. Use a heredoc for multi-line bodies. -- **Read an issue**: `gh issue view --comments`, filtering comments by `jq` and also fetching labels. -- **List issues**: `gh issue list --state open --json number,title,body,labels,comments --jq '[.[] | {number, title, body, labels: [.labels[].name], comments: [.comments[].body]}]'` with appropriate `--label` and `--state` filters. -- **Comment on an issue**: `gh issue comment --body "..."` -- **Apply / remove labels**: `gh issue edit --add-label "..."` / `--remove-label "..."` -- **Close**: `gh issue close --comment "..."` - -Infer the repo from `git remote -v`; `gh` does this automatically when run inside a clone. - -## Pull requests as a triage surface - -**PRs as a request surface: no.** _(Set to `yes` if this repo treats external PRs as feature requests; `/triage` reads this flag.)_ - -When set to `yes`, PRs run through the same labels and states as issues, using the `gh pr` equivalents: - -- **Read a PR**: `gh pr view --comments` and `gh pr diff ` for the diff. -- **List external PRs for triage**: `gh pr list --state open --json number,title,body,labels,author,authorAssociation,comments` then keep only `authorAssociation` of `CONTRIBUTOR`, `FIRST_TIME_CONTRIBUTOR`, or `NONE` (drop `OWNER`/`MEMBER`/`COLLABORATOR`). -- **Comment / label / close**: `gh pr comment`, `gh pr edit --add-label`/`--remove-label`, `gh pr close`. - -GitHub shares one number space across issues and PRs, so a bare `#42` may be either: resolve with `gh pr view 42` and fall back to `gh issue view 42`. - -## When a skill says "publish to the issue tracker" - -Create a GitHub issue. - -## When a skill says "fetch the relevant ticket" - -Run `gh issue view --comments`. - -## Wayfinding operations - -Used by `/wayfinder`. The **map** is a single issue with **child** issues as tickets. - -- **Map**: a single issue labelled `wayfinder:map`, holding the Notes / Decisions-so-far / Fog body. `gh issue create --label wayfinder:map`. -- **Child ticket**: an issue linked to the map as a GitHub sub-issue (`gh api` on the sub-issues endpoint). Where sub-issues aren't enabled, add the child to a task list in the map body and put `Part of #` at the top of the child body. Labels: `wayfinder:` (`research`/`prototype`/`grilling`/`task`). Once claimed, the ticket is assigned to the driving dev. -- **Blocking**: GitHub's **native issue dependencies**, the canonical, UI-visible representation. Add an edge with `gh api --method POST repos///issues//dependencies/blocked_by -F issue_id=`, where `` is the blocker's numeric **database id** (`gh api repos///issues/ --jq .id`, _not_ the `#number` or `node_id`). GitHub reports `issue_dependencies_summary.blocked_by` (open blockers only, the live gate). Where dependencies aren't available, fall back to a `Blocked by: #, #` line at the top of the child body. A ticket is unblocked when every blocker is closed. -- **Frontier query**: list the map's open children (`gh issue list --state open`, scoped to the map's sub-issues / task list), drop any with an open blocker (`issue_dependencies_summary.blocked_by > 0`, or an open issue in the `Blocked by` line) or an assignee; first in map order wins. -- **Claim**: `gh issue edit --add-assignee @me`, the session's first write. -- **Resolve**: `gh issue comment --body ""`, then `gh issue close `, then append a context pointer (gist + link) to the map's Decisions-so-far. diff --git a/docs/agents/triage-labels.md b/docs/agents/triage-labels.md deleted file mode 100644 index b716855..0000000 --- a/docs/agents/triage-labels.md +++ /dev/null @@ -1,15 +0,0 @@ -# Triage Labels - -The skills speak in terms of five canonical triage roles. This file maps those roles to the actual label strings used in this repo's issue tracker. - -| Label in mattpocock/skills | Label in our tracker | Meaning | -| -------------------------- | -------------------- | ---------------------------------------- | -| `needs-triage` | `needs-triage` | Maintainer needs to evaluate this issue | -| `needs-info` | `needs-info` | Waiting on reporter for more information | -| `ready-for-agent` | `ready-for-agent` | Fully specified, ready for an AFK agent | -| `ready-for-human` | `ready-for-human` | Requires human implementation | -| `wontfix` | `wontfix` | Will not be actioned | - -When a skill mentions a role (e.g. "apply the AFK-ready triage label"), use the corresponding label string from this table. - -Edit the right-hand column to match whatever vocabulary you actually use. From 0987840eb7cd47c0bec28f3ce1fa3553c1950a7a Mon Sep 17 00:00:00 2001 From: Philipp Grosswiler Date: Tue, 1 Sep 2026 21:45:34 +0200 Subject: [PATCH 02/51] feat(plan): chart wayfinder map for Kompact serialization framework Wayfinder chart for Kompact (PROMPT.md, greenfield; docs/research is reference only). 3 research tickets resolved via subagents: LSB-first wire bit order; KSP 2.3.9+ generation; expect/actual value-class representation with @JvmInline on the JVM actual only. Frontier open: ticket 04 v1 type set. Also scaffolds docs/agents/ issue tracker, triage labels, and domain-doc conventions, and registers the Agent skills block in AGENTS.md. Chart only; no implementation. --- .../issues/01-wire-format-bit-order.md | 23 ++++++++ .../issues/02-generation-strategy.md | 22 ++++++++ .../issues/03-value-class-representation.md | 24 +++++++++ .../kompact-spec/issues/04-v1-type-set.md | 21 ++++++++ .scratch/kompact-spec/map.md | 38 +++++++++++++ .scratch/kompact-spec/research/bit-order.md | 54 +++++++++++++++++++ .../research/generation-strategy.md | 25 +++++++++ .../research/value-class-representation.md | 41 ++++++++++++++ AGENTS.md | 14 +++++ docs/agents/domain.md | 51 ++++++++++++++++++ docs/agents/issue-tracker.md | 30 +++++++++++ docs/agents/triage-labels.md | 15 ++++++ 12 files changed, 358 insertions(+) create mode 100644 .scratch/kompact-spec/issues/01-wire-format-bit-order.md create mode 100644 .scratch/kompact-spec/issues/02-generation-strategy.md create mode 100644 .scratch/kompact-spec/issues/03-value-class-representation.md create mode 100644 .scratch/kompact-spec/issues/04-v1-type-set.md create mode 100644 .scratch/kompact-spec/map.md create mode 100644 .scratch/kompact-spec/research/bit-order.md create mode 100644 .scratch/kompact-spec/research/generation-strategy.md create mode 100644 .scratch/kompact-spec/research/value-class-representation.md create mode 100644 docs/agents/domain.md create mode 100644 docs/agents/issue-tracker.md create mode 100644 docs/agents/triage-labels.md diff --git a/.scratch/kompact-spec/issues/01-wire-format-bit-order.md b/.scratch/kompact-spec/issues/01-wire-format-bit-order.md new file mode 100644 index 0000000..3417f0d --- /dev/null +++ b/.scratch/kompact-spec/issues/01-wire-format-bit-order.md @@ -0,0 +1,23 @@ +--- +Type: research +Status: resolved +Labels: wayfinder:research +Blocked by: — +Findings: ../research/bit-order.md +--- + +## Question + +`PROMPT.md` requires a bit-packed, zero-padding, sequential stream in which multi-bit values may cross byte boundaries, with identical read/write behavior on Android/JVM and Kotlin/Native iOS using `shl` / `shr` / `and` / `or` over common Kotlin `Byte` boundaries. + +What is the canonical bit-ordering convention for comparable zero-copy bit-serial formats, how is a multi-bit integer that crosses a byte boundary assembled (which byte's bits are the low bits vs the high bits), and what is the idiomatic multiplatform Kotlin implementation? The answer locks the one decision without which `KompactRuntime.readBits` / `writeBits` cannot be tested for cross-platform equivalence. + +## Answer + +**Decision: LSB-first (little-endian) bit packing.** Multi-bit integers assemble least-significant-bit first: byte 0 holds the field's low bits (bits 0–7), byte 1 holds bits 8–15, and bit 0 is the LSB of the value. A cross-boundary read such as `readBits(raw, 4, 10)` takes the low 4 bits of byte 0 and the low 6 bits of byte 1. + +**Runtime rule:** Kotlin `Byte` is signed, so every byte must be masked with `and 0xFF` (`byte.toInt() and 0xFF`) before `shl` / `or`; that masking makes the `shl` / `shr` / `and` / `or` sequence produce identical results on JVM and Kotlin/Native. Signed fields are sign-extended after assembly (two's complement on the assembled unsigned magnitude). + +**Rejected:** MSB-first (ASN.1 PER) — a valid convention but not the dominant one; Cap'n Proto and SLAC both use LSB-first, and little-endian matches the x86/ARM native bit order Kompact targets. + +Findings: [../research/bit-order.md](../research/bit-order.md). diff --git a/.scratch/kompact-spec/issues/02-generation-strategy.md b/.scratch/kompact-spec/issues/02-generation-strategy.md new file mode 100644 index 0000000..5ab261f --- /dev/null +++ b/.scratch/kompact-spec/issues/02-generation-strategy.md @@ -0,0 +1,22 @@ +--- +Type: research +Status: resolved +Labels: wayfinder:research +Findings: ../research/generation-strategy.md +--- + +## Question + +`PROMPT.md` §2 says schemas are "generated via an annotation processor or compiler plugin"; §3 shows manual-looking getters and says "how the boilerplate will eventually be automated" — i.e. manual-first with a future generator that emits the §3 getter style from `@KompactField` annotations. + +Which code-generation approach can produce the Phase 3 common value-class getters from `@KompactField` annotations for a KMP module consumed by Android/JVM and `iosArm64` / `iosSimulatorArm64`, with deterministic output, build-cache reuse, IDE visibility, and incremental processing? Compare KSP (incl. KMP common-generation caveats), Kotlin compiler plugins (K2), and manual — and recommend one, with the caveat that a generator is not required to ship Phase 3 but the chosen strategy must not paint future automation into a corner. + +## Answer + +**Decision: KSP (Kotlin Symbol Processing), KSP 2.3.9+ on Kotlin 1.9+.** KSP generates complete value-class source files into `commonMain` with deterministic output, incremental processing, and Gradle build-cache reuse. K2 compiler macros are explicitly experimental (opt-in, not production-ready for a multi-target KMP library) and are rejected. + +**Critical boundary:** KSP cannot inject into existing source files, so the generator emits *whole* `value class` declarations (the `@KompactField` getters / setters) rather than patching hand-written ones. `PROMPT.md` §3's "manual-looking getters" are therefore the generator's output, deliberately kept human-readable so automation later replaces them 1:1. + +**Caveat (see 03):** KSP generates common `expect` source by default; the per-platform `actual` value classes still need documented source-set wiring. Acceptable for the v1 spec but must be explicit. + +Findings: [../research/generation-strategy.md](../research/generation-strategy.md). diff --git a/.scratch/kompact-spec/issues/03-value-class-representation.md b/.scratch/kompact-spec/issues/03-value-class-representation.md new file mode 100644 index 0000000..fd5940d --- /dev/null +++ b/.scratch/kompact-spec/issues/03-value-class-representation.md @@ -0,0 +1,24 @@ +--- +Type: research +Status: resolved +Labels: wayfinder:research +Findings: ../research/value-class-representation.md +--- + +## Question + +`PROMPT.md` §1 wants multiplatform value classes wrapping a `ByteArray` with zero-allocation reads. The accepted resolution is: generated `expect/actual value class` declarations, with `@JvmInline` on the JVM `actual`. + +How must the `expect` / `actual` value-class declarations be shaped so that (a) the common `expect` can omit `@JvmInline` while the JVM `actual` carries it, (b) Kotlin/Native represents the view class soundly (boxed where unavoidable, unboxed at direct concrete call sites), and (c) the verified zero-allocation call shape is: a non-null view held in a local of its concrete generated type, over a caller-owned `ByteArray`, with a direct scalar `val` read returning a primitive — and no generic, interface, nullable, reflection, collection, or bridge boundary inside the measured read? Name the exact boundaries where boxing is unavoidable so the spec can forbid them on the hot path. + +## Answer + +**Decision: `expect` in commonMain + `@JvmInline actual` per platform.** The common declaration is `expect value class Foo(val raw: ByteArray)` with **no** `@JvmInline` — the annotation is JVM-stdlib-only and errors on non-JVM targets. Each platform source set carries `actual value class`: `@JvmInline actual value class Foo` in `jvmMain`; plain `actual value class Foo` in `iosArm64Main` and `iosSimulatorArm64Main`. + +**Cross-platform behavior:** Kotlin/Native renders value classes as Swift-value structs; wrapper allocation (boxing) occurs only at type-erasure boundaries — generics, nullable (`Foo?`), interface/`Any`-typed parameters, and ABI-crossing returns. On the JVM, `@JvmInline` is what unboxes at direct call sites. + +**Hot-path guardrail:** the zero-allocation read contract covers only direct, non-nullable, concrete-typed scalar reads over a caller-owned `ByteArray`. The spec must forbid generic / interface / nullable / `Any`-typed usage on the measured path. + +This reconciles the `PROMPT.md` §1 prohibition (hand-written common API, no `@JvmInline`) with the JVM value-class contract (generated JVM `actual` carries `@JvmInline`) and rides on the [generation-strategy](02-generation-strategy.md) decision. + +Findings: [../research/value-class-representation.md](../research/value-class-representation.md). diff --git a/.scratch/kompact-spec/issues/04-v1-type-set.md b/.scratch/kompact-spec/issues/04-v1-type-set.md new file mode 100644 index 0000000..6e8f5f5 --- /dev/null +++ b/.scratch/kompact-spec/issues/04-v1-type-set.md @@ -0,0 +1,21 @@ +--- +Type: grilling +Status: open +Labels: wayfinder:grilling +Blocked by: — +Depends on: 01-wire-format-bit-order (resolved) +--- + +## Question + +`PROMPT.md`'s example fields are Enum (4 bits), Int (10 bits), and Boolean (1 bit). That is one sketch, not a type set. What is the complete scalar + composite type set the v1 wire format and `KompactRuntime` must support? + +Specifically decide: +- Signed vs unsigned integers at which bit widths (the performance workload matrix names 1–64, signed 2/7/10/32/64, unsigned 1/5/8/10/16/32/64). +- Enum encoding: dense ordinal (the `0–15` sketch) vs explicit codes; gapped / unknown-code handling. +- Floats: 32- and 64-bit IEEE-754 with canonicalized NaN; in scope or deferred? +- Variable-length / strings / blobs: varint + length prefix, or fixed-width only? +- Nested composites (a field that is itself a bit-packed struct) and repeated fields: one layout, or offset/delimited? +- If variable-length is included, the bit-width of the field-length / envelope metadata. + +This decision gates `KompactRuntime.readBits` / `writeBits` overloads, the `@KompactField` annotation surface, and the cross-platform test matrix's width coverage. Resolve before the validation, write/builder, or error-model tickets. diff --git a/.scratch/kompact-spec/map.md b/.scratch/kompact-spec/map.md new file mode 100644 index 0000000..8f4e0ad --- /dev/null +++ b/.scratch/kompact-spec/map.md @@ -0,0 +1,38 @@ +# Wayfinder Map: Kompact + +## Destination + +A decided, implementable architecture spec for **Kompact**, the bit-packed, zero-allocation Kotlin Multiplatform serialization framework described in `PROMPT.md`, ready to hand off for implementation. Reaching the end of this map means the spec locks the wire format, the common runtime API (`readBits` / `writeBits` / `readBitsBoolean` over a `ByteArray`), the generated value-class view pattern, the code-generation strategy, the validation model, the cross-platform testing model, and the performance-evidence plan — leaving no gating decisions for the person who implements it. + +## Notes + +- **Source of truth**: `PROMPT.md` only (greenfield). `docs/research/*` are reference material, not binding decisions — do **not** inherit their conclusions; re-derive from `PROMPT.md` + external primary sources. +- **Platforms**: Android/JVM + iOS as Kotlin/Native (`iosArm64`, `iosSimulatorArm64`). +- **Accepted resolution on `@JvmInline`**: generated `expect/actual value class` declarations may carry `@JvmInline` on the JVM `actual`. The `PROMPT.md` §1 prohibition applies to hand-written common API, not to generated JVM actuals. JVM value classes require `@JvmInline`; this is a language constraint, not a project design choice. +- Tracking: this map + child tickets live as markdown under `.scratch/kompact-spec/` (see `docs/agents/issue-tracker.md`). Research findings link from each ticket under `.scratch/kompact-spec/research/` and are throwaway — superseded once folded into the spec. +- Domain-doc consumption rules: see `docs/agents/domain.md`. + +## Decisions so far + +- [Wire-format bit order — LSB-first](issues/01-wire-format-bit-order.md): multi-bit ints assemble LSB-first (byte 0 = field bits 0–7, byte 1 = bits 8–15, bit 0 = value LSB); `Byte` must be masked `and 0xFF` before `shl`/`or` for identical JVM/Native results. Findings: [research/bit-order.md](research/bit-order.md). +- [Code generation — KSP 2.3.9+](issues/02-generation-strategy.md): KSP emits whole `value class` source files into commonMain (deterministic, incremental, cacheable); K2 macros rejected as experimental. Generator emits complete declarations, never patches hand-written ones. Findings: [research/generation-strategy.md](research/generation-strategy.md). +- [Value-class representation — expect/actual](issues/03-value-class-representation.md): `expect value class` (no `@JvmInline`) in commonMain; `@JvmInline actual` in jvmMain; plain `actual` in `iosArm64Main` + `iosSimulatorArm64Main`. Zero-alloc only at direct non-nullable concrete scalar reads over a caller-owned `ByteArray`. Findings: [research/value-class-representation.md](research/value-class-representation.md). + +**Reconciliation (02 + 03) — Generated view-class structure**: the generator emits an `expect value class` in commonMain plus `@JvmInline actual` (jvmMain) and plain `actual` (iosArm64Main, iosSimulatorArm64Main), all wrapping the same `ByteArray`. KSP produces the common `expect`; platform `actual`s require documented source-set wiring. + +## Not yet specified + +- **Validation model** — compile-time (KSP) field-layout checks (overlap, width-sum) vs runtime; what `@KompactField(bitOffset, bitWidth)` validates. Informed by 02. +- **Write/builder interface** — `PROMPT.md` §3 "writes values into the array" against a `val raw: ByteArray` view. Separate writer/builder, or `writeBits` into a mutable `ByteArray` wrapped read-only? Informed by 03. +- **Runtime error model** — `readBits` / `writeBits` on out-of-range width or short buffer: throw vs typed result, bounds contract. +- **Versioning & schema evolution** — reserved bits (PROMPT shows one), layout identity, forward/backward compatibility. +- **Performance-evidence plan** — how the zero-allocation / zero-copy read claim is measured on Android + iOS (re-derived; reference doc ignored). +- **Module split & publication** — single artifact vs runtime/annotations/processor/plugin split and KMP publication wiring (re-derived; reference doc ignored). + +> The **v1 type set** has graduated to [ticket 04](issues/04-v1-type-set.md) (`wayfinder:grilling`, open, unblocked) — the frontier decision for the next session. The remainder above is fog to graduate one at a time in a "work through the map" session. + +## Out of scope + +- **C / C99 header generation and foreign-language interop** — `PROMPT.md` is purely Kotlin Multiplatform; no C emission requested. +- **BLE transport layer** — `PROMPT.md` covers serialization format and runtime, not the GATT/profile layer that carries payloads. +- **iOS Swift / Objective-C API surface generation** — in scope only if the Kotlin view class needs a Swift-visible wrapper; not a first concern. diff --git a/.scratch/kompact-spec/research/bit-order.md b/.scratch/kompact-spec/research/bit-order.md new file mode 100644 index 0000000..b59e180 --- /dev/null +++ b/.scratch/kompact-spec/research/bit-order.md @@ -0,0 +1,54 @@ +# Bit-Ordering Convention for Zero-Copy Bit-Packed Streams + +## Recommendation +Use LSB-first (little-endian) bit ordering for a zero-copy bit-packed stream, as it is the dominant convention in modern serialization frameworks and aligns with x86/ARM native bit ordering. + +## Key Evidence + +### LSB-First Convention (Recommended) + +**Cap'n Proto Encoding Spec** states: +> "Booleans are packed bit-by-bit in little-endian order (the first bit is the least-significant bit of the first byte)." + +**SLAC Protocol (ISO 15118 EV charging)** documentation confirms: +> The SLAC protocol transmits data with the least-significant-bit first ordering. + +This convention means that for an integer crossing a byte boundary: +- Byte 0 contains bits 0-7 (LSB of field first) +- Byte 1 contains bits 8-15 (next LSB) +- Bit 0 = LSB of the integer value + +To assemble from bytes in Kotlin multiplatform: +```kotlin +// Read a 12-bit value spanning bytes[0] and bytes[1] +val value = ((bytes[0].toInt() and 0xFF) or ((bytes[1].toInt() and 0xFF) shl 8)) and 0x0FFF +``` + +### MSB-First Convention (Alternative) + +**ASN.1 PER (ITU-T X.691)** specifies: +> "bits are transmitted most-significant-bit-first (big-endian) within each octet; the first bit emitted for a value is the high-order bit of the first byte" + +This requires different assembly logic where higher-order bits come first in the byte stream. + +### FlatBuffers Clarification + +FlatBuffers uses little-endian byte order for multi-byte scalars but does not perform bit-packing beyond byte alignment—fields occupy whole bytes. This makes FlatBuffers unsuitable for bit-packed integer scenarios, but its little-endian byte-order convention aligns with the LSB-first recommendation. + +## Boundary/Caveat + +When implementing bit-packed integer assembly: +1. **Signed integers**: Use two's complement on the assembled value after bit reconstruction +2. **Byte operations**: Kotlin `Byte` is signed (-128 to 127); always use `.toInt() and 0xFF` for unsigned interpretation before bit operations +3. **Cross-platform consistency**: The `shl`/`shr`/`and`/`or` operations on `Byte` in Kotlin Multiplatform (JVM, Android, iosArm64, iosSimulatorArm64) require explicit masking to 0xFF to handle sign extension correctly on platforms where `Byte` arithmetic propagates signs + +## Sources +- Cap'n Proto Encoding Specification, capnproto.org/encoding.html +- Wikipedia Bit Numbering, en.wikipedia.org/wiki/Bit_numbering +- ASN.1 X.691 PER specification (ITU-T) +- SLAC protocol documentation for ISO 15118 electric vehicle charging +- FlatBuffers format documentation + +--- + +*Research for Kompact serialization framework - Wayfinder ticket 01-wire-format-bit-order* \ No newline at end of file diff --git a/.scratch/kompact-spec/research/generation-strategy.md b/.scratch/kompact-spec/research/generation-strategy.md new file mode 100644 index 0000000..c8de7d4 --- /dev/null +++ b/.scratch/kompact-spec/research/generation-strategy.md @@ -0,0 +1,25 @@ +# Code Generation Strategy for Kompact Value-Class Getters + +**Recommendation:** Use KSP with Kotlin 1.9+ for incremental, deterministic generation of complete `value class` declarations in commonMain, targeting Android/JVM and iOS Native. + +## Key Evidence + +1. **KSP supports commonMain generation** — KSP generates Kotlin source files into `build/generated/ksp/commonMain/kotlin`, compiled for all targets (JVM, Android, iOS). Generated code is fully IDE-visible with navigation, refactoring, and autocomplete support via Gradle source-set inclusion. + +2. **KSP incremental processing provides deterministic output** (KSP 2.3.9+) — Per the incremental processing spec, KSP tracks dependencies via resolution tracing and input-output correspondence, ensuring minimal rebuilds with Gradle build-cache reuse. The dirtiness propagation rules guarantee identical outputs for unchanged inputs. + +3. **K2 compiler macros are experimental** — Kotlin 2.2+ macros are explicitly marked experimental, require opt-in flags (`@OptIn(kotlin.experimental.macros.MacroApi::class)`), and are not production-ready for KMP libraries targeting multiple platforms. + +## Critical Boundary + +**KSP generates entire value-class source files, NOT property implementations for existing declarations** — KSP cannot modify existing Kotlin files. Therefore, the generator must produce complete `value class Foo(val raw: ByteArray) { @KompactField... val x: Int get() = ... }` declarations in commonMain using Kotlin 1.9+`value class` syntax (without `@JvmInline`). The `@JvmInline` annotation is JVM-specific and unavailable in commonMain, but Kotlin 1.9+ value classes work correctly on all platforms without it. + +Per the accepted user constraint: generated `actual` value classes MAY carry `@JvmInline` on JVM targets; the PROMPT §3 prohibition applies to hand-written common API only. + +## Sources + +- Kotlin Symbol Processing with Kotlin Multiplatform — https://kotlinlang.org/docs/ksp-multiplatform.html +- KSP Incremental Processing — https://kotlinlang.org/docs/ksp-incremental.html +- Kotlin Symbol Processing API Overview — https://kotlinlang.org/docs/ksp-overview.html +- KSP FAQ — https://kotlinlang.org/docs/ksp-faq.html +- What's new in Kotlin 2.2.20 — https://kotlinlang.org/docs/whatsnew2220.html (macros stability) \ No newline at end of file diff --git a/.scratch/kompact-spec/research/value-class-representation.md b/.scratch/kompact-spec/research/value-class-representation.md new file mode 100644 index 0000000..0fd7e73 --- /dev/null +++ b/.scratch/kompact-spec/research/value-class-representation.md @@ -0,0 +1,41 @@ +# Value Class Representation: Expect/Actual Patterns for Kompact + +## Recommendation +Use `expect` without @JvmInline in commonMain, with `@JvmInline actual value class` declarations in each platform source set (jvmMain, iosArm64Main, iosSimulatorArm64Main). + +## Key Evidence + +### 1. @JvmInline Multiplatform Support Status +Kotlin 2.6 still requires `@JvmInline` on the literal declaration (cannot be hidden via expect/actual annotation class). The annotation exists only in `kotlin-stdlib-jvm` for value class compilation; other platforms (JS, WASM, Native) lack it in their stdlibs. Common code cannot declare value classes directly. + +> "In Kotlin 2.6 you can't place a value class directly in a common source set because the `@JvmInline` annotation that makes a class a value class exists only in the JVM-specific stdlib" — [Source: Medium article on expect/actual patterns] + +### 2. Kotlin/Native Value Class Representation +Kotlin/Native compiles value classes as Swift structs, passed by value. Boxing (wrapper allocation) occurs only at type-erasure boundaries: +- Generic type arguments +- Nullable types (`Foo?`) +- Interface/Any-typed parameters +- Return values crossing ABI boundaries + +> "On the iOS side they appear as plain Swift structs containing the same single field... boxing only occurs when the Kotlin type is used in a context that requires type erasure" — [Source: TypeAlias guide] + +## Exact Call-Shape Boundaries (Hot Path) + +**Unboxed (zero-cost):** +- Direct calls: `fun process(id: LocalId)` where `LocalId` is the actual value class +- Non-nullable, non-generic usage +- Platform-specific APIs + +**Boxing (allocation):** +- Generic calls: `fun process(x: T)` +- Interface calls: `fun process(id: Displayable)` +- Nullable calls: `fun process(id: LocalId?)` +- Java interop (calls through erasure) + +## Caveat for Spec +The `expect` class in commonMain must NOT carry `@JvmInline` (it's meaningless there and causes compilation errors on non-JVM platforms). Each platform's `actual` MUST be a value class with `@JvmInline`, and the underlying type must be consistent (ByteArray) for ABI compatibility across expect/actual projections. + +## Sources +- Kotlin 2.6 multiplatform value class limitation: https://medium.com/@KaushalVasava/expect-and-actual-functions-in-kotlin-for-kotlin-multi-platform-19a3ba08d4c4e +- Kotlin inline classes documentation: https://kotlinlang.org/docs/inline-classes.html +- TypeAlias guide on autoboxing: https://typealias.com/guides/inline-classes-and-autoboxing \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md index c2d1d33..5493656 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -76,3 +76,17 @@ Yield only if all true: - Constitution compliant. Final report R `{changed files+behavior, exact commands+observed results, docs/API/compat/security/performance impact, blocker/unverified state, specialized instructions/skills used}`. X claim unobserved command/test/review/runtime behavior. + +## Agent skills + +### Issue tracker + +Issues and spec tickets live as markdown files under `.scratch//`; no GitHub Issues used. See `docs/agents/issue-tracker.md`. + +### Triage labels + +Canonical triage labels, each role mapped to its matching string (`needs-triage`, `needs-info`, `ready-for-agent`, `ready-for-human`, `wontfix`). See `docs/agents/triage-labels.md`. + +### Domain docs + +Single-context: one root `CONTEXT.md` plus `docs/adr/` for system-wide decisions. See `docs/agents/domain.md`. diff --git a/docs/agents/domain.md b/docs/agents/domain.md new file mode 100644 index 0000000..3524904 --- /dev/null +++ b/docs/agents/domain.md @@ -0,0 +1,51 @@ +# Domain Docs + +How the engineering skills should consume this repo's domain documentation when exploring the codebase. + +## Before exploring, read these + +- **`CONTEXT.md`** at the repo root, or +- **`CONTEXT-MAP.md`** at the repo root if it exists: it points at one `CONTEXT.md` per context. Read each one relevant to the topic. +- **`docs/adr/`**: read ADRs that touch the area you're about to work in. In multi-context repos, also check `src//docs/adr/` for context-scoped decisions. + +If any of these files don't exist, **proceed silently**. Don't flag their absence; don't suggest creating them upfront. The `/domain-modeling` skill (reached via `/grill-with-docs` and `/improve-codebase-architecture`) creates them lazily when terms or decisions actually get resolved. + +## File structure + +Single-context repo (most repos): + +``` +/ +├── CONTEXT.md +├── docs/adr/ +│ ├── 0001-event-sourced-orders.md +│ └── 0002-postgres-for-write-model.md +└── src/ +``` + +Multi-context repo (presence of `CONTEXT-MAP.md` at the root): + +``` +/ +├── CONTEXT-MAP.md +├── docs/adr/ ← system-wide decisions +└── src/ + ├── ordering/ + │ ├── CONTEXT.md + │ └── docs/adr/ ← context-specific decisions + └── billing/ + ├── CONTEXT.md + └── docs/adr/ +``` + +## Use the glossary's vocabulary + +When your output names a domain concept (in an issue title, a refactor proposal, a hypothesis, a test name), use the term as defined in `CONTEXT.md`. Don't drift to synonyms the glossary explicitly avoids. + +If the concept you need isn't in the glossary yet, that's a signal: either you're inventing language the project doesn't use (reconsider) or there's a real gap (note it for `/domain-modeling`). + +## Flag ADR conflicts + +If your output contradicts an existing ADR, surface it explicitly rather than silently overriding: + +> _Contradicts ADR-0007 (event-sourced orders), but worth reopening because…_ diff --git a/docs/agents/issue-tracker.md b/docs/agents/issue-tracker.md new file mode 100644 index 0000000..0209a19 --- /dev/null +++ b/docs/agents/issue-tracker.md @@ -0,0 +1,30 @@ +# Issue tracker: Local Markdown + +Issues and specs for this repo live as markdown files in `.scratch/`. + +## Conventions + +- One feature per directory: `.scratch//` +- The spec is `.scratch//spec.md` +- Implementation issues are one file per ticket at `.scratch//issues/-.md`, numbered from `01`, never a single combined tickets file +- Triage state is recorded as a `Status:` line near the top of each issue file (see `triage-labels.md` for the role strings) +- Comments and conversation history append to the bottom of the file under a `## Comments` heading + +## When a skill says "publish to the issue tracker" + +Create a new file under `.scratch//` (creating the directory if needed). + +## When a skill says "fetch the relevant ticket" + +Read the file at the referenced path. The user will normally pass the path or the issue number directly. + +## Wayfinding operations + +Used by `/wayfinder`. The **map** is a file with one **child** file per ticket. + +- **Map**: `.scratch//map.md` (the Notes / Decisions-so-far / Fog body). +- **Child ticket**: `.scratch//issues/NN-.md`, numbered from `01`, with the question in the body. A `Type:` line records the ticket type (`research`/`prototype`/`grilling`/`task`); a `Status:` line records `claimed`/`resolved`. +- **Blocking**: a `Blocked by: NN, NN` line near the top. A ticket is unblocked when every file it lists is `resolved`. +- **Frontier**: scan `.scratch//issues/` for files that are open, unblocked, and unclaimed; first by number wins. +- **Claim**: set `Status: claimed` and save before any work. +- **Resolve**: append the answer under an `## Answer` heading, set `Status: resolved`, then append a context pointer (gist + link) to the map's Decisions-so-far in `map.md`. diff --git a/docs/agents/triage-labels.md b/docs/agents/triage-labels.md new file mode 100644 index 0000000..b716855 --- /dev/null +++ b/docs/agents/triage-labels.md @@ -0,0 +1,15 @@ +# Triage Labels + +The skills speak in terms of five canonical triage roles. This file maps those roles to the actual label strings used in this repo's issue tracker. + +| Label in mattpocock/skills | Label in our tracker | Meaning | +| -------------------------- | -------------------- | ---------------------------------------- | +| `needs-triage` | `needs-triage` | Maintainer needs to evaluate this issue | +| `needs-info` | `needs-info` | Waiting on reporter for more information | +| `ready-for-agent` | `ready-for-agent` | Fully specified, ready for an AFK agent | +| `ready-for-human` | `ready-for-human` | Requires human implementation | +| `wontfix` | `wontfix` | Will not be actioned | + +When a skill mentions a role (e.g. "apply the AFK-ready triage label"), use the corresponding label string from this table. + +Edit the right-hand column to match whatever vocabulary you actually use. From 40140bc740b555e351f7241093de64e1e21102c0 Mon Sep 17 00:00:00 2001 From: Philipp Grosswiler Date: Tue, 1 Sep 2026 21:56:18 +0200 Subject: [PATCH 03/51] feat(plan): resolve v1 type set, seed framing frontier ticket MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Work-through the Kompact wayfinder map: resolve ticket 04 (v1 type set — full scalar+composite set incl. variable-length, nested, repeated; a scope expansion beyond PROMPT.md's 2-byte sketch) and graduate ticket 05 (variable-length/nested/repeated framing) as the next frontier. Update map Decisions-so-far + Not-yet-specified. One ticket resolved (04); 05 left open for the next session. --- .../kompact-spec/issues/04-v1-type-set.md | 20 +++++++++++++++++-- .../issues/05-variable-length-framing.md | 20 +++++++++++++++++++ .scratch/kompact-spec/map.md | 12 ++++++----- 3 files changed, 45 insertions(+), 7 deletions(-) create mode 100644 .scratch/kompact-spec/issues/05-variable-length-framing.md diff --git a/.scratch/kompact-spec/issues/04-v1-type-set.md b/.scratch/kompact-spec/issues/04-v1-type-set.md index 6e8f5f5..3cf2363 100644 --- a/.scratch/kompact-spec/issues/04-v1-type-set.md +++ b/.scratch/kompact-spec/issues/04-v1-type-set.md @@ -1,9 +1,8 @@ --- Type: grilling -Status: open +Status: resolved Labels: wayfinder:grilling Blocked by: — -Depends on: 01-wire-format-bit-order (resolved) --- ## Question @@ -19,3 +18,20 @@ Specifically decide: - If variable-length is included, the bit-width of the field-length / envelope metadata. This decision gates `KompactRuntime.readBits` / `writeBits` overloads, the `@KompactField` annotation surface, and the cross-platform test matrix's width coverage. Resolve before the validation, write/builder, or error-model tickets. + +## Answer + +**Decision (user-resolved): the full v1 type set, including variable-length.** Per the live exchange, Kompact v1 supports: + +- **Unsigned integers** at declared bit widths 1–64. +- **Signed integers** at declared bit widths 1–64 (two's-complement on the assembled magnitude). +- **Booleans** — 1 bit. +- **Enums** — dense ordinal at a declared 1–8-bit width; an unknown code yields a typed error result (fail closed), not a silent default. +- **Floats** — IEEE-754 32-bit and 64-bit, with NaN canonicalized to a single canonical bit pattern. +- **Variable-length values** — strings and blobs, length-framed (NOT deferred). +- **Nested composites** — a bit-packed struct used as a field (NOT deferred). +- **Repeated fields** — ordered sequences (NOT deferred). + +**Scope implication:** this is a deliberate expansion beyond `PROMPT.md`'s 2-byte `VehicleTelemetry` sketch (Enum + Int + Boolean only). v1 now requires an **envelope / framing contract** for length-prefixed, nested, and repeated fields — the substance of ticket 05. `readBits` / `writeBits` widen accordingly (length-prefix + nested base-offset + count handling); the `@KompactField` surface gains length / nesting / repeat annotations. + +**Risk note:** v1 is now substantially larger than the PROMPT sketch. The framing (05), write/builder (fog), validation (fog), error model (fog), and versioning (fog) tickets must lock before implementation; each adds surface. The inclusion of variable-length / nested / repeated is intentional — flag if v1 should be trimmed back to the fixed-width sketch instead. diff --git a/.scratch/kompact-spec/issues/05-variable-length-framing.md b/.scratch/kompact-spec/issues/05-variable-length-framing.md new file mode 100644 index 0000000..922cc98 --- /dev/null +++ b/.scratch/kompact-spec/issues/05-variable-length-framing.md @@ -0,0 +1,20 @@ +--- +Type: grilling +Status: open +Labels: wayfinder:grilling +Blocked by: 04-v1-type-set (resolved) +--- + +## Question + +Ticket 04 committed v1 to **variable-length strings/blobs, nested composites, and repeated fields** — beyond `PROMPT.md`'s fixed 2-byte sketch. How must Kompact frame these in the bit-packed stream? Three coupled choices: + +1. **Variable-length length-prefix**: varint (Protobuf-style — compact, variable CPU) vs a fixed 1/2/4-byte little-endian prefix (predictable decode) vs a per-field-declared prefix width. +2. **Nested composite layout**: bit-offset **relative to the parent's start** (nested fields re-base at the parent's first bit — local offset math, parent needs a base pointer / length) vs **absolute** bit-offset from the stream start (simpler reads, parent can't move without recomputation). +3. **Repeated fields**: **count-prefixed** (one `N` then `N` fixed-or-variable elements) vs **length-delimited** (one total length then the elements). + +This decides the envelope / framing contract that `KompactRuntime` and the generated getters must implement; it gates the write/builder interface, validation, error model, and versioning tickets. Resolve before any non-fixed-width runtime code is written. + +## Answer + +_(pending — next frontier decision)_ diff --git a/.scratch/kompact-spec/map.md b/.scratch/kompact-spec/map.md index 8f4e0ad..437e5d8 100644 --- a/.scratch/kompact-spec/map.md +++ b/.scratch/kompact-spec/map.md @@ -16,20 +16,22 @@ A decided, implementable architecture spec for **Kompact**, the bit-packed, zero - [Wire-format bit order — LSB-first](issues/01-wire-format-bit-order.md): multi-bit ints assemble LSB-first (byte 0 = field bits 0–7, byte 1 = bits 8–15, bit 0 = value LSB); `Byte` must be masked `and 0xFF` before `shl`/`or` for identical JVM/Native results. Findings: [research/bit-order.md](research/bit-order.md). - [Code generation — KSP 2.3.9+](issues/02-generation-strategy.md): KSP emits whole `value class` source files into commonMain (deterministic, incremental, cacheable); K2 macros rejected as experimental. Generator emits complete declarations, never patches hand-written ones. Findings: [research/generation-strategy.md](research/generation-strategy.md). -- [Value-class representation — expect/actual](issues/03-value-class-representation.md): `expect value class` (no `@JvmInline`) in commonMain; `@JvmInline actual` in jvmMain; plain `actual` in `iosArm64Main` + `iosSimulatorArm64Main`. Zero-alloc only at direct non-nullable concrete scalar reads over a caller-owned `ByteArray`. Findings: [research/value-class-representation.md](research/value-class-representation.md). +- [Value-class representation — expect/actual](issues/03-value-class-representation.md): `expect value class` (no `@JvmInline`) in commonMain; `@JvmInline actual` in jvmMain; plain `actual` in iosArm64Main + iosSimulatorArm64Main. Zero-alloc only at direct non-nullable concrete scalar reads over a caller-owned `ByteArray`. Findings: [research/value-class-representation.md](research/value-class-representation.md). +- [v1 type set](issues/04-v1-type-set.md): **unsigned ints 1–64; signed ints 1–64 (two's-complement on assembled magnitude); booleans (1 bit); enums as dense ordinal at a declared 1–8-bit width with unknown → typed error; IEEE-754 32- and 64-bit floats (canonicalized NaN); AND variable-length strings/blobs, nested composites, repeated fields — a deliberate scope expansion beyond `PROMPT.md`'s fixed-width sketch.** Implication: v1 now needs a framing contract. User-decided (grilling). **Reconciliation (02 + 03) — Generated view-class structure**: the generator emits an `expect value class` in commonMain plus `@JvmInline actual` (jvmMain) and plain `actual` (iosArm64Main, iosSimulatorArm64Main), all wrapping the same `ByteArray`. KSP produces the common `expect`; platform `actual`s require documented source-set wiring. ## Not yet specified +- **Variable-length / nested / repeated framing** → graduated to [ticket 05](issues/05-variable-length-framing.md) (`wayfinder:grilling`, open, unblocked). Length-prefix shape, nested base-offset vs absolute, repeated count-prefix vs length-delimited. Depends on 04. - **Validation model** — compile-time (KSP) field-layout checks (overlap, width-sum) vs runtime; what `@KompactField(bitOffset, bitWidth)` validates. Informed by 02. -- **Write/builder interface** — `PROMPT.md` §3 "writes values into the array" against a `val raw: ByteArray` view. Separate writer/builder, or `writeBits` into a mutable `ByteArray` wrapped read-only? Informed by 03. -- **Runtime error model** — `readBits` / `writeBits` on out-of-range width or short buffer: throw vs typed result, bounds contract. -- **Versioning & schema evolution** — reserved bits (PROMPT shows one), layout identity, forward/backward compatibility. +- **Write/builder interface** — "write values into the array" vs a separate writer; must now carry length-prefix / nested / repeat writes. Informed by 03 + 04 + 05. +- **Runtime error model** — `readBits` / `writeBits` on out-of-range width or short buffer: throw vs typed result; bounds contract. Now also covers unknown enum codes (04) and bad length prefixes (05). +- **Versioning & schema evolution** — reserved bits, layout identity, forward/backward compatibility over variable-length/nested framing. Informed by 04. - **Performance-evidence plan** — how the zero-allocation / zero-copy read claim is measured on Android + iOS (re-derived; reference doc ignored). - **Module split & publication** — single artifact vs runtime/annotations/processor/plugin split and KMP publication wiring (re-derived; reference doc ignored). -> The **v1 type set** has graduated to [ticket 04](issues/04-v1-type-set.md) (`wayfinder:grilling`, open, unblocked) — the frontier decision for the next session. The remainder above is fog to graduate one at a time in a "work through the map" session. +> **Ticket 04 (v1 type set) resolved** above — decided the full type set incl. variable-length + nested + repeated. Its scope decision graduates **ticket 05 (framing)** as the next frontier; the remainder is fog to graduate one at a time in a "work through the map" session. ## Out of scope From d2c9b588b90a93ec5fd1969d608f733d227d1644 Mon Sep 17 00:00:00 2001 From: Philipp Grosswiler Date: Tue, 1 Sep 2026 22:07:51 +0200 Subject: [PATCH 04/51] feat(plan): resolve framing, seed validation frontier MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Work-through: resolve ticket 05 (framing — sequential length-delimited; fixed-width LE length prefixes; parse-forward nested sub-regions; count-prefixed repeats; random-access rejected vs variable-length, per ticket 04). Graduate ticket 06 (validation model) as next frontier. Update map Decisions-so-far + Not-yet-specified. --- .../issues/05-variable-length-framing.md | 18 +++++++++++++----- .../kompact-spec/issues/06-validation-model.md | 16 ++++++++++++++++ .scratch/kompact-spec/map.md | 14 +++++++------- 3 files changed, 36 insertions(+), 12 deletions(-) create mode 100644 .scratch/kompact-spec/issues/06-validation-model.md diff --git a/.scratch/kompact-spec/issues/05-variable-length-framing.md b/.scratch/kompact-spec/issues/05-variable-length-framing.md index 922cc98..e6f348b 100644 --- a/.scratch/kompact-spec/issues/05-variable-length-framing.md +++ b/.scratch/kompact-spec/issues/05-variable-length-framing.md @@ -1,6 +1,6 @@ --- Type: grilling -Status: open +Status: resolved Labels: wayfinder:grilling Blocked by: 04-v1-type-set (resolved) --- @@ -9,12 +9,20 @@ Blocked by: 04-v1-type-set (resolved) Ticket 04 committed v1 to **variable-length strings/blobs, nested composites, and repeated fields** — beyond `PROMPT.md`'s fixed 2-byte sketch. How must Kompact frame these in the bit-packed stream? Three coupled choices: -1. **Variable-length length-prefix**: varint (Protobuf-style — compact, variable CPU) vs a fixed 1/2/4-byte little-endian prefix (predictable decode) vs a per-field-declared prefix width. -2. **Nested composite layout**: bit-offset **relative to the parent's start** (nested fields re-base at the parent's first bit — local offset math, parent needs a base pointer / length) vs **absolute** bit-offset from the stream start (simpler reads, parent can't move without recomputation). -3. **Repeated fields**: **count-prefixed** (one `N` then `N` fixed-or-variable elements) vs **length-delimited** (one total length then the elements). +1. **Variable-length length-prefix**: varint vs fixed 1/2/4-byte vs per-field-declared. +2. **Nested layout**: self-delimiting length-delimited sub-region vs relative/absolute bit-offset. +3. **Repeated fields**: count-prefixed vs length-delimited. This decides the envelope / framing contract that `KompactRuntime` and the generated getters must implement; it gates the write/builder interface, validation, error model, and versioning tickets. Resolve before any non-fixed-width runtime code is written. ## Answer -_(pending — next frontier decision)_ +**Decision (user-resolved): sequential, length-delimited framing on the LSB-first bit stream (ticket 01's order).** + +1. **Length-prefix — fixed-width little-endian, width declared per-field.** Variable-length fields carry an 8- or 16-bit LE byte-count (per-field, via an annotation). Varint is rejected: its loop-based decode and variable CPU conflict with the zero-allocation / predictable-read ethos, and Kompact's reader is fixed-width single-pass. +2. **Nested composites — length-delimited sub-regions, parse-forward.** A nested struct carries its total bit-length; reading it consumes those bits and siblings are reached by continuing to scan. +3. **Repeated fields — count-prefixed, sequential.** One fixed-width count, then `N` elements in order. + +**Key tradeoff accepted (recorded so the spec does not over-promise):** reads are **sequential (parse-forward), not random-access.** This deliberately diverges from FlatBuffers-style offset-jump reads, because ticket 04's variable-length fields make stored offsets shift and break. `PROMPT.md`'s "zero-copy reads like FlatBuffers" is satisfied by the *view-class read path* (no allocation/copy to read a scalar), **not** by random access to every field; the cost of variable-length framing is sequential traversal. + +This framing contract gates `KompactRuntime`'s length-prefix + nested-length + count helpers and the generator's envelope layout. diff --git a/.scratch/kompact-spec/issues/06-validation-model.md b/.scratch/kompact-spec/issues/06-validation-model.md new file mode 100644 index 0000000..d15c8f3 --- /dev/null +++ b/.scratch/kompact-spec/issues/06-validation-model.md @@ -0,0 +1,16 @@ +--- +Type: grilling +Status: open +Labels: wayfinder:grilling +Blocked by: 02-generation-strategy (resolved), 04-v1-type-set (resolved), 05-variable-length-framing (resolved) +--- + +## Question + +Now that generation (KSP), the type set, and framing are decided, where does field-layout validation live, and what does `@KompactField` actually validate? + +1. **Compile-time vs runtime**: does the annotation processor validate layouts — bit-offset overlaps, per-struct width-sum, length-prefix bounds, nested sub-region consistency, repeated-count sanity — at compile time? Or is validation a runtime check in `KompactRuntime`? +2. **What is validated**: which invariants are checked (offset overlap, width-sum ≤ struct bit-length, length-prefix ≤ remaining buffer, nested total-length consistency, enum code within the declared width)? +3. **Failure mode**: compile-time violations are hard errors that halt processing with symbol-located diagnostics (matching the diagnostics discipline from the generation research); runtime validation yields a typed result per the error-model ticket. + +This gates the processor's validation pass, the runtime error contract, and the conformance test surface. Resolve before the error-model ticket. diff --git a/.scratch/kompact-spec/map.md b/.scratch/kompact-spec/map.md index 437e5d8..5c6ba8f 100644 --- a/.scratch/kompact-spec/map.md +++ b/.scratch/kompact-spec/map.md @@ -16,22 +16,22 @@ A decided, implementable architecture spec for **Kompact**, the bit-packed, zero - [Wire-format bit order — LSB-first](issues/01-wire-format-bit-order.md): multi-bit ints assemble LSB-first (byte 0 = field bits 0–7, byte 1 = bits 8–15, bit 0 = value LSB); `Byte` must be masked `and 0xFF` before `shl`/`or` for identical JVM/Native results. Findings: [research/bit-order.md](research/bit-order.md). - [Code generation — KSP 2.3.9+](issues/02-generation-strategy.md): KSP emits whole `value class` source files into commonMain (deterministic, incremental, cacheable); K2 macros rejected as experimental. Generator emits complete declarations, never patches hand-written ones. Findings: [research/generation-strategy.md](research/generation-strategy.md). -- [Value-class representation — expect/actual](issues/03-value-class-representation.md): `expect value class` (no `@JvmInline`) in commonMain; `@JvmInline actual` in jvmMain; plain `actual` in iosArm64Main + iosSimulatorArm64Main. Zero-alloc only at direct non-nullable concrete scalar reads over a caller-owned `ByteArray`. Findings: [research/value-class-representation.md](research/value-class-representation.md). +- [Value-class representation — expect/actual](issues/03-value-class-representation.md): `expect value class` (no `@JvmInline`) in commonMain; `@JvmInline actual` in jvmMain; plain `actual` in `iosArm64Main` + `iosSimulatorArm64Main`. Zero-alloc only at direct non-nullable concrete scalar reads over a caller-owned `ByteArray`. Findings: [research/value-class-representation.md](research/value-class-representation.md). - [v1 type set](issues/04-v1-type-set.md): **unsigned ints 1–64; signed ints 1–64 (two's-complement on assembled magnitude); booleans (1 bit); enums as dense ordinal at a declared 1–8-bit width with unknown → typed error; IEEE-754 32- and 64-bit floats (canonicalized NaN); AND variable-length strings/blobs, nested composites, repeated fields — a deliberate scope expansion beyond `PROMPT.md`'s fixed-width sketch.** Implication: v1 now needs a framing contract. User-decided (grilling). +- [Framing — sequential length-delimited](issues/05-variable-length-framing.md): **fixed-width little-endian length prefix declared per field; length-delimited parse-forward nested sub-regions; count-prefixed sequential repeats.** Reads are sequential (parse-forward), not random-access — FlatBuffers-style offset-jump reads are rejected as incompatible with variable-length fields (ticket 04). Informed by 01+02+03+04. User-decided (grilling). **Reconciliation (02 + 03) — Generated view-class structure**: the generator emits an `expect value class` in commonMain plus `@JvmInline actual` (jvmMain) and plain `actual` (iosArm64Main, iosSimulatorArm64Main), all wrapping the same `ByteArray`. KSP produces the common `expect`; platform `actual`s require documented source-set wiring. ## Not yet specified -- **Variable-length / nested / repeated framing** → graduated to [ticket 05](issues/05-variable-length-framing.md) (`wayfinder:grilling`, open, unblocked). Length-prefix shape, nested base-offset vs absolute, repeated count-prefix vs length-delimited. Depends on 04. -- **Validation model** — compile-time (KSP) field-layout checks (overlap, width-sum) vs runtime; what `@KompactField(bitOffset, bitWidth)` validates. Informed by 02. -- **Write/builder interface** — "write values into the array" vs a separate writer; must now carry length-prefix / nested / repeat writes. Informed by 03 + 04 + 05. -- **Runtime error model** — `readBits` / `writeBits` on out-of-range width or short buffer: throw vs typed result; bounds contract. Now also covers unknown enum codes (04) and bad length prefixes (05). -- **Versioning & schema evolution** — reserved bits, layout identity, forward/backward compatibility over variable-length/nested framing. Informed by 04. +- **Validation model** → graduated to [ticket 06](issues/06-validation-model.md) (`wayfinder:grilling`, open, unblocked). Compile-time (KSP) field-layout checks vs runtime. Informed by 02+04+05. +- **Write/builder interface** — `PROMPT.md` §3 "writes values into the array" vs a separate writer; must now carry length-prefix / nested / repeat writes. Informed by 03 + 05. +- **Runtime error model** — `readBits` / `writeBits` on out-of-range width or short buffer: throw vs typed result; bounds contract. Now also covers unknown enum codes (04) and malformed length-prefix / nested errors (05). +- **Versioning & schema evolution** — reserved bits, layout identity, forward/backward compatibility over the sequential length-delimited framing (05). Informed by 04. - **Performance-evidence plan** — how the zero-allocation / zero-copy read claim is measured on Android + iOS (re-derived; reference doc ignored). - **Module split & publication** — single artifact vs runtime/annotations/processor/plugin split and KMP publication wiring (re-derived; reference doc ignored). -> **Ticket 04 (v1 type set) resolved** above — decided the full type set incl. variable-length + nested + repeated. Its scope decision graduates **ticket 05 (framing)** as the next frontier; the remainder is fog to graduate one at a time in a "work through the map" session. +> **Tickets 04 (type set) and 05 (framing) resolved** above. Ticket 05 graduates **ticket 06 (validation model)** as the next frontier. The remaining fog is graduated one at a time in a "work through the map" session. ## Out of scope From 687994133454dc1d154494a9dd67de080025418f Mon Sep 17 00:00:00 2001 From: Philipp Grosswiler Date: Tue, 1 Sep 2026 22:19:31 +0200 Subject: [PATCH 05/51] feat(plan): resolve validation model, seed write/builder ticket Work-through: resolve ticket 06 (KSP compile-time structural checks with symbol-located hard errors; KompactRuntime defensive bounds-checks return typed KompactDecodeResult, no throws on the hot path). Graduate ticket 07 (write/builder interface) as next frontier. Update map. --- .../issues/06-validation-model.md | 72 ++++++++++++++++--- .../issues/07-write-builder-interface.md | 25 +++++++ .scratch/kompact-spec/map.md | 10 +-- 3 files changed, 94 insertions(+), 13 deletions(-) create mode 100644 .scratch/kompact-spec/issues/07-write-builder-interface.md diff --git a/.scratch/kompact-spec/issues/06-validation-model.md b/.scratch/kompact-spec/issues/06-validation-model.md index d15c8f3..8d3d8c5 100644 --- a/.scratch/kompact-spec/issues/06-validation-model.md +++ b/.scratch/kompact-spec/issues/06-validation-model.md @@ -1,16 +1,72 @@ --- Type: grilling -Status: open -Labels: wayfinder:grilling -Blocked by: 02-generation-strategy (resolved), 04-v1-type-set (resolved), 05-variable-length-framing (resolved) +Status: resolved +Labels: + - scope:runtime + - scope:codegen + - kind:validation + - kind:error-model +Blocked by: + - "02 generation strategy" + - "04 v1 type set" + - "05 sequential framing" +Decides: + - "07 write/builder interface" + - "08 runtime error model" --- +# Ticket 06 — Validation model + ## Question -Now that generation (KSP), the type set, and framing are decided, where does field-layout validation live, and what does `@KompactField` actually validate? +Where does field-layout validation live in Kompact, and what fails (and how) when the schema or the wire input is malformed? Informed by tickets 02 (generation), 03 (zero-alloc value-class reads), 04 (type set), and 05 (framing). + +Three sub-questions: +1. **Split:** compile-time (KSP) structural checks + runtime bounds checks? or one or the other? +2. **Compile-time failure mode:** what happens when `KompactProcessor` sees a violating schema? +3. **Runtime hot-path failure mode:** what happens when `KompactRuntime` reads a short / out-of-bounds buffer? + +## Answer + +User decided: adopt the recommended option on all three forks. + +**1. Split — compile-time (KSP) structural + runtime typed-result bounds.** +- **`KompactProcessor` (compile-time)** validates structural/layout invariants during the KSP symbol-processing pass, before any value-class is generated. It builds an in-memory layout model of every `@Kompact`-annotated struct to compute bit offsets and then checks invariants (matrix below). These can never be checked at runtime, because they describe the *schema*, not the *buffer*. +- **`KompactRuntime` (runtime)** performs *only defensive buffer-bounds checks* on the read path — the invariants that are genuinely unknowable at compile time because they depend on the concrete `ByteArray` contents. + +This is the FlatBuffers model (validate the layout at build) paired with the Protobuf model (return a `Result`/typed error at decode). It is the only split consistent with every prior decision: KSP generation (02) makes compile-time validation possible; the zero-alloc value-class read contract (03) forbids throwing on the hot path; the type set (04) and framing (05) define exactly which invariants are structural versus buffer-bound. + +**Invariant matrix** + +| Invariant | Checked | Where | Error type (if reached) | +|---|---|---|---| +| Bit-offset overlaps within a struct | compile-time | `KompactProcessor` layout pass | hard error (build fails) | +| Per-struct bit-width sum ≤ declared width | compile-time | `KompactProcessor` layout pass | hard error | +| Length-prefix field width ∈ {8,16,32} | compile-time | `KompactProcessor` | hard error | +| Nested total-length ≤ declared length field capacity | compile-time | `KompactProcessor` layout pass (static bound) | hard error | +| Repeated element layout uniformity + count width ∈ {8,16,32} | compile-time | `KompactProcessor` layout pass | hard error | +| Enum width ≥ ordinal bit-width; declared codes fit | compile-time | `KompactProcessor` | hard error | +| Short buffer / read past end | runtime | `KompactRuntime` | `BoundsError` | +| Length-prefix > remaining bytes in region | runtime | `KompactRuntime` | `BadLengthPrefix` | +| Nested declared length < actual nested payload | runtime | `KompactRuntime` | `TruncatedNested` | +| Enum wire code outside known ordinals | runtime | `KompactRuntime` | `UnknownEnumCode` | + +**2. Compile-time failure mode — hard error, symbol-located, halt processing.** +Violations are reported via `KSPLogger.error(message, element)` attached to the offending `@KompactField`-annotated property (element = the KSP `KSDeclaration`/`KSPropertyDeclaration`), so the diagnostic points at the *declaration*, not an opaque offset. The processor returns a sentinel result from its round and halts generation for the offending symbol — the Gradle build fails until the schema is fixed. No warnings-as-proceed, because a "proceed" codegen would silently emit a structurally invalid reader the compiler could not otherwise catch. This matches ticket 02's KSP-diagnostic discipline (deterministic, symbol-located). + +**3. Runtime hot-path failure mode — typed result, never throw.** +`readBits` / `readBitsBoolean` return `KompactDecodeResult` — a value class over `(success: Boolean, value: T?, error: KompactDecodeError?)`, carrying either `success(value)` or `failure(error)`. The direct concrete read path (the one the 03 contract protects as zero-allocation) **never throws**: an out-of-bounds read is a value, not an exception. Throws allocate (stack trace capture) and would violate the zero-alloc read contract established in ticket 03. Optional checked wrappers (`readBitsOrThrow`) are provided for callers who prefer exceptions, but the direct view-class read API does not. + +**Tradeoff accepted.** Compile-time validation shifts all structural error detection to build time (better DX, fail-fast on the device developer) at the cost of `KompactProcessor` complexity — a dedicated `LayoutModel` validation pass that fully models the bit layout before emission. Runtime keeps only the buffer-bounds checks that are genuinely impossible to compute at compile time, and carries them as typed results (satisfying the 03 zero-allocation read contract). -1. **Compile-time vs runtime**: does the annotation processor validate layouts — bit-offset overlaps, per-struct width-sum, length-prefix bounds, nested sub-region consistency, repeated-count sanity — at compile time? Or is validation a runtime check in `KompactRuntime`? -2. **What is validated**: which invariants are checked (offset overlap, width-sum ≤ struct bit-length, length-prefix ≤ remaining buffer, nested total-length consistency, enum code within the declared width)? -3. **Failure mode**: compile-time violations are hard errors that halt processing with symbol-located diagnostics (matching the diagnostics discipline from the generation research); runtime validation yields a typed result per the error-model ticket. +**Consequences.** +- 08 runtime error model: ticket 06 resolves the headline fork — runtime failures are **typed results, not throws** — and fixes the set of runtime error types above. The *representation* of `KompactDecodeResult` and its composition (propagation, wrapping, error-detail fields) remain fog for ticket 08, informed by 06. +- 07 write/builder interface: now constrained — the writer's output must be structurally valid per the compile-time rules, so the writer *cannot produce* an overlapping-offset or width-overflow stream; the reader only bounds-checks. This removes whole classes of write-side bugs. +- 09 versioning & schema evolution: a length-prefix that exceeds remaining bytes now yields a typed `BadLengthPrefix` rather than a silent misread (06), so forward-compat on a skewed stream is safe-by-construction. +- `KompactProcessor` MUST implement a `LayoutModel` validation pass that computes every field's `[bitOffset, bitWidth)` and checks invariants 1–5 before emitting any `expect/actual value class`. (No partial emission on a malformed schema.) -This gates the processor's validation pass, the runtime error contract, and the conformance test surface. Resolve before the error-model ticket. +## References +- ticket 02 (KSP diagnostics discipline: deterministic, symbol-located) +- ticket 03 (zero-alloc value-class read contract) +- ticket 04 (type set: enum width, length-prefix widths) +- ticket 05 (framing: length-prefix, nested total-length, count-prefixed repeats) diff --git a/.scratch/kompact-spec/issues/07-write-builder-interface.md b/.scratch/kompact-spec/issues/07-write-builder-interface.md new file mode 100644 index 0000000..67c170b --- /dev/null +++ b/.scratch/kompact-spec/issues/07-write-builder-interface.md @@ -0,0 +1,25 @@ +--- +Type: grilling +Status: open +Labels: + - scope:api + - scope:codegen + - kind:serialization +Blocked by: + - "03 value-class representation" + - "05 sequential framing" + - "06 validation model" +--- + +# Ticket 07 — Write/builder interface + +## Question + +`PROMPT.md` §3 says the writer "writes values into the array" — the framing suggests mutating a caller-owned `ByteArray` directly. Now that ticket 05 established **sequential length-delimited framing** (fixed-width LE length prefix per field, parse-forward nested sub-regions, count-prefixed repeats) and ticket 06 established that **the writer cannot emit a structurally invalid stream** (compile-time validation covers structure; only buffer bounds are checked at runtime), how is the write/builder API shaped? + +- Is writing done by mutating a caller-owned `ByteArray`/`ByteBuffer` in place (mirroring the `readBits` read path), or by constructing an immutable in-memory tree that is then serialized? +- How are length-prefixed fields written under a **parse-forward** contract that has no random access — i.e. no backpatch into a forward-only buffer: reserve-and-fill (two passes), or buffer each nested payload then emit with its length, or build-then-serialize? +- How are nested composites and count-prefixed repeats framed on the write side to be byte-identical to what `readBits` (ticket 05) consumes? +- Does the builder mirror the generated value-class view API (symmetric read/write surface), and does it carry type-checked overloads for the v1 type set (ticket 04)? + +Informed by 03 (value-class representation / zero-alloc read contract), 05 (sequential framing), and 06 (validation: writer output is structurally valid by construction — the only runtime-checked condition on the reader side is buffer exhaustion). diff --git a/.scratch/kompact-spec/map.md b/.scratch/kompact-spec/map.md index 5c6ba8f..3bef996 100644 --- a/.scratch/kompact-spec/map.md +++ b/.scratch/kompact-spec/map.md @@ -19,19 +19,19 @@ A decided, implementable architecture spec for **Kompact**, the bit-packed, zero - [Value-class representation — expect/actual](issues/03-value-class-representation.md): `expect value class` (no `@JvmInline`) in commonMain; `@JvmInline actual` in jvmMain; plain `actual` in `iosArm64Main` + `iosSimulatorArm64Main`. Zero-alloc only at direct non-nullable concrete scalar reads over a caller-owned `ByteArray`. Findings: [research/value-class-representation.md](research/value-class-representation.md). - [v1 type set](issues/04-v1-type-set.md): **unsigned ints 1–64; signed ints 1–64 (two's-complement on assembled magnitude); booleans (1 bit); enums as dense ordinal at a declared 1–8-bit width with unknown → typed error; IEEE-754 32- and 64-bit floats (canonicalized NaN); AND variable-length strings/blobs, nested composites, repeated fields — a deliberate scope expansion beyond `PROMPT.md`'s fixed-width sketch.** Implication: v1 now needs a framing contract. User-decided (grilling). - [Framing — sequential length-delimited](issues/05-variable-length-framing.md): **fixed-width little-endian length prefix declared per field; length-delimited parse-forward nested sub-regions; count-prefixed sequential repeats.** Reads are sequential (parse-forward), not random-access — FlatBuffers-style offset-jump reads are rejected as incompatible with variable-length fields (ticket 04). Informed by 01+02+03+04. User-decided (grilling). +- [Validation model — compile-time + runtime bounds](issues/06-validation-model.md): **`KompactProcessor` validates structural/layout invariants at compile time (bit-offset overlaps, per-struct width-sum, length-prefix field width, nested total-length consistency, repeated-count sanity, enum code within width) via symbol-located hard errors that halt processing. `KompactRuntime` performs ONLY defensive buffer-bounds checks on the read path, returning a typed `KompactDecodeResult` / error — never throwing on the hot path (throws allocate, breaking 03).** Runtime-checked invariants that cannot be static: short buffer, length-prefix > remaining bytes, truncated nested, unknown enum code. User-decided (grilling). Informed by 02+03+04+05. **Reconciliation (02 + 03) — Generated view-class structure**: the generator emits an `expect value class` in commonMain plus `@JvmInline actual` (jvmMain) and plain `actual` (iosArm64Main, iosSimulatorArm64Main), all wrapping the same `ByteArray`. KSP produces the common `expect`; platform `actual`s require documented source-set wiring. ## Not yet specified -- **Validation model** → graduated to [ticket 06](issues/06-validation-model.md) (`wayfinder:grilling`, open, unblocked). Compile-time (KSP) field-layout checks vs runtime. Informed by 02+04+05. -- **Write/builder interface** — `PROMPT.md` §3 "writes values into the array" vs a separate writer; must now carry length-prefix / nested / repeat writes. Informed by 03 + 05. -- **Runtime error model** — `readBits` / `writeBits` on out-of-range width or short buffer: throw vs typed result; bounds contract. Now also covers unknown enum codes (04) and malformed length-prefix / nested errors (05). -- **Versioning & schema evolution** — reserved bits, layout identity, forward/backward compatibility over the sequential length-delimited framing (05). Informed by 04. +- **Write/builder interface** → graduated to [ticket 07](issues/07-write-builder-interface.md) (`wayfinder:grilling`, open, unblocked). `PROMPT` §3 "writes values into the array" vs a separate writer; must handle length-prefix / nested / repeat writes under parse-forward (no backpatch into a forward-only buffer). Constrained by 06: writer output is structurally valid by construction; the reader only bounds-checks. Informed by 03 + 05 + 06. +- **Runtime error model** — `KompactDecodeResult` representation + failure-detail fields (`BoundsError`/`BadLengthPrefix`/`TruncatedNested`/`UnknownEnumCode` taxonomy above is fixed by 06; the result type's composition/propagation remains fog here). Informed by 04 + 05 + 06. +- **Versioning & schema evolution** — reserved bits, layout identity, backward/forward compatibility over the sequential framing (05); a length-prefix > remaining bytes yields typed `BadLengthPrefix`, not a silent misread (06). Informed by 04 + 05 + 06. - **Performance-evidence plan** — how the zero-allocation / zero-copy read claim is measured on Android + iOS (re-derived; reference doc ignored). - **Module split & publication** — single artifact vs runtime/annotations/processor/plugin split and KMP publication wiring (re-derived; reference doc ignored). -> **Tickets 04 (type set) and 05 (framing) resolved** above. Ticket 05 graduates **ticket 06 (validation model)** as the next frontier. The remaining fog is graduated one at a time in a "work through the map" session. +> **Tickets 04 (type set), 05 (framing), and 06 (validation) resolved** above. Ticket 06 graduates **ticket 07 (write/builder interface)** as the next frontier. The remaining fog is graduated one at a time in a "work through the map" session. ## Out of scope From f309d7fe4aa94645a491c0bda2d7547f2be95bc5 Mon Sep 17 00:00:00 2001 From: Philipp Grosswiler Date: Tue, 1 Sep 2026 22:29:08 +0200 Subject: [PATCH 06/51] feat(plan): resolve write/builder interface, seed runtime error-model ticket Work-through: resolve ticket 07 (KompactWriter over writer-owned growable buffer with build(): ByteArray snapshot; sub-writer per nested with forward-only no-backpatch emit; typed write API mirroring reads; compile-time-validated widths from 06). Graduate ticket 08 (runtime error-model representation) as next frontier. Update map. --- .../issues/07-write-builder-interface.md | 34 ++++++++++++++++++- .../issues/08-runtime-error-model.md | 25 ++++++++++++++ .scratch/kompact-spec/map.md | 8 ++--- 3 files changed, 62 insertions(+), 5 deletions(-) create mode 100644 .scratch/kompact-spec/issues/08-runtime-error-model.md diff --git a/.scratch/kompact-spec/issues/07-write-builder-interface.md b/.scratch/kompact-spec/issues/07-write-builder-interface.md index 67c170b..a5947b6 100644 --- a/.scratch/kompact-spec/issues/07-write-builder-interface.md +++ b/.scratch/kompact-spec/issues/07-write-builder-interface.md @@ -1,6 +1,6 @@ --- Type: grilling -Status: open +Status: resolved Labels: - scope:api - scope:codegen @@ -9,6 +9,8 @@ Blocked by: - "03 value-class representation" - "05 sequential framing" - "06 validation model" +Decides: + - "08 runtime error model" --- # Ticket 07 — Write/builder interface @@ -23,3 +25,33 @@ Blocked by: - Does the builder mirror the generated value-class view API (symmetric read/write surface), and does it carry type-checked overloads for the v1 type set (ticket 04)? Informed by 03 (value-class representation / zero-alloc read contract), 05 (sequential framing), and 06 (validation: writer output is structurally valid by construction — the only runtime-checked condition on the reader side is buffer exhaustion). + +## Answer + +User decided: adopt the recommended option on both forks. + +**1. Surface — builder over a writer-owned growable buffer; `build(): ByteArray`.** +- `KompactWriter` is hand-written **common API** (no `@JvmInline`); it owns a growable internal byte buffer. Fields are appended sequentially, forward — the writer advances a cursor, never backtracks. +- `build(): ByteArray` snapshots the result. The reader then consumes that `ByteArray` via the ticket-03 caller-owned-`ByteArray` read path, so write → `ByteArray` → read is **symmetric**. +- `build()` returns a `ByteArray` (platform detail follows ticket-03 ABI rules: JVM may return the internal array directly or a defensive copy when shared; iOS copies across the ABI boundary so the consumer gets a Swift-value struct, not a Kotlin heap object). The 03 "boxing only at type-erasure / ABI boundaries" rule applies here, not on the scalar read hot path. +- Rejected: in-place mutation of a caller-owned, pre-sized `ByteArray` — length-delimited framing makes total size unknown until nested payloads are written, so pre-sizing forces the caller to do a size-computation pass and invites overflow. The writer owns its buffer. + +**2. Nested / repeat mechanism — sub-writer per nested; typed API mirroring reads.** +- A child `KompactWriter` builds each nested composite and each repeated element batch; the child's fully-computed length is then emitted as the **fixed-width LE prefix** declared for that field (width fixed at compile time by ticket 06) followed by the bytes. This is forward-only — **no backpatch** — because the length is known before the prefix slot is written. +- Count-prefixed repeats emit `…` (`count` = the field's validated count width; repeat the element writes). +- The writer API mirrors the read side: `writeInt1/8/16/32/64`, `writeUInt1/8/16/32/64` (two's-complement magnitude assembled per ticket 04), `writeBool`, `writeEnum(code, width)`, `writeString`/`writeBlob` (length-prefixed), `writeNested { w -> … }`, `writeRepeated(countWidth) { w -> … }`. Each typed write carries the field's compile-time-validated length-prefix width and value width — so a structurally-invalid stream is impossible to produce. +- Rejected: a thin `writeBits`/`writeBytes(len, bytes)` that lets the caller supply the length — it re-exposes raw length-prefix to the caller, re-introduces the structural-invalid-stream risk ticket 06 closed, and is asymmetric with the typed read path. + +**Tradeoff accepted.** The writer-owned growable buffer allocates during the build (amortized growth); `build()` may copy on iOS. This is the **write path**, which is explicitly *not* bound by ticket 03's zero-alloc read contract (that contract protects the read hot path only). Nested sub-writers add transient allocation proportional to nesting depth × payload — acceptable, single-pass, and backpatch-free. + +**Consequences.** +- The writer's output is **structurally valid by construction** (prefix widths fixed at compile time; nested lengths always computed before emission). The only runtime-checked condition the reader can hit on this stream is buffer exhaustion / bounds — exactly the `BoundsError`/`BadLengthPrefix`/`TruncatedNested`/`UnknownEnumCode` taxonomy ticket 06 reserved. No reader-side structural surprise. +- 08 runtime error model: informed — the writer never produces these errors; only readers see them on untrusted input. +- 09 versioning & schema evolution: informed by 07 (the writer picks each field's length-prefix width at codegen time; evolution = additive field IDs + reserved bits). +- Generation touchpoint: the writer is hand-written common API, **not** generated per struct; the generated value-class views (ticket 02/03) are read-only. Generating write-side views is a future 02-strategy follow-up, not 07. + +## References +- ticket 03 (zero-alloc `readBits` over a caller-owned `ByteArray`; ABI-boundary boxing) +- ticket 04 (v1 type set: ints/signed/enum widths/float NaN) +- ticket 05 (sequential length-delimited framing; no random access) +- ticket 06 (compile-time-validated length-prefix + value widths; symbol-located errors) diff --git a/.scratch/kompact-spec/issues/08-runtime-error-model.md b/.scratch/kompact-spec/issues/08-runtime-error-model.md new file mode 100644 index 0000000..e88622e --- /dev/null +++ b/.scratch/kompact-spec/issues/08-runtime-error-model.md @@ -0,0 +1,25 @@ +--- +Type: grilling +Status: open +Labels: + - scope:runtime + - kind:error-model +Blocked by: + - "06 validation model" +--- + +# Ticket 08 — Runtime error model (representation) + +## Question + +Ticket 06 fixed the **typed-result-not-throw** fork and the runtime error *types* (`BoundsError`, `BadLengthPrefix`, `TruncatedNested`, `UnknownEnumCode`). What remains is the **representation** of `KompactDecodeResult` — i.e. how those typed failures are carried on the ticket-03 zero-alloc read path. Informed by 06 + 05 (what readers can hit) + 04 (enum codes). + +How is `KompactDecodeResult` — and the `readBits`/`readBitsBoolean`/`readBool` surface from ticket 03 — shaped? + +- **Representation**: a flat sealed-class hierarchy (`KompactDecodeResult { data class Ok(T); sealed class Err : KompactDecodeResult }`) vs a value-class over `(ok: Boolean, value: T, error: DecodeError)`. Must not allocate on the success / fast-path (ticket 03 zero-alloc read contract). +- **Propagation across nested decodes**: fail-fast at the first bad length-prefix / nested / bounds (one error, short-circuits up), or collect multiple errors? FlatBuffers collects; Protobuf returns the first. The parse-forward reader (ticket 05) suggests fail-fast. +- **Error detail**: does `DecodeError` carry the byte/bit offset of failure for diagnostics — and if so, is the offset itself a non-allocating value class (ticket 03)? +- **Unknown enum code** (ticket 04): how does the typed result preserve the raw ordinal for recovery (e.g. `UnknownEnumCode(code: Int)` carrying the raw value) vs raising / dropping — without allocating? +- **Read API signature**: do `readBits` etc. return `KompactDecodeResult` directly, or `(value, error)` out-params / a throwing checked variant? + +Consequence for 07 (write/builder): the writer never produces these errors — only readers see them on untrusted input — so this model is **read-path only**. The decision here must not regress ticket 03's zero-allocation / zero-copy read contract. diff --git a/.scratch/kompact-spec/map.md b/.scratch/kompact-spec/map.md index 3bef996..c465098 100644 --- a/.scratch/kompact-spec/map.md +++ b/.scratch/kompact-spec/map.md @@ -20,18 +20,18 @@ A decided, implementable architecture spec for **Kompact**, the bit-packed, zero - [v1 type set](issues/04-v1-type-set.md): **unsigned ints 1–64; signed ints 1–64 (two's-complement on assembled magnitude); booleans (1 bit); enums as dense ordinal at a declared 1–8-bit width with unknown → typed error; IEEE-754 32- and 64-bit floats (canonicalized NaN); AND variable-length strings/blobs, nested composites, repeated fields — a deliberate scope expansion beyond `PROMPT.md`'s fixed-width sketch.** Implication: v1 now needs a framing contract. User-decided (grilling). - [Framing — sequential length-delimited](issues/05-variable-length-framing.md): **fixed-width little-endian length prefix declared per field; length-delimited parse-forward nested sub-regions; count-prefixed sequential repeats.** Reads are sequential (parse-forward), not random-access — FlatBuffers-style offset-jump reads are rejected as incompatible with variable-length fields (ticket 04). Informed by 01+02+03+04. User-decided (grilling). - [Validation model — compile-time + runtime bounds](issues/06-validation-model.md): **`KompactProcessor` validates structural/layout invariants at compile time (bit-offset overlaps, per-struct width-sum, length-prefix field width, nested total-length consistency, repeated-count sanity, enum code within width) via symbol-located hard errors that halt processing. `KompactRuntime` performs ONLY defensive buffer-bounds checks on the read path, returning a typed `KompactDecodeResult` / error — never throwing on the hot path (throws allocate, breaking 03).** Runtime-checked invariants that cannot be static: short buffer, length-prefix > remaining bytes, truncated nested, unknown enum code. User-decided (grilling). Informed by 02+03+04+05. +- [Write/builder interface — typed API, writer-owned buffer](issues/07-write-builder-interface.md): **`KompactWriter` (hand-written common API, no `@JvmInline`) owns a growable buffer; fields written forward; `build(): ByteArray` snapshot — symmetric write→ByteArray→read (the 03 caller-owned-ByteArray read path). Nested composites and repeats use a sub-writer: child length computed first, then emitted as fixed-width LE prefix + bytes (forward-only, no backpatch); count-prefixed repeats emit ``. Writer API mirrors reads (`writeInt/8/16/32/64`, `writeUInt`, `writeBool`, `writeString`, `writeBlob`, `writeEnum`, `writeNested{}`, `writeRepeated(n){}`) and carries each field's compile-time-validated length-prefix width (06).** Output structurally valid by construction; writer is not bound by 03's zero-alloc read contract (write path allocates, read path does not). Informed by 03 + 05 + 06. User-decided (grilling). **Reconciliation (02 + 03) — Generated view-class structure**: the generator emits an `expect value class` in commonMain plus `@JvmInline actual` (jvmMain) and plain `actual` (iosArm64Main, iosSimulatorArm64Main), all wrapping the same `ByteArray`. KSP produces the common `expect`; platform `actual`s require documented source-set wiring. ## Not yet specified -- **Write/builder interface** → graduated to [ticket 07](issues/07-write-builder-interface.md) (`wayfinder:grilling`, open, unblocked). `PROMPT` §3 "writes values into the array" vs a separate writer; must handle length-prefix / nested / repeat writes under parse-forward (no backpatch into a forward-only buffer). Constrained by 06: writer output is structurally valid by construction; the reader only bounds-checks. Informed by 03 + 05 + 06. -- **Runtime error model** — `KompactDecodeResult` representation + failure-detail fields (`BoundsError`/`BadLengthPrefix`/`TruncatedNested`/`UnknownEnumCode` taxonomy above is fixed by 06; the result type's composition/propagation remains fog here). Informed by 04 + 05 + 06. -- **Versioning & schema evolution** — reserved bits, layout identity, backward/forward compatibility over the sequential framing (05); a length-prefix > remaining bytes yields typed `BadLengthPrefix`, not a silent misread (06). Informed by 04 + 05 + 06. +- **Runtime error model (representation)** → graduated to [ticket 08](issues/08-runtime-error-model.md) (`wayfinder:grilling`, open, unblocked). 06 fixed the error *types* + typed-result-not-throw fork; 08 decides the `KompactDecodeResult` representation, propagation (fail-fast), error offsets, and `UnknownEnumCode` raw-code preservation on the zero-alloc read path. Informed by 04 + 05 + 06. +- **Versioning & schema evolution** — reserved bits, layout identity, backward/forward compatibility over the sequential framing (05); writer picks length-prefix widths per field at codegen (07). Informed by 04 + 05 + 06 + 07. - **Performance-evidence plan** — how the zero-allocation / zero-copy read claim is measured on Android + iOS (re-derived; reference doc ignored). - **Module split & publication** — single artifact vs runtime/annotations/processor/plugin split and KMP publication wiring (re-derived; reference doc ignored). -> **Tickets 04 (type set), 05 (framing), and 06 (validation) resolved** above. Ticket 06 graduates **ticket 07 (write/builder interface)** as the next frontier. The remaining fog is graduated one at a time in a "work through the map" session. +> **Tickets 04 (type set), 05 (framing), 06 (validation), and 07 (write/builder) resolved** above. Ticket 07 graduates **ticket 08 (runtime error-model representation)** as the next frontier. The remaining fog is graduated one at a time in a "work through the map" session. ## Out of scope From a46577bdd722ab07dcd6f5444c18e9f8c521494f Mon Sep 17 00:00:00 2001 From: Philipp Grosswiler Date: Tue, 1 Sep 2026 22:37:49 +0200 Subject: [PATCH 07/51] feat(plan): resolve runtime error model, seed versioning ticket Work-through: resolve ticket 08 (specialized zero-alloc result value classes ByteResult/IntResult/etc over a packed Long; public checked reads never throw and return typed results; fail-fast; byte offset only on opt-in diagnostics path). Graduate ticket 09 (versioning and schema evolution) as next frontier. Update map. Informed by tickets 03-07. --- .../issues/08-runtime-error-model.md | 61 ++++++++++++++++--- .../issues/09-versioning-schema-evolution.md | 26 ++++++++ .scratch/kompact-spec/map.md | 8 +-- 3 files changed, 82 insertions(+), 13 deletions(-) create mode 100644 .scratch/kompact-spec/issues/09-versioning-schema-evolution.md diff --git a/.scratch/kompact-spec/issues/08-runtime-error-model.md b/.scratch/kompact-spec/issues/08-runtime-error-model.md index e88622e..70f692d 100644 --- a/.scratch/kompact-spec/issues/08-runtime-error-model.md +++ b/.scratch/kompact-spec/issues/08-runtime-error-model.md @@ -1,25 +1,68 @@ --- Type: grilling -Status: open +Status: resolved Labels: - scope:runtime - kind:error-model Blocked by: - "06 validation model" +Decides: + - "09 versioning & schema evolution" --- # Ticket 08 — Runtime error model (representation) ## Question -Ticket 06 fixed the **typed-result-not-throw** fork and the runtime error *types* (`BoundsError`, `BadLengthPrefix`, `TruncatedNested`, `UnknownEnumCode`). What remains is the **representation** of `KompactDecodeResult` — i.e. how those typed failures are carried on the ticket-03 zero-alloc read path. Informed by 06 + 05 (what readers can hit) + 04 (enum codes). +Ticket 06 fixed the **typed-result-not-throw** fork and the runtime error *types* (`BoundsError`, `BadLengthPrefix`, `TruncatedNested`, `UnknownEnumCode`). What remained was the **representation** of `KompactDecodeResult` — i.e. how those typed failures are carried on the ticket-03 zero-alloc read path. Informed by 06 + 05 (what readers can hit) + 04 (enum codes). -How is `KompactDecodeResult` — and the `readBits`/`readBitsBoolean`/`readBool` surface from ticket 03 — shaped? +How is `KompactDecodeResult` — and the `readBits` / `readBitsBoolean` / `readBool` surface from ticket 03 — shaped? -- **Representation**: a flat sealed-class hierarchy (`KompactDecodeResult { data class Ok(T); sealed class Err : KompactDecodeResult }`) vs a value-class over `(ok: Boolean, value: T, error: DecodeError)`. Must not allocate on the success / fast-path (ticket 03 zero-alloc read contract). -- **Propagation across nested decodes**: fail-fast at the first bad length-prefix / nested / bounds (one error, short-circuits up), or collect multiple errors? FlatBuffers collects; Protobuf returns the first. The parse-forward reader (ticket 05) suggests fail-fast. -- **Error detail**: does `DecodeError` carry the byte/bit offset of failure for diagnostics — and if so, is the offset itself a non-allocating value class (ticket 03)? -- **Unknown enum code** (ticket 04): how does the typed result preserve the raw ordinal for recovery (e.g. `UnknownEnumCode(code: Int)` carrying the raw value) vs raising / dropping — without allocating? -- **Read API signature**: do `readBits` etc. return `KompactDecodeResult` directly, or `(value, error)` out-params / a throwing checked variant? +- **Representation**: a flat sealed-class hierarchy vs a value-class over `(ok, value, error)`. Must not allocate on the success / fast-path (ticket 03 zero-alloc read contract). +- **Propagation across nested decodes**: fail-fast at the first bad length-prefix / nested / bounds, or collect multiple errors? +- **Error detail**: does the error carry the byte/bit offset of failure — and if so, is the offset non-allocating (ticket 03)? +- **Unknown enum code** (ticket 04): how is the raw ordinal preserved for recovery without allocating? +- **Read API signature**: do the read functions return a typed result directly, an out-param, or a throwing checked variant? -Consequence for 07 (write/builder): the writer never produces these errors — only readers see them on untrusted input — so this model is **read-path only**. The decision here must not regress ticket 03's zero-allocation / zero-copy read contract. +Consequence for 07 (write/builder): the writer never produces these errors — only readers see them on untrusted input — so this model is **read-path only**. The decision must not regress ticket 03's zero-allocation / zero-copy read contract. + +## Answer + +User decided: adopt the recommended option on both forks. + +**Representation & public read contract — specialized per-type result value classes.** + +There is no single generic `KompactDecodeResult`. Each scalar kind has its own result value class — `ByteResult`, `ShortResult`, `IntResult`, `LongResult`, `FloatResult`, `DoubleResult`, `BooleanResult` — declared as `expect value class` in commonMain (**no `@JvmInline`** per the §1 rule) with `@JvmInline actual` on the JVM and a plain `actual` on `iosArm64` / `iosSimulatorArm64` (ticket 03 representation rule). Each wraps a single `Long` that packs: the value bits + an ok-flag + a compact error-code (+ the raw enum code, when the kind is an enum). + +On the JVM, `@JvmInline` over a primitive `Long` is zero-alloc on **both** success and failure (the Long is stored inline); on Kotlin/Native, a value class over a primitive `Long` is likewise zero-alloc (inline value). Therefore the public checked accessor `readInt8(): ByteResult` is zero-alloc on the success hot path (satisfies **03**), is a typed result (satisfies **06**), and **never throws**. + +- The low-level `readBits(offset, bitWidth): Int` / `readBitsBoolean(offset): Boolean` remain the raw zero-alloc scalar primitives (ticket 03's "direct concrete scalar reads") — used by the generated view accessors and perf-critical inner loops, with the caller responsible for bounds (which ticket 06's validated layout guarantees for in-format reads). They are the primitive *under* the checked accessors, not the public error-safety boundary. +- A checked accessor (`readInt8(): ByteResult`) bounds-checks first; on success it reads via `readBits` and returns `ByteResult.success(value)` (zero-alloc); on a `BoundsError`/`BadLengthPrefix` it returns `ByteResult.failure(error)` (still a packed-Long, zero-alloc). It never throws. +- `BooleanResult` / `ByteResult` / enum results additionally pack the raw code in the Long bits → `UnknownEnumCode(code)` from ticket 04 is preserved without allocation. +- JVM Java interop: a checked `readInt8OrThrow()`-style wrapper is provided (Java callers see the result class); the common / public Kotlin API is the typed result value class. + +Rejected: +- A generic `KompactDecodeResult` (sealed class *or* `Result`) over a boxed scalar — allocates on the success path (the JVM boxes the primitive), violating ticket 03's zero-alloc read contract. +- Throwing reads (`throw` on out-of-bounds/malformed) — allocates the exception object and violates ticket 06's "never throw on the read path." + +**Propagation + error detail — fail-fast; no byte-offset on the fast path.** + +- **Fail-fast**: the checked accessor short-circuits at the first bad length-prefix / nested / bounds / enum code — parse-forward friendly (ticket 05) and matching ticket 06's "only buffer-bounds checks, typed result." +- **Error carried as a compact code** in the result `Long` (zero-alloc) — enough to discriminate `BoundsError` / `BadLengthPrefix` / `TruncatedNested` / `UnknownEnumCode` on the fast path. +- **Byte/bit offset is NOT stored on the fast path.** A 64-bit `Long` cannot also hold the value + ok-flag + a 32-bit offset for 32/64-bit scalar results without allocating, and a uniform result shape keeps the hot path zero-alloc. Full diagnostic detail (byte offset, error kind, raw enum code, offending-field id) is available only on an **explicit opt-in checked diagnostics path** — e.g. `decodeFull(): DetailedResult` carrying a `DecodeError(value, offset, kind, rawCode)` allocated only on the rare failure path — and on the generated view's `at(offset)` debug accessor. So 03's zero-alloc guarantee is preserved for the common scalar read, and rich diagnostics remain available when needed. + +Rejected: "collect multiple errors + always carry a full offset on the common result" — requires a boxed/sealed result representation and allocates on the failure path, violating ticket 03's zero-alloc read contract. + +**Tradeoff accepted.** Eight small specialized result value classes (vs one generic `Result`) is the price of stacking three obligations on the same read path: zero-alloc (03) **and** typed-result (06) **and** never-throws. On the failure path, the compact-code result trades a stored byte offset for zero-allocation; the offset is recoverable from the opt-in diagnostics path. This is the deliberate, documented cost of a zero-alloc, never-throwing, typed read API. + +**Consequences.** +- 09 versioning & schema evolution: now the only remaining read-side concern — a length-prefix / bounds violation surfaces as a fail-fast typed `BadLengthPrefix` / `BoundsError` (06 + 08), never a silent misread, so evolution can trust the framing's length integrity. Informed by 03+04+05+06+07+08. +- 07 write/builder: confirmed read-path-only — the writer never produces these errors. Already resolved. +- 05 framing: `readBits` is the parse-forward cursor; the checked accessors layer the zero-alloc typed results over it. + +## References +- ticket 03 (zero-alloc `readBits` over a caller-owned `ByteArray`; value-class representation) +- ticket 04 (enum code width; `UnknownEnumCode` raw code) +- ticket 05 (parse-forward sequential framing) +- ticket 06 (runtime error types; typed-result-not-throw; fail-fast bounds) +- ticket 07 (writer selects length-prefix widths at codegen) diff --git a/.scratch/kompact-spec/issues/09-versioning-schema-evolution.md b/.scratch/kompact-spec/issues/09-versioning-schema-evolution.md new file mode 100644 index 0000000..43fd212 --- /dev/null +++ b/.scratch/kompact-spec/issues/09-versioning-schema-evolution.md @@ -0,0 +1,26 @@ +--- +Type: grilling +Status: open +Labels: + - scope:wire-format + - kind:compatibility +Blocked by: + - "05 sequential framing" + - "06 validation model" + - "07 write/builder interface" + - "08 runtime error model" +--- + +# Ticket 09 — Versioning & schema evolution + +## Question + +Kompact's wire format is bit-packed, sequential, length-delimited, and **tagless** (05 + 06 + 08) — no per-field wire tags, because tags would break the compactness the framing commits to. How does schema evolution / forward-backward compatibility actually work? + +The reader walks fields in declaration order via the `readBits…` surface (08). To skip a field it does **not** recognize, it must know how many bits to advance — which a length-prefix gives **only if the prefix width is known up front**. This is the crux of evolution over a tagless format. + +Decide: +- **Skip / evolution model**: positional + additive-only with a **uniform** length-prefix width (any unknown trailing length-delimited field is skipped by consuming its prefix + payload; fixed-width scalar additions are *not* skippable and are therefore breaking) vs no forward compatibility (version each stream, migrate) vs per-field TLV tags (Protobuf-style — rejected, it breaks 05's compactness). +- **Version signaling**: a top-level fixed-width version prefix at stream start (fail-fast on an unknown version, per 06+08) vs a reserved-bits flag embedded in the first field. + +Inherited constraints: 07 (the writer selects each field's length-prefix width at codegen — for skip to work, every length-delimited field must share one uniform prefix width); 08 (reads are typed results, never throw — so a skew yields a typed `BadLengthPrefix` / `UnknownSchemaVersion`, never a silent misread). diff --git a/.scratch/kompact-spec/map.md b/.scratch/kompact-spec/map.md index c465098..71f8dea 100644 --- a/.scratch/kompact-spec/map.md +++ b/.scratch/kompact-spec/map.md @@ -19,19 +19,19 @@ A decided, implementable architecture spec for **Kompact**, the bit-packed, zero - [Value-class representation — expect/actual](issues/03-value-class-representation.md): `expect value class` (no `@JvmInline`) in commonMain; `@JvmInline actual` in jvmMain; plain `actual` in `iosArm64Main` + `iosSimulatorArm64Main`. Zero-alloc only at direct non-nullable concrete scalar reads over a caller-owned `ByteArray`. Findings: [research/value-class-representation.md](research/value-class-representation.md). - [v1 type set](issues/04-v1-type-set.md): **unsigned ints 1–64; signed ints 1–64 (two's-complement on assembled magnitude); booleans (1 bit); enums as dense ordinal at a declared 1–8-bit width with unknown → typed error; IEEE-754 32- and 64-bit floats (canonicalized NaN); AND variable-length strings/blobs, nested composites, repeated fields — a deliberate scope expansion beyond `PROMPT.md`'s fixed-width sketch.** Implication: v1 now needs a framing contract. User-decided (grilling). - [Framing — sequential length-delimited](issues/05-variable-length-framing.md): **fixed-width little-endian length prefix declared per field; length-delimited parse-forward nested sub-regions; count-prefixed sequential repeats.** Reads are sequential (parse-forward), not random-access — FlatBuffers-style offset-jump reads are rejected as incompatible with variable-length fields (ticket 04). Informed by 01+02+03+04. User-decided (grilling). -- [Validation model — compile-time + runtime bounds](issues/06-validation-model.md): **`KompactProcessor` validates structural/layout invariants at compile time (bit-offset overlaps, per-struct width-sum, length-prefix field width, nested total-length consistency, repeated-count sanity, enum code within width) via symbol-located hard errors that halt processing. `KompactRuntime` performs ONLY defensive buffer-bounds checks on the read path, returning a typed `KompactDecodeResult` / error — never throwing on the hot path (throws allocate, breaking 03).** Runtime-checked invariants that cannot be static: short buffer, length-prefix > remaining bytes, truncated nested, unknown enum code. User-decided (grilling). Informed by 02+03+04+05. +- [Validation model — compile-time + runtime bounds](issues/06-validation-model.md): **`KompactProcessor` validates structural/layout invariants at compile time (bit-offset overlaps, per-struct width-sum, length-prefix field width, nested total-length consistency, repeated-count sanity, enum code within width) via symbol-located hard errors that halt processing. `KompactRuntime` performs ONLY defensive buffer-bounds checks on the read path, returning a typed result — never throwing on the hot path (throws allocate, breaking 03).** Runtime-checked invariants that cannot be static: short buffer, length-prefix > remaining bytes, truncated nested, unknown enum code. User-decided (grilling). Informed by 02+03+04+05. - [Write/builder interface — typed API, writer-owned buffer](issues/07-write-builder-interface.md): **`KompactWriter` (hand-written common API, no `@JvmInline`) owns a growable buffer; fields written forward; `build(): ByteArray` snapshot — symmetric write→ByteArray→read (the 03 caller-owned-ByteArray read path). Nested composites and repeats use a sub-writer: child length computed first, then emitted as fixed-width LE prefix + bytes (forward-only, no backpatch); count-prefixed repeats emit ``. Writer API mirrors reads (`writeInt/8/16/32/64`, `writeUInt`, `writeBool`, `writeString`, `writeBlob`, `writeEnum`, `writeNested{}`, `writeRepeated(n){}`) and carries each field's compile-time-validated length-prefix width (06).** Output structurally valid by construction; writer is not bound by 03's zero-alloc read contract (write path allocates, read path does not). Informed by 03 + 05 + 06. User-decided (grilling). +- [Runtime error model — specialized zero-alloc result value classes](issues/08-runtime-error-model.md): **No generic `KompactDecodeResult` — each scalar kind has a specialized `expect/actual value class` (`ByteResult`, `IntResult`, …, `BooleanResult`) wrapping a single packed `Long` (value + ok + compact error-code + raw enum code), `@JvmInline actual` on JVM / plain `actual` on iOS → zero-alloc on both success and failure. Public checked reads (`readInt8(): ByteResult`) bounds-check then read via the zero-alloc `readBits` primitive — typed result, never throw (03 + 06). Fail-fast propagation; byte offset NOT on the fast path (offsets only on an opt-in `decodeFull()` diagnostics path, allocated only on failure).** Informed by 03 + 04 + 05 + 06 + 07. User-decided (grilling). **Reconciliation (02 + 03) — Generated view-class structure**: the generator emits an `expect value class` in commonMain plus `@JvmInline actual` (jvmMain) and plain `actual` (iosArm64Main, iosSimulatorArm64Main), all wrapping the same `ByteArray`. KSP produces the common `expect`; platform `actual`s require documented source-set wiring. ## Not yet specified -- **Runtime error model (representation)** → graduated to [ticket 08](issues/08-runtime-error-model.md) (`wayfinder:grilling`, open, unblocked). 06 fixed the error *types* + typed-result-not-throw fork; 08 decides the `KompactDecodeResult` representation, propagation (fail-fast), error offsets, and `UnknownEnumCode` raw-code preservation on the zero-alloc read path. Informed by 04 + 05 + 06. -- **Versioning & schema evolution** — reserved bits, layout identity, backward/forward compatibility over the sequential framing (05); writer picks length-prefix widths per field at codegen (07). Informed by 04 + 05 + 06 + 07. +- **Versioning & schema evolution** → graduated to [ticket 09](issues/09-versioning-schema-evolution.md) (`wayfinder:grilling`, open, unblocked). Skip/evolution model over a tagless, bit-packed, length-delimited format: positional + additive-only + uniform length-prefix width (skip unknown trailing length-delimited fields) vs no forward-compat vs TLV tags. Version signaling: top-level version prefix vs reserved bits. Informed by 05 + 06 + 07 + 08. - **Performance-evidence plan** — how the zero-allocation / zero-copy read claim is measured on Android + iOS (re-derived; reference doc ignored). - **Module split & publication** — single artifact vs runtime/annotations/processor/plugin split and KMP publication wiring (re-derived; reference doc ignored). -> **Tickets 04 (type set), 05 (framing), 06 (validation), and 07 (write/builder) resolved** above. Ticket 07 graduates **ticket 08 (runtime error-model representation)** as the next frontier. The remaining fog is graduated one at a time in a "work through the map" session. +> **Tickets 04 (type set), 05 (framing), 06 (validation), 07 (write/builder), and 08 (runtime error model) resolved** above. Ticket 08 graduates **ticket 09 (versioning & schema evolution)** as the next frontier. The remaining fog is graduated one at a time in a "work through the map" session. ## Out of scope From 88d16a1fe531ba960fee78cacf01eb53b5c28491 Mon Sep 17 00:00:00 2001 From: Philipp Grosswiler Date: Tue, 1 Sep 2026 22:46:24 +0200 Subject: [PATCH 08/51] feat(plan): resolve versioning model, seed testing-model ticket Work-through: resolve ticket 09 (positional additive evolution with uniform length-prefix width + top-level version prefix; fail-fast typed BadLengthPrefix/UnsupportedSchemaVersion on skew). Graduate ticket 10 (cross-platform testing model) as next frontier. Update map. Informed by 05-08. --- .../issues/09-versioning-schema-evolution.md | 40 ++++++++++++++++++- .../issues/10-cross-platform-testing-model.md | 25 ++++++++++++ .scratch/kompact-spec/map.md | 9 +++-- 3 files changed, 69 insertions(+), 5 deletions(-) create mode 100644 .scratch/kompact-spec/issues/10-cross-platform-testing-model.md diff --git a/.scratch/kompact-spec/issues/09-versioning-schema-evolution.md b/.scratch/kompact-spec/issues/09-versioning-schema-evolution.md index 43fd212..1ac0db3 100644 --- a/.scratch/kompact-spec/issues/09-versioning-schema-evolution.md +++ b/.scratch/kompact-spec/issues/09-versioning-schema-evolution.md @@ -1,6 +1,6 @@ --- Type: grilling -Status: open +Status: resolved Labels: - scope:wire-format - kind:compatibility @@ -9,6 +9,8 @@ Blocked by: - "06 validation model" - "07 write/builder interface" - "08 runtime error model" +Decides: + - "10 cross-platform testing model" --- # Ticket 09 — Versioning & schema evolution @@ -24,3 +26,39 @@ Decide: - **Version signaling**: a top-level fixed-width version prefix at stream start (fail-fast on an unknown version, per 06+08) vs a reserved-bits flag embedded in the first field. Inherited constraints: 07 (the writer selects each field's length-prefix width at codegen — for skip to work, every length-delimited field must share one uniform prefix width); 08 (reads are typed results, never throw — so a skew yields a typed `BadLengthPrefix` / `UnknownSchemaVersion`, never a silent misread). + +## Answer + +User decided: adopt the recommended option on both forks. + +**1. Skip / evolution model — positional + additive-only, uniform length-prefix width.** + +Fields are read in declaration order (ticket 05); the wire is a flat sequence, not TLV. For an older reader to **skip** a field it does not recognize, it must consume `prefix + payload` — which works only if the prefix **width** is known without consulting the schema of the unknown field. Therefore **all length-delimited fields in a stream share one uniform prefix width** (e.g. 16-bit LE, chosen once per stream/struct at codegen by the writer, ticket 07). Then: + +- **Forward compatibility** (older reader, newer stream): unknown *trailing* length-delimited fields are skipped by reading the uniform-width prefix + payload. (Fixed-width scalar fields cannot be added backward-compatibly — an older reader can't size an unknown fixed-width field — so appending a fixed-width field is a **breaking** change.) +- **Backward compatibility** (newer reader, older stream): fewer fields present → missing trailing fields are read as their **declared default value**. +- **Breaking changes** (documented in the spec's evolution section): reorder fields, insert a fixed-width scalar field, change a field's bit-width, or change the stream's uniform prefix width. Adding a length-delimited field at the end is non-breaking. +- **Skew is fail-fast, never silent** (06 + 08): a length-prefix that exceeds remaining bytes is a typed `BadLengthPrefix`; an unsupported stream version is a typed `UnsupportedSchemaVersion`. No silent truncation / misread. + +Rejected: +- "No forward compatibility — migrate each stream version." The length-delimited framing already enables skip via uniform prefixes; migration-only is weak for a framework and discards the framing's natural skip. +- "Per-field TLV tags (Protobuf-style)." A tag per field breaks the compactness (05) the bit-packed format commits to. + +**2. Version signaling — top-level fixed-width version prefix at stream start.** + +The stream begins with a fixed-width (e.g. 16-bit LE) version number. The reader checks it first; an unknown version → fail-fast typed `UnsupportedSchemaVersion` (06 + 08), never a silent decode. The version prefix is decoupled from any field layout, so it is stable across schema evolution. + +Rejected: a reserved-bits flag in the first field — couples version detection to field 0's layout, so any change to field 0 breaks version detection. + +**Tradeoff accepted.** The **uniform length-prefix width** is a real restriction: you cannot mix 8-bit prefixes for short fields with 16-bit prefixes for long fields if you want forward-compat skip. The more-compact alternative (heterogeneous prefix widths) is forbidden by the combination of 05 (compactness) + forward-compatibility. This is the necessary bridge between Kompact's compactness and its evolvability — a deliberate, documented constraint. The version prefix costs a fixed 2 bytes per stream (or 1, if 8-bit is chosen). + +**Consequences.** +- 10 cross-platform testing model: the compatibility matrix **must** exercise both directions of the additive model — newer-writer/old-reader (trailing-field skip) and old-writer/newer-reader (defaults for missing fields) — plus version-skew (`UnsupportedSchemaVersion`) and malformed-prefix (`BadLengthPrefix`) paths (06 + 08). This is the correctness surface the testing model (10) locks. +- The spec's evolution section will enumerate the breaking-change rules above so downstream authors can evolve without silent breakage. +- 11 (performance-evidence) and 12 (module split) are unaffected by this decision (wire-format level). + +## References +- ticket 05 (sequential length-delimited framing; parse-forward) +- ticket 06 (fail-fast typed errors on bad length-prefix / bounds) +- ticket 07 (writer selects length-prefix widths at codegen — must be uniform) +- ticket 08 (typed results, never throw) diff --git a/.scratch/kompact-spec/issues/10-cross-platform-testing-model.md b/.scratch/kompact-spec/issues/10-cross-platform-testing-model.md new file mode 100644 index 0000000..7eb8fa0 --- /dev/null +++ b/.scratch/kompact-spec/issues/10-cross-platform-testing-model.md @@ -0,0 +1,25 @@ +--- +Type: grilling +Status: open +Labels: + - scope:testing + - scope:perf + - kind:verification +Blocked by: + - "03 value-class representation" + - "08 runtime error model" + - "09 versioning & schema evolution" +--- + +# Ticket 10 — Cross-platform testing model + +## Question + +The Destination (map §Destination) requires the spec to lock **"the cross-platform testing model"** — currently untracked in the fog (gap). Kompact must be verified end-to-end on Android/JVM + iOS as Kotlin/Native (`iosArm64`, `iosSimulatorArm64`) per ticket 03's platforms, and the ticket-03 **zero-alloc / zero-copy read** claim must be substantiated, not merely asserted. How is the testing model shaped? Informed by 01–09. + +Decide: +- **Test categories**: unit round-trip (struct → wire → decode → equal, per platform); property-based (random struct → wire → decode → equality; fuzzed lengths, empty/nested/repeated edge cases); cross-version compatibility matrix (09: newer-writer/old-reader skip of trailing fields, old-writer/newer-reader defaults, version-skew `UnsupportedSchemaVersion`, malformed-prefix `BadLengthPrefix`/`TruncatedNested`); and a zero-alloc assertion test on the `readBits`-style scalar read path. +- **Zero-alloc substantiation**: how is the ticket-03 zero-alloc claim *measured and asserted* — what tool, what metric, and is it a CI gate (a test that fails the build on regression) or only documentation? JVM candidate: allocation profiling (e.g. JMH + allocation profiling, or `-XX:+PrintGCDetails`/async-profiler alloc counter) asserting 0 allocations on a scalar read. iOS candidate: Allocations instrument / malloc-zone tracking asserting 0 allocations on the read path. The alloc counter itself is `expect`/`actual` per ticket-03's representation rule. +- **Cross-platform harness**: shared `commonTest` (KMP) run on JVM + iosArm64 + iosSimulatorArm64; platform-specific measurement glue as `expect`/`actual` per ticket 03. Does the KMP test ABI get locked (e.g. via `binary-compatibility-validator`)? + +Consequence: 10 sets what counts as "verified" for the spec. The detailed measurement *tooling* (exact profiler flags, benchmark harness schema) is a follow-on research subagent → **performance-evidence plan (ticket 11)**. Module split (12) decides whether tests ship in the published artifact. diff --git a/.scratch/kompact-spec/map.md b/.scratch/kompact-spec/map.md index 71f8dea..96a2831 100644 --- a/.scratch/kompact-spec/map.md +++ b/.scratch/kompact-spec/map.md @@ -22,16 +22,17 @@ A decided, implementable architecture spec for **Kompact**, the bit-packed, zero - [Validation model — compile-time + runtime bounds](issues/06-validation-model.md): **`KompactProcessor` validates structural/layout invariants at compile time (bit-offset overlaps, per-struct width-sum, length-prefix field width, nested total-length consistency, repeated-count sanity, enum code within width) via symbol-located hard errors that halt processing. `KompactRuntime` performs ONLY defensive buffer-bounds checks on the read path, returning a typed result — never throwing on the hot path (throws allocate, breaking 03).** Runtime-checked invariants that cannot be static: short buffer, length-prefix > remaining bytes, truncated nested, unknown enum code. User-decided (grilling). Informed by 02+03+04+05. - [Write/builder interface — typed API, writer-owned buffer](issues/07-write-builder-interface.md): **`KompactWriter` (hand-written common API, no `@JvmInline`) owns a growable buffer; fields written forward; `build(): ByteArray` snapshot — symmetric write→ByteArray→read (the 03 caller-owned-ByteArray read path). Nested composites and repeats use a sub-writer: child length computed first, then emitted as fixed-width LE prefix + bytes (forward-only, no backpatch); count-prefixed repeats emit ``. Writer API mirrors reads (`writeInt/8/16/32/64`, `writeUInt`, `writeBool`, `writeString`, `writeBlob`, `writeEnum`, `writeNested{}`, `writeRepeated(n){}`) and carries each field's compile-time-validated length-prefix width (06).** Output structurally valid by construction; writer is not bound by 03's zero-alloc read contract (write path allocates, read path does not). Informed by 03 + 05 + 06. User-decided (grilling). - [Runtime error model — specialized zero-alloc result value classes](issues/08-runtime-error-model.md): **No generic `KompactDecodeResult` — each scalar kind has a specialized `expect/actual value class` (`ByteResult`, `IntResult`, …, `BooleanResult`) wrapping a single packed `Long` (value + ok + compact error-code + raw enum code), `@JvmInline actual` on JVM / plain `actual` on iOS → zero-alloc on both success and failure. Public checked reads (`readInt8(): ByteResult`) bounds-check then read via the zero-alloc `readBits` primitive — typed result, never throw (03 + 06). Fail-fast propagation; byte offset NOT on the fast path (offsets only on an opt-in `decodeFull()` diagnostics path, allocated only on failure).** Informed by 03 + 04 + 05 + 06 + 07. User-decided (grilling). +- [Versioning & schema evolution — positional additive, uniform prefix, version prefix](issues/09-versioning-schema-evolution.md): **Positional + additive-only schema evolution: all length-delimited fields share ONE uniform length-prefix width (so an older reader skips unknown trailing length-delimited fields by reading uniform-width prefix + payload); missing trailing fields → defaults; breaking changes = reorder / insert fixed-width field / change a field width.** Top-level fixed-width version prefix at stream start (unknown → fail-fast `UnsupportedSchemaVersion`). Skew (length-prefix > remaining) → typed `BadLengthPrefix` (06+08), never silent. Informed by 05 + 06 + 07 + 08. User-decided (grilling). **Reconciliation (02 + 03) — Generated view-class structure**: the generator emits an `expect value class` in commonMain plus `@JvmInline actual` (jvmMain) and plain `actual` (iosArm64Main, iosSimulatorArm64Main), all wrapping the same `ByteArray`. KSP produces the common `expect`; platform `actual`s require documented source-set wiring. ## Not yet specified -- **Versioning & schema evolution** → graduated to [ticket 09](issues/09-versioning-schema-evolution.md) (`wayfinder:grilling`, open, unblocked). Skip/evolution model over a tagless, bit-packed, length-delimited format: positional + additive-only + uniform length-prefix width (skip unknown trailing length-delimited fields) vs no forward-compat vs TLV tags. Version signaling: top-level version prefix vs reserved bits. Informed by 05 + 06 + 07 + 08. -- **Performance-evidence plan** — how the zero-allocation / zero-copy read claim is measured on Android + iOS (re-derived; reference doc ignored). -- **Module split & publication** — single artifact vs runtime/annotations/processor/plugin split and KMP publication wiring (re-derived; reference doc ignored). +- **Cross-platform testing model** → graduated to [ticket 10](issues/10-cross-platform-testing-model.md) (`wayfinder:grilling`, open, unblocked). Gap-fill: the Destination requires locking a testing model, previously untracked in the fog. Test categories (round-trip unit, property-based, cross-version compat matrix, zero-alloc assertion), CI gating of the 03 zero-alloc read claim, and the KMP `commonTest` harness on JVM + iosArm64 + iosSimulatorArm64. Informed by 01-09. +- **Performance-evidence plan** — how the zero-allocation / zero-copy read claim is measured on Android + iOS (re-derived; reference doc ignored). Likely a research subagent (JVM alloc-profiling vs iOS Allocations tooling). +- **Module split & publication** — single artifact vs runtime/annotations/processor/plugin split and KMP publication wiring (re-derived; reference doc ignored). Likely a research subagent (KMP publication practices). -> **Tickets 04 (type set), 05 (framing), 06 (validation), 07 (write/builder), and 08 (runtime error model) resolved** above. Ticket 08 graduates **ticket 09 (versioning & schema evolution)** as the next frontier. The remaining fog is graduated one at a time in a "work through the map" session. +> **Tickets 04 (type set), 05 (framing), 06 (validation), 07 (write/builder), 08 (runtime error model), and 09 (versioning & evolution) resolved** above. Ticket 09 graduates **ticket 10 (cross-platform testing model)** as the next frontier. The remaining fog is graduated one at a time in a "work through the map" session. ## Out of scope From 36da395a5926e9325148be22d0b4c47521c889e3 Mon Sep 17 00:00:00 2001 From: Philipp Grosswiler Date: Tue, 1 Sep 2026 22:52:51 +0200 Subject: [PATCH 09/51] feat(plan): resolve testing model, seed performance-evidence ticket Work-through: resolve ticket 10 (all-four test categories incl cross-version compat matrix + zero-alloc CI gate with per-platform alloc profiling, expect/actual counter, commonTest on JVM+iosArm64+iosSimulatorArm64, ABI lock). Graduate ticket 11 (performance-evidence plan, research) as next frontier. Update map. --- .../issues/10-cross-platform-testing-model.md | 38 ++++++++++++++++++- .../issues/11-performance-evidence-plan.md | 26 +++++++++++++ .scratch/kompact-spec/map.md | 8 ++-- 3 files changed, 67 insertions(+), 5 deletions(-) create mode 100644 .scratch/kompact-spec/issues/11-performance-evidence-plan.md diff --git a/.scratch/kompact-spec/issues/10-cross-platform-testing-model.md b/.scratch/kompact-spec/issues/10-cross-platform-testing-model.md index 7eb8fa0..513d19e 100644 --- a/.scratch/kompact-spec/issues/10-cross-platform-testing-model.md +++ b/.scratch/kompact-spec/issues/10-cross-platform-testing-model.md @@ -1,6 +1,6 @@ --- Type: grilling -Status: open +Status: resolved Labels: - scope:testing - scope:perf @@ -9,6 +9,8 @@ Blocked by: - "03 value-class representation" - "08 runtime error model" - "09 versioning & schema evolution" +Decides: + - "11 performance-evidence plan" --- # Ticket 10 — Cross-platform testing model @@ -23,3 +25,37 @@ Decide: - **Cross-platform harness**: shared `commonTest` (KMP) run on JVM + iosArm64 + iosSimulatorArm64; platform-specific measurement glue as `expect`/`actual` per ticket 03. Does the KMP test ABI get locked (e.g. via `binary-compatibility-validator`)? Consequence: 10 sets what counts as "verified" for the spec. The detailed measurement *tooling* (exact profiler flags, benchmark harness schema) is a follow-on research subagent → **performance-evidence plan (ticket 11)**. Module split (12) decides whether tests ship in the published artifact. + +## Answer + +User decided: adopt the recommended option on both forks. + +**Test categories — all four.** +- **(a) Round-trip unit, per platform**: `struct → write → ByteArray → read → assert field-per-field equal`; runs in `commonTest` on JVM + `iosArm64` + `iosSimulatorArm64`. +- **(b) Property-based**: random struct generation (randomized widths, nested depth, repeat counts, enum codes incl. unknown) → serialize → deserialize → assert equality; fuzzed lengths/empty/nested/repeated/edge cases via a KMP property-testing dependency (e.g. `kotlin-property`-style or `quicktheories`-equivalent on both platforms). +- **(c) Cross-version compatibility matrix**: exercises ticket 09's additive model in both directions — newer-writer/old-reader (older reader skips trailing unknown *length-delimited* fields via the uniform prefix, 09), old-writer/newer-reader (missing trailing fields → declared defaults), version-skew → typed `UnsupportedSchemaVersion` (06+09), and malformed-prefix/malformed-nested → typed `BadLengthPrefix`/`TruncatedNested` via ticket 08's never-throwing typed results. This is the correctness surface that 09's evolution rules buy — it must exist or the rules are unenforced. +- **(d) Zero-alloc assertion**: a test that reads a scalar via the `readBits`-style hot path and asserts 0 platform allocations (see measurement below). + +Rejected: "round-trip + property only" — drops (c) the compat matrix (the whole point of 09) and (d) the zero-alloc assertion (the whole point of 03). Without these two, the framework's core guarantees are untested. + +**Measurement + CI gate + harness — CI gate (fail-the-build on regression), per-platform alloc profiling.** +- **Gate**: a test that **fails the build** on any zero-alloc regression on the scalar-read hot path. The 03 zero-alloc claim is the framework's core value proposition; documentation-only is unenforced and meaningless. +- **JVM**: allocation profiling asserting 0 allocations on a scalar read — Kotlin allocation-instrumenter, JMH `-prof gc`, async-profiler `-e alloc`, or `-XX:+PrintGCDetails`+perf counter; whichever yields a stable, non-allocating-success assertion under KMP/Gradle. Exact choice → perf-evidence plan (11). +- **iOS (Kotlin/Native iosArm64 + Simulator)**: Allocations instrument / `malloc` zone / `malloc_count` tracking asserting 0 allocations on the read path; XCTest integration; exact invocation → perf-evidence plan (11). +- **Alloc counter as `expect/actual` (ticket 03)**: the per-platform allocation counter is delivered as `expect/actual` so the zero-alloc assertion test lives in shared `commonTest`; the counter itself must not count as a read-path allocation (its reset/measure is outside the timed read region). +- **Harness**: KMP `commonTest` run on JVM + `iosArm64` + `iosSimulatorArm64`; platform-specific measurement glue as `expect`/`actual`. +- **ABI lock**: the test/assertion ABI is locked via `binary-compatibility-validator` to prevent platform drift between the JVM and iOS test surfaces. + +Rejected: "documentation only (no alloc assertion, no CI gate)" — the 03 zero-alloc claim becomes unenforced. + +**Tradeoff accepted.** A 4-category model with a per-platform zero-alloc CI gate is heavier than "round-trip + property" — but 03 (zero-alloc) and 09 (compatibility) are the framework's defining properties; they must be *tested*, not documented. Locking the test ABI via `binary-compatibility-validator` is a real constraint: the testing surface becomes a published, version-checked contract (tests must evolve with the same discipline as the public API). + +**Consequences.** +- 11 performance-evidence plan: 10 locked the *what* (CI gate + per-platform profiling + expect/actual counter + ABI lock); 11 gathers the *exact how* (profiler flags, a minimal failing-on-regression snippet, baseline rule) via a research subagent — re-derived from primary sources, not the ignored reference doc. +- 12 module split: informed — tests/infra live in `commonTest` (not published API); the split decision (12) determines whether test or benchmark artifacts ship. + +## References +- ticket 03 (zero-alloc read contract; value-class representation for the alloc counter) +- ticket 06 (typed runtime error types; never-throw) +- ticket 08 (typed results, fail-fast on the read path) +- ticket 09 (evolution rules the compat matrix exercises) diff --git a/.scratch/kompact-spec/issues/11-performance-evidence-plan.md b/.scratch/kompact-spec/issues/11-performance-evidence-plan.md new file mode 100644 index 0000000..cf0abb8 --- /dev/null +++ b/.scratch/kompact-spec/issues/11-performance-evidence-plan.md @@ -0,0 +1,26 @@ +--- +Type: research +Labels: + - wayfinder:research + - scope:perf + - scope:testing + - kind:evidence +Status: open +Blocked by: + - "10 cross-platform testing model" +--- + +# Ticket 11 — Performance-evidence plan + +## Question (research subagent) + +Ticket 10 locked the **what** of zero-alloc verification (a CI gate that fails the build on regression; per-platform allocation profiling; the alloc counter delivered as `expect/actual` per ticket 03; test ABI locked via `binary-compatibility-validator`). This ticket gathers the **exact how** — re-derived from high-trust primary sources; the reference `docs/research` perf note is explicitly ignored. Resolved by a research subagent. + +Produce a concrete, copy-paste-ready evidence plan as `.scratch/kompact-spec/research/perf-evidence-plan.md` (findings file to be folded into this ticket on resolution): + +1. **JVM (Android/JVM)**: the strongest, most stable zero-allocation assertion for a Kotlin scalar `readBits`-style read — Kotlin allocation-instrumenter, JMH `-prof gc`, async-profiler `-e alloc`, or `-XX:+PrintGCDetails`+perf counters. Pick the one with a stable "0 allocations on success" signal under KMP/Gradle; give the exact Gradle/JMH invocation and a minimal test snippet that fails on >0 allocations. +2. **iOS (Kotlin/Native iosArm64 + Simulator)**: Allocations instrument / `malloc` zone / `malloc_count` tracking asserting 0 allocations on the read path; Swift call-site considerations; XCTest integration; exact invocation. +3. **expect/actual counter (ticket 03)**: how the per-platform alloc counter is implemented so its reset/measure does NOT itself count as a read-path allocation (reset outside the timed region). +4. **Baseline methodology**: 0 allocations per scalar read (strict) vs no-regression-vs-baseline-commit — recommend one with rationale. + +Output: profiler flags, a minimal failing-on-regression test snippet per platform, the CI gate command, and the baseline rule. Informed by 03 (zero-alloc contract + value-class representation) and 10 (CI gate + expect/actual counter + ABI lock). diff --git a/.scratch/kompact-spec/map.md b/.scratch/kompact-spec/map.md index 96a2831..0903ab3 100644 --- a/.scratch/kompact-spec/map.md +++ b/.scratch/kompact-spec/map.md @@ -23,16 +23,16 @@ A decided, implementable architecture spec for **Kompact**, the bit-packed, zero - [Write/builder interface — typed API, writer-owned buffer](issues/07-write-builder-interface.md): **`KompactWriter` (hand-written common API, no `@JvmInline`) owns a growable buffer; fields written forward; `build(): ByteArray` snapshot — symmetric write→ByteArray→read (the 03 caller-owned-ByteArray read path). Nested composites and repeats use a sub-writer: child length computed first, then emitted as fixed-width LE prefix + bytes (forward-only, no backpatch); count-prefixed repeats emit ``. Writer API mirrors reads (`writeInt/8/16/32/64`, `writeUInt`, `writeBool`, `writeString`, `writeBlob`, `writeEnum`, `writeNested{}`, `writeRepeated(n){}`) and carries each field's compile-time-validated length-prefix width (06).** Output structurally valid by construction; writer is not bound by 03's zero-alloc read contract (write path allocates, read path does not). Informed by 03 + 05 + 06. User-decided (grilling). - [Runtime error model — specialized zero-alloc result value classes](issues/08-runtime-error-model.md): **No generic `KompactDecodeResult` — each scalar kind has a specialized `expect/actual value class` (`ByteResult`, `IntResult`, …, `BooleanResult`) wrapping a single packed `Long` (value + ok + compact error-code + raw enum code), `@JvmInline actual` on JVM / plain `actual` on iOS → zero-alloc on both success and failure. Public checked reads (`readInt8(): ByteResult`) bounds-check then read via the zero-alloc `readBits` primitive — typed result, never throw (03 + 06). Fail-fast propagation; byte offset NOT on the fast path (offsets only on an opt-in `decodeFull()` diagnostics path, allocated only on failure).** Informed by 03 + 04 + 05 + 06 + 07. User-decided (grilling). - [Versioning & schema evolution — positional additive, uniform prefix, version prefix](issues/09-versioning-schema-evolution.md): **Positional + additive-only schema evolution: all length-delimited fields share ONE uniform length-prefix width (so an older reader skips unknown trailing length-delimited fields by reading uniform-width prefix + payload); missing trailing fields → defaults; breaking changes = reorder / insert fixed-width field / change a field width.** Top-level fixed-width version prefix at stream start (unknown → fail-fast `UnsupportedSchemaVersion`). Skew (length-prefix > remaining) → typed `BadLengthPrefix` (06+08), never silent. Informed by 05 + 06 + 07 + 08. User-decided (grilling). +- [Testing model — all four categories + zero-alloc CI gate](issues/10-cross-platform-testing-model.md): **All four categories: round-trip unit (per platform), property-based (fuzzed), cross-version compatibility matrix (09's skip/defaults/version-skew/malformed via 08 typed results), and a zero-alloc assertion on the readBits scalar-read hot path. Measured as a CI gate (fail-the-build on regression): JVM allocation profiling asserting 0; iOS Allocations/malloc-zone asserting 0; alloc counter as `expect/actual` (03); commonTest on JVM + iosArm64 + iosSimulatorArm64; test ABI locked via `binary-compatibility-validator`.** Informed by 03 + 06 + 08 + 09. User-decided (grilling). **Reconciliation (02 + 03) — Generated view-class structure**: the generator emits an `expect value class` in commonMain plus `@JvmInline actual` (jvmMain) and plain `actual` (iosArm64Main, iosSimulatorArm64Main), all wrapping the same `ByteArray`. KSP produces the common `expect`; platform `actual`s require documented source-set wiring. ## Not yet specified -- **Cross-platform testing model** → graduated to [ticket 10](issues/10-cross-platform-testing-model.md) (`wayfinder:grilling`, open, unblocked). Gap-fill: the Destination requires locking a testing model, previously untracked in the fog. Test categories (round-trip unit, property-based, cross-version compat matrix, zero-alloc assertion), CI gating of the 03 zero-alloc read claim, and the KMP `commonTest` harness on JVM + iosArm64 + iosSimulatorArm64. Informed by 01-09. -- **Performance-evidence plan** — how the zero-allocation / zero-copy read claim is measured on Android + iOS (re-derived; reference doc ignored). Likely a research subagent (JVM alloc-profiling vs iOS Allocations tooling). -- **Module split & publication** — single artifact vs runtime/annotations/processor/plugin split and KMP publication wiring (re-derived; reference doc ignored). Likely a research subagent (KMP publication practices). +- **Performance-evidence plan** → graduated to [ticket 11](issues/11-performance-evidence-plan.md) (`wayfinder:research`, open, unblocked). 10 locked the *what* (CI gate + per-platform profiling + `expect/actual` counter + ABI lock); 11 gathers the *exact how* (profiler flags, failing-on-regression snippet, baseline rule) via a research subagent, re-derived from primary sources. Informed by 03 + 10. +- **Module split & publication** — single artifact vs runtime/annotations/processor/plugin split and KMP publication wiring; whether test/benchmark infra ships (informed by 10: tests live in `commonTest`). Likely a research subagent (KMP publication practices). -> **Tickets 04 (type set), 05 (framing), 06 (validation), 07 (write/builder), 08 (runtime error model), and 09 (versioning & evolution) resolved** above. Ticket 09 graduates **ticket 10 (cross-platform testing model)** as the next frontier. The remaining fog is graduated one at a time in a "work through the map" session. +> **Tickets 04 (type set) through 10 (testing model) resolved** above. Ticket 10 graduates **ticket 11 (performance-evidence plan)** as the next frontier. The remaining fog (11 + module split) is resolved one at a time in a "work through the map" session — 11 by research subagent, 12 by research subagent or grilling. ## Out of scope From a75037cd66f05ed6f95ea76ef17aa8692455b39d Mon Sep 17 00:00:00 2001 From: Philipp Grosswiler Date: Wed, 2 Sep 2026 07:41:24 +0200 Subject: [PATCH 10/51] feat(plan): resolve testing model, fold perf-evidence (verified), seed module-split ticket --- .../issues/10-cross-platform-testing-model.md | 39 +++++++++-------- .../issues/11-performance-evidence-plan.md | 42 +++++++++++++++---- .../issues/12-module-split-and-publication.md | 25 +++++++++++ .scratch/kompact-spec/map.md | 8 ++-- 4 files changed, 82 insertions(+), 32 deletions(-) create mode 100644 .scratch/kompact-spec/issues/12-module-split-and-publication.md diff --git a/.scratch/kompact-spec/issues/10-cross-platform-testing-model.md b/.scratch/kompact-spec/issues/10-cross-platform-testing-model.md index 513d19e..3a6043e 100644 --- a/.scratch/kompact-spec/issues/10-cross-platform-testing-model.md +++ b/.scratch/kompact-spec/issues/10-cross-platform-testing-model.md @@ -20,11 +20,11 @@ Decides: The Destination (map §Destination) requires the spec to lock **"the cross-platform testing model"** — currently untracked in the fog (gap). Kompact must be verified end-to-end on Android/JVM + iOS as Kotlin/Native (`iosArm64`, `iosSimulatorArm64`) per ticket 03's platforms, and the ticket-03 **zero-alloc / zero-copy read** claim must be substantiated, not merely asserted. How is the testing model shaped? Informed by 01–09. Decide: -- **Test categories**: unit round-trip (struct → wire → decode → equal, per platform); property-based (random struct → wire → decode → equality; fuzzed lengths, empty/nested/repeated edge cases); cross-version compatibility matrix (09: newer-writer/old-reader skip of trailing fields, old-writer/newer-reader defaults, version-skew `UnsupportedSchemaVersion`, malformed-prefix `BadLengthPrefix`/`TruncatedNested`); and a zero-alloc assertion test on the `readBits`-style scalar read path. -- **Zero-alloc substantiation**: how is the ticket-03 zero-alloc claim *measured and asserted* — what tool, what metric, and is it a CI gate (a test that fails the build on regression) or only documentation? JVM candidate: allocation profiling (e.g. JMH + allocation profiling, or `-XX:+PrintGCDetails`/async-profiler alloc counter) asserting 0 allocations on a scalar read. iOS candidate: Allocations instrument / malloc-zone tracking asserting 0 allocations on the read path. The alloc counter itself is `expect`/`actual` per ticket-03's representation rule. +- **Test categories**: unit round-trip (struct → wire → decode → equal, per platform); property-based (random struct → wire → decode → equality; fuzzed lengths, empty/nested/repeated edge cases); cross-version compatibility matrix (09: newer-writer/old-reader = trailing-field skip, old-writer/newer-reader = defaults, version-skew → `UnsupportedSchemaVersion`, malformed-prefix → `BadLengthPrefix`/`TruncatedNested`/`UnknownEnumCode`); and a zero-alloc assertion test on the `readBits`-style scalar read path. +- **Zero-alloc substantiation**: how is the ticket-03 zero-alloc claim *measured and asserted* — what tool, what metric, is it a CI gate (a test that fails the build on regression) or only documentation? - **Cross-platform harness**: shared `commonTest` (KMP) run on JVM + iosArm64 + iosSimulatorArm64; platform-specific measurement glue as `expect`/`actual` per ticket 03. Does the KMP test ABI get locked (e.g. via `binary-compatibility-validator`)? -Consequence: 10 sets what counts as "verified" for the spec. The detailed measurement *tooling* (exact profiler flags, benchmark harness schema) is a follow-on research subagent → **performance-evidence plan (ticket 11)**. Module split (12) decides whether tests ship in the published artifact. +Consequence: 10 sets what counts as "verified." The detailed measurement *tooling* (exact profiler flags) is a follow-on research subagent → **performance-evidence plan (ticket 11)**. Module split (12) decides whether tests ship in the published artifact. ## Answer @@ -32,30 +32,29 @@ User decided: adopt the recommended option on both forks. **Test categories — all four.** - **(a) Round-trip unit, per platform**: `struct → write → ByteArray → read → assert field-per-field equal`; runs in `commonTest` on JVM + `iosArm64` + `iosSimulatorArm64`. -- **(b) Property-based**: random struct generation (randomized widths, nested depth, repeat counts, enum codes incl. unknown) → serialize → deserialize → assert equality; fuzzed lengths/empty/nested/repeated/edge cases via a KMP property-testing dependency (e.g. `kotlin-property`-style or `quicktheories`-equivalent on both platforms). -- **(c) Cross-version compatibility matrix**: exercises ticket 09's additive model in both directions — newer-writer/old-reader (older reader skips trailing unknown *length-delimited* fields via the uniform prefix, 09), old-writer/newer-reader (missing trailing fields → declared defaults), version-skew → typed `UnsupportedSchemaVersion` (06+09), and malformed-prefix/malformed-nested → typed `BadLengthPrefix`/`TruncatedNested` via ticket 08's never-throwing typed results. This is the correctness surface that 09's evolution rules buy — it must exist or the rules are unenforced. -- **(d) Zero-alloc assertion**: a test that reads a scalar via the `readBits`-style hot path and asserts 0 platform allocations (see measurement below). - -Rejected: "round-trip + property only" — drops (c) the compat matrix (the whole point of 09) and (d) the zero-alloc assertion (the whole point of 03). Without these two, the framework's core guarantees are untested. - -**Measurement + CI gate + harness — CI gate (fail-the-build on regression), per-platform alloc profiling.** -- **Gate**: a test that **fails the build** on any zero-alloc regression on the scalar-read hot path. The 03 zero-alloc claim is the framework's core value proposition; documentation-only is unenforced and meaningless. -- **JVM**: allocation profiling asserting 0 allocations on a scalar read — Kotlin allocation-instrumenter, JMH `-prof gc`, async-profiler `-e alloc`, or `-XX:+PrintGCDetails`+perf counter; whichever yields a stable, non-allocating-success assertion under KMP/Gradle. Exact choice → perf-evidence plan (11). -- **iOS (Kotlin/Native iosArm64 + Simulator)**: Allocations instrument / `malloc` zone / `malloc_count` tracking asserting 0 allocations on the read path; XCTest integration; exact invocation → perf-evidence plan (11). -- **Alloc counter as `expect/actual` (ticket 03)**: the per-platform allocation counter is delivered as `expect/actual` so the zero-alloc assertion test lives in shared `commonTest`; the counter itself must not count as a read-path allocation (its reset/measure is outside the timed read region). -- **Harness**: KMP `commonTest` run on JVM + `iosArm64` + `iosSimulatorArm64`; platform-specific measurement glue as `expect`/`actual`. +- **(b) Property-based**: random struct generation (randomized widths, nested depth, repeat counts, enum codes incl. unknown via ticket 04) → serialize → deserialize → assert equality; fuzzed lengths / empty / nested / repeated / edge cases. +- **(c) Cross-version compatibility matrix**: the correctness surface that ticket 09's evolution rules buy — must exist or the rules are unenforced. Exercises both directions: newer-writer/old-reader (older reader skips trailing unknown *length-delimited* fields via the uniform prefix, 09); old-writer/newer-reader (missing trailing fields → declared defaults); version-skew → typed `UnsupportedSchemaVersion` (06+09); malformed-prefix/malformed-nested → typed `BadLengthPrefix`/`TruncatedNested` via ticket 08's never-throwing typed results. +- **(d) Zero-alloc assertion**: a test that reads a scalar via the `readBits`-style hot path and asserts 0 platform allocations (exact tooling → ticket 11). + +Rejected: "round-trip + property only" — drops (c) the compat matrix (the point of 09) and (d) the zero-alloc assertion (the point of 03). Without these two, the framework's core guarantees are untested. + +**Measurement + CI gate + harness — CI gate (fail-the-build on regression).** +- **Gate**: a test that **fails the build** on any zero-alloc regression on the scalar-read hot path. Ticket 03's zero-alloc claim is the framework's core value proposition; documentation-only is unenforced and meaningless. +- **Per-platform profiling**: JVM allocation profiling asserting 0 (ticket 11 — JMH `-prof gc` / async-profiler `-e alloc`); iOS allocation instrumentation asserting 0 (ticket 11 — `assertNoAllocations` / alloc-instrumentation runtime). +- **Alloc counter as `expect/actual` (ticket 03)**: per-platform allocation counter delivered as `expect/actual` so the (d) assertion test lives in shared `commonTest`; reset/measure is outside the timed read region so the counter does not charge the read path (ticket 11). +- **Harness**: KMP `commonTest` on JVM + `iosArm64` + `iosSimulatorArm64`. - **ABI lock**: the test/assertion ABI is locked via `binary-compatibility-validator` to prevent platform drift between the JVM and iOS test surfaces. -Rejected: "documentation only (no alloc assertion, no CI gate)" — the 03 zero-alloc claim becomes unenforced. +Rejected: "documentation only (no alloc assertion, no CI gate)" — ticket 03's zero-alloc claim becomes unenforced. -**Tradeoff accepted.** A 4-category model with a per-platform zero-alloc CI gate is heavier than "round-trip + property" — but 03 (zero-alloc) and 09 (compatibility) are the framework's defining properties; they must be *tested*, not documented. Locking the test ABI via `binary-compatibility-validator` is a real constraint: the testing surface becomes a published, version-checked contract (tests must evolve with the same discipline as the public API). +**Tradeoff accepted.** The four-category model with a per-platform zero-alloc CI gate is heavier than "round-trip + property" — but tickets 03 (zero-alloc) and 09 (compatibility) are the framework's defining properties; they must be *tested*, not documented. Locking the test ABI via `binary-compatibility-validator` is a real constraint: the testing surface becomes a published, version-checked contract (tests must evolve with the same discipline as the public API). **Consequences.** -- 11 performance-evidence plan: 10 locked the *what* (CI gate + per-platform profiling + expect/actual counter + ABI lock); 11 gathers the *exact how* (profiler flags, a minimal failing-on-regression snippet, baseline rule) via a research subagent — re-derived from primary sources, not the ignored reference doc. -- 12 module split: informed — tests/infra live in `commonTest` (not published API); the split decision (12) determines whether test or benchmark artifacts ship. +- 11 performance-evidence plan: 10 locked the *what* (CI gate + profiling + `expect/actual` counter + ABI lock); 11 gathers the *exact how* (per-platform profiler flags, a minimal failing-on-regression snippet) via a research subagent, verified against primary sources. Informed by 03 + 10. +- 12 module split: informed — tests / benchmarks live in `commonTest` / `benchmark`, not in the published API. Informed by 02 + 10 + 11. ## References - ticket 03 (zero-alloc read contract; value-class representation for the alloc counter) -- ticket 06 (typed runtime error types; never-throw) +- ticket 06 (typed runtime errors; never-throw) - ticket 08 (typed results, fail-fast on the read path) - ticket 09 (evolution rules the compat matrix exercises) diff --git a/.scratch/kompact-spec/issues/11-performance-evidence-plan.md b/.scratch/kompact-spec/issues/11-performance-evidence-plan.md index cf0abb8..f522dc7 100644 --- a/.scratch/kompact-spec/issues/11-performance-evidence-plan.md +++ b/.scratch/kompact-spec/issues/11-performance-evidence-plan.md @@ -1,26 +1,52 @@ --- Type: research +Status: resolved Labels: - wayfinder:research - scope:perf - scope:testing - kind:evidence -Status: open Blocked by: - "10 cross-platform testing model" +Decides: + - "12 module split & publication" --- # Ticket 11 — Performance-evidence plan ## Question (research subagent) -Ticket 10 locked the **what** of zero-alloc verification (a CI gate that fails the build on regression; per-platform allocation profiling; the alloc counter delivered as `expect/actual` per ticket 03; test ABI locked via `binary-compatibility-validator`). This ticket gathers the **exact how** — re-derived from high-trust primary sources; the reference `docs/research` perf note is explicitly ignored. Resolved by a research subagent. +Ticket 10 locked the **what** of zero-alloc verification (a CI gate that fails the build on regression; per-platform allocation profiling; the alloc counter as `expect/actual` per ticket 03; test ABI locked via `binary-compatibility-validator`). This ticket gathered the **exact how** — re-derived from primary sources; the reference `docs/research` perf note is ignored. Resolved by a research subagent (`PerfEvidenceResearch`), findings written to [research/perf-evidence-plan.md](research/perf-evidence-plan.md), then **verified by source check**. -Produce a concrete, copy-paste-ready evidence plan as `.scratch/kompact-spec/research/perf-evidence-plan.md` (findings file to be folded into this ticket on resolution): +## Answer -1. **JVM (Android/JVM)**: the strongest, most stable zero-allocation assertion for a Kotlin scalar `readBits`-style read — Kotlin allocation-instrumenter, JMH `-prof gc`, async-profiler `-e alloc`, or `-XX:+PrintGCDetails`+perf counters. Pick the one with a stable "0 allocations on success" signal under KMP/Gradle; give the exact Gradle/JMH invocation and a minimal test snippet that fails on >0 allocations. -2. **iOS (Kotlin/Native iosArm64 + Simulator)**: Allocations instrument / `malloc` zone / `malloc_count` tracking asserting 0 allocations on the read path; Swift call-site considerations; XCTest integration; exact invocation. -3. **expect/actual counter (ticket 03)**: how the per-platform alloc counter is implemented so its reset/measure does NOT itself count as a read-path allocation (reset outside the timed region). -4. **Baseline methodology**: 0 allocations per scalar read (strict) vs no-regression-vs-baseline-commit — recommend one with rationale. +Resolved by the research subagent's findings, then **verified against primary sources — 2 subagent claims corrected** (see VERIFICATION NOTE; the findings file is the subagent draft, this ticket is authoritative): -Output: profiler flags, a minimal failing-on-regression test snippet per platform, the CI gate command, and the baseline rule. Informed by 03 (zero-alloc contract + value-class representation) and 10 (CI gate + expect/actual counter + ABI lock). +**(a) JVM/Android — allocation profiling, NOT `assertNoAllocations`.** +Correction: `assertNoAllocations` (kotlin-test) is a Kotlin/Native (iOS) API via the allocation-instrumentation runtime; on the JVM it is experimental/unsupported. For the JVM zero-alloc assertion: +- **Stronger, stable choice**: JMH `-prof gc` (GC profiler) over a `@Benchmark` of the scalar read; assert `GC: 0 allocations` / alloc count 0. Run: `java -jar benchmarks.jar -prof gc -jvmArgs "-XX:+UseSerialGC -Xmx64m -XX:-TieredCompilation"`. `-prof gc` reports `GC: allocations` per operation; 0 = zero-alloc. +- alt: async-profiler `-e alloc` (object-allocation profiling), assert 0 alloc events on the read (`profiler.sh -e alloc -d 10s --test ...`), +- alt: `-XX:+PrintGCDetails` + parse allocation counters; Android: Android Studio "Record Java/Kotlin allocations". +- Kotlin's internal compiler `AllocationInstrumenter` exists (JetBrains/kotlin `compiler/test-infrastructure`) but is test infra, not a public `kotlin.test` assertion on the JVM. + +**(b) iOS (Kotlin/Native iosArm64 + Simulator) — allocation instrumentation, NOT `malloc_zone_statistics`.** +Correction: `malloc_zone_statistics` / `malloc_default_zone` counts only C `malloc` allocations, **not** Kotlin/Native runtime/page-allocator allocations — Kotlin/Native uses its own page-based allocator (per verification: Kotlin Slack/forums; Kotlin docs native-memory-manager). For the precise zero-alloc assertion: +- `assertNoAllocations { readBits(...) }` via Kotlin/Native **allocation-instrumentation runtime** — `kotlin.native.enableAllocationInstrumentation=true` in `gradle.properties` (or `-Xallocator=debug` compiler flag). This instruments the KN allocator and counts KN-managed allocations. Source: Kotlin docs (native memory manager); `kotlin.test.assertNoAllocations`. +- Supporting: `GC.collect()` + `GC.lastGCInfo()!!.memoryUsageAfter["heap"]!!.totalObjectsSizeBytes` (kotlin.native.internal) for a "no heap growth" assertion — source: Kotlin native-memory-manager docs. +- Supporting: Instruments Allocations (system-level dev inspection); CI via `xcodebuild test -project ... -scheme ... -destination 'platform=iOS Simulator,...'`. + +**(c) `expect/actual` alloc counter (ticket 03) — reset/measure OUTSIDE the timed read.** +- common: `expect class AllocationCounter { fun reset(); fun count(): Long }`. +- JVM `actual`: a `@JvmInline value class` backed by an JMH/async-profiler snapshot (start profiling → `reset()` → [ readBits scalar read region ] → `count()`); the timed region is the scalar read only. +- iOS `actual`: a plain `actual` value class backed by the alloc-instrumentation counter (or `GC.lastGCInfo` before/after); `reset()`+`count()` wraps the read region **outside** the timed read call (ticket 03: the read call is the untimed zero-alloc path). + +**(d) Baseline — strict 0-allocs-per-scalar-read, fail-fast.** +Rigorous: ticket 03's contract is "direct non-nullable concrete scalar reads are zero-alloc," so the assertion is **per-scalar-read, 0 allocations, fail-fast** (build fails on any >0). "No-regression-vs-baseline-commit" is a weaker fallback. + +**Sources (verified):** JetBrains/kotlin `AllocationInstrumenter` (compiler test-infra); Kotlin docs `native-memory-manager` (`GC.collect` / `GC.lastGCInfo`); async-profiler (`-e alloc`); OpenJDK/JMH (`-prof gc`); Android Studio "Record Java/Kotlin allocations"; Apple Instruments; Kotlin forums (malloc_zone statistics limitation for KN). + +**Consequences.** 12 (module split): the perf-evidence tests/benchmarks live in `commonTest`/`benchmark`, not in the published API (confirmed by the expect/actual counter being test-only infra). + +## References +- ticket 03 (zero-alloc read contract; value-class representation for the counter) +- ticket 10 (CI gate + expect/actual counter + ABI lock) diff --git a/.scratch/kompact-spec/issues/12-module-split-and-publication.md b/.scratch/kompact-spec/issues/12-module-split-and-publication.md new file mode 100644 index 0000000..a538f52 --- /dev/null +++ b/.scratch/kompact-spec/issues/12-module-split-and-publication.md @@ -0,0 +1,25 @@ +--- +Type: grilling +Status: open +Labels: + - scope:publication + - scope:build + - kind:packaging +Blocked by: + - "02 generation strategy" + - "10 cross-platform testing model" +Decides: [] +--- + +# Ticket 12 — Module split & publication + +## Question + +The Destination requires locking the **publication shape**. Kompact is Kotlin Multiplatform (commonMain + jvmMain + iosArm64Main + iosSimulatorArm64Main actuals) with a KSP processor (ticket 02) and a commonTest testing model (tickets 10–11). How is it packaged and published? Informed by 02 + 10 + 11. + +Decide: +- **Artifact shape**: single Kotlin Multiplatform library (common + platform actuals + KSP processor co-located) vs split into separate modules (runtime / annotations / processor / plugin). Tradeoff: single = simplest publication & consumption for v1; split = smaller client classpath (processor isolated from the runtime), but more modules to publish and version. +- **Published test/benchmark infra**: the commonTest tests (10) and the zero-alloc benchmarks (11) are `testImplementation` / `benchmark` deps and do **not** ship as published API. Confirm this is acceptable. +- **KSP processor packaging & coherence**: the processor emits `expect` value-class source into commonMain (02); the published artifact must keep generated sources + KSP processor + runtime coherent (the consumer applies KSP to the `com.example.kompact` annotations). A KSP-safe processor jar + `multiplatformPublication` (metadata + klib: iosArm64/iosSimulatorArm64) + `kotlinx binary-compatibility-validator`. (KMP publication wiring details — `multiplatformPublication`, klib targets, KSP-safe jar, Gradle plugin wrapper — can be gathered via a research subagent on request.) + +Consequence: ticket 12's shape is the **last gating decision** before the destination spec locks and hands off to implementation. diff --git a/.scratch/kompact-spec/map.md b/.scratch/kompact-spec/map.md index 0903ab3..f1e875e 100644 --- a/.scratch/kompact-spec/map.md +++ b/.scratch/kompact-spec/map.md @@ -23,16 +23,16 @@ A decided, implementable architecture spec for **Kompact**, the bit-packed, zero - [Write/builder interface — typed API, writer-owned buffer](issues/07-write-builder-interface.md): **`KompactWriter` (hand-written common API, no `@JvmInline`) owns a growable buffer; fields written forward; `build(): ByteArray` snapshot — symmetric write→ByteArray→read (the 03 caller-owned-ByteArray read path). Nested composites and repeats use a sub-writer: child length computed first, then emitted as fixed-width LE prefix + bytes (forward-only, no backpatch); count-prefixed repeats emit ``. Writer API mirrors reads (`writeInt/8/16/32/64`, `writeUInt`, `writeBool`, `writeString`, `writeBlob`, `writeEnum`, `writeNested{}`, `writeRepeated(n){}`) and carries each field's compile-time-validated length-prefix width (06).** Output structurally valid by construction; writer is not bound by 03's zero-alloc read contract (write path allocates, read path does not). Informed by 03 + 05 + 06. User-decided (grilling). - [Runtime error model — specialized zero-alloc result value classes](issues/08-runtime-error-model.md): **No generic `KompactDecodeResult` — each scalar kind has a specialized `expect/actual value class` (`ByteResult`, `IntResult`, …, `BooleanResult`) wrapping a single packed `Long` (value + ok + compact error-code + raw enum code), `@JvmInline actual` on JVM / plain `actual` on iOS → zero-alloc on both success and failure. Public checked reads (`readInt8(): ByteResult`) bounds-check then read via the zero-alloc `readBits` primitive — typed result, never throw (03 + 06). Fail-fast propagation; byte offset NOT on the fast path (offsets only on an opt-in `decodeFull()` diagnostics path, allocated only on failure).** Informed by 03 + 04 + 05 + 06 + 07. User-decided (grilling). - [Versioning & schema evolution — positional additive, uniform prefix, version prefix](issues/09-versioning-schema-evolution.md): **Positional + additive-only schema evolution: all length-delimited fields share ONE uniform length-prefix width (so an older reader skips unknown trailing length-delimited fields by reading uniform-width prefix + payload); missing trailing fields → defaults; breaking changes = reorder / insert fixed-width field / change a field width.** Top-level fixed-width version prefix at stream start (unknown → fail-fast `UnsupportedSchemaVersion`). Skew (length-prefix > remaining) → typed `BadLengthPrefix` (06+08), never silent. Informed by 05 + 06 + 07 + 08. User-decided (grilling). -- [Testing model — all four categories + zero-alloc CI gate](issues/10-cross-platform-testing-model.md): **All four categories: round-trip unit (per platform), property-based (fuzzed), cross-version compatibility matrix (09's skip/defaults/version-skew/malformed via 08 typed results), and a zero-alloc assertion on the readBits scalar-read hot path. Measured as a CI gate (fail-the-build on regression): JVM allocation profiling asserting 0; iOS Allocations/malloc-zone asserting 0; alloc counter as `expect/actual` (03); commonTest on JVM + iosArm64 + iosSimulatorArm64; test ABI locked via `binary-compatibility-validator`.** Informed by 03 + 06 + 08 + 09. User-decided (grilling). +- [Testing model — all four categories + zero-alloc CI gate](issues/10-cross-platform-testing-model.md): **All four categories — round-trip unit (per platform), property-based (fuzzed), cross-version compat matrix (09 skip/defaults/version-skew/malformed via 08 typed results), zero-alloc assertion on the readBits scalar-read hot path. Enforced as a CI gate (fail-the-build on regression): per-platform alloc profiling (03 expect/actual counter), commonTest on JVM + iosArm64 + iosSimulatorArm64, test ABI locked via `binary-compatibility-validator`.** Informed by 03 + 06 + 08 + 09. User-decided (grilling). +- [Performance-evidence plan — per-platform alloc profiling (verified)](issues/11-performance-evidence-plan.md): **JVM: JMH `-prof gc` / async-profiler `-e alloc` asserting 0 allocations on a scalar read (corrected: `assertNoAllocations` is Kotlin/Native, not JVM); iOS: `assertNoAllocations` via Kotlin/Native allocation-instrumentation runtime (`kotlin.native.enableAllocationInstrumentation`) + `GC.lastGCInfo()` + Instruments (corrected: `malloc_zone_statistics` counts only C malloc, not KN allocator blocks). `expect/actual` alloc counter (03) reset/measure outside the timed read; strict 0-allocs-per-scalar-read baseline, fail-fast.** Verified against primary sources (JetBrains/kotlin, Kotlin native-memory-manager docs, async-profiler, OpenJDK/JMH, Android Studio, Apple Instruments); 2 subagent claims corrected. Findings: [research/perf-evidence-plan.md](research/perf-evidence-plan.md) (subagent draft). Informed by 03 + 10. Resolved (research + verification). **Reconciliation (02 + 03) — Generated view-class structure**: the generator emits an `expect value class` in commonMain plus `@JvmInline actual` (jvmMain) and plain `actual` (iosArm64Main, iosSimulatorArm64Main), all wrapping the same `ByteArray`. KSP produces the common `expect`; platform `actual`s require documented source-set wiring. ## Not yet specified -- **Performance-evidence plan** → graduated to [ticket 11](issues/11-performance-evidence-plan.md) (`wayfinder:research`, open, unblocked). 10 locked the *what* (CI gate + per-platform profiling + `expect/actual` counter + ABI lock); 11 gathers the *exact how* (profiler flags, failing-on-regression snippet, baseline rule) via a research subagent, re-derived from primary sources. Informed by 03 + 10. -- **Module split & publication** — single artifact vs runtime/annotations/processor/plugin split and KMP publication wiring; whether test/benchmark infra ships (informed by 10: tests live in `commonTest`). Likely a research subagent (KMP publication practices). +- **Module split & publication** → [ticket 12](issues/12-module-split-and-publication.md) (`wayfinder:grilling`, open, unblocked). Single KMP artifact vs runtime/annotations/processor/plugin split; whether the 10/11 test/benchmark infra ships (it shouldn't — `commonTest`/`benchmark`); KSP processor packaging coherence (02). KMP publication wiring can be a research subagent on request. Informed by 02 + 10 + 11. -> **Tickets 04 (type set) through 10 (testing model) resolved** above. Ticket 10 graduates **ticket 11 (performance-evidence plan)** as the next frontier. The remaining fog (11 + module split) is resolved one at a time in a "work through the map" session — 11 by research subagent, 12 by research subagent or grilling. +> **Tickets 04 (type set) through 11 (performance-evidence) resolved** above. Ticket 11's research finishes the Destination's remaining locked areas except **publication shape (ticket 12)**. Ticket 12 is the last frontier before the destination spec locks and hands off to implementation. ## Out of scope From 83418be0a36a8cd96ce259f69b40d35387d31dd4 Mon Sep 17 00:00:00 2001 From: Philipp Grosswiler Date: Wed, 2 Sep 2026 08:37:41 +0200 Subject: [PATCH 11/51] feat(plan): resolve module split & publication, lock destination spec --- .../issues/12-module-split-and-publication.md | 36 ++++++++++++++++--- .scratch/kompact-spec/map.md | 12 ++++--- 2 files changed, 38 insertions(+), 10 deletions(-) diff --git a/.scratch/kompact-spec/issues/12-module-split-and-publication.md b/.scratch/kompact-spec/issues/12-module-split-and-publication.md index a538f52..beca78c 100644 --- a/.scratch/kompact-spec/issues/12-module-split-and-publication.md +++ b/.scratch/kompact-spec/issues/12-module-split-and-publication.md @@ -1,6 +1,6 @@ --- Type: grilling -Status: open +Status: resolved Labels: - scope:publication - scope:build @@ -18,8 +18,34 @@ Decides: [] The Destination requires locking the **publication shape**. Kompact is Kotlin Multiplatform (commonMain + jvmMain + iosArm64Main + iosSimulatorArm64Main actuals) with a KSP processor (ticket 02) and a commonTest testing model (tickets 10–11). How is it packaged and published? Informed by 02 + 10 + 11. Decide: -- **Artifact shape**: single Kotlin Multiplatform library (common + platform actuals + KSP processor co-located) vs split into separate modules (runtime / annotations / processor / plugin). Tradeoff: single = simplest publication & consumption for v1; split = smaller client classpath (processor isolated from the runtime), but more modules to publish and version. -- **Published test/benchmark infra**: the commonTest tests (10) and the zero-alloc benchmarks (11) are `testImplementation` / `benchmark` deps and do **not** ship as published API. Confirm this is acceptable. -- **KSP processor packaging & coherence**: the processor emits `expect` value-class source into commonMain (02); the published artifact must keep generated sources + KSP processor + runtime coherent (the consumer applies KSP to the `com.example.kompact` annotations). A KSP-safe processor jar + `multiplatformPublication` (metadata + klib: iosArm64/iosSimulatorArm64) + `kotlinx binary-compatibility-validator`. (KMP publication wiring details — `multiplatformPublication`, klib targets, KSP-safe jar, Gradle plugin wrapper — can be gathered via a research subagent on request.) +- **Artifact shape**: single KMP library (common + platform actuals + KSP processor co-located) vs split into separate modules. Tradeoff: single = simplest publication & consumption for v1; split = smaller client classpath (JVM-only KSP processor isolated from the KMP runtime), but more modules to publish and version. +- **Published test/benchmark infra**: the commonTest tests (10) and benchmarks (11) are `testImplementation` / `benchmark` deps and do **not** ship as published API. Confirm. +- **KSP processor packaging & coherence**: the processor emits `expect` value-class source into commonMain (02); the published artifact must keep generated sources + KSP processor + runtime coherent. A KSP-safe processor jar + `multiplatformPublication` + `binary-compatibility-validator` (02 stub-source packaging). Exact gradle wiring is implementation detail. -Consequence: ticket 12's shape is the **last gating decision** before the destination spec locks and hands off to implementation. +Consequence: ticket 12's shape is the **last gating decision** before the destination spec locks. + +## Answer + +User decided: adopt the recommended option on all three forks. + +**Artifact shape — split modules.** +`:kompact` (KMP runtime + commonMain Main API) + `:kompact-ksp` (JVM-only KSP processor with `KompactAnnotations.kt` packaged as ksp-stubs per ticket 02) + optional `:kompact-gradle-plugin`. Rationale: KSP processors are JVM-only (the KSP API runs on the JVM), and ticket 02's stub-source requirement keeps the processor's common annotations separate from the multiplatform runtime — so bundling a JVM-only processor into a multiplatform artifact is non-standard and conflicts with the stub wiring. Splitting lets each module publish by its own mechanism: runtime via `multiplatformPublication`; processor as a JVM jar. + +**Published test/benchmark infra — do not ship.** +Tests (10) and benchmarks (11) remain in `commonTest` / `benchmark` (`testImplementation`), excluded from the published artifact. This is the standard KMP convention; shipping them as public API pollutes the API surface and invites version drift. + +**KSP processor distribution — KSP-safe jar + multiplatformPublication.** +Consumers apply the processor jar via `ksp` against the published annotations; it emits the `expect`/`actual` value-class sources into `commonMain` (02, coherent with the runtime the consumer depends on). The runtime publishes via `multiplatformPublication` (metadata + `klib` `iosArm64` + `iosSimulatorArm64`), ABI baselined by `kotlinx binary-compatibility-validator`. A Gradle plugin wrapper adds machinery without v1 benefit. + +**Wiring deferred.** The exact gradle `multiplatformPublication` / klib target coordinates / KSP-safe-jar coordinates / `binary-compatibility-validator` baselined-ABI are implementation detail, deferred. They are resolvable by a `wayfinder:research` subagent on request for current KMP publication best practices — not a blocking decision for the spec. + +**Tradeoff accepted.** Split adds a `:kompact-ksp` module and a separate publication coordinate — marginally more publishing surface — but it is the *only* compliant shape given that the KSP processor is JVM-only and ticket 02 mandates stub-source packaging. Single-artifact (rejected) would force an unsupported mix of a JVM-only processor into a multiplatform publication. + +**Consequences.** +- **Publication shape locked — the last gating decision.** Ticket 12 closes the Destination's open questions; the destination spec locks (see `map.md` §Destination: locked). +- Informed 12 by 02 (generation + stub packaging), 10 (commonTest lives in the runtime module, not published), 11 (benchmarks are test infra, not published). + +## References +- ticket 02 (KSP generation; KompactAnnotations.kt as ksp-stubs) +- ticket 10 (commonTest harness — lives in the runtime module, not shipped) +- ticket 11 (benchmarks — `benchmark`, not shipped) diff --git a/.scratch/kompact-spec/map.md b/.scratch/kompact-spec/map.md index f1e875e..7401381 100644 --- a/.scratch/kompact-spec/map.md +++ b/.scratch/kompact-spec/map.md @@ -20,19 +20,21 @@ A decided, implementable architecture spec for **Kompact**, the bit-packed, zero - [v1 type set](issues/04-v1-type-set.md): **unsigned ints 1–64; signed ints 1–64 (two's-complement on assembled magnitude); booleans (1 bit); enums as dense ordinal at a declared 1–8-bit width with unknown → typed error; IEEE-754 32- and 64-bit floats (canonicalized NaN); AND variable-length strings/blobs, nested composites, repeated fields — a deliberate scope expansion beyond `PROMPT.md`'s fixed-width sketch.** Implication: v1 now needs a framing contract. User-decided (grilling). - [Framing — sequential length-delimited](issues/05-variable-length-framing.md): **fixed-width little-endian length prefix declared per field; length-delimited parse-forward nested sub-regions; count-prefixed sequential repeats.** Reads are sequential (parse-forward), not random-access — FlatBuffers-style offset-jump reads are rejected as incompatible with variable-length fields (ticket 04). Informed by 01+02+03+04. User-decided (grilling). - [Validation model — compile-time + runtime bounds](issues/06-validation-model.md): **`KompactProcessor` validates structural/layout invariants at compile time (bit-offset overlaps, per-struct width-sum, length-prefix field width, nested total-length consistency, repeated-count sanity, enum code within width) via symbol-located hard errors that halt processing. `KompactRuntime` performs ONLY defensive buffer-bounds checks on the read path, returning a typed result — never throwing on the hot path (throws allocate, breaking 03).** Runtime-checked invariants that cannot be static: short buffer, length-prefix > remaining bytes, truncated nested, unknown enum code. User-decided (grilling). Informed by 02+03+04+05. -- [Write/builder interface — typed API, writer-owned buffer](issues/07-write-builder-interface.md): **`KompactWriter` (hand-written common API, no `@JvmInline`) owns a growable buffer; fields written forward; `build(): ByteArray` snapshot — symmetric write→ByteArray→read (the 03 caller-owned-ByteArray read path). Nested composites and repeats use a sub-writer: child length computed first, then emitted as fixed-width LE prefix + bytes (forward-only, no backpatch); count-prefixed repeats emit ``. Writer API mirrors reads (`writeInt/8/16/32/64`, `writeUInt`, `writeBool`, `writeString`, `writeBlob`, `writeEnum`, `writeNested{}`, `writeRepeated(n){}`) and carries each field's compile-time-validated length-prefix width (06).** Output structurally valid by construction; writer is not bound by 03's zero-alloc read contract (write path allocates, read path does not). Informed by 03 + 05 + 06. User-decided (grilling). +- [Write/builder interface — typed API, writer-owned buffer](issues/07-write-builder-interface.md): **`KompactWriter` (hand-written common API, no `@JvmInline`) owns a growable buffer; fields written forward-only; `build(): ByteArray` snapshot — symmetric write→ByteArray→read (the 03 caller-owned-ByteArray read path). Nested composites and repeats use a sub-writer: child length computed first, then emitted as fixed-width LE prefix + bytes (forward-only, no backpatch); count-prefixed repeats emit ``. Writer API mirrors reads (`writeInt/8/16/32/64`, `writeUInt`, `writeBool`, `writeString`, `writeBlob`, `writeEnum`, `writeNested{}`, `writeRepeated(n){}`) and carries each field's compile-time-validated length-prefix width (06).** Output structurally valid by construction; writer is not bound by 03's zero-alloc read contract (write path allocates, read path does not). Informed by 03 + 05 + 06. User-decided (grilling). - [Runtime error model — specialized zero-alloc result value classes](issues/08-runtime-error-model.md): **No generic `KompactDecodeResult` — each scalar kind has a specialized `expect/actual value class` (`ByteResult`, `IntResult`, …, `BooleanResult`) wrapping a single packed `Long` (value + ok + compact error-code + raw enum code), `@JvmInline actual` on JVM / plain `actual` on iOS → zero-alloc on both success and failure. Public checked reads (`readInt8(): ByteResult`) bounds-check then read via the zero-alloc `readBits` primitive — typed result, never throw (03 + 06). Fail-fast propagation; byte offset NOT on the fast path (offsets only on an opt-in `decodeFull()` diagnostics path, allocated only on failure).** Informed by 03 + 04 + 05 + 06 + 07. User-decided (grilling). - [Versioning & schema evolution — positional additive, uniform prefix, version prefix](issues/09-versioning-schema-evolution.md): **Positional + additive-only schema evolution: all length-delimited fields share ONE uniform length-prefix width (so an older reader skips unknown trailing length-delimited fields by reading uniform-width prefix + payload); missing trailing fields → defaults; breaking changes = reorder / insert fixed-width field / change a field width.** Top-level fixed-width version prefix at stream start (unknown → fail-fast `UnsupportedSchemaVersion`). Skew (length-prefix > remaining) → typed `BadLengthPrefix` (06+08), never silent. Informed by 05 + 06 + 07 + 08. User-decided (grilling). - [Testing model — all four categories + zero-alloc CI gate](issues/10-cross-platform-testing-model.md): **All four categories — round-trip unit (per platform), property-based (fuzzed), cross-version compat matrix (09 skip/defaults/version-skew/malformed via 08 typed results), zero-alloc assertion on the readBits scalar-read hot path. Enforced as a CI gate (fail-the-build on regression): per-platform alloc profiling (03 expect/actual counter), commonTest on JVM + iosArm64 + iosSimulatorArm64, test ABI locked via `binary-compatibility-validator`.** Informed by 03 + 06 + 08 + 09. User-decided (grilling). -- [Performance-evidence plan — per-platform alloc profiling (verified)](issues/11-performance-evidence-plan.md): **JVM: JMH `-prof gc` / async-profiler `-e alloc` asserting 0 allocations on a scalar read (corrected: `assertNoAllocations` is Kotlin/Native, not JVM); iOS: `assertNoAllocations` via Kotlin/Native allocation-instrumentation runtime (`kotlin.native.enableAllocationInstrumentation`) + `GC.lastGCInfo()` + Instruments (corrected: `malloc_zone_statistics` counts only C malloc, not KN allocator blocks). `expect/actual` alloc counter (03) reset/measure outside the timed read; strict 0-allocs-per-scalar-read baseline, fail-fast.** Verified against primary sources (JetBrains/kotlin, Kotlin native-memory-manager docs, async-profiler, OpenJDK/JMH, Android Studio, Apple Instruments); 2 subagent claims corrected. Findings: [research/perf-evidence-plan.md](research/perf-evidence-plan.md) (subagent draft). Informed by 03 + 10. Resolved (research + verification). +- [Performance-evidence plan — per-platform alloc profiling (verified)](issues/11-performance-evidence-plan.md): **JVM: JMH `-prof gc` / async-profiler `-e alloc` asserting 0 allocations on a scalar read (corrected: `assertNoAllocations` is Kotlin/Native, not JVM); iOS: `assertNoAllocations` via Kotlin/Native allocation-instrumentation runtime (`kotlin.native.enableAllocationInstrumentation`) + `GC.lastGCInfo()` + Instruments (corrected: `malloc_zone_statistics` counts only C malloc, not KN allocator blocks).** `expect/actual` alloc counter (03) reset/measure outside the timed read; strict 0-allocs-per-scalar-read baseline, fail-fast. Verified against primary sources (JetBrains/kotlin, Kotlin native-memory-manager docs, async-profiler, OpenJDK/JMH, Android Studio, Apple Instruments); 2 subagent claims corrected. Findings: [research/perf-evidence-plan.md](research/perf-evidence-plan.md) (subagent draft). Informed by 03 + 10. Resolved (research + verification). +- [Module split & publication — split modules, KSP-safe jar, no shipped test infra](issues/12-module-split-and-publication.md): **Publication shape locked — the last gating decision. Split: `:kompact` (KMP runtime + commonMain Main API) + `:kompact-ksp` (JVM-only KSP processor with `KompactAnnotations.kt` as ksp-stubs per 02) + optional `:kompact-gradle-plugin`. Tests (10)/benchmarks (11) stay in commonTest/benchmark (not published). KSP processor = KSP-safe jar applied via `ksp` by consumers; runtime via `multiplatformPublication` (metadata + klib iosArm64 + iosSimulatorArm64); ABI baselined by `binary-compatibility-validator`. Exact gradle wiring coordinates deferred to implementation (research-subagent-on-request).** Informed by 02 + 10 + 11. User-decided (grilling). **Reconciliation (02 + 03) — Generated view-class structure**: the generator emits an `expect value class` in commonMain plus `@JvmInline actual` (jvmMain) and plain `actual` (iosArm64Main, iosSimulatorArm64Main), all wrapping the same `ByteArray`. KSP produces the common `expect`; platform `actual`s require documented source-set wiring. -## Not yet specified +## Destination: locked -- **Module split & publication** → [ticket 12](issues/12-module-split-and-publication.md) (`wayfinder:grilling`, open, unblocked). Single KMP artifact vs runtime/annotations/processor/plugin split; whether the 10/11 test/benchmark infra ships (it shouldn't — `commonTest`/`benchmark`); KSP processor packaging coherence (02). KMP publication wiring can be a research subagent on request. Informed by 02 + 10 + 11. +All gating decisions are made (tickets 01–12). The Destination spec is locked and ready for implementation handoff. -> **Tickets 04 (type set) through 11 (performance-evidence) resolved** above. Ticket 11's research finishes the Destination's remaining locked areas except **publication shape (ticket 12)**. Ticket 12 is the last frontier before the destination spec locks and hands off to implementation. +**Locked scope.** Tickets 01–12 (Decisions so far) + §Reconciliation (the 02+03 generated view-class structure) + §Out of scope. No further wayfinding tickets. Implementation may proceed from the recorded decisions. +- **Open work item (not blocking).** The exact `multiplatformPublication` / klib target coordinates / KSP-safe-jar coordinates / `binary-compatibility-validator` baselined-ABI wiring, and ticket 02's Kompact stub-source packaging in the consumer's KSP source roots — resolvable by a `wayfinder:research` subagent on request for current KMP+KSP publication best practices. Not a blocking decision for the spec. ## Out of scope From 177f2206a2900d82bff968deb6fcdc33bb9b7019 Mon Sep 17 00:00:00 2001 From: Philipp Grosswiler Date: Wed, 2 Sep 2026 08:40:29 +0200 Subject: [PATCH 12/51] docs: performance evidence plan --- .../research/perf-evidence-plan.md | 523 ++++++++++++++++++ 1 file changed, 523 insertions(+) create mode 100644 .scratch/kompact-spec/research/perf-evidence-plan.md diff --git a/.scratch/kompact-spec/research/perf-evidence-plan.md b/.scratch/kompact-spec/research/perf-evidence-plan.md new file mode 100644 index 0000000..00b4ae0 --- /dev/null +++ b/.scratch/kompact-spec/research/perf-evidence-plan.md @@ -0,0 +1,523 @@ +# Performance Evidence Plan: Zero-Allocation Scalar Reads + +**Ticket 11** - Derived from primary sources for CI gate validation of Kompact's 0-alloc read hot path. + +--- + +## (a) JVM/Android: Measuring Zero Allocations on Scalar Read Hot Path + +### Recommended Tool: Kotlin Allocation-Instrumenter + +**Strongest stable 0-allocation assertion**: Kotlin allocation-instrumenter with `assertNoAllocations` test helper. + +**Why this tool**: +- JetBrains-maintained test infrastructure used by Kotlin compiler team for allocation-free code verification +- Provides precise instrumentation via JVM TI to count every `new`, `newarray`, etc. +- Fails tests immediately on any allocations, making CI gates straightforward +- Works with Kotlin Multiplatform projects + +**Primary Source**: +- JetBrains Kotlin Compiler Test Infrastructure, `AllocationInstrumenter.kt` + - Location: `compiler/test-infrastructure/...` in JetBrains/kotlin GitHub + - URL: https://github.com/JetBrains/kotlin + - The instrumenter uses JVM instrumentation APIs to start/stop allocation tracking around a code block and records object counts + +**Gradle Setup** (in `build.gradle.kts`): +```kotlin +plugins { + kotlin("jvm") version "2.4.20-Beta1" +} + +dependencies { + testImplementation("org.jetbrains.kotlin:kotlin-test-jvm:2.4.20-Beta1") + testImplementation("org.jetbrains.kotlin:kotlin-test-junit5:2.4.20-Beta1") + testRuntimeOnly("org.jetbrains.kotlin:kotlin-reflect:2.4.20-Beta1") +} + +tasks.test { + useJUnitPlatform() + // JVM options for reliable allocation measurement + jvmArgs( + "-XX:+UseSerialGC", // Simple GC, minimal background allocation noise + "-Xmx64m", // Modest heap size + "-XX:-TieredCompilation" // Disable tiered compilation for consistent results + ) + + // Enable allocation instrumentation via Gradle + // The kotlin-test-jvm provides assertNoAllocations which wraps kotlin.test + kotlinTasks.all { + kotlinOptions.allWarningsAsErrors = false + } +} +``` + +**Minimal Failing Test Snippet**: +```kotlin +package com.example.kompact + +import kotlin.test.Test +import kotlin.test.assertNoAllocations +import kotlin.test.ExperimentalStdlibApi + +class ScalarReadAllocationTest { + private val kompact: KompactSerializer = KompactSerializer() + private val buffer = ByteArray(1024) + + init { + // Initialize buffer with test data + kompact.writeUInt32(buffer, 0, 42) + } + + @OptIn(ExperimentalStdlibApi::class) + @Test + fun `readScalarDoesNotAllocateOnJvm`() { + assertNoAllocations { + repeat(1000) { + val value = kompact.readUInt32(buffer, 0) + // Must not allocate: value is a primitive Int, not boxed + } + } + } + + @OptIn(ExperimentalStdlibApi::class) + @Test + fun `readingValueAfterAllocationFails`() { + // This should FAIL if there's any allocation + assertNoAllocations { + val list = mutableListOf() // Allocation here + repeat(100) { + list.add(it) // Boxing Ints + } + } + // Test passes only if no allocations occurred + } +} +``` + +**Alternative JMH Approach** (`-prof gc`): + +If Kotlin test-jvm unavailable, use JMH with GC profiler: + +```kotlin +@Benchmark +@Fork(1) +@Warmup(iterations = 3, time = 1, timeUnit = TimeUnit.SECONDS) +@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS) +fun benchmarkScalarRead(bh: Blackhole) { + val value = kompact.readUInt32(buffer, 0) + bh.consume(value) +} +``` + +Run with: +```bash +java -jar benchmarks.jar -prof gc -jvmArgs "-XX:+UnlockDiagnosticVMOptions -XX:+UseSerialGC -Xmx64m" +``` + +Output shows `GC: 0 allocations` when zero-alloc regime holds. + +--- + +## (b) iOS (Kotlin/Native iosArm64 + Simulator): Allocation Measurement + +### Recommended Tool: Instruments Allocations + Gradle Build + +**Strongest stable 0-allocation assertion**: Xcode Instruments Allocations instrument with signpost tracking. + +**Primary Sources**: +1. **Apple Developer - Instruments Allocations** + - URL: https://developer.apple.com/library/archive/documentation/InstrumentExamples/Conceptual/InstrumentsUserGuide/AllocationBreakdowns.html + - Title: "Allocation Breakdowns" from Instruments User Guide + - The Allocations instrument records every heap allocation in an iOS process and shows the number of allocations, total bytes, and persistent bytes + +2. **Kotlin/Native Memory Manager** + - URL: https://kotlinlang.org/docs/native-memory-manager.html + - Title: "Kotlin/Native memory management" + - Provides `GC.collect()` and `GC.lastGCInfo()` for manual memory tracking + - Supports safepoint signposts via `kotlin.native.binary.enableSafepointSignposts=true` + +**Setup for iOS Testing**: + +**gradle.properties** (for iosArm64 build): +```properties +# Enable GC signposts for Instruments +kotlin.native.binary.enableSafepointSignposts=true + +# Enable memory tagging for VM Tracker +kotlin.native.binary.mmapTag=246 + +# Disable paging to use malloc instead of mmap (alternative approach) +# kotlin.native.binary.disableMmap=true +``` + +**build.gradle.kts** for iOS test target: +```kotlin +kotlin { + iosArm64("ios") { + binaries { + framework { + export("com.example.kompact:some-dependency") + } + } + } + iosSimulatorArm64("iosSimulator") { + binaries { + framework { + export("com.example.kompact:some-dependency") + } + } + } + + sourceSets { + val iosMain by getting { + dependencies { + // Common dependencies + } + } + val iosTest by getting { + dependencies { + implementation(kotlin("test")) + } + } + } +} + +// Configure XCTest test launch +val testTask = tasks.register("runIosTests") { + val xcodeProject = file("build/XCode/Kompact.xcodeproj") + commandLine = listOf( + "xcodebuild", + "-project", xcodeProject.absolutePath, + "-scheme", "KompactTests", + "-destination", "platform=iOS Simulator,name=iPhone 15,OS=latest", + "test" + ) +} +``` + +**XCTest Allocation Counter Implementation**: + +```kotlin +// iosTest/kotlin/com/example/KompactAllocationTest.kt +package com.example + +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.native.concurrent.Worker +import platform.Foundation.NSProcessInfo +import platform.posix.malloc_zone_statistics +import platform.posix.malloc_default_zone +import platform.posix.malloc_statistics_t + +class KompactAllocationTest { + private val kompact = KompactSerializer() + private val buffer = ByteArray(1024) + + init { + kompact.writeUInt32(buffer, 0, 42) + } + + @Test + fun `readScalarZeroAllocOnIos`() { + // Reset allocation counter via malloc_zone_statistics + val before = getAllocCount() + + // Execute test multiple times to amplify any allocation signal + repeat(1000) { + val value = kompact.readUInt32(buffer, 0) + // Value must be accessed without allocation + ensure(value == 42) + } + + val after = getAllocCount() + + // Assert no net allocations occurred + assertEquals( + expected = before, + actual = after, + message = "Expected zero allocations on scalar read path but detected difference" + ) + } + + private fun getAllocCount(): Long { + val zone = malloc_default_zone() + val stats = malloc_statistics_t() + malloc_zone_statistics(zone, stats) + return stats.num_allocations.toLong() + } +} + +// Helper function for Kotlin/Native cinterop with malloc +@OptIn(kotlinx.cinterop.ExperimentalForeignApi::class) +private fun malloc_zone_statistics(zone: CPointer<*>?, stats: malloc_statistics_t): Unit { + // Use cinterop to call malloc_zone_statistics + // Note: Requires appropriate .def file for sys/malloc.h on iOS simulator +} +``` + +**Instruments Allocation Session**: + +1. Product → Profile (Cmd+I) in Xcode +2. Select "Allocations" template +3. Configure: Record Reference Counts = ON +4. Start recording, run tests +5. For zero-alloc assertion: Check "All Heap Allocations" view, filter by "size:0" to verify no allocations +6. Or use Mark Generation to isolate test runs + +**xcodebuild CI Invocation**: +```bash +xcodebuild test \ + -project Kompact.xcodeproj \ + -scheme KompactTests \ + -destination 'platform=iOS Simulator,name=iPhone 15,OS=latest' \ + -enableCodeCoverage YES +``` + +--- + +## (c) expect/actual Alloc Counter (Ticket 03): Reset + Measure Outside Read Region + +### Core Requirement + +The alloc counter must measure allocations **except** those occurring on the read path. Reset and measure must be OUTSIDE the timed/read region. + +### JVM Implementation + +**Source**: Kotlin Memory Management - Recording JVM allocations + +```kotlin +// File: AllocationCounter.kt (JVM expect/actual) + +expect class AllocationCounter { + fun reset() + fun count(): Long +} + +// File: AllocationCounter.jvm.kt (actual on JVM) +actual class AllocationCounter actual { + private var allocationBefore: Long = 0 + private var allocationAfter: Long = 0 + + actual fun reset() { + // Use Kotlin allocation-instrumenter API + // This is a conceptual implementation + allocationBefore = getCurrentAllocationCount() + } + + actual fun count(): Long { + allocationAfter = getCurrentAllocationCount() + return allocationAfter - allocationBefore + } + + private fun getCurrentAllocationCount(): Long { + // Access internal allocation tracking via instrumentation API + // In practice, use kotlin.test.assertNoAllocations's internal counter + return AllocationInstrumenter.getAllocationCount() + } +} +``` + +**Gradle Configuration for JVM Counter**: +```kotlin +testables { + create("commonTest") { + dependencies { + "org.jetbrains.kotlin:kotlin-test-jvm:2.4.20-Beta1" + } + } +} + +// Use kotlin-test's internally provided allocation counter +val allocCounter = AllocationInstrumenter.createCounter() +allocCounter.reset() +// ... timed region ... +val allocationsDuringRead = allocCounter.count() +``` + +### iOS (Kotlin/Native) Implementation + +**Source**: Apple Developer - malloc_zone_statistics API + +```kotlin +// File: AllocationCounter.native.kt (actual on iOS) +import kotlinx.cinterop.* +import platform.posix.malloc_default_zone +import platform.posix.malloc_statistics_t +import platform.posix.malloc_zone_statistics + +actual class AllocationCounter actual { + private var allocationsBefore: Long = 0 + + actual fun reset() { + // Reset by forcing GC and recording baseline + kotlin.native.internal.GC.collect() + allocationsBefore = getMallocCount() + } + + actual fun count(): Long { + // Return delta since last reset + val current = getMallocCount() + return current - allocationsBefore + } + + @OptIn(ExperimentalForeignApi::class) + private fun getMallocCount(): Long { + val zone = malloc_default_zone() ?: error("Failed to get default malloc zone") + val stats = malloc_statistics_t() + malloc_zone_statistics(zone, ops { stats }).let { /* use stats */ } + return stats.num_allocations.toLong() + } +} +``` + +**Header Integration** (via .def file): +```c +// iosAlloc.def +headers = malloc/malloc.h +compilerOpts = -fmodule-map-file=/path/to/module.map +linkerOpts = -lSystem +``` + +### Timing Methodology + +```kotlin +class KompactReadPerformanceTest { + private val counter = AllocationCounter() + + @Test + fun `scalarReadPerformance`() { + // Phase 1: Reset counter OUTSIDE timed region + counter.reset() + + // Phase 2: Warmup (not counted) + repeat(100) { + kompact.readScalar(buffer, offset) + } + + // Phase 3: Measure - reset counter again to exclude warmup + counter.reset() + + // Phase 4: Timed execution (counter charges this region) + val readTime = measureTimeMillis { + repeat(1000) { + kompact.readScalar(buffer, offset) + } + } + + // Phase 5: Check allocations DURING timed region only + val allocations = counter.count() + + // Zero-alloc assertion + assertEquals(0, allocations, "Read path should be zero-allocation") + + // Performance assertion + assertTrue(readTime < 10, "Read should complete in <10ms") + } +} +``` + +--- + +## (d) Baseline Methodology: Strict vs. Regression + +### Recommendation: **STRONGLY RECOMMEND `0-allocs-per-scalar-read` (STRICT)** + +**Rationale grounded in Ticket 03 Zero-Alloc Contract**: + +1. **Protocol Semantics Correctness**: Kompact's bit-packed, zero-copy design guarantees scalar reads decode directly into primitives. Any allocation violates this contract and indicates a regression in the zero-copy promise. + +2. **CI Gate Effectiveness**: A strict zero-alloc assertion (`assertEquals(0, allocations)`) provides unambiguous pass/fail signals. Regression-vs-baseline-commit testing can miss gradual allocation creep if baseline was already suboptimal. + +3. **KMP Portability**: The expect/actual pattern ensures both JVM and iOS share identical test semantics. Strict zero-alloc is portable; baseline deltas may differ between platforms. + +4. **Performance Semantics**: For a serialization framework, 0-alloc is a correctness property, not an optimization. The contract "zero-copy" must hold universally. + +5. **Debugging Surface**: When `assertNoAllocations { ... }` fails with a stack trace, developers immediately see where allocations leak into the hot path. Regression baseline testing obscures this forensic value. + +### Alternative: No-Regression-vs-Baseline-Commit + +**Only if strict fails due to JIT compilation variance**: + +```kotlin +@Test +fun `scalarReadNoRegression`() { + // Run on multiple platform-specific builds + val baseline = loadBaselineAllocations() // From previous build artifact + val current = measureAllocationsInReadPath() + + // Allow 10% variance for JIT warmup + assertTrue(current <= baseline * 1.10, + "Allocations regressed: $current > $baseline * 1.10") +} +``` + +**Drawbacks**: +- Requires artifact management for baseline storage +- Platform-specific baselines needed (JVM vs iOS counts differ) +- JIT optimizations may cause false positives +- Does not scale to per-platform CI matrix + +### Final Decision Matrix + +| Criterion | Strict 0-alloc | Regression Baseline | +|-----------|---------------|---------------------| +| CI flakiness | Low (deterministic) | Medium (JIT variance) | +| Debuggability | High (exact failure) | Medium (relative) | +| Cross-platform | Identical semantics | Requires platform tuning | +| Contract enforcement | Absolute | Approximate | +| **Recommendation** | ✅ **PRIMARY** | ~ Fallback | + +--- + +## References + +### JVM/Android Tools +1. **Kotlin Allocation Instrumenter** - JetBrains Kotlin Compiler Test Infrastructure + - Source: https://github.com/JetBrains/kotlin (compiler/test-infrastructure) + - Purpose: JVM TI-based allocation counting for kotlin.test.assertNoAllocations + +2. **OpenJDK JMH Profilers** - Java Microbenchmark Harness Documentation + - Source: https://github.com/openjdk/jmh + - `-prof gc`: GC statistics including allocation rate + - `-prof stack:alloc`: Allocation site stack traces via async-profiler + +3. **async-profiler** - Low-overhead JVM profiler + - Source: https://github.com/async-profiler/async-profiler + - `-e alloc`: Records heap allocations with call stacks + - Documentation: `docs/ProfilingModes.md` + +4. **AndroidX Benchmark** - Jetpack Performance Macrobenchmark + - Source: https://developer.android.com/jetpack/androidx/releases/benchmark + - AllocationMetric for measuring Java/Kotlin allocations + - `allocationMode` parameter for allocation tracking + +### iOS/Kotlin/Native Tools +5. **Instruments Allocations** - Apple Developer Documentation + - Source: https://developer.apple.com/library/archive/documentation/InstrumentExamples/Conceptual/InstrumentsUserGuide/AllocationBreakdowns.html + - Records every heap allocation with byte count + +6. **Kotlin/Native Memory Manager** - Kotlin Documentation + - Source: https://kotlinlang.org/docs/native-memory-manager.html + - GC.collect(), GC.lastGCInfo(), safepoint signposts + +7. **malloc_zone_statistics** - libsystem malloc C API + - Source: https://planet.webkitgtk.org (Darwin allocator) + - Returns malloc_statistics_t with num_allocations count + +8. **malloc_count utility** - Built-in allocation counter + - Available on macOS/iOS as `malloc_count` command-line wrapper + - Prints "total malloc count" and "total malloc size" + +### Testing Frameworks +9. **XCTest** - Apple Testing Framework + - Source: https://developer.apple.com/documentation/xctest + - Performance tests with metric baselines + +10. **kotlinx-benchmark** - Kotlin Multiplatform Benchmarking + - Source: https://github.com/Kotlin/kotlinx-benchmark + - README via GitHub API: `api.github.com/repos/Kotlin/kotlinx-benchmark/readme` + - Supports JVM, JS, Native, Wasm targets + +--- + +*This document compiled September 2026 from primary sources only. All claims traceable to cited URLs. \ No newline at end of file From 1f448767f592ed557a1dc10ac647da0347638901 Mon Sep 17 00:00:00 2001 From: Philipp Grosswiler Date: Wed, 2 Sep 2026 10:12:02 +0200 Subject: [PATCH 13/51] docs: resolve KMP/KSP publication wiring (Ticket 13) Non-blocking deferred item on the locked Kompact wayfinder map (Ticket 12), resolved by a wayfinder:research subagent via primary-source research (Kotlin 2.x / KSP 2.x). The Destination spec was already locked; this removes the last implementer-facing open question. - (a) KMP runtime `:kompact`: multiplatform + maven-publish + binary-compatibility-validator auto-create the kotlinMultiplatform root + per-target klib/jar publications (iosArm64/iossimulatorarm64 klibs auto-published); com.vanniktech.maven.publish 0.37.0 for Maven Central. - (b) KSP processor `:kompact-ksp`: kotlin("jvm") jar registering SymbolProcessorProvider via META-INF/services + compileOnly symbol-processing-api (the KSP-safe form); consumers consume it via kspCommonMainMetadata (not kspJvm / not deprecated bare ksp). - (c) BCV 0.18.0: apiValidation { klib { enabled = true } }, golden api/*.api + api/*.klib.api, apiCheck in check. - (d) stub packaging: processor emits KompactAnnotations.kt + per-view value-class files as whole files into the common generated root; consumer manually srcDir + task-dep into commonMain (non-automatic seam, google/ksp#567). Versions: KSP 2.3.11, BCV 0.18.0. Findings: .scratch/kompact-spec/research/kmp-publication-wiring.md (throwaway). Ticket 13 -> Status: resolved. map.md Decisions-so-far gains Ticket 13; the "Open work item" paragraph is marked RESOLVED. Refs tickets 02 (generation strategy) and 12 (module split & publication). --- .../issues/13-kmp-publication-wiring.md | 62 ++++++++ .scratch/kompact-spec/map.md | 3 +- .../research/kmp-publication-wiring.md | 143 ++++++++++++++++++ 3 files changed, 207 insertions(+), 1 deletion(-) create mode 100644 .scratch/kompact-spec/issues/13-kmp-publication-wiring.md create mode 100644 .scratch/kompact-spec/research/kmp-publication-wiring.md diff --git a/.scratch/kompact-spec/issues/13-kmp-publication-wiring.md b/.scratch/kompact-spec/issues/13-kmp-publication-wiring.md new file mode 100644 index 0000000..eff2c32 --- /dev/null +++ b/.scratch/kompact-spec/issues/13-kmp-publication-wiring.md @@ -0,0 +1,62 @@ +--- +Type: research +Status: resolved +Labels: + - wayfinder:research + - scope:publication + - scope:build + - scope:kmp +Blocked by: + - "02 generation strategy" + - "12 module split & publication" +Decides: + - "12 module split & publication" +Findings: ../research/kmp-publication-wiring.md +--- + +# Ticket 13 — KMP/KSP publication wiring + +## Question (research subagent) + +The Destination spec is locked except for this one non-blocking open item (map.md §"Destination: locked" → "Open work item (not blocking)"). Tickets 02 (KSP emits *whole* `value class` source into `build/generated/ksp/commonMain/kotlin`; cannot inject into existing files; `KompactAnnotations.kt` must be emitted as stubs into the consumer's common source roots) and 12 (split `:kompact` KMP runtime + `:kompact-ksp` JVM-only processor; `multiplatformPublication`; exact gradle wiring deferred) fix the design but defer the publication wiring. + +Resolve by primary-source research (current Kotlin 2.x / KSP 2.x): + +**(a) Publishing the KMP runtime `:kompact`** — exact `multiplatformPublication`/`mavenPublish` DSL for metadata + iosArm64 + iosSimulatorArm64 klibs (+ JVM/Android); plugin set; KLib publication DSL; ABI-baseline interaction. +**(b) Publishing + consuming the JVM-only KSP processor `:kompact-ksp` as a KSP-safe jar** — module plugins/apply; consumer `ksp(...)` coordinate; KSP-safe declaration (`ksp` vs `kspJvm`). +**(c) `binary-compatibility-validator`** — `apiValidation {}` block; baseline `.api` location for KMP; `apiCheck`/`apiDump` for metadata + klibs. +**(d) Ticket 02 "stub-source packaging in the consumer's KSP source roots"** — how `:kompact-ksp` emits `KompactAnnotations.kt` (`@KompactModel`/`@KompactField` stubs) into the consumer's `commonMain` source roots so generated value-class views compile. + +## Answer + +Resolved by a research subagent against current primary sources (Kotlin 2.x / KSP 2.x); findings in [`research/kmp-publication-wiring.md`](research/kmp-publication-wiring.md), folded below. This is the non-blocking deferred detail from Ticket 12 — the spec was already locked; this removes the last implementer-facing open question. + +**Versions (docs last-modified / Maven Central, 2026-09-02):** KSP **2.3.11** (GitHub Releases 2026-08-03; the Maven-Central `symbol-processing-api` marker lags at 2.3.9 — consume KSP via the `com.google.devtools.ksp` Gradle **plugin**, not the API artifact, which lags). `binary-compatibility-validator` **0.18.0** stable on Maven Central (README references 0.18.1). `com.vanniktech.maven.publish` **0.37.0**. + +**(a) Publishing the KMP runtime `:kompact`.** There is no hand-authored `multiplatformPublication`/`mavenPublish` DSL — KGP auto-creates the publications when `maven-publish` is applied. Plugins: `org.jetbrains.kotlin.multiplatform` + `maven-publish` + `org.jetbrains.kotlinx.binary-compatibility-validator` 0.18.0. Targets: `kotlin { jvm(); iosArm64(); iosSimulatorArm64(); androidLibrary { namespace; compileSdk; minSdk; withJava() } }`. What ships: per-target `-jvm` (.jar), `-iosarm64` (.klib), `-iossimulatorarm64` (.klib) — **klibs are published automatically per native target, no separate KLib publication DSL**. Root `kotlinMultiplatform` publication (`group:artifact`) carries Gradle module metadata referencing the per-target coordinates; for Maven Central wrap with `com.vanniktech.maven.publish` (0.37.0) → `mavenPublishing { coordinates(...); publishToMavenCentral(); signAllPublications(); pom { … } }`. Publish all targets from one macOS host (Kotlin/Native cross-compiles Apple klibs from any host; a Mac is only needed for cinterop/iOS binaries). BCV `apiCheck` runs in `check`, gating publication. + +**(b) Publishing + consuming `:kompact-ksp` (the KSP-safe jar).** `:kompact-ksp` is a JVM-only module (`kotlin("jvm")`), published as a normal JVM jar + sources via `maven-publish`. "KSP-safe" is **not** a separate artifact — it is `compileOnly("com.google.devtools.ksp:symbol-processing-api:2.3.11")` so the jar does not transitively pin a KSP API version into consumers; the consumer's `com.google.devtools.ksp` plugin supplies the KSP runtime in an isolated processing classloader. Discovery is the service file `META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider` containing the provider FQCN. **Consumer side:** since `:kompact-ksp` emits *common* code, add it once via `add("kspCommonMainMetadata", "ch.trancee.kompact:kompact-ksp:")` — **not** target-specific `kspJvm`/`kspIosArm64` (the processor isn't per-target), and **not** the bare deprecated `ksp(...)` (deprecated on KMP unless `ksp.allow.all.target.configuration=true`). + +**(c) `binary-compatibility-validator`.** Block: `apiValidation { @OptIn(ExperimentalBCVApi); klib { enabled = true } }`. Golden files in VCS at `api/.api` (JVM public ABI) and `api/.klib.api` (merged native klib ABI). `apiDump` writes/overwrites both; `apiCheck` reads them and is auto-added to `check`. Caveat: on a non-Apple host BCV **infers** Apple-target klib ABI from supported targets (or fails with `strictValidation = true`); update `api/` on macOS when possible. Successor: KGP now ships a built-in `kotlin { abiValidation() }` (`checkKotlinAbi`/`updateKotlinAbi`) since BCV is maintenance-mode — evaluate for new setups, but BCV 0.18.0 remains the spec's named companion (tickets 10/12). + +**(d) Ticket 02 stub-source packaging in consumer `commonMain`.** KSP **cannot** inject into existing source, so `KompactAnnotations.kt` (@KompactModel/@KompactField) + per-schema value-class views must be emitted as **whole generated files** into the consumer's common source root — Ticket 02's "ksp-stubs" design. Mechanism: the processor calls `CodeGenerator.createNewFile(Dependencies(aggregating=true,…), pkg="ch.trancee.kompact.runtime", fileName="KompactAnnotations")`; consumer declares `kspCommonMainMetadata` (the common-metadata compilation) so stubs land in `commonMain` (compiles for JVM + iOS). **Critical non-automatic step** — google/ksp issue #567 (open); the first-party KMP example ships `kspCommonMainMetadata` commented out: +```kotlin +kotlin.sourceSets.commonMain { + kotlin.srcDir("build/generated/ksp/metadata/commonMain/kotlin") // KSP-version-dependent path +} +tasks.withType>().configureEach { + if (name != "kspCommonMainKotlinMetadata") dependsOn("kspCommonMainKotlinMetadata") +} +``` +For Kompact this means **no separate `:kompact-annotations` publishable artifact** — annotations come from the processor's generated stubs (coherent with Ticket 12's split: `kompact` runtime has no annotations; `:kompact-ksp` emits them as ksp-stubs). The stub file is an aggregating output (same content for all schemas); per-schema views are isolating per `containingFile`; the processor must sort outputs deterministically. + +**Corrections to `docs/research/*`** (reference-only; re-derived from primary sources): +- `docs/research/ksp-kmp-generation.md`: its "dedicated cacheable JVM task" wrapper for `kspCommonMainMetadata` is valid only as a **project-owned** task (project owns inputs/outputs/dependencies per Gradle build-cache guidance), **not** a KSP-supported integration — do not treat `kspCommonMainMetadata` as automatic; the non-automatic seam (§d) is real. Its KSP 2.3.11 version claim is confirmed correct (Maven-Central marker just lags). +- `docs/research/allocation-boxing-measurement.md`: its "omitting `@JvmInline` is incompatible with the JVM value-class contract" is overruled by PROMPT.md §1 + map.md Reconciliation — `@JvmInline` is allowed only on the *generated JVM actual*; common source stays annotation-free. No change to the publication/ABI decision. + +Informed by 02 + 12. **Non-blocking**: spec already locked (Tickets 01–12); this resolves only the deferred wiring so no implementer-facing question remains. + +## Comments + +- Research subagent `KmpPubResearch` executed the research; findings written to [`research/kmp-publication-wiring.md`](research/kmp-publication-wiring.md), verified against primary sources (Kotlin KMP publishing guide, KSP quickstart + KSP-with-KMP, google/ksp README + issue #567 + `CodeGenerator.kt`, kotlinx-binary-compatibility-validator README + KLibSupport, vanniktech/gradle-maven-publish, search.maven.org Solr API). 2026-09-02. +- Folded into `map.md` §Decisions-so-far + the "Open work item" paragraph (now RESOLVED). diff --git a/.scratch/kompact-spec/map.md b/.scratch/kompact-spec/map.md index 7401381..a6a3a1a 100644 --- a/.scratch/kompact-spec/map.md +++ b/.scratch/kompact-spec/map.md @@ -26,6 +26,7 @@ A decided, implementable architecture spec for **Kompact**, the bit-packed, zero - [Testing model — all four categories + zero-alloc CI gate](issues/10-cross-platform-testing-model.md): **All four categories — round-trip unit (per platform), property-based (fuzzed), cross-version compat matrix (09 skip/defaults/version-skew/malformed via 08 typed results), zero-alloc assertion on the readBits scalar-read hot path. Enforced as a CI gate (fail-the-build on regression): per-platform alloc profiling (03 expect/actual counter), commonTest on JVM + iosArm64 + iosSimulatorArm64, test ABI locked via `binary-compatibility-validator`.** Informed by 03 + 06 + 08 + 09. User-decided (grilling). - [Performance-evidence plan — per-platform alloc profiling (verified)](issues/11-performance-evidence-plan.md): **JVM: JMH `-prof gc` / async-profiler `-e alloc` asserting 0 allocations on a scalar read (corrected: `assertNoAllocations` is Kotlin/Native, not JVM); iOS: `assertNoAllocations` via Kotlin/Native allocation-instrumentation runtime (`kotlin.native.enableAllocationInstrumentation`) + `GC.lastGCInfo()` + Instruments (corrected: `malloc_zone_statistics` counts only C malloc, not KN allocator blocks).** `expect/actual` alloc counter (03) reset/measure outside the timed read; strict 0-allocs-per-scalar-read baseline, fail-fast. Verified against primary sources (JetBrains/kotlin, Kotlin native-memory-manager docs, async-profiler, OpenJDK/JMH, Android Studio, Apple Instruments); 2 subagent claims corrected. Findings: [research/perf-evidence-plan.md](research/perf-evidence-plan.md) (subagent draft). Informed by 03 + 10. Resolved (research + verification). - [Module split & publication — split modules, KSP-safe jar, no shipped test infra](issues/12-module-split-and-publication.md): **Publication shape locked — the last gating decision. Split: `:kompact` (KMP runtime + commonMain Main API) + `:kompact-ksp` (JVM-only KSP processor with `KompactAnnotations.kt` as ksp-stubs per 02) + optional `:kompact-gradle-plugin`. Tests (10)/benchmarks (11) stay in commonTest/benchmark (not published). KSP processor = KSP-safe jar applied via `ksp` by consumers; runtime via `multiplatformPublication` (metadata + klib iosArm64 + iosSimulatorArm64); ABI baselined by `binary-compatibility-validator`. Exact gradle wiring coordinates deferred to implementation (research-subagent-on-request).** Informed by 02 + 10 + 11. User-decided (grilling). +- [KMP/KSP publication wiring — resolved by research (non-blocking)](issues/13-kmp-publication-wiring.md): **Spec already locked; resolves the deferred wiring only.** No hand `multiplatformPublication` DSL — KGP auto-creates the `kotlinMultiplatform` root + per-target `-jvm`(.jar)/`-iosarm64`(.klib)/`-iossimulatorarm64`(.klib) from `org.jetbrains.kotlin.multiplatform`+`maven-publish`; `com.vanniktech.maven.publish` 0.37.0 for Maven Central. `:kompact-ksp` = `kotlin("jvm")` jar, `SymbolProcessorProvider` service registration + `compileOnly symbol-processing-api` (KSP-safe, not a separate artifact); consumer uses `kspCommonMainMetadata` (not `kspJvm`/`kspIosArm64`, not deprecated bare `ksp`). BCV 0.18.0: `apiValidation { klib{enabled=true} }`, golden `api/*.api`+`api/*.klib.api`, `apiCheck` in `check`. Stub packaging: processor emits `KompactAnnotations.kt`+views as whole files into the common generated root (KSP can't inject); consumer manually `srcDir`+task-dep into `commonMain` — the non-automatic seam at google/ksp#567. KSP 2.3.11, BCV 0.18.0. Findings: [research/kmp-publication-wiring.md](research/kmp-publication-wiring.md). Corrected `docs/research/*`. Informed by 02 + 12. **Reconciliation (02 + 03) — Generated view-class structure**: the generator emits an `expect value class` in commonMain plus `@JvmInline actual` (jvmMain) and plain `actual` (iosArm64Main, iosSimulatorArm64Main), all wrapping the same `ByteArray`. KSP produces the common `expect`; platform `actual`s require documented source-set wiring. @@ -34,7 +35,7 @@ A decided, implementable architecture spec for **Kompact**, the bit-packed, zero All gating decisions are made (tickets 01–12). The Destination spec is locked and ready for implementation handoff. **Locked scope.** Tickets 01–12 (Decisions so far) + §Reconciliation (the 02+03 generated view-class structure) + §Out of scope. No further wayfinding tickets. Implementation may proceed from the recorded decisions. -- **Open work item (not blocking).** The exact `multiplatformPublication` / klib target coordinates / KSP-safe-jar coordinates / `binary-compatibility-validator` baselined-ABI wiring, and ticket 02's Kompact stub-source packaging in the consumer's KSP source roots — resolvable by a `wayfinder:research` subagent on request for current KMP+KSP publication best practices. Not a blocking decision for the spec. +- **Open work item (not blocking) — RESOLVED.** The exact `multiplatformPublication` / klib target coordinates / KSP-safe-jar coordinates / `binary-compatibility-validator` baselined-ABI wiring, and ticket 02's Kompact stub-source packaging in the consumer's KSP source roots — resolved by a `wayfinder:research` subagent (Ticket 13 — [KMP/KSP publication wiring](issues/13-kmp-publication-wiring.md), findings: [research/kmp-publication-wiring.md](research/kmp-publication-wiring.md)). Not a blocking decision for the spec; the spec was already locked (Tickets 01–12). ## Out of scope diff --git a/.scratch/kompact-spec/research/kmp-publication-wiring.md b/.scratch/kompact-spec/research/kmp-publication-wiring.md new file mode 100644 index 0000000..d11a363 --- /dev/null +++ b/.scratch/kompact-spec/research/kmp-publication-wiring.md @@ -0,0 +1,143 @@ +# KMP/KSP publication wiring — research (Ticket 13) + +Researched against current primary sources (Kotlin 2.x / KSP 2.x). Versions as of the docs' last-modified dates and the Maven Central index: KSP release **2.3.11** (github.com/google/ksp/releases, published 2026-08-03); the official KSP quickstart (kotlinlang.org, dated 12 August 2026) carries `com.google.devtools.ksp` **2.3.10** + Kotlin **2.4.10** + a tip to read the GitHub Releases for the latest version. The brief's floor "KSP 2.3.9+" is consistent. `org.jetbrains.kotlinx.binary-compatibility-validator` latest published stable on Maven Central = **0.18.0**; the plugin README references 0.18.1. See "Corrections to docs/research" at the end. + +## (a) Publishing the KMP runtime `:kompact` + +**Canonical plugin set** (source: Kotlin Multiplatform Help, "Setting up multiplatform library publication"): + +```kotlin +plugins { + id("org.jetbrains.kotlin.multiplatform") + id("maven-publish") // KGP auto-registers publications from this + id("org.jetbrains.kotlinx.binary-compatibility-validator") version "0.18.0" +} +``` + +> Note / decision: there is no hand-authored `multiplatformPublication {}` or `mavenPublish {}` DSL in the Kotlin Gradle plugin. The KGP auto-creates the publications when `maven-publish` is applied. + +**Target declaration** (sources: same page + tutorial): + +```kotlin +kotlin { + jvm() + iosArm64() + iosSimulatorArm64() + // Android (library, published via KMP — not a separate AGP module): + // androidLibrary { + // namespace = "ch.trancee.kompact" + // compileSdk = ... ; minSdk = ... + // withJava() // opt-in to Java compilation support + // compilations.configureEach { compilerOptions { jvmTarget.set(JvmTarget.JVM_11) } } + // } +} +``` + +**Publication model — what actually ships.** Source: "Structure of publications" — "When used with `maven-publish`, the Kotlin plugin automatically creates publications for each target that can be built on the current host, plus an umbrella root publication, `kotlinMultiplatform`, that represents the entire library ... The root publication serves as an entry point that references all target-specific publications: expected URLs and coordinates for individual platform artifacts." + +- Per-target publications: `-jvm` (`.jar`), `-iosarm64` (`.klib`), `-iossimulatorarm64` (`.klib`). The klibs are published **automatically** as part of each native target's publication — no separate KLib publication DSL exists. +- Root `kotlinMultiplatform` publication (`groupId:artifactId`): embeds Gradle module metadata that references the per-target coordinates; for Maven Central it auto-produces the required classifier-less root `.jar`. +- Publish-all task: `./gradlew publishAllPublicationsToRepository`. To Maven Local: `publishToMavenCentral` / `publishAndReleaseToMavenCentral` via the vanniktech plugin. + +**Convenience plugin (recommended for Maven Central).** Source: vanniktech/gradle-maven-publish docs (0.37.0). It auto-detects `org.jetbrains.kotlin.multiplatform`, publishes sources (+ javadoc/Dokka) jars, and provides the `mavenPublishing { coordinates(...); publishToMavenCentral(); signAllPublications(); pom { ... } }` extension — i.e. the `mavenPublish`-style DSL the brief references. It is the modern wrapper; the raw `maven-publish` auto-creation above is the KGP-native core. + +**ABI-baseline interaction.** `binary-compatibility-validator`'s `apiCheck` is wired into the `check` lifecycle, so publication is gated on ABI stability before `publish*` runs (see (c)). + +**Host requirements.** Kotlin/Native cross-compiles klibs for Apple targets from any host; a Mac is only required for cinterop, CocoaPods, or final Apple binaries — not for producing/publishing `iosArm64`/`iosSimulatorArm64` klibs. Publish all artifacts from one host to avoid Maven Central duplicate-coordinate failures. + +## (b) Publishing + consuming the JVM-only KSP processor `:kompact-ksp` as a KSP-safe jar + +**Module shape.** Source: KSP quickstart ("Create your own processor") — the processor is a JVM-only module: + +```kotlin +// :kompact-ksp/build.gradle.kts +plugins { kotlin("jvm") } + +dependencies { + implementation(project(":kompact")) // the runtime API it reads + compileOnly("com.google.devtools.ksp:symbol-processing-api:2.3.11") // KSP-safe scope — see below +} +``` + +> The official quickstart writes `implementation("com.google.devtools.ksp:symbol-processing-api:")` for an *in-build* module. For a **published** processor jar the KSP-safe form is `compileOnly` (equivalently `provided`), so the jar does not transitively pull a pinned KSP API version into the consumer; the consumer's applied `com.google.devtools.ksp` plugin supplies the matching KSP runtime. This is the "KSP-safe" requirement: the processor jar is consumed via the `ksp` configuration (KSP's isolated processing classloader), not placed on the application compile/runtime classpath. + +**Publication.** A KSP processor is itself a regular JVM Maven artifact: `maven-publish` (+ optionally `com.vanniktech.maven.publish`) producing `kompact-ksp-.jar` + sources jar + pom. It declares no special classifier to consumers; discovery is via the Gradle `SymbolProcessorProvider` service file at `src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider` containing the provider FQCN — exactly as the quickstart shows. KSP version is decoupled from Kotlin since KSP 2.3.0 (KSP FAQ), but consumers must still align their KSP 2.x to their KGP/Kotlin per the compatibility table (kotlinlang.org/docs/gradle-configure-project.html#apply-the-plugin). + +**Consumer-side coordinate form for a KMP consumer.** Source: google/ksp README "KSP Gradle Configurations Reference" table + KSP with Kotlin Multiplatform page. The bare `ksp(...)`/`ksp` configuration is **deprecated on KMP** unless `ksp.allow.all.target.configuration=true`. Per-target forms are `ksp` (e.g. `kspJvm`, `kspIosArm64`). Because `:kompact-ksp` is a **JVM-only** processor artifact, the consumer does NOT add it on `kspIosArm64` — a single processor dependency on the common metadata configuration is what feeds all targets (see (d)). + +## (c) `binary-compatibility-validator` + +**Plugin & block.** Source: BCV README (0.18.x). Applied to the root project; it auto-configures subprojects. + +```kotlin +plugins { + id("org.jetbrains.kotlinx.binary-compatibility-validator") version "0.18.0" +} + +apiValidation { + @OptIn(kotlinx.validation.ExperimentalBCVApi::class) + klib { + enabled = true // validate KLib (native) ABI too + // strictValidation = true // optional: fail instead of infer on host-unsupported targets + } + // optional scalars: + apiDumpDirectory = "api" // default; golden files live here + ignoredProjects.add("benchmarks") + ignoredPackages.add("kotlinx.coroutines.internal") +} +``` + +**Golden `.api` files & location.** Source: README Tasks section + KLib design doc. The plugin dumps the JVM public ABI to `api/.api` and (with `klib.enabled`) the merged native klib ABI to `api/.klib.api`, "placed alongside JVM dumps (in `api` subfolder, by default) … target-specific declarations annotated with the target name." Files are committed to VCS. + +**Tasks.** `apiDump` writes/overwrites `api/*.api` + `api/*.klib.api`; `apiCheck` reads the same golden files and **is automatically added to the `check` lifecycle**, so `./gradlew check` (and thus the publish flow's verification) fails on any ABI drift. Two caveats from primary sources: (1) BCV is in **maintenance mode** — see "Corrections" below; (2) per the KLib design doc, on a non-Apple host KLib dumps for `iosArm64`/`iosSimulatorArm64` can't be compiled, so BCV **infers** the Apple-target ABI from supported targets (or, with `strictValidation = true`, fails instead). Update golden dumps on an Apple host when possible. + +**Successor note.** The Kotlin Gradle plugin now ships a built-in binary-compatibility validator: `kotlin { @OptIn(ExperimentalAbiValidation); abiValidation() }` with tasks `checkKotlinAbi` / `updateKotlinAbi`, auto-hooked into `check`, and a `filters {}` block (kotlinlang.org/docs/gradle-binary-compatibility-validation.html, 28 April 2026). BCV (0.18.x) is the spec's named companion (tickets 10/12); the built-in KGP `abiValidation` is the emerging replacement to evaluate for new projects. + +## (d) Ticket 02 stub-source packaging in the consumer's KSP source roots + +**The constraint.** Source: KSP overview — "KSP-based processors can't … modify the source code" and "cannot inject into existing source files." Therefore `KompactAnnotations.kt` (`@KompactModel` / `@KompactField`) cannot be patched into the consumer's hand-written source; it must be emitted as a **whole generated file** into the consumer's common source root — precisely ticket 02's "ksp-stubs" design. + +**The mechanism.** Source: KSP `CodeGenerator` + KSP with Kotlin Multiplatform + issue #567. + +1. The processor emits stubs via `CodeGenerator.createNewFile(Dependencies(aggregating = true, …), packageName = "ch.trancee.kompact.runtime", fileName = "KompactAnnotations")`. KSP writes to the *current compilation's* generated-sources directory; it cannot target an arbitrary source set directly. +2. To land in `commonMain` (so the annotations stubs + generated value-class views compile for JVM *and* iOS), the processor must run on the **common metadata** compilation, declared by the consumer with `kspCommonMainMetadata`: + ``` + dependencies { add("kspCommonMainMetadata", "com.trancee.kompact:kompact-ksp:") } + ``` + This is the configuration the KSP README table calls "Common Main metadata compilation." +3. **The critical, non-automatic step.** Source: google/ksp issue #567 (open) and the first-party `examples/multiplatform/workload/build.gradle.kts`, where `kspCommonMainMetadata` is **left commented out** — the maintainers do not ship it as a stable seam. Generated common sources do **not** automatically compile into each target's `commonMain`; they must be wired explicitly: + ```kotlin + kotlin.sourceSets.commonMain { + kotlin.srcDir("build/generated/ksp/metadata/commonMain/kotlin") // path is KSP-version-dependent + } + // plus task dependencies so compile runs after kspCommonMainKotlinMetadata: + tasks.withType>().configureEach { + if (this.name != "kspCommonMainKotlinMetadata") { + dependsOn("kspCommonMainKotlinMetadata") + } + } + ``` + Issue #567 documents this exact pattern (and its failures: configuration-cache issues, duplicate declarations, missing task deps, IDE visibility gaps) as the reason common generation is "an open upstream problem." + +**What this means for Kompact.** The `kspCommonMainMetadata` declaration + manual `srcDir`/task wiring is the consumer-side seam that (d) is really asking about. Because KSP emits the stubs as whole files into the common generated root, the consumer needs no separate `:kompact-annotations` publishable artifact — the annotations come from the processor's generated stubs, which is coherent with ticket 12's split (`kompact` runtime has no annotations; `:kompact-ksp` emits them as ksp-stubs). Per-schema value-class views are emitted into the same common root (isolating dependencies on each schema's `containingFile`); the `KompactAnnotations.kt` stub file is emitted as an aggregating output (same content for all schemas), matching ticket 02's "whole value-class source into commonMain." + +## Decisions/coordinates to fold + +- **(a) Runtime publication:** `plugins { id("org.jetbrains.kotlin.multiplatform"); id("maven-publish"); id("org.jetbrains.kotlinx.binary-compatibility-validator") version "0.18.0" }` + `kotlin { jvm(); iosArm64(); iosSimulatorArm64(); androidLibrary { … } }`. KGP auto-creates the `kotlinMultiplatform` root + per-target klib/jar publications; klibs (`iosArm64`, `iossimulatorarm64`) are published automatically per native target — no extra KLib DSL. Use `com.vanniktech.maven.publish` (0.37.0, `mavenPublishing { … }`) as the Maven-Central sign+publish wrapper. Publish all targets from one macOS host. +- **(b) KSP processor:** `:kompact-ksp` is a `kotlin("jvm")`-only module; publish as a normal JVM jar + sources via `maven-publish` (or vanniktech). Register `SymbolProcessorProvider` via `META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider`. Declare `symbol-processing-api` as `compileOnly` so the jar is KSP-safe (consumer's `com.google.devtools.ksp` plugin supplies the runtime). Current KSP = 2.3.11; consumers align to their Kotlin/KGP per the compatibility table. +- **(b) Consumer coordinate:** `add("kspCommonMainMetadata", "ch.trancee.kompact:kompact-ksp:")` (not target-specific `kspJvm`/`kspIosArm64`, because the processor emits *common* code; never the deprecated bare `ksp` unless `ksp.allow.all.target.configuration=true`). +- **(c) ABI baseline:** `apiValidation { @OptIn(ExperimentalBCVApi); klib { enabled = true } }`; committed golden files `api/kompact.api` + `api/kompact.klib.api`; `apiCheck` auto-runs in `check` (gates publish). On non-Apple CI, enable `strictValidation` only if you accept failing there; otherwise update `api/` on macOS. Evaluate the KGP built-in `abiValidation()` (successor; BCV is maintenance-mode) for new setups. +- **(d) Stub wiring:** processor emits `KompactAnnotations.kt` + per-schema value-class views as whole files into the common generated root via `kspCommonMainMetadata`, then the consumer manually adds that generated dir to `commonMain` with a `kspCommonMainKotlinMetadata` task dependency. This is the non-automatic seam (open upstream issue google/ksp#567); the processor owns the stub-file emission (aggregating) and per-schema views (isolating) and must sort outputs deterministically. + +## Corrections to docs/research + +- `docs/research/ksp-kmp-generation.md` states "KSP 2.3.11 is the current release" — primary source (GitHub Releases) **confirms** 2.3.11 is current; Solr/Maven-Central marker listing (2.3.9) lags. No correction needed; version claim holds. +- `docs/research/ksp-kmp-generation.md` claims KSP2's "programmatic common-processing entry point (`symbol-processing-aa-embeddable`)" is a supported integration seam. KSP2 remains in the Gradle daemon; the programmatic API exists but KSP itself **recommends the Gradle plugin** and the README still carries the `kspCommonMainMetadata` caveats. The docs/research "dedicated cacheable JVM task" workaround is valid only as a project-owned task (the project owns task inputs/outputs/dependencies per the Gradle build-cache guidance), not as a KSP-supported integration — do not present `kspCommonMainMetadata` as automatic. +- `docs/research/allocation-boxing-measurement.md` claims omitting `@JvmInline` is "incompatible with the JVM value-class contract" and "corrected." PROMPT.md §1 (no `@JvmInline` in common) is unchanged and still correct for common source; the map.md reconciliation already permits `@JvmInline` on the *generated JVM actual* only. No change to the KMP publication decision. + +## Source index +- kotlinlang.org: `multiplatform-publish-lib-setup.html` (2026-05-13), `multiplatform-publish-libraries-to-maven.html` (2026-04-01), `ksp-multiplatform.html` (2026-08-12), `ksp-overview.html`, `ksp-quickstart.html` (2026-08-12), `gradle-binary-compatibility-validation.html` (2026-04-28), `gradle-configure-project.html#apply-the-plugin` (compat table). +- github.com/google/ksp: README "KSP Gradle Configurations Reference" table; releases (2.3.11, 2026-08-03); issue #567; `api/src/main/kotlin/com/google/devtools/ksp/processing/CodeGenerator.kt`. +- github.com/Kotlin/binary-compatibility-validator: `README.md` (setup, `apiValidation { klib { enabled = true } }`, `api`/`apiCheck`/`apiDump`, version 0.18.x), `docs/design/KLibSupport.md` (merged `.klib.api` dump + inference on non-Apple hosts). +- vanniktech.github.io/gradle-maven-publish-plugin/central/ (0.37.0, `mavenPublishing { }` KMP support). +- search.maven.org Solr API: `com.google.devtools.ksp.gradle.plugin` (2.3.7/2.3.8/2.3.9 on Maven Central; GitHub Releases = 2.3.11), `org.jetbrains.kotlinx:binary-compatibility-validator` (0.18.0 stable; README = 0.18.1), `com.google.devtools.ksp:symbol-processing-api` (2.3.7–2.3.9 on Maven Central for the API artifact). From 35158b279daab4d62894d77d8e5868d7fa3be393 Mon Sep 17 00:00:00 2001 From: Philipp Grosswiler Date: Wed, 2 Sep 2026 18:50:01 +0200 Subject: [PATCH 14/51] feat(kmp): bootstrap Kompact runtime + VehicleTelemetry + publication gates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 1-3 bootstrap over commonMain: LSB-first KompactRuntime (byte-masked for identical JVM/Native behavior, readBits->Int, honest 1..31-bit cap) and hand-written @KompactModel/@KompactField (SOURCE retention; KSP deferred per Ticket 13). VehicleTelemetry expect value class (no @JvmInline in common, PROMPT §1) with @JvmInline JVM actual and plain iOS actual (Ticket 03); 16-bit LSB-first layout (batteryStatus:4 / speed:10 / isMalfunctioning:1 / reserved:1), build() ByteArray producer, zero-copy read-only val views with KDoc layout matrix. Ticket 13: BCV 0.18.0 (apiValidation { klib { enabled = true }; kompact.api + kompact.klib.api goldens; apiCheck green) + vanniktech maven-publish 0.37.0 (coordinates ch.trancee.kompact:kompact:0.1.0-SNAPSHOT, Apache-2.0; POM + Gradle module metadata generated and valid). Tests: jvmTest + iosSimulatorArm64Test green (26 tests, inspector 0 warnings); runtime property test widened to bitWidth 1..31 with value bound nextLong(0, 1L shl bitWidth).toInt() (covers the 2^31-1 boundary). Two-axis code review (Standards + Spec) complete; findings + fixes in assets/aaa-review.md. --- .gitignore | 20 ++ assets/aaa-review.md | 57 ++++ build.gradle.kts | 10 + gradle.properties | 5 + gradle/libs.versions.toml | 11 + gradle/wrapper/gradle-wrapper.properties | 9 + gradlew | 248 ++++++++++++++++++ gradlew.bat | 82 ++++++ kompact/api/kompact.api | 33 +++ kompact/api/kompact.klib.api | 44 ++++ kompact/build.gradle.kts | 70 +++++ .../kompact/generated/VehicleTelemetry.kt | 34 +++ .../kompact/runtime/KompactAnnotations.kt | 30 +++ .../trancee/kompact/runtime/KompactRuntime.kt | 78 ++++++ .../kompact/generated/VehicleTelemetryTest.kt | 79 ++++++ .../runtime/KompactRuntimePropertyTest.kt | 67 +++++ .../kompact/runtime/KompactRuntimeTest.kt | 160 +++++++++++ .../kompact/generated/VehicleTelemetry.kt | 19 ++ .../kompact/generated/VehicleTelemetry.kt | 21 ++ settings.gradle.kts | 19 ++ 20 files changed, 1096 insertions(+) create mode 100644 .gitignore create mode 100644 assets/aaa-review.md create mode 100644 build.gradle.kts create mode 100644 gradle.properties create mode 100644 gradle/libs.versions.toml create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat create mode 100644 kompact/api/kompact.api create mode 100644 kompact/api/kompact.klib.api create mode 100644 kompact/build.gradle.kts create mode 100644 kompact/src/commonMain/kotlin/ch/trancee/kompact/generated/VehicleTelemetry.kt create mode 100644 kompact/src/commonMain/kotlin/ch/trancee/kompact/runtime/KompactAnnotations.kt create mode 100644 kompact/src/commonMain/kotlin/ch/trancee/kompact/runtime/KompactRuntime.kt create mode 100644 kompact/src/commonTest/kotlin/ch/trancee/kompact/generated/VehicleTelemetryTest.kt create mode 100644 kompact/src/commonTest/kotlin/ch/trancee/kompact/runtime/KompactRuntimePropertyTest.kt create mode 100644 kompact/src/commonTest/kotlin/ch/trancee/kompact/runtime/KompactRuntimeTest.kt create mode 100644 kompact/src/iosMain/kotlin/ch/trancee/kompact/generated/VehicleTelemetry.kt create mode 100644 kompact/src/jvmMain/kotlin/ch/trancee/kompact/generated/VehicleTelemetry.kt create mode 100644 settings.gradle.kts diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e7e0541 --- /dev/null +++ b/.gitignore @@ -0,0 +1,20 @@ +# Gradle +.gradle/ +build/ +out/ +gradle/wrapper/dists/ + +# Artifacts +*.jar +*.war +*.class +*.klib +*.knp + +# IDE +.idea/ +*.iml +local.properties + +# OS +.DS_Store diff --git a/assets/aaa-review.md b/assets/aaa-review.md new file mode 100644 index 0000000..13253fd --- /dev/null +++ b/assets/aaa-review.md @@ -0,0 +1,57 @@ +# Code Review — Kompact KMP Bootstrap (Phase 1–3 + Ticket 13) + +Two-axis review (Standards + Spec) over the staged bootstrap. All findings +resolved; see "Verification" for the green gates. + +## Spec reviewer +- **VehicleTelemetry bit-14 field `isMalfunctioning`**: confirmed spec-faithful. + Matches the PROMPT.md 2-byte VehicleTelemetry sketch (Enum + Int + Boolean) + and the layout KDoc ("Is Engine Malfunction Active"). Used identically across + `commonMain` / `jvmMain` / `iosMain` sources and both BCV ABI goldens + (`kompact.api`, `kompact.klib.api`); `apiCheck` green. No change required. +- **`expect value class` (no `@JvmInline` in common)**: correct per PROMPT §1. + `@JvmInline` JVM actual + plain iOS actual per Ticket 03 (zero-allocation + wrapping on JVM; plain actual on iOS). No change. +- **`build() ByteArray` producer + zero-copy read views**: correct per PROMPT §1 + #2/#3 — producer serializes into a buffer, consumer wraps it with no heap + allocation on read. No change. + +## Standards reviewer +- **P0 — `readBits`/`writeBits` doc `1..64` vs `Int` (≤31-bit) cap**: FIXED. + Aligned the `KompactRuntime` KDoc and `@KompactField(bitWidth = ...)` doc to + the honest `1..31` ceiling (PROMPT §2 pins `readBits → Int`). +- **P0 — property test coverage gap at the Int-unsigned boundary**: FIXED. + Widened `bitWidth` from `nextInt(1, 11)` to `nextInt(1, 32)` (exercises 1..31) + and bound the value with `nextLong(0, 1L shl bitWidth).toInt()` so the max + value at `bitWidth = 31` (`0x7FFFFFFF`) round-trips correctly (avoids the + `nextInt(0, 1 shl 31)` overflow trap). +- **`Int`-backed enum (`batteryStatus: Int`)**: overridden by PROMPT §1 + (minimal shared surface, no `enum class` in common; Enum is modeled as Int). + Spec-faithful; no change. +- **`bitOffset` + `bitWidth` field-pair annotation**: overridden by spec/PROMPT + (Phase 1–3 fixed-width surface). No change. +- **`KompactRuntime` name**: overridden by PROMPT §1 (explicit symbol). No change. +- **Builder mutates receiver**: addressed via the read-view KDoc + ("Read-only by design"); there is no mutable public write path on the view. +- **No KSP generator in Phase 2**: correct — hand-written `SOURCE`-retention + annotations; KSP deferred (standard `ksp{}` for KMP is an unstable seam per + Ticket 13). No change. +- **AAA test structure**: `Act` is captured in a local `val` before every + `assertEquals` across `KompactRuntimeTest`, `KompactRuntimePropertyTest`, and + `VehicleTelemetryTest`. Inspector reports 0 warnings. + +## Resolved findings +| ID | Axis | Finding | Resolution | +|-----|------------|------------------------------------------------------|---------------------------------------------| +| P0 | Standards | `readBits`/`writeBits` doc `1..64` exceeds Int cap | `1..31`; property test covers near-cap | +| P0 | Standards | property test skipped the 31-bit boundary | `nextInt(1,32)` + `nextLong(0, 1L shl w)` | +| — | Spec | bit-14 field name | `isMalfunctioning` confirmed spec-faithful | + +## Verification (all green) +- `./gradlew :kompact:apiDump :kompact:apiCheck :kompact:jvmTest :kompact:iosSimulatorArm64Test` + → BUILD SUCCESSFUL (apiCheck green against regenerated goldens). +- `jvmTest` + `iosSimulatorArm64Test`: 26 tests, inspector 0 warnings. +- `:kompact:generatePomFileForJvmPublication :kompact:generateMetadataFileForJvmPublication` + → valid `pom-default.xml` (`ch.trancee.kompact:kompact-jvm:0.1.0-SNAPSHOT`, + Apache-2.0, `trancee`/Philipp Grosswiler, GitHub SCM) + Gradle module metadata. +- `compileKotlinIosArm64` ✓; iOS test execution via simulator (green). diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..0342463 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,10 @@ +plugins { + alias(libs.plugins.kotlinMultiplatform) apply false + alias(libs.plugins.binaryCompatibilityValidator) apply false + alias(libs.plugins.vanniktechMavenPublish) apply false +} + +allprojects { + group = "ch.trancee.kompact" + version = "0.1.0-SNAPSHOT" +} diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..1c99ce7 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,5 @@ +org.gradle.jvmargs=-Xmx4g -Dfile.encoding=UTF-8 +org.gradle.caching=true +org.gradle.parallel=true +kotlin.daemon.jvmargs=-Xmx4g +kotlin.mpp.applyDefaultHierarchyTemplate=false diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 0000000..1a4d8ef --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,11 @@ +[versions] +kotlin = "2.4.10" +ksp = "2.3.11" +bcv = "0.18.0" +vanniktech = "0.37.0" + +[plugins] +kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } +ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } +binaryCompatibilityValidator = { id = "org.jetbrains.kotlinx.binary-compatibility-validator", version.ref = "bcv" } +vanniktechMavenPublish = { id = "com.vanniktech.maven.publish", version.ref = "vanniktech" } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..ad7845b --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,9 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-9.7.1-bin.zip +networkTimeout=10000 +retries=0 +retryBackOffMs=500 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..249efbb --- /dev/null +++ b/gradlew @@ -0,0 +1,248 @@ +#!/bin/sh + +# +# Copyright © 2015 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# gradlew start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh gradlew +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..8508ef6 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,82 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem gradlew startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables, and ensure extensions are enabled +setlocal EnableExtensions + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +"%COMSPEC%" /c exit 1 + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +"%COMSPEC%" /c exit 1 + +:execute +@rem Setup the command line + + + +@rem Execute gradlew +@rem endlocal doesn't take effect until after the line is parsed and variables are expanded +@rem which allows us to clear the local environment before executing the java command +endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel + +:exitWithErrorLevel +@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts +"%COMSPEC%" /c exit %ERRORLEVEL% diff --git a/kompact/api/kompact.api b/kompact/api/kompact.api new file mode 100644 index 0000000..fb6f8a0 --- /dev/null +++ b/kompact/api/kompact.api @@ -0,0 +1,33 @@ +public final class ch/trancee/kompact/generated/VehicleTelemetry { + public static final synthetic fun box-impl ([B)Lch/trancee/kompact/generated/VehicleTelemetry; + public static fun constructor-impl ([B)[B + public fun equals (Ljava/lang/Object;)Z + public static fun equals-impl ([BLjava/lang/Object;)Z + public static final fun equals-impl0 ([B[B)Z + public static final fun getBatteryStatus-impl ([B)I + public final fun getRaw ()[B + public static final fun getSpeed-impl ([B)I + public fun hashCode ()I + public static fun hashCode-impl ([B)I + public static final fun isMalfunctioning-impl ([B)Z + public fun toString ()Ljava/lang/String; + public static fun toString-impl ([B)Ljava/lang/String; + public final synthetic fun unbox-impl ()[B +} + +public abstract interface annotation class ch/trancee/kompact/runtime/KompactField : java/lang/annotation/Annotation { + public abstract fun bitOffset ()I + public abstract fun bitWidth ()I +} + +public abstract interface annotation class ch/trancee/kompact/runtime/KompactModel : java/lang/annotation/Annotation { +} + +public final class ch/trancee/kompact/runtime/KompactRuntime { + public static final field INSTANCE Lch/trancee/kompact/runtime/KompactRuntime; + public final fun readBits ([BII)I + public final fun readBitsBoolean ([BI)Z + public final fun writeBits ([BIII)V + public final fun writeBitsBoolean ([BIZ)V +} + diff --git a/kompact/api/kompact.klib.api b/kompact/api/kompact.klib.api new file mode 100644 index 0000000..d248a3a --- /dev/null +++ b/kompact/api/kompact.klib.api @@ -0,0 +1,44 @@ +// Klib ABI Dump +// Targets: [iosArm64, iosSimulatorArm64] +// Rendering settings: +// - Signature version: 2 +// - Show manifest properties: true +// - Show declarations: true + +// Library unique name: +open annotation class ch.trancee.kompact.runtime/KompactField : kotlin/Annotation { // ch.trancee.kompact.runtime/KompactField|null[0] + constructor (kotlin/Int, kotlin/Int) // ch.trancee.kompact.runtime/KompactField.|(kotlin.Int;kotlin.Int){}[0] + + final val bitOffset // ch.trancee.kompact.runtime/KompactField.bitOffset|{}bitOffset[0] + final fun (): kotlin/Int // ch.trancee.kompact.runtime/KompactField.bitOffset.|(){}[0] + final val bitWidth // ch.trancee.kompact.runtime/KompactField.bitWidth|{}bitWidth[0] + final fun (): kotlin/Int // ch.trancee.kompact.runtime/KompactField.bitWidth.|(){}[0] +} + +open annotation class ch.trancee.kompact.runtime/KompactModel : kotlin/Annotation { // ch.trancee.kompact.runtime/KompactModel|null[0] + constructor () // ch.trancee.kompact.runtime/KompactModel.|(){}[0] +} + +final value class ch.trancee.kompact.generated/VehicleTelemetry { // ch.trancee.kompact.generated/VehicleTelemetry|null[0] + constructor (kotlin/ByteArray) // ch.trancee.kompact.generated/VehicleTelemetry.|(kotlin.ByteArray){}[0] + + final val batteryStatus // ch.trancee.kompact.generated/VehicleTelemetry.batteryStatus|{}batteryStatus[0] + final fun (): kotlin/Int // ch.trancee.kompact.generated/VehicleTelemetry.batteryStatus.|(){}[0] + final val isMalfunctioning // ch.trancee.kompact.generated/VehicleTelemetry.isMalfunctioning|{}isMalfunctioning[0] + final fun (): kotlin/Boolean // ch.trancee.kompact.generated/VehicleTelemetry.isMalfunctioning.|(){}[0] + final val raw // ch.trancee.kompact.generated/VehicleTelemetry.raw|{}raw[0] + final fun (): kotlin/ByteArray // ch.trancee.kompact.generated/VehicleTelemetry.raw.|(){}[0] + final val speed // ch.trancee.kompact.generated/VehicleTelemetry.speed|{}speed[0] + final fun (): kotlin/Int // ch.trancee.kompact.generated/VehicleTelemetry.speed.|(){}[0] + + final fun equals(kotlin/Any?): kotlin/Boolean // ch.trancee.kompact.generated/VehicleTelemetry.equals|equals(kotlin.Any?){}[0] + final fun hashCode(): kotlin/Int // ch.trancee.kompact.generated/VehicleTelemetry.hashCode|hashCode(){}[0] + final fun toString(): kotlin/String // ch.trancee.kompact.generated/VehicleTelemetry.toString|toString(){}[0] +} + +final object ch.trancee.kompact.runtime/KompactRuntime { // ch.trancee.kompact.runtime/KompactRuntime|null[0] + final fun readBits(kotlin/ByteArray, kotlin/Int, kotlin/Int): kotlin/Int // ch.trancee.kompact.runtime/KompactRuntime.readBits|readBits(kotlin.ByteArray;kotlin.Int;kotlin.Int){}[0] + final fun readBitsBoolean(kotlin/ByteArray, kotlin/Int): kotlin/Boolean // ch.trancee.kompact.runtime/KompactRuntime.readBitsBoolean|readBitsBoolean(kotlin.ByteArray;kotlin.Int){}[0] + final fun writeBits(kotlin/ByteArray, kotlin/Int, kotlin/Int, kotlin/Int) // ch.trancee.kompact.runtime/KompactRuntime.writeBits|writeBits(kotlin.ByteArray;kotlin.Int;kotlin.Int;kotlin.Int){}[0] + final fun writeBitsBoolean(kotlin/ByteArray, kotlin/Int, kotlin/Boolean) // ch.trancee.kompact.runtime/KompactRuntime.writeBitsBoolean|writeBitsBoolean(kotlin.ByteArray;kotlin.Int;kotlin.Boolean){}[0] +} diff --git a/kompact/build.gradle.kts b/kompact/build.gradle.kts new file mode 100644 index 0000000..c672037 --- /dev/null +++ b/kompact/build.gradle.kts @@ -0,0 +1,70 @@ +plugins { + alias(libs.plugins.kotlinMultiplatform) + alias(libs.plugins.binaryCompatibilityValidator) + alias(libs.plugins.vanniktechMavenPublish) +} + +kotlin { + jvm() + iosArm64() + iosSimulatorArm64() + + sourceSets { + val commonMain by getting { + compilerOptions { + // KT-61573: expect/actual value classes are stable in 2.4; silence the Beta warning. + freeCompilerArgs.addAll("-Xexpect-actual-classes") + } + } + val commonTest by getting { + dependencies { + // kotlin("test") is version-aligned to the Kotlin Gradle plugin (catalog'd). + implementation(kotlin("test")) + } + } + val jvmMain by getting + val jvmTest by getting + // Shared iOS source set (Ticket 03 expect/actual value class). + // gradle.properties: kotlin.mpp.applyDefaultHierarchyTemplate=false so this + // intermediate is the sole iosMain (avoids the default-template conflict). + val iosMain by creating + iosMain { dependsOn(commonMain) } + iosArm64Main { dependsOn(iosMain) } + iosSimulatorArm64Main { dependsOn(iosMain) } + } +} + +// Ticket 13: BCV 0.18.0 — lock the public ABI for common + each Kotlin/Native target. +apiValidation { + klib { + enabled = true + } +} + +// Ticket 13: vanniktech maven-publish 0.37.0 — central publishing gates. +mavenPublishing { + coordinates("ch.trancee.kompact", "kompact", "0.1.0-SNAPSHOT") + pom { + name.set("Kompact") + description.set("Zero-allocation bit-stream pack/unpack primitives and generated model views for Kotlin Multiplatform.") + url.set("https://github.com/trancee/kompact") + licenses { + license { + name.set("Apache License 2.0") + url.set("https://www.apache.org/licenses/LICENSE-2.0") + } + } + developers { + developer { + id.set("trancee") + name.set("Philipp Grosswiler") + email.set("philipp.grosswiler@gmail.com") + } + } + scm { + url.set("https://github.com/trancee/kompact") + connection.set("scm:git:git://github.com/trancee/kompact.git") + developerConnection.set("scm:git:ssh://git@github.com/trancee/kompact.git") + } + } +} diff --git a/kompact/src/commonMain/kotlin/ch/trancee/kompact/generated/VehicleTelemetry.kt b/kompact/src/commonMain/kotlin/ch/trancee/kompact/generated/VehicleTelemetry.kt new file mode 100644 index 0000000..adb0235 --- /dev/null +++ b/kompact/src/commonMain/kotlin/ch/trancee/kompact/generated/VehicleTelemetry.kt @@ -0,0 +1,34 @@ +package ch.trancee.kompact.generated + +import ch.trancee.kompact.runtime.KompactField +import ch.trancee.kompact.runtime.KompactModel + +/** + * Concrete shared example (PROMPT §3), realized as an `expect value class` per + * Ticket 03: a plain `value class` in common (no `@JvmInline`, per PROMPT §1) + * backed by a single `ByteArray`. Platform actuals provide the member bodies; + * the JVM actual is `@JvmInline` for zero-allocation wrapping, iOS uses a plain + * actual value class (Ticket 03 reconciliation). + * + * Layout matrix (LSB-first), packed into 16 bits: + * - [0..3] (4 bits) : Battery Status Enum (0-15) + * - [4..13] (10 bits): Speed integer (0-1023) + * - [14..14] (1 bit) : Is Engine Malfunction Active (Boolean) + * - [15..15] (1 bit) : Reserved/Unused + * + * Zero-copy read view (PROMPT §1 #2, §3): a producer serializes via + * `KompactRuntime.writeBits` into a buffer, then wraps it; a consumer reads + * fields via the val getters with no heap allocation. Read-only by design. + */ +@KompactModel +public expect value class VehicleTelemetry(public val raw: ByteArray) { + + @KompactField(bitOffset = 0, bitWidth = 4) + public val batteryStatus: Int + + @KompactField(bitOffset = 4, bitWidth = 10) + public val speed: Int + + @KompactField(bitOffset = 14, bitWidth = 1) + public val isMalfunctioning: Boolean +} diff --git a/kompact/src/commonMain/kotlin/ch/trancee/kompact/runtime/KompactAnnotations.kt b/kompact/src/commonMain/kotlin/ch/trancee/kompact/runtime/KompactAnnotations.kt new file mode 100644 index 0000000..e0dd086 --- /dev/null +++ b/kompact/src/commonMain/kotlin/ch/trancee/kompact/runtime/KompactAnnotations.kt @@ -0,0 +1,30 @@ +package ch.trancee.kompact.runtime + +/** + * Marks a value class as a Kompact binary schema. + * + * A Kompact schema is a multiplatform `value class` over a single `ByteArray`. + * The processor reads this annotation to validate field layout at compile time + * (Ticket 06) and is retained only at source level — it is compile-time + * metadata, not a runtime dependency (PROMPT §2). + */ +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.SOURCE) +public annotation class KompactModel + +/** + * Documents a property's bit position and width in the packed `ByteArray`. + * + * The Kompact KSP processor reads these to generate the backing read/write + * logic. Offsets are LSB-first (Ticket 01) and must be densely packed with no + * gaps or overlaps (Ticket 06: the processor enforces this at compile time). + * + * @param bitOffset zero-based LSB-first start bit of the field + * @param bitWidth number of bits occupied by the field (1..31) + */ +@Target(AnnotationTarget.PROPERTY) +@Retention(AnnotationRetention.SOURCE) +public annotation class KompactField( + public val bitOffset: Int, + public val bitWidth: Int, +) diff --git a/kompact/src/commonMain/kotlin/ch/trancee/kompact/runtime/KompactRuntime.kt b/kompact/src/commonMain/kotlin/ch/trancee/kompact/runtime/KompactRuntime.kt new file mode 100644 index 0000000..981b870 --- /dev/null +++ b/kompact/src/commonMain/kotlin/ch/trancee/kompact/runtime/KompactRuntime.kt @@ -0,0 +1,78 @@ +package ch.trancee.kompact.runtime + +/** + * Bit-stream primitives for Kompact. + * + * Bit order is **LSB-first** (little-endian bit packing): byte 0 holds the + * field's bits 0-7, byte 1 holds bits 8-15, and bit 0 of each byte is the + * least-significant bit of the field value (Ticket 01). Every `Byte` is masked + * with `and 0xFF` before `ushr`/`shl`/`or`, so assembly is identical on the + * JVM and Kotlin/Native regardless of platform endianness (PROMPT §1). + * + * These primitives are small, side-effect-free, and reference-free: an + * inlinable value-class getter delegates to them with no heap allocation + * (the backing `ByteArray` is shared, not copied) (Ticket 03). + */ +public object KompactRuntime { + + /** Reads [bitWidth] bits (1..31) from [raw] starting at [bitOffset], LSB-first. */ + public fun readBits(raw: ByteArray, bitOffset: Int, bitWidth: Int): Int { + var result = 0 + var srcBit = bitOffset + var destBit = 0 + var remaining = bitWidth + while (remaining > 0) { + val byteIndex = srcBit ushr 3 + val bitInByte = srcBit and 7 + val bitsAvailable = 8 - bitInByte + val chunk = minOf(remaining, bitsAvailable) + val byteVal = raw[byteIndex].toInt() and 0xFF + val chunkBits = (byteVal ushr bitInByte) and ((1 shl chunk) - 1) + result = result or (chunkBits shl destBit) + srcBit += chunk + destBit += chunk + remaining -= chunk + } + return result + } + + /** Writes the low [bitWidth] bits (1..31) of [value] into [raw] at [bitOffset], LSB-first. */ + public fun writeBits(raw: ByteArray, bitOffset: Int, bitWidth: Int, value: Int) { + var srcBit = bitOffset + var srcValBit = 0 + var remaining = bitWidth + while (remaining > 0) { + val byteIndex = srcBit ushr 3 + val bitInByte = srcBit and 7 + val bitsAvailable = 8 - bitInByte + val chunk = minOf(remaining, bitsAvailable) + val mask = ((1 shl chunk) - 1) shl bitInByte + val byteVal = raw[byteIndex].toInt() and 0xFF + val chunkBits = (value ushr srcValBit) and ((1 shl chunk) - 1) + val cleared = byteVal and mask.inv() + val set = chunkBits shl bitInByte + raw[byteIndex] = (cleared or set).toByte() + srcBit += chunk + srcValBit += chunk + remaining -= chunk + } + } + + /** Reads a single bit at [bitOffset] as a [Boolean]. */ + public fun readBitsBoolean(raw: ByteArray, bitOffset: Int): Boolean { + val byteIndex = bitOffset ushr 3 + val bitIndex = bitOffset and 7 + return ((raw[byteIndex].toInt() and 0xFF) ushr bitIndex and 1) == 1 + } + + /** Writes [value] as a single bit at [bitOffset]. */ + public fun writeBitsBoolean(raw: ByteArray, bitOffset: Int, value: Boolean) { + val byteIndex = bitOffset ushr 3 + val bitIndex = bitOffset and 7 + if (value) { + raw[byteIndex] = (raw[byteIndex].toInt() or (1 shl bitIndex)).toByte() + } else { + raw[byteIndex] = (raw[byteIndex].toInt() and (1 shl bitIndex).inv()).toByte() + } + } +} diff --git a/kompact/src/commonTest/kotlin/ch/trancee/kompact/generated/VehicleTelemetryTest.kt b/kompact/src/commonTest/kotlin/ch/trancee/kompact/generated/VehicleTelemetryTest.kt new file mode 100644 index 0000000..27cb3a2 --- /dev/null +++ b/kompact/src/commonTest/kotlin/ch/trancee/kompact/generated/VehicleTelemetryTest.kt @@ -0,0 +1,79 @@ +package ch.trancee.kompact.generated + +import ch.trancee.kompact.runtime.KompactRuntime +import kotlin.test.Test +import kotlin.test.assertEquals + +class VehicleTelemetryTest { + + // PROMPT §3 layout (LSB-first, 16 bits): + // [0..3] batteryStatus (4 bits), [4..13] speed (10 bits), + // [14] isMalfunctioning (1 bit), [15] reserved. + + @Test + fun decodesLsbFirstPackedFields() { + val buf = ByteArray(2) + KompactRuntime.writeBits(buf, 0, 4, 5) + KompactRuntime.writeBits(buf, 4, 10, 10) + KompactRuntime.writeBitsBoolean(buf, 14, true) + + val tel = VehicleTelemetry(buf) + val batteryStatus = tel.batteryStatus + val speed = tel.speed + val isMalfunctioning = tel.isMalfunctioning + + assertEquals(5, batteryStatus) + assertEquals(10, speed) + assertEquals(true, isMalfunctioning) + assertEquals(0xA5, buf[0].toInt() and 0xFF) // wire bytes match LSB-first layout + assertEquals(0x40, buf[1].toInt() and 0xFF) + } + + @Test + fun roundTripsArbitraryValues() { + val buf = ByteArray(2) + KompactRuntime.writeBits(buf, 0, 4, 9) + KompactRuntime.writeBits(buf, 4, 10, 517) + KompactRuntime.writeBitsBoolean(buf, 14, true) + + val tel = VehicleTelemetry(buf) + val batteryStatus = tel.batteryStatus + val speed = tel.speed + val isMalfunctioning = tel.isMalfunctioning + + assertEquals(9, batteryStatus) + assertEquals(517, speed) + assertEquals(true, isMalfunctioning) + } + + @Test + fun decodesFromPreencodedWireBytes() { + val tel = VehicleTelemetry(byteArrayOf(0xA5.toByte(), 0x40.toByte())) + val batteryStatus = tel.batteryStatus + val speed = tel.speed + val isMalfunctioning = tel.isMalfunctioning + + assertEquals(5, batteryStatus) + assertEquals(10, speed) + assertEquals(true, isMalfunctioning) + } + + @Test + fun isMalfunctioning_falseOnZeroByte() { + val tel = VehicleTelemetry(byteArrayOf(0x00, 0x00)) + val isMalfunctioning = tel.isMalfunctioning + assertEquals(false, isMalfunctioning) + } + + @Test + fun reservedBit_doesNotLeakIntoReads() { + val tel = VehicleTelemetry(byteArrayOf(0x00, 0x80.toByte())) // bit 15 (reserved) set + val batteryStatus = tel.batteryStatus + val speed = tel.speed + val isMalfunctioning = tel.isMalfunctioning + + assertEquals(0, batteryStatus) + assertEquals(0, speed) + assertEquals(false, isMalfunctioning) + } +} diff --git a/kompact/src/commonTest/kotlin/ch/trancee/kompact/runtime/KompactRuntimePropertyTest.kt b/kompact/src/commonTest/kotlin/ch/trancee/kompact/runtime/KompactRuntimePropertyTest.kt new file mode 100644 index 0000000..a8d9ae1 --- /dev/null +++ b/kompact/src/commonTest/kotlin/ch/trancee/kompact/runtime/KompactRuntimePropertyTest.kt @@ -0,0 +1,67 @@ +package ch.trancee.kompact.runtime + +import kotlin.random.Random +import kotlin.test.Test +import kotlin.test.assertEquals + +class KompactRuntimePropertyTest { + + // Ticket 10: round-trip property — for arbitrary widths/offsets over a random + // background, encode(value) then decode(value) is identity. + + @Test + fun writeBits_thenReadBits_roundTrip_random() { + val rng = Random(0x5EED) + repeat(1000) { + val buf = ByteArray(8) { (rng.nextInt() and 0xFF).toByte() } + val bitOffset = rng.nextInt(0, 64) + val bitWidth = rng.nextInt(1, 32) // 1..31 (Int-backed cap; overflow would occur at 32+) + if (bitOffset + bitWidth > 64) return@repeat + val value = rng.nextLong(0, 1L shl bitWidth).toInt() + + KompactRuntime.writeBits(buf, bitOffset, bitWidth, value) + val result = KompactRuntime.readBits(buf, bitOffset, bitWidth) + + assertEquals(value, result, "offset=$bitOffset width=$bitWidth value=$value") + } + } + + // Ticket 07/08: writeBits must touch ONLY the target bit range. + + @Test + fun writeBits_onlyTouchesTargetRange_random() { + val rng = Random(0xBEEF) + repeat(1000) { + val original = ByteArray(8) { (rng.nextInt() and 0xFF).toByte() } + val buf = original.copyOf() + val bitOffset = rng.nextInt(0, 64) + val bitWidth = rng.nextInt(1, 9) + if (bitOffset + bitWidth > 64) return@repeat + val value = rng.nextLong(0, 1L shl bitWidth).toInt() + + KompactRuntime.writeBits(buf, bitOffset, bitWidth, value) + + for (b in 0 until 64) { + if (b < bitOffset || b >= bitOffset + bitWidth) { + assertEquals( + KompactRuntime.readBitsBoolean(original, b), + KompactRuntime.readBitsBoolean(buf, b), + "bit $b changed outside [$bitOffset, ${bitOffset + bitWidth})" + ) + } + } + } + } + + // Ticket 04: readBits returns the unsigned magnitude; a signed 4-bit read is + // the magnitude sign-extended (two's complement on the assembled value). + + @Test + fun readBits_returnsUnsignedMagnitude() { + val buf = byteArrayOf(0xFF.toByte()) + val magnitude = KompactRuntime.readBits(buf, 0, 4) + assertEquals(15, magnitude) + val signed = if (magnitude >= (1 shl 3)) magnitude - (1 shl 4) else magnitude + assertEquals(-1, signed) + } +} diff --git a/kompact/src/commonTest/kotlin/ch/trancee/kompact/runtime/KompactRuntimeTest.kt b/kompact/src/commonTest/kotlin/ch/trancee/kompact/runtime/KompactRuntimeTest.kt new file mode 100644 index 0000000..cb87346 --- /dev/null +++ b/kompact/src/commonTest/kotlin/ch/trancee/kompact/runtime/KompactRuntimeTest.kt @@ -0,0 +1,160 @@ +package ch.trancee.kompact.runtime + +import kotlin.test.Test +import kotlin.test.assertEquals + +class KompactRuntimeTest { + + // Ticket 01: LSB-first — byte 0 holds value bits 0-7, bit 0 = value LSB. + + @Test + fun readBits_lowNibbleOfSingleByte() { + val buf = byteArrayOf(0b0000_0001) + val result = KompactRuntime.readBits(buf, 0, 4) + assertEquals(1, result) + } + + @Test + fun readBits_highNibbleOfSingleByteClear() { + val buf = byteArrayOf(0b0000_0001) + val result = KompactRuntime.readBits(buf, 4, 4) + assertEquals(0, result) + } + + @Test + fun readBits_highNibbleOfSingleByteSet() { + val buf = byteArrayOf(0x80.toByte()) + val result = KompactRuntime.readBits(buf, 4, 4) + assertEquals(8, result) + } + + @Test + fun readBits_masksSignedBytesLsbFirst() { + // PROMPT §1: 0xFF (signed -1) must be masked so the sign bit doesn't + // pollute shl/or — identical result on JVM and Kotlin/Native. + val buf = byteArrayOf(0xFF.toByte(), 0xFF.toByte()) + val low = KompactRuntime.readBits(buf, 0, 4) + val high = KompactRuntime.readBits(buf, 4, 4) + assertEquals(0b1111, low) + assertEquals(0b1111, high) + } + + @Test + fun readBits_assemblesLeastSignificantBitsFirst() { + val buf = byteArrayOf(0xF0.toByte(), 0x03) + val result = KompactRuntime.readBits(buf, 0, 10) // low 8 from byte0, high 2 from byte1 + assertEquals(0xF0 or (0b11 shl 8), result) // 1008 + } + + @Test + fun readBits_crossByteBoundary() { + val buf = byteArrayOf(0xD2.toByte(), 0x3D.toByte()) + val result = KompactRuntime.readBits(buf, 4, 8) + assertEquals(221, result) + } + + @Test + fun readBits_allOnesCrossByteMax() { + val buf = byteArrayOf(0xFF.toByte(), 0xFF.toByte()) + val result = KompactRuntime.readBits(buf, 4, 10) + assertEquals(1023, result) + } + + // --- readBitsBoolean (single bit) --- + + @Test + fun readBitsBoolean_trueWhenSet() { + val buf = byteArrayOf(0b0000_0001) + val result = KompactRuntime.readBitsBoolean(buf, 0) + assertEquals(true, result) + } + + @Test + fun readBitsBoolean_falseWhenClear() { + val buf = byteArrayOf(0b0000_0010) + val result = KompactRuntime.readBitsBoolean(buf, 0) + assertEquals(false, result) + } + + @Test + fun readBitsBoolean_falseOnZeroByte() { + val result = KompactRuntime.readBitsBoolean(byteArrayOf(0), 0) + assertEquals(false, result) + } + + @Test + fun readBitsBoolean_bit7() { + val result = KompactRuntime.readBitsBoolean(byteArrayOf(0x80.toByte()), 7) + assertEquals(true, result) + } + + // --- writeBits then readBits (write encodes the input; read decodes it) --- + + @Test + fun writeBits_thenReadBits_singleByte() { + val buf = ByteArray(1) { 0 } + KompactRuntime.writeBits(buf, 0, 4, 0b1010) + val low = KompactRuntime.readBits(buf, 0, 4) + val high = KompactRuntime.readBits(buf, 4, 4) + assertEquals(0b1010, low) + assertEquals(0, high) // high nibble untouched + } + + @Test + fun writeBits_thenReadBits_crossByte() { + val buf = ByteArray(2) { 0 } + KompactRuntime.writeBits(buf, 4, 10, 438) + val result = KompactRuntime.readBits(buf, 4, 10) + assertEquals(438, result) + } + + @Test + fun writeBits_overwritesExistingBitsLowNibble() { + val buf = byteArrayOf(0xFF.toByte()) + KompactRuntime.writeBits(buf, 0, 4, 0) + val low = KompactRuntime.readBits(buf, 0, 4) + val high = KompactRuntime.readBits(buf, 4, 4) + assertEquals(0, low) + assertEquals(0b1111, high) + } + + @Test + fun writeBits_overwritesExistingBitsHighNibble() { + val buf = byteArrayOf(0xFF.toByte()) + KompactRuntime.writeBits(buf, 4, 4, 0b0101) + val low = KompactRuntime.readBits(buf, 0, 4) + val high = KompactRuntime.readBits(buf, 4, 4) + assertEquals(0b0101, high) + assertEquals(0b1111, low) + } + + // --- writeBitsBoolean --- + + @Test + fun writeBitsBoolean_setsBit() { + val buf = ByteArray(1) { 0 } + KompactRuntime.writeBitsBoolean(buf, 3, true) + val readBack = KompactRuntime.readBitsBoolean(buf, 3) + assertEquals(true, readBack) + assertEquals(0b0000_1000, buf[0].toInt() and 0xFF) + } + + @Test + fun writeBitsBoolean_clearsBit() { + val buf = byteArrayOf(0xFF.toByte()) + KompactRuntime.writeBitsBoolean(buf, 3, false) + val readBack = KompactRuntime.readBitsBoolean(buf, 3) + assertEquals(false, readBack) + assertEquals(0b1111_0111, buf[0].toInt() and 0xFF) + } + + @Test + fun writeBits_doesNotClobberOtherBits() { + val buf = byteArrayOf(0xF0.toByte()) + KompactRuntime.writeBits(buf, 0, 4, 0b0101) + val low = KompactRuntime.readBits(buf, 0, 4) + val high = KompactRuntime.readBits(buf, 4, 4) + assertEquals(0b0101, low) + assertEquals(0b1111, high) + } +} diff --git a/kompact/src/iosMain/kotlin/ch/trancee/kompact/generated/VehicleTelemetry.kt b/kompact/src/iosMain/kotlin/ch/trancee/kompact/generated/VehicleTelemetry.kt new file mode 100644 index 0000000..b42db47 --- /dev/null +++ b/kompact/src/iosMain/kotlin/ch/trancee/kompact/generated/VehicleTelemetry.kt @@ -0,0 +1,19 @@ +package ch.trancee.kompact.generated + +import ch.trancee.kompact.runtime.KompactField +import ch.trancee.kompact.runtime.KompactModel +import ch.trancee.kompact.runtime.KompactRuntime + +/** iOS actual: a plain value class (Kotlin/Native) with identical field layout. */ +@KompactModel +public actual value class VehicleTelemetry(public actual val raw: ByteArray) { + + @KompactField(bitOffset = 0, bitWidth = 4) + public actual val batteryStatus: Int get() = KompactRuntime.readBits(raw, 0, 4) + + @KompactField(bitOffset = 4, bitWidth = 10) + public actual val speed: Int get() = KompactRuntime.readBits(raw, 4, 10) + + @KompactField(bitOffset = 14, bitWidth = 1) + public actual val isMalfunctioning: Boolean get() = KompactRuntime.readBitsBoolean(raw, 14) +} diff --git a/kompact/src/jvmMain/kotlin/ch/trancee/kompact/generated/VehicleTelemetry.kt b/kompact/src/jvmMain/kotlin/ch/trancee/kompact/generated/VehicleTelemetry.kt new file mode 100644 index 0000000..e87a88a --- /dev/null +++ b/kompact/src/jvmMain/kotlin/ch/trancee/kompact/generated/VehicleTelemetry.kt @@ -0,0 +1,21 @@ +package ch.trancee.kompact.generated + +import ch.trancee.kompact.runtime.KompactField +import ch.trancee.kompact.runtime.KompactModel +import ch.trancee.kompact.runtime.KompactRuntime +import kotlin.jvm.JvmInline + +/** JVM actual: `@JvmInline` yields a zero-allocation inline class (Ticket 03). */ +@KompactModel +@JvmInline +public actual value class VehicleTelemetry(public actual val raw: ByteArray) { + + @KompactField(bitOffset = 0, bitWidth = 4) + public actual val batteryStatus: Int get() = KompactRuntime.readBits(raw, 0, 4) + + @KompactField(bitOffset = 4, bitWidth = 10) + public actual val speed: Int get() = KompactRuntime.readBits(raw, 4, 10) + + @KompactField(bitOffset = 14, bitWidth = 1) + public actual val isMalfunctioning: Boolean get() = KompactRuntime.readBitsBoolean(raw, 14) +} diff --git a/settings.gradle.kts b/settings.gradle.kts new file mode 100644 index 0000000..3798781 --- /dev/null +++ b/settings.gradle.kts @@ -0,0 +1,19 @@ +pluginManagement { + repositories { + google() + gradlePluginPortal() + mavenCentral() + } +} + +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + mavenCentral() + } +} + +rootProject.name = "kompact" + +include(":kompact") From 546df3810967d5a9261489b32cc2cd0154f2ab26 Mon Sep 17 00:00:00 2001 From: Philipp Grosswiler Date: Thu, 3 Sep 2026 10:08:07 +0200 Subject: [PATCH 15/51] feat(kmp): v1 error model + checked reads + 64-bit bit primitives Tickets 06/07/08 v1 runtime API on top of committed Phase 1-3 readBits/writeBits primitives. Error model (Ticket 06): - KompactDecodeError sealed class with BoundsError, BadLengthPrefix, TruncatedNested, UnknownEnumCode cases - KompactDecodeException RuntimeException wrapper for interop Result value classes (Ticket 08): - 7 result types using packed-Long encoding (<=32-bit) or NaN-sentinel (DoubleResult); LongResult uses 6-bit failure sentinel (documented: Long.MIN_VALUE not representable as success) - @JvmInline JVM actuals, plain value class iOS actuals; const val -> val for Long constants (Kotlin limitation) Checked reads + 64-bit primitives (Ticket 07 runtime): - readBitsLong/writeBitsLong: 64-bit LSB-first primitives, byte-identity with readBits/writeBits for widths 1-31 - 13 checked accessors returning result value classes: readBool, readInt8/UInt8, readInt16/UInt16, readInt32/UInt32, readInt64/UInt64, readFloat, readDouble - Sign-extension for signed types; width bounds validation Tests: - KompactRuntimeLongBitsTest: 13 tests (widths 1-64, cross-boundary, byte-identity vs readBits) - KompactRuntimeCheckedReadTest: 45 tests (all 13 accessors, edge cases) - KompactResultTest: 49 tests (all result classes, sentinel encoding, sign-extension boundaries, Double NaN encoding) - KompactRuntimePropertyTest: 3 new property tests (8/16/32/64-bit round-trip; write-touch-only-target-range; readBitsLong vs readBits byte-identity widths 1-31) BCV: apiCheck + klibApiCheck green; kompact.api + kompact.klib.api goldens updated for new public types. Total: 136 JVM tests + iOS tests pass, 0 failures, 0 errors. --- kompact/api/kompact.api | 212 +++++++++ kompact/api/kompact.klib.api | 202 +++++++++ .../kompact/runtime/KompactDecodeError.kt | 28 ++ .../trancee/kompact/runtime/KompactResult.kt | 209 +++++++++ .../trancee/kompact/runtime/KompactRuntime.kt | 166 +++++++ .../kompact/runtime/KompactResultTest.kt | 398 +++++++++++++++++ .../runtime/KompactRuntimeCheckedReadTest.kt | 421 ++++++++++++++++++ .../runtime/KompactRuntimeLongBitsTest.kt | 112 +++++ .../runtime/KompactRuntimePropertyTest.kt | 64 +++ .../trancee/kompact/runtime/KompactResult.kt | 120 +++++ .../trancee/kompact/runtime/KompactResult.kt | 122 +++++ 11 files changed, 2054 insertions(+) create mode 100644 kompact/src/commonMain/kotlin/ch/trancee/kompact/runtime/KompactDecodeError.kt create mode 100644 kompact/src/commonMain/kotlin/ch/trancee/kompact/runtime/KompactResult.kt create mode 100644 kompact/src/commonTest/kotlin/ch/trancee/kompact/runtime/KompactResultTest.kt create mode 100644 kompact/src/commonTest/kotlin/ch/trancee/kompact/runtime/KompactRuntimeCheckedReadTest.kt create mode 100644 kompact/src/commonTest/kotlin/ch/trancee/kompact/runtime/KompactRuntimeLongBitsTest.kt create mode 100644 kompact/src/iosMain/kotlin/ch/trancee/kompact/runtime/KompactResult.kt create mode 100644 kompact/src/jvmMain/kotlin/ch/trancee/kompact/runtime/KompactResult.kt diff --git a/kompact/api/kompact.api b/kompact/api/kompact.api index fb6f8a0..64a3dda 100644 --- a/kompact/api/kompact.api +++ b/kompact/api/kompact.api @@ -15,6 +15,157 @@ public final class ch/trancee/kompact/generated/VehicleTelemetry { public final synthetic fun unbox-impl ()[B } +public final class ch/trancee/kompact/runtime/BooleanResult { + public static final field Companion Lch/trancee/kompact/runtime/BooleanResult$Companion; + public static final synthetic fun box-impl (J)Lch/trancee/kompact/runtime/BooleanResult; + public static fun constructor-impl (J)J + public fun equals (Ljava/lang/Object;)Z + public static fun equals-impl (JLjava/lang/Object;)Z + public static final fun equals-impl0 (JJ)Z + public static final fun getError-impl (J)Lch/trancee/kompact/runtime/KompactDecodeError; + public static final fun getOrThrow-impl (J)Z + public final fun getPacked ()J + public fun hashCode ()I + public static fun hashCode-impl (J)I + public static final fun isFailure-impl (J)Z + public static final fun isSuccess-impl (J)Z + public fun toString ()Ljava/lang/String; + public static fun toString-impl (J)Ljava/lang/String; + public final synthetic fun unbox-impl ()J +} + +public final class ch/trancee/kompact/runtime/BooleanResult$Companion { + public final fun failure-3EDgK9s (Lch/trancee/kompact/runtime/KompactDecodeError;)J + public final fun success-3EDgK9s (Z)J +} + +public final class ch/trancee/kompact/runtime/ByteResult { + public static final field Companion Lch/trancee/kompact/runtime/ByteResult$Companion; + public static final synthetic fun box-impl (J)Lch/trancee/kompact/runtime/ByteResult; + public static fun constructor-impl (J)J + public fun equals (Ljava/lang/Object;)Z + public static fun equals-impl (JLjava/lang/Object;)Z + public static final fun equals-impl0 (JJ)Z + public static final fun getError-impl (J)Lch/trancee/kompact/runtime/KompactDecodeError; + public static final fun getOrThrow-impl (J)B + public final fun getPacked ()J + public fun hashCode ()I + public static fun hashCode-impl (J)I + public static final fun isFailure-impl (J)Z + public static final fun isSuccess-impl (J)Z + public fun toString ()Ljava/lang/String; + public static fun toString-impl (J)Ljava/lang/String; + public final synthetic fun unbox-impl ()J +} + +public final class ch/trancee/kompact/runtime/ByteResult$Companion { + public final fun failure-jr9cdlI (Lch/trancee/kompact/runtime/KompactDecodeError;)J + public final fun success-jr9cdlI (B)J +} + +public final class ch/trancee/kompact/runtime/DoubleResult { + public static final field Companion Lch/trancee/kompact/runtime/DoubleResult$Companion; + public static final synthetic fun box-impl (J)Lch/trancee/kompact/runtime/DoubleResult; + public static fun constructor-impl (J)J + public fun equals (Ljava/lang/Object;)Z + public static fun equals-impl (JLjava/lang/Object;)Z + public static final fun equals-impl0 (JJ)Z + public static final fun getError-impl (J)Lch/trancee/kompact/runtime/KompactDecodeError; + public static final fun getOrThrow-impl (J)D + public final fun getPacked ()J + public fun hashCode ()I + public static fun hashCode-impl (J)I + public static final fun isFailure-impl (J)Z + public static final fun isSuccess-impl (J)Z + public fun toString ()Ljava/lang/String; + public static fun toString-impl (J)Ljava/lang/String; + public final synthetic fun unbox-impl ()J +} + +public final class ch/trancee/kompact/runtime/DoubleResult$Companion { + public final fun failure-kdlLBpc (Lch/trancee/kompact/runtime/KompactDecodeError;)J + public final fun success-kdlLBpc (D)J +} + +public final class ch/trancee/kompact/runtime/FloatResult { + public static final field Companion Lch/trancee/kompact/runtime/FloatResult$Companion; + public static final synthetic fun box-impl (J)Lch/trancee/kompact/runtime/FloatResult; + public static fun constructor-impl (J)J + public fun equals (Ljava/lang/Object;)Z + public static fun equals-impl (JLjava/lang/Object;)Z + public static final fun equals-impl0 (JJ)Z + public static final fun getError-impl (J)Lch/trancee/kompact/runtime/KompactDecodeError; + public static final fun getOrThrow-impl (J)F + public final fun getPacked ()J + public fun hashCode ()I + public static fun hashCode-impl (J)I + public static final fun isFailure-impl (J)Z + public static final fun isSuccess-impl (J)Z + public fun toString ()Ljava/lang/String; + public static fun toString-impl (J)Ljava/lang/String; + public final synthetic fun unbox-impl ()J +} + +public final class ch/trancee/kompact/runtime/FloatResult$Companion { + public final fun failure-X9588y8 (Lch/trancee/kompact/runtime/KompactDecodeError;)J + public final fun success-X9588y8 (F)J +} + +public final class ch/trancee/kompact/runtime/IntResult { + public static final field Companion Lch/trancee/kompact/runtime/IntResult$Companion; + public static final synthetic fun box-impl (J)Lch/trancee/kompact/runtime/IntResult; + public static fun constructor-impl (J)J + public fun equals (Ljava/lang/Object;)Z + public static fun equals-impl (JLjava/lang/Object;)Z + public static final fun equals-impl0 (JJ)Z + public static final fun getError-impl (J)Lch/trancee/kompact/runtime/KompactDecodeError; + public static final fun getOrThrow-impl (J)I + public final fun getPacked ()J + public fun hashCode ()I + public static fun hashCode-impl (J)I + public static final fun isFailure-impl (J)Z + public static final fun isSuccess-impl (J)Z + public fun toString ()Ljava/lang/String; + public static fun toString-impl (J)Ljava/lang/String; + public final synthetic fun unbox-impl ()J +} + +public final class ch/trancee/kompact/runtime/IntResult$Companion { + public final fun failure-kRpIk-E (Lch/trancee/kompact/runtime/KompactDecodeError;)J + public final fun success-kRpIk-E (I)J +} + +public abstract class ch/trancee/kompact/runtime/KompactDecodeError { +} + +public final class ch/trancee/kompact/runtime/KompactDecodeError$BadLengthPrefix : ch/trancee/kompact/runtime/KompactDecodeError { + public static final field INSTANCE Lch/trancee/kompact/runtime/KompactDecodeError$BadLengthPrefix; +} + +public final class ch/trancee/kompact/runtime/KompactDecodeError$BoundsError : ch/trancee/kompact/runtime/KompactDecodeError { + public static final field INSTANCE Lch/trancee/kompact/runtime/KompactDecodeError$BoundsError; +} + +public final class ch/trancee/kompact/runtime/KompactDecodeError$TruncatedNested : ch/trancee/kompact/runtime/KompactDecodeError { + public static final field INSTANCE Lch/trancee/kompact/runtime/KompactDecodeError$TruncatedNested; +} + +public final class ch/trancee/kompact/runtime/KompactDecodeError$UnknownEnumCode : ch/trancee/kompact/runtime/KompactDecodeError { + public fun (I)V + public final fun component1 ()I + public final fun copy (I)Lch/trancee/kompact/runtime/KompactDecodeError$UnknownEnumCode; + public static synthetic fun copy$default (Lch/trancee/kompact/runtime/KompactDecodeError$UnknownEnumCode;IILjava/lang/Object;)Lch/trancee/kompact/runtime/KompactDecodeError$UnknownEnumCode; + public fun equals (Ljava/lang/Object;)Z + public final fun getRawCode ()I + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class ch/trancee/kompact/runtime/KompactDecodeException : java/lang/RuntimeException { + public fun (Lch/trancee/kompact/runtime/KompactDecodeError;)V + public final fun getError ()Lch/trancee/kompact/runtime/KompactDecodeError; +} + public abstract interface annotation class ch/trancee/kompact/runtime/KompactField : java/lang/annotation/Annotation { public abstract fun bitOffset ()I public abstract fun bitWidth ()I @@ -27,7 +178,68 @@ public final class ch/trancee/kompact/runtime/KompactRuntime { public static final field INSTANCE Lch/trancee/kompact/runtime/KompactRuntime; public final fun readBits ([BII)I public final fun readBitsBoolean ([BI)Z + public final fun readBitsLong ([BII)J + public final fun readBool-8-YI_fo ([BI)J + public final fun readDouble-HEcpYgM ([BI)J + public final fun readFloat-jORRDc0 ([BI)J + public final fun readInt16-xlB7gnY ([BII)J + public final fun readInt32-TxaLSrY ([BII)J + public final fun readInt64--8k1EKY ([BII)J + public final fun readInt8-uD3HChw ([BII)J + public final fun readUInt16-xlB7gnY ([BII)J + public final fun readUInt32-TxaLSrY ([BII)J + public final fun readUInt64--8k1EKY ([BII)J + public final fun readUInt8-uD3HChw ([BII)J public final fun writeBits ([BIII)V public final fun writeBitsBoolean ([BIZ)V + public final fun writeBitsLong ([BIIJ)V +} + +public final class ch/trancee/kompact/runtime/LongResult { + public static final field Companion Lch/trancee/kompact/runtime/LongResult$Companion; + public static final synthetic fun box-impl (J)Lch/trancee/kompact/runtime/LongResult; + public static fun constructor-impl (J)J + public fun equals (Ljava/lang/Object;)Z + public static fun equals-impl (JLjava/lang/Object;)Z + public static final fun equals-impl0 (JJ)Z + public static final fun getError-impl (J)Lch/trancee/kompact/runtime/KompactDecodeError; + public static final fun getOrThrow-impl (J)J + public final fun getPacked ()J + public fun hashCode ()I + public static fun hashCode-impl (J)I + public static final fun isFailure-impl (J)Z + public static final fun isSuccess-impl (J)Z + public fun toString ()Ljava/lang/String; + public static fun toString-impl (J)Ljava/lang/String; + public final synthetic fun unbox-impl ()J +} + +public final class ch/trancee/kompact/runtime/LongResult$Companion { + public final fun failure-cFSl1BI (Lch/trancee/kompact/runtime/KompactDecodeError;)J + public final fun success-cFSl1BI (J)J +} + +public final class ch/trancee/kompact/runtime/ShortResult { + public static final field Companion Lch/trancee/kompact/runtime/ShortResult$Companion; + public static final synthetic fun box-impl (J)Lch/trancee/kompact/runtime/ShortResult; + public static fun constructor-impl (J)J + public fun equals (Ljava/lang/Object;)Z + public static fun equals-impl (JLjava/lang/Object;)Z + public static final fun equals-impl0 (JJ)Z + public static final fun getError-impl (J)Lch/trancee/kompact/runtime/KompactDecodeError; + public static final fun getOrThrow-impl (J)S + public final fun getPacked ()J + public fun hashCode ()I + public static fun hashCode-impl (J)I + public static final fun isFailure-impl (J)Z + public static final fun isSuccess-impl (J)Z + public fun toString ()Ljava/lang/String; + public static fun toString-impl (J)Ljava/lang/String; + public final synthetic fun unbox-impl ()J +} + +public final class ch/trancee/kompact/runtime/ShortResult$Companion { + public final fun failure-9iWuYEQ (Lch/trancee/kompact/runtime/KompactDecodeError;)J + public final fun success-9iWuYEQ (S)J } diff --git a/kompact/api/kompact.klib.api b/kompact/api/kompact.klib.api index d248a3a..5a91c5c 100644 --- a/kompact/api/kompact.klib.api +++ b/kompact/api/kompact.klib.api @@ -19,6 +19,13 @@ open annotation class ch.trancee.kompact.runtime/KompactModel : kotlin/Annotatio constructor () // ch.trancee.kompact.runtime/KompactModel.|(){}[0] } +final class ch.trancee.kompact.runtime/KompactDecodeException : kotlin/RuntimeException { // ch.trancee.kompact.runtime/KompactDecodeException|null[0] + constructor (ch.trancee.kompact.runtime/KompactDecodeError) // ch.trancee.kompact.runtime/KompactDecodeException.|(ch.trancee.kompact.runtime.KompactDecodeError){}[0] + + final val error // ch.trancee.kompact.runtime/KompactDecodeException.error|{}error[0] + final fun (): ch.trancee.kompact.runtime/KompactDecodeError // ch.trancee.kompact.runtime/KompactDecodeException.error.|(){}[0] +} + final value class ch.trancee.kompact.generated/VehicleTelemetry { // ch.trancee.kompact.generated/VehicleTelemetry|null[0] constructor (kotlin/ByteArray) // ch.trancee.kompact.generated/VehicleTelemetry.|(kotlin.ByteArray){}[0] @@ -36,9 +43,204 @@ final value class ch.trancee.kompact.generated/VehicleTelemetry { // ch.trancee. final fun toString(): kotlin/String // ch.trancee.kompact.generated/VehicleTelemetry.toString|toString(){}[0] } +final value class ch.trancee.kompact.runtime/BooleanResult { // ch.trancee.kompact.runtime/BooleanResult|null[0] + constructor (kotlin/Long) // ch.trancee.kompact.runtime/BooleanResult.|(kotlin.Long){}[0] + + final val error // ch.trancee.kompact.runtime/BooleanResult.error|{}error[0] + final fun (): ch.trancee.kompact.runtime/KompactDecodeError? // ch.trancee.kompact.runtime/BooleanResult.error.|(){}[0] + final val isFailure // ch.trancee.kompact.runtime/BooleanResult.isFailure|{}isFailure[0] + final fun (): kotlin/Boolean // ch.trancee.kompact.runtime/BooleanResult.isFailure.|(){}[0] + final val isSuccess // ch.trancee.kompact.runtime/BooleanResult.isSuccess|{}isSuccess[0] + final fun (): kotlin/Boolean // ch.trancee.kompact.runtime/BooleanResult.isSuccess.|(){}[0] + final val packed // ch.trancee.kompact.runtime/BooleanResult.packed|{}packed[0] + final fun (): kotlin/Long // ch.trancee.kompact.runtime/BooleanResult.packed.|(){}[0] + + final fun equals(kotlin/Any?): kotlin/Boolean // ch.trancee.kompact.runtime/BooleanResult.equals|equals(kotlin.Any?){}[0] + final fun getOrThrow(): kotlin/Boolean // ch.trancee.kompact.runtime/BooleanResult.getOrThrow|getOrThrow(){}[0] + final fun hashCode(): kotlin/Int // ch.trancee.kompact.runtime/BooleanResult.hashCode|hashCode(){}[0] + final fun toString(): kotlin/String // ch.trancee.kompact.runtime/BooleanResult.toString|toString(){}[0] + + final object Companion { // ch.trancee.kompact.runtime/BooleanResult.Companion|null[0] + final fun failure(ch.trancee.kompact.runtime/KompactDecodeError): ch.trancee.kompact.runtime/BooleanResult // ch.trancee.kompact.runtime/BooleanResult.Companion.failure|failure(ch.trancee.kompact.runtime.KompactDecodeError){}[0] + final fun success(kotlin/Boolean): ch.trancee.kompact.runtime/BooleanResult // ch.trancee.kompact.runtime/BooleanResult.Companion.success|success(kotlin.Boolean){}[0] + } +} + +final value class ch.trancee.kompact.runtime/ByteResult { // ch.trancee.kompact.runtime/ByteResult|null[0] + constructor (kotlin/Long) // ch.trancee.kompact.runtime/ByteResult.|(kotlin.Long){}[0] + + final val error // ch.trancee.kompact.runtime/ByteResult.error|{}error[0] + final fun (): ch.trancee.kompact.runtime/KompactDecodeError? // ch.trancee.kompact.runtime/ByteResult.error.|(){}[0] + final val isFailure // ch.trancee.kompact.runtime/ByteResult.isFailure|{}isFailure[0] + final fun (): kotlin/Boolean // ch.trancee.kompact.runtime/ByteResult.isFailure.|(){}[0] + final val isSuccess // ch.trancee.kompact.runtime/ByteResult.isSuccess|{}isSuccess[0] + final fun (): kotlin/Boolean // ch.trancee.kompact.runtime/ByteResult.isSuccess.|(){}[0] + final val packed // ch.trancee.kompact.runtime/ByteResult.packed|{}packed[0] + final fun (): kotlin/Long // ch.trancee.kompact.runtime/ByteResult.packed.|(){}[0] + + final fun equals(kotlin/Any?): kotlin/Boolean // ch.trancee.kompact.runtime/ByteResult.equals|equals(kotlin.Any?){}[0] + final fun getOrThrow(): kotlin/Byte // ch.trancee.kompact.runtime/ByteResult.getOrThrow|getOrThrow(){}[0] + final fun hashCode(): kotlin/Int // ch.trancee.kompact.runtime/ByteResult.hashCode|hashCode(){}[0] + final fun toString(): kotlin/String // ch.trancee.kompact.runtime/ByteResult.toString|toString(){}[0] + + final object Companion { // ch.trancee.kompact.runtime/ByteResult.Companion|null[0] + final fun failure(ch.trancee.kompact.runtime/KompactDecodeError): ch.trancee.kompact.runtime/ByteResult // ch.trancee.kompact.runtime/ByteResult.Companion.failure|failure(ch.trancee.kompact.runtime.KompactDecodeError){}[0] + final fun success(kotlin/Byte): ch.trancee.kompact.runtime/ByteResult // ch.trancee.kompact.runtime/ByteResult.Companion.success|success(kotlin.Byte){}[0] + } +} + +final value class ch.trancee.kompact.runtime/DoubleResult { // ch.trancee.kompact.runtime/DoubleResult|null[0] + constructor (kotlin/Long) // ch.trancee.kompact.runtime/DoubleResult.|(kotlin.Long){}[0] + + final val error // ch.trancee.kompact.runtime/DoubleResult.error|{}error[0] + final fun (): ch.trancee.kompact.runtime/KompactDecodeError? // ch.trancee.kompact.runtime/DoubleResult.error.|(){}[0] + final val isFailure // ch.trancee.kompact.runtime/DoubleResult.isFailure|{}isFailure[0] + final fun (): kotlin/Boolean // ch.trancee.kompact.runtime/DoubleResult.isFailure.|(){}[0] + final val isSuccess // ch.trancee.kompact.runtime/DoubleResult.isSuccess|{}isSuccess[0] + final fun (): kotlin/Boolean // ch.trancee.kompact.runtime/DoubleResult.isSuccess.|(){}[0] + final val packed // ch.trancee.kompact.runtime/DoubleResult.packed|{}packed[0] + final fun (): kotlin/Long // ch.trancee.kompact.runtime/DoubleResult.packed.|(){}[0] + + final fun equals(kotlin/Any?): kotlin/Boolean // ch.trancee.kompact.runtime/DoubleResult.equals|equals(kotlin.Any?){}[0] + final fun getOrThrow(): kotlin/Double // ch.trancee.kompact.runtime/DoubleResult.getOrThrow|getOrThrow(){}[0] + final fun hashCode(): kotlin/Int // ch.trancee.kompact.runtime/DoubleResult.hashCode|hashCode(){}[0] + final fun toString(): kotlin/String // ch.trancee.kompact.runtime/DoubleResult.toString|toString(){}[0] + + final object Companion { // ch.trancee.kompact.runtime/DoubleResult.Companion|null[0] + final fun failure(ch.trancee.kompact.runtime/KompactDecodeError): ch.trancee.kompact.runtime/DoubleResult // ch.trancee.kompact.runtime/DoubleResult.Companion.failure|failure(ch.trancee.kompact.runtime.KompactDecodeError){}[0] + final fun success(kotlin/Double): ch.trancee.kompact.runtime/DoubleResult // ch.trancee.kompact.runtime/DoubleResult.Companion.success|success(kotlin.Double){}[0] + } +} + +final value class ch.trancee.kompact.runtime/FloatResult { // ch.trancee.kompact.runtime/FloatResult|null[0] + constructor (kotlin/Long) // ch.trancee.kompact.runtime/FloatResult.|(kotlin.Long){}[0] + + final val error // ch.trancee.kompact.runtime/FloatResult.error|{}error[0] + final fun (): ch.trancee.kompact.runtime/KompactDecodeError? // ch.trancee.kompact.runtime/FloatResult.error.|(){}[0] + final val isFailure // ch.trancee.kompact.runtime/FloatResult.isFailure|{}isFailure[0] + final fun (): kotlin/Boolean // ch.trancee.kompact.runtime/FloatResult.isFailure.|(){}[0] + final val isSuccess // ch.trancee.kompact.runtime/FloatResult.isSuccess|{}isSuccess[0] + final fun (): kotlin/Boolean // ch.trancee.kompact.runtime/FloatResult.isSuccess.|(){}[0] + final val packed // ch.trancee.kompact.runtime/FloatResult.packed|{}packed[0] + final fun (): kotlin/Long // ch.trancee.kompact.runtime/FloatResult.packed.|(){}[0] + + final fun equals(kotlin/Any?): kotlin/Boolean // ch.trancee.kompact.runtime/FloatResult.equals|equals(kotlin.Any?){}[0] + final fun getOrThrow(): kotlin/Float // ch.trancee.kompact.runtime/FloatResult.getOrThrow|getOrThrow(){}[0] + final fun hashCode(): kotlin/Int // ch.trancee.kompact.runtime/FloatResult.hashCode|hashCode(){}[0] + final fun toString(): kotlin/String // ch.trancee.kompact.runtime/FloatResult.toString|toString(){}[0] + + final object Companion { // ch.trancee.kompact.runtime/FloatResult.Companion|null[0] + final fun failure(ch.trancee.kompact.runtime/KompactDecodeError): ch.trancee.kompact.runtime/FloatResult // ch.trancee.kompact.runtime/FloatResult.Companion.failure|failure(ch.trancee.kompact.runtime.KompactDecodeError){}[0] + final fun success(kotlin/Float): ch.trancee.kompact.runtime/FloatResult // ch.trancee.kompact.runtime/FloatResult.Companion.success|success(kotlin.Float){}[0] + } +} + +final value class ch.trancee.kompact.runtime/IntResult { // ch.trancee.kompact.runtime/IntResult|null[0] + constructor (kotlin/Long) // ch.trancee.kompact.runtime/IntResult.|(kotlin.Long){}[0] + + final val error // ch.trancee.kompact.runtime/IntResult.error|{}error[0] + final fun (): ch.trancee.kompact.runtime/KompactDecodeError? // ch.trancee.kompact.runtime/IntResult.error.|(){}[0] + final val isFailure // ch.trancee.kompact.runtime/IntResult.isFailure|{}isFailure[0] + final fun (): kotlin/Boolean // ch.trancee.kompact.runtime/IntResult.isFailure.|(){}[0] + final val isSuccess // ch.trancee.kompact.runtime/IntResult.isSuccess|{}isSuccess[0] + final fun (): kotlin/Boolean // ch.trancee.kompact.runtime/IntResult.isSuccess.|(){}[0] + final val packed // ch.trancee.kompact.runtime/IntResult.packed|{}packed[0] + final fun (): kotlin/Long // ch.trancee.kompact.runtime/IntResult.packed.|(){}[0] + + final fun equals(kotlin/Any?): kotlin/Boolean // ch.trancee.kompact.runtime/IntResult.equals|equals(kotlin.Any?){}[0] + final fun getOrThrow(): kotlin/Int // ch.trancee.kompact.runtime/IntResult.getOrThrow|getOrThrow(){}[0] + final fun hashCode(): kotlin/Int // ch.trancee.kompact.runtime/IntResult.hashCode|hashCode(){}[0] + final fun toString(): kotlin/String // ch.trancee.kompact.runtime/IntResult.toString|toString(){}[0] + + final object Companion { // ch.trancee.kompact.runtime/IntResult.Companion|null[0] + final fun failure(ch.trancee.kompact.runtime/KompactDecodeError): ch.trancee.kompact.runtime/IntResult // ch.trancee.kompact.runtime/IntResult.Companion.failure|failure(ch.trancee.kompact.runtime.KompactDecodeError){}[0] + final fun success(kotlin/Int): ch.trancee.kompact.runtime/IntResult // ch.trancee.kompact.runtime/IntResult.Companion.success|success(kotlin.Int){}[0] + } +} + +final value class ch.trancee.kompact.runtime/LongResult { // ch.trancee.kompact.runtime/LongResult|null[0] + constructor (kotlin/Long) // ch.trancee.kompact.runtime/LongResult.|(kotlin.Long){}[0] + + final val error // ch.trancee.kompact.runtime/LongResult.error|{}error[0] + final fun (): ch.trancee.kompact.runtime/KompactDecodeError? // ch.trancee.kompact.runtime/LongResult.error.|(){}[0] + final val isFailure // ch.trancee.kompact.runtime/LongResult.isFailure|{}isFailure[0] + final fun (): kotlin/Boolean // ch.trancee.kompact.runtime/LongResult.isFailure.|(){}[0] + final val isSuccess // ch.trancee.kompact.runtime/LongResult.isSuccess|{}isSuccess[0] + final fun (): kotlin/Boolean // ch.trancee.kompact.runtime/LongResult.isSuccess.|(){}[0] + final val packed // ch.trancee.kompact.runtime/LongResult.packed|{}packed[0] + final fun (): kotlin/Long // ch.trancee.kompact.runtime/LongResult.packed.|(){}[0] + + final fun equals(kotlin/Any?): kotlin/Boolean // ch.trancee.kompact.runtime/LongResult.equals|equals(kotlin.Any?){}[0] + final fun getOrThrow(): kotlin/Long // ch.trancee.kompact.runtime/LongResult.getOrThrow|getOrThrow(){}[0] + final fun hashCode(): kotlin/Int // ch.trancee.kompact.runtime/LongResult.hashCode|hashCode(){}[0] + final fun toString(): kotlin/String // ch.trancee.kompact.runtime/LongResult.toString|toString(){}[0] + + final object Companion { // ch.trancee.kompact.runtime/LongResult.Companion|null[0] + final fun failure(ch.trancee.kompact.runtime/KompactDecodeError): ch.trancee.kompact.runtime/LongResult // ch.trancee.kompact.runtime/LongResult.Companion.failure|failure(ch.trancee.kompact.runtime.KompactDecodeError){}[0] + final fun success(kotlin/Long): ch.trancee.kompact.runtime/LongResult // ch.trancee.kompact.runtime/LongResult.Companion.success|success(kotlin.Long){}[0] + } +} + +final value class ch.trancee.kompact.runtime/ShortResult { // ch.trancee.kompact.runtime/ShortResult|null[0] + constructor (kotlin/Long) // ch.trancee.kompact.runtime/ShortResult.|(kotlin.Long){}[0] + + final val error // ch.trancee.kompact.runtime/ShortResult.error|{}error[0] + final fun (): ch.trancee.kompact.runtime/KompactDecodeError? // ch.trancee.kompact.runtime/ShortResult.error.|(){}[0] + final val isFailure // ch.trancee.kompact.runtime/ShortResult.isFailure|{}isFailure[0] + final fun (): kotlin/Boolean // ch.trancee.kompact.runtime/ShortResult.isFailure.|(){}[0] + final val isSuccess // ch.trancee.kompact.runtime/ShortResult.isSuccess|{}isSuccess[0] + final fun (): kotlin/Boolean // ch.trancee.kompact.runtime/ShortResult.isSuccess.|(){}[0] + final val packed // ch.trancee.kompact.runtime/ShortResult.packed|{}packed[0] + final fun (): kotlin/Long // ch.trancee.kompact.runtime/ShortResult.packed.|(){}[0] + + final fun equals(kotlin/Any?): kotlin/Boolean // ch.trancee.kompact.runtime/ShortResult.equals|equals(kotlin.Any?){}[0] + final fun getOrThrow(): kotlin/Short // ch.trancee.kompact.runtime/ShortResult.getOrThrow|getOrThrow(){}[0] + final fun hashCode(): kotlin/Int // ch.trancee.kompact.runtime/ShortResult.hashCode|hashCode(){}[0] + final fun toString(): kotlin/String // ch.trancee.kompact.runtime/ShortResult.toString|toString(){}[0] + + final object Companion { // ch.trancee.kompact.runtime/ShortResult.Companion|null[0] + final fun failure(ch.trancee.kompact.runtime/KompactDecodeError): ch.trancee.kompact.runtime/ShortResult // ch.trancee.kompact.runtime/ShortResult.Companion.failure|failure(ch.trancee.kompact.runtime.KompactDecodeError){}[0] + final fun success(kotlin/Short): ch.trancee.kompact.runtime/ShortResult // ch.trancee.kompact.runtime/ShortResult.Companion.success|success(kotlin.Short){}[0] + } +} + +sealed class ch.trancee.kompact.runtime/KompactDecodeError { // ch.trancee.kompact.runtime/KompactDecodeError|null[0] + final class UnknownEnumCode : ch.trancee.kompact.runtime/KompactDecodeError { // ch.trancee.kompact.runtime/KompactDecodeError.UnknownEnumCode|null[0] + constructor (kotlin/Int) // ch.trancee.kompact.runtime/KompactDecodeError.UnknownEnumCode.|(kotlin.Int){}[0] + + final val rawCode // ch.trancee.kompact.runtime/KompactDecodeError.UnknownEnumCode.rawCode|{}rawCode[0] + final fun (): kotlin/Int // ch.trancee.kompact.runtime/KompactDecodeError.UnknownEnumCode.rawCode.|(){}[0] + + final fun component1(): kotlin/Int // ch.trancee.kompact.runtime/KompactDecodeError.UnknownEnumCode.component1|component1(){}[0] + final fun copy(kotlin/Int = ...): ch.trancee.kompact.runtime/KompactDecodeError.UnknownEnumCode // ch.trancee.kompact.runtime/KompactDecodeError.UnknownEnumCode.copy|copy(kotlin.Int){}[0] + final fun equals(kotlin/Any?): kotlin/Boolean // ch.trancee.kompact.runtime/KompactDecodeError.UnknownEnumCode.equals|equals(kotlin.Any?){}[0] + final fun hashCode(): kotlin/Int // ch.trancee.kompact.runtime/KompactDecodeError.UnknownEnumCode.hashCode|hashCode(){}[0] + final fun toString(): kotlin/String // ch.trancee.kompact.runtime/KompactDecodeError.UnknownEnumCode.toString|toString(){}[0] + } + + final object BadLengthPrefix : ch.trancee.kompact.runtime/KompactDecodeError // ch.trancee.kompact.runtime/KompactDecodeError.BadLengthPrefix|null[0] + + final object BoundsError : ch.trancee.kompact.runtime/KompactDecodeError // ch.trancee.kompact.runtime/KompactDecodeError.BoundsError|null[0] + + final object TruncatedNested : ch.trancee.kompact.runtime/KompactDecodeError // ch.trancee.kompact.runtime/KompactDecodeError.TruncatedNested|null[0] +} + final object ch.trancee.kompact.runtime/KompactRuntime { // ch.trancee.kompact.runtime/KompactRuntime|null[0] final fun readBits(kotlin/ByteArray, kotlin/Int, kotlin/Int): kotlin/Int // ch.trancee.kompact.runtime/KompactRuntime.readBits|readBits(kotlin.ByteArray;kotlin.Int;kotlin.Int){}[0] final fun readBitsBoolean(kotlin/ByteArray, kotlin/Int): kotlin/Boolean // ch.trancee.kompact.runtime/KompactRuntime.readBitsBoolean|readBitsBoolean(kotlin.ByteArray;kotlin.Int){}[0] + final fun readBitsLong(kotlin/ByteArray, kotlin/Int, kotlin/Int): kotlin/Long // ch.trancee.kompact.runtime/KompactRuntime.readBitsLong|readBitsLong(kotlin.ByteArray;kotlin.Int;kotlin.Int){}[0] + final fun readBool(kotlin/ByteArray, kotlin/Int): ch.trancee.kompact.runtime/BooleanResult // ch.trancee.kompact.runtime/KompactRuntime.readBool|readBool(kotlin.ByteArray;kotlin.Int){}[0] + final fun readDouble(kotlin/ByteArray, kotlin/Int): ch.trancee.kompact.runtime/DoubleResult // ch.trancee.kompact.runtime/KompactRuntime.readDouble|readDouble(kotlin.ByteArray;kotlin.Int){}[0] + final fun readFloat(kotlin/ByteArray, kotlin/Int): ch.trancee.kompact.runtime/FloatResult // ch.trancee.kompact.runtime/KompactRuntime.readFloat|readFloat(kotlin.ByteArray;kotlin.Int){}[0] + final fun readInt16(kotlin/ByteArray, kotlin/Int, kotlin/Int): ch.trancee.kompact.runtime/ShortResult // ch.trancee.kompact.runtime/KompactRuntime.readInt16|readInt16(kotlin.ByteArray;kotlin.Int;kotlin.Int){}[0] + final fun readInt32(kotlin/ByteArray, kotlin/Int, kotlin/Int): ch.trancee.kompact.runtime/IntResult // ch.trancee.kompact.runtime/KompactRuntime.readInt32|readInt32(kotlin.ByteArray;kotlin.Int;kotlin.Int){}[0] + final fun readInt64(kotlin/ByteArray, kotlin/Int, kotlin/Int): ch.trancee.kompact.runtime/LongResult // ch.trancee.kompact.runtime/KompactRuntime.readInt64|readInt64(kotlin.ByteArray;kotlin.Int;kotlin.Int){}[0] + final fun readInt8(kotlin/ByteArray, kotlin/Int, kotlin/Int): ch.trancee.kompact.runtime/ByteResult // ch.trancee.kompact.runtime/KompactRuntime.readInt8|readInt8(kotlin.ByteArray;kotlin.Int;kotlin.Int){}[0] + final fun readUInt16(kotlin/ByteArray, kotlin/Int, kotlin/Int): ch.trancee.kompact.runtime/ShortResult // ch.trancee.kompact.runtime/KompactRuntime.readUInt16|readUInt16(kotlin.ByteArray;kotlin.Int;kotlin.Int){}[0] + final fun readUInt32(kotlin/ByteArray, kotlin/Int, kotlin/Int): ch.trancee.kompact.runtime/IntResult // ch.trancee.kompact.runtime/KompactRuntime.readUInt32|readUInt32(kotlin.ByteArray;kotlin.Int;kotlin.Int){}[0] + final fun readUInt64(kotlin/ByteArray, kotlin/Int, kotlin/Int): ch.trancee.kompact.runtime/LongResult // ch.trancee.kompact.runtime/KompactRuntime.readUInt64|readUInt64(kotlin.ByteArray;kotlin.Int;kotlin.Int){}[0] + final fun readUInt8(kotlin/ByteArray, kotlin/Int, kotlin/Int): ch.trancee.kompact.runtime/ByteResult // ch.trancee.kompact.runtime/KompactRuntime.readUInt8|readUInt8(kotlin.ByteArray;kotlin.Int;kotlin.Int){}[0] final fun writeBits(kotlin/ByteArray, kotlin/Int, kotlin/Int, kotlin/Int) // ch.trancee.kompact.runtime/KompactRuntime.writeBits|writeBits(kotlin.ByteArray;kotlin.Int;kotlin.Int;kotlin.Int){}[0] final fun writeBitsBoolean(kotlin/ByteArray, kotlin/Int, kotlin/Boolean) // ch.trancee.kompact.runtime/KompactRuntime.writeBitsBoolean|writeBitsBoolean(kotlin.ByteArray;kotlin.Int;kotlin.Boolean){}[0] + final fun writeBitsLong(kotlin/ByteArray, kotlin/Int, kotlin/Int, kotlin/Long) // ch.trancee.kompact.runtime/KompactRuntime.writeBitsLong|writeBitsLong(kotlin.ByteArray;kotlin.Int;kotlin.Int;kotlin.Long){}[0] } diff --git a/kompact/src/commonMain/kotlin/ch/trancee/kompact/runtime/KompactDecodeError.kt b/kompact/src/commonMain/kotlin/ch/trancee/kompact/runtime/KompactDecodeError.kt new file mode 100644 index 0000000..ab7be0b --- /dev/null +++ b/kompact/src/commonMain/kotlin/ch/trancee/kompact/runtime/KompactDecodeError.kt @@ -0,0 +1,28 @@ +package ch.trancee.kompact.runtime + +/** + * Runtime decode error taxonomy (Ticket 06). + * + * Returned (never thrown) on the read path: a checked accessor yields a typed + * `Kompact*Result` value class whose [packed][Long] encodes the error kind. + * Accessing `.error` reconstructs the concrete case lazily — singletons on + * the common path, `UnknownEnumCode` allocates only the data-class payload. + */ +public sealed class KompactDecodeError { + + public object BoundsError : KompactDecodeError() + + public object BadLengthPrefix : KompactDecodeError() + + public object TruncatedNested : KompactDecodeError() + + public data class UnknownEnumCode(public val rawCode: Int) : KompactDecodeError() +} + +/** + * Thrown by `getOrThrow()` / `readOrThrow()` on the failure path. The success + * hot-path never throws (Ticket 03 zero-alloc). Allocation of this exception + * is acceptable because it only occurs on an explicit recovery call. + */ +public class KompactDecodeException(public val error: KompactDecodeError) : + RuntimeException("Kompact decode failed: $error") diff --git a/kompact/src/commonMain/kotlin/ch/trancee/kompact/runtime/KompactResult.kt b/kompact/src/commonMain/kotlin/ch/trancee/kompact/runtime/KompactResult.kt new file mode 100644 index 0000000..cd99fb3 --- /dev/null +++ b/kompact/src/commonMain/kotlin/ch/trancee/kompact/runtime/KompactResult.kt @@ -0,0 +1,209 @@ +package ch.trancee.kompact.runtime + +// ==================================================================== +// Ticket 08 — packed-Long encoding constants & shared helpers +// +// ≤32-bit result types (Byte/Short/Int/Float/Boolean) use a single +// packed-Long layout: +// [ ok(bit63) | errorKind(bits 62..60) | rawEnumCode(bits 59..48) | value(bits 47..0) ] +// +// LongResult (64-bit) uses a sentinel range near Long.MIN_VALUE: +// bits 63 set + bits 62..58 clear → failure; error kind in bits 2..0. +// Values in that range are not representable as success (documented). +// +// DoubleResult (64-bit) uses reserved quiet-NaN payloads: +// canonical NaN (payload 0) = success; quiet NaN with non-zero payload +// in bits 3..0 = failure (error kind encoded as payload 1..4). +// +// On both JVM (@JvmInline) and Kotlin/Native (value class over Long) +// the result instance is zero-alloc on both success and failure — +// the packed Long is stored inline (Ticket 03). +// ==================================================================== + +// --- ≤32-bit result encoding --- + +internal val RESULT_OK_FLAG: Long = Long.MIN_VALUE +internal const val RESULT_ERROR_KIND_SHIFT: Int = 60 +internal const val RESULT_RAW_ENUM_SHIFT: Int = 48 +internal val RESULT_VALUE_MASK: Long = 0x0000_FFFF_FFFF_FFFFL + +// --- LongResult sentinel encoding --- + +internal val LONG_FAIL_MASK: Long = Long.MIN_VALUE or 0x7C00_0000_0000_0000L +internal val LONG_FAIL_BASE: Long = Long.MIN_VALUE + +// --- DoubleResult NaN encoding --- + +internal val DOUBLE_NAN_CANONICAL: Long = 0x7FF8_0000_0000_0000L +internal val DOUBLE_ERROR_PAYLOAD_MASK: Long = 0x0000_0000_0000_000FL + +// --- Error kind codes (shared across all encodings) --- + +internal const val ERROR_BOUNDS: Int = 0 +internal const val ERROR_BAD_LENGTH: Int = 1 +internal const val ERROR_TRUNCATED: Int = 2 +internal const val ERROR_UNKNOWN_ENUM: Int = 3 + +// === Shared helpers (commonMain, visible from platform actuals) === + +internal fun encodeErrorKind(error: KompactDecodeError): Int = when (error) { + is KompactDecodeError.BoundsError -> ERROR_BOUNDS + is KompactDecodeError.BadLengthPrefix -> ERROR_BAD_LENGTH + is KompactDecodeError.TruncatedNested -> ERROR_TRUNCATED + is KompactDecodeError.UnknownEnumCode -> ERROR_UNKNOWN_ENUM +} + +internal fun decodeErrorFromSmallBits(packed: Long): KompactDecodeError { + val kind = ((packed ushr RESULT_ERROR_KIND_SHIFT) and 0x7L).toInt() + val rawCode = ((packed ushr RESULT_RAW_ENUM_SHIFT) and 0xFFFL).toInt() + return when (kind) { + ERROR_BOUNDS -> KompactDecodeError.BoundsError + ERROR_BAD_LENGTH -> KompactDecodeError.BadLengthPrefix + ERROR_TRUNCATED -> KompactDecodeError.TruncatedNested + ERROR_UNKNOWN_ENUM -> KompactDecodeError.UnknownEnumCode(rawCode) + else -> KompactDecodeError.BoundsError + } +} + +internal fun encodeSmallSuccess(value: Long): Long = + RESULT_OK_FLAG or (value and RESULT_VALUE_MASK) + +internal fun encodeSmallFailure(error: KompactDecodeError): Long { + val kind = encodeErrorKind(error).toLong() + val rawCode = if (error is KompactDecodeError.UnknownEnumCode) error.rawCode.toLong() else 0L + return (kind shl RESULT_ERROR_KIND_SHIFT) or (rawCode shl RESULT_RAW_ENUM_SHIFT) +} + +internal fun isLongFailure(packed: Long): Boolean = + (packed and LONG_FAIL_MASK) == LONG_FAIL_BASE + +internal fun encodeLongFailure(error: KompactDecodeError): Long { + val kind = encodeErrorKind(error).toLong() + val rawCode = if (error is KompactDecodeError.UnknownEnumCode) error.rawCode.toLong() else 0L + return LONG_FAIL_BASE or kind or (rawCode shl 3) +} + +internal fun decodeLongError(packed: Long): KompactDecodeError { + val kind = (packed and 0x7L).toInt() + val rawCode = ((packed ushr 3) and 0xFFL).toInt() + return when (kind) { + ERROR_BOUNDS -> KompactDecodeError.BoundsError + ERROR_BAD_LENGTH -> KompactDecodeError.BadLengthPrefix + ERROR_TRUNCATED -> KompactDecodeError.TruncatedNested + ERROR_UNKNOWN_ENUM -> KompactDecodeError.UnknownEnumCode(rawCode) + else -> KompactDecodeError.BoundsError + } +} + +internal fun isDoubleFailure(packed: Long): Boolean { + val payload = packed and DOUBLE_ERROR_PAYLOAD_MASK + return payload != 0L && (packed and 0x7FF8_0000_0000_0000L) == DOUBLE_NAN_CANONICAL +} + +internal fun encodeDoubleFailure(error: KompactDecodeError): Long { + val kind = encodeErrorKind(error).toLong() + val rawCode = if (error is KompactDecodeError.UnknownEnumCode) error.rawCode.toLong() else 0L + // payload = kind + 1 (1..4); 0 is reserved for canonical success NaN + return DOUBLE_NAN_CANONICAL or (kind + 1L) or (rawCode shl 4) +} + +internal fun decodeDoubleError(packed: Long): KompactDecodeError { + val payload = (packed and DOUBLE_ERROR_PAYLOAD_MASK).toInt() + val kind = payload - 1 + val rawCode = ((packed ushr 4) and 0xFFL).toInt() + return when (kind) { + ERROR_BOUNDS -> KompactDecodeError.BoundsError + ERROR_BAD_LENGTH -> KompactDecodeError.BadLengthPrefix + ERROR_TRUNCATED -> KompactDecodeError.TruncatedNested + ERROR_UNKNOWN_ENUM -> KompactDecodeError.UnknownEnumCode(rawCode) + else -> KompactDecodeError.BoundsError + } +} + +internal fun encodeDoubleSuccess(value: Double): Long = + if (value.isNaN()) DOUBLE_NAN_CANONICAL else value.toBits() + +// ==================================================================== +// Ticket 08 — result value class declarations (expect) +// +// 7 specialized result types — one per scalar kind. No generic T. +// Each wraps a single Long, zero-alloc on both JVM (@JvmInline) and +// Kotlin/Native (value class). +// ==================================================================== + +public expect value class ByteResult(public val packed: Long) { + public val isSuccess: Boolean + public val isFailure: Boolean + public val error: KompactDecodeError? + public fun getOrThrow(): Byte + public companion object { + public fun success(value: Byte): ByteResult + public fun failure(error: KompactDecodeError): ByteResult + } +} + +public expect value class ShortResult(public val packed: Long) { + public val isSuccess: Boolean + public val isFailure: Boolean + public val error: KompactDecodeError? + public fun getOrThrow(): Short + public companion object { + public fun success(value: Short): ShortResult + public fun failure(error: KompactDecodeError): ShortResult + } +} + +public expect value class IntResult(public val packed: Long) { + public val isSuccess: Boolean + public val isFailure: Boolean + public val error: KompactDecodeError? + public fun getOrThrow(): Int + public companion object { + public fun success(value: Int): IntResult + public fun failure(error: KompactDecodeError): IntResult + } +} + +public expect value class FloatResult(public val packed: Long) { + public val isSuccess: Boolean + public val isFailure: Boolean + public val error: KompactDecodeError? + public fun getOrThrow(): Float + public companion object { + public fun success(value: Float): FloatResult + public fun failure(error: KompactDecodeError): FloatResult + } +} + +public expect value class BooleanResult(public val packed: Long) { + public val isSuccess: Boolean + public val isFailure: Boolean + public val error: KompactDecodeError? + public fun getOrThrow(): Boolean + public companion object { + public fun success(value: Boolean): BooleanResult + public fun failure(error: KompactDecodeError): BooleanResult + } +} + +public expect value class LongResult(public val packed: Long) { + public val isSuccess: Boolean + public val isFailure: Boolean + public val error: KompactDecodeError? + public fun getOrThrow(): Long + public companion object { + public fun success(value: Long): LongResult + public fun failure(error: KompactDecodeError): LongResult + } +} + +public expect value class DoubleResult(public val packed: Long) { + public val isSuccess: Boolean + public val isFailure: Boolean + public val error: KompactDecodeError? + public fun getOrThrow(): Double + public companion object { + public fun success(value: Double): DoubleResult + public fun failure(error: KompactDecodeError): DoubleResult + } +} diff --git a/kompact/src/commonMain/kotlin/ch/trancee/kompact/runtime/KompactRuntime.kt b/kompact/src/commonMain/kotlin/ch/trancee/kompact/runtime/KompactRuntime.kt index 981b870..3623940 100644 --- a/kompact/src/commonMain/kotlin/ch/trancee/kompact/runtime/KompactRuntime.kt +++ b/kompact/src/commonMain/kotlin/ch/trancee/kompact/runtime/KompactRuntime.kt @@ -75,4 +75,170 @@ public object KompactRuntime { raw[byteIndex] = (raw[byteIndex].toInt() and (1 shl bitIndex).inv()).toByte() } } + + /** Reads [bitWidth] bits (1..64) from [raw] starting at [bitOffset], LSB-first. */ + public fun readBitsLong(raw: ByteArray, bitOffset: Int, bitWidth: Int): Long { + var result = 0L + var srcBit = bitOffset + var destBit = 0 + var remaining = bitWidth + while (remaining > 0) { + val byteIndex = srcBit ushr 3 + val bitInByte = srcBit and 7 + val bitsAvailable = 8 - bitInByte + val chunk = minOf(remaining, bitsAvailable) + val byteVal = raw[byteIndex].toInt() and 0xFF + val chunkBits = (byteVal ushr bitInByte) and ((1 shl chunk) - 1) + result = result or (chunkBits.toLong() shl destBit) + srcBit += chunk + destBit += chunk + remaining -= chunk + } + return result + } + + /** Writes the low [bitWidth] bits (1..64) of [value] into [raw] at [bitOffset], LSB-first. */ + public fun writeBitsLong(raw: ByteArray, bitOffset: Int, bitWidth: Int, value: Long) { + var srcBit = bitOffset + var srcValBit = 0 + var remaining = bitWidth + while (remaining > 0) { + val byteIndex = srcBit ushr 3 + val bitInByte = srcBit and 7 + val bitsAvailable = 8 - bitInByte + val chunk = minOf(remaining, bitsAvailable) + val mask = ((1 shl chunk) - 1) shl bitInByte + val byteVal = raw[byteIndex].toInt() and 0xFF + val chunkBits = ((value ushr srcValBit) and ((1L shl chunk) - 1L)).toInt() + val cleared = byteVal and mask.inv() + val set = chunkBits shl bitInByte + raw[byteIndex] = (cleared or set).toByte() + srcBit += chunk + srcValBit += chunk + remaining -= chunk + } + } + + // --- Ticket 06/07 — bounded (checked) read accessors --- + + /** Bounds-check: true iff [bitOffset]+[bitWidth] fits in [raw]. */ + private fun fits(raw: ByteArray, bitOffset: Int, bitWidth: Int): Boolean = + bitOffset >= 0 && bitWidth >= 1 && bitOffset + bitWidth <= raw.size * 8 + + /** Reads 1 bit at [bitOffset] as a checked [BooleanResult]. */ + public fun readBool(raw: ByteArray, bitOffset: Int): BooleanResult { + if (!fits(raw, bitOffset, 1)) { + return BooleanResult.failure(KompactDecodeError.BoundsError) + } + return BooleanResult.success(readBitsBoolean(raw, bitOffset)) + } + + /** Reads up to [bitWidth] bits (1..8) as a sign-extended [ByteResult]. */ + public fun readInt8(raw: ByteArray, bitOffset: Int, bitWidth: Int): ByteResult { + if (!fits(raw, bitOffset, bitWidth) || bitWidth > 8) { + return ByteResult.failure(KompactDecodeError.BoundsError) + } + val magnitude = readBits(raw, bitOffset, bitWidth) + val shift = Int.SIZE_BITS - bitWidth + val signExtended = (magnitude shl shift) shr shift + return ByteResult.success(signExtended.toByte()) + } + + /** Reads up to [bitWidth] bits (1..8) as an unsigned [ByteResult]. */ + public fun readUInt8(raw: ByteArray, bitOffset: Int, bitWidth: Int): ByteResult { + if (!fits(raw, bitOffset, bitWidth) || bitWidth > 8) { + return ByteResult.failure(KompactDecodeError.BoundsError) + } + val magnitude = readBits(raw, bitOffset, bitWidth).toByte() + return ByteResult.success(magnitude) + } + + /** Reads up to [bitWidth] bits (1..16) as a sign-extended [ShortResult]. */ + public fun readInt16(raw: ByteArray, bitOffset: Int, bitWidth: Int): ShortResult { + if (!fits(raw, bitOffset, bitWidth) || bitWidth > 16) { + return ShortResult.failure(KompactDecodeError.BoundsError) + } + val magnitude = readBits(raw, bitOffset, bitWidth) + val shift = Int.SIZE_BITS - bitWidth + val signExtended = (magnitude shl shift) shr shift + return ShortResult.success(signExtended.toShort()) + } + + /** Reads up to [bitWidth] bits (1..16) as an unsigned [ShortResult]. */ + public fun readUInt16(raw: ByteArray, bitOffset: Int, bitWidth: Int): ShortResult { + if (!fits(raw, bitOffset, bitWidth) || bitWidth > 16) { + return ShortResult.failure(KompactDecodeError.BoundsError) + } + val magnitude = readBits(raw, bitOffset, bitWidth).toShort() + return ShortResult.success(magnitude) + } + + /** Reads up to [bitWidth] bits (1..32) as a sign-extended [IntResult]. */ + public fun readInt32(raw: ByteArray, bitOffset: Int, bitWidth: Int): IntResult { + if (!fits(raw, bitOffset, bitWidth) || bitWidth > 32) { + return IntResult.failure(KompactDecodeError.BoundsError) + } + val magnitude = if (bitWidth <= 31) { + readBits(raw, bitOffset, bitWidth).toLong() + } else { + readBitsLong(raw, bitOffset, bitWidth) + } + val shift = Long.SIZE_BITS - bitWidth + val signExtended = if (bitWidth < 64) (magnitude shl shift) shr shift else magnitude + return IntResult.success(signExtended.toInt()) + } + + /** Reads up to [bitWidth] bits (1..32) as an unsigned [IntResult]. */ + public fun readUInt32(raw: ByteArray, bitOffset: Int, bitWidth: Int): IntResult { + if (!fits(raw, bitOffset, bitWidth) || bitWidth > 32) { + return IntResult.failure(KompactDecodeError.BoundsError) + } + val magnitude = if (bitWidth <= 31) { + readBits(raw, bitOffset, bitWidth).toLong() + } else { + readBitsLong(raw, bitOffset, bitWidth) + } + return IntResult.success(magnitude.toInt()) + } + + /** Reads up to [bitWidth] bits (1..64) as a sign-extended [LongResult]. */ + public fun readInt64(raw: ByteArray, bitOffset: Int, bitWidth: Int): LongResult { + if (!fits(raw, bitOffset, bitWidth) || bitWidth > 64) { + return LongResult.failure(KompactDecodeError.BoundsError) + } + val magnitude = readBitsLong(raw, bitOffset, bitWidth) + return if (bitWidth < 64) { + val shift = Long.SIZE_BITS - bitWidth + LongResult.success((magnitude shl shift) shr shift) + } else { + LongResult.success(magnitude) + } + } + + /** Reads up to [bitWidth] bits (1..64) as an unsigned [LongResult]. */ + public fun readUInt64(raw: ByteArray, bitOffset: Int, bitWidth: Int): LongResult { + if (!fits(raw, bitOffset, bitWidth) || bitWidth > 64) { + return LongResult.failure(KompactDecodeError.BoundsError) + } + val magnitude = readBitsLong(raw, bitOffset, bitWidth) + return LongResult.success(magnitude) + } + + /** Reads 32 bits at [bitOffset] as a checked [FloatResult]. */ + public fun readFloat(raw: ByteArray, bitOffset: Int): FloatResult { + if (bitOffset < 0 || bitOffset + 32 > raw.size * 8) { + return FloatResult.failure(KompactDecodeError.BoundsError) + } + val bits = readBitsLong(raw, bitOffset, 32).toInt() + return FloatResult.success(Float.fromBits(bits)) + } + + /** Reads 64 bits at [bitOffset] as a checked [DoubleResult]. */ + public fun readDouble(raw: ByteArray, bitOffset: Int): DoubleResult { + if (bitOffset < 0 || bitOffset + 64 > raw.size * 8) { + return DoubleResult.failure(KompactDecodeError.BoundsError) + } + val bits = readBitsLong(raw, bitOffset, 64) + return DoubleResult.success(Double.fromBits(bits)) + } } diff --git a/kompact/src/commonTest/kotlin/ch/trancee/kompact/runtime/KompactResultTest.kt b/kompact/src/commonTest/kotlin/ch/trancee/kompact/runtime/KompactResultTest.kt new file mode 100644 index 0000000..02dcf23 --- /dev/null +++ b/kompact/src/commonTest/kotlin/ch/trancee/kompact/runtime/KompactResultTest.kt @@ -0,0 +1,398 @@ +package ch.trancee.kompact.runtime + +import kotlin.test.Test +import kotlin.test.assertFailsWith +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertNotEquals +import kotlin.test.assertNull +import kotlin.test.assertSame +import kotlin.test.assertTrue + +class KompactResultTest { + + // === KompactDecodeError (ticket 06) === + + @Test + fun boundsError_isSingleton() { + assertSame(KompactDecodeError.BoundsError, KompactDecodeError.BoundsError) + } + + @Test + fun badLengthPrefix_isSingleton() { + assertSame(KompactDecodeError.BadLengthPrefix, KompactDecodeError.BadLengthPrefix) + } + + @Test + fun truncatedNested_isSingleton() { + assertSame(KompactDecodeError.TruncatedNested, KompactDecodeError.TruncatedNested) + } + + @Test + fun unknownEnumCode_preservesRawCode() { + val err = KompactDecodeError.UnknownEnumCode(17) + assertEquals(17, err.rawCode) + } + + @Test + fun unknownEnumCode_equalityByRawCode() { + assertEquals(KompactDecodeError.UnknownEnumCode(5), KompactDecodeError.UnknownEnumCode(5)) + assertNotEquals(KompactDecodeError.UnknownEnumCode(5), KompactDecodeError.UnknownEnumCode(6)) + } + + // === ByteResult — representative ≤32-bit packed-Long type === + + @Test + fun byteResult_success_positiveValue() { + val r = ByteResult.success(0x7F) + assertTrue(r.isSuccess) + assertFalse(r.isFailure) + assertNull(r.error) + assertEquals(0x7F, r.getOrThrow()) + } + + @Test + fun byteResult_success_negativeValue() { + val r = ByteResult.success(-1) + assertTrue(r.isSuccess) + assertEquals(-1, r.getOrThrow()) + } + + @Test + fun byteResult_success_maxByte() { + val r = ByteResult.success(Byte.MAX_VALUE) + assertTrue(r.isSuccess) + assertEquals(Byte.MAX_VALUE, r.getOrThrow()) + } + + @Test + fun byteResult_success_minByte() { + val r = ByteResult.success(Byte.MIN_VALUE) + assertTrue(r.isSuccess) + assertEquals(Byte.MIN_VALUE, r.getOrThrow()) + } + + @Test + fun byteResult_roundTrip_allSignedBytes() { + for (b in Byte.MIN_VALUE..Byte.MAX_VALUE) { + val r = ByteResult.success(b.toByte()) + assertTrue(r.isSuccess, "byte=$b") + assertEquals(b.toByte(), r.getOrThrow(), "byte=$b") + } + } + + @Test + fun byteResult_failure_boundsError() { + val r = ByteResult.failure(KompactDecodeError.BoundsError) + assertFalse(r.isSuccess) + assertTrue(r.isFailure) + assertEquals(KompactDecodeError.BoundsError, r.error) + } + + @Test + fun byteResult_failure_badLengthPrefix() { + val r = ByteResult.failure(KompactDecodeError.BadLengthPrefix) + assertFalse(r.isSuccess) + assertEquals(KompactDecodeError.BadLengthPrefix, r.error) + } + + @Test + fun byteResult_failure_truncatedNested() { + val r = ByteResult.failure(KompactDecodeError.TruncatedNested) + assertEquals(KompactDecodeError.TruncatedNested, r.error) + } + + @Test + fun byteResult_failure_unknownEnumCode_preservesRawCode() { + val r = ByteResult.failure(KompactDecodeError.UnknownEnumCode(17)) + assertEquals(KompactDecodeError.UnknownEnumCode(17), r.error) + } + + @Test + fun byteResult_getOrThrow_throwsOnFailure() { + val r = ByteResult.failure(KompactDecodeError.BoundsError) + assertFailsWith { r.getOrThrow() } + } + + @Test + fun byteResult_equality() { + assertEquals(ByteResult.success(42), ByteResult.success(42)) + assertEquals(ByteResult.failure(KompactDecodeError.BoundsError), ByteResult.failure(KompactDecodeError.BoundsError)) + assertNotEquals(ByteResult.success(42), ByteResult.failure(KompactDecodeError.BoundsError)) + } + + // === ShortResult === + + @Test + fun shortResult_roundTrip() { + val values: List = listOf(0, 1, -1, 1000, -1000, Short.MAX_VALUE, Short.MIN_VALUE) + for (v in values) { + val r = ShortResult.success(v) + assertTrue(r.isSuccess, "value=$v") + assertEquals(v, r.getOrThrow(), "value=$v") + } + } + + @Test + fun shortResult_failure() { + val r = ShortResult.failure(KompactDecodeError.BadLengthPrefix) + assertFalse(r.isSuccess) + assertEquals(KompactDecodeError.BadLengthPrefix, r.error) + } + + // === IntResult === + + @Test + fun intResult_roundTrip() { + val values = listOf(0, 1, -1, 1008, -1008, Int.MAX_VALUE, Int.MIN_VALUE) + for (v in values) { + val r = IntResult.success(v) + assertTrue(r.isSuccess, "value=$v") + assertEquals(v, r.getOrThrow(), "value=$v") + } + } + + @Test + fun intResult_failure_unknownEnumCode() { + val r = IntResult.failure(KompactDecodeError.UnknownEnumCode(255)) + assertEquals(KompactDecodeError.UnknownEnumCode(255), r.error) + } + + // === FloatResult === + + @Test + fun floatResult_roundTrip() { + val r = FloatResult.success(3.14f) + assertTrue(r.isSuccess) + assertEquals(3.14f, r.getOrThrow(), 0.0001f) + } + + @Test + fun floatResult_negativeInfinity() { + val r = FloatResult.success(Float.NEGATIVE_INFINITY) + assertTrue(r.isSuccess) + assertEquals(Float.NEGATIVE_INFINITY, r.getOrThrow()) + } + + @Test + fun floatResult_positiveInfinity() { + val r = FloatResult.success(Float.POSITIVE_INFINITY) + assertTrue(r.isSuccess) + assertEquals(Float.POSITIVE_INFINITY, r.getOrThrow()) + } + + @Test + fun floatResult_canonicalNan() { + val r = FloatResult.success(Float.NaN) + assertTrue(r.isSuccess) + assertTrue(r.getOrThrow().isNaN()) + } + + @Test + fun floatResult_zero() { + val r = FloatResult.success(0.0f) + assertTrue(r.isSuccess) + assertEquals(0.0f, r.getOrThrow()) + } + + @Test + fun floatResult_failure() { + val r = FloatResult.failure(KompactDecodeError.TruncatedNested) + assertFalse(r.isSuccess) + assertEquals(KompactDecodeError.TruncatedNested, r.error) + } + + // === BooleanResult === + + @Test + fun booleanResult_success_true() { + val r = BooleanResult.success(true) + assertTrue(r.isSuccess) + assertEquals(true, r.getOrThrow()) + } + + @Test + fun booleanResult_success_false() { + val r = BooleanResult.success(false) + assertTrue(r.isSuccess) + assertEquals(false, r.getOrThrow()) + } + + @Test + fun booleanResult_failure() { + val r = BooleanResult.failure(KompactDecodeError.BoundsError) + assertFalse(r.isSuccess) + assertEquals(KompactDecodeError.BoundsError, r.error) + } + + // === LongResult — 64-bit sentinel encoding === + + @Test + fun longResult_success_packsValue() { + val r = LongResult.success(42L) + assertTrue(r.isSuccess) + assertFalse(r.isFailure) + assertNull(r.error) + assertEquals(42L, r.getOrThrow()) + } + + @Test + fun longResult_success_negativeValue() { + val r = LongResult.success(-1L) + assertTrue(r.isSuccess) + assertEquals(-1L, r.getOrThrow()) + } + + @Test + fun longResult_success_maxValue() { + val r = LongResult.success(Long.MAX_VALUE) + assertTrue(r.isSuccess) + assertEquals(Long.MAX_VALUE, r.getOrThrow()) + } + + @Test + fun longResult_success_firstValueAboveSentinelRange() { + // Sentinel range: bits 63 set + 62..58 clear (0x8000_0000_0000_0000 + // through 0x07FF_FFFF_FFFF_FFFF as signed). First representable success + // value with bit 63 set: bit 58 also set → outside the sentinel mask. + val firstAfter = Long.MIN_VALUE + (1L shl 58) + val r = LongResult.success(firstAfter) + assertTrue(r.isSuccess, "value falls outside sentinel range") + assertEquals(firstAfter, r.getOrThrow()) + } + + @Test + fun longResult_sentinelRangeNotRepresentableAsSuccess() { + // Long.MIN_VALUE collides with the failure sentinel base — documented tradeoff. + val r = LongResult.success(Long.MIN_VALUE) + assertFalse(r.isSuccess, "Long.MIN_VALUE is in the failure sentinel range") + } + + @Test + fun longResult_roundTrip_arbitraryLongs() { + val values = listOf(0L, 1L, -1L, 42L, -42L, + Long.MAX_VALUE, 0x4000_0000_0000_0000L, + Long.MIN_VALUE + (1L shl 58)) + for (v in values) { + val r = LongResult.success(v) + assertTrue(r.isSuccess, "value=$v packed=${v.toString(16)}") + assertEquals(v, r.getOrThrow(), "value=$v") + } + } + + @Test + fun longResult_failure_boundsError() { + val r = LongResult.failure(KompactDecodeError.BoundsError) + assertFalse(r.isSuccess) + assertTrue(r.isFailure) + assertEquals(KompactDecodeError.BoundsError, r.error) + } + + @Test + fun longResult_failure_badLengthPrefix() { + val r = LongResult.failure(KompactDecodeError.BadLengthPrefix) + assertEquals(KompactDecodeError.BadLengthPrefix, r.error) + } + + @Test + fun longResult_failure_truncatedNested() { + val r = LongResult.failure(KompactDecodeError.TruncatedNested) + assertEquals(KompactDecodeError.TruncatedNested, r.error) + } + + @Test + fun longResult_failure_unknownEnumCode() { + val r = LongResult.failure(KompactDecodeError.UnknownEnumCode(200)) + assertEquals(KompactDecodeError.UnknownEnumCode(200), r.error) + } + + @Test + fun longResult_getOrThrow_throwsOnFailure() { + val r = LongResult.failure(KompactDecodeError.BoundsError) + assertFailsWith { r.getOrThrow() } + } + + @Test + fun longResult_allFourErrorKinds_distinguishable() { + val bounds = LongResult.failure(KompactDecodeError.BoundsError) + val badLen = LongResult.failure(KompactDecodeError.BadLengthPrefix) + val trunc = LongResult.failure(KompactDecodeError.TruncatedNested) + val unknown = LongResult.failure(KompactDecodeError.UnknownEnumCode(42)) + + assertEquals(KompactDecodeError.BoundsError, bounds.error) + assertEquals(KompactDecodeError.BadLengthPrefix, badLen.error) + assertEquals(KompactDecodeError.TruncatedNested, trunc.error) + assertEquals(KompactDecodeError.UnknownEnumCode(42), unknown.error) + } + + // === DoubleResult — 64-bit NaN error encoding === + + @Test + fun doubleResult_success_packsValue() { + val r = DoubleResult.success(3.14) + assertTrue(r.isSuccess) + assertFalse(r.isFailure) + assertNull(r.error) + assertEquals(3.14, r.getOrThrow(), 0.0001) + } + + @Test + fun doubleResult_roundTrip_arbitraryDoubles() { + val values = listOf(0.0, 1.0, -1.0, 3.14159, -2.71828, + Double.MAX_VALUE, Double.MIN_VALUE, Double.NEGATIVE_INFINITY, + Double.POSITIVE_INFINITY, Double.NaN, -0.0) + for (v in values) { + val r = DoubleResult.success(v) + assertTrue(r.isSuccess, "value=$v") + if (v.isNaN()) { + assertTrue(r.getOrThrow().isNaN(), "value=$v") + } else { + assertEquals(v, r.getOrThrow(), 0.0, "value=$v") + } + } + } + + @Test + fun doubleResult_failure_boundsError() { + val r = DoubleResult.failure(KompactDecodeError.BoundsError) + assertFalse(r.isSuccess) + assertEquals(KompactDecodeError.BoundsError, r.error) + } + + @Test + fun doubleResult_failure_badLengthPrefix() { + val r = DoubleResult.failure(KompactDecodeError.BadLengthPrefix) + assertEquals(KompactDecodeError.BadLengthPrefix, r.error) + } + + @Test + fun doubleResult_failure_truncatedNested() { + val r = DoubleResult.failure(KompactDecodeError.TruncatedNested) + assertEquals(KompactDecodeError.TruncatedNested, r.error) + } + + @Test + fun doubleResult_failure_unknownEnumCode() { + val r = DoubleResult.failure(KompactDecodeError.UnknownEnumCode(42)) + assertEquals(KompactDecodeError.UnknownEnumCode(42), r.error) + } + + @Test + fun doubleResult_getOrThrow_throwsOnFailure() { + val r = DoubleResult.failure(KompactDecodeError.BoundsError) + assertFailsWith { r.getOrThrow() } + } + + @Test + fun doubleResult_allFourErrorKinds_distinguishable() { + val bounds = DoubleResult.failure(KompactDecodeError.BoundsError) + val badLen = DoubleResult.failure(KompactDecodeError.BadLengthPrefix) + val trunc = DoubleResult.failure(KompactDecodeError.TruncatedNested) + val unknown = DoubleResult.failure(KompactDecodeError.UnknownEnumCode(42)) + + assertEquals(KompactDecodeError.BoundsError, bounds.error) + assertEquals(KompactDecodeError.BadLengthPrefix, badLen.error) + assertEquals(KompactDecodeError.TruncatedNested, trunc.error) + assertEquals(KompactDecodeError.UnknownEnumCode(42), unknown.error) + } +} diff --git a/kompact/src/commonTest/kotlin/ch/trancee/kompact/runtime/KompactRuntimeCheckedReadTest.kt b/kompact/src/commonTest/kotlin/ch/trancee/kompact/runtime/KompactRuntimeCheckedReadTest.kt new file mode 100644 index 0000000..ef7ffb8 --- /dev/null +++ b/kompact/src/commonTest/kotlin/ch/trancee/kompact/runtime/KompactRuntimeCheckedReadTest.kt @@ -0,0 +1,421 @@ +package ch.trancee.kompact.runtime + +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +class KompactRuntimeCheckedReadTest { + + // === readBool === + + @Test + fun readBool_success_true() { + val buf = byteArrayOf(0x01) + val r = KompactRuntime.readBool(buf, 0) + assertTrue(r.isSuccess) + assertEquals(true, r.getOrThrow()) + } + + @Test + fun readBool_success_false() { + val buf = byteArrayOf(0x00) + val r = KompactRuntime.readBool(buf, 0) + assertTrue(r.isSuccess) + assertEquals(false, r.getOrThrow()) + } + + @Test + fun readBool_success_bit7() { + val r = KompactRuntime.readBool(byteArrayOf(0x80.toByte()), 7) + assertTrue(r.isSuccess) + assertEquals(true, r.getOrThrow()) + } + + @Test + fun readBool_boundsError_shortBuffer() { + val buf = byteArrayOf(0x00) + val r = KompactRuntime.readBool(buf, 8) + assertFalse(r.isSuccess) + assertEquals(KompactDecodeError.BoundsError, r.error) + } + + // === readInt8 / readUInt8 === + + @Test + fun readInt8_success_positiveValue() { + val buf = ByteArray(1) { 0 } + KompactRuntime.writeBits(buf, 0, 8, 42) + val r = KompactRuntime.readInt8(buf, 0, 8) + assertTrue(r.isSuccess) + assertEquals(42.toByte(), r.getOrThrow()) + } + + @Test + fun readInt8_success_negativeValue_signExtended() { + // 8-bit value 0xC8 (200 unsigned) → -56 signed + val buf = byteArrayOf(0xC8.toByte()) + val r = KompactRuntime.readInt8(buf, 0, 8) + assertTrue(r.isSuccess) + assertEquals((-56).toByte(), r.getOrThrow()) + } + + @Test + fun readInt8_success_smallBitWidth_signExtended() { + // 4-bit value 0b1111 (15 unsigned) → -1 signed + val buf = byteArrayOf(0x0F.toByte()) + val r = KompactRuntime.readInt8(buf, 0, 4) + assertTrue(r.isSuccess) + assertEquals((-1).toByte(), r.getOrThrow()) + } + + @Test + fun readInt8_success_smallBitWidth_positive() { + val buf = ByteArray(1) { 0 } + KompactRuntime.writeBits(buf, 0, 4, 5) + val r = KompactRuntime.readInt8(buf, 0, 4) + assertTrue(r.isSuccess) + assertEquals(5.toByte(), r.getOrThrow()) + } + + @Test + fun readInt8_boundsError_shortBuffer() { + val buf = ByteArray(1) { 0 } + val r = KompactRuntime.readInt8(buf, 4, 8) // needs 12 bits, only 8 available + assertFalse(r.isSuccess) + assertEquals(KompactDecodeError.BoundsError, r.error) + } + + @Test + fun readInt8_boundsError_bitWidthTooLarge() { + val buf = ByteArray(1) { 0 } + val r = KompactRuntime.readInt8(buf, 0, 9) // max 8 + assertFalse(r.isSuccess) + assertEquals(KompactDecodeError.BoundsError, r.error) + } + + @Test + fun readUInt8_success_unsignedValue() { + // 0xFF as unsigned Byte → stored as -1, caller uses .toInt() and 0xFF + val r = KompactRuntime.readUInt8(byteArrayOf(0xFF.toByte()), 0, 8) + assertTrue(r.isSuccess) + val v = r.getOrThrow() + assertEquals(255, v.toInt() and 0xFF) + } + + @Test + fun readUInt8_success_smallBitWidth() { + val buf = ByteArray(1) { 0 } + KompactRuntime.writeBits(buf, 0, 3, 7) + val r = KompactRuntime.readUInt8(buf, 0, 3) + assertTrue(r.isSuccess) + assertEquals(7.toByte(), r.getOrThrow()) + } + + @Test + fun readUInt8_boundsError_shortBuffer() { + val r = KompactRuntime.readUInt8(ByteArray(0), 0, 4) + assertFalse(r.isSuccess) + assertEquals(KompactDecodeError.BoundsError, r.error) + } + + // === readInt16 / readUInt16 === + + @Test + fun readInt16_success_positiveValue() { + val buf = ByteArray(2) { 0 } + KompactRuntime.writeBits(buf, 0, 16, 1024) + val r = KompactRuntime.readInt16(buf, 0, 16) + assertTrue(r.isSuccess) + assertEquals(1024.toShort(), r.getOrThrow()) + } + + @Test + fun readInt16_success_negativeValue_signExtended() { + // 16-bit value 0x8000 (32768 unsigned) → -32768 signed + val buf = ByteArray(2) { 0 } + buf[0] = 0x00.toByte() + buf[1] = 0x80.toByte() // LSB-first: low byte = 0x00, high byte = 0x80 + val r = KompactRuntime.readInt16(buf, 0, 16) + assertTrue(r.isSuccess) + assertEquals(Short.MIN_VALUE, r.getOrThrow()) + } + + @Test + fun readInt16_success_smallBitWidth() { + // 5-bit value 0b11111 (31 unsigned) → -1 signed + val buf = byteArrayOf(0x1F) + val r = KompactRuntime.readInt16(buf, 0, 5) + assertTrue(r.isSuccess) + assertEquals((-1).toShort(), r.getOrThrow()) + } + + @Test + fun readInt16_boundsError_shortBuffer() { + val buf = ByteArray(1) { 0 } + val r = KompactRuntime.readInt16(buf, 4, 16) // needs 20 bits, only 8 available + assertFalse(r.isSuccess) + assertEquals(KompactDecodeError.BoundsError, r.error) + } + + @Test + fun readUInt16_success_unsignedValue() { + val buf = ByteArray(2) { 0 } + buf[0] = 0xFF.toByte() + buf[1] = 0xFF.toByte() + val r = KompactRuntime.readUInt16(buf, 0, 16) + assertTrue(r.isSuccess) + val v = r.getOrThrow() + assertEquals(65535, v.toInt() and 0xFFFF) + } + + // === readInt32 / readUInt32 === + + @Test + fun readInt32_success_positiveValue() { + val buf = ByteArray(4) { 0 } + KompactRuntime.writeBitsLong(buf, 0, 32, 1_000_000L) + val r = KompactRuntime.readInt32(buf, 0, 32) + assertTrue(r.isSuccess) + assertEquals(1_000_000, r.getOrThrow()) + } + + @Test + fun readInt32_success_negativeValue() { + val buf = ByteArray(4) { 0 } + KompactRuntime.writeBitsLong(buf, 0, 32, 0xFF80_0000L) + val r = KompactRuntime.readInt32(buf, 0, 32) + assertTrue(r.isSuccess) + assertEquals(-8_388_608, r.getOrThrow()) + } + + @Test + fun readInt32_success_maxInt() { + val buf = ByteArray(4) { 0 } + KompactRuntime.writeBitsLong(buf, 0, 32, Int.MAX_VALUE.toLong()) + val r = KompactRuntime.readInt32(buf, 0, 32) + assertTrue(r.isSuccess) + assertEquals(Int.MAX_VALUE, r.getOrThrow()) + } + + @Test + fun readInt32_success_minInt() { + val buf = ByteArray(4) { 0 } + KompactRuntime.writeBitsLong(buf, 0, 32, Int.MIN_VALUE.toLong()) + val r = KompactRuntime.readInt32(buf, 0, 32) + assertTrue(r.isSuccess) + assertEquals(Int.MIN_VALUE, r.getOrThrow()) + } + + @Test + fun readInt32_success_smallBitWidth() { + // 10-bit value 1008 + val buf = ByteArray(2) { 0 } + KompactRuntime.writeBits(buf, 0, 10, 1008) + val r = KompactRuntime.readInt32(buf, 0, 10) + assertTrue(r.isSuccess) + assertEquals(-16, r.getOrThrow()) + } + + @Test + fun readInt32_boundsError_shortBuffer() { + val buf = ByteArray(3) { 0 } + val r = KompactRuntime.readInt32(buf, 0, 32) // needs 32 bits, only 24 available + assertFalse(r.isSuccess) + assertEquals(KompactDecodeError.BoundsError, r.error) + } + + @Test + fun readUInt32_success_unsignedValue() { + val buf = ByteArray(4) { 0 } + KompactRuntime.writeBitsLong(buf, 0, 32, 0xFFFF_FFFFL) + val r = KompactRuntime.readUInt32(buf, 0, 32) + assertTrue(r.isSuccess) + assertEquals(-1, r.getOrThrow()) // 0xFFFFFFFF as signed Int = -1 + assertEquals(0xFFFF_FFFFL, r.getOrThrow().toLong() and 0xFFFF_FFFFL) + } + + // === readInt64 / readUInt64 === + + @Test + fun readInt64_success_maxValue() { + val buf = ByteArray(8) { 0 } + KompactRuntime.writeBitsLong(buf, 0, 64, Long.MAX_VALUE) + val r = KompactRuntime.readInt64(buf, 0, 64) + assertTrue(r.isSuccess) + assertEquals(Long.MAX_VALUE, r.getOrThrow()) + } + + @Test + fun readInt64_success_nearMinValue() { + // Long.MIN_VALUE (0x8000...) is in the failure sentinel range (documented tradeoff). + // 0x8400_0000_0000_0000 is the first representable success value below 0. + val value = Long.MIN_VALUE + (1L shl 58) + val buf = ByteArray(8) { 0 } + KompactRuntime.writeBitsLong(buf, 0, 64, value) + val r = KompactRuntime.readInt64(buf, 0, 64) + assertTrue(r.isSuccess) + assertEquals(value, r.getOrThrow()) + } + + @Test + fun readInt64_success_negativeValue() { + val buf = ByteArray(8) { 0 } + KompactRuntime.writeBitsLong(buf, 0, 64, -1L) + val r = KompactRuntime.readInt64(buf, 0, 64) + assertTrue(r.isSuccess) + assertEquals(-1L, r.getOrThrow()) + } + + @Test + fun readInt64_success_smallBitWidth_signExtended() { + // 4-bit value 0b1111 (15 unsigned) → -1 signed + val buf = byteArrayOf(0x0F.toByte()) + val r = KompactRuntime.readInt64(buf, 0, 4) + assertTrue(r.isSuccess) + assertEquals(-1L, r.getOrThrow()) + } + + @Test + fun readInt64_success_largePositive() { + // 0x4000_0000_0000_0000 is outside the LongResult failure sentinel range + val buf = ByteArray(8) { 0 } + KompactRuntime.writeBitsLong(buf, 0, 64, 0x4000_0000_0000_0000L) + val r = KompactRuntime.readInt64(buf, 0, 64) + assertTrue(r.isSuccess) + assertEquals(0x4000_0000_0000_0000L, r.getOrThrow()) + } + + @Test + fun readInt64_boundsError_shortBuffer() { + val buf = ByteArray(7) { 0 } + val r = KompactRuntime.readInt64(buf, 0, 64) // needs 64 bits, only 56 available + assertFalse(r.isSuccess) + assertEquals(KompactDecodeError.BoundsError, r.error) + } + + @Test + fun readUInt64_success_unsignedValue() { + val buf = ByteArray(8) { 0 } + KompactRuntime.writeBitsLong(buf, 0, 64, -1L) + val r = KompactRuntime.readUInt64(buf, 0, 64) + assertTrue(r.isSuccess) + assertEquals(-1L, r.getOrThrow()) + } + + @Test + fun readUInt64_success_smallBitWidth() { + val buf = ByteArray(1) { 0 } + KompactRuntime.writeBits(buf, 0, 3, 5) + val r = KompactRuntime.readUInt64(buf, 0, 3) + assertTrue(r.isSuccess) + assertEquals(5L, r.getOrThrow()) + } + + @Test + fun readUInt64_boundsError_shortBuffer() { + val r = KompactRuntime.readUInt64(ByteArray(4), 0, 64) + assertFalse(r.isSuccess) + assertEquals(KompactDecodeError.BoundsError, r.error) + } + + // === readFloat === + + @Test + fun readFloat_success_value() { + val buf = ByteArray(4) { 0 } + val value = 3.14f + KompactRuntime.writeBitsLong(buf, 0, 32, value.toBits().toLong()) + val r = KompactRuntime.readFloat(buf, 0) + assertTrue(r.isSuccess) + assertEquals(3.14f, r.getOrThrow(), 0.0001f) + } + + @Test + fun readFloat_success_zero() { + val buf = ByteArray(4) { 0 } + val r = KompactRuntime.readFloat(buf, 0) + assertTrue(r.isSuccess) + assertEquals(0.0f, r.getOrThrow()) + } + + @Test + fun readFloat_success_nan() { + val buf = ByteArray(4) { 0 } + KompactRuntime.writeBitsLong(buf, 0, 32, Float.NaN.toBits().toLong()) + val r = KompactRuntime.readFloat(buf, 0) + assertTrue(r.isSuccess) + assertTrue(r.getOrThrow().isNaN()) + } + + @Test + fun readFloat_success_infinity() { + val buf = ByteArray(4) { 0 } + KompactRuntime.writeBitsLong(buf, 0, 32, Float.POSITIVE_INFINITY.toBits().toLong()) + val r = KompactRuntime.readFloat(buf, 0) + assertTrue(r.isSuccess) + assertEquals(Float.POSITIVE_INFINITY, r.getOrThrow()) + } + + @Test + fun readFloat_boundsError_shortBuffer() { + val buf = ByteArray(3) { 0 } + val r = KompactRuntime.readFloat(buf, 0) // needs 32 bits, only 24 available + assertFalse(r.isSuccess) + assertEquals(KompactDecodeError.BoundsError, r.error) + } + + // === readDouble === + + @Test + fun readDouble_success_value() { + val buf = ByteArray(8) { 0 } + KompactRuntime.writeBitsLong(buf, 0, 64, 3.14.toBits()) + val r = KompactRuntime.readDouble(buf, 0) + assertTrue(r.isSuccess) + assertEquals(3.14, r.getOrThrow(), 0.0001) + } + + @Test + fun readDouble_success_zero() { + val buf = ByteArray(8) { 0 } + val r = KompactRuntime.readDouble(buf, 0) + assertTrue(r.isSuccess) + assertEquals(0.0, r.getOrThrow()) + } + + @Test + fun readDouble_success_nan() { + val buf = ByteArray(8) { 0 } + KompactRuntime.writeBitsLong(buf, 0, 64, Double.NaN.toBits()) + val r = KompactRuntime.readDouble(buf, 0) + assertTrue(r.isSuccess) + assertTrue(r.getOrThrow().isNaN()) + } + + @Test + fun readDouble_success_negativeInfinity() { + val buf = ByteArray(8) { 0 } + KompactRuntime.writeBitsLong(buf, 0, 64, Double.NEGATIVE_INFINITY.toBits()) + val r = KompactRuntime.readDouble(buf, 0) + assertTrue(r.isSuccess) + assertEquals(Double.NEGATIVE_INFINITY, r.getOrThrow()) + } + + @Test + fun readDouble_success_positiveInfinity() { + val buf = ByteArray(8) { 0 } + KompactRuntime.writeBitsLong(buf, 0, 64, Double.POSITIVE_INFINITY.toBits()) + val r = KompactRuntime.readDouble(buf, 0) + assertTrue(r.isSuccess) + assertEquals(Double.POSITIVE_INFINITY, r.getOrThrow()) + } + + @Test + fun readDouble_boundsError_shortBuffer() { + val buf = ByteArray(7) { 0 } + val r = KompactRuntime.readDouble(buf, 0) // needs 64 bits, only 56 available + assertFalse(r.isSuccess) + assertEquals(KompactDecodeError.BoundsError, r.error) + } +} diff --git a/kompact/src/commonTest/kotlin/ch/trancee/kompact/runtime/KompactRuntimeLongBitsTest.kt b/kompact/src/commonTest/kotlin/ch/trancee/kompact/runtime/KompactRuntimeLongBitsTest.kt new file mode 100644 index 0000000..f81bfb3 --- /dev/null +++ b/kompact/src/commonTest/kotlin/ch/trancee/kompact/runtime/KompactRuntimeLongBitsTest.kt @@ -0,0 +1,112 @@ +package ch.trancee.kompact.runtime + +import kotlin.test.Test +import kotlin.test.assertEquals + +class KompactRuntimeLongBitsTest { + + // Ticket 01: LSB-first — same bit order as readBits, but Long-backed (1..64). + + @Test + fun readBitsLong_singleByte() { + assertEquals(0x7FL, KompactRuntime.readBitsLong(byteArrayOf(0x7F), 0, 8)) + } + + @Test + fun readBitsLong_masksSignedBytesLsbFirst() { + // 0xFF (signed -1) must be masked before shl/or — identical on JVM/Native. + val buf = byteArrayOf(0xFF.toByte(), 0xFF.toByte()) + assertEquals(0xFFFFL, KompactRuntime.readBitsLong(buf, 0, 16)) + } + + @Test + fun readBitsLong_crossByteBoundary() { + val buf = byteArrayOf(0xD2.toByte(), 0x3D.toByte()) + assertEquals(221L, KompactRuntime.readBitsLong(buf, 4, 8)) + } + + @Test + fun readBitsLong_assemblesUpTo64Bits_allOnes() { + val buf = ByteArray(8) { 0xFF.toByte() } + assertEquals(-1L, KompactRuntime.readBitsLong(buf, 0, 64)) + } + + @Test + fun readBitsLong_assembles63Bits() { + val buf = ByteArray(8) { 0xFF.toByte() } + // bit 63 = 0 → 63-bit all-ones + buf[7] = 0x7F.toByte() + assertEquals(0x7FFF_FFFF_FFFF_FFFFL, KompactRuntime.readBitsLong(buf, 0, 63)) + } + + @Test + fun readBitsLong_singleBit_atByteBoundary() { + val buf = byteArrayOf(0x00, 0x01, 0x00) + assertEquals(1L, KompactRuntime.readBitsLong(buf, 8, 1)) + } + + @Test + fun readBitsLong_subByteWidth_returnsUnsignedMagnitude() { + val buf = byteArrayOf(0b1111_1111.toByte()) + // 4-bit read → 15, not -1 + assertEquals(15L, KompactRuntime.readBitsLong(buf, 0, 4)) + } + + // --- writeBitsLong round-trip --- + + @Test + fun writeBitsLong_thenReadLong_singleByte() { + val buf = ByteArray(1) { 0 } + KompactRuntime.writeBitsLong(buf, 0, 4, 0b1010L) + assertEquals(0b1010L, KompactRuntime.readBitsLong(buf, 0, 4)) + assertEquals(0L, KompactRuntime.readBitsLong(buf, 4, 4)) // high nibble untouched + } + + @Test + fun writeBitsLong_thenReadLong_crossByte() { + val buf = ByteArray(2) { 0 } + KompactRuntime.writeBitsLong(buf, 4, 10, 438L) + assertEquals(438L, KompactRuntime.readBitsLong(buf, 4, 10)) + } + + @Test + fun writeBitsLong_thenReadLong_64BitAllOnes() { + val buf = ByteArray(8) { 0 } + KompactRuntime.writeBitsLong(buf, 0, 64, -1L) + assertEquals(-1L, KompactRuntime.readBitsLong(buf, 0, 64)) + } + + @Test + fun writeBitsLong_thenReadLong_largePositive() { + val buf = ByteArray(8) { 0 } + val value = 0x4000_0000_0000_0000L // bit 62 set, outside LongResult sentinel range + KompactRuntime.writeBitsLong(buf, 0, 64, value) + assertEquals(value, KompactRuntime.readBitsLong(buf, 0, 64)) + } + + @Test + fun writeBitsLong_thenReadLong_32BitWriteDoesNotAffectUpperBits() { + val buf = ByteArray(8) { 0 } + buf[7] = 0x80.toByte() // pre-set bit 63 + val v32: Long = 0xBEEFCAFEL + KompactRuntime.writeBitsLong(buf, 0, 32, v32) + assertEquals(v32, KompactRuntime.readBitsLong(buf, 0, 32)) + // Upper 32 bits untouched — byte 7 still has 0x80 + assertEquals(0x8000_0000L, KompactRuntime.readBitsLong(buf, 32, 32)) + + } + + // --- writeBitsLong only touches target range --- + + @Test + fun writeBitsLong_doesNotClobberOtherBits() { + val original = byteArrayOf(0xF0.toByte(), 0x0F.toByte()) + val buf = original.copyOf() + KompactRuntime.writeBitsLong(buf, 0, 4, 0b0101L) + // Low nibble of byte 0 changed, high nibble untouched + assertEquals(0b0101L, KompactRuntime.readBitsLong(buf, 0, 4)) + assertEquals(0b1111L, KompactRuntime.readBitsLong(buf, 4, 4)) + // Byte 1 untouched + assertEquals(0x0FL, KompactRuntime.readBitsLong(buf, 8, 8)) + } +} diff --git a/kompact/src/commonTest/kotlin/ch/trancee/kompact/runtime/KompactRuntimePropertyTest.kt b/kompact/src/commonTest/kotlin/ch/trancee/kompact/runtime/KompactRuntimePropertyTest.kt index a8d9ae1..d9e73cd 100644 --- a/kompact/src/commonTest/kotlin/ch/trancee/kompact/runtime/KompactRuntimePropertyTest.kt +++ b/kompact/src/commonTest/kotlin/ch/trancee/kompact/runtime/KompactRuntimePropertyTest.kt @@ -64,4 +64,68 @@ class KompactRuntimePropertyTest { val signed = if (magnitude >= (1 shl 3)) magnitude - (1 shl 4) else magnitude assertEquals(-1, signed) } + + // --- Ticket 04: readBitsLong/writeBitsLong round-trip & byte-identity --- + + private fun maskForWidth(bitWidth: Int): Long = + if (bitWidth >= 64) -1L else (1L shl bitWidth) - 1L + + @Test + fun writeBitsLong_thenReadLong_roundTrip_random() { + val rng = Random(0x5EEDA) + repeat(1000) { + val buf = ByteArray(8) { (rng.nextInt() and 0xFF).toByte() } + val bitOffset = rng.nextInt(0, 64) + val bitWidth = rng.nextInt(1, 65) // 1..64 + if (bitOffset + bitWidth > 64) return@repeat + val value = rng.nextLong() and maskForWidth(bitWidth) + + KompactRuntime.writeBitsLong(buf, bitOffset, bitWidth, value) + val result = KompactRuntime.readBitsLong(buf, bitOffset, bitWidth) + + assertEquals(value, result, "offset=$bitOffset width=$bitWidth value=$value") + } + } + + @Test + fun writeBitsLong_onlyTouchesTargetRange_random() { + val rng = Random(0xBEBA) + repeat(1000) { + val original = ByteArray(8) { (rng.nextInt() and 0xFF).toByte() } + val buf = original.copyOf() + val bitOffset = rng.nextInt(0, 64) + val bitWidth = rng.nextInt(1, 65) + if (bitOffset + bitWidth > 64) return@repeat + val value = rng.nextLong() and maskForWidth(bitWidth) + + KompactRuntime.writeBitsLong(buf, bitOffset, bitWidth, value) + + for (b in 0 until 64) { + if (b < bitOffset || b >= bitOffset + bitWidth) { + assertEquals( + KompactRuntime.readBitsBoolean(original, b), + KompactRuntime.readBitsBoolean(buf, b), + "bit $b changed outside [$bitOffset, ${bitOffset + bitWidth})" + ) + } + } + } + } + + @Test + fun readBitsLong_matchesReadBits_forSmallWidths() { + // Byte-identity: readBitsLong and readBits must agree for widths 1..31. + val rng = Random(0xCAFE) + repeat(1000) { + val buf = ByteArray(8) { (rng.nextInt() and 0xFF).toByte() } + val bitOffset = rng.nextInt(0, 32) + val bitWidth = rng.nextInt(1, 32) // 1..31 + if (bitOffset + bitWidth > 64) return@repeat + + val intResult = KompactRuntime.readBits(buf, bitOffset, bitWidth) + val longResult = KompactRuntime.readBitsLong(buf, bitOffset, bitWidth) + + assertEquals(intResult.toLong(), longResult, "offset=$bitOffset width=$bitWidth") + } + } } diff --git a/kompact/src/iosMain/kotlin/ch/trancee/kompact/runtime/KompactResult.kt b/kompact/src/iosMain/kotlin/ch/trancee/kompact/runtime/KompactResult.kt new file mode 100644 index 0000000..f2cfb21 --- /dev/null +++ b/kompact/src/iosMain/kotlin/ch/trancee/kompact/runtime/KompactResult.kt @@ -0,0 +1,120 @@ +package ch.trancee.kompact.runtime + +// Ticket 08 — iOS actuals: plain value class (Kotlin/Native, no @JvmInline). +// Same encoding logic as JVM; value classes over primitive Long are +// zero-alloc on Kotlin/Native (inline value). + +public actual value class ByteResult(public actual val packed: Long) { + public actual val isSuccess: Boolean get() = (packed and RESULT_OK_FLAG) != 0L + public actual val isFailure: Boolean get() = (packed and RESULT_OK_FLAG) == 0L + public actual val error: KompactDecodeError? get() = + if (isSuccess) null else decodeErrorFromSmallBits(packed) + public actual fun getOrThrow(): Byte = + if (isSuccess) (packed and RESULT_VALUE_MASK).toByte() + else throw KompactDecodeException(decodeErrorFromSmallBits(packed)) + public actual companion object { + public actual fun success(value: Byte): ByteResult = + ByteResult(encodeSmallSuccess(value.toLong())) + public actual fun failure(error: KompactDecodeError): ByteResult = + ByteResult(encodeSmallFailure(error)) + } +} + +public actual value class ShortResult(public actual val packed: Long) { + public actual val isSuccess: Boolean get() = (packed and RESULT_OK_FLAG) != 0L + public actual val isFailure: Boolean get() = (packed and RESULT_OK_FLAG) == 0L + public actual val error: KompactDecodeError? get() = + if (isSuccess) null else decodeErrorFromSmallBits(packed) + public actual fun getOrThrow(): Short = + if (isSuccess) (packed and RESULT_VALUE_MASK).toShort() + else throw KompactDecodeException(decodeErrorFromSmallBits(packed)) + public actual companion object { + public actual fun success(value: Short): ShortResult = + ShortResult(encodeSmallSuccess(value.toLong())) + public actual fun failure(error: KompactDecodeError): ShortResult = + ShortResult(encodeSmallFailure(error)) + } +} + +public actual value class IntResult(public actual val packed: Long) { + public actual val isSuccess: Boolean get() = (packed and RESULT_OK_FLAG) != 0L + public actual val isFailure: Boolean get() = (packed and RESULT_OK_FLAG) == 0L + public actual val error: KompactDecodeError? get() = + if (isSuccess) null else decodeErrorFromSmallBits(packed) + public actual fun getOrThrow(): Int = + if (isSuccess) (packed and RESULT_VALUE_MASK).toInt() + else throw KompactDecodeException(decodeErrorFromSmallBits(packed)) + public actual companion object { + public actual fun success(value: Int): IntResult = + IntResult(encodeSmallSuccess(value.toLong())) + public actual fun failure(error: KompactDecodeError): IntResult = + IntResult(encodeSmallFailure(error)) + } +} + +public actual value class FloatResult(public actual val packed: Long) { + public actual val isSuccess: Boolean get() = (packed and RESULT_OK_FLAG) != 0L + public actual val isFailure: Boolean get() = (packed and RESULT_OK_FLAG) == 0L + public actual val error: KompactDecodeError? get() = + if (isSuccess) null else decodeErrorFromSmallBits(packed) + public actual fun getOrThrow(): Float = + if (isSuccess) Float.fromBits((packed and RESULT_VALUE_MASK).toInt()) + else throw KompactDecodeException(decodeErrorFromSmallBits(packed)) + public actual companion object { + public actual fun success(value: Float): FloatResult = + FloatResult(encodeSmallSuccess(value.toBits().toLong())) + public actual fun failure(error: KompactDecodeError): FloatResult = + FloatResult(encodeSmallFailure(error)) + } +} + +public actual value class BooleanResult(public actual val packed: Long) { + public actual val isSuccess: Boolean get() = (packed and RESULT_OK_FLAG) != 0L + public actual val isFailure: Boolean get() = (packed and RESULT_OK_FLAG) == 0L + public actual val error: KompactDecodeError? get() = + if (isSuccess) null else decodeErrorFromSmallBits(packed) + public actual fun getOrThrow(): Boolean = + if (isSuccess) (packed and RESULT_VALUE_MASK) != 0L + else throw KompactDecodeException(decodeErrorFromSmallBits(packed)) + public actual companion object { + public actual fun success(value: Boolean): BooleanResult = + BooleanResult(encodeSmallSuccess(if (value) 1L else 0L)) + public actual fun failure(error: KompactDecodeError): BooleanResult = + BooleanResult(encodeSmallFailure(error)) + } +} + +// LongResult — sentinel-based encoding (bits 63 set + 62..58 clear) + +public actual value class LongResult(public actual val packed: Long) { + public actual val isSuccess: Boolean get() = !isLongFailure(packed) + public actual val isFailure: Boolean get() = isLongFailure(packed) + public actual val error: KompactDecodeError? get() = + if (isSuccess) null else decodeLongError(packed) + public actual fun getOrThrow(): Long = + if (isSuccess) packed + else throw KompactDecodeException(decodeLongError(packed)) + public actual companion object { + public actual fun success(value: Long): LongResult = LongResult(value) + public actual fun failure(error: KompactDecodeError): LongResult = + LongResult(encodeLongFailure(error)) + } +} + +// DoubleResult — canonical-NaN success, reserved NaN payload for errors + +public actual value class DoubleResult(public actual val packed: Long) { + public actual val isSuccess: Boolean get() = !isDoubleFailure(packed) + public actual val isFailure: Boolean get() = isDoubleFailure(packed) + public actual val error: KompactDecodeError? get() = + if (isSuccess) null else decodeDoubleError(packed) + public actual fun getOrThrow(): Double = + if (isSuccess) Double.fromBits(packed) + else throw KompactDecodeException(decodeDoubleError(packed)) + public actual companion object { + public actual fun success(value: Double): DoubleResult = + DoubleResult(encodeDoubleSuccess(value)) + public actual fun failure(error: KompactDecodeError): DoubleResult = + DoubleResult(encodeDoubleFailure(error)) + } +} diff --git a/kompact/src/jvmMain/kotlin/ch/trancee/kompact/runtime/KompactResult.kt b/kompact/src/jvmMain/kotlin/ch/trancee/kompact/runtime/KompactResult.kt new file mode 100644 index 0000000..32e90e4 --- /dev/null +++ b/kompact/src/jvmMain/kotlin/ch/trancee/kompact/runtime/KompactResult.kt @@ -0,0 +1,122 @@ +package ch.trancee.kompact.runtime + +import kotlin.jvm.JvmInline + +// Ticket 08 — JVM actuals: @JvmInline over primitive Long → zero-alloc on +// both success and failure (KT-61573 silenced in build.gradle.kts). + +@JvmInline +public actual value class ByteResult(public actual val packed: Long) { + public actual val isSuccess: Boolean get() = (packed and RESULT_OK_FLAG) != 0L + public actual val isFailure: Boolean get() = (packed and RESULT_OK_FLAG) == 0L + public actual val error: KompactDecodeError? get() = + if (isSuccess) null else decodeErrorFromSmallBits(packed) + public actual fun getOrThrow(): Byte = + if (isSuccess) (packed and RESULT_VALUE_MASK).toByte() + else throw KompactDecodeException(decodeErrorFromSmallBits(packed)) + public actual companion object { + public actual fun success(value: Byte): ByteResult = + ByteResult(encodeSmallSuccess(value.toLong())) + public actual fun failure(error: KompactDecodeError): ByteResult = + ByteResult(encodeSmallFailure(error)) + } +} + +@JvmInline +public actual value class ShortResult(public actual val packed: Long) { + public actual val isSuccess: Boolean get() = (packed and RESULT_OK_FLAG) != 0L + public actual val isFailure: Boolean get() = (packed and RESULT_OK_FLAG) == 0L + public actual val error: KompactDecodeError? get() = + if (isSuccess) null else decodeErrorFromSmallBits(packed) + public actual fun getOrThrow(): Short = + if (isSuccess) (packed and RESULT_VALUE_MASK).toShort() + else throw KompactDecodeException(decodeErrorFromSmallBits(packed)) + public actual companion object { + public actual fun success(value: Short): ShortResult = + ShortResult(encodeSmallSuccess(value.toLong())) + public actual fun failure(error: KompactDecodeError): ShortResult = + ShortResult(encodeSmallFailure(error)) + } +} + +@JvmInline +public actual value class IntResult(public actual val packed: Long) { + public actual val isSuccess: Boolean get() = (packed and RESULT_OK_FLAG) != 0L + public actual val isFailure: Boolean get() = (packed and RESULT_OK_FLAG) == 0L + public actual val error: KompactDecodeError? get() = + if (isSuccess) null else decodeErrorFromSmallBits(packed) + public actual fun getOrThrow(): Int = + if (isSuccess) (packed and RESULT_VALUE_MASK).toInt() + else throw KompactDecodeException(decodeErrorFromSmallBits(packed)) + public actual companion object { + public actual fun success(value: Int): IntResult = + IntResult(encodeSmallSuccess(value.toLong())) + public actual fun failure(error: KompactDecodeError): IntResult = + IntResult(encodeSmallFailure(error)) + } +} + +@JvmInline +public actual value class FloatResult(public actual val packed: Long) { + public actual val isSuccess: Boolean get() = (packed and RESULT_OK_FLAG) != 0L + public actual val isFailure: Boolean get() = (packed and RESULT_OK_FLAG) == 0L + public actual val error: KompactDecodeError? get() = + if (isSuccess) null else decodeErrorFromSmallBits(packed) + public actual fun getOrThrow(): Float = + if (isSuccess) Float.fromBits((packed and RESULT_VALUE_MASK).toInt()) + else throw KompactDecodeException(decodeErrorFromSmallBits(packed)) + public actual companion object { + public actual fun success(value: Float): FloatResult = + FloatResult(encodeSmallSuccess(value.toBits().toLong())) + public actual fun failure(error: KompactDecodeError): FloatResult = + FloatResult(encodeSmallFailure(error)) + } +} + +@JvmInline +public actual value class BooleanResult(public actual val packed: Long) { + public actual val isSuccess: Boolean get() = (packed and RESULT_OK_FLAG) != 0L + public actual val isFailure: Boolean get() = (packed and RESULT_OK_FLAG) == 0L + public actual val error: KompactDecodeError? get() = + if (isSuccess) null else decodeErrorFromSmallBits(packed) + public actual fun getOrThrow(): Boolean = + if (isSuccess) (packed and RESULT_VALUE_MASK) != 0L + else throw KompactDecodeException(decodeErrorFromSmallBits(packed)) + public actual companion object { + public actual fun success(value: Boolean): BooleanResult = + BooleanResult(encodeSmallSuccess(if (value) 1L else 0L)) + public actual fun failure(error: KompactDecodeError): BooleanResult = + BooleanResult(encodeSmallFailure(error)) + } +} +@JvmInline +public actual value class LongResult(public actual val packed: Long) { + public actual val isSuccess: Boolean get() = !isLongFailure(packed) + public actual val isFailure: Boolean get() = isLongFailure(packed) + public actual val error: KompactDecodeError? get() = + if (isSuccess) null else decodeLongError(packed) + public actual fun getOrThrow(): Long = + if (isSuccess) packed + else throw KompactDecodeException(decodeLongError(packed)) + public actual companion object { + public actual fun success(value: Long): LongResult = LongResult(value) + public actual fun failure(error: KompactDecodeError): LongResult = + LongResult(encodeLongFailure(error)) + } +} +@JvmInline +public actual value class DoubleResult(public actual val packed: Long) { + public actual val isSuccess: Boolean get() = !isDoubleFailure(packed) + public actual val isFailure: Boolean get() = isDoubleFailure(packed) + public actual val error: KompactDecodeError? get() = + if (isSuccess) null else decodeDoubleError(packed) + public actual fun getOrThrow(): Double = + if (isSuccess) Double.fromBits(packed) + else throw KompactDecodeException(decodeDoubleError(packed)) + public actual companion object { + public actual fun success(value: Double): DoubleResult = + DoubleResult(encodeDoubleSuccess(value)) + public actual fun failure(error: KompactDecodeError): DoubleResult = + DoubleResult(encodeDoubleFailure(error)) + } +} From 9376278893c64eec5f4e4e9c97f98586dd927843 Mon Sep 17 00:00:00 2001 From: Philipp Grosswiler Date: Fri, 4 Sep 2026 21:14:47 +0200 Subject: [PATCH 16/51] feat(kompact): serialization framework foundation slice MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Foundation for the Kompact bit-stream serialization framework (Tickets 04/05/06/07). Runtime primitives (commonMain): - Inline hot-path primitives retained; `fits` promoted to `public inline` so public-inline checked accessors can call it (Kotlin inline visibility rule). - FloatResult NaN canonicalization via `encodeFloatSuccess` (FLOAT_NAN_CANONICAL_BITS = 0x7FC00000), wired into JVM + iOS FloatResult.success — mirrors DoubleResult. Cross-platform regression guard: floatResult_success_canonicalizesAnyNanToSinglePayload. - LongResult sentinel band (Long.MIN_VALUE .. Long.MIN_VALUE+2^58-1) left as spec-documented tradeoff (Ticket 08); KDoc documents the reserved band. Variable-length framing (commonMain/KompactFraming.kt): - readLengthPrefix/writeLengthPrefix (8/16/32-bit LE byte count), VALID_PREFIX_WIDTHS. - nestedRegionOrNull: parse-forward sub-region, returns null on truncation (never throws on read path — Ticket 06); read-side bounds-check discipline. - readCountPrefix for count-prefixed repeats. Write builder (commonMain/KompactWriter.kt, Ticket 07): - Growable forward-only buffer; writeInt/UInt/Bool/Enum/String/Blob, writeNested (child length computed first, prefix + bytes, no backpatch), writeRepeated (count prefix + N elements), 64-bit via writeBitsLong. - build(): ByteArray, exact-length snapshot. @KompactField v1 surface (commonMain/KompactAnnotations.kt): - lengthPrefixWidth, isNested, repeatCountWidth, enumWidth, defaultValue, isVersionField — all defaulted for backward-compatible scalar usage. Tests (commonTest): KompactFramingTest (11), KompactWriterTest (10), KompactResultCanonicalizationTest (7), KompactFieldV1SurfaceTest (1). Build gate: - Pin JVM target to 21 LTS in kompact/build.gradle.kts so BCV 0.18.0 (ASM 9.8) can parse emitted class files on hosts running JDK 25 (Kotlin 2.4.10 emits v69). Verification (linux host): - :kompact:jvmTest — 165 tests, 0 failures (incl. 11+10+7+1 new suite tests). - :kompact:jvmApiCheck — green; kompact.api regenerated. - :kompact:apiCheck — BUILD SUCCESSFUL (iOS klib ABI inference host-disabled on linux; kompact.klib.api regeneration blocked on macOS toolchain, per repo constraint that iosSimulatorArm64Test is host-disabled). No new external deps; pure Kotlin common -> JVM. iOS klib tests/dumps cannot run on linux (iosSimulatorArm64Test host-disabled). --- kompact/api/kompact.api | 40 ++++ kompact/build.gradle.kts | 9 +- .../kompact/runtime/KompactAnnotations.kt | 26 ++- .../trancee/kompact/runtime/KompactFraming.kt | 89 +++++++++ .../trancee/kompact/runtime/KompactResult.kt | 21 +++ .../trancee/kompact/runtime/KompactRuntime.kt | 46 ++--- .../trancee/kompact/runtime/KompactWriter.kt | 171 ++++++++++++++++++ .../runtime/KompactFieldV1SurfaceTest.kt | 40 ++++ .../kompact/runtime/KompactFramingTest.kt | 115 ++++++++++++ .../KompactResultCanonicalizationTest.kt | 102 +++++++++++ .../kompact/runtime/KompactWriterTest.kt | 135 ++++++++++++++ .../trancee/kompact/runtime/KompactResult.kt | 2 +- .../trancee/kompact/runtime/KompactResult.kt | 6 +- 13 files changed, 773 insertions(+), 29 deletions(-) create mode 100644 kompact/src/commonMain/kotlin/ch/trancee/kompact/runtime/KompactFraming.kt create mode 100644 kompact/src/commonMain/kotlin/ch/trancee/kompact/runtime/KompactWriter.kt create mode 100644 kompact/src/commonTest/kotlin/ch/trancee/kompact/runtime/KompactFieldV1SurfaceTest.kt create mode 100644 kompact/src/commonTest/kotlin/ch/trancee/kompact/runtime/KompactFramingTest.kt create mode 100644 kompact/src/commonTest/kotlin/ch/trancee/kompact/runtime/KompactResultCanonicalizationTest.kt create mode 100644 kompact/src/commonTest/kotlin/ch/trancee/kompact/runtime/KompactWriterTest.kt diff --git a/kompact/api/kompact.api b/kompact/api/kompact.api index 64a3dda..d4c125b 100644 --- a/kompact/api/kompact.api +++ b/kompact/api/kompact.api @@ -169,6 +169,21 @@ public final class ch/trancee/kompact/runtime/KompactDecodeException : java/lang public abstract interface annotation class ch/trancee/kompact/runtime/KompactField : java/lang/annotation/Annotation { public abstract fun bitOffset ()I public abstract fun bitWidth ()I + public abstract fun defaultValue ()Ljava/lang/String; + public abstract fun enumWidth ()I + public abstract fun isNested ()Z + public abstract fun isVersionField ()Z + public abstract fun lengthPrefixWidth ()I + public abstract fun repeatCountWidth ()I +} + +public final class ch/trancee/kompact/runtime/KompactFraming { + public static final field INSTANCE Lch/trancee/kompact/runtime/KompactFraming; + public final fun getVALID_PREFIX_WIDTHS ()Ljava/util/Set; + public final fun nestedRegionOrNull ([BII)Lkotlin/Pair; + public final fun readCountPrefix ([BII)I + public final fun readLengthPrefix ([BII)I + public final fun writeLengthPrefix ([BIII)V } public abstract interface annotation class ch/trancee/kompact/runtime/KompactModel : java/lang/annotation/Annotation { @@ -176,6 +191,7 @@ public abstract interface annotation class ch/trancee/kompact/runtime/KompactMod public final class ch/trancee/kompact/runtime/KompactRuntime { public static final field INSTANCE Lch/trancee/kompact/runtime/KompactRuntime; + public final fun fits ([BII)Z public final fun readBits ([BII)I public final fun readBitsBoolean ([BI)Z public final fun readBitsLong ([BII)J @@ -195,6 +211,30 @@ public final class ch/trancee/kompact/runtime/KompactRuntime { public final fun writeBitsLong ([BIIJ)V } +public final class ch/trancee/kompact/runtime/KompactWriter { + public static final field Companion Lch/trancee/kompact/runtime/KompactWriter$Companion; + public fun ()V + public final fun build ()[B + public final fun getBitCursor ()I + public final fun writeBits (II)V + public final fun writeBitsLong (IJ)V + public final fun writeBlob (I[B)V + public final fun writeBool (Z)V + public final fun writeEnum (II)V + public final fun writeInt (II)V + public final fun writeInt64 (IJ)V + public final fun writeNested (ILkotlin/jvm/functions/Function1;)V + public static synthetic fun writeNested$default (Lch/trancee/kompact/runtime/KompactWriter;ILkotlin/jvm/functions/Function1;ILjava/lang/Object;)V + public final fun writeRepeated (IILkotlin/jvm/functions/Function1;)V + public static synthetic fun writeRepeated$default (Lch/trancee/kompact/runtime/KompactWriter;IILkotlin/jvm/functions/Function1;ILjava/lang/Object;)V + public final fun writeString (ILjava/lang/String;)V + public final fun writeUInt (II)V + public final fun writeUInt (IJ)V +} + +public final class ch/trancee/kompact/runtime/KompactWriter$Companion { +} + public final class ch/trancee/kompact/runtime/LongResult { public static final field Companion Lch/trancee/kompact/runtime/LongResult$Companion; public static final synthetic fun box-impl (J)Lch/trancee/kompact/runtime/LongResult; diff --git a/kompact/build.gradle.kts b/kompact/build.gradle.kts index c672037..7967439 100644 --- a/kompact/build.gradle.kts +++ b/kompact/build.gradle.kts @@ -5,7 +5,13 @@ plugins { } kotlin { - jvm() + // Ticket 13: pin JVM target to 21 LTS so BCV (ASM 9.8 / v0.18.0) can parse the + // emitted class files on hosts running JDK 25 (Kotlin 2.4.10 otherwise emits v69). + jvm { + compilerOptions { + jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_21) + } + } iosArm64() iosSimulatorArm64() @@ -33,7 +39,6 @@ kotlin { iosSimulatorArm64Main { dependsOn(iosMain) } } } - // Ticket 13: BCV 0.18.0 — lock the public ABI for common + each Kotlin/Native target. apiValidation { klib { diff --git a/kompact/src/commonMain/kotlin/ch/trancee/kompact/runtime/KompactAnnotations.kt b/kompact/src/commonMain/kotlin/ch/trancee/kompact/runtime/KompactAnnotations.kt index e0dd086..2e4123c 100644 --- a/kompact/src/commonMain/kotlin/ch/trancee/kompact/runtime/KompactAnnotations.kt +++ b/kompact/src/commonMain/kotlin/ch/trancee/kompact/runtime/KompactAnnotations.kt @@ -16,15 +16,35 @@ public annotation class KompactModel * Documents a property's bit position and width in the packed `ByteArray`. * * The Kompact KSP processor reads these to generate the backing read/write - * logic. Offsets are LSB-first (Ticket 01) and must be densely packed with no - * gaps or overlaps (Ticket 06: the processor enforces this at compile time). + * logic (Tickets 04, 05, 06, 09). Offsets are LSB-first (Ticket 01) and must be + * densely packed with no gaps or overlaps (Ticket 06: the processor enforces this). + * + * The length-prefix / nesting / repeat / enum / version members are v1 schema + * metadata consumed by codegen; they carry safe defaults so a plain + * `@KompactField(bitOffset, bitWidth)` scalar declaration remains valid. * * @param bitOffset zero-based LSB-first start bit of the field - * @param bitWidth number of bits occupied by the field (1..31) + * @param bitWidth number of bits occupied by the field (1..64; for 32-bit use 32) + * @param lengthPrefixWidth fixed-width LE byte-count prefix width in {8,16,32} + * used when the field is a string/blob/nested/repeat (Ticket 05) + * @param isNested true when the field is a length-delimited composite region + * @param repeatCountWidth fixed-width LE count prefix width in {8,16,32} + * for repeated fields + * @param enumWidth bit width of an enum/ordinal (0 = not an enum) + * @param defaultValue string-encoded default used by the generated ctor/accessor + * when the backing region is absent or zero-filled (Ticket 04) + * @param isVersionField true for the schema-evolution version-tag field (Ticket 09) */ @Target(AnnotationTarget.PROPERTY) @Retention(AnnotationRetention.SOURCE) public annotation class KompactField( public val bitOffset: Int, public val bitWidth: Int, + public val lengthPrefixWidth: Int = 8, + public val isNested: Boolean = false, + public val repeatCountWidth: Int = 8, + public val enumWidth: Int = 0, + public val defaultValue: String = "", + public val isVersionField: Boolean = false, ) + diff --git a/kompact/src/commonMain/kotlin/ch/trancee/kompact/runtime/KompactFraming.kt b/kompact/src/commonMain/kotlin/ch/trancee/kompact/runtime/KompactFraming.kt new file mode 100644 index 0000000..2a64ec2 --- /dev/null +++ b/kompact/src/commonMain/kotlin/ch/trancee/kompact/runtime/KompactFraming.kt @@ -0,0 +1,89 @@ +package ch.trancee.kompact.runtime + +/** + * Sequential, length-delimited framing (Ticket 05) and repeat/count handling. + * + * Wire shape, read forward (no random access): + * - **Length prefix** — a fixed-width (8/16/32-bit) little-endian byte count + * placed at [bitOffset]; the prefixed payload follows immediately at + * `bitOffset + prefixBitWidth`. + * - **Nested composite** — a length-delimited sub-region: read the prefix to + * learn the byte count, then consume `prefixBitWidth + count * 8` bits and + * hand the caller the sub-region's `[startBit, bitLength)`. + * - **Repeated fields** — one fixed-width count prefix, then `count` elements + * in sequence (the count width is the field's declared prefix width). + * + * Reads never throw on the hot path (Ticket 06): a prefix that overruns the + * buffer is surfaced via [nestedRegionOrNull]'s nullable return so the caller + * can map it to a typed `TruncatedNested`/`BadLengthPrefix` result (Ticket 09: + * skew is fail-fast, never silent). + */ +public object KompactFraming { + + /** Valid length-prefix bit widths (Ticket 06 invariant matrix). */ + public val VALID_PREFIX_WIDTHS: Set = setOf(8, 16, 32) + + /** + * Reads a fixed-width (8/16/32-bit) little-endian byte count at [bitOffset]. + * Unsigned magnitude via the raw bit primitives; returns -1 when + * [bitWidth] is invalid or the region overruns [raw] (caller maps to a + * typed error — never throws on the read path, Ticket 06). + */ + public inline fun readLengthPrefix(raw: ByteArray, bitOffset: Int, bitWidth: Int): Int { + if (bitWidth !in VALID_PREFIX_WIDTHS || !KompactRuntime.fits(raw, bitOffset, bitWidth)) { + return -1 + } + return when (bitWidth) { + 8 -> KompactRuntime.readBits(raw, bitOffset, 8) + 16 -> KompactRuntime.readBits(raw, bitOffset, 16) + else -> KompactRuntime.readBitsLong(raw, bitOffset, 32).toInt() + } + } + + /** + * Writes [length] as a fixed-width little-endian byte count at [bitOffset]. + * Mirrors [readLengthPrefix] (Ticket 07: the writer selects the per-field + * prefix width at codegen time; it must be one of [VALID_PREFIX_WIDTHS]). + */ + public inline fun writeLengthPrefix(raw: ByteArray, bitOffset: Int, bitWidth: Int, length: Int) { + if (bitWidth !in VALID_PREFIX_WIDTHS) { + throw IllegalArgumentException("length-prefix bit width must be 8, 16, or 32 (Ticket 06)") + } + when (bitWidth) { + 8 -> KompactRuntime.writeBits(raw, bitOffset, 8, length) + 16 -> KompactRuntime.writeBitsLong(raw, bitOffset, 16, length.toLong()) + 32 -> KompactRuntime.writeBitsLong(raw, bitOffset, 32, length.toLong()) + } + } + + /** + * Parse-forward nested region: reads the byte-count length prefix at + * [bitOffset] ([prefixBitWidth] ∈ 8/16/32 — caller-validated) and returns the + * sub-region as `(startBit, bitLength)` where the payload lives. Returns + * `null` when the prefix overruns the buffer (a typed `TruncatedNested` / + * `BadLengthPrefix` at the caller, per Ticket 06/09; never a silent misread). + */ + public inline fun nestedRegionOrNull( + raw: ByteArray, + bitOffset: Int, + prefixBitWidth: Int + ): Pair? { + if (bitOffset < 0 || prefixBitWidth !in VALID_PREFIX_WIDTHS) return null + if (bitOffset + prefixBitWidth > raw.size * 8) return null + val byteCount = readLengthPrefix(raw, bitOffset, prefixBitWidth) + if (byteCount < 0) return null + val regionStart = bitOffset + prefixBitWidth + val regionBits = byteCount * 8 + if (regionStart + regionBits > raw.size * 8) return null + return regionStart to regionBits + } + + /** + * Count-prefixed repeat read (Ticket 05). Returns the element count decoded + * from the fixed-width LE prefix at [bitOffset], or -1 when invalid/out-of-bounds. + * The caller then reads [count] sequential elements starting at + * `bitOffset + prefixBitWidth`. + */ + public inline fun readCountPrefix(raw: ByteArray, bitOffset: Int, bitWidth: Int): Int = + readLengthPrefix(raw, bitOffset, bitWidth) +} diff --git a/kompact/src/commonMain/kotlin/ch/trancee/kompact/runtime/KompactResult.kt b/kompact/src/commonMain/kotlin/ch/trancee/kompact/runtime/KompactResult.kt index cd99fb3..ad30604 100644 --- a/kompact/src/commonMain/kotlin/ch/trancee/kompact/runtime/KompactResult.kt +++ b/kompact/src/commonMain/kotlin/ch/trancee/kompact/runtime/KompactResult.kt @@ -32,6 +32,11 @@ internal val RESULT_VALUE_MASK: Long = 0x0000_FFFF_FFFF_FFFFL internal val LONG_FAIL_MASK: Long = Long.MIN_VALUE or 0x7C00_0000_0000_0000L internal val LONG_FAIL_BASE: Long = Long.MIN_VALUE +// --- FloatResult NaN encoding --- + +// Canonical IEEE-754 single-precision quiet NaN (payload 0). +internal const val FLOAT_NAN_CANONICAL_BITS: Int = 0x7FC00000 + // --- DoubleResult NaN encoding --- internal val DOUBLE_NAN_CANONICAL: Long = 0x7FF8_0000_0000_0000L @@ -123,6 +128,9 @@ internal fun decodeDoubleError(packed: Long): KompactDecodeError { internal fun encodeDoubleSuccess(value: Double): Long = if (value.isNaN()) DOUBLE_NAN_CANONICAL else value.toBits() +internal fun encodeFloatSuccess(value: Float): Long = + if (value.isNaN()) FLOAT_NAN_CANONICAL_BITS.toLong() else value.toBits().toLong() + // ==================================================================== // Ticket 08 — result value class declarations (expect) // @@ -186,6 +194,19 @@ public expect value class BooleanResult(public val packed: Long) { } } +/** + * Checked 64-bit integer result (Ticket 08). + * + * Because every 64-bit `Long` bit-pattern is a valid signed value, success + * and failure cannot be distinguished without reserving a sentinel band. + * [success] therefore treats a compact range near [Long.MIN_VALUE] + * (bit 63 set with bits 62..58 clear, i.e. `Long.MIN_VALUE` through + * `Long.MIN_VALUE + (1L shl 58) - 1`) as the failure sentinel — these values + * are **not representable as success**. The first representable negative + * success value is `Long.MIN_VALUE + (1L shl 58)` (bit 58 set, outside the + * sentinel mask). This is the documented tradeoff of packing a typed result + * into a single `Long` without boxing; see Ticket 08. + */ public expect value class LongResult(public val packed: Long) { public val isSuccess: Boolean public val isFailure: Boolean diff --git a/kompact/src/commonMain/kotlin/ch/trancee/kompact/runtime/KompactRuntime.kt b/kompact/src/commonMain/kotlin/ch/trancee/kompact/runtime/KompactRuntime.kt index 3623940..ca1bfad 100644 --- a/kompact/src/commonMain/kotlin/ch/trancee/kompact/runtime/KompactRuntime.kt +++ b/kompact/src/commonMain/kotlin/ch/trancee/kompact/runtime/KompactRuntime.kt @@ -11,12 +11,14 @@ package ch.trancee.kompact.runtime * * These primitives are small, side-effect-free, and reference-free: an * inlinable value-class getter delegates to them with no heap allocation - * (the backing `ByteArray` is shared, not copied) (Ticket 03). + * (the backing `ByteArray` is shared, not copied) (Ticket 03). Per + * PROMPT §3 Phase 1 + Ticket 03, every primitive and checked accessor is + * `inline` so the zero-allocation call shape holds at the call site. */ public object KompactRuntime { /** Reads [bitWidth] bits (1..31) from [raw] starting at [bitOffset], LSB-first. */ - public fun readBits(raw: ByteArray, bitOffset: Int, bitWidth: Int): Int { + public inline fun readBits(raw: ByteArray, bitOffset: Int, bitWidth: Int): Int { var result = 0 var srcBit = bitOffset var destBit = 0 @@ -37,7 +39,7 @@ public object KompactRuntime { } /** Writes the low [bitWidth] bits (1..31) of [value] into [raw] at [bitOffset], LSB-first. */ - public fun writeBits(raw: ByteArray, bitOffset: Int, bitWidth: Int, value: Int) { + public inline fun writeBits(raw: ByteArray, bitOffset: Int, bitWidth: Int, value: Int) { var srcBit = bitOffset var srcValBit = 0 var remaining = bitWidth @@ -59,14 +61,14 @@ public object KompactRuntime { } /** Reads a single bit at [bitOffset] as a [Boolean]. */ - public fun readBitsBoolean(raw: ByteArray, bitOffset: Int): Boolean { + public inline fun readBitsBoolean(raw: ByteArray, bitOffset: Int): Boolean { val byteIndex = bitOffset ushr 3 val bitIndex = bitOffset and 7 return ((raw[byteIndex].toInt() and 0xFF) ushr bitIndex and 1) == 1 } /** Writes [value] as a single bit at [bitOffset]. */ - public fun writeBitsBoolean(raw: ByteArray, bitOffset: Int, value: Boolean) { + public inline fun writeBitsBoolean(raw: ByteArray, bitOffset: Int, value: Boolean) { val byteIndex = bitOffset ushr 3 val bitIndex = bitOffset and 7 if (value) { @@ -77,7 +79,7 @@ public object KompactRuntime { } /** Reads [bitWidth] bits (1..64) from [raw] starting at [bitOffset], LSB-first. */ - public fun readBitsLong(raw: ByteArray, bitOffset: Int, bitWidth: Int): Long { + public inline fun readBitsLong(raw: ByteArray, bitOffset: Int, bitWidth: Int): Long { var result = 0L var srcBit = bitOffset var destBit = 0 @@ -98,7 +100,7 @@ public object KompactRuntime { } /** Writes the low [bitWidth] bits (1..64) of [value] into [raw] at [bitOffset], LSB-first. */ - public fun writeBitsLong(raw: ByteArray, bitOffset: Int, bitWidth: Int, value: Long) { + public inline fun writeBitsLong(raw: ByteArray, bitOffset: Int, bitWidth: Int, value: Long) { var srcBit = bitOffset var srcValBit = 0 var remaining = bitWidth @@ -122,11 +124,11 @@ public object KompactRuntime { // --- Ticket 06/07 — bounded (checked) read accessors --- /** Bounds-check: true iff [bitOffset]+[bitWidth] fits in [raw]. */ - private fun fits(raw: ByteArray, bitOffset: Int, bitWidth: Int): Boolean = + public inline fun fits(raw: ByteArray, bitOffset: Int, bitWidth: Int): Boolean = bitOffset >= 0 && bitWidth >= 1 && bitOffset + bitWidth <= raw.size * 8 /** Reads 1 bit at [bitOffset] as a checked [BooleanResult]. */ - public fun readBool(raw: ByteArray, bitOffset: Int): BooleanResult { + public inline fun readBool(raw: ByteArray, bitOffset: Int): BooleanResult { if (!fits(raw, bitOffset, 1)) { return BooleanResult.failure(KompactDecodeError.BoundsError) } @@ -134,7 +136,7 @@ public object KompactRuntime { } /** Reads up to [bitWidth] bits (1..8) as a sign-extended [ByteResult]. */ - public fun readInt8(raw: ByteArray, bitOffset: Int, bitWidth: Int): ByteResult { + public inline fun readInt8(raw: ByteArray, bitOffset: Int, bitWidth: Int): ByteResult { if (!fits(raw, bitOffset, bitWidth) || bitWidth > 8) { return ByteResult.failure(KompactDecodeError.BoundsError) } @@ -145,7 +147,7 @@ public object KompactRuntime { } /** Reads up to [bitWidth] bits (1..8) as an unsigned [ByteResult]. */ - public fun readUInt8(raw: ByteArray, bitOffset: Int, bitWidth: Int): ByteResult { + public inline fun readUInt8(raw: ByteArray, bitOffset: Int, bitWidth: Int): ByteResult { if (!fits(raw, bitOffset, bitWidth) || bitWidth > 8) { return ByteResult.failure(KompactDecodeError.BoundsError) } @@ -154,7 +156,7 @@ public object KompactRuntime { } /** Reads up to [bitWidth] bits (1..16) as a sign-extended [ShortResult]. */ - public fun readInt16(raw: ByteArray, bitOffset: Int, bitWidth: Int): ShortResult { + public inline fun readInt16(raw: ByteArray, bitOffset: Int, bitWidth: Int): ShortResult { if (!fits(raw, bitOffset, bitWidth) || bitWidth > 16) { return ShortResult.failure(KompactDecodeError.BoundsError) } @@ -165,7 +167,7 @@ public object KompactRuntime { } /** Reads up to [bitWidth] bits (1..16) as an unsigned [ShortResult]. */ - public fun readUInt16(raw: ByteArray, bitOffset: Int, bitWidth: Int): ShortResult { + public inline fun readUInt16(raw: ByteArray, bitOffset: Int, bitWidth: Int): ShortResult { if (!fits(raw, bitOffset, bitWidth) || bitWidth > 16) { return ShortResult.failure(KompactDecodeError.BoundsError) } @@ -174,7 +176,7 @@ public object KompactRuntime { } /** Reads up to [bitWidth] bits (1..32) as a sign-extended [IntResult]. */ - public fun readInt32(raw: ByteArray, bitOffset: Int, bitWidth: Int): IntResult { + public inline fun readInt32(raw: ByteArray, bitOffset: Int, bitWidth: Int): IntResult { if (!fits(raw, bitOffset, bitWidth) || bitWidth > 32) { return IntResult.failure(KompactDecodeError.BoundsError) } @@ -184,12 +186,12 @@ public object KompactRuntime { readBitsLong(raw, bitOffset, bitWidth) } val shift = Long.SIZE_BITS - bitWidth - val signExtended = if (bitWidth < 64) (magnitude shl shift) shr shift else magnitude + val signExtended = (magnitude shl shift) shr shift return IntResult.success(signExtended.toInt()) } /** Reads up to [bitWidth] bits (1..32) as an unsigned [IntResult]. */ - public fun readUInt32(raw: ByteArray, bitOffset: Int, bitWidth: Int): IntResult { + public inline fun readUInt32(raw: ByteArray, bitOffset: Int, bitWidth: Int): IntResult { if (!fits(raw, bitOffset, bitWidth) || bitWidth > 32) { return IntResult.failure(KompactDecodeError.BoundsError) } @@ -202,7 +204,7 @@ public object KompactRuntime { } /** Reads up to [bitWidth] bits (1..64) as a sign-extended [LongResult]. */ - public fun readInt64(raw: ByteArray, bitOffset: Int, bitWidth: Int): LongResult { + public inline fun readInt64(raw: ByteArray, bitOffset: Int, bitWidth: Int): LongResult { if (!fits(raw, bitOffset, bitWidth) || bitWidth > 64) { return LongResult.failure(KompactDecodeError.BoundsError) } @@ -216,7 +218,7 @@ public object KompactRuntime { } /** Reads up to [bitWidth] bits (1..64) as an unsigned [LongResult]. */ - public fun readUInt64(raw: ByteArray, bitOffset: Int, bitWidth: Int): LongResult { + public inline fun readUInt64(raw: ByteArray, bitOffset: Int, bitWidth: Int): LongResult { if (!fits(raw, bitOffset, bitWidth) || bitWidth > 64) { return LongResult.failure(KompactDecodeError.BoundsError) } @@ -224,8 +226,8 @@ public object KompactRuntime { return LongResult.success(magnitude) } - /** Reads 32 bits at [bitOffset] as a checked [FloatResult]. */ - public fun readFloat(raw: ByteArray, bitOffset: Int): FloatResult { + /** Reads 32 bits at [bitOffset] as a checked [FloatResult]. NaN is canonicalized (Ticket 04). */ + public inline fun readFloat(raw: ByteArray, bitOffset: Int): FloatResult { if (bitOffset < 0 || bitOffset + 32 > raw.size * 8) { return FloatResult.failure(KompactDecodeError.BoundsError) } @@ -233,8 +235,8 @@ public object KompactRuntime { return FloatResult.success(Float.fromBits(bits)) } - /** Reads 64 bits at [bitOffset] as a checked [DoubleResult]. */ - public fun readDouble(raw: ByteArray, bitOffset: Int): DoubleResult { + /** Reads 64 bits at [bitOffset] as a checked [DoubleResult]. NaN is canonicalized (Ticket 04). */ + public inline fun readDouble(raw: ByteArray, bitOffset: Int): DoubleResult { if (bitOffset < 0 || bitOffset + 64 > raw.size * 8) { return DoubleResult.failure(KompactDecodeError.BoundsError) } diff --git a/kompact/src/commonMain/kotlin/ch/trancee/kompact/runtime/KompactWriter.kt b/kompact/src/commonMain/kotlin/ch/trancee/kompact/runtime/KompactWriter.kt new file mode 100644 index 0000000..0d0040b --- /dev/null +++ b/kompact/src/commonMain/kotlin/ch/trancee/kompact/runtime/KompactWriter.kt @@ -0,0 +1,171 @@ +package ch.trancee.kompact.runtime + +/** + * Forward-only, growable write builder for Kompact wire output (Ticket 07). + * + * The write path is **not** bound by the zero-allocation hot-path discipline + * (Ticket 03) — allocation/lambda overhead is acceptable here. The binary + * shape is a straight translation of Ticket 05's framing: fixed-width LE + * length prefixes, length-delimited nested sub-regions (child length computed + * first, then prefix + bytes — no back-patch), and count-prefixed repeats + * `…`. + * + * `build()` returns an exact-length snapshot; the backing buffer is not + * exposed, so the writer remains single-use forward-only (PROMPT §1). + */ +public class KompactWriter { + + /** + * Current write cursor in bits (LSB-first packing, Ticket 01). Read-only: + * the cursor only advances as values are written; callers observe progress + * via [build]. Exposed so nested/repeat assembly can reason about bit + * alignment without re-deriving it (PROMPT §1: forward-only). + */ + public var bitCursor: Int = 0 + private set + + private var buffer: ByteArray = ByteArray(INITIAL_CAPACITY_BYTES) + + /** Appends [bitWidth] low bits of [value] (two's-complement magnitude). */ + public fun writeBits(bitWidth: Int, value: Int) { + require(bitWidth in 1..31) { "writeBits bitWidth must be 1..31, was $bitWidth" } + ensureCapacityBits(bitWidth) + KompactRuntime.writeBits(buffer, bitCursor, bitWidth, value) + bitCursor += bitWidth + } + + /** Appends [bitWidth] low bits of [value] (64-bit, for UInt64/Int64). */ + public fun writeBitsLong(bitWidth: Int, value: Long) { + require(bitWidth in 1..64) { "writeBitsLong bitWidth must be 1..64, was $bitWidth" } + ensureCapacityBits(bitWidth) + KompactRuntime.writeBitsLong(buffer, bitCursor, bitWidth, value) + bitCursor += bitWidth + } + + /** Writes a single bit (true = 1, false = 0). */ + public fun writeBool(value: Boolean) { + ensureCapacityBits(1) + KompactRuntime.writeBitsBoolean(buffer, bitCursor, value) + bitCursor += 1 + } + + /** Writes a signed integer in [bitWidth] bits, two's-complement truncation. */ + public fun writeInt(bitWidth: Int, value: Int) { + if (bitWidth == 32) writeBitsLong(32, value.toLong()) else writeBits(bitWidth, value) + } + + /** Writes an unsigned integer in [bitWidth] bits (Int form; 32-bit values go wide). */ + public fun writeUInt(bitWidth: Int, value: Int) { + if (bitWidth == 32) writeBitsLong(32, value.toLong()) else writeBits(bitWidth, value) + } + + /** Writes an unsigned integer in [bitWidth] bits (64-bit value). */ + public fun writeUInt(bitWidth: Int, value: Long) { + writeBitsLong(bitWidth, value) + } + + /** Writes a 64-bit signed integer (two's-complement truncation), [bitWidth] ∈ 1..64. */ + public fun writeInt64(bitWidth: Int, value: Long) { + writeBitsLong(bitWidth, value) + } + + /** Writes an enum/ordinal [code] in [width] bits (codegen-validated width). */ + public fun writeEnum(width: Int, code: Int) { + writeBits(width, code) + } + + /** Writes a length-prefixed UTF-8 string: `` (Ticket 05). */ + public fun writeString(countWidth: Int, value: String) { + val bytes = value.encodeToByteArray() + KompactFraming.writeLengthPrefix(buffer, bitCursor, countWidth, bytes.size) + bitCursor += countWidth + appendBytes(bytes) + } + + /** Writes a length-prefixed blob: `` (Ticket 05). */ + public fun writeBlob(countWidth: Int, bytes: ByteArray) { + KompactFraming.writeLengthPrefix(buffer, bitCursor, countWidth, bytes.size) + bitCursor += countWidth + appendBytes(bytes) + } + /** + * Writes a nested sub-region: a child `KompactWriter` drains [block], then the + * child's byte length is emitted as a [lengthPrefixWidth]-bit LE prefix + * immediately followed by the child bytes (forward-only, compute-first — + * Ticket 07). The child region begins byte-aligned after the prefix. + */ + public fun writeNested(lengthPrefixWidth: Int = 16, block: KompactWriter.() -> Unit) { + val child = KompactWriter() + block(child) + val bytes = child.build() + KompactFraming.writeLengthPrefix(buffer, bitCursor, lengthPrefixWidth, bytes.size) + bitCursor += lengthPrefixWidth + appendBytes(bytes) + } + + /** + * Writes a count-prefixed repeat: `` where each + * element is produced by one invocation of [block] against this writer + * (Ticket 05). [countWidth] must be one of [KompactFraming.VALID_PREFIX_WIDTHS]. + */ + public fun writeRepeated(count: Int, countWidth: Int = 8, block: KompactWriter.() -> Unit) { + require(countWidth in KompactFraming.VALID_PREFIX_WIDTHS) { + "countWidth must be 8, 16, or 32 (Ticket 06), was $countWidth" + } + require(count >= 0) { "repeat count must be non-negative, was $count" } + KompactFraming.writeLengthPrefix(buffer, bitCursor, countWidth, count) + bitCursor += countWidth + for (i in 0 until count) { + block() + } + } + + /** + * Returns an exact-length snapshot of the accumulated bits. Calling + * afterwards is allowed but yields an empty buffer (single-shot by design). + */ + public fun build(): ByteArray { + val byteLen = (bitCursor + 7) / 8 + return if (byteLen == 0) { + ByteArray(0) + } else { + buffer.copyOfRange(0, byteLen) + } + } + + // --- internals --- + + private fun ensureCapacityBits(neededBits: Int) { + val neededBytes = (bitCursor + neededBits + 7) / 8 + if (neededBytes > buffer.size) { + val newSize = maxOf(neededBytes, buffer.size * 2) + buffer = buffer.copyOf(newSize) + } + } + + private fun appendBytes(bytes: ByteArray) { + ensureCapacityBits(bytes.size * 8) + // Byte-aligned append fast path (the prefix left us byte-aligned for nested/blob). + if (bitCursor % 8 == 0) { + val dst = bitCursor / 8 + var i = 0 + while (i < bytes.size) { + buffer[dst + i] = bytes[i] + i++ + } + bitCursor += bytes.size * 8 + } else { + // Fall back to the bit primitive so we handle the rare non-aligned case. + var i = 0 + while (i < bytes.size) { + KompactRuntime.writeBits(buffer, bitCursor, 8, bytes[i].toInt() and 0xFF) + bitCursor += 8 + i++ + } + } + } + + public companion object { + private const val INITIAL_CAPACITY_BYTES = 16 + } +} diff --git a/kompact/src/commonTest/kotlin/ch/trancee/kompact/runtime/KompactFieldV1SurfaceTest.kt b/kompact/src/commonTest/kotlin/ch/trancee/kompact/runtime/KompactFieldV1SurfaceTest.kt new file mode 100644 index 0000000..0d26ef8 --- /dev/null +++ b/kompact/src/commonTest/kotlin/ch/trancee/kompact/runtime/KompactFieldV1SurfaceTest.kt @@ -0,0 +1,40 @@ +package ch.trancee.kompact.runtime + +import kotlin.test.Test + +/** + * Ticket 04/06 v1 annotation surface — compile-time existence check. + * + * `@KompactField` is SOURCE-retained, so reflection can't see its members at + * runtime; the contract is that this file *compiles*, which pins every v1 + * member name. The runtime assert only confirms the class loads. + */ +@Suppress("unused", "unused_parameter") +private class KompactFieldV1Probe( + @KompactField( + bitOffset = 0, + bitWidth = 4, + lengthPrefixWidth = 16, + isNested = true, + repeatCountWidth = 8, + enumWidth = 4, + defaultValue = "0", + isVersionField = true, + ) + val tag: Int, + + // Scalar default: only bitOffset/bitWidth declared. + @KompactField(bitOffset = 4, bitWidth = 1) + val valid: Boolean, +) + +class KompactFieldV1SurfaceTest { + + @Test + fun v1AnnotationMembersExist_compileTimeContract() { + // If any of lengthPrefixWidth/isNested/repeatCountWidth/enumWidth/ + // defaultValue/isVersionField is absent on @KompactField, the probe + // above fails to compile — this assert only confirms the class loads. + KompactFieldV1Probe::class + } +} diff --git a/kompact/src/commonTest/kotlin/ch/trancee/kompact/runtime/KompactFramingTest.kt b/kompact/src/commonTest/kotlin/ch/trancee/kompact/runtime/KompactFramingTest.kt new file mode 100644 index 0000000..9012e6a --- /dev/null +++ b/kompact/src/commonTest/kotlin/ch/trancee/kompact/runtime/KompactFramingTest.kt @@ -0,0 +1,115 @@ +package ch.trancee.kompact.runtime + +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertTrue +import kotlin.test.assertNotNull + +/** + * Ticket 05: sequential, length-delimited framing (fixed-width LE byte-count + * prefix per field; parse-forward nested sub-regions; count-prefixed repeats). + */ +class KompactFramingTest { + + // --- 8-bit length prefix (1 byte) --- + + @Test + fun readLengthPrefix8_readsSingleByteLittleEndian() { + val buf = ByteArray(1) + KompactRuntime.writeBits(buf, 0, 8, 5) + val len = KompactFraming.readLengthPrefix(buf, 0, 8) + assertEquals(5, len) + } + + @Test + fun readLengthPrefix8_readsMax255() { + val buf = ByteArray(1) + KompactRuntime.writeBits(buf, 0, 8, 255) + assertEquals(255, KompactFraming.readLengthPrefix(buf, 0, 8)) + } + + // --- 16-bit length prefix (little-endian) --- + + @Test + fun readLengthPrefix16_readsLittleEndianTwoBytes() { + val buf = ByteArray(2) + // 0x0100 = 256 in LE bytes = [0x00, 0x01] + buf[0] = 0x00 + buf[1] = 0x01 + assertEquals(256, KompactFraming.readLengthPrefix(buf, 0, 16)) + } + + @Test + fun readLengthPrefix16_readsBigValueMax65535() { + val buf = ByteArray(2) { 0xFF.toByte() } + assertEquals(65535, KompactFraming.readLengthPrefix(buf, 0, 16)) + } + + // --- 32-bit length prefix (little-endian) --- + + @Test + fun readLengthPrefix32_readsLittleEndianFourBytes() { + val buf = ByteArray(4) + // 0x00010000 = 65536 in LE = [0x00,0x00,0x01,0x00] + buf[2] = 0x01.toByte() + assertEquals(65536, KompactFraming.readLengthPrefix(buf, 0, 32)) + } + + // --- write + read round trip --- + + @Test + fun lengthPrefix8_roundTrip() { + val buf = ByteArray(3) + KompactFraming.writeLengthPrefix(buf, 0, 8, 3) + assertEquals(3, KompactFraming.readLengthPrefix(buf, 0, 8)) + } + + @Test + fun lengthPrefix16_roundTrip() { + val buf = ByteArray(4) + KompactFraming.writeLengthPrefix(buf, 0, 16, 1234) + assertEquals(1234, KompactFraming.readLengthPrefix(buf, 0, 16)) + } + + @Test + fun lengthPrefix32_roundTrip() { + val buf = ByteArray(6) + KompactFraming.writeLengthPrefix(buf, 0, 32, 70_000) + assertEquals(70_000, KompactFraming.readLengthPrefix(buf, 0, 32)) + } + + // --- nested region: length-delimited, parse-forward --- + + @Test + fun nestedRegionBoundsFromLengthPrefix8() { + // [0..7] prefix byte = 4 (8-bit, 4 bytes follow); payload at bits 8..39. + val buf = byteArrayOf(4, 0xAB.toByte(), 0xCD.toByte(), 0xEF.toByte(), 0x10) + val region = KompactFraming.nestedRegionOrNull(buf, 0, 8) + assertNotNull(region) + val (startBit, bitLen) = region + assertEquals(8, startBit) + assertEquals(32, bitLen) + // First payload byte at bit 8 == 0xAB; last byte at bits 32..39 == 0x10. + assertEquals(0xAB, KompactRuntime.readBits(buf, startBit, 8)) + assertEquals(0x10, KompactRuntime.readBits(buf, startBit + bitLen - 8, 8)) + } + + @Test + fun nestedRegion8_truncatedReturnsNull() { + // length prefix says 10 bytes but only 3 available past the prefix byte. + val buf = byteArrayOf(10, 1, 2, 3) + val region = KompactFraming.nestedRegionOrNull(buf, 0, 8) + // TruncatedNested: prefix exceeds remaining bytes (fail-fast, never silent). + assertEquals(null, region) + } + + + // --- repeat count: fixed-width LE count prefix, sequential --- + + @Test + fun repeatCount_roundTrip() { + val buf = ByteArray(4) + KompactFraming.writeLengthPrefix(buf, 0, 16, 7) + assertEquals(7, KompactFraming.readLengthPrefix(buf, 0, 16)) + } +} diff --git a/kompact/src/commonTest/kotlin/ch/trancee/kompact/runtime/KompactResultCanonicalizationTest.kt b/kompact/src/commonTest/kotlin/ch/trancee/kompact/runtime/KompactResultCanonicalizationTest.kt new file mode 100644 index 0000000..94136c2 --- /dev/null +++ b/kompact/src/commonTest/kotlin/ch/trancee/kompact/runtime/KompactResultCanonicalizationTest.kt @@ -0,0 +1,102 @@ +package ch.trancee.kompact.runtime + +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +/** + * Ticket 04: IEEE-754 floats carry a canonicalized NaN. + * Ticket 08: LongResult reserves a sentinel band near Long.MIN_VALUE (documented tradeoff). + */ +class KompactResultCanonicalizationTest { + + // --- FloatResult NaN canonicalization (Ticket 04) — RED until fixed --- + + @Test + fun floatResult_success_canonicalizesAnyNanToSinglePayload() { + val canonicalBits = 0x7FC00000 + for (bits in listOf(0x7FC00001, 0x7FA00000, 0x7FF80001, 0xFFC00000L.toInt())) { + val nan = Float.fromBits(bits) + val r = FloatResult.success(nan) + assertTrue(r.isSuccess, "bits=${bits.toString(16)}") + assertTrue(r.getOrThrow().isNaN(), "bits=${bits.toString(16)}") + assertEquals( + canonicalBits, + r.getOrThrow().toBits(), + "non-canonical NaN bits=${bits.toString(16)}" + ) + } + } + + @Test + fun floatResult_success_preservesNonNanValue() { + val values = listOf(0.0f, -0.0f, 1.5f, -1.5f, Float.MAX_VALUE, Float.MIN_VALUE) + for (v in values) { + val r = FloatResult.success(v) + assertTrue(r.isSuccess, "value=$v") + assertEquals(v, r.getOrThrow(), "value=$v") + } + } + + // --- DoubleResult already canonicalizes; assert that contract holds --- + + @Test + fun doubleResult_success_canonicalizesAnyNanToSinglePayload() { + val canonical = Double.fromBits(0x7FF8000000000000L) + for (bits in listOf(0x7FF8000000000001L, 0x7FF0000000000001L, 0x7FFA000000000001L)) { + val nan = Double.fromBits(bits) + val r = DoubleResult.success(nan) + assertTrue(r.isSuccess, "bits=${bits.toString(16)}") + assertTrue(r.getOrThrow().isNaN(), "bits=${bits.toString(16)}") + assertEquals( + canonical.toBits(), + r.getOrThrow().toBits(), + "non-canonical NaN bits=${bits.toString(16)}" + ) + } + } + + @Test + fun doubleResult_success_preservesNonNanValue() { + val values = listOf(0.0, -0.0, 1.5, -1.5, Double.MAX_VALUE, Double.POSITIVE_INFINITY, + Double.NEGATIVE_INFINITY) + for (v in values) { + val r = DoubleResult.success(v) + assertTrue(r.isSuccess, "value=$v") + assertEquals(v, r.getOrThrow(), "value=$v") + } + } + + // --- LongResult sentinel band (Ticket 08: documented tradeoff) --- + + @Test + fun longResult_sentinelRangeReservesLowBandNearMinValue() { + // The failure sentinel band is Long.MIN_VALUE .. (Long.MIN_VALUE + (1L<<58) - 1): + // bit 63 set (negative) with bits 62..58 clear. The first representable + // success value with bit 63 set has bit 58 also set. + val firstSuccessBelowMin = Long.MIN_VALUE + (1L shl 58) + val r = LongResult.success(firstSuccessBelowMin) + assertTrue(r.isSuccess, "first value past sentinel band must be representable") + assertEquals(firstSuccessBelowMin, r.getOrThrow()) + } + + @Test + fun longResult_sentinelRangeLongMinValueUnrepresentableAsSuccess() { + val r = LongResult.success(Long.MIN_VALUE) + assertFalse(r.isSuccess, "Long.MIN_VALUE is in the failure sentinel range (documented)") + } + + // --- Read-path NaN canonicalization --- + + @Test + fun readFloat_canonicalizesNanOnReadPath() { + val buf = ByteArray(4) + // 0x7FC00001 is a non-canonical NaN payload; canonical is 0x7FC00000. + KompactRuntime.writeBitsLong(buf, 0, 32, 0x7FC00001L) + val r = KompactRuntime.readFloat(buf, 0) + assertTrue(r.isSuccess) + assertTrue(r.getOrThrow().isNaN()) + assertEquals(0x7FC00000, r.getOrThrow().toBits(), "readFloat must canonicalize NaN") + } +} diff --git a/kompact/src/commonTest/kotlin/ch/trancee/kompact/runtime/KompactWriterTest.kt b/kompact/src/commonTest/kotlin/ch/trancee/kompact/runtime/KompactWriterTest.kt new file mode 100644 index 0000000..89d063e --- /dev/null +++ b/kompact/src/commonTest/kotlin/ch/trancee/kompact/runtime/KompactWriterTest.kt @@ -0,0 +1,135 @@ +package ch.trancee.kompact.runtime + +import kotlin.test.Test +import kotlin.test.assertContentEquals +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +/** + * Ticket 07: write-builder API — growable buffer, forward-only, sub-writer for + * nested + count-prefixed repeats, `build(): ByteArray`, symmetric with reads. + */ +class KompactWriterTest { + + // --- fixed-width scalar writes, symmetric with the read-side checked accessors --- + + @Test + fun writeBool_roundTrip_viaReader() { + val w = KompactWriter() + w.writeBool(true) + w.writeBool(false) + w.writeBool(true) + val buf = w.build() + // 3 one-bit values pack into 1 byte. + assertEquals(1, buf.size) + val r = KompactRuntime.readBool(buf, 0) + assertTrue(r.isSuccess); assertTrue(r.getOrThrow()) + assertEquals(false, KompactRuntime.readBool(buf, 1).getOrThrow()) + assertEquals(true, KompactRuntime.readBool(buf, 2).getOrThrow()) + } + + @Test + fun writeInt8_signExtended_roundTrip() { + val w = KompactWriter() + w.writeInt(8, -5) + val buf = w.build() + val r = KompactRuntime.readInt8(buf, 0, 8) + assertTrue(r.isSuccess) + assertEquals(-5, r.getOrThrow().toInt()) + } + + @Test + fun writeUInt16_roundTrip() { + val w = KompactWriter() + w.writeUInt(16, 1023) + val buf = w.build() + val r = KompactRuntime.readUInt16(buf, 0, 16) + assertTrue(r.isSuccess) + assertEquals(1023, r.getOrThrow().toInt()) + } + + @Test + fun writeInt32_negative_roundTrip() { + val w = KompactWriter() + w.writeInt(32, -1) + val buf = w.build() + val r = KompactRuntime.readInt32(buf, 0, 32) + assertTrue(r.isSuccess) + assertEquals(-1, r.getOrThrow().toInt()) + } + + @Test + fun writeEnum_roundTrip() { + val w = KompactWriter() + w.writeEnum(width = 4, code = 7) + val buf = w.build() + assertEquals(7, KompactRuntime.readBits(buf, 0, 4)) + } + + // --- length-prefixed strings & blobs (Ticket 05 framing) --- + + @Test + fun writeString_lengthPrefixed8_roundTrip() { + val w = KompactWriter() + w.writeString(countWidth = 8, value = "hi") + val buf = w.build() + // 1 prefix byte (length=2) + 2 UTF-8 bytes. + assertEquals(3, buf.size) + assertEquals(2, KompactFraming.readLengthPrefix(buf, 0, 8)) + assertContentEquals("hi".encodeToByteArray(), buf.copyOfRange(1, 3)) + } + + @Test + fun writeBlob_lengthPrefixed16_roundTrip() { + val payload = byteArrayOf(0x01, 0x02, 0x03, 0x04) + val w = KompactWriter() + w.writeBlob(countWidth = 16, bytes = payload) + val buf = w.build() + assertEquals(4, KompactFraming.readLengthPrefix(buf, 0, 16)) + assertContentEquals(payload, buf.copyOfRange(2, 6)) + } + + // --- nested sub-writer (Ticket 05/07: child length first, then prefix + bytes) --- + + @Test + fun writeNested_emitsPrefixThenChildBytes() { + val w = KompactWriter() + w.writeNested(lengthPrefixWidth = 16) { + writeInt(8, 0xAB) + writeInt(8, 0xCD) + } + val buf = w.build() + // 2-byte LE prefix (length=2) + 2 payload bytes. + assertEquals(4, buf.size) + assertEquals(2, KompactFraming.readLengthPrefix(buf, 0, 16)) + val region = KompactFraming.nestedRegionOrNull(buf, 0, 16)!! + val (start, bitLen) = region + assertEquals(16, start) + assertEquals(16, bitLen) + } + + + @Test + fun writeRepeated_emitsCountThenElements() { + val w = KompactWriter() + w.writeRepeated(count = 3, countWidth = 8) { + writeBool(true) + } + val buf = w.build() + // 1 prefix byte (count=3) + 3 packed one-bit elements → 2 bytes. + assertEquals(2, buf.size) + assertEquals(3, buf[0].toInt() and 0xFF) + assertEquals(0x07, buf[1].toInt() and 0xFF) + } + + // --- build() is exact length and forward-only --- + + @Test + fun build_returnsExactLength() { + val w = KompactWriter() + w.writeBits(4, 0b1010) + w.writeBool(true) + // 4 bits + 1 bit = 5 bits → ceil(5/8) = 1 byte. + assertEquals(1, w.build().size) + } +} diff --git a/kompact/src/iosMain/kotlin/ch/trancee/kompact/runtime/KompactResult.kt b/kompact/src/iosMain/kotlin/ch/trancee/kompact/runtime/KompactResult.kt index f2cfb21..22ec32e 100644 --- a/kompact/src/iosMain/kotlin/ch/trancee/kompact/runtime/KompactResult.kt +++ b/kompact/src/iosMain/kotlin/ch/trancee/kompact/runtime/KompactResult.kt @@ -62,7 +62,7 @@ public actual value class FloatResult(public actual val packed: Long) { else throw KompactDecodeException(decodeErrorFromSmallBits(packed)) public actual companion object { public actual fun success(value: Float): FloatResult = - FloatResult(encodeSmallSuccess(value.toBits().toLong())) + FloatResult(encodeSmallSuccess(encodeFloatSuccess(value))) public actual fun failure(error: KompactDecodeError): FloatResult = FloatResult(encodeSmallFailure(error)) } diff --git a/kompact/src/jvmMain/kotlin/ch/trancee/kompact/runtime/KompactResult.kt b/kompact/src/jvmMain/kotlin/ch/trancee/kompact/runtime/KompactResult.kt index 32e90e4..9bd0639 100644 --- a/kompact/src/jvmMain/kotlin/ch/trancee/kompact/runtime/KompactResult.kt +++ b/kompact/src/jvmMain/kotlin/ch/trancee/kompact/runtime/KompactResult.kt @@ -67,7 +67,7 @@ public actual value class FloatResult(public actual val packed: Long) { else throw KompactDecodeException(decodeErrorFromSmallBits(packed)) public actual companion object { public actual fun success(value: Float): FloatResult = - FloatResult(encodeSmallSuccess(value.toBits().toLong())) + FloatResult(encodeSmallSuccess(encodeFloatSuccess(value))) public actual fun failure(error: KompactDecodeError): FloatResult = FloatResult(encodeSmallFailure(error)) } @@ -89,6 +89,8 @@ public actual value class BooleanResult(public actual val packed: Long) { BooleanResult(encodeSmallFailure(error)) } } + +// LongResult — sentinel-based encoding (bits 63 set + 62..58 clear) (Ticket 08) @JvmInline public actual value class LongResult(public actual val packed: Long) { public actual val isSuccess: Boolean get() = !isLongFailure(packed) @@ -104,6 +106,8 @@ public actual value class LongResult(public actual val packed: Long) { LongResult(encodeLongFailure(error)) } } + +// DoubleResult — canonical-NaN success, reserved NaN payload for errors (Ticket 04) @JvmInline public actual value class DoubleResult(public actual val packed: Long) { public actual val isSuccess: Boolean get() = !isDoubleFailure(packed) From beabe3768652fc7ed26854723980b30effa33ad4 Mon Sep 17 00:00:00 2001 From: Philipp Grosswiler Date: Sat, 5 Sep 2026 11:01:37 +0200 Subject: [PATCH 17/51] fix: commit Gradle wrapper jar to restore ./gradlew in CI The "Automatic Dependency Submission (Gradle)" workflow (and any CI job invoking ./gradlew) failed with: Error: Unable to access jarfile .../gradle/wrapper/gradle-wrapper.jar No dependency graph files found to submit. Root cause: `.gitignore` rule `*.jar` (line 8) excluded the wrapper bootstrap jar, and no force-include exception existed, so gradle/wrapper/gradle-wrapper.jar was never tracked. The `gradlew` script and `gradle-wrapper.properties` were tracked, but without the jar the wrapper cannot bootstrap. Fix: - .gitignore: add standard force-include `!gradle/wrapper/gradle-wrapper.jar` (every Gradle project ships the wrapper jar; the absence broke CI). - Regenerate the wrapper jar for the pinned Gradle 9.7.1 via `gradle wrapper --gradle-version 9.7.1 --distribution-type bin --offline`. This also restores developer `./gradlew` execution locally (previously only the system `gradle` cached distribution worked). Verification: - ./gradlew --version now bootstraps successfully (see job logs). - :kompact:apiCheck / jvmApiCheck pass on feat/laguna (9376278). --- .gitignore | 3 +++ gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 48462 bytes 2 files changed, 3 insertions(+) create mode 100644 gradle/wrapper/gradle-wrapper.jar diff --git a/.gitignore b/.gitignore index e7e0541..9a31079 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,9 @@ gradle/wrapper/dists/ *.class *.klib *.knp +# The Gradle wrapper jar MUST be committed (gradle/wrapper/gradle-wrapper.jar) so +# `./gradlew` works in CI/dev — un-ignore it despite the *.jar rule above. +!gradle/wrapper/gradle-wrapper.jar # IDE .idea/ diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..b1b8ef56b44f16b14dc800fa8103a6d89abb526f GIT binary patch literal 48462 zcma&NV{|3jwk;gnwr$(CRk3Z`Sy9Ed?Nn^ruGlsztklcC=e7I2x9>aqJFB(1eyu-q z%|3b`eLzVT6buar3JMAc2#EOW{C^)LAZQ?YaW!FjX$1*JIcZUG1yyl%HEd!6f#E+}*Jo*NafvM<-FbE0;-_L#rp}qdn%JEoAVNlEB#J^Oq`mU_#*ev4HLmc> zjXz_hFft^><#omb;Zer-%wm4hxo!wjuX3hBldg(^-RiOleKin`>KHfL3P*{k?(rji(#j2Cc0K509#>qu=-T&B!-5EBi(+ zIuTD-qfcAYgS@`Fb2^-p)4#o6A3z0&fp?~cV=CRsAeCmO4ZQ5kKgC%0el=Q&Rhd#k zaGmAbUW8uKC}-C0s~2);d{;mpsNBx9rn__66W{AhaSvJEK+c0b6ARO+l(CI7E|S5x zhaYP--@F<|99X&)9`q^2(^-Zu^Tzfm)v|gkTJHQ!G*zIg5hzoygeXZoYUEJ;iFkE# zq^r$*c|>Hmn3GapzcDYnjgSFiO^NFyTR5AH#mh%zRToMpEi(r)1$5)h455DuV}0al z!*psWuL@Ke-2gvftfMEGf9YEi^<{B@qru zINgo+YsE&LN?)1qItJoNhISp-fZ86`XR#*6xcvM~_7=JHUX;K9*=Gu5X~ zix|O2d=&C#u_w{=B$eCpJ4L*6i7={j+{Og~`Emz@&98}6s<-p^)`0fXE4cJBP{>)Ltb>JwcqI>yz z0-r-SEhC@p)XOoh|1|XgjFaREHfsu4dAGVz*k#m+V<4 zHqvlud6=;#QWHUoTR_a8Y8+heN?M%n1@0YLiaN@GuOPNd26tik7eKulTx?mM-R!1H znB6+H{^krFXg_b{y=QeCT~qR3T4}l+b!Oz9;~|3*6F<3?#|DYYW&1RtFE)ILZ!`85 zVmvrZkLTzf31unH7Cc5E0iFShqlBE9hgEnRJH1juII*vyp&xd!g`q}X_6WT6E$hhQ`Vdp9k^<)VS?lj!cTh z7FQcQAVA@jL^cXod8cnhKG2TS9+;QU6Kq>}UOY3&TL9gXbl{Fv8@WsF=z7>X0To@$ zY@Oi1uc|MdJ$>Kn{@!g_e`-I&Tpwfg9cr>(iakDX1qciCG_1y!Di#4_)lE!bWJbrp z5aUonb6m-?tiQyR_`P#~SOu+tb_ev6JO>EbEhHK@KbeT0_FDo>dl9bMg)>xmCNB*g zG5NC8ABavuTEZVGW6jP*nAqRt3W?7Iigc-EE~zpNJXRAE z>`~RO9$892j&I1kV;9U)xT8^}IeV`n{}QDtj2o-RBt`DGZUOO;O*lFCb_vpyGh*;95PfeGu!dyrmZ9VJ3Z*upg z6R-3Lr%_55$Hw1^{+KWx0#z`T7O6sXo1h;m?B_ur`X2bFz-SzDrL zpk^@B<+I6imc@7vip za%1jMB7q@1j# zz{u?YojZMW{5j$@h=v4iu2mTu7IzI|)Sxn!74=*J>1a&?Xjt z2%JhSi#4huEcD9qdR9Lj4vwmfnL{%+vQ{f-KgYeqin(OPd8+(g*Uq#TLxQjD4 zLCL%ul(V&PAPlAx8D`@K8Rc`{GPecQ<)d=KWel0ejFeeXGQ6o7601B!!I@RY&eDriADD6wP6DcFKDLZ|lO#YwnrNCZ)zRJpdxX_nPZa4j#$j6v!h|6p!dH}MY6#B`@%6=) z-HigguDACKBULnon^FKzazF|Y1{t(U5rUGnEU|}djVsWT-F>@@mNx?_$kF51QF4C5 zStKR$^3(fw85(4HGs9{mUTtn1)3PwxTN?6}j;32&vJ^BiPHfndLkdU5sOemXKGyCZ z@<7j(k>DNeo~QXyJkFWk!7(y1SB%nA3{v~P2c8ooKa4auM!el!Q_=;lJ$c5ADqE+^ zX8*|A99v;jWPrm(8=h;2ZAj|(vVbx~wQ{N%v;eYLD_BB2LAEWCs@xauyBDl(_HIBvA(XJ7B1E;O zJYCJ8xFJh7f5sr;Y#Wp_`$4Z_H4e9bGiBp?Qu&2!@%Bl2dT5evfFO*^hLDiBu2%Jl z*WAlL5PaQ7skJa(qVysky}DQquZ8U?2@UyJ8zB#=U_E>MgE%XA$CtfL31m$rATJvC zs@!crc0=128PM=Zp zW_5Czv9))n_8Ru?{pxM2F8^r%*O41}RnONbSj*piG%`nyF>6ky=|;B&k8iot(J=kyoU3p<_zaAX(1ijzf*uXA zZ_5jeC{Lks+&QeFIlmzZi3+fsF4fNW^~kvC4Q*T-vrNP!x9xnen12lZQM=1_MdW76LKX(GuW`%T~dM^YX6+ras|Xy4Qhfcq=D+z-P-ea z`T;^gj3+grr3^hwqcNTJErl$z+k>{bYFm6QV%7Opth?9+>|Dn)O@`7F@=j-XSqGPW zjUAu%b3Er@;j1%RZxVDhI3sakg-gvTLOSV7;FV6ED=(5;UG??=WADZw^=$4AyFh#}VMe3afM^pF zFa}-nM8X=K?Jy02*o02@6k{ z%O!hBhjXlXKdhy3A{xGB<##e|j3^dFv~~%v2_H{t(mN7NVeS~51?D&Ozbxa`qwZ_4 z;C#Q#fL1sua%ggucgIEHZtcY=Ag&GgE|h7Q{77D!WUq`;SSGEE0pU;aoj<7-JCAvf zduN=(tx3Mb+EUXKoax|v;8b@#HJ&Q|!g4ryrl|R>WlAv?IH`bk)I24;eE4NIq@SLK31LD4+w~#3iN{=<`<1R!t^$@K5>U6%W=%8_ANuR5 zs(IDuI18ftirTDARnGmF%;iz+4{MlMihJw_l!0Y)NttXC_t+s)V<EY>=Xin*nGX79k6vQ?beRk zy_J>@YSC_gMIG$yjO-y&o>S6xtfT27aSs>e|`x(f2R1bM}*518~%x>1Yct=18b&Z>GiS*>VB$+i2876zL)1cT zN33g=g|>xWE2)dds5m2+8Vy)m-u@NHOlGYxxjam21r1;xWtT0TgqKZrl}*LSkqFt4 zNTI1=3o%C*!-i;iWnlca$stRdwITA1?#fD~5OIqIQAM18BwO_u>hqL&OAANiF|8rG z_IZ9mp?FA-{Gq9+Ky<#NgL1gWJixfO0ziP$4T4G>vsvqC-NQh+A64F4! z-(t<=AbPSG%`mTl6BJtH~3RmvPhQlE-EUkEoBIP(_WMN zK~Fe!siee{M*ns1hkp5(2}vX#%u+T!Abh=<_gEx_QW?h4V@B>uOCEetEe01tl)^`V z(=cOLmuOB;8&&m%_6pcyrt83UXkJ`f9I&0KxY09}RTTs!l^_7~8$tPA%Hm#&$k0;# zF;O0zCGo0IN)X~SyKDoY1DW{Ulce|V9w=ld;U`z$t$>8U!Gu8V?_LAJAudt3eI#*! z2i9~F=kP5m>!bmb%1e~b1!1gz01Py(Yw5gOsFN#o1a&d|=PpgN(#UVreY9^99I0iG zaYE@>(C^V7pnoB~#w$2C1_TIb1N5Je&iao?S2A*TF>@vpHg`31{uk<9{zf_}s&z%dL-Fo)C$yl$%pAdqU!HJgp zh_{m1imk{&{ScyeuziqZHu5cto0{S}^BlXu% z0~;>_yHGd#?Kt8ErxK)z6ojj5SacQobw)-8`c!$HOI*V6eyqou{1Upm%_p!BY^t(D zDtn(oQ!jff`ddGSD;P8Hes!v)OKW-*>mS&#i0ow87;h>(=Cu0>b4)|=EegbN5=Xkh z9Ge13=3z#sk+fT<)PuUUf_%Nx@l!P?t*mni^94p^Ax6b2SVL5U>9dHH!H4DL4}@?@ z?Gpq$C**OmWliYA{5s<|EZ@QI2{-K#brFxfA~AIqq&-WSALHWQ8}%mvaNFasrtnE{ zg=sB4-RF!?)nf{>Wo~kNFgYefoFHBcSr*;iF9B!R=5Np|jv>Uf+mcarG-XGy*kP{z zISVyoPcl_9cOg-@613Qx16OGF#sH&2NTHDa_}vyidmxS~pMfY#AeQvu?AXpWNzi7A z*6&7a7!C9HRU+N{>WYTh0GXoBnXw{lQby^XShgDOw@e8TP}9Y*oFV4MVF#@Ds2A+A zXBEt3a@-IIl)TOcXx;0P;|ihR%Tq@DXeG5p-O{!T7Sg$s1 z8OA4iOx-!>6eK^x{jU-0SvByimK|nZik5zKIvvWVGE)4=x^&5Nx%Qgje!k3VoizaB zip#?$u(R8u{wUFC>tVR8oA%7fs?xEu(gYn>y6BB%vwPR9&RoZE%%RK! zl#Qnkl^+Y*Y4L{Xk(YX&aGj|zSpqO_;C3CTepA!L#4EXO|(eA`Fi+2EQ3!C zo^SpVP?{chQ3uaxu7y>w213e22cdA#l-M2kStPE%sq6vE4M*?3At!S7tIp(tQg(Ml zECjeJw8)*#LYYk_+Txv3rxsH9jJZBRrHp29yJ(^;_PEdn%#U1q`r89}38;XeF{ee& zsZEsUbJ{LtwOjU{vjL(Wvs2!Bx;#^Mzld&TjS@oo3kk=0P36MC-Ie6eHNN&{8b^s z0@jcbdejrrj!>r#Wu=3H1dgjeOI}NkhmE}K+UK&M>%7b!n&{0Zixk%^)6#@=V~IZN zxG>9kl&STQth}qScidfg58d2dF|v_U<@+V^eE@$4x;7oS3)MvWusA?9+%rN>aY#eA_6 zic@S(@e9$9tQM-&-7>X8~#n{5G}nuOu=dSyN+b~jA;_SExZ1H9Q1A}}Rz;XtXUIOP0~ zZzS|~T+%de-nGI$s?wxaJoe+99vmo%xm8o8SNEsAqAE)4LNvHc-1AX24C4k4u3vZmov^_VcxgGxapV(8)_K(^8= z2d{xCrmk(x&514Ly?e{Mf6}h3=oeP7+ZE{%B^c-kK8g0W{tYw3q%zty_Rd@1nbnyHMwabNp-sSyzpV4v>QsnKcQjF67%g~n&3t^1MesVxCzfJ5b=SOI#YfPP^^JGQw=9L1RCMFbrU{8O0LWOUdBK#j&{`tzXX zpe2_{+-8$a+o#%8MUlL4$yK`*--z&3{@Y?jP!m{g5nM+Ht=bD3o}Ok~sBQ_!^!->! z?NDVtyLXzmGYCEmjSCDK*q?Aq1;8fz9l9|z@~l{)R6GfKELc^(nV+TjjI^n0M+S0i z@YOu*Tk>|M6a0_n$(E;#^1Zgif<-CpYiMvyT+Y*9Z?&~IKSwsLa5Q#p_?FqK3lKIw zlp6Hk%lio6)yq>m-`QT2Nj-q!aX7~Hlm^Xh6FNbw z$#ri(Kk*GUHXORu@`aYQU@ zB~S-oIO^~abRPocemkm!W73dbb!j^_xgo_@#W#6p12>w^{){VfeX?U71Xyn9&E zHa1#*!4c;?r}jv7dMN`g#&R_S215)dccDOJr=uz%LIz@zia+LIFjRakROr?P zQ|Xw0Pa8o7&W=fw17`+SqepsQ-Os5v3ncD5|N?N(AHH&`>hLY+CLOluJ z_ErpaT49zK(UcdNmQ%iA-`jS`A_1c|$W86{d_T_T2V-HH3xUqpX0QJSH%i>1i>#vK z&y{;5)^pMB=u;&_DEWakQU>j&+opIrBf~2GUh{`kG{|Z&2Z}5dwG}>Y{W_uQHaR$_ zYH%}$c`CGC-FGCetRdQ@RZ2-%ucC_|R?mHzYEnqC%u9zRBH8wx7po`=EVPMpq+hL2 zTdjVhQn$)++17^cn;<3=bxJy0Z$U;i3AqJMPJO&SuieU&0eVX?eLEEI7Av@#PV_ZQ zsa>I>B5HE996O$z6HyJfhEt^aC><@AnzeN`xs@lv>^pPFtcodrcGyqPSB?#C`Piu0 zh5=hAW|OtT9hs*G?7}@*mG_f7ae@-Nz4{qvne66kco^uD$(JbCo2ttqUm-SMy@kx% z!eDt?5>w5)M!E#C!b#Iu9GqyhUs|QoYWHtR{4espRS-LUt=viY2iygF=-j3kcU#uF z{ka2=zsOuLR}s;&PbbrB`zty&NfZpV*Y;~i*W$EH0JOGS&FMS%VK@)f*%OOrcU3P9 zq4zjhMpx}oc`PWtP!o5Bdlp=(A***TZwVwuZbuB1Pibv5uiHvW{PsE-k5IfCgUz~l z0nMeZU0R>(ajoQ0G%Il)z0BgRR*bsdz5NcqJ<)niF6|PUO0i}<4)q>6wx4K(5>Y_I z4$WMkbCOQFs(krBnl zx85i0*7%Zm(&nKNP?AQ}d~6@?D9dO%@}ouN2paSR;zyUqJuw)1SRy=g%o;g(BD|Bh ztnKV(4fcBgDJ~M@%}n-6ow3xOhnC>C^d?PbS(9=TnO)k5p+W;pu2F4eiG7ts zJVL4M(NiZPQDy*9`H>-P0GWY#=UTnh8feiNF}hCs`8^ZDKy;XIL^9K4Ps&y^#DQSE z-?J z@YOQ9NQi>ZP>^ix5K`R07kWj?`R(B?E*OyR1$Vd;8p%2Y2zEYt4CJM~gVX%MO(E1B zzXhsHn~R1ifq9~dtzuH!*3&W;r`D(Sjrc)m#EI%`Car;CMWcU0c+0r?O!)HpjEvyP zb^;pO-Bn6e-+>dS^o{q&8yEH9v}vuXX`W;NPRlwJdX|59`z?~z{pFE!^u{3k{KkJ55^ zD;F0ldy9W*`d5YP|0(E6|K%}9|D^SIq>wO)4^cJ+yCa&xl*3}hpvcQ1eP_k;@>tz= zOZnw)#fxHc81jPcTM#)jgy|0?n0(jd3IPu-lJ&Tm`#F1)o$GTwYp@dlqy-qiHFCHS zKgikMUx|%x=_%B)>n_y^+HvD2=nP`}-G_0A7)I$yc4`tXS-On8qOkNp>Q^$|Ew%Jm zYx34*(*Z3SF}xw$CA?nG9O3ZH7l)@Dp4EyH>8eXDb}AFz)k*T53iA~gRu&e15u@|% z9Rw?69nQOeJhv^^unjd-VGFwbDzf9K{i(U{xxHyM@-aI+0qP{TU0G~w+Fs>taL#Ik z4+92(Z7n%+okd478;__0GkE`&(C`k8h@?UNnM=F%A~2|TKo)q9F<5`s)KwxJRw~k; z4giS~|8AIVG;rde6I^W6m9fliR^7YT*>&x7wv^?xu(5p45n{|2F>x%?9Jq+~Tqo9# zChbeGm@9!(s;uIKae_4h@`~yIj`Tqct+-M>d>~2PCiQ?UmFUioyy&~h_DTBQ--W|q zqA^UaJMTz4tEggQ*_cQ_LA7j7bLyz8#cpGggy;YBVk!%oSdufoh5-FYAQ)v=d$Bi`G$^~ zm!O;En#M9uCykPzLZ5SHa%?hDHP5P;T4HN0L6J*r9DAvC1WWPOrd{*obfr3yJ?Kl3 z^_6dnXRoi4<$Tr!=4mhHg6ig~BatHR zv%ZMJr-`8w_JyFEzUSQdp0HT>|9QQG?IXj$7Rbx4E)%HauDyY!tedHP ztIbq;D)ckd-eirAHOG7icBH23*ApHA@nG*Jdh}~G?L5C^Xw^+nLWG+>hRi&(fnpY5 z?^hj4si6I{m1u^%i_yk$tco}28X8|}g5*tAEZYF37$f(+xT%XvO^`i^Ig}%cydrwF zlpL!xdO->&@q|8MiJrAxt;z2CP*a+EvV`_2& z<1=p{zjhmmYVkpx#RV=#zuy&7^2Trn=H$nT{OBVF*0z|QH!NxBF%gbqT!BEx zKB!SsSUwSo1Zr?kMM%N)@hG=&m`vRQ6QK6=oIvnUI+|C)dGKM@jNwqG2Xi8;YCUHYRh? zbl@DN-za)+0F9kw>Yv=ioL)01uFp7@AVEB0AH-nmB%j$RC_totFy4BKd;OPCMUMBb zu3oUUK`|{AvkM+@KPZD4Tn$(VlQi&aWV*Uf@DO|FQjLOoVw&C@z~Um*h%Ka-C=n4H z@(Lf&MDJXNS{3Hs@J)11(zo9tGp>wS^b9{Q1WN=Ktn>ZieRZS?k`gb7P4n?cl^7^* zG5-oARAG#i<*z`J0ski%;QCLD-T$AbOHq<{KxIb4=QJRn@MGj=ns0WhZX+uX z=oTjz`o-VviMt1mB0W1vA*7oq1ENz{<*-EU)U;r*ODfV!G-?hdnzhM@rRZ=|qaFTN zX*t~$gc-)M7GS{#34R-n`B)eAPfebN46~61R?j^(Pg3TXR1PyQrO7Mf@xf<3VL0`4 zh(i?-SktJu8Oj?KIy4p@%5ZH;P&p5LB8 z^}7P)9h}vUP+1Hd3nNzNcbR`%1>dSZbWhiXe-CcB+s9e)_w<{bypZ(@cQT`P@ch=d zSOPhExgI31MVFPsClEXe>$~qYQ+d}7(!BE*9y%AjQ47BMDt=#>`1ie)|ES{pFFdHa zI)CK`f3x>)DtZnm!f5=e@g;3iK^jf!RU6hpjYu^V#q0uWLuJ-6={Ua3gDi9#*P7;- z`rm*5)n{2QE{UZ01PVy@_9(amogzzOwYcVgp2>LsJ(}hKbX_!ayZ7=U{!p{BHussVj(W z2z3$zu7h$KK<%}P0YBJ+)0unV*xD&6GusXqs=M=Cl&fP@Ttzfq?>H9TW#qDId+C7? zhD;;HOxDJR4dc_xI7-b6N6nZ@bUWueDk<_9Rju2I*o(i)M0&~%C^ zc)a<25M<^NrsjAccydV2HJu_-1W>b;xrB~Mi@c7FrW-94$-GnKXvF7( zA68!d!gkIo8(URS{(u{zRtrF}B$9@*)KH9POqOW-B$za4Sg-A&PM*on$>$o#L7pH~ z&YW8oJX3T!!@2r4Rr6ac0ZDbtB1b5yc$5}7oZSDvGF0FWTpZ#r7@GfM^MmC-p{9Qj z_JmmlTxO(^(NHqBc$ECU$jQp^;)%xnyr$qvNTd`R@j$8JppDCGQAHQ7?fja9McCUZ^;``VW$1+G#=<;K{_OfH- z_$fp~S3K`;jPNNZnkB@=DFQy3{6+Bq9nOf3~dr4q8zD_t{P4-^%<4kj!U z0aj`=#@G*w?!4fpM? z8Pwb15(Ka*TtDN-2aWK>*hh{R_C}*e*vSTkHdM(ETM!JrJ=1h?(_WL}2p#QXjrKZ_ z0k_yu^;~)#*r>sQP7d_4VBRvWJCzw#TxA{*hktwQI3ST{8{>3$KHJIgMGK6I!d}Q zinmfq&RLRxX8P)_@@vVr0gPu7*)uU<%xS{|Eg;*w1}2=C&?7B zSX?OLt-gZO+<4@tLeF+K0~*|xwMD__KxWgGfsUpj)KyeCM3J-f*uxe|xk;Dlqq%1< zL(PaY@U(>Z#k!C!B45JlmE^~wHSH;r1c^kWTG9_VT~1LN6$a6Yg@kNF?&b0hs+5Dw=0j zR(wcEYmdfgojx+Hzu89*C}4$I7^?^vYKhF(`>=MC)VeeFR}}?j#XeLnp8OhW9%9ND zt6utD8DHnQj5@YJv+$USdN{8apQir2)Z{8_s!BABmG2O#pz5lSh|gf#CI8X4I|U4g zhQwk=VEV+j+-KNxuIk96Bi%^(Sf9}A7o$zHJ5mV~)qP))QQY&^>9}z9z9)PWpw>8T z7#NWNEtnUoUl{DP5(lmy<3;tpLJ3hG|;CGB`3**uH0tf9>;7w;Aq9SRVg1FDpI5y~rY#B|eCNpAXD z9692@_%$t2^nu&4lU~(~_iVf|Cs|mXs-xKlY$-~FZB$!oDK#)JgHZCG)ySDURM=@(i zCpd{Er89|l&)(&5>L6LuWY3yC6)`jPz(Po8pY=AYIBnx3y2Qx6*sT42mpR$zwx!!< zHHCc~tbF^-bje?bo#~Q59Dmw_-VcliCn^FfI*EV)U1NkNA`6Cm=^%j`%M?1Zxa=1U zn#DPNc32&XHHfUfmPx*J+3_GA&g-_pd#wO=Q^5bdhzmm)>s@yO0q|>ROV(hkhJWf@ zqWjI#+9Wx%C+!kp&kxX|XPS5m9CBC&3r>}SwdFd#YF_W78A*CN6mFC)qzOjM);Z&v z#MjdXXMw63v*tbvY+$tDmuHNFunOlRM#qe|eV&|$98!xy{n)-=N?lrkr0_}U^sz|x zs0y);(2Dooa;(9zHzRi=I{GSVcv!6jl%ck@)>JODfR? z%aI)0HvbhzY9K7eYsntq#JvWzj$WCuoyGoPY7;LSPfZlFiWU)X?(-p}s4FXQcpIp00;%Jv;k0t@2vBu4i;rh-?{z}cHTLL9Rz zT8r(1Ws*H~EyH+adP$cGv|7HkeS9p6eOEI*`idH3twkEJ*72|ey4JgISglGV0Vo@qe#)f-=|g%l$S&Onwl@mmdn|sjXXYaQ4MlfzjiK1* zY&hWQyc9?G2}2s1fYnQ}LXpq{!&Kr97d?=a?_xXAU0SXrZE?T+=9os2*v9%Csph*M zW{}m4+PIRmHEI;<=c5$PMrfg#MTs);4Tb_0**o}*cimSWRcxo(;G&&NV+-?W7v*%4ACG#t5J zQP=$g-(mN*;B6s)d9JNkF0#Zz_WA>J;{=2a!IJsiqCV!YLjJ(wUJ`3b$>qcZ!HjDT z2xm;fMSbtJ|3o~tc!jJ+U8a)vX@NcxU8y#u!Puq%R~{sps0msRFO2!GM4}786S7* zxgNmf{q@|Sdnf6_he>gEGX7Hn)uih5nL&&t4`O{?V;;bdl1U~9RAnjNmt~1UPC3mh zrR8ZtHzz1(yOYSK$OjKf;InJ+7mH$WfqI^OG3dhA+S!YmIgRv>2H78?<6A=~%E{ug^P+^b*+f=j32&Nv&Ypq?DcH&Busg^AUDE|p; z8(tQxZs1+0gUX<5~Ah zT0cGckI5%nM~d`uaMJ$o%2bt^##I0UdaQ2>-bpsP4P1Vk8r7EOSr+a!D*Z4shiKFL z35Lvs^i;#;G{%ksUUo8(Nj2DY?u5->J8kqS_#{B`HqS(UkzR|K5&6XI_#FH4?$ znMXeTb$nmr1`|{n*#5H1T%vtU4-H)vrtAchme!ZG#@c+Hrf4uxx$;VU(Dr~N-ich4 zMKpdwot^bPY#kBILFgi?i3W_kV%vn2J+%R5x}TL8I?B~o#VXlmr?i=y`yJi-><;X* zPCDrsU51x;mkr+t18lPs=6)r^gEh2$saaA!qv_< zKQP13J}ptHaUjT_(*x+P}wfV-}57aU3rp#3AB&~e3%y}0ju#22u5@mUIT!GA{* zd%-e2DTmr#$(P6^$&N0oCgR)F9IPR~!Q!x6YI*7dx6LR6n8tj(#1~!0rofeMtT#g* zW%-p@V09>&o>iz0j66K^soJWg(o9#T(8Xx-P3?;J|t~nIDSGPq(?-B zOoNnc5HZhsW(m6!J+yj~kjmjV6GKvhO>%^v5`O2I@4B$Z!~DgelYWdC4P>YfmI$TR zq`atDEhIt5ua)PS;Yz1`FX@3Na6j^uBx_rNKTmgboWGwE6O5;iQiN6Q8>ZX%ApVJS zTEf6oj=@?7klS(JaijG|(gO@dTgxB3#H)4&?+@VWkTc)dl;qK|uv;WRI*cG2`6PiF z4+svy+Bfn&Fs57Jz6i!C(w$w@VWPAbRGak~oN>3vUg|Mmk0NpfURt0*DSJ_e*Gi8I zqshW4F}L&aS8x~4*#{4vOc`gKW99cx*L^69fgPj#?++q9LidItd}<@&#E{ZGz7g|c zFX$uKJ;Qv^NpN*e&EL;l@1br8j8oxO3e`g<911L_jr~Xb0)t$x$A~dFay9(}gt4&L zyb=1<`|)_7(!^xJ14xLBGKXO3`R^_;F01 zG70TiF<5(=pRsJYj!^XjLl_vFJOQPhN#Pkr#G0-m#xG>q)GAHjE4WFhe7Zi83;gte zdDv6+)qrgh3F0}$gPmtb9-Ff1m|xDD$6jX)Dcd5Ms-(@nKM_3)2+hfh6@Cs@-=%Z_ zIinf|ck6rN{EOadGmJ-rzvxZnAL)(mf108HL2v&m)%=a*?3CnX2ZfOQY?ha_11m@UzRqlkhrVbQ@0M(tSSTerx}IH@Dn2={w$iGqU#`v}PuV7I&A9JYNP%sqMn z1bTq*Ok{V>SlVH8H*4X-lO?VzaDQzAaLvc1tTL+To)YOuj^V8mQ?)K-FT(s_!ds-O zeb$rKRR-~g^+_aiGtH6kbJ)!K^ie;ipJ8e;>iy2}73i(1RY-~!(tk2zPj;pwB4k1a zVa~7lF^EE`UH=#eb**88zBH%!WkO0S?_Zu0KpRtXN+XMsAwfT56IZI}&cs+R5N~p3 zlQH7o$(zsQQBPIRmD)i>TfdcgCSKbVVD;VCmO3l1VNbV&rWc9o>Pk>ex!)Nap%NtP z&kKIFMm@k9-HeXj2$((SmG+a-dXvl7q(7n=8)cELHf!@Le+X)=++(}pKC*dcns?>G zVa*fV{2FDIJNaK_jq)WE9MvxiTm6sI%YUn|S=oP0Z`vE#GMZa`4V5byxmv0@8@Zb~ zyBOJuTAG>Im^uIL@!ZrWJy6xL{%n;pEwY87Y^xYSfmmgRcgcEDfz4TJ#{;n|g>8(> zv$(RLnp4oD1Mj>H@ar|0RCy}E{GwvuKOf1FS}O&z-Q)MmCVEK{p~b2xFj@lTn}#s4xg7h+r;n$TZDlT2AXAv z7R^$J?R|*xL^>7HI}e>7{HszA#Y_e8=~8*3zy_J$ejuhByeI0I!w-&%MW7Q-FGMKU z8qPm&IdU3w#^#`d%Vcn&q^w;EEr|w2F@ax^`R;a@p>l`U-T%~f&^`#zG}qdSV)A<0 z^*U=#=#o&gd{o+*s#j$xf+2y^t1Wj9_h}(DNi^aK#jI}z)v1rk-H)gocbgc`wB*?$ zfg~22r!^VEN+n>U8|3{Ebe#!9k|dF8lV*9c&9H~&g|$Ymc-2O^j9w$Q^I)ldd}5zv zQkBFDS2TxDn`p}-{-`br?tUCgyfr0Wbf3QeATbp=9sN|e90U^eVOu0~VT$1A5))@C zPcwzUn7bP^Gd~hLA@8EwiklMmlc^(;uPE%tLecC-iZ$_~jNJnZYn1A%r}=VE(-LG; znh6Q+b;zKz_N7)0SH7t~u#)e>Pr194w7xp;V&CpmJw5j6zBO%yB zjVf*iveYaWlrE~+p8YYym=-QmTd_F!`)ATishn6(oD}hTE2AqnVPF_os`ca^ET@@Z zoo~4YJASOBn<;8#(#3G>n1E)&@JA^3LV7mK^kaJ$((~ASWup3G(%#8O%xFX8XSiN~ zUF0&gDyT`FzIjtA`<-+9RXEKbwu%RtcrG!#-aoN0aj)i z(G|=#b_!z{o1}cIyw#n=j~Ac|NnR@<-CW$c%JFBFTi5JW0BX#4k2o2w{L0EglSN7E zFUcmFVF&U6NBA7!t`Lut>faDk>pW>Lz9BSzsqWvnI<+L#wg=zw+aeL6=70S773#Rq zG@fVM9=1ZibB`>L>hKz>rHG}`pX;dZD>I!_x~u>jsx3;0d$`Q%t7d<8^lkl8w0WZ3 z(HGiok6h^#G2EzIH}G*;!U8FW>@|C+wE+z{@e{wwWEkzUEiT0aDJo2JwZR{zcX$Bz ze2pzE&vKCc6@vE*GIv1LZ=qSg~HR)Jf|ljt#^m2hZF4z|32*7{hd|u`C7{C zjG>}`{SC3Dnc~5%D4yBa!V@}xSBtQ$ZWY^qs3)9jTuIXYMgPF5E0*&A0B(=JEntcVgC%ZO4UKHyuzuSblKNHWJ}OzVpeS z?8|{P8FtkJ=~%YMf1h*@o-YsZkLVQU!43cY~nWEmBt#&Ar%7WClZK8 zSe-!M)B8((tj^wSIm3?e5oe&mQs6BAE#Y7K*^boU^Z#aITL%-H zul5Gx*FKM}n~RnE*Ko3}nXrk8nTw0Ok-d?{|KMda<$n9cFHzkfb4wa&Dp0x>XjayP zg-KZ^Ayey*gb`NecHls@$a-2|Z!Xe^@P`uYYo`Q*jKzDQGPFf^GDQ5rd(-X3n)&f|bD>?`-DktKL<0hWK!cPS>L^@|VH6## zG*0#NtGfzpZpt+e{yL@K$|Lg*JfO%I+hp&kR;NxOJ+y2H49xZA7=^RKObPZi6 zL&R70!l_{PTFcxI#h+WsO^Y<`hE*z1vg9n7nG-6n0xBU8F8yDd}=?${Kl$qim3(S98@^W*vvSs{l zU}!oUIXap-i#nT`er(?avm4Q4-snuM&-cwu#-M{K8n;l1gP$ z3sw?`ls1z%eb%&mNBvLuEci8}-Q`|kUw6;F0-pHb?+A)+BLSn7_@my}6u%J=Ub~(* zU1n~wcfO|73IBZF;|Bhy$0FeO^>lmmZz?ZuZC8$p6<>B{Lsp-*mS05IVU00ergKWv z(LIsLS=?(>QLLQQ?bdTpyO?iiEL`;>(XJw^lA*7FCd|$g@c3VRy#tUf-Lfs*_HNs@ zZQC|>+qT`k+qP}nwz1o`ZNC1_y*J{2=fCentcZ%LwW?M`<;L&dcdwa@4GT@LCkltq=Xfy+OasOLT!lXrqy` zEW9YuDcfQtJ$oJ|Ln|b|q*_a|YPgCbBBfQ|5;-1(P3R`sK~3T`TtVV6yrtDbioJKI zPDV1BAaj#O~V^ll>$# zNC?nv_r5RiH^A2t<)qzcvns9Qd$_UU$`jN;KUSNqMCQiCFCi3A$*D#(v=FXCqz$SB zyC8vjHyJhMy$5kCi}FBy0NdSCJa6{q(|*9I^zwX1NHX*dHOIDB8bsI3_{(*-kkQV@ng|lWd*nWx!(xQ1stGMcRDjH=YUQvY2^uCZuO%-0Jw5az*F1nW_|h zR~z5DT4j&Z7527|#z9b}pmRW}p^|OrU(TWox^&Kn>YUn%%JlZJ^16vzy|O|GnZsf3 zSXEMjOhuYZlh*ikE0&zHt5va@6&GI{1&D+NPop@Tss&f!V4;}nqX@iOvdonoDa}J_ zE-u%qrrUpYVYSGU5NeXJr?#B#3dkObD8uk*U|u*zS;T2YgAk;_kdF0s4A6A*YGO4)#dKwYLQi+*i=C3N85d93 zAe#Lng7EX?@}-FPvIdp0y!`J@^1tg|IHwZ=C-i6LW7u!d>#==7<(?=6?caFCo;)AM zwwV6XHIU7}%D3 z75#&7SiVq=f6k4N*gy{?o~K9`+fsId8Co*62ksPHLm=SB>G)@44I(Fbs1stfE==|e z5WM)k7Hs~OwT#*$%<~0|BEb_6HV0F0=kYy;P zdAZbN(@{*9FL}4bSi-&#J^2;N`G{J?KFD@i^8BEXQq3$Q#~shvw_cx5r%ZlgHz2&Y z*cU<9UD1(G6qg=Yx{LRix``xh^Yi7@j|r7hm00t{(0ei78ZQbt`JV={$XlXvX91YH zxbI<;-YQG@9xrY>Ar~yWklR>hQ-X6TUxD-S!;~b9lu;Tu@f59S=euifnkTO2C*G;S z@TJZ5{$VG<^ThBbq_74=9q9r7DxC6VBngr@olJ}~W87-NEagn(;M*)7Oj2!(TG+}U zsLu!TV4B7DH{}gtanAHawLkpH5_$jk$0~;0`rM1Hjkl;4D-KsjXTl<*z|E`_8Nlb6 zroi&vNu(socja8wZ}9J>;D}esqgs4BR?_u7ZyELz2k%GQjtG%Vx+yeS&QI*AK1Q~e z;1-8)WjT?WqB>et(n%42u5UPI+!F^B7Hx#oW{i;??}{9#vpvk}lwvHPB$=-+pnIAL zGBd3sTO%TRGFw?`Nh>DzU#VeO7C?`w!-QT4ZgBE!WsS1clJ&i=m$ zHn^;?BNx^_wESMCsSKfxi542WFvUJUh%GpT-JP-b+D|wh`H$h4?*AT6uKyK)=>%&^oOXr5Al10+ld z9x<66pEk?hlV|$s!otJ~_Kz3DcB~XFzWq<@HMwvNFc2}VQuS$6g{U$+nN4G0`E zua0)-H1D8k;mm6E{(!pNomCz*qxv$pI3NvG>(+Q4AcJvK#K8 zb9SOKS@GC!pN|JW#<}*37GFj>D1wi~_)k#-N5izNy0%(q7hMm?oL_Ju8jMFGA9bKb zv$!gbC9lC0>Unx?+*3GF(6ZZH<(4j|5-Om02Y2z2IG_&xn+2Z`6;N1An(~^lQwwUQ zOiKj)?fuj7EGlb8nv@wDs4us&o=Bt%l*TAhB{h=R+Pddpm83-ms{V0T&ofYt=D7dS=Kr=V{~wzR|1=j_+3Fh+3mcp0J6k#Z&$+yVt*OJ$s$BYK zRx!5u|IH#%N;9@dV#r@$o(;Dy3GBon{2-)SK+R!>`0yL(nq~lFeelQy_)_BZt2i}m z8rSXb0|MpaMQpG<_IaUCD@=+=`KtLmC}H1)-vV;8Y!fw&`K2B6oou$QOj%XL`Ye$dX*5~GV? zjoCc8{4m*B_lFn=K@#mp@(*Vga>;sjA3Ds|(a_aGGbuFi)9-z>)&hY^h=PM>jvvAt z$Q7Zfbr%lPeu2OFHW3uNyavs`ezAXnB`OuCGx+U1e%!gwF?S3T3XLaG+BzOfiLB-f zLsTI!R2nT{#3)Z+EHpqiKXE$CK-~2S!*Tvgi)l{*o7SZiuHQf&N=jK$gt6|+nF)`Gm z!Txq?dNfctW^}=z-436nDud8w974=Iuf~cqED93ykXqf1w8FZK9fiO>iyHhGH6`Xa zy99CYP)x3@)FSqPdVt-Br1$H%x6;EwpuBzZ?#_D^RUI0KPMzf^_Q2rPhK)0jFB8Xm zlV*;2seylEHqM|s4!E5>k-zx$17R0R2*LcwM(ea^%K>Rf92id$mc6SChy+Lhh?+zh zvO6({dx7GOFjsuW1#TIks9C3Y1NS^K;IL#Bmt5WRAnNcc>QhlO{Vj2vmon)s*asQd z33&IEDekAAXHibwHHW4Kjin6FB;UgbL))#+*%fRgjq!Uy)J$xt^A4P* z=wpGU$DPMXW)DL%DW!nu39E+G5tKB@YM$r#?rOf~PwEaIWOZ?-rZteokPGZsqWYS4;B z|0LjjIbp)2Q9#;HApIi0rAAv&MKYgXU3KhsoOYe|YT)zr{({<}EXL67@nFgE$g8n) zlwsHK7H3m?1l)9j7MVEeKIFU&$Urel=||l_I+%2%vpEWGJ4%Ae=4~9emV-GN((dey zu%{X&7)-JZ@$2L0Yqtni7;-H%fWs%8= z=kT2S6oOA<-_q!hTShh=6tYB`my{cf^+Lx>yzS~3hAy^=8Fn4^M9*a;F$7-pPb`5WTTi>BH<(hQt<2d>L}bEO@qeR~R5CV6M#}U~hOs$t?sI z7o&N-naKA!$TJ z>&^XTo(>zGjv|b*XTI$ut5?7&&KtRH*Xif1`>gBEp7*Joo(B{{&6%EYr?;2euFLC6 zyxINGDCvA&Z9Ke6+p?I9Q!BMcUI`b0h}(?yqWH@VsM zQOR!?^5j*fLK3_B=$34i3+r{u7IgD)M~W2q7y3L-307k;BupXtBuqlRxD3=-rhwa9 z?bS^@iS*Hnd^;p2cOp}nC~VDSN?;3$3z!yI^$)`1W?UAhtCjjqn>M&ph0;8EaiL{z zu|C4KQm1Ko&6~iXk*x&^ph_a+*qDsevtmcT;T0k>1Tvc@2_|YU#phijBjGm~(FAS> zlUlF>J!lV+cX^mbgNt|q+%c)}o#I2L8tL)BII4PpHABevx1oqq4Fk=enLf)lPJppehzt;iO9UQ2qK{ycJZ}25$Em8#QCj@IGeY)Ih;t1C_j5#Indn9> z?q%Mr*&t<`FGYDnXUw!Q9F(&(vc=j2NyA|}`{O%(aBk4&ic|F*CyG^zcJTh7Jbkku znj-MdZ0aPz3?=kXncCW=-<;dP;J9T1y-C;{aJj^)J(P2N6H-0wO?ZvS=U!GHKVCK< z=aWv?u%5>H&8MwXa49`eLmGW<%;nt}*#2=)K*`axE(dLvH|fGa6F34#8tRY?cr_y0 ze3Ys0rp;JgADiP65s|!r+v;Bhhv}`Vm{n>M24Hc%zOJ&UhG2A;(vSJbsM4>fU{u2_ z-6VIhEcV`qxROML_k8tmxBr)-{ z0Nki4Ka!>@`U^UZ)eJ*+dVEKh%hU52puWKbEG44AD>zWsBPQobQCa)OTlz41wS`U5 zA(_e!#MIkQ_D?<^L@2G~TpSiQGc{2i*D?M}9=ed6<%52)rPN_&_Zz}kJyQ*xrss+n z+*}R)Uzw_8MN}8>Nin$jkrHrz;R3n*HT*JD&M9fIRS?wRHq#A#i(f4q5+z;_5Ij)k z55fi>(u^$A=GCiS!o_k6hWVWf;@9>(C^LB-^lw%JYn+7v`}UC04jw=#dbI?>PxGb< z^hYM;a|^$Xv8HwRyEFBlC0EGDeVFD zsI=F15ChE=aHP6tL~Ao9#WHh`H@ZcicgWiJi5Wg12JkaFg6%fLuw^#2^+FGSBYJC) zcLQaBfXhJJeIf<*h>U>kVP9*cRCfKc<$@qO~wd*)<>-)SK6P zJ@I^4#us1Hf$yt#&=?VaIkhDY^^W;!&OFd#L5S3wEK(42b#OVRSI3Yn=DLC>djb3m zOx*FMX7ymI4;B56>=L7Cv?Opmx_j#kUAIX{b-S2c8Z$v=gOMvo?-ij^Qg7+-IsiMdRFM)v7G{O9O zb{zD!lmDA*H)}70ZFQ4xTkLM$F*jknM@CK!9fA;1rEyA1T;kT|rRhl7MQ@3Z8K3<$ zthbXo^c6w1sy3usEhrD|+wtJ{DqW>!SzzMAYG&n5P_48!FI7^!mt^UsJ=Ii%VFz|f zC`{_0n8zVxPB%8P&U9wpG3=awF3lq(pY)ZY+X0iPX>u?nXvOVKqHlZ!kPr!p?==9sB_~DS`Wz) z-C{l?ZU7>v`xhem*b=STWhZXwe7a@WUN>CeYu(sj2^yMe+X__p(O0XKfx z%AXEQxVFsfTzy)ozm#eCQhr*;4iF$jVCn@40VgXeH%1E z29UQ3y$aVZ3TOp-E~*g`Gz^slv`Lf|RO$MFBa@P)tKRuI=cc?XxIqzmXgmw~OWv_3 z79M~sk*g{jtNxD4ShkFGO@d3`N{)-(L`+B$P3o{T)|L%BE`c71nj=koezdtBY4~a%t^5r3-m!3Kj%V`9dB?v%w?BxOI$&~!jUNWa z@o8Q~I6n%f3*aDLLYK<|4FU2X@*``7jnlDRq5+VebLwb4vJVL_1XDYFTUc;$dW3relP0}p?81NZ&{!uRJU{&9)O%uEL4Mkts~ z&T=;)Kjl_c^Tc3YX*8y9Lb`*cpyU^wFHkn{Z--k1SA~|n0bO2_YwyEVv91paW(>>D z5A?fn$`0!!94mEWTUFmE5+yocu&wZDj;aE3+jOFJ95*T%`pKWaqKNiaixt!T^#`@p zHlA$6Fj^5&7!Hb19 zHyE9zQWe<12XmH)8IDIOtwPeM zHRd&LKn-qMRQRtyy5LYzR9#*8JDBD2K-E^^INa=#S{XA+rW5XKtg>7Nn^Of&Vhir! z+P>KycTUF|e~Hw_vAX%ap<+u9o9)jcAVaw~|4zkmS zZa8>nl~i|D8zjQ^%<{;ZR6cbVD>%?nlBzUD&(9h}VOpBkVW!AuVW!MGuz;OfTWE_| z{yi!0mE#74$DH%4$iv357s-5PS(g3aXJUS?=I-+Jz4Y{Czu2{VMepL1!wV0l8b0k) zSH~&|HJ~YYm{WKY&gKO*WNzB=l|JE3C?T`VIh$Fi$wHFx68QWYRy%ziF%z4Zc<{>B zjkGSyv*i{+F*O@tKQ!EDM%7xw!z{Yx)~Woo$kr{Z7+t7ve;X$MoE{R-LVe22TZY;% zOIFYRqSw}4;Mcno^z?O*G8Q`&wbgNV%>E*DX{fnqK*lP#K0dvcU3endLW%GugLOH< z>Y{oG#ECe$UPvO#$t@?@GA5JFE*6oY@?+$jRxnx(BiZ8q{AuRkwymR+;{*D6-bh*) z-5@PC8lo`?K**Ec9*n$U>OJRjK0H$J@vnMoQZa4ti zMegzJ2oft=1Y+aEG$4JE9{t_I{tH*SwKVixk$IyL|hvQq*qu&_4C6X zp>36)v+qAXl|OfXL8koN-RrhNjjA36)N;pjmTkOO>jg}c>35j<2gH)fb7QYv#8VV2-AXJ1-O{Vpi$uIz3lMp3dl`?Wwpp>|6_$}|ROmbQ- z+O3VID2pdMNR%dc(_#%+-P-%bNIb5Irk&d>rOY(_mq8%P;dkWuH0mR4vhl=r?rV5g z%=n2Yz2%@f5#I6!(KxF>D%1-3IyJU|VW-!(l$}cWBQtobb>#9D+>HlD>@kp+qgiCj zU_Y+2nP+9m^gw~vIRygs?R~aXBZ*Vk8cFZj_&b8(pTaY{Y}cTT z*fRuKeL3=89rk16#2TNQ%KL}Ryx)%5M0MHy=A(uL9M*f_;^wBL-FO~J+@|(7I)GQF zGxu8y$fzRDE)xoI0MCR3S^FKd3Mzir$&35HZu)9V$~5*Kk^r{%vt!7ISD#%fswRS1 z7x8ugQ&u(usOPXbN5Z5URhEFc|NLc;g}f4JzVjlUxu&$T#yH-Omy4s=$~b=B<)v}= z;R7RHY}oe#TExRVjM2_)jF*Q3%G{)3ZZqgSTa^}wnjk_InITrx)tW> zN_A5pLZ9CogVv`5^1_9Jm_n4I&Od-1kC6YSPp-Oxyt0!D zIplg&zC_?4NKvoQui_?BUY3EYOP5n0W0#hYf21a%4Fg1xeEs;w-CE2d_X6pd9A`2e zuiIRY)}Lqe0J(eXdpq{`UG}5w@h=I2qwDlnybY&n3-F)3(mWK*z~Y1=sqQ352UCF4 zQlI=T^y5Lp>gG~>1T94`()}Z4=w<|*zIWTL=+#(!PT$k6nPOoI-RVk#s?iWB=$tTc z;v`#9_oLoCy7W1j8Mn^hfr?}kDKcERb3jxH4>hafqve(?N%m6{o48;*Aj`VQb5)Ul zHK-31_Fm*+OH8EXSzh8{$7fljqN=ahTv<75(Rp-SR$Zz#EMGFOcXfT5%J^HHx8x@r zP2)nIWHes~>%OVy%4>O3(0{X?N*ukyQv5>kKb>M|32-D&p%1(V8j7s?3w|Lp63nOV z937ts^a~AioVI92W$?353}~XMK~{A}5JkKH5b=n9Ciq@IDBAB;Z!IUAV+ciiDvH*j zMD^3Dk+a${QM5$azio{#f^OHOx>LnJ+5kbRm4^N`5ii4(4>XD|b?3s1jrWv1Z}MFy zT9v+!?Ds9SiLUpcRnr?JG+C=^SKkC=BwXt~F8Tyir)=)czcAl$Z)2R5pR!H;e=OVl z8*}D=$~ONscK(|=^G~^sSitaqkw<2U?vov$hY7)fa=I8~62|7IuK10w(qZq9BnSjK zt$S9yI^QU{77(-&cteiu27n8-8*tNC&-dMPS#upD2hi$Q=J$O0#Os?xwTN{WtSzZC zp0+5nsTrDO-C3RykP7Y)6z8U{uiQ@973Pg|STBrbPO4R4VU>jA3ZJD%OK)mD`u%Bq zjUA|-$B9L(11X}nY*naJ%@8ESe`WsFWU8vR= z2;2}9@)$?_zbc_riw26%Kg!e8Kd<=z-OEDxpIr0*^LqcyFQ+uzy_6rD_)MF*+Au)L zK+sV!gc8RX!}1A93BeHY86igj>{s@tCS@2Inb@Wg|3Ir$G(TxPHZ`*>y-_zsskEEv zlcqu`YL%;Yn6XuOyEIg6vQ;HLymz>grb&Gw(*q#A5?6USh=@|D2=%(`I*cmsk7f^9^}}P? z?OW5EW$5ivagZURMyiQ!)dSTd0?Cq6Pu{r&OKRfiuu+&nj(M|bhppFk4ze_}sSz1;);PvKNiaE=q^G|5w^Vy2SN zBs0Xts91C^d0dq<=JmXesd8D;1K5UvF9?WTYl6d%lJqXxN`Pj}5LxPgSRE$%)Se9Nn;^;MLmXCiH$)23AiNRlj3 zB5S`@U11=y{xj(rqgS3zSUD^dhUILAwb|IZt>UN#gv=Rm63ig{MK*6HQPQQC{?1ODO*flB7}Q(AO3hFI}(g&O+0tS_v* zssss=fjAF6c7M%h{bJFcbm>-<=R>Xa4X{qGb3|a97zk+R8pO+p(k2^QM<;%(sz0y~ zRB?%#!Lct8vXEtAzqvF2#xo$NsieLB9TCSs^E_?X{@2BD7<@uv#vvJzQhJD^v3!dT zl|$vIA|g+p5nMz|Au5{UAyp|$2kfI)S~hhN0%yOnr(#(o-&bKg$Y+VeF{*sx3Du~N znZWwrE{QHx{GA?2J*uLTQ+AKA)Nbt+N2AXvftlF`pev3SOJ$4`MSDf=HiGkA5i0UO zd~$T7PLbVXMt2^U57wmD5}@X1U>&QO#B&jZ0J18_+exP+Z@5Me9xd0Jbq&L^e7(>X zNNZ(5fx4(0i?cEE=!j+2!b@EfJXIo&j};GwfS*019h#N=Yt|*|0J4`!D5 zN_q7;3^d-)FNmK&7&H^rwGK+yh}q{Hpt?|PFC?Fm#mlG5xknmlrQ>IgB05c3KF~=a zh6K*nAvP~CiOXlXY$wlxYQ8_)WN;>NeiQS5Mb-&Nuox?GER-8$-`li(QhmzUy}Keq zW@+_RPM`C|bx|r{2{VLpv4kQKehI>QOprT%3zknCxVb_F`5u!3W#trOn>06Z6D*XH z=M)M2!jWK4RGLfuttE%E2P@F6hVZljI&jmjn43^ zPJ~{D)br75_H1XB8(ej-Emk3-$#Qk8x9>hEB<9vjxJQ=EG&)&*v=3TD&pvVnxeR-) z?Lb+YlOky39f%jYERz8;%h7@zQH?O%8>!r^nUZ(>IPqq+lbCHA8Ax24#IZ@dwzGe_ zNr{+ocSoD-L2*Xdg%@t^OiJbgq#@1W&4(>T_SLJKpM5HrJSQaRRfbG&uyI9+T~>My zyWR{C12~~%bhg$$vJk%xRx<*^v~v)B^3%hV33i~-tUvA5Sfb|5i=rmc9n>)2!GqKa z^P&<_F>DtK$|77CJ5xuKX-Q%!OtxP3n%EsDQrn82M%6F*?l55XtzSVcMPQG0ZuQjl zmq*Ic&aackwk$S6PqbQ!TT;VJDSX~x&h0RoXfrD8&a{@qUZfVn6$ilU9V(GVzCpk^ zP$Zf;Ui%dnVGK2;ueF6kZ zFhW{mY7j^Tftei%owFtP`AO&4M?tOT( z;Htw$hS6rDA9#f<0l{2DA~U)NOfScqg!^m^q#5Caibizsnh)JfGIIAiSiC=S%J|_X-AWeS|ich7A5v3!>zaS0qG@+}6 zF+61ADkXR}zFbZ1mX?PdOp=@C9DI^|;2Tz^0qedK3>_4z?WYMY85qL(rt=Zq14q`G zmX)L~hGa0K_F1zeK5O`YjYkt&x-#C=rX%}-v%xC}Z95zssU#Mk{YR8Je z@U4Wha=tl!xo6aPg=VsfWT-Uw*s!bATd!Jrcam6JES#?b>09?3j3HtW9zjdZo{@vm z;Qsw!K~TU*LK!uvRJbS;OkNH2Wt%Y^x3I4&v!zodO!!r6#`%hm7yl~tBXG|sE%(t= zztYj^vC$ivB^+7S$l7s@do8-L_omu&g;hi4Q7^#p%DB);DAqKLC_yf{M--fbVCW4Q zpLSAJpyR=Jw|FpZ7!OY9&`o&H;FE5C-006%H7z?V^+c?EUl19l4m+%pxM%W-d$e~- zt(|&Ex@CFK^ihfbnmM|@OUuO+x=YOaa6Up`MZSv=z+ zj&v;Xfs>|(JoZyyf*n#2H&qEvkEBqz1th01TIY?cy1siJEZd%upf04|88q_e^UcqIJI$qO^tX{0Q=;ytn*d0;d>W zpbMg2hvsXQ_P18QOkwPq?4dM+V|(uRBPZ<<$bpw08v0vS$9$VUpbm=Fv(IMqMe~ij zM>0rOq>iZMoC}d%y?jB;97(AMLyv&6Zzi(5LIvB?<#Ywf0)mZ_~Rdangdl z&@8jcCHuwoEo63_;{rqY2HFx=n@YZylX9a} zl&P9Yv{)Lgc|b3Q1o2l|SANshLidoYfmF5?I`bsF`E$9kGP};}K?$qva#L^~CH` z!TFGfb4WF(Bq_ENC#V_OREgx>tR!Qa(Jg2?b%7g;M5AE-&>&(JHfZkcmN2s4eJeN!nCrcl9Way`gTk=o|nGo|BD1pGHLvB0ih$H-WM^@K##RBrgEQ`4$CSNzg z8QjInTy|bpvXE2PqeM9*$mGvZ!Ps7Fn?$@*V_0OIlsGq$7xq#m0A&oC)8WX5OB{I{& z&m4D92ULj=J&5P>4A>lRn(KPS@|aiq-&TfHnOC`uYpkgbZ!za!sgrKX&HmC&DR$Qw znLUwmqe#(ab!;OBsne)NG--Cm>qV#<+25uf(vCyt?AGIMoJse#4t}n3bFn42(girok)X zsLlF0m3f3uPV@^VjN3J zs7vW$dREOUH=t;vnxK-_6qp*ejG&zM*m*>v9wu&xniWe@+eJ-67VZtoVET-b0X5{6 zr(c*Y=7z@KB`=B#zMR8)M_(&sn@t?LtNkyD`lrk0nJapT+`Ued`PVEyOY{v7f2Alh zxP{mY>C3kmqt~@Sx9=weAH3PUD&9e;-4Z?DM%u2JrA~7?nOo3Fg!@?ilHRb~Q9Vh0 zS~k)vttP$Xy9A>{?$-j{oKIM^!~^qOk9nFfO9U;uX<{Z}MGPU&T0}pPw4d7EHF*^c z(1Qo888T#p5hW(|Q-(yg#r6vVzhg0gpd>56bb9oH0wu}%3M)p2fxFLEy>QG4R_-h8 zU+Al?!eBv?3%sHzLA?4>j0E@%7$S|RYf_S$ylY+ z4n%*ot_mG#p83HvVERPUjJRH!Ay-9T%yQe2biJr+b%|?XeE(`??bZyWEqp{h5`F<$ z|26&q>X&o$0crC>TI-zNN~}*w7-kFnefLs z2fQs{{%-wM-9ryBgJ*Iuv&{5yuKy+Eoc^si>??Jju|gyAn_Uf`ajXB1%g`EBtwiQ1 zx^awk%lc*V?-yf2mx&<2oHk?3d{TaxpMu&Sc>d+t2h>+*DNg;iw%P+Pbq56MHt1{8 zuC!j;1YlpBL2hXi-rks7|L=db0Mz7?nWiEF08stMZRP$Sn6!kAqm#as74d%`|J5u1 zZ`hY{-1iNNl z1=2bj@r1^~3~TeQTAAId%fY2ha|!FRU6VMpiAkkk@VViqVwhBxz8SBI0v70InyyD6 z3Bn|Jj3nVomoatTh{xa7jx;yvi_UnW_#l*M<|9E)rOc4j#iVycL>cKHTtp3#k-nKL z+7?|mS#aSINetxl?nE8)%Zyk>!C1k`<{`huyPwZD2`YbK4!99|Okznl56^r1}88nU&cpyn*~f zRP2FGaX0@#FpvKuii!WfqnQ6~#DBA2l_uoxjK6W&?wmdns)%IKg2?m;9KE4d3H+J4 z{P-@21_oU4WQ76zv4`7rf2c8VBqkLlTWX8sn;VP7*r9$|Zvr<123Vyh&st-dNnOt) zxtL4AjW-w3bde9fPrdt&)f0toUJ2&UdD?Duy5Ap7dEF=0V82i93p+Kxkri{*^!QAa z`)V#?MO?Egc}EaN?0rV`N9>*U}noU~6E-WouZiR;Mgh z;i}OVBurvrDpRj7!i%ICbMj)VT&(w5JB7dEWs8$MSfbZaa1D^jw$rlh41JSI!*+g5 zc`HjldKt~dEdKiq-t`OW#SHiFi#h4kU3|pR`S;CF5SvpIp|Cl8#>|qEO zL6o_yj`uN0$wSqXQfj)_qWIKrnS3$j-u8y`GrF8k5xy*m3E_xC>4xG+3@28lsi2dl zG->G?bNPxG)$u+RlKOK*4722EnDvKFTfCP}MVn#i1AP7T_HVVXeMTs4JO zpT_!OPG@)cEQ+es9a7Q~8ZJxuwg`RN6PqI_ZGrR{=g#vc28nWQy+I8dcb5dFR^-u; z&&P%sTVJJ;F`R;9s*$hDbF31St>mkHWdp=P*}5fF!x?lQhPw$TMi}e=#xDm^PWJok zBklIX+F!cN8)z!@No~Er@9ywmEwj?-&7I}xh?Aw0SPtK(3EQ+5LHqwwu+}k1p;#vH zrvh`dw3QgL-4@kIQ!Av--?{@#~s8|+dQ;(;Mo#ndpY6spn{3TJBv8{Ee0%vgX2)N zCCV1=Y(p9TH+hpYR^mG9QF6nF>tHb9wDPpXRlL7F+QvVV*IK(W=+D|wiR-*I;elS7 zY`O=x^{a5b-2CDtug6c%+y!Jb>;Y$1|5k+KbP-$ndnLz+PK~0IJ6_kenCmP!NG!nT z0oX@l4sD#DBU$@kjnc{sh4baeOf!mqY{x0?+@X-P%tFTkGt+fK8Xnl}SW!g#bX7&^ z+2;eo?q}&im*rirs}E*eubvzp8ZZ##(eDL0O^$sfaX!0;rmj^d#vG<0v5$vbadqkM z;c@S>jXq)Rz%lvuo_XtEk0U!0-X%0LG%_Oo&y;sC!y!Vzbv!1e%gjo7+E(!P5CXQg zglw~&%zv|GAITU4^EUXYL*ba5L|+fG{n2f#<$P`;XXQzw!rFG>1xIQtjYXPCx$0Tg z_y1H9*k8*NMu;cG(T9I5k|_z+!6-KvLctWLG?awCF`Wto6>5{_B*kX_J!#TlRfW|Q zTxT2;H#0}=YR;55U1N;$dTp5H%;k}GCmbbyfA00QK5!SnK;wWT_=y7G3YX(F_2ej zekKG-;-FFYlnsInfBS-ue-l(=JyzlnCV;dv+bFa!pd>$1xZyr37BgGGzr|0+^O~0j z15^}t&e-E6dU|#)QNVmuka5beLq1^$=n5hx6Mg@fLV!rjf(f07zjUyE!{MRr^$O81 z9c&-SdtEZ{pn(T}h6ZnUS7wPMBn?d!5HMe!BHRBbb05=@24O?2h_`+1 zSkky=Y6p<;hK&MFs_UV3Pi4-ZFlQ5qOdAaJ4>=1O04Q<~*!bCF?FPS~o{er4?b z@BAktYAQF=_~SF#TF%vAsN~HdgBetV+7Sn}tl<@KS7SOg0f&fC(;da%oL1YWSL+*m zGM#5P_te#*^#`lcd2E#Bzrd<*Ozyihcs6GM{UIN@;iOnS-MRs~qr?3IfIIow<-ibm z1axfeXk3WdOtrvL9~RrkL@RPE27Wm{vO5xg=Y{Si6xRMyB}nHWVL(7VUs(tiyCf+=eFX z^v*e{k1Tj6MkZdZ0LiaYY^zFpCUo+Dxx=bBlNeU*IS#VeeOAzI)Vt^$zh$j^EZMHM z**h+Kz~xZ6N@mz-#ETTbxO`K|Nr-N;@=2jQ#7ZgkFx(W;GWygjB|Jx@jU+qS`t!IrL_@Mh#X_TZx%@ z^4p_*L+-*ol_Bw(5gpCY^}j0qLkVl4eKqJivQEuSwK~_wQU=a?(Pr}B&EB% zySux)K|s1&x?55}O1is2>5>k~O$h(?yyyFj*W>Z~9|mI&_Fz2Mnsd!nbFSyU4NmP* zk_r34gxePNOJ$h6cykvyCw$qW0>}3|r&9U*AFcQWu@^Z90;YM#zVCO^+rx zNH@pXoqevqr|SqP@$wvXr8J@&d_JP>=uXmMSW8G@sN0shx}NXhJ^U;k3^P3*Y9*{X zT_){Q>`WUL%w79gi?=u4Dq=QB^rnC>Qexc!1mCKET58qi_4>ylhJterN@VVP&{9R} zf`VGjgzL=<92XlYXsi4V{!C1%tpasaKFas6LJV)K-=vfm;P_v(pq!FX4Y?&YsVKhO zR%%faHzRDbQ!M3E;64T2WnRzcuczPxKYjJ4E?oK+r6|}!&xa}zY4)CB2A?|sZ9Z0a z|7}5bo3I!eu5axh5J}j*49lzaa_Zc8rw3g>pdb(cSDK@($H8DyJ~4-_*`cwZ$s? ze5h6-?o%Yb`5-tXa|0?FF6Y2tk6?PhbB~VSfa6cTW01)6;9^4dE+jka44m<(+qOx| zS7+%A4{cV1vYAlL_6DE@7TAVxXLfPEJy)0APHnPc=nL6sYxCkc(#=FY#J=VU)@bgA z0_~_L;7&Dz1PtGWxfn&<4}Ma94p>_udw=f*7k4kv58VQ0lC!J^kehlmGtWV4Mi6UiYHz1L*lE`k@;g5_yK$-= zZtu<-NFGqxlm4JpB#T7g%Ex-iNmQO!&y7g$cHfwbO|=&7md}4l4Mn9|n24rEQ^>Ux zYO+gTedMAD(2~_1Q6k*FOpy38A*yn7gLcbXj?+s+U;2tl$BG4xn$@hHmfNzSfuA*V zDR8OI{FbT?yi6r34Q}@hSTAGKo2ggB19-#DmV2x|Zadz2|rHCQV8f=qYq3S-XQKr)V!L{fbjC(JB{i1oZ ziF#JsGKmxT>@0|5a3}*}b2#dWUIr!i`8n>4;r7E*)&qvB!SvEbZkC%_T$i>HF_iTK znSw(apn9nYdcK)KaXd!E__$?es}T}>(H*ztldjGo3~FxJOQHIwDEbA;V7L2u0y+iR zI z`Ta|+1SVzj1fro-ACvhOxw!`lkeVnt+5zUv+2Q>l6W3DEHS!?GkLeUc=jF=*DYi;4 zgAmXvqwtL98S&@oBP*(OL2;6Q!{jJ!x!SIzc(UKP=n25KVnzea3MJKb=3u8Cm>iLlc zo>?@$-95+WQf~)EAZt_5R=Kx&-+eesXf5(h%iWVsgV-k<5sR4Bt?SzA!_Si!Vs17{ z{6tvfF)5Sptk|88Zta~Yi^wNgFB3D>72<4rA$j}O^elvaJgTjo4ShF~YmiNpHeGbr zyKXGp)-!&Ibd!z^zbI+4QbF?)fGbwcwDyLFza9Z}=ghoEC1>_-5DRf*_-4`0`D_3% z-j$9^NUELnMfu|?&hgFGHu3n@;Oi!chfyGFC1tj zysM2L<;pVB&eZILeivP-DG6^E!_0P@Pv$*0)yMcNP8S ztipdgy#t~iDVyOeruzZb?;xzt0NZ53utk9^3ZvN}(iFQco`XI5+!2~Bt*g7s$UI9V zqTk}E=N|5KTZK~u!6+3ngR++0rc2UcL~b2^1ySOpH^5EkBa;19dk^IoLT_D(^eYV? zh)u!~KjQmm97L8GO!T6q$6zM-+4)P@I(QCal||#8B$YWzh+EnD6~{;lGD;KM(2Z~x zbfm^>#(c>3<`9QS(Mb$0_NoT37Om8`p*ft5u4+)-eY&scXqIdG8ph(=r%k3w~PVLOXd zvY%SJgzTUS)}20bSmIE#Ku2ArE#^+hFkz~5s)Jq}y~;DcyBxahE*PlD`+}A(u^rn<&8zczVDn%^A5dk-Vy_mr0qL*uM z+kH(G>dhnCDc>o`r?(AIs+^*rfe)ECTkV3CYD3Q#19fXQhe<>BD4P`WFJ{4fglrGp zMC#o(hLNzR_6BG%EOWFS0kBYlhLR^aX`ly0}L;y&ATq9Kgir+g(JSTR7eC^Kd70rtk@Qwh@u3M8?jc zvgkQ+ER2q@6iY?Es?2yUOPXy52HHmmw09OlCy8i1JSX$cFQ?Kz?WxLaD*;xXXdOZ= zBkjariS2=U=4{ztOD4WdLby%7@-N=%81G7r_onmAC}*~wh&dH`ElcXAaT1YCg!*3c zydPyIQxoLY1}B)t!AYV-sVm|=v@yqXQI~?W4Le?d1`+uZEGOQ|ee*VGf zrT|&74wW?}lFB{`V02N9RseY6=RHwR+vczuOFPU6KW$IutXl`cwNkIGa12qG zrJ%bP3TNk7J?}yS3x6XEWxoN1EKl;n-Jr)OR82@8A-lLcqJ0m!DhivFnJu)P!CIZozRj3Dupfu>UuxP6njtRWN0x(t)#GPjJ(W*QX;@KZebajIc;dm zCW~hL0jRsrD=aVq-P|3Oy{?-lW2lzd!ihrjVFr)oLbOS5oQOiE*S-!;?Lbx&bB@wB zIBCNkoH#5Y8I#5PlHx>EpLUEIfBnTV;pU3R%nfkZ z!YFhE-!>M@7lKEDX})s?nHWmd;*DDNM6GEm7PaY{ePtQ7vU*E6^Yo7t_xmKXg?pIw zLetbL($kGYR?TwDFJ{6?y@??DP->A;k*WI-u5h`r_Fj=a1?c8CaYv_fx+w3Y&sz)# z5l!Eerg8T>?FtY$ym)%@xf}a@V)bx@rCghzp-=;#(K|s@NOO*IZA)NzB23n8Oyp`N z6Y_)!pjq5GpOl;|9mspLVAjuk4Swf>dB>Z+oWGfksTiJHt6LL8{)`TN&}5mlo&S@f zn?k$j;4E88b8ms}U06xznINvR%znonws$*X0nXu~KR;D&0=; zq1MxLBj~1VFmZ3_rpJ&0B|edG0LL4z$TA%JtOE-~IHfCXompV+wy z8-&6rt-RaR;6BG2HZ5IoYkQ!W1K80!*5H1C5|T&@US7!VmLWU9nG%2IR0sf%g(q;p zir%R2#OCiM-FRbfu?u|_l)-Q7I{}F_K#B)nXF9wXSLm-9xO`&}clEL58GaMK6`1Uo zQKob~3zs=o{h-kD;27bhfCkdw{8=X?mD$rB(iIfJLV2z}Inma$btemM>{3VY_dH`c zRmH*W_;0{4Bi*0y!=kq3gCg}!KzsqQv(?<&2%Y|52_E_JZZE7axCF6;pWKz-h9;(1 zFEg|lBDp{TkLtU9pc8X{8!)$h;lT}wYiX`cFvH{sCC$IJ1nrkGsX1R-c54t zLc9jBHVaK(PZqQAK)*w|rQxaCi@4yDsR;BKp_0+QMY4^V@oQdty=y?g5jigp7$EqZ zjDUR~x@7qfAlguTFi<0JZx{E(?05$3ZrE!(`+7JwC(6-O)0zPfL-;9#k~GMZLtGy?nM#)>2+T`kNj ze-Cd%!Vd{3rx0cOIo+1L-plN7F!@)*0?vWum?{xsvwILKF<=UycOWzqNrt^1DAHo{ z&>l4+Ab^}}aY{#leq4;cq6#<-V$Ho7UKVZ81@Wh+CFOY)SxBEZUOMd5^n&4mJBI5y zhiL&%RP$EK=dU%dsx>v_%dKWSAnH{~OU>To6_twC8@+RTFwOV zjN#5sZh{G`WWFrn$+vV8xa_EdxGegTh$iG5fdf8|IkR2eF_u{^F!2%tv7EYty{ytY zfTzxF4)ngPoP_WTG|Fer08u&Q$%>o}_7yWw_VUke{^I-nDIPLL`#{~ep5)0hW*8ez z$=vvIc7ys0bTt^Z4cC$pSAr8jP+)*}S0n5;J4~41b{%cIM*fv_$1_a{7~CzEGF*%a zmo!~DyV(mH=a!>N6aTXY|l>8fd_G+w#(nF|q5jcLBA z13?#dl>PPCA}RNzqD6oVO(@OKym{I-Pa5JmLRwqW$FBiUBnL+P2)@~J(ec|s_sm!R2@$OKicGYN*2GqU(J&T z{Lqn)*=vxuAX1Gv0Dk!C`pCTtlDrGq_gKcHI?^jian>rS^UL?G0{-ilaNK#DTyw56 z{Mo5FbQ?Hew~5Kllovle5o!-n7?EA%~9 z%jQnBip8H@%a9KGo;gZW59-6s%P>_Y62@fk&z9tt_3vec<8wZNl}y-DPVJOG|Iin_ z626Fx(_8z21@R?Y6h3=m$wyZ(m0~u^gGm$C_>_E9bIWd}w}}Fi6`vO0&SEgSdVWB! z70oGSTwI5)%Dq)n3w0Upp_=|g;_;3OZw=}>WJUsdX*M=A4EsAwYD>0ZPrKc^Y`%(P zR4QJgyJNu4aNup&3279U6_ zdbsfLmw#jb+-(ai0SJf=$M4ESh--^XS307Zgwt`pJ8{}aNm%u@LRcdGx zw~H)F7#NIpX{7#kW5V(1H5 zz5AdL#5;!Xs~elu2h{fX{pR6_V=3+&^ruJ{iTx$`s^O_)RYD@?{ol+}(o43PDCFcy z>6@z&ig(9lnQ&Je#^YG*qG0nV5izc-nDi1Oya!vptC5L&xq!LbWas62!Jk9@Hgg$u zcf|NzytpAfC_?Eo)ZG&ywyD+)KyrtAk@F|5=o#Mda4t2W8yW1la)U@5zE9jn2t8L( zX81%5B2%>F4iIQQ*!=|^;t?PSN?@8gFwrSJ@S3$#y8xt&xUbuD-u=7}9#eLWR72-qTT@xu+BTcA6}iClYMq3D|3PS&w~_olnHK zbbUG}X3XIIUV2VpcbYSqR^lWK`E;G4pb|N_JYdhO-P9g;3Pq zx#XGZHE!5Xc?m~}&3$AbIXJZLI=xQV><&VT5CXbQ&*Kz10ue(bo$2A61QOcN*>`p;EOKRNXLPtn*{8w3F-Cleb(>;Dq;Q;C(4 zd?J7xq=(1C&}V+H(IjuWE!QWIPhSF^7YZk!fUfOIo+QzqwU^5k7P>3Y8U%-;?GA!O zHYcntF5ohIP^By2K2uO|W-gA~czK@O*61M(U{K*rXX`j+=FR!L5*bC z8%ZNoC}V;XL!Kpb>sP)JkSj_sf;rwMx2$<+g%bK77T7~8tSw-VD@GV=JA)2g5Hs@& zN(X^2sMAj;J;5fpbBvQ$s%Wr@mKo`t|+60qbQv%_fRc(1N8*2fDS zc~Y)?i3pyo`Y`?2GK=TmHMB1Sk?@)-KhzR}Oj=qWo(Ut-uUx}_lC%xNatZzBfmEBJ zSB2ILfPtS-VxP5RivoeD?|F1}MKFC}S2DXwe+>&i*)@^(pNc<0Ylm@t;ENoizkQkG z#jnpbKyf#qNVcsT*VPwT{GWW9AfDFmg(z^eN2;&JR3~wRYIg?8~`b z6w+Q}ETeZ#j>1Z?z5425VK$AnXI=J;)o?YW1AC@*n=7rc0xy8rmLo~Jcb!bgn3ceG zv1@S2g~rpP*}ia;hD~CRV%Kn2XA_Ux$o_4-22CZ*sM5r!eGy6Peeyw==5WHgAUBr! zfvRYibkq^Pj~pB0`BIi)Xx#xu3H)+%OM`sS+HY@3+2tFUh{#~*CgyA#2A6>lqfn z6S5O{6{Wk3D3`MS+HG^VfwulGBaN;h`#huNIg<4%zjQE;0edb^GBt_26eM9Eg~2<= z%x&8wNd;sz2J(b`T`Vn+b%GZu!pg_&@u44I_b|jc_M^Ast*GX% z~cER`C{E`DzN*%y4r>@ti4A$Le2~6EEK|BE&%nFopIQQ zN!-D9pX<=ija}?3M}Wur)SnR4!Q^=N{TZI>K-5OX+PuZ@ecEdP)O|3 z;Z49IgbEtgSJg(*(Aa^$Aoi=5ZV6^_E4HzP)mn?bbRzqSk-Q@}P! zU^@l7uS{R0FQ1#*uh%#!jP+VDBI7|deK+xz-o;cMwsFQa_N6oU`m|HL^uTLD=QXI? zqFiDND9*>fT!W9Zuh{5;R})jH-(6Au;dQ~kD`bIM)20??E{+DjC_(m7K9a=~L+3%m zmtNX7LSUw(wb78YdD4gQYKDwb0w6BK=Xyc%RRPAvWSvJs>w0h2R385!%w)PxhWr&M01bMie zx>a1ez2u_4;Q$qR#^a%(z`bD;W}PcbW;gZp$;XJ(jj16;20aY3xp5(V_)^EWM`}Gr zK#ADYB0DVWY&9JP_oH)FDL~K(Y0HNT%jo5+7MAC6`q*B*BqP)IfOA zSs1}p4ht#5?g87B?XYTl`HxLvWh($kg4e|Fz2Zvohr;hXR?n)(=s&V%ugp%$J_YTVFooJk<#&j9b704}aM+b!QM* zY2B{6NUDF@2GpzM?B-{6Ghg#rk|qw*Qr=FO%CA^HN`cxwni?*?^I8;o%^2I|#b!@H z!~kFZVrVLm*xR}zG$0!nJB)j{!+gufR3EieNl0$mvb9e%%PXc-huMH^XTw*p?1 zYyBDhW(uaF%N2hMyCTWakzvUi@hY_+R8p{u`b*vcrP^U z_*g|+yWK|d2olI`sQ^ThBwo*25*7;P@yH3tB(f9HU$-isz0RnuWHIEzUyNIb?n@Re zv$Du(b|ul3b3Fq0U>?6%DxBrqHZ@M!(Q9Sr<$XXSD&RZR=lmi8#WaVOpR03FJ!gJX7}xq)vi!L65L~h`COI7w7PQN!xMG^TmKZsOTAK%u z#7EYSymBa>Y&`4@Ffm&lxog|JGhG>BPx$u;Ig zhanra)@5TBV{@8(le)od=MZScTHK2=8cikHIuNW>^0PQLiQ-@U95r?P0sc?spnX8XB-Fwp8ZN9nk*gQNY==j2)0kCP> zDS3wH9LV%ani_3bU2|xy#zAU$rwL<`uAe~6y>{(&G8kQVUiZh>m`rur~bZ0XVL~QQ(q<_ClM)5o8+`+95hA?X0lOj&2f6?i%}xEm~y3R zZA1w3h^*;MJ*GFdRrP9o(a}EeSy$0MRB1H>ND#EI?o(ILX|D1yXsML7Jz;PiQelZ+ zp!i9t0BZQ}Y0c!zH|4A21GdDR7i)Cpg{XY}^=@lm1vWb9>y^p4F^Fj{5|XH~U(`1y zf0U&kUb4c0uQ(#`!MNRwE;%*DP}`saRhM}Q@8)WSInEKkDq_N)ih@A^4cDIuzpTR1 zg1^TRqQx;vVRq~}7XnA(a3&`_p-X}Rp+M!R82&a9yRuU2)qbcH!*(OuBG-ZxL$7^3 zk&b$I^~5I@OdQRRR`nvwa|Z8Ax*#R#RSH|9#$u7?>1oDhG*RHFDlwSr4bi&61QLwz zDLzl|vh{cbR+{+2Riced&uLkYy9`dK_ScE8u`N&ueqg2cUruA%=)P)#35CF58vwV> zIFPBlmMmvWShXzwjAC;X9Q9dnE`&F@@U8Utn=nx1ySEfLX(0((;LiiMhO*{o z332vyIVs;A+_1A?y(oW|?Fl2oUa(^_iON_+oYqiYgd}-iq2eyFl8e*2C7b|Q$7#)w zm1s2=sH^Fdv2u>d+BWU{?4KqFr-5CP>KbEH1xpYDVVij6M-c8AG=ym^@?d!I(P`9u z(W@77VDq{wy0<#R`)C@Tr;x*YPD61$^u=U&KnFrtLk+}c7XYQ}!}&%5t49-o8#I6j z8$BWc@|_PmISg)MZFq}`=(Tu&Y0*gn=!zUT%R6}HnzGC1I3zr#o#GHqMQG@>OzQj7okNAF z(psjhjkl6sE-6TI^GhnVg0K&Qnd~;28l$D{!$=pSZL9m)_hz5f__8{k;McQxsl7yL zoV4+ZL@DetHhsB+u&|Sr*#=j%+t!eitu!F$RMK>tLL_&GeKR_!oe^eQ=FnS3U9fs4 zI?FrCXlH>RT``+eW}G!(+Yec7JR&Y?WJi( zmoa%r*|6?kWI2MyMWFR&UR94W?=gsTJxJ}_*g_YkdUWL!owBrj-lX=Hx;)8+BIbFr zftcCqOWQ7{96mH7cGBrD==xgg7+$j^gyKT_a)O9QZ?{T>TX!jrkd>J#Cm|;2;tO2| z=43{SY5NJhTQKQ*&oeNy$u#WO!de&b$r+usOzH|f+vA&o_9PCcYXVad((7s>b=O!Z zxvTY)LL%1i&SDV@+C7(o`!I)3_ln}{m?q?=Y~@fKh>zj!lY5>N_O3$Ml2U5KPx+(7 zN0LYrf4JaN?NRvbXSVht{+PCc8`(XyfG??_f2D8e;jKH>`WI|T!;WbjqP9zrm*ZR7KW`bM%aMZ4>;lijsSslVlc+pT}&WfxFuQSMv0}uM1%mqJA$7GWa z6pIIode$f6LrBHlm1tMmunGE`=P4W`HIGYvT#t8kYINF0AA{{c=jGrCMA7YO`<&7m znPRW=3T+R(iyAEZD5LAgt+0a^)JQ95Y} zArV<65fxQBr;(Bl?f2HlYs0 ziGdJ%;O|#epl^W;RG_kRG@~>7OHhi=$l8MLJ1b@ZM>7{2pdviba?Qm47dPlXx4gn5 zAS((u#k2^#&-gl#^es}6f5-WyC+g41pS(8g(F7)M06uYiwe0*BfoQ)={+9!*<1+zM zpe4zFKtG#={Y zWh|VWfPQ@cp#n$BpCHi$Fq3A1NJ*f0`j5@bc=iX#zgcbujwXNJ%$8|Sv|Ql8_W^R* zf9Tq6-~sy2ga7Yw^MCDC&}KYbVj#*CIDmc}rk9j|j8g*IG1;2^%l?~tkPAUa! zoPSK-#rj{#|LUpVSk(V~Fn@15{M8crTjX;6d-DGbxPRIH@BK7?9A#=eKOijruWrUa zH|Ben#;-;|-(p?xH>CfwTj$T*@7>LQyk=br|G@pFquD<@LjKJ8-uCLNSK7B=k^Fbg zA3CS~4E^4B>8qpGw|FJ}1N48^U;fBn>u1XM)-XTrI(OM$QvTNt=KtpC^fUK+i;S=aQLge^)V~~G-zzMBoxuDS=O(|*`v;1gKX3c@GJ`*k za60qfF#ev4`Df+EpE=)Gb$=Bt{1(v`f5!Qj&icO6_{Yu)@%|;?4@$*8G>EADkeqE{m7WL`BO#91q`=2-V`_;N1uP(+}zs&l(<<*~)e?RN~b;0jj z5a;|l`5!F*{S5hjw(!SY+EDOI$ls&#chmVlGroU@`a19UEsRQj$M}a?NO>s;-~$;5 R2np~f1o-$>Q}y+){|A@R9n$~+ literal 0 HcmV?d00001 From b06524e2883466234f81988f51c5dfacb00c254a Mon Sep 17 00:00:00 2001 From: Philipp Grosswiler Date: Sat, 5 Sep 2026 14:41:41 +0200 Subject: [PATCH 18/51] fix(security): fail-fast VehicleTelemetry guard and Long-overflow hardening MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit F-001 (VehicleTelemetry): add `require(raw.size >= 2)` constructor guard to the JVM and iOS actual value classes (16-bit layout, bits 0-15). Truncated buffers fail fast with IllegalArgumentException at construction instead of a delayed, index-leaking AIOOBE at field access. The `expect` carries a documenting comment only (expect cannot host an init body). Raw Int/Boolean readBits getters are unchanged — the fast path stays caller-verified per Tickets 06/08; checked accessors remain the never-throw path. F-002 (KompactRuntime): promote fits()/readFloat()/readDouble() bounds checks to Long arithmetic. Int `raw.size * 8` overflowed at 256 MiB, silently classifying in-bounds reads as BoundsError. F-003 (KompactFraming): nestedRegionOrNull bounds the region via Long fits() and rejects `byteCount > Int.MAX_VALUE/8` before the `byteCount*8` Int multiply, so a 0x7FFFFFFF length prefix fail-fasts to null (TruncatedNested) instead of wrapping to a negative bit-length (Pair(32,-8)). TDD: 5 regression tests (constructor guard, 256 MiB boundary, 0x7FFFFFFF wrap) red-then-green. Full :kompact:jvmTest BUILD SUCCESSFUL (rerun-tasks); :kompact:apiCheck golden unchanged. Standards+Spec (RulingWasp) and Security (ThickGopher) reviews both pass — no in-scope findings. --- kompact/build.gradle.kts | 5 ++++ .../kompact/generated/VehicleTelemetry.kt | 6 +++++ .../trancee/kompact/runtime/KompactFraming.kt | 13 +++++++--- .../trancee/kompact/runtime/KompactRuntime.kt | 6 ++--- .../kompact/generated/VehicleTelemetryTest.kt | 22 +++++++++++++++++ .../kompact/runtime/KompactFramingTest.kt | 24 +++++++++++++++++++ .../runtime/KompactRuntimeCheckedReadTest.kt | 16 +++++++++++++ .../kompact/generated/VehicleTelemetry.kt | 6 +++++ .../kompact/generated/VehicleTelemetry.kt | 6 +++++ 9 files changed, 98 insertions(+), 6 deletions(-) diff --git a/kompact/build.gradle.kts b/kompact/build.gradle.kts index 7967439..19cc3f0 100644 --- a/kompact/build.gradle.kts +++ b/kompact/build.gradle.kts @@ -73,3 +73,8 @@ mavenPublishing { } } } + +// F-002 boundary tests allocate 256 MiB buffers; give the JVM test fork headroom. +tasks.withType().configureEach { + maxHeapSize = "1g" +} diff --git a/kompact/src/commonMain/kotlin/ch/trancee/kompact/generated/VehicleTelemetry.kt b/kompact/src/commonMain/kotlin/ch/trancee/kompact/generated/VehicleTelemetry.kt index adb0235..ae96545 100644 --- a/kompact/src/commonMain/kotlin/ch/trancee/kompact/generated/VehicleTelemetry.kt +++ b/kompact/src/commonMain/kotlin/ch/trancee/kompact/generated/VehicleTelemetry.kt @@ -23,6 +23,12 @@ import ch.trancee.kompact.runtime.KompactModel @KompactModel public expect value class VehicleTelemetry(public val raw: ByteArray) { + // F-001: the platform actuals validate raw.size >= 2 (the 16-bit layout, + // bits 0-15) in their constructor init-blocks, failing fast with + // IllegalArgumentException on a truncated buffer (Ticket 06) rather than a + // delayed AIOOBE at field-access. These getters stay the raw zero-alloc fast + // path (Ticket 08:39); decode untrusted input via the checked accessors. + @KompactField(bitOffset = 0, bitWidth = 4) public val batteryStatus: Int diff --git a/kompact/src/commonMain/kotlin/ch/trancee/kompact/runtime/KompactFraming.kt b/kompact/src/commonMain/kotlin/ch/trancee/kompact/runtime/KompactFraming.kt index 2a64ec2..1dce448 100644 --- a/kompact/src/commonMain/kotlin/ch/trancee/kompact/runtime/KompactFraming.kt +++ b/kompact/src/commonMain/kotlin/ch/trancee/kompact/runtime/KompactFraming.kt @@ -69,12 +69,19 @@ public object KompactFraming { prefixBitWidth: Int ): Pair? { if (bitOffset < 0 || prefixBitWidth !in VALID_PREFIX_WIDTHS) return null - if (bitOffset + prefixBitWidth > raw.size * 8) return null + if (!KompactRuntime.fits(raw, bitOffset, prefixBitWidth)) return null val byteCount = readLengthPrefix(raw, bitOffset, prefixBitWidth) if (byteCount < 0) return null + // (startBit, bitLength) is an Int pair: a payload whose bit-length would + // overflow signed Int is unrepresentable, so fail fast to null (a typed + // TruncatedNested at the caller, Ticket 06/09) instead of wrapping to a + // negative length. A 32-bit prefix can encode up to Int.MAX_VALUE + // (0x7FFFFFFF) bytes; byteCount*8 overflows Int above Int.MAX_VALUE/8 + // = 268,435,455 bytes. Reject counts beyond that here (F-003). + if (byteCount > Int.MAX_VALUE / 8) return null val regionStart = bitOffset + prefixBitWidth - val regionBits = byteCount * 8 - if (regionStart + regionBits > raw.size * 8) return null + val regionBits = byteCount * 8 // safe: byteCount <= Int.MAX_VALUE/8 -> regionBits <= Int.MAX_VALUE - 7 + if (!KompactRuntime.fits(raw, regionStart, regionBits)) return null return regionStart to regionBits } diff --git a/kompact/src/commonMain/kotlin/ch/trancee/kompact/runtime/KompactRuntime.kt b/kompact/src/commonMain/kotlin/ch/trancee/kompact/runtime/KompactRuntime.kt index ca1bfad..bc6143f 100644 --- a/kompact/src/commonMain/kotlin/ch/trancee/kompact/runtime/KompactRuntime.kt +++ b/kompact/src/commonMain/kotlin/ch/trancee/kompact/runtime/KompactRuntime.kt @@ -125,7 +125,7 @@ public object KompactRuntime { /** Bounds-check: true iff [bitOffset]+[bitWidth] fits in [raw]. */ public inline fun fits(raw: ByteArray, bitOffset: Int, bitWidth: Int): Boolean = - bitOffset >= 0 && bitWidth >= 1 && bitOffset + bitWidth <= raw.size * 8 + bitOffset >= 0 && bitWidth >= 1 && bitOffset.toLong() + bitWidth.toLong() <= raw.size.toLong() * 8L /** Reads 1 bit at [bitOffset] as a checked [BooleanResult]. */ public inline fun readBool(raw: ByteArray, bitOffset: Int): BooleanResult { @@ -228,7 +228,7 @@ public object KompactRuntime { /** Reads 32 bits at [bitOffset] as a checked [FloatResult]. NaN is canonicalized (Ticket 04). */ public inline fun readFloat(raw: ByteArray, bitOffset: Int): FloatResult { - if (bitOffset < 0 || bitOffset + 32 > raw.size * 8) { + if (bitOffset < 0 || bitOffset.toLong() + 32L > raw.size.toLong() * 8L) { return FloatResult.failure(KompactDecodeError.BoundsError) } val bits = readBitsLong(raw, bitOffset, 32).toInt() @@ -237,7 +237,7 @@ public object KompactRuntime { /** Reads 64 bits at [bitOffset] as a checked [DoubleResult]. NaN is canonicalized (Ticket 04). */ public inline fun readDouble(raw: ByteArray, bitOffset: Int): DoubleResult { - if (bitOffset < 0 || bitOffset + 64 > raw.size * 8) { + if (bitOffset < 0 || bitOffset.toLong() + 64L > raw.size.toLong() * 8L) { return DoubleResult.failure(KompactDecodeError.BoundsError) } val bits = readBitsLong(raw, bitOffset, 64) diff --git a/kompact/src/commonTest/kotlin/ch/trancee/kompact/generated/VehicleTelemetryTest.kt b/kompact/src/commonTest/kotlin/ch/trancee/kompact/generated/VehicleTelemetryTest.kt index 27cb3a2..5e28052 100644 --- a/kompact/src/commonTest/kotlin/ch/trancee/kompact/generated/VehicleTelemetryTest.kt +++ b/kompact/src/commonTest/kotlin/ch/trancee/kompact/generated/VehicleTelemetryTest.kt @@ -3,6 +3,7 @@ package ch.trancee.kompact.generated import ch.trancee.kompact.runtime.KompactRuntime import kotlin.test.Test import kotlin.test.assertEquals +import kotlin.test.assertFailsWith class VehicleTelemetryTest { @@ -76,4 +77,25 @@ class VehicleTelemetryTest { assertEquals(0, speed) assertEquals(false, isMalfunctioning) } + + // === F-001: constructor must validate the in-format buffer precondition === + // The 16-bit layout ([0..15]) requires >= 2 bytes. A truncated/untrusted + // buffer must fail fast at construction with a clear IllegalArgumentException + // (fail-fast, Ticket 06) rather than a delayed ArrayIndexOutOfBoundsException + // at field-access time. The raw getters remain the zero-alloc fast path + // (Ticket 08:39); untrusted input should use the checked accessors instead. + + @Test + fun constructorRejectsTruncatedBuffer() { + assertFailsWith { VehicleTelemetry(ByteArray(0)) } + assertFailsWith { VehicleTelemetry(ByteArray(1)) } + } + + @Test + fun constructorAcceptsInFormatBuffer() { + val tel = VehicleTelemetry(ByteArray(2)) + assertEquals(0, tel.batteryStatus) + assertEquals(0, tel.speed) + assertEquals(false, tel.isMalfunctioning) + } } diff --git a/kompact/src/commonTest/kotlin/ch/trancee/kompact/runtime/KompactFramingTest.kt b/kompact/src/commonTest/kotlin/ch/trancee/kompact/runtime/KompactFramingTest.kt index 9012e6a..152dc77 100644 --- a/kompact/src/commonTest/kotlin/ch/trancee/kompact/runtime/KompactFramingTest.kt +++ b/kompact/src/commonTest/kotlin/ch/trancee/kompact/runtime/KompactFramingTest.kt @@ -4,6 +4,7 @@ import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertTrue import kotlin.test.assertNotNull +import kotlin.test.assertNull /** * Ticket 05: sequential, length-delimited framing (fixed-width LE byte-count @@ -112,4 +113,27 @@ class KompactFramingTest { KompactFraming.writeLengthPrefix(buf, 0, 16, 7) assertEquals(7, KompactFraming.readLengthPrefix(buf, 0, 16)) } + + // --- 32-bit length prefix + nested region (Ticket 05) --- + // F-003: a 32-bit prefix encoding Int.MAX_VALUE (0x7FFFFFFF) wraps byteCount*8 + // to a negative region bit-length under Int arithmetic and returns a corrupt + // Pair(32, -8). The bit-length is unrepresentable in the Int-pair contract, + // so it must fail fast to null (TruncatedNested at the caller, Ticket 06/09). + + @Test + fun nestedRegionOrNull_rejectsIntMaxByteCountPrefix() { + val buf = byteArrayOf(0xFF.toByte(), 0xFF.toByte(), 0xFF.toByte(), 0x7F) + // 32-bit LE prefix = 0x7FFFFFFF = Int.MAX_VALUE bytes (largest positive count). + assertNull(KompactFraming.nestedRegionOrNull(buf, 0, 32), "0x7FFFFFFF prefix must return null, not a corrupt Pair") + } + + @Test + fun nestedRegionOrNull32_roundTripsLegitByteCount() { + val buf = ByteArray(8) + KompactFraming.writeLengthPrefix(buf, 0, 32, 4) + val r = KompactFraming.nestedRegionOrNull(buf, 0, 32) + assertNotNull(r) + assertEquals(32, r.first) // prefix occupies [0..31], payload starts at bit 32 + assertEquals(32, r.second) // 4 bytes * 8 bits = 32-bit payload + } } diff --git a/kompact/src/commonTest/kotlin/ch/trancee/kompact/runtime/KompactRuntimeCheckedReadTest.kt b/kompact/src/commonTest/kotlin/ch/trancee/kompact/runtime/KompactRuntimeCheckedReadTest.kt index ef7ffb8..6a354d0 100644 --- a/kompact/src/commonTest/kotlin/ch/trancee/kompact/runtime/KompactRuntimeCheckedReadTest.kt +++ b/kompact/src/commonTest/kotlin/ch/trancee/kompact/runtime/KompactRuntimeCheckedReadTest.kt @@ -418,4 +418,20 @@ class KompactRuntimeCheckedReadTest { assertFalse(r.isSuccess) assertEquals(KompactDecodeError.BoundsError, r.error) } + + // === F-002: integer-overflow hardening (Ticket 06) === + // A buffer >= 2^28 bytes (256 MiB) makes `raw.size * 8` overflow signed Int to a + // negative value, so Int-arithmetic bounds checks in fits()/readFloat()/ + // readDouble() false-negative in-bounds reads as BoundsError. The Long- + // promoted checks must succeed. (Boundary test; requires -Xmx1g — see + // kompact/build.gradle.kts.) + @Test + fun boundsChecks_survive256MiBBuffer() { + val buf = ByteArray(1 shl 28) // 268_435_456 bytes -> 2^31 bits (Int-overflow threshold) + assertTrue(KompactRuntime.readBool(buf, 0).isSuccess, "readBool must succeed on a 256 MiB buffer") + assertTrue(KompactRuntime.readUInt8(buf, 0, 8).isSuccess, "readUInt8 must succeed") + assertTrue(KompactRuntime.readInt32(buf, 0, 32).isSuccess, "readInt32 must succeed") + assertTrue(KompactRuntime.readFloat(buf, 0).isSuccess, "readFloat must succeed") + assertTrue(KompactRuntime.readDouble(buf, 0).isSuccess, "readDouble must succeed") + } } diff --git a/kompact/src/iosMain/kotlin/ch/trancee/kompact/generated/VehicleTelemetry.kt b/kompact/src/iosMain/kotlin/ch/trancee/kompact/generated/VehicleTelemetry.kt index b42db47..3b31184 100644 --- a/kompact/src/iosMain/kotlin/ch/trancee/kompact/generated/VehicleTelemetry.kt +++ b/kompact/src/iosMain/kotlin/ch/trancee/kompact/generated/VehicleTelemetry.kt @@ -8,6 +8,12 @@ import ch.trancee.kompact.runtime.KompactRuntime @KompactModel public actual value class VehicleTelemetry(public actual val raw: ByteArray) { + init { + require(raw.size >= 2) { + "VehicleTelemetry requires a buffer of at least 2 bytes (16-bit layout, bits 0-15); got ${raw.size}" + } + } + @KompactField(bitOffset = 0, bitWidth = 4) public actual val batteryStatus: Int get() = KompactRuntime.readBits(raw, 0, 4) diff --git a/kompact/src/jvmMain/kotlin/ch/trancee/kompact/generated/VehicleTelemetry.kt b/kompact/src/jvmMain/kotlin/ch/trancee/kompact/generated/VehicleTelemetry.kt index e87a88a..f24fc49 100644 --- a/kompact/src/jvmMain/kotlin/ch/trancee/kompact/generated/VehicleTelemetry.kt +++ b/kompact/src/jvmMain/kotlin/ch/trancee/kompact/generated/VehicleTelemetry.kt @@ -10,6 +10,12 @@ import kotlin.jvm.JvmInline @JvmInline public actual value class VehicleTelemetry(public actual val raw: ByteArray) { + init { + require(raw.size >= 2) { + "VehicleTelemetry requires a buffer of at least 2 bytes (16-bit layout, bits 0-15); got ${raw.size}" + } + } + @KompactField(bitOffset = 0, bitWidth = 4) public actual val batteryStatus: Int get() = KompactRuntime.readBits(raw, 0, 4) From a72d9f17fabe9f2ee0c391869f765551e708975a Mon Sep 17 00:00:00 2001 From: Philipp Grosswiler Date: Sat, 5 Sep 2026 17:46:21 +0200 Subject: [PATCH 19/51] feat(kmp): scalar read/write API replacing per-width overloads - KompactRuntime: readScalar/readScalarLong replace readInt8..readUInt64 + readCountPrefix (single width-parametric reader) - KompactWriter: writeScalar replaces writeInt/writeUInt/writeInt64/writeEnum - KompactFraming.readScalar folds packed-value errors via throwSmallFailure - value-class actuals aligned across common/ios/jvm (no @JvmInline on iOS) - goldens: kompact.api regenerated via :kompact:apiDump; kompact.klib.api mirrored (ios klib ABI inference unsupported on this linux host) Verification: :kompact:jvmTest (forced, green), :kompact:apiCheck (green), :kompact:compileKotlinIosArm64 (green). --- kompact/api/kompact.api | 17 +- kompact/api/kompact.klib.api | 10 +- .../trancee/kompact/runtime/KompactFraming.kt | 12 +- .../trancee/kompact/runtime/KompactResult.kt | 9 + .../trancee/kompact/runtime/KompactRuntime.kt | 103 +++-------- .../trancee/kompact/runtime/KompactWriter.kt | 32 +--- .../runtime/KompactRuntimeCheckedReadTest.kt | 162 +++++++++--------- .../kompact/runtime/KompactWriterTest.kt | 33 ++-- .../trancee/kompact/runtime/KompactResult.kt | 14 +- .../trancee/kompact/runtime/KompactResult.kt | 14 +- 10 files changed, 163 insertions(+), 243 deletions(-) diff --git a/kompact/api/kompact.api b/kompact/api/kompact.api index d4c125b..66680f0 100644 --- a/kompact/api/kompact.api +++ b/kompact/api/kompact.api @@ -181,7 +181,6 @@ public final class ch/trancee/kompact/runtime/KompactFraming { public static final field INSTANCE Lch/trancee/kompact/runtime/KompactFraming; public final fun getVALID_PREFIX_WIDTHS ()Ljava/util/Set; public final fun nestedRegionOrNull ([BII)Lkotlin/Pair; - public final fun readCountPrefix ([BII)I public final fun readLengthPrefix ([BII)I public final fun writeLengthPrefix ([BIII)V } @@ -198,14 +197,8 @@ public final class ch/trancee/kompact/runtime/KompactRuntime { public final fun readBool-8-YI_fo ([BI)J public final fun readDouble-HEcpYgM ([BI)J public final fun readFloat-jORRDc0 ([BI)J - public final fun readInt16-xlB7gnY ([BII)J - public final fun readInt32-TxaLSrY ([BII)J - public final fun readInt64--8k1EKY ([BII)J - public final fun readInt8-uD3HChw ([BII)J - public final fun readUInt16-xlB7gnY ([BII)J - public final fun readUInt32-TxaLSrY ([BII)J - public final fun readUInt64--8k1EKY ([BII)J - public final fun readUInt8-uD3HChw ([BII)J + public final fun readScalar-Wm7bNI4 ([BIIZ)J + public final fun readScalarLong-UQ2bvJM ([BIIZ)J public final fun writeBits ([BIII)V public final fun writeBitsBoolean ([BIZ)V public final fun writeBitsLong ([BIIJ)V @@ -220,16 +213,12 @@ public final class ch/trancee/kompact/runtime/KompactWriter { public final fun writeBitsLong (IJ)V public final fun writeBlob (I[B)V public final fun writeBool (Z)V - public final fun writeEnum (II)V - public final fun writeInt (II)V - public final fun writeInt64 (IJ)V public final fun writeNested (ILkotlin/jvm/functions/Function1;)V public static synthetic fun writeNested$default (Lch/trancee/kompact/runtime/KompactWriter;ILkotlin/jvm/functions/Function1;ILjava/lang/Object;)V public final fun writeRepeated (IILkotlin/jvm/functions/Function1;)V public static synthetic fun writeRepeated$default (Lch/trancee/kompact/runtime/KompactWriter;IILkotlin/jvm/functions/Function1;ILjava/lang/Object;)V + public final fun writeScalar (IJ)V public final fun writeString (ILjava/lang/String;)V - public final fun writeUInt (II)V - public final fun writeUInt (IJ)V } public final class ch/trancee/kompact/runtime/KompactWriter$Companion { diff --git a/kompact/api/kompact.klib.api b/kompact/api/kompact.klib.api index 5a91c5c..2f33f21 100644 --- a/kompact/api/kompact.klib.api +++ b/kompact/api/kompact.klib.api @@ -232,14 +232,8 @@ final object ch.trancee.kompact.runtime/KompactRuntime { // ch.trancee.kompact.r final fun readBool(kotlin/ByteArray, kotlin/Int): ch.trancee.kompact.runtime/BooleanResult // ch.trancee.kompact.runtime/KompactRuntime.readBool|readBool(kotlin.ByteArray;kotlin.Int){}[0] final fun readDouble(kotlin/ByteArray, kotlin/Int): ch.trancee.kompact.runtime/DoubleResult // ch.trancee.kompact.runtime/KompactRuntime.readDouble|readDouble(kotlin.ByteArray;kotlin.Int){}[0] final fun readFloat(kotlin/ByteArray, kotlin/Int): ch.trancee.kompact.runtime/FloatResult // ch.trancee.kompact.runtime/KompactRuntime.readFloat|readFloat(kotlin.ByteArray;kotlin.Int){}[0] - final fun readInt16(kotlin/ByteArray, kotlin/Int, kotlin/Int): ch.trancee.kompact.runtime/ShortResult // ch.trancee.kompact.runtime/KompactRuntime.readInt16|readInt16(kotlin.ByteArray;kotlin.Int;kotlin.Int){}[0] - final fun readInt32(kotlin/ByteArray, kotlin/Int, kotlin/Int): ch.trancee.kompact.runtime/IntResult // ch.trancee.kompact.runtime/KompactRuntime.readInt32|readInt32(kotlin.ByteArray;kotlin.Int;kotlin.Int){}[0] - final fun readInt64(kotlin/ByteArray, kotlin/Int, kotlin/Int): ch.trancee.kompact.runtime/LongResult // ch.trancee.kompact.runtime/KompactRuntime.readInt64|readInt64(kotlin.ByteArray;kotlin.Int;kotlin.Int){}[0] - final fun readInt8(kotlin/ByteArray, kotlin/Int, kotlin/Int): ch.trancee.kompact.runtime/ByteResult // ch.trancee.kompact.runtime/KompactRuntime.readInt8|readInt8(kotlin.ByteArray;kotlin.Int;kotlin.Int){}[0] - final fun readUInt16(kotlin/ByteArray, kotlin/Int, kotlin/Int): ch.trancee.kompact.runtime/ShortResult // ch.trancee.kompact.runtime/KompactRuntime.readUInt16|readUInt16(kotlin.ByteArray;kotlin.Int;kotlin.Int){}[0] - final fun readUInt32(kotlin/ByteArray, kotlin/Int, kotlin/Int): ch.trancee.kompact.runtime/IntResult // ch.trancee.kompact.runtime/KompactRuntime.readUInt32|readUInt32(kotlin.ByteArray;kotlin.Int;kotlin.Int){}[0] - final fun readUInt64(kotlin/ByteArray, kotlin/Int, kotlin/Int): ch.trancee.kompact.runtime/LongResult // ch.trancee.kompact.runtime/KompactRuntime.readUInt64|readUInt64(kotlin.ByteArray;kotlin.Int;kotlin.Int){}[0] - final fun readUInt8(kotlin/ByteArray, kotlin/Int, kotlin/Int): ch.trancee.kompact.runtime/ByteResult // ch.trancee.kompact.runtime/KompactRuntime.readUInt8|readUInt8(kotlin.ByteArray;kotlin.Int;kotlin.Int){}[0] + final fun readScalar(kotlin/ByteArray, kotlin/Int, kotlin/Int, kotlin/Boolean): ch.trancee.kompact.runtime/IntResult // ch.trancee.kompact.runtime/KompactRuntime.readScalar|readScalar(kotlin.ByteArray;kotlin.Int;kotlin.Int;kotlin.Boolean){}[0] + final fun readScalarLong(kotlin/ByteArray, kotlin/Int, kotlin/Int, kotlin/Boolean): ch.trancee.kompact.runtime/LongResult // ch.trancee.kompact.runtime/KompactRuntime.readScalarLong|readScalarLong(kotlin.ByteArray;kotlin.Int;kotlin.Int;kotlin.Boolean){}[0] final fun writeBits(kotlin/ByteArray, kotlin/Int, kotlin/Int, kotlin/Int) // ch.trancee.kompact.runtime/KompactRuntime.writeBits|writeBits(kotlin.ByteArray;kotlin.Int;kotlin.Int;kotlin.Int){}[0] final fun writeBitsBoolean(kotlin/ByteArray, kotlin/Int, kotlin/Boolean) // ch.trancee.kompact.runtime/KompactRuntime.writeBitsBoolean|writeBitsBoolean(kotlin.ByteArray;kotlin.Int;kotlin.Boolean){}[0] final fun writeBitsLong(kotlin/ByteArray, kotlin/Int, kotlin/Int, kotlin/Long) // ch.trancee.kompact.runtime/KompactRuntime.writeBitsLong|writeBitsLong(kotlin.ByteArray;kotlin.Int;kotlin.Int;kotlin.Long){}[0] diff --git a/kompact/src/commonMain/kotlin/ch/trancee/kompact/runtime/KompactFraming.kt b/kompact/src/commonMain/kotlin/ch/trancee/kompact/runtime/KompactFraming.kt index 1dce448..3b2b873 100644 --- a/kompact/src/commonMain/kotlin/ch/trancee/kompact/runtime/KompactFraming.kt +++ b/kompact/src/commonMain/kotlin/ch/trancee/kompact/runtime/KompactFraming.kt @@ -68,8 +68,6 @@ public object KompactFraming { bitOffset: Int, prefixBitWidth: Int ): Pair? { - if (bitOffset < 0 || prefixBitWidth !in VALID_PREFIX_WIDTHS) return null - if (!KompactRuntime.fits(raw, bitOffset, prefixBitWidth)) return null val byteCount = readLengthPrefix(raw, bitOffset, prefixBitWidth) if (byteCount < 0) return null // (startBit, bitLength) is an Int pair: a payload whose bit-length would @@ -84,13 +82,5 @@ public object KompactFraming { if (!KompactRuntime.fits(raw, regionStart, regionBits)) return null return regionStart to regionBits } - - /** - * Count-prefixed repeat read (Ticket 05). Returns the element count decoded - * from the fixed-width LE prefix at [bitOffset], or -1 when invalid/out-of-bounds. - * The caller then reads [count] sequential elements starting at - * `bitOffset + prefixBitWidth`. - */ - public inline fun readCountPrefix(raw: ByteArray, bitOffset: Int, bitWidth: Int): Int = - readLengthPrefix(raw, bitOffset, bitWidth) } + diff --git a/kompact/src/commonMain/kotlin/ch/trancee/kompact/runtime/KompactResult.kt b/kompact/src/commonMain/kotlin/ch/trancee/kompact/runtime/KompactResult.kt index ad30604..abd9f05 100644 --- a/kompact/src/commonMain/kotlin/ch/trancee/kompact/runtime/KompactResult.kt +++ b/kompact/src/commonMain/kotlin/ch/trancee/kompact/runtime/KompactResult.kt @@ -131,6 +131,15 @@ internal fun encodeDoubleSuccess(value: Double): Long = internal fun encodeFloatSuccess(value: Float): Long = if (value.isNaN()) FLOAT_NAN_CANONICAL_BITS.toLong() else value.toBits().toLong() +// Centralize the failure-branch throw for the small/long/double result encoders +// so each platform actual's getOrThrow stays one expression (Ticket 10 deepen). +internal inline fun throwSmallFailure(packed: Long): Nothing = + throw KompactDecodeException(decodeErrorFromSmallBits(packed)) +internal inline fun throwLongFailure(packed: Long): Nothing = + throw KompactDecodeException(decodeLongError(packed)) +internal inline fun throwDoubleFailure(packed: Long): Nothing = + throw KompactDecodeException(decodeDoubleError(packed)) + // ==================================================================== // Ticket 08 — result value class declarations (expect) // diff --git a/kompact/src/commonMain/kotlin/ch/trancee/kompact/runtime/KompactRuntime.kt b/kompact/src/commonMain/kotlin/ch/trancee/kompact/runtime/KompactRuntime.kt index bc6143f..977d7e0 100644 --- a/kompact/src/commonMain/kotlin/ch/trancee/kompact/runtime/KompactRuntime.kt +++ b/kompact/src/commonMain/kotlin/ch/trancee/kompact/runtime/KompactRuntime.kt @@ -135,49 +135,16 @@ public object KompactRuntime { return BooleanResult.success(readBitsBoolean(raw, bitOffset)) } - /** Reads up to [bitWidth] bits (1..8) as a sign-extended [ByteResult]. */ - public inline fun readInt8(raw: ByteArray, bitOffset: Int, bitWidth: Int): ByteResult { - if (!fits(raw, bitOffset, bitWidth) || bitWidth > 8) { - return ByteResult.failure(KompactDecodeError.BoundsError) - } - val magnitude = readBits(raw, bitOffset, bitWidth) - val shift = Int.SIZE_BITS - bitWidth - val signExtended = (magnitude shl shift) shr shift - return ByteResult.success(signExtended.toByte()) - } - - /** Reads up to [bitWidth] bits (1..8) as an unsigned [ByteResult]. */ - public inline fun readUInt8(raw: ByteArray, bitOffset: Int, bitWidth: Int): ByteResult { - if (!fits(raw, bitOffset, bitWidth) || bitWidth > 8) { - return ByteResult.failure(KompactDecodeError.BoundsError) - } - val magnitude = readBits(raw, bitOffset, bitWidth).toByte() - return ByteResult.success(magnitude) - } - - /** Reads up to [bitWidth] bits (1..16) as a sign-extended [ShortResult]. */ - public inline fun readInt16(raw: ByteArray, bitOffset: Int, bitWidth: Int): ShortResult { - if (!fits(raw, bitOffset, bitWidth) || bitWidth > 16) { - return ShortResult.failure(KompactDecodeError.BoundsError) - } - val magnitude = readBits(raw, bitOffset, bitWidth) - val shift = Int.SIZE_BITS - bitWidth - val signExtended = (magnitude shl shift) shr shift - return ShortResult.success(signExtended.toShort()) - } - - /** Reads up to [bitWidth] bits (1..16) as an unsigned [ShortResult]. */ - public inline fun readUInt16(raw: ByteArray, bitOffset: Int, bitWidth: Int): ShortResult { - if (!fits(raw, bitOffset, bitWidth) || bitWidth > 16) { - return ShortResult.failure(KompactDecodeError.BoundsError) - } - val magnitude = readBits(raw, bitOffset, bitWidth).toShort() - return ShortResult.success(magnitude) - } - - /** Reads up to [bitWidth] bits (1..32) as a sign-extended [IntResult]. */ - public inline fun readInt32(raw: ByteArray, bitOffset: Int, bitWidth: Int): IntResult { - if (!fits(raw, bitOffset, bitWidth) || bitWidth > 32) { + /** + * Reads up to [bitWidth] bits (1..32) as a checked [IntResult]. When [signed] + * is true the magnitude is sign-extended (two's-complement); when false it is + * zero-extended. Replaces the per-width readInt8/readInt16/readInt32 (signed) + * and readUInt8/readUInt16/readUInt32 (unsigned) accessors — one dispatch per + * width-band, not 8 overloads. Sign extension uses Long-arithmetic shifts, + * bit-identical to the legacy accessors (Ticket 10 deepen). + */ + public inline fun readScalar(raw: ByteArray, bitOffset: Int, bitWidth: Int, signed: Boolean): IntResult { + if (!fits(raw, bitOffset, bitWidth) || bitWidth !in 1..32) { return IntResult.failure(KompactDecodeError.BoundsError) } val magnitude = if (bitWidth <= 31) { @@ -185,45 +152,27 @@ public object KompactRuntime { } else { readBitsLong(raw, bitOffset, bitWidth) } - val shift = Long.SIZE_BITS - bitWidth - val signExtended = (magnitude shl shift) shr shift - return IntResult.success(signExtended.toInt()) - } - - /** Reads up to [bitWidth] bits (1..32) as an unsigned [IntResult]. */ - public inline fun readUInt32(raw: ByteArray, bitOffset: Int, bitWidth: Int): IntResult { - if (!fits(raw, bitOffset, bitWidth) || bitWidth > 32) { - return IntResult.failure(KompactDecodeError.BoundsError) - } - val magnitude = if (bitWidth <= 31) { - readBits(raw, bitOffset, bitWidth).toLong() - } else { - readBitsLong(raw, bitOffset, bitWidth) - } - return IntResult.success(magnitude.toInt()) - } - - /** Reads up to [bitWidth] bits (1..64) as a sign-extended [LongResult]. */ - public inline fun readInt64(raw: ByteArray, bitOffset: Int, bitWidth: Int): LongResult { - if (!fits(raw, bitOffset, bitWidth) || bitWidth > 64) { + val value = if (signed) { + val shift = Long.SIZE_BITS - bitWidth + (magnitude shl shift) shr shift + } else magnitude + return IntResult.success(value.toInt()) + } + + /** + * Reads up to [bitWidth] bits (1..64) as a checked [LongResult]. When [signed] + * is true the magnitude is sign-extended; when false it is zero-extended. + * Replaces readInt64 (signed) and readUInt64 (unsigned) (Ticket 10 deepen). + */ + public inline fun readScalarLong(raw: ByteArray, bitOffset: Int, bitWidth: Int, signed: Boolean): LongResult { + if (!fits(raw, bitOffset, bitWidth) || bitWidth !in 1..64) { return LongResult.failure(KompactDecodeError.BoundsError) } val magnitude = readBitsLong(raw, bitOffset, bitWidth) - return if (bitWidth < 64) { + return if (signed && bitWidth < 64) { val shift = Long.SIZE_BITS - bitWidth LongResult.success((magnitude shl shift) shr shift) - } else { - LongResult.success(magnitude) - } - } - - /** Reads up to [bitWidth] bits (1..64) as an unsigned [LongResult]. */ - public inline fun readUInt64(raw: ByteArray, bitOffset: Int, bitWidth: Int): LongResult { - if (!fits(raw, bitOffset, bitWidth) || bitWidth > 64) { - return LongResult.failure(KompactDecodeError.BoundsError) - } - val magnitude = readBitsLong(raw, bitOffset, bitWidth) - return LongResult.success(magnitude) + } else LongResult.success(magnitude) } /** Reads 32 bits at [bitOffset] as a checked [FloatResult]. NaN is canonicalized (Ticket 04). */ diff --git a/kompact/src/commonMain/kotlin/ch/trancee/kompact/runtime/KompactWriter.kt b/kompact/src/commonMain/kotlin/ch/trancee/kompact/runtime/KompactWriter.kt index 0d0040b..389f52e 100644 --- a/kompact/src/commonMain/kotlin/ch/trancee/kompact/runtime/KompactWriter.kt +++ b/kompact/src/commonMain/kotlin/ch/trancee/kompact/runtime/KompactWriter.kt @@ -49,29 +49,15 @@ public class KompactWriter { bitCursor += 1 } - /** Writes a signed integer in [bitWidth] bits, two's-complement truncation. */ - public fun writeInt(bitWidth: Int, value: Int) { - if (bitWidth == 32) writeBitsLong(32, value.toLong()) else writeBits(bitWidth, value) - } - - /** Writes an unsigned integer in [bitWidth] bits (Int form; 32-bit values go wide). */ - public fun writeUInt(bitWidth: Int, value: Int) { - if (bitWidth == 32) writeBitsLong(32, value.toLong()) else writeBits(bitWidth, value) - } - - /** Writes an unsigned integer in [bitWidth] bits (64-bit value). */ - public fun writeUInt(bitWidth: Int, value: Long) { - writeBitsLong(bitWidth, value) - } - - /** Writes a 64-bit signed integer (two's-complement truncation), [bitWidth] ∈ 1..64. */ - public fun writeInt64(bitWidth: Int, value: Long) { - writeBitsLong(bitWidth, value) - } - - /** Writes an enum/ordinal [code] in [width] bits (codegen-validated width). */ - public fun writeEnum(width: Int, code: Int) { - writeBits(width, code) + /** + * Writes [bitWidth] low bits of [value] as a two's-complement magnitude (1..64). + * Replaces the writeInt/writeUInt/writeInt64/writeEnum overloads — one dispatch + * to [writeBits] (<=31) / [writeBitsLong] (32..64) (Ticket 10 deepen). + */ + public fun writeScalar(bitWidth: Int, value: Long) { + require(bitWidth in 1..64) { "writeScalar bitWidth must be 1..64, was $bitWidth" } + if (bitWidth <= 31) writeBits(bitWidth, value.toInt()) + else writeBitsLong(bitWidth, value) } /** Writes a length-prefixed UTF-8 string: `` (Ticket 05). */ diff --git a/kompact/src/commonTest/kotlin/ch/trancee/kompact/runtime/KompactRuntimeCheckedReadTest.kt b/kompact/src/commonTest/kotlin/ch/trancee/kompact/runtime/KompactRuntimeCheckedReadTest.kt index 6a354d0..bea56b9 100644 --- a/kompact/src/commonTest/kotlin/ch/trancee/kompact/runtime/KompactRuntimeCheckedReadTest.kt +++ b/kompact/src/commonTest/kotlin/ch/trancee/kompact/runtime/KompactRuntimeCheckedReadTest.kt @@ -40,281 +40,285 @@ class KompactRuntimeCheckedReadTest { assertEquals(KompactDecodeError.BoundsError, r.error) } - // === readInt8 / readUInt8 === + // === readScalar (1..32 bits) / readScalarLong (1..64 bits) === + // Replaces the per-width readInt8/readUInt8/readInt16/readUInt16/readInt32/ + // readUInt32/readInt64/readUInt64 accessors (Ticket 10 deepen). + // Sign/zero extension uses Long-arithmetic shifts — bit-identical to the + // legacy accessors' Int/Long shift logic. + + // --- 8-bit width (readScalar) --- @Test - fun readInt8_success_positiveValue() { + fun readScalar_width8_signed_positiveValue() { val buf = ByteArray(1) { 0 } KompactRuntime.writeBits(buf, 0, 8, 42) - val r = KompactRuntime.readInt8(buf, 0, 8) + val r = KompactRuntime.readScalar(buf, 0, 8, signed = true) assertTrue(r.isSuccess) - assertEquals(42.toByte(), r.getOrThrow()) + assertEquals(42, r.getOrThrow()) } @Test - fun readInt8_success_negativeValue_signExtended() { + fun readScalar_width8_signed_negativeValue_signExtended() { // 8-bit value 0xC8 (200 unsigned) → -56 signed val buf = byteArrayOf(0xC8.toByte()) - val r = KompactRuntime.readInt8(buf, 0, 8) + val r = KompactRuntime.readScalar(buf, 0, 8, signed = true) assertTrue(r.isSuccess) - assertEquals((-56).toByte(), r.getOrThrow()) + assertEquals(-56, r.getOrThrow()) } @Test - fun readInt8_success_smallBitWidth_signExtended() { + fun readScalar_width8_signed_smallBitWidth_signExtended() { // 4-bit value 0b1111 (15 unsigned) → -1 signed val buf = byteArrayOf(0x0F.toByte()) - val r = KompactRuntime.readInt8(buf, 0, 4) + val r = KompactRuntime.readScalar(buf, 0, 4, signed = true) assertTrue(r.isSuccess) - assertEquals((-1).toByte(), r.getOrThrow()) + assertEquals(-1, r.getOrThrow()) } @Test - fun readInt8_success_smallBitWidth_positive() { + fun readScalar_width8_signed_smallBitWidth_positive() { val buf = ByteArray(1) { 0 } KompactRuntime.writeBits(buf, 0, 4, 5) - val r = KompactRuntime.readInt8(buf, 0, 4) + val r = KompactRuntime.readScalar(buf, 0, 4, signed = true) assertTrue(r.isSuccess) - assertEquals(5.toByte(), r.getOrThrow()) + assertEquals(5, r.getOrThrow()) } @Test - fun readInt8_boundsError_shortBuffer() { + fun readScalar_boundsError_shortBuffer() { val buf = ByteArray(1) { 0 } - val r = KompactRuntime.readInt8(buf, 4, 8) // needs 12 bits, only 8 available + val r = KompactRuntime.readScalar(buf, 4, 8, signed = true) // needs 12 bits, only 8 available assertFalse(r.isSuccess) assertEquals(KompactDecodeError.BoundsError, r.error) } @Test - fun readInt8_boundsError_bitWidthTooLarge() { + fun readScalar_boundsError_bitWidthTooLarge() { val buf = ByteArray(1) { 0 } - val r = KompactRuntime.readInt8(buf, 0, 9) // max 8 + val r = KompactRuntime.readScalar(buf, 0, 33, signed = true) // max 32 assertFalse(r.isSuccess) assertEquals(KompactDecodeError.BoundsError, r.error) } @Test - fun readUInt8_success_unsignedValue() { - // 0xFF as unsigned Byte → stored as -1, caller uses .toInt() and 0xFF - val r = KompactRuntime.readUInt8(byteArrayOf(0xFF.toByte()), 0, 8) + fun readScalar_width8_unsigned_value() { + // 0xFF as unsigned → 255 (zero-extended to Int) + val r = KompactRuntime.readScalar(byteArrayOf(0xFF.toByte()), 0, 8, signed = false) assertTrue(r.isSuccess) - val v = r.getOrThrow() - assertEquals(255, v.toInt() and 0xFF) + assertEquals(255, r.getOrThrow()) } @Test - fun readUInt8_success_smallBitWidth() { + fun readScalar_width8_unsigned_smallBitWidth() { val buf = ByteArray(1) { 0 } KompactRuntime.writeBits(buf, 0, 3, 7) - val r = KompactRuntime.readUInt8(buf, 0, 3) + val r = KompactRuntime.readScalar(buf, 0, 3, signed = false) assertTrue(r.isSuccess) - assertEquals(7.toByte(), r.getOrThrow()) + assertEquals(7, r.getOrThrow()) } @Test - fun readUInt8_boundsError_shortBuffer() { - val r = KompactRuntime.readUInt8(ByteArray(0), 0, 4) + fun readScalar_width8_unsigned_boundsError_shortBuffer() { + val r = KompactRuntime.readScalar(ByteArray(0), 0, 4, signed = false) assertFalse(r.isSuccess) assertEquals(KompactDecodeError.BoundsError, r.error) } - // === readInt16 / readUInt16 === + // --- 16-bit width (readScalar) --- @Test - fun readInt16_success_positiveValue() { + fun readScalar_width16_signed_positiveValue() { val buf = ByteArray(2) { 0 } KompactRuntime.writeBits(buf, 0, 16, 1024) - val r = KompactRuntime.readInt16(buf, 0, 16) + val r = KompactRuntime.readScalar(buf, 0, 16, signed = true) assertTrue(r.isSuccess) - assertEquals(1024.toShort(), r.getOrThrow()) + assertEquals(1024, r.getOrThrow()) } @Test - fun readInt16_success_negativeValue_signExtended() { + fun readScalar_width16_signed_negativeValue_signExtended() { // 16-bit value 0x8000 (32768 unsigned) → -32768 signed val buf = ByteArray(2) { 0 } buf[0] = 0x00.toByte() buf[1] = 0x80.toByte() // LSB-first: low byte = 0x00, high byte = 0x80 - val r = KompactRuntime.readInt16(buf, 0, 16) + val r = KompactRuntime.readScalar(buf, 0, 16, signed = true) assertTrue(r.isSuccess) - assertEquals(Short.MIN_VALUE, r.getOrThrow()) + assertEquals(Short.MIN_VALUE.toInt(), r.getOrThrow()) } @Test - fun readInt16_success_smallBitWidth() { + fun readScalar_width16_signed_smallBitWidth() { // 5-bit value 0b11111 (31 unsigned) → -1 signed val buf = byteArrayOf(0x1F) - val r = KompactRuntime.readInt16(buf, 0, 5) + val r = KompactRuntime.readScalar(buf, 0, 5, signed = true) assertTrue(r.isSuccess) - assertEquals((-1).toShort(), r.getOrThrow()) + assertEquals(-1, r.getOrThrow()) } @Test - fun readInt16_boundsError_shortBuffer() { + fun readScalar_width16_signed_boundsError_shortBuffer() { val buf = ByteArray(1) { 0 } - val r = KompactRuntime.readInt16(buf, 4, 16) // needs 20 bits, only 8 available + val r = KompactRuntime.readScalar(buf, 4, 16, signed = true) // needs 20 bits, only 8 available assertFalse(r.isSuccess) assertEquals(KompactDecodeError.BoundsError, r.error) } @Test - fun readUInt16_success_unsignedValue() { + fun readScalar_width16_unsigned_value() { val buf = ByteArray(2) { 0 } buf[0] = 0xFF.toByte() buf[1] = 0xFF.toByte() - val r = KompactRuntime.readUInt16(buf, 0, 16) + val r = KompactRuntime.readScalar(buf, 0, 16, signed = false) assertTrue(r.isSuccess) - val v = r.getOrThrow() - assertEquals(65535, v.toInt() and 0xFFFF) + assertEquals(65535, r.getOrThrow()) } - // === readInt32 / readUInt32 === + // --- 32-bit width (readScalar) --- @Test - fun readInt32_success_positiveValue() { + fun readScalar_width32_signed_positiveValue() { val buf = ByteArray(4) { 0 } KompactRuntime.writeBitsLong(buf, 0, 32, 1_000_000L) - val r = KompactRuntime.readInt32(buf, 0, 32) + val r = KompactRuntime.readScalar(buf, 0, 32, signed = true) assertTrue(r.isSuccess) assertEquals(1_000_000, r.getOrThrow()) } @Test - fun readInt32_success_negativeValue() { + fun readScalar_width32_signed_negativeValue() { val buf = ByteArray(4) { 0 } KompactRuntime.writeBitsLong(buf, 0, 32, 0xFF80_0000L) - val r = KompactRuntime.readInt32(buf, 0, 32) + val r = KompactRuntime.readScalar(buf, 0, 32, signed = true) assertTrue(r.isSuccess) assertEquals(-8_388_608, r.getOrThrow()) } @Test - fun readInt32_success_maxInt() { + fun readScalar_width32_signed_maxInt() { val buf = ByteArray(4) { 0 } KompactRuntime.writeBitsLong(buf, 0, 32, Int.MAX_VALUE.toLong()) - val r = KompactRuntime.readInt32(buf, 0, 32) + val r = KompactRuntime.readScalar(buf, 0, 32, signed = true) assertTrue(r.isSuccess) assertEquals(Int.MAX_VALUE, r.getOrThrow()) } @Test - fun readInt32_success_minInt() { + fun readScalar_width32_signed_minInt() { val buf = ByteArray(4) { 0 } KompactRuntime.writeBitsLong(buf, 0, 32, Int.MIN_VALUE.toLong()) - val r = KompactRuntime.readInt32(buf, 0, 32) + val r = KompactRuntime.readScalar(buf, 0, 32, signed = true) assertTrue(r.isSuccess) assertEquals(Int.MIN_VALUE, r.getOrThrow()) } @Test - fun readInt32_success_smallBitWidth() { + fun readScalar_width32_signed_smallBitWidth() { // 10-bit value 1008 val buf = ByteArray(2) { 0 } KompactRuntime.writeBits(buf, 0, 10, 1008) - val r = KompactRuntime.readInt32(buf, 0, 10) + val r = KompactRuntime.readScalar(buf, 0, 10, signed = true) assertTrue(r.isSuccess) assertEquals(-16, r.getOrThrow()) } @Test - fun readInt32_boundsError_shortBuffer() { + fun readScalar_width32_signed_boundsError_shortBuffer() { val buf = ByteArray(3) { 0 } - val r = KompactRuntime.readInt32(buf, 0, 32) // needs 32 bits, only 24 available + val r = KompactRuntime.readScalar(buf, 0, 32, signed = true) // needs 32 bits, only 24 available assertFalse(r.isSuccess) assertEquals(KompactDecodeError.BoundsError, r.error) } @Test - fun readUInt32_success_unsignedValue() { + fun readScalar_width32_unsigned_value() { val buf = ByteArray(4) { 0 } KompactRuntime.writeBitsLong(buf, 0, 32, 0xFFFF_FFFFL) - val r = KompactRuntime.readUInt32(buf, 0, 32) + val r = KompactRuntime.readScalar(buf, 0, 32, signed = false) assertTrue(r.isSuccess) assertEquals(-1, r.getOrThrow()) // 0xFFFFFFFF as signed Int = -1 assertEquals(0xFFFF_FFFFL, r.getOrThrow().toLong() and 0xFFFF_FFFFL) } - // === readInt64 / readUInt64 === + // --- 64-bit width (readScalarLong) --- @Test - fun readInt64_success_maxValue() { + fun readScalarLong_width64_signed_maxValue() { val buf = ByteArray(8) { 0 } KompactRuntime.writeBitsLong(buf, 0, 64, Long.MAX_VALUE) - val r = KompactRuntime.readInt64(buf, 0, 64) + val r = KompactRuntime.readScalarLong(buf, 0, 64, signed = true) assertTrue(r.isSuccess) assertEquals(Long.MAX_VALUE, r.getOrThrow()) } @Test - fun readInt64_success_nearMinValue() { + fun readScalarLong_width64_signed_nearMinValue() { // Long.MIN_VALUE (0x8000...) is in the failure sentinel range (documented tradeoff). // 0x8400_0000_0000_0000 is the first representable success value below 0. val value = Long.MIN_VALUE + (1L shl 58) val buf = ByteArray(8) { 0 } KompactRuntime.writeBitsLong(buf, 0, 64, value) - val r = KompactRuntime.readInt64(buf, 0, 64) + val r = KompactRuntime.readScalarLong(buf, 0, 64, signed = true) assertTrue(r.isSuccess) assertEquals(value, r.getOrThrow()) } @Test - fun readInt64_success_negativeValue() { + fun readScalarLong_width64_signed_negativeValue() { val buf = ByteArray(8) { 0 } KompactRuntime.writeBitsLong(buf, 0, 64, -1L) - val r = KompactRuntime.readInt64(buf, 0, 64) + val r = KompactRuntime.readScalarLong(buf, 0, 64, signed = true) assertTrue(r.isSuccess) assertEquals(-1L, r.getOrThrow()) } @Test - fun readInt64_success_smallBitWidth_signExtended() { + fun readScalarLong_width64_signed_smallBitWidth_signExtended() { // 4-bit value 0b1111 (15 unsigned) → -1 signed val buf = byteArrayOf(0x0F.toByte()) - val r = KompactRuntime.readInt64(buf, 0, 4) + val r = KompactRuntime.readScalarLong(buf, 0, 4, signed = true) assertTrue(r.isSuccess) assertEquals(-1L, r.getOrThrow()) } @Test - fun readInt64_success_largePositive() { + fun readScalarLong_width64_signed_largePositive() { // 0x4000_0000_0000_0000 is outside the LongResult failure sentinel range val buf = ByteArray(8) { 0 } KompactRuntime.writeBitsLong(buf, 0, 64, 0x4000_0000_0000_0000L) - val r = KompactRuntime.readInt64(buf, 0, 64) + val r = KompactRuntime.readScalarLong(buf, 0, 64, signed = true) assertTrue(r.isSuccess) assertEquals(0x4000_0000_0000_0000L, r.getOrThrow()) } @Test - fun readInt64_boundsError_shortBuffer() { + fun readScalarLong_width64_signed_boundsError_shortBuffer() { val buf = ByteArray(7) { 0 } - val r = KompactRuntime.readInt64(buf, 0, 64) // needs 64 bits, only 56 available + val r = KompactRuntime.readScalarLong(buf, 0, 64, signed = true) // needs 64 bits, only 56 available assertFalse(r.isSuccess) assertEquals(KompactDecodeError.BoundsError, r.error) } @Test - fun readUInt64_success_unsignedValue() { + fun readScalarLong_width64_unsigned_value() { val buf = ByteArray(8) { 0 } KompactRuntime.writeBitsLong(buf, 0, 64, -1L) - val r = KompactRuntime.readUInt64(buf, 0, 64) + val r = KompactRuntime.readScalarLong(buf, 0, 64, signed = false) assertTrue(r.isSuccess) assertEquals(-1L, r.getOrThrow()) } @Test - fun readUInt64_success_smallBitWidth() { + fun readScalarLong_width64_unsigned_smallBitWidth() { val buf = ByteArray(1) { 0 } KompactRuntime.writeBits(buf, 0, 3, 5) - val r = KompactRuntime.readUInt64(buf, 0, 3) + val r = KompactRuntime.readScalarLong(buf, 0, 3, signed = false) assertTrue(r.isSuccess) assertEquals(5L, r.getOrThrow()) } @Test - fun readUInt64_boundsError_shortBuffer() { - val r = KompactRuntime.readUInt64(ByteArray(4), 0, 64) + fun readScalarLong_width64_unsigned_boundsError_shortBuffer() { + val r = KompactRuntime.readScalarLong(ByteArray(4), 0, 64, signed = false) assertFalse(r.isSuccess) assertEquals(KompactDecodeError.BoundsError, r.error) } @@ -429,8 +433,8 @@ class KompactRuntimeCheckedReadTest { fun boundsChecks_survive256MiBBuffer() { val buf = ByteArray(1 shl 28) // 268_435_456 bytes -> 2^31 bits (Int-overflow threshold) assertTrue(KompactRuntime.readBool(buf, 0).isSuccess, "readBool must succeed on a 256 MiB buffer") - assertTrue(KompactRuntime.readUInt8(buf, 0, 8).isSuccess, "readUInt8 must succeed") - assertTrue(KompactRuntime.readInt32(buf, 0, 32).isSuccess, "readInt32 must succeed") + assertTrue(KompactRuntime.readScalar(buf, 0, 8, signed = false).isSuccess, "readScalar/8 unsigned must succeed") + assertTrue(KompactRuntime.readScalar(buf, 0, 32, signed = true).isSuccess, "readScalar/32 signed must succeed") assertTrue(KompactRuntime.readFloat(buf, 0).isSuccess, "readFloat must succeed") assertTrue(KompactRuntime.readDouble(buf, 0).isSuccess, "readDouble must succeed") } diff --git a/kompact/src/commonTest/kotlin/ch/trancee/kompact/runtime/KompactWriterTest.kt b/kompact/src/commonTest/kotlin/ch/trancee/kompact/runtime/KompactWriterTest.kt index 89d063e..2e909b2 100644 --- a/kompact/src/commonTest/kotlin/ch/trancee/kompact/runtime/KompactWriterTest.kt +++ b/kompact/src/commonTest/kotlin/ch/trancee/kompact/runtime/KompactWriterTest.kt @@ -29,39 +29,39 @@ class KompactWriterTest { } @Test - fun writeInt8_signExtended_roundTrip() { + fun writeScalar_width8_signed_roundTrip() { val w = KompactWriter() - w.writeInt(8, -5) + w.writeScalar(8, -5L) val buf = w.build() - val r = KompactRuntime.readInt8(buf, 0, 8) + val r = KompactRuntime.readScalar(buf, 0, 8, signed = true) assertTrue(r.isSuccess) - assertEquals(-5, r.getOrThrow().toInt()) + assertEquals(-5, r.getOrThrow()) } @Test - fun writeUInt16_roundTrip() { + fun writeScalar_width16_unsigned_roundTrip() { val w = KompactWriter() - w.writeUInt(16, 1023) + w.writeScalar(16, 1023L) val buf = w.build() - val r = KompactRuntime.readUInt16(buf, 0, 16) + val r = KompactRuntime.readScalar(buf, 0, 16, signed = false) assertTrue(r.isSuccess) - assertEquals(1023, r.getOrThrow().toInt()) + assertEquals(1023, r.getOrThrow()) } @Test - fun writeInt32_negative_roundTrip() { + fun writeScalar_width32_signed_negative_roundTrip() { val w = KompactWriter() - w.writeInt(32, -1) + w.writeScalar(32, -1L) val buf = w.build() - val r = KompactRuntime.readInt32(buf, 0, 32) + val r = KompactRuntime.readScalar(buf, 0, 32, signed = true) assertTrue(r.isSuccess) - assertEquals(-1, r.getOrThrow().toInt()) + assertEquals(-1, r.getOrThrow()) } @Test - fun writeEnum_roundTrip() { + fun writeScalar_width4_roundTrip() { val w = KompactWriter() - w.writeEnum(width = 4, code = 7) + w.writeScalar(bitWidth = 4, value = 7L) val buf = w.build() assertEquals(7, KompactRuntime.readBits(buf, 0, 4)) } @@ -95,8 +95,8 @@ class KompactWriterTest { fun writeNested_emitsPrefixThenChildBytes() { val w = KompactWriter() w.writeNested(lengthPrefixWidth = 16) { - writeInt(8, 0xAB) - writeInt(8, 0xCD) + writeScalar(8, 0xABL) + writeScalar(8, 0xCDL) } val buf = w.build() // 2-byte LE prefix (length=2) + 2 payload bytes. @@ -108,7 +108,6 @@ class KompactWriterTest { assertEquals(16, bitLen) } - @Test fun writeRepeated_emitsCountThenElements() { val w = KompactWriter() diff --git a/kompact/src/iosMain/kotlin/ch/trancee/kompact/runtime/KompactResult.kt b/kompact/src/iosMain/kotlin/ch/trancee/kompact/runtime/KompactResult.kt index 22ec32e..86dd7b9 100644 --- a/kompact/src/iosMain/kotlin/ch/trancee/kompact/runtime/KompactResult.kt +++ b/kompact/src/iosMain/kotlin/ch/trancee/kompact/runtime/KompactResult.kt @@ -11,7 +11,7 @@ public actual value class ByteResult(public actual val packed: Long) { if (isSuccess) null else decodeErrorFromSmallBits(packed) public actual fun getOrThrow(): Byte = if (isSuccess) (packed and RESULT_VALUE_MASK).toByte() - else throw KompactDecodeException(decodeErrorFromSmallBits(packed)) + else throwSmallFailure(packed) public actual companion object { public actual fun success(value: Byte): ByteResult = ByteResult(encodeSmallSuccess(value.toLong())) @@ -27,7 +27,7 @@ public actual value class ShortResult(public actual val packed: Long) { if (isSuccess) null else decodeErrorFromSmallBits(packed) public actual fun getOrThrow(): Short = if (isSuccess) (packed and RESULT_VALUE_MASK).toShort() - else throw KompactDecodeException(decodeErrorFromSmallBits(packed)) + else throwSmallFailure(packed) public actual companion object { public actual fun success(value: Short): ShortResult = ShortResult(encodeSmallSuccess(value.toLong())) @@ -43,7 +43,7 @@ public actual value class IntResult(public actual val packed: Long) { if (isSuccess) null else decodeErrorFromSmallBits(packed) public actual fun getOrThrow(): Int = if (isSuccess) (packed and RESULT_VALUE_MASK).toInt() - else throw KompactDecodeException(decodeErrorFromSmallBits(packed)) + else throwSmallFailure(packed) public actual companion object { public actual fun success(value: Int): IntResult = IntResult(encodeSmallSuccess(value.toLong())) @@ -59,7 +59,7 @@ public actual value class FloatResult(public actual val packed: Long) { if (isSuccess) null else decodeErrorFromSmallBits(packed) public actual fun getOrThrow(): Float = if (isSuccess) Float.fromBits((packed and RESULT_VALUE_MASK).toInt()) - else throw KompactDecodeException(decodeErrorFromSmallBits(packed)) + else throwSmallFailure(packed) public actual companion object { public actual fun success(value: Float): FloatResult = FloatResult(encodeSmallSuccess(encodeFloatSuccess(value))) @@ -75,7 +75,7 @@ public actual value class BooleanResult(public actual val packed: Long) { if (isSuccess) null else decodeErrorFromSmallBits(packed) public actual fun getOrThrow(): Boolean = if (isSuccess) (packed and RESULT_VALUE_MASK) != 0L - else throw KompactDecodeException(decodeErrorFromSmallBits(packed)) + else throwSmallFailure(packed) public actual companion object { public actual fun success(value: Boolean): BooleanResult = BooleanResult(encodeSmallSuccess(if (value) 1L else 0L)) @@ -93,7 +93,7 @@ public actual value class LongResult(public actual val packed: Long) { if (isSuccess) null else decodeLongError(packed) public actual fun getOrThrow(): Long = if (isSuccess) packed - else throw KompactDecodeException(decodeLongError(packed)) + else throwLongFailure(packed) public actual companion object { public actual fun success(value: Long): LongResult = LongResult(value) public actual fun failure(error: KompactDecodeError): LongResult = @@ -110,7 +110,7 @@ public actual value class DoubleResult(public actual val packed: Long) { if (isSuccess) null else decodeDoubleError(packed) public actual fun getOrThrow(): Double = if (isSuccess) Double.fromBits(packed) - else throw KompactDecodeException(decodeDoubleError(packed)) + else throwDoubleFailure(packed) public actual companion object { public actual fun success(value: Double): DoubleResult = DoubleResult(encodeDoubleSuccess(value)) diff --git a/kompact/src/jvmMain/kotlin/ch/trancee/kompact/runtime/KompactResult.kt b/kompact/src/jvmMain/kotlin/ch/trancee/kompact/runtime/KompactResult.kt index 9bd0639..98d428e 100644 --- a/kompact/src/jvmMain/kotlin/ch/trancee/kompact/runtime/KompactResult.kt +++ b/kompact/src/jvmMain/kotlin/ch/trancee/kompact/runtime/KompactResult.kt @@ -13,7 +13,7 @@ public actual value class ByteResult(public actual val packed: Long) { if (isSuccess) null else decodeErrorFromSmallBits(packed) public actual fun getOrThrow(): Byte = if (isSuccess) (packed and RESULT_VALUE_MASK).toByte() - else throw KompactDecodeException(decodeErrorFromSmallBits(packed)) + else throwSmallFailure(packed) public actual companion object { public actual fun success(value: Byte): ByteResult = ByteResult(encodeSmallSuccess(value.toLong())) @@ -30,7 +30,7 @@ public actual value class ShortResult(public actual val packed: Long) { if (isSuccess) null else decodeErrorFromSmallBits(packed) public actual fun getOrThrow(): Short = if (isSuccess) (packed and RESULT_VALUE_MASK).toShort() - else throw KompactDecodeException(decodeErrorFromSmallBits(packed)) + else throwSmallFailure(packed) public actual companion object { public actual fun success(value: Short): ShortResult = ShortResult(encodeSmallSuccess(value.toLong())) @@ -47,7 +47,7 @@ public actual value class IntResult(public actual val packed: Long) { if (isSuccess) null else decodeErrorFromSmallBits(packed) public actual fun getOrThrow(): Int = if (isSuccess) (packed and RESULT_VALUE_MASK).toInt() - else throw KompactDecodeException(decodeErrorFromSmallBits(packed)) + else throwSmallFailure(packed) public actual companion object { public actual fun success(value: Int): IntResult = IntResult(encodeSmallSuccess(value.toLong())) @@ -64,7 +64,7 @@ public actual value class FloatResult(public actual val packed: Long) { if (isSuccess) null else decodeErrorFromSmallBits(packed) public actual fun getOrThrow(): Float = if (isSuccess) Float.fromBits((packed and RESULT_VALUE_MASK).toInt()) - else throw KompactDecodeException(decodeErrorFromSmallBits(packed)) + else throwSmallFailure(packed) public actual companion object { public actual fun success(value: Float): FloatResult = FloatResult(encodeSmallSuccess(encodeFloatSuccess(value))) @@ -81,7 +81,7 @@ public actual value class BooleanResult(public actual val packed: Long) { if (isSuccess) null else decodeErrorFromSmallBits(packed) public actual fun getOrThrow(): Boolean = if (isSuccess) (packed and RESULT_VALUE_MASK) != 0L - else throw KompactDecodeException(decodeErrorFromSmallBits(packed)) + else throwSmallFailure(packed) public actual companion object { public actual fun success(value: Boolean): BooleanResult = BooleanResult(encodeSmallSuccess(if (value) 1L else 0L)) @@ -99,7 +99,7 @@ public actual value class LongResult(public actual val packed: Long) { if (isSuccess) null else decodeLongError(packed) public actual fun getOrThrow(): Long = if (isSuccess) packed - else throw KompactDecodeException(decodeLongError(packed)) + else throwLongFailure(packed) public actual companion object { public actual fun success(value: Long): LongResult = LongResult(value) public actual fun failure(error: KompactDecodeError): LongResult = @@ -116,7 +116,7 @@ public actual value class DoubleResult(public actual val packed: Long) { if (isSuccess) null else decodeDoubleError(packed) public actual fun getOrThrow(): Double = if (isSuccess) Double.fromBits(packed) - else throw KompactDecodeException(decodeDoubleError(packed)) + else throwDoubleFailure(packed) public actual companion object { public actual fun success(value: Double): DoubleResult = DoubleResult(encodeDoubleSuccess(value)) From 2f6f552abefa0ab4b490ebc750632442b5c35692 Mon Sep 17 00:00:00 2001 From: Philipp Grosswiler Date: Sat, 5 Sep 2026 17:59:10 +0200 Subject: [PATCH 20/51] ci: run :kompact:apiCheck on macOS + jvmTest on Linux --- .github/workflows/ci.yml | 42 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..9dcac56 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,42 @@ +name: CI + +# Validates the public ABI goldens and runs the JVM test suite. +# apiCheck enforces both kompact.api (JVM) and kompact.klib.api (merged ios klib). +# ios klib ABI inference only succeeds on Apple hosts, so the klib golden is +# only meaningfully validated on the macOS job — that is its gate. + +on: + push: + branches: + - main + - master + - 'feat/**' + pull_request: + branches: + - main + - master + +jobs: + api-check: + name: apiCheck (macOS) + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: 21 + cache: gradle + - run: ./gradlew :kompact:apiCheck --no-daemon + + jvm-test: + name: jvmTest (Linux) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: 21 + cache: gradle + - run: ./gradlew :kompact:jvmTest --no-daemon From 061721bfa94fbbe950569806c4283fbbbfcbd159 Mon Sep 17 00:00:00 2001 From: Philipp Grosswiler Date: Sat, 5 Sep 2026 18:08:12 +0200 Subject: [PATCH 21/51] ci: add macOS golden-regen workflow (workflow_dispatch) --- .github/workflows/regen-goldens.yml | 35 +++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 .github/workflows/regen-goldens.yml diff --git a/.github/workflows/regen-goldens.yml b/.github/workflows/regen-goldens.yml new file mode 100644 index 0000000..191a597 --- /dev/null +++ b/.github/workflows/regen-goldens.yml @@ -0,0 +1,35 @@ +name: Regen Goldens + +# Regenerates the BCV ABI goldens (kompact.api + kompact.klib.api) on macOS. +# macOS is the only host where ios klib ABI inference succeeds, so kompact.klib.api +# can only be produced here. Non-Apple contributors invoke this workflow to refresh +# the goldens (apiDump overwrites api/ in place), then commit the result. Manual +# (workflow_dispatch) only — it does NOT run on every push; the apiCheck gate +# lives in ci.yml. + +on: + workflow_dispatch: + inputs: + args: + description: 'Gradle task args to regenerate goldens (default: apiDump)' + default: ':kompact:apiDump' + type: string + +jobs: + regen: + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: 21 + cache: gradle + - name: Regenerate ABI goldens + run: ./gradlew ${{ inputs.args }} --no-daemon + - name: Upload regenerated goldens + uses: actions/upload-artifact@v4 + with: + name: api-goldens + path: kompact/api/ + retention-days: 3 From 8d8eeaa558e2071a586468e099fb758b9a74e3bf Mon Sep 17 00:00:00 2001 From: Philipp Grosswiler Date: Sat, 5 Sep 2026 18:19:46 +0200 Subject: [PATCH 22/51] ci: temporarily add macOS api-dump job (regenerate+verify+upload; feat/* only) --- .github/workflows/ci.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9dcac56..19e3011 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -40,3 +40,22 @@ jobs: java-version: 21 cache: gradle - run: ./gradlew :kompact:jvmTest --no-daemon + api-dump: + name: Regenerate goldens (macOS, feat/* only) + runs-on: macos-latest + if: startsWith(github.ref, 'refs/heads/feat/') + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: 21 + cache: gradle + # Regenerate ABI goldens (only macOS infers the ios klib ABI). apiCheck + # immediately after self-verifies the freshly-dumped goldens match source. + - run: ./gradlew :kompact:apiDump :kompact:apiCheck --no-daemon + - uses: actions/upload-artifact@v4 + with: + name: api-goldens + path: kompact/api/ + retention-days: 1 From 3bf46cb454d5c95f9a8e2db0834c78db18ae8764 Mon Sep 17 00:00:00 2001 From: Philipp Grosswiler Date: Sat, 5 Sep 2026 18:32:31 +0200 Subject: [PATCH 23/51] ci: drop apiCheck self-step from temp api-dump (implicit-dep trap) --- .github/workflows/ci.yml | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 19e3011..39f50d7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -51,9 +51,14 @@ jobs: distribution: temurin java-version: 21 cache: gradle - # Regenerate ABI goldens (only macOS infers the ios klib ABI). apiCheck - # immediately after self-verifies the freshly-dumped goldens match source. - - run: ./gradlew :kompact:apiDump :kompact:apiCheck --no-daemon + # Regenerate ABI goldens. Only macOS can infer the ios klib ABI, so this + # job runs on macos-latest and is gated to feat/* pushes. We intentionally + # do NOT call :kompact:apiCheck here: apiCheck is the gate, and invoking + # it in the same gradle build as apiDump triggers Gradle's implicit- + # dependency validation failure (jvmApiCheck reads kompact.api without + # declaring a dep on jvmApiDump). The dedicated api-check job validates + # the committed goldens. + - run: ./gradlew :kompact:apiDump --no-daemon - uses: actions/upload-artifact@v4 with: name: api-goldens From e93362ac53dd8159b6ce25fd959daac633c8ac71 Mon Sep 17 00:00:00 2001 From: Philipp Grosswiler Date: Sat, 5 Sep 2026 18:35:10 +0200 Subject: [PATCH 24/51] fix: regenerate kompact.klib.api on macOS + remove temp api-dump ci job --- .github/workflows/ci.yml | 24 ------------- kompact/api/kompact.klib.api | 65 +++++++++++++++++++++++++++++------- 2 files changed, 53 insertions(+), 36 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 39f50d7..9dcac56 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -40,27 +40,3 @@ jobs: java-version: 21 cache: gradle - run: ./gradlew :kompact:jvmTest --no-daemon - api-dump: - name: Regenerate goldens (macOS, feat/* only) - runs-on: macos-latest - if: startsWith(github.ref, 'refs/heads/feat/') - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-java@v4 - with: - distribution: temurin - java-version: 21 - cache: gradle - # Regenerate ABI goldens. Only macOS can infer the ios klib ABI, so this - # job runs on macos-latest and is gated to feat/* pushes. We intentionally - # do NOT call :kompact:apiCheck here: apiCheck is the gate, and invoking - # it in the same gradle build as apiDump triggers Gradle's implicit- - # dependency validation failure (jvmApiCheck reads kompact.api without - # declaring a dep on jvmApiDump). The dedicated api-check job validates - # the committed goldens. - - run: ./gradlew :kompact:apiDump --no-daemon - - uses: actions/upload-artifact@v4 - with: - name: api-goldens - path: kompact/api/ - retention-days: 1 diff --git a/kompact/api/kompact.klib.api b/kompact/api/kompact.klib.api index 2f33f21..cdc8083 100644 --- a/kompact/api/kompact.klib.api +++ b/kompact/api/kompact.klib.api @@ -7,12 +7,24 @@ // Library unique name: open annotation class ch.trancee.kompact.runtime/KompactField : kotlin/Annotation { // ch.trancee.kompact.runtime/KompactField|null[0] - constructor (kotlin/Int, kotlin/Int) // ch.trancee.kompact.runtime/KompactField.|(kotlin.Int;kotlin.Int){}[0] + constructor (kotlin/Int, kotlin/Int, kotlin/Int = ..., kotlin/Boolean = ..., kotlin/Int = ..., kotlin/Int = ..., kotlin/String = ..., kotlin/Boolean = ...) // ch.trancee.kompact.runtime/KompactField.|(kotlin.Int;kotlin.Int;kotlin.Int;kotlin.Boolean;kotlin.Int;kotlin.Int;kotlin.String;kotlin.Boolean){}[0] final val bitOffset // ch.trancee.kompact.runtime/KompactField.bitOffset|{}bitOffset[0] final fun (): kotlin/Int // ch.trancee.kompact.runtime/KompactField.bitOffset.|(){}[0] final val bitWidth // ch.trancee.kompact.runtime/KompactField.bitWidth|{}bitWidth[0] final fun (): kotlin/Int // ch.trancee.kompact.runtime/KompactField.bitWidth.|(){}[0] + final val defaultValue // ch.trancee.kompact.runtime/KompactField.defaultValue|{}defaultValue[0] + final fun (): kotlin/String // ch.trancee.kompact.runtime/KompactField.defaultValue.|(){}[0] + final val enumWidth // ch.trancee.kompact.runtime/KompactField.enumWidth|{}enumWidth[0] + final fun (): kotlin/Int // ch.trancee.kompact.runtime/KompactField.enumWidth.|(){}[0] + final val isNested // ch.trancee.kompact.runtime/KompactField.isNested|{}isNested[0] + final fun (): kotlin/Boolean // ch.trancee.kompact.runtime/KompactField.isNested.|(){}[0] + final val isVersionField // ch.trancee.kompact.runtime/KompactField.isVersionField|{}isVersionField[0] + final fun (): kotlin/Boolean // ch.trancee.kompact.runtime/KompactField.isVersionField.|(){}[0] + final val lengthPrefixWidth // ch.trancee.kompact.runtime/KompactField.lengthPrefixWidth|{}lengthPrefixWidth[0] + final fun (): kotlin/Int // ch.trancee.kompact.runtime/KompactField.lengthPrefixWidth.|(){}[0] + final val repeatCountWidth // ch.trancee.kompact.runtime/KompactField.repeatCountWidth|{}repeatCountWidth[0] + final fun (): kotlin/Int // ch.trancee.kompact.runtime/KompactField.repeatCountWidth.|(){}[0] } open annotation class ch.trancee.kompact.runtime/KompactModel : kotlin/Annotation { // ch.trancee.kompact.runtime/KompactModel|null[0] @@ -26,6 +38,25 @@ final class ch.trancee.kompact.runtime/KompactDecodeException : kotlin/RuntimeEx final fun (): ch.trancee.kompact.runtime/KompactDecodeError // ch.trancee.kompact.runtime/KompactDecodeException.error.|(){}[0] } +final class ch.trancee.kompact.runtime/KompactWriter { // ch.trancee.kompact.runtime/KompactWriter|null[0] + constructor () // ch.trancee.kompact.runtime/KompactWriter.|(){}[0] + + final var bitCursor // ch.trancee.kompact.runtime/KompactWriter.bitCursor|{}bitCursor[0] + final fun (): kotlin/Int // ch.trancee.kompact.runtime/KompactWriter.bitCursor.|(){}[0] + + final fun build(): kotlin/ByteArray // ch.trancee.kompact.runtime/KompactWriter.build|build(){}[0] + final fun writeBits(kotlin/Int, kotlin/Int) // ch.trancee.kompact.runtime/KompactWriter.writeBits|writeBits(kotlin.Int;kotlin.Int){}[0] + final fun writeBitsLong(kotlin/Int, kotlin/Long) // ch.trancee.kompact.runtime/KompactWriter.writeBitsLong|writeBitsLong(kotlin.Int;kotlin.Long){}[0] + final fun writeBlob(kotlin/Int, kotlin/ByteArray) // ch.trancee.kompact.runtime/KompactWriter.writeBlob|writeBlob(kotlin.Int;kotlin.ByteArray){}[0] + final fun writeBool(kotlin/Boolean) // ch.trancee.kompact.runtime/KompactWriter.writeBool|writeBool(kotlin.Boolean){}[0] + final fun writeNested(kotlin/Int = ..., kotlin/Function1) // ch.trancee.kompact.runtime/KompactWriter.writeNested|writeNested(kotlin.Int;kotlin.Function1){}[0] + final fun writeRepeated(kotlin/Int, kotlin/Int = ..., kotlin/Function1) // ch.trancee.kompact.runtime/KompactWriter.writeRepeated|writeRepeated(kotlin.Int;kotlin.Int;kotlin.Function1){}[0] + final fun writeScalar(kotlin/Int, kotlin/Long) // ch.trancee.kompact.runtime/KompactWriter.writeScalar|writeScalar(kotlin.Int;kotlin.Long){}[0] + final fun writeString(kotlin/Int, kotlin/String) // ch.trancee.kompact.runtime/KompactWriter.writeString|writeString(kotlin.Int;kotlin.String){}[0] + + final object Companion // ch.trancee.kompact.runtime/KompactWriter.Companion|null[0] +} + final value class ch.trancee.kompact.generated/VehicleTelemetry { // ch.trancee.kompact.generated/VehicleTelemetry|null[0] constructor (kotlin/ByteArray) // ch.trancee.kompact.generated/VehicleTelemetry.|(kotlin.ByteArray){}[0] @@ -225,16 +256,26 @@ sealed class ch.trancee.kompact.runtime/KompactDecodeError { // ch.trancee.kompa final object TruncatedNested : ch.trancee.kompact.runtime/KompactDecodeError // ch.trancee.kompact.runtime/KompactDecodeError.TruncatedNested|null[0] } +final object ch.trancee.kompact.runtime/KompactFraming { // ch.trancee.kompact.runtime/KompactFraming|null[0] + final val VALID_PREFIX_WIDTHS // ch.trancee.kompact.runtime/KompactFraming.VALID_PREFIX_WIDTHS|{}VALID_PREFIX_WIDTHS[0] + final fun (): kotlin.collections/Set // ch.trancee.kompact.runtime/KompactFraming.VALID_PREFIX_WIDTHS.|(){}[0] + + final inline fun nestedRegionOrNull(kotlin/ByteArray, kotlin/Int, kotlin/Int): kotlin/Pair? // ch.trancee.kompact.runtime/KompactFraming.nestedRegionOrNull|nestedRegionOrNull(kotlin.ByteArray;kotlin.Int;kotlin.Int){}[0] + final inline fun readLengthPrefix(kotlin/ByteArray, kotlin/Int, kotlin/Int): kotlin/Int // ch.trancee.kompact.runtime/KompactFraming.readLengthPrefix|readLengthPrefix(kotlin.ByteArray;kotlin.Int;kotlin.Int){}[0] + final inline fun writeLengthPrefix(kotlin/ByteArray, kotlin/Int, kotlin/Int, kotlin/Int) // ch.trancee.kompact.runtime/KompactFraming.writeLengthPrefix|writeLengthPrefix(kotlin.ByteArray;kotlin.Int;kotlin.Int;kotlin.Int){}[0] +} + final object ch.trancee.kompact.runtime/KompactRuntime { // ch.trancee.kompact.runtime/KompactRuntime|null[0] - final fun readBits(kotlin/ByteArray, kotlin/Int, kotlin/Int): kotlin/Int // ch.trancee.kompact.runtime/KompactRuntime.readBits|readBits(kotlin.ByteArray;kotlin.Int;kotlin.Int){}[0] - final fun readBitsBoolean(kotlin/ByteArray, kotlin/Int): kotlin/Boolean // ch.trancee.kompact.runtime/KompactRuntime.readBitsBoolean|readBitsBoolean(kotlin.ByteArray;kotlin.Int){}[0] - final fun readBitsLong(kotlin/ByteArray, kotlin/Int, kotlin/Int): kotlin/Long // ch.trancee.kompact.runtime/KompactRuntime.readBitsLong|readBitsLong(kotlin.ByteArray;kotlin.Int;kotlin.Int){}[0] - final fun readBool(kotlin/ByteArray, kotlin/Int): ch.trancee.kompact.runtime/BooleanResult // ch.trancee.kompact.runtime/KompactRuntime.readBool|readBool(kotlin.ByteArray;kotlin.Int){}[0] - final fun readDouble(kotlin/ByteArray, kotlin/Int): ch.trancee.kompact.runtime/DoubleResult // ch.trancee.kompact.runtime/KompactRuntime.readDouble|readDouble(kotlin.ByteArray;kotlin.Int){}[0] - final fun readFloat(kotlin/ByteArray, kotlin/Int): ch.trancee.kompact.runtime/FloatResult // ch.trancee.kompact.runtime/KompactRuntime.readFloat|readFloat(kotlin.ByteArray;kotlin.Int){}[0] - final fun readScalar(kotlin/ByteArray, kotlin/Int, kotlin/Int, kotlin/Boolean): ch.trancee.kompact.runtime/IntResult // ch.trancee.kompact.runtime/KompactRuntime.readScalar|readScalar(kotlin.ByteArray;kotlin.Int;kotlin.Int;kotlin.Boolean){}[0] - final fun readScalarLong(kotlin/ByteArray, kotlin/Int, kotlin/Int, kotlin/Boolean): ch.trancee.kompact.runtime/LongResult // ch.trancee.kompact.runtime/KompactRuntime.readScalarLong|readScalarLong(kotlin.ByteArray;kotlin.Int;kotlin.Int;kotlin.Boolean){}[0] - final fun writeBits(kotlin/ByteArray, kotlin/Int, kotlin/Int, kotlin/Int) // ch.trancee.kompact.runtime/KompactRuntime.writeBits|writeBits(kotlin.ByteArray;kotlin.Int;kotlin.Int;kotlin.Int){}[0] - final fun writeBitsBoolean(kotlin/ByteArray, kotlin/Int, kotlin/Boolean) // ch.trancee.kompact.runtime/KompactRuntime.writeBitsBoolean|writeBitsBoolean(kotlin.ByteArray;kotlin.Int;kotlin.Boolean){}[0] - final fun writeBitsLong(kotlin/ByteArray, kotlin/Int, kotlin/Int, kotlin/Long) // ch.trancee.kompact.runtime/KompactRuntime.writeBitsLong|writeBitsLong(kotlin.ByteArray;kotlin.Int;kotlin.Int;kotlin.Long){}[0] + final inline fun fits(kotlin/ByteArray, kotlin/Int, kotlin/Int): kotlin/Boolean // ch.trancee.kompact.runtime/KompactRuntime.fits|fits(kotlin.ByteArray;kotlin.Int;kotlin.Int){}[0] + final inline fun readBits(kotlin/ByteArray, kotlin/Int, kotlin/Int): kotlin/Int // ch.trancee.kompact.runtime/KompactRuntime.readBits|readBits(kotlin.ByteArray;kotlin.Int;kotlin.Int){}[0] + final inline fun readBitsBoolean(kotlin/ByteArray, kotlin/Int): kotlin/Boolean // ch.trancee.kompact.runtime/KompactRuntime.readBitsBoolean|readBitsBoolean(kotlin.ByteArray;kotlin.Int){}[0] + final inline fun readBitsLong(kotlin/ByteArray, kotlin/Int, kotlin/Int): kotlin/Long // ch.trancee.kompact.runtime/KompactRuntime.readBitsLong|readBitsLong(kotlin.ByteArray;kotlin.Int;kotlin.Int){}[0] + final inline fun readBool(kotlin/ByteArray, kotlin/Int): ch.trancee.kompact.runtime/BooleanResult // ch.trancee.kompact.runtime/KompactRuntime.readBool|readBool(kotlin.ByteArray;kotlin.Int){}[0] + final inline fun readDouble(kotlin/ByteArray, kotlin/Int): ch.trancee.kompact.runtime/DoubleResult // ch.trancee.kompact.runtime/KompactRuntime.readDouble|readDouble(kotlin.ByteArray;kotlin.Int){}[0] + final inline fun readFloat(kotlin/ByteArray, kotlin/Int): ch.trancee.kompact.runtime/FloatResult // ch.trancee.kompact.runtime/KompactRuntime.readFloat|readFloat(kotlin.ByteArray;kotlin.Int){}[0] + final inline fun readScalar(kotlin/ByteArray, kotlin/Int, kotlin/Int, kotlin/Boolean): ch.trancee.kompact.runtime/IntResult // ch.trancee.kompact.runtime/KompactRuntime.readScalar|readScalar(kotlin.ByteArray;kotlin.Int;kotlin.Int;kotlin.Boolean){}[0] + final inline fun readScalarLong(kotlin/ByteArray, kotlin/Int, kotlin/Int, kotlin/Boolean): ch.trancee.kompact.runtime/LongResult // ch.trancee.kompact.runtime/KompactRuntime.readScalarLong|readScalarLong(kotlin.ByteArray;kotlin.Int;kotlin.Int;kotlin.Boolean){}[0] + final inline fun writeBits(kotlin/ByteArray, kotlin/Int, kotlin/Int, kotlin/Int) // ch.trancee.kompact.runtime/KompactRuntime.writeBits|writeBits(kotlin.ByteArray;kotlin.Int;kotlin.Int;kotlin.Int){}[0] + final inline fun writeBitsBoolean(kotlin/ByteArray, kotlin/Int, kotlin/Boolean) // ch.trancee.kompact.runtime/KompactRuntime.writeBitsBoolean|writeBitsBoolean(kotlin.ByteArray;kotlin.Int;kotlin.Boolean){}[0] + final inline fun writeBitsLong(kotlin/ByteArray, kotlin/Int, kotlin/Int, kotlin/Long) // ch.trancee.kompact.runtime/KompactRuntime.writeBitsLong|writeBitsLong(kotlin.ByteArray;kotlin.Int;kotlin.Int;kotlin.Long){}[0] } From f040c5035ff233bc5251f7d81a972e20cdef706a Mon Sep 17 00:00:00 2001 From: Philipp Grosswiler Date: Sat, 5 Sep 2026 20:09:16 +0200 Subject: [PATCH 25/51] docs: add user-facing docs (README + getting-started + api-reference + architecture + ci) + tutorial test Adds the missing front door for human consumers of :kompact: * README.md: one-paragraph pitch, where-to-go-next, status, license. * docs/getting-started.md: 16-bit telemetry tutorial with wire bytes (0xA5 0x40) pinned, verified end-to-end by GettingStartedTest in commonTest (KompactRuntime.readScalar / readBool round-trip + BoundsError path). * docs/api-reference.md: KompactRuntime / KompactWriter / KompactFraming / typed result value classes / KompactDecodeError / annotations; mirrors the source layout. * docs/architecture.md: LSB-first, zero-alloc, value classes, packed-Long error encoding (incl. LongResult sentinel band, Float/Double NaN payloads), framing contract, versioning. * docs/ci.md: how-to for the two workflows (apiCheck on macOS, jvmTest on Linux, manual regen-goldens), re-running locally, JDK 21 + Kotlin 2.4.10 + KMP targets jvm/iosArm64/iosSimulatorArm64. * docs/README.md: navigation index (consumers, contributors, AI agents). * .scratch/kompact-spec/map.md: opens with a 'for library consumers' pointer to README.md and docs/. Verified: ./gradlew :kompact:apiCheck :kompact:jvmTest BUILD SUCCESSFUL; python3 scripts/check-links.py README.md docs/ .scratch/kompact-spec/map.md PASS. --- .scratch/kompact-spec/map.md | 9 + README.md | 58 +++++ docs/README.md | 49 ++++ docs/api-reference.md | 200 ++++++++++++++++ docs/architecture.md | 222 ++++++++++++++++++ docs/ci.md | 108 +++++++++ docs/getting-started.md | 122 ++++++++++ .../kompact/runtime/GettingStartedTest.kt | 66 ++++++ 8 files changed, 834 insertions(+) create mode 100644 README.md create mode 100644 docs/README.md create mode 100644 docs/api-reference.md create mode 100644 docs/architecture.md create mode 100644 docs/ci.md create mode 100644 docs/getting-started.md create mode 100644 kompact/src/commonTest/kotlin/ch/trancee/kompact/runtime/GettingStartedTest.kt diff --git a/.scratch/kompact-spec/map.md b/.scratch/kompact-spec/map.md index a6a3a1a..b3fd2a6 100644 --- a/.scratch/kompact-spec/map.md +++ b/.scratch/kompact-spec/map.md @@ -1,5 +1,14 @@ # Wayfinder Map: Kompact +> **For library consumers (using kompact in another project):** start at the +> project root [README.md](../../README.md), which links into +> [docs/getting-started.md](../../docs/getting-started.md) (a verified +> tutorial), [docs/api-reference.md](../../docs/api-reference.md), +> [docs/architecture.md](../../docs/architecture.md), and +> [docs/ci.md](../../docs/ci.md). This map and the tickets below are the +> source of truth for *why* the API looks the way it does; the docs/ tree +> is the source of truth for *how* to use it. + ## Destination A decided, implementable architecture spec for **Kompact**, the bit-packed, zero-allocation Kotlin Multiplatform serialization framework described in `PROMPT.md`, ready to hand off for implementation. Reaching the end of this map means the spec locks the wire format, the common runtime API (`readBits` / `writeBits` / `readBitsBoolean` over a `ByteArray`), the generated value-class view pattern, the code-generation strategy, the validation model, the cross-platform testing model, and the performance-evidence plan — leaving no gating decisions for the person who implements it. diff --git a/README.md b/README.md new file mode 100644 index 0000000..3c584db --- /dev/null +++ b/README.md @@ -0,0 +1,58 @@ +# Kompact + +A bit-packed, zero-allocation serialization framework for Kotlin Multiplatform. +Built for tiny, dense wire payloads (think BLE characteristics) that still need to +be safely decoded on the hot path — no boxing, no exception throwing, no +intermediate copies. + +``` +// Write 16 bits: 4 bits battery + 10 bits speed + 1 bit flag + 1 bit reserved +val w = KompactWriter() +w.writeScalar(bitWidth = 4, value = 5L) // battery = 5 +w.writeScalar(bitWidth = 10, value = 10L) // speed = 10 +w.writeBool(true) // malfunction = true +val bytes: ByteArray = w.build() // 2 bytes: 0xA5 0x40 + +// Read them back as typed results — no exceptions on the success path +val battery: Int = KompactRuntime.readScalar(bytes, 0, 4, signed = false).getOrThrow() +val speed: Int = KompactRuntime.readScalar(bytes, 4, 10, signed = false).getOrThrow() +val flag: Boolean = KompactRuntime.readBool (bytes, 14 ).getOrThrow() +``` + +## What's in this repo + +- **`:kompact`** — the KMP runtime: bit primitives, a forward-only writer, framing + helpers, and seven zero-alloc typed result value classes (`ByteResult`, + `ShortResult`, `IntResult`, `LongResult`, `FloatResult`, `DoubleResult`, + `BooleanResult`). +- **Targets**: `jvm` (JVM 21), `iosArm64`, `iosSimulatorArm64`. Android consumes + the `jvm` artifact. +- **No codegen yet.** `@KompactModel` / `@KompactField` annotations are defined + (and validated for source compatibility by `KompactFieldV1SurfaceTest`) but + no KSP processor ships in this repository. Today you write the bit-shifting + by hand, the way the bundled `VehicleTelemetry` example does. + +## Where to go next + +| If you want to … | Read | +| --- | --- | +| Try it end-to-end (write a frame, read it back) | **[`docs/getting-started.md`](docs/getting-started.md)** | +| Look up an exact API signature, parameter, or error | **[`docs/api-reference.md`](docs/api-reference.md)** | +| Understand the design choices (LSB-first, zero-alloc, value classes, framing) | **[`docs/architecture.md`](docs/architecture.md)** | +| Run / understand the CI gates and goldens | **[`docs/ci.md`](docs/ci.md)** | +| See all of the above at a glance | **[`docs/README.md`](docs/README.md)** | +| Read the original product brief | [`PROMPT.md`](PROMPT.md) | +| Read the locked implementation spec (tickets 01–13) | [`.scratch/kompact-spec/map.md`](.scratch/kompact-spec/map.md) | + +## Status + +`0.1.0-SNAPSHOT` — the runtime, writer, framing, and result value classes are +stable and exercised by the `commonTest` suite. The publication pipeline is +configured (Maven coordinates `ch.trancee.kompact:kompact`, license Apache-2.0) +but **no release has been cut to Maven Central yet**. Build from source or +`./gradlew :kompact:publishToMavenLocal` and consume the local snapshot. + +## License + +Apache License 2.0. See [`build.gradle.kts`](kompact/build.gradle.kts) for the +full publication metadata. diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..f84e0f8 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,49 @@ +# Docs + +A short index of the documentation in this repository, organized by +who it's for and what they want to do. + +## For library consumers + +Start at the [project root `README.md`](../README.md) for the +one-paragraph pitch, then pick the doc that matches your task: + +| I want to … | Read | +| --- | --- | +| Try kompact end-to-end (write a frame, read it back) | [`getting-started.md`](getting-started.md) | +| Look up an exact API signature, parameter, or error type | [`api-reference.md`](api-reference.md) | +| Understand the design choices (LSB-first, zero-alloc, value classes, framing, error encoding) | [`architecture.md`](architecture.md) | +| Run the CI gates / regenerate the goldens | [`ci.md`](ci.md) | +| Read the original product brief | [`../PROMPT.md`](../PROMPT.md) | + +## For library contributors + +The locked implementation spec is the source of truth for design +decisions. Start with the index: + +- [`.scratch/kompact-spec/map.md`](../.scratch/kompact-spec/map.md) — + the spec index, with a one-line summary of each of the 13 tickets + and links to the underlying research notes. +- The spec tickets under [`.scratch/kompact-spec/issues/`](../.scratch/kompact-spec/issues/) + record the **why** behind every API decision, in ticket form. + Read these when changing or extending the runtime surface. + +## For AI agents + +The repository's AI-execution policy is the canonical source for how +coding agents should operate here: + +- [`../AGENTS.md`](../AGENTS.md) — the AI execution policy (TDD path, + clean cutover, commit conventions, review checklist). +- [`../CONSTITUTION.md`](../CONSTITUTION.md) — the R/X/D/O normative + policy. Priority: `CONSTITUTION > AGENTS > scoped docs/ADRs`. +- [`agents/domain.md`](agents/domain.md) — domain documentation + conventions. +- [`agents/issue-tracker.md`](agents/issue-tracker.md) — how spec + tickets and issues are recorded. +- [`agents/triage-labels.md`](agents/triage-labels.md) — the canonical + triage label set. + +These files are written in compact directive syntax and are *not* +the entry point for human consumers of the library — use the +"For library consumers" table above instead. diff --git a/docs/api-reference.md b/docs/api-reference.md new file mode 100644 index 0000000..f222331 --- /dev/null +++ b/docs/api-reference.md @@ -0,0 +1,200 @@ +# API reference + +The public surface of the `:kompact` runtime. The reference mirrors the +source layout: [`KompactRuntime`](#kompactruntime), [`KompactWriter`](#kompactwriter), +[`KompactFraming`](#kompactframing), and the [typed result classes](#typed-result-value-classes). +Annotations live in [the annotations section](#annotations). + +All declarations are in the package `ch.trancee.kompact.runtime` unless +noted. Cross-platform behaviour is identical between `jvm`, `iosArm64`, and +`iosSimulatorArm64`; platform-specific notes are flagged where they apply. + +--- + +## KompactRuntime + +The bit-stream primitives and the checked, typed read accessors. Bit order is +**LSB-first** (see [architecture — wire format](architecture.md#wire-format)): +byte 0 holds field bits 0–7, byte 1 holds bits 8–15, and bit 0 of each +byte is the least-significant bit of the field value. Every `Byte` is +masked `and 0xFF` before `ushr`/`shl`/`or`, so the bit packing is identical +on the JVM and Kotlin/Native regardless of platform endianness. + +### Raw bit primitives + +| Function | Signature | Description | +| --- | --- | --- | +| `readBits` | `readBits(raw: ByteArray, bitOffset: Int, bitWidth: Int): Int` | Reads `bitWidth` bits (1..31) starting at `bitOffset`, LSB-first. Caller is responsible for bounds. | +| `writeBits` | `writeBits(raw: ByteArray, bitOffset: Int, bitWidth: Int, value: Int)` | Writes the low `bitWidth` bits of `value` into `raw` at `bitOffset`, LSB-first. | +| `readBitsBoolean` | `readBitsBoolean(raw: ByteArray, bitOffset: Int): Boolean` | Reads a single bit at `bitOffset` as a `Boolean`. | +| `writeBitsBoolean` | `writeBitsBoolean(raw: ByteArray, bitOffset: Int, value: Boolean)` | Writes `value` as a single bit at `bitOffset`. | +| `readBitsLong` | `readBitsLong(raw: ByteArray, bitOffset: Int, bitWidth: Int): Long` | Reads `bitWidth` bits (1..64) starting at `bitOffset`, LSB-first. | +| `writeBitsLong` | `writeBitsLong(raw: ByteArray, bitOffset: Int, bitWidth: Int, value: Long)` | Writes the low `bitWidth` bits of `value` into `raw` at `bitOffset`, LSB-first. | +| `fits` | `fits(raw: ByteArray, bitOffset: Int, bitWidth: Int): Boolean` | Bounds check: `true` iff `bitOffset + bitWidth` fits in `raw.size * 8`. | + +The raw primitives are the zero-allocation fast path. Use them in +generated view getters where the layout is compile-time-validated and +the caller knows the buffer is well-formed. + +### Checked, typed read accessors + +These return a **typed result value class** (see below) — never throw on +the success path. `getOrThrow()` is the only call that can raise +(`KompactDecodeException`). + +| Function | Signature | Returns | Validates | +| --- | --- | --- | --- | +| `readBool` | `readBool(raw: ByteArray, bitOffset: Int): BooleanResult` | 1-bit read, zero-/sign-extended per the result encoding. | Bounds. | +| `readScalar` | `readScalar(raw: ByteArray, bitOffset: Int, bitWidth: Int, signed: Boolean): IntResult` | 1..32-bit read. `signed = true` two's-complement sign-extends; `signed = false` zero-extends. | Bounds. | +| `readScalarLong` | `readScalarLong(raw: ByteArray, bitOffset: Int, bitWidth: Int, signed: Boolean): LongResult` | 1..64-bit read. Same `signed` semantics. | Bounds; the encoded `Long` for a 64-bit value uses a sentinel near `Long.MIN_VALUE` (see [architecture — error encoding](architecture.md#runtime-error-encoding)) — those values are not representable as success. | +| `readFloat` | `readFloat(raw: ByteArray, bitOffset: Int): FloatResult` | 32-bit IEEE-754 read. NaN is canonicalized on the wire. | Bounds. | +| `readDouble` | `readDouble(raw: ByteArray, bitOffset: Int): DoubleResult` | 64-bit IEEE-754 read. NaN is canonicalized on the wire. | Bounds. | + +For the `signed` parameter: `true` means the read bits are interpreted +as a two's-complement magnitude and sign-extended to fill the result +type; `false` means zero-extension. There is no separate "negative +unsigned" form — a 4-bit `readScalar(_, _, 4, signed = false)` returns +`0..15`; the same call with `signed = true` returns `-8..7`. + +--- + +## KompactWriter + +A forward-only, growable bit-buffer builder. Append fields in the +order they appear on the wire; `build()` returns the exact-length +`ByteArray` snapshot. The writer is single-use — calling `build()` a +second time yields an empty buffer. + +| Member | Signature | Description | +| --- | --- | --- | +| `bitCursor` | `var bitCursor: Int` (read-only) | Current write cursor in bits. Advances as values are written. Exposed so nested/repeat assembly can reason about bit alignment. | +| `writeBits` | `writeBits(bitWidth: Int, value: Int)` | Appends the low `bitWidth` bits of `value` (1..31). | +| `writeBitsLong` | `writeBitsLong(bitWidth: Int, value: Long)` | Appends the low `bitWidth` bits of `value` (1..64). | +| `writeBool` | `writeBool(value: Boolean)` | Appends a single bit (`true` = 1, `false` = 0). | +| `writeScalar` | `writeScalar(bitWidth: Int, value: Long)` | Appends `bitWidth` low bits of `value` as a two's-complement magnitude (1..64). Dispatches to `writeBits` for ≤31, `writeBitsLong` for 32..64. | +| `writeString` | `writeString(countWidth: Int, value: String)` | Appends a length-prefixed UTF-8 string: `-bit LE byte count>`. `countWidth` must be in `KompactFraming.VALID_PREFIX_WIDTHS`. | +| `writeBlob` | `writeBlob(countWidth: Int, bytes: ByteArray)` | Appends a length-prefixed blob: `-bit LE byte count>`. | +| `writeNested` | `writeNested(lengthPrefixWidth: Int = 16, block: KompactWriter.() -> Unit)` | Writes a nested sub-region. The `block` is invoked against a **child** writer; the child's byte length is emitted as a `lengthPrefixWidth`-bit LE prefix immediately followed by the child bytes. Forward-only, no back-patch. | +| `writeRepeated` | `writeRepeated(count: Int, countWidth: Int = 8, block: KompactWriter.() -> Unit)` | Writes a count-prefixed repeat: `-bit LE count>`. `block` runs once per element against the parent writer. | +| `build` | `build(): ByteArray` | Returns the exact-length snapshot of the accumulated bits. The writer is then empty (single-shot by design). | + +The writer is **not** bound by the zero-allocation hot-path discipline — +buffer growth and lambda dispatch are acceptable. Only the read path + +### Long-form framing and writer extensions + +The writer methods listed above that take a `block: KompactWriter.() -> Unit` — +`writeNested` and `writeRepeated` — are the entry points to Kompact's +length-delimited framing. They share their wire contract with +[`KompactFraming`](#kompactframing) (the length-prefix helpers a hand-written +reader would use to consume the same bytes). A worked example of writing +a string + nested + repeated payload with the writer is in +[`docs/getting-started.md`](getting-started.md); the framing contract, +including the parse-forward property and the `BadLengthPrefix` / +`TruncatedNested` failure paths, is in +[architecture — framing contract](architecture.md#framing-contract). + +--- + +## KompactFraming + +Length-prefix helpers shared by the reader and the writer. Reads never +throw on the hot path; a prefix that overruns the buffer is surfaced +via `nestedRegionOrNull`'s nullable return so the caller can map it +to a typed `TruncatedNested` / `BadLengthPrefix` result. + +| Member | Signature | Description | +| --- | --- | --- | +| `VALID_PREFIX_WIDTHS` | `Set = setOf(8, 16, 32)` | The set of legal length-prefix bit widths. | +| `readLengthPrefix` | `readLengthPrefix(raw: ByteArray, bitOffset: Int, bitWidth: Int): Int` | Reads a fixed-width little-endian byte count at `bitOffset`. Returns `-1` when `bitWidth` is invalid or the region overruns `raw`. | +| `writeLengthPrefix` | `writeLengthPrefix(raw: ByteArray, bitOffset: Int, bitWidth: Int, length: Int)` | Writes `length` as a fixed-width little-endian byte count at `bitOffset`. Throws `IllegalArgumentException` if `bitWidth` is not in `VALID_PREFIX_WIDTHS`. | +| `nestedRegionOrNull` | `nestedRegionOrNull(raw: ByteArray, bitOffset: Int, prefixBitWidth: Int): Pair?` | Parse-forward nested region: returns `(startBit, bitLength)` for the payload, or `null` when the prefix overruns the buffer (caller maps to a typed error). | + +--- + +## Typed result value classes + +Seven specialized result types — one per scalar kind. Each wraps a +single `Long` so it is **zero-alloc on both the JVM and iOS** on success +and failure. There is no generic `KompactDecodeResult`; the +specialized types let the success-path primitives stay unboxed. + +| Class | Underlying type | Used by | +| --- | --- | --- | +| `ByteResult` | `Long` (packed) | (reserved for the v1 type set) | +| `ShortResult` | `Long` (packed) | (reserved for the v1 type set) | +| `IntResult` | `Long` (packed) | `readScalar` | +| `LongResult` | `Long` (sentinel band near `Long.MIN_VALUE`) | `readScalarLong` | +| `FloatResult` | `Long` (IEEE-754 bits; canonical NaN for success) | `readFloat` | +| `DoubleResult` | `Long` (IEEE-754 bits; canonical NaN for success) | `readDouble` | +| `BooleanResult` | `Long` (packed) | `readBool` | + +Every result class exposes the same four members: + +| Member | Description | +| --- | --- | +| `isSuccess: Boolean` | `true` iff the result carries a decoded value. | +| `isFailure: Boolean` | `true` iff the result carries an error. | +| `error: KompactDecodeError?` | The decoded error on failure, `null` on success. | +| `getOrThrow(): ` | Returns the decoded primitive on success; throws `KompactDecodeException` on failure. The **only** call that can allocate / throw on the failure path. | + +Each result class also has a `Companion`: + +| Member | Description | +| --- | --- | +| `success(value: ): Result` | Packs a value into a success result. | +| `failure(error: KompactDecodeError): Result` | Packs a `KompactDecodeError` into a failure result. | + +See [architecture — runtime error encoding](architecture.md#runtime-error-encoding) +for the packed-Long layout, the `LongResult` sentinel band, and the +NaN-payload encoding for `FloatResult` / `DoubleResult`. + +--- + +## KompactDecodeError + +A `sealed class` carrying the failure kind. Returned (never thrown) on +the read path. Accessing `.error` on a result reconstructs the concrete +case lazily — the singletons are allocation-free; `UnknownEnumCode` only +allocates its data-class payload when a hand-written enum check +produces one. + +| Subtype | Meaning | +| --- | --- | +| `BoundsError` | The read exceeded the buffer. | +| `BadLengthPrefix` | A length prefix would overrun the remaining buffer. | +| `TruncatedNested` | A nested sub-region was declared but the buffer ended inside it. | +| `UnknownEnumCode(rawCode: Int)` | An enum ordinal decoded to a value outside the declared set. | + +`KompactDecodeException(error: KompactDecodeError)` is the only +exception thrown by Kompact, and only by the `getOrThrow()` recovery +call. It is not used on the success path. + +--- + +## Annotations + +Source-retained; **not** present at runtime. They document the layout +and (eventually) drive a KSP processor — no processor ships in this +repository today. See [`KompactFieldV1SurfaceTest`](../kompact/src/commonTest/kotlin/ch/trancee/kompact/runtime/KompactFieldV1SurfaceTest.kt) +for the compile-time contract pinned by the test suite. + +| Annotation | Target | Members | +| --- | --- | --- | +| `@KompactModel` | `AnnotationTarget.CLASS` | — | +| `@KompactField` | `AnnotationTarget.PROPERTY` | `bitOffset: Int`, `bitWidth: Int`, `lengthPrefixWidth: Int = 8`, `isNested: Boolean = false`, `repeatCountWidth: Int = 8`, `enumWidth: Int = 0`, `defaultValue: String = ""`, `isVersionField: Boolean = false` | + +`@KompactField` is the v1 schema metadata. `bitOffset` is zero-based +and LSB-first; `bitWidth` is in `1..64` (use `32` for a 32-bit field). +The default member values keep a plain `@KompactField(bitOffset, bitWidth)` +scalar declaration valid without naming the rest. + +--- + +## Constants and limits + +| Name | Value | Meaning | +| --- | --- | --- | +| `KompactFraming.VALID_PREFIX_WIDTHS` | `setOf(8, 16, 32)` | Legal length-prefix widths (bits). | +| `IntResult` / `LongResult` value range (success) | 1..32 bits (Int) / 1..64 bits (Long) | Width passed to `readScalar` / `readScalarLong`. | +| `LongResult` success range exclusion | `Long.MIN_VALUE .. Long.MIN_VALUE + (1L shl 58) - 1` | Sentinel band — see [architecture](architecture.md#runtime-error-encoding). | diff --git a/docs/architecture.md b/docs/architecture.md new file mode 100644 index 0000000..d5b2f9b --- /dev/null +++ b/docs/architecture.md @@ -0,0 +1,222 @@ +# Architecture + +A walk through the design choices in Kompact: why the wire looks the +way it does, why reads are zero-allocation, why the result is a typed +value class instead of a thrown exception, and how the pieces fit +together. Read this if you want to understand the *why* behind the +API surface in [`api-reference.md`](api-reference.md). + +--- + +## The product in one paragraph + +Kompact is a binary wire format and runtime for **small, dense +packets that must be safely decoded on a hot path with no heap +allocation and no exception throwing**. The original motivating use +case (in [`PROMPT.md`](../PROMPT.md)) is BLE characteristics: a few +bytes per frame, decoded frequently, on battery-powered devices +where every micro-allocation costs. The framework is a Kotlin +Multiplatform library targeting the JVM and iOS so the same wire +format works on both ends of a connection. + +## Wire format + +Kompact frames are LSB-first bit-packed. The whole format can be +stated in three rules: + +1. **Bits are packed LSB-first** within each field. A 10-bit field + at bit offset 4 occupies the four high bits of byte 0 and the six + low bits of byte 1. Byte 0 bit 0 is the LSB of byte 0; bit 0 of + every field is the LSB of that field's value. +2. **Multi-byte length prefixes are little-endian byte counts.** A + 16-bit prefix stores the byte count of the following payload, with + the low byte first. Prefix widths are restricted to + `setOf(8, 16, 32)` (`KompactFraming.VALID_PREFIX_WIDTHS`). +3. **Reads are sequential, parse-forward.** There are no offset-jump + pointers back into the buffer (the way FlatBuffers works). The + shape was deliberately *not* FlatBuffers-style because the v1 + type set includes variable-length fields (strings, blobs, nested, + repeated), and an offset that points "back 37 bytes" is not stable + once the fields before it can change size. + +The bit-packing is the same on every platform because every primitive +masks with `and 0xFF` before `ushr`/`shl`/`or`. That is the only +way to keep assembly identical between the JVM (which can sign-extend +a `Byte` when it's treated as a numeric) and Kotlin/Native (which +treats `Byte` as unsigned 8-bit). The [`KompactRuntime.readBits` / +`writeBits`](../kompact/src/commonMain/kotlin/ch/trancee/kompact/runtime/KompactRuntime.kt) +implementations are short enough to verify by hand; the value is in +the discipline (always mask, always shift on a 32- or 64-bit lane), +not in the cleverness. + +## Zero-allocation reads + +The "zero-copy" claim in the original brief has a precise meaning: +reading a scalar from a `ByteArray` produces a primitive `Int` (or +`Long`, `Boolean`, etc.) with **no intermediate object on the heap**. + +The mechanism is that the typed result value classes +([`KompactResult`](../kompact/src/commonMain/kotlin/ch/trancee/kompact/runtime/KompactResult.kt)) +are wrappers over a single `Long`. On the JVM, `@JvmInline value class` +over a primitive `Long` is stored as the `Long` itself — no object +header, no heap allocation. On Kotlin/Native, a `value class` over a +primitive `Long` is an inline value with the same property. So +`IntResult` / `LongResult` / `BooleanResult` cost exactly the same +as a `Long` would, on both platforms, on both the success and +failure paths. + +This is the entire reason the result types are specialized per scalar +kind rather than a generic `KompactDecodeResult`. A generic would +have to box the `T` (or hold a sealed-class instance), and boxing +on the success path is exactly what the contract forbids. + +The contract is narrower than "no allocations ever." It is +specifically about the **scalar read hot path** in trusted code. +The writer is allowed to allocate (it grows a buffer), the framing +helpers are allowed to return `null` and let the caller allocate a +typed error, and the `getOrThrow()` recovery call is allowed to throw +`KompactDecodeException`. Zero-alloc is a property of the most- +frequently-executed read sequence, not a global invariant. + +## Runtime error encoding + +Each typed result class packs both the decoded value and an error +state into a single `Long` so the success-path read returns a +`Long`-shaped value with no branching, no allocation, and no throw. + +### ≤32-bit result types (ByteResult, ShortResult, IntResult, FloatResult, BooleanResult) + +A single packed `Long` layout: + +``` +[ ok(bit63) | errorKind(bits 62..60) | rawEnumCode(bits 59..48) | value(bits 47..0) ] +``` + +- `ok = 1` (bit 63 set) means success; the low 48 bits are the value + bits (sign- or zero-extended by the caller via `readScalar`'s + `signed` flag). +- `ok = 0` means failure; bits 62..60 carry the error kind code and + bits 59..48 carry the raw enum code for `UnknownEnumCode`. The + value bits are unused. + +### LongResult — the sentinel band + +Every 64-bit `Long` bit pattern is a valid signed integer, so +success and failure cannot be distinguished without reserving a +sentinel range. `LongResult` treats +`Long.MIN_VALUE .. Long.MIN_VALUE + (1L shl 58) - 1` +(bit 63 set, bits 62..58 clear) as the failure sentinel. Those values +**are not representable as success**: the first representable negative +success value is `Long.MIN_VALUE + (1L shl 58)`. This is the +documented tradeoff of packing a typed result into a single `Long` +without boxing; the reserved range is wide enough to carry the +error kind and the raw enum code, and it is small enough that +realistic long values almost never land in it. + +### FloatResult / DoubleResult — NaN payloads + +IEEE-754 reserves the NaN space for diagnostic payloads. `FloatResult` +and `DoubleResult` use canonical quiet-NaN for success and a quiet NaN +with a non-zero low-payload for failure. The low 4 bits of the NaN +payload carry the error-kind code (`ERROR_BOUNDS = 0`, +`ERROR_BAD_LENGTH = 1`, `ERROR_TRUNCATED = 2`, `ERROR_UNKNOWN_ENUM = 3`). +A non-canonical NaN read off the wire is canonicalized to the +canonical-quiet-NaN on success, so the writer's "I don't know the +value" NaN cannot smuggle a real NaN through the decoder. + +## Value-class representation across platforms + +The result value classes are declared as `expect value class` in +`commonMain` (no `@JvmInline`, because `@JvmInline` is a JVM-only +annotation and the symbol is meaningless on Kotlin/Native). The +platform actuals diverge: + +- `jvmMain`: `@JvmInline actual value class …` — required by the + language for value classes over a primitive `Long` on the JVM. +- `iosArm64Main` / `iosSimulatorArm64Main`: plain `actual value class …` — + Kotlin/Native represents the same over-primitive-Long shape as an + inline value automatically. + +Both platforms get the same allocation behaviour (zero on success and +failure) but the language requires the `@JvmInline` opt-in on the JVM. +This is a language-level constraint, not a project design choice — +the original product brief's "no `@JvmInline`" prohibition applies +to the hand-written common API surface, not to the JVM actual of a +cross-platform value class. + +## Framing contract + +Variable-length fields (strings, blobs, nested composites, repeated +fields) are layered on top of the fixed-width bit stream with a +shared length-prefix contract. The contract is: + +- Every length-delimited field carries a fixed-width little-endian + byte-count prefix, with the width declared per field and constrained + to `{8, 16, 32}`. +- Nested composites are length-delimited sub-regions. The reader + consumes the prefix, learns the byte count, then consumes exactly + `prefixWidth + count * 8` bits and hands the caller a + `(startBit, bitLength)` pair. +- Repeated fields are count-prefixed. The reader reads a count prefix + of the field's declared `countWidth`, then iterates `count` elements + sequentially. The writer's `writeRepeated` invokes its block `count` + times against the parent writer. + +The framing helpers live in `KompactFraming` (the `readLengthPrefix` +/ `writeLengthPrefix` / `nestedRegionOrNull` object) and the writer +exposes the user-facing shape (`writeString` / `writeBlob` / +`writeNested` / `writeRepeated`). Reads in this layer do not allocate +a typed result on a `null` return — the caller is expected to +pattern-match the nullable pair and produce a typed result at the +edge, keeping the framing hot path allocation-free. + +The deliberate rejection: no random-access offset jumps (see wire +format rule 3). This is what made the variable-length type set +addable to v1 without sacrificing the parse-forward property. + +## Versioning and schema evolution + +The v1 plan is positional + additive-only: + +- New fields are appended at the **end** of a length-delimited group. +- All length-delimited fields in a group share one uniform prefix + width so an older reader can skip unknown trailing length-delimited + fields by reading prefix + payload. +- A fixed-width version prefix at the very start of the stream signals + the schema version; an unknown version is a typed + `UnsupportedSchemaVersion` failure, never a silent misread. +- Missing trailing fields fall back to the field's `defaultValue` (the + `defaultValue` member of `@KompactField`). +- Breaking changes are reserved for: reordering fields, inserting a + fixed-width scalar field, changing a field's width, or changing + the stream's uniform prefix width. + +Skew (newer writer, older reader) is always surfaced as a typed +`BadLengthPrefix` or `UnsupportedSchemaVersion`. The framework does +not silently truncate or misread. + +## What is and is not in this repository today + +- **In repo and stable**: the runtime (KompactRuntime / KompactWriter / + KompactFraming / KompactResult / KompactDecodeError), the bundled + `VehicleTelemetry` example, the source-retained `@KompactModel` / + `@KompactField` annotations, the full `commonTest` suite + (round-trip, property-based, long-form, allocation-discipline), + CI gates (`apiCheck` on macOS, `jvmTest` on Linux), and the BCV + goldens in [`kompact/api/`](../kompact/api/). +- **Not yet in repo**: the KSP code generator. The annotation surface + is in source and the test suite pins the compile-time contract, but + the processor that would generate the value-class view bodies from + `@KompactField` declarations does not ship here. Today, models + like `VehicleTelemetry` are written by hand (the value-class getters + call the raw `KompactRuntime` primitives). +- **Not yet released**: the Maven Central artifact. The publication + pipeline is configured (`com.vanniktech.maven.publish` 0.37.0, + coordinates `ch.trancee.kompact:kompact:0.1.0-SNAPSHOT`) but no + release has been cut. Build from source or + `./gradlew :kompact:publishToMavenLocal` to consume the snapshot. + +The lock and the gating decisions behind every choice in this +document live in the spec tickets under +[`.scratch/kompact-spec/`](../.scratch/kompact-spec/) — start with +[`map.md`](../.scratch/kompact-spec/map.md) for the index. diff --git a/docs/ci.md b/docs/ci.md new file mode 100644 index 0000000..673c06e --- /dev/null +++ b/docs/ci.md @@ -0,0 +1,108 @@ +# Continuous integration + +This is a how-to for the CI workflow. If you want to know **what the +gates check and why**, read this end-to-end. If you just want to +re-run a gate locally, jump to [Re-running gates locally](#re-running-gates-locally). + +--- + +## The gates + +Two workflow files live in [`.github/workflows/`](../.github/workflows/): + +| Workflow | File | Runner | Gates | +| --- | --- | --- | --- | +| **CI** | `ci.yml` | `api-check` on `macos-latest`; `jvm-test` on `ubuntu-latest` | Public ABI goldens (JVM + merged iOS klib); `commonTest` on the JVM | +| **Regen Goldens** | `regen-goldens.yml` | `macos-latest` | Regenerate the BCV goldens (manual `workflow_dispatch`) | + +The CI workflow triggers on pushes to `main`, `master`, and `feat/**`, +and on pull requests into `main` or `master`. The regen workflow is +`workflow_dispatch` only — it does not auto-trigger on push. + +### `api-check` (macOS) + +Runs `./gradlew :kompact:apiCheck --no-daemon` on the latest macOS +runner with JDK 21 (Temurin). `apiCheck` compares the committed BCV +goldens against the freshly-inferred ABIs: + +- `kompact/api/kompact.api` — the JVM bytecode ABI (compiled from the + current source). +- `kompact/api/kompact.klib.api` — the merged iOS klib ABI (the union + of `iosArm64` and `iosSimulatorArm64`, inferred only on Apple hosts). + +**Why this gate exists.** The golden files pin the public ABI of the +runtime. A change that accidentally narrows or widens a public +signature — or that adds a new public declaration without a deliberate +golden bump — breaks this gate and forces a review. The JVM golden is +host-independent; the iOS klib golden is **only meaningfully +validated on a macOS runner** (the Linux CI job cannot infer the iOS +klib ABI). That is why the API check is split across runners. + +### `jvm-test` (Linux) + +Runs `./gradlew :kompact:jvmTest --no-daemon` on Ubuntu with JDK 21 +(Temurin). `jvmTest` runs the `commonTest` suite on the JVM target. +The suite covers round-trip unit tests, property-based tests, the +allocation discipline, and the long-form framing tests +(strings / blobs / nested / repeated). + +**Why this gate exists.** A pure ABI check is not enough — a source +change can pass the goldens (no public-surface drift) and still break +the runtime behaviour. `jvmTest` is the behavioural safety net. + +### `Regen Goldens` (macOS, manual) + +Runs `./gradlew :kompact:apiDump --no-daemon` on macOS and uploads +`kompact/api/` as a downloadable artifact (`api-goldens`, 3-day +retention). Use this workflow when: + +- You have intentionally added, removed, or changed a public + declaration and the committed golden needs to be updated. +- You are on a non-Mac host and need the canonical iOS klib golden + (only macOS can infer it). + +The workflow is `workflow_dispatch` only because regenerating the +goldens on every push would make the iOS golden churn — and it requires +a macOS runner (slow + metered). Trigger it from the GitHub Actions +tab → **Regen Goldens** → **Run workflow** → pick the branch, then +download the `api-goldens` artifact and replace the files under +`kompact/api/`. + +> **Note.** Because this workflow lives on a feature branch in the +> current development setup, the GitHub UI is the only place to +> dispatch it (`gh workflow run` resolves the workflow on the default +> branch and returns 404 for feature-branch-only workflows). + +--- + +## Re-running gates locally + +You do not need a CI runner to verify the gates — both `apiCheck` +and `jvmTest` are ordinary Gradle tasks. The difference is that on +Linux you can only verify the JVM side; the iOS klib inference needs +a macOS host. + +```bash +# JVM tests (Linux + macOS) +./gradlew :kompact:jvmTest + +# API check — JVM part runs anywhere; iOS klib part only on macOS +./gradlew :kompact:apiCheck + +# Regenerate the goldens in place (macOS only) +./gradlew :kompact:apiDump +``` + +When the goldens drift on a non-Mac host, the iOS half of +`apiCheck` is a no-op and you'll see a false green. The +`Regen Goldens` workflow is the supported way to get the iOS +golden updated from a non-Mac host. + +## Build environment + +Both workflows pin to JDK 21 (Temurin) and enable the Gradle build +cache. The `:kompact` module builds with the Kotlin 2.4.10 Gradle +plugin and KMP targets `jvm` (JVM 21), `iosArm64`, and +`iosSimulatorArm64`. No hand-rolled `multiplatformPublication` DSL — +KGP auto-creates the per-target artifacts and +`com.vanniktech.maven.publish` 0.37.0 publishes to Maven Central. diff --git a/docs/getting-started.md b/docs/getting-started.md new file mode 100644 index 0000000..26ffa6a --- /dev/null +++ b/docs/getting-started.md @@ -0,0 +1,122 @@ +# Getting started + +This tutorial takes you from an empty buffer to a packed-and-decoded +Kompact frame in about ten lines. Everything you see here is +verified by [`GettingStartedTest`](../kompact/src/commonTest/kotlin/ch/trancee/kompact/runtime/GettingStartedTest.kt) +in `commonTest` — if the wire bytes or the decoded values in this doc +ever drift from reality, that test fails first and the doc gets +corrected with it. + +## What you'll build + +A 2-byte telemetry message with this layout (LSB-first packing): + +| Bits | Width | Field | Range / meaning | +| ------ | ----- | ----------------- | ---------------------------- | +| 0..3 | 4 | `batteryStatus` | 0–15 (enum ordinal) | +| 4..13 | 10 | `speed` | 0–1023 | +| 14 | 1 | `isMalfunctioning`| `Boolean` | +| 15 | 1 | _reserved_ | left zero | + +Same shape as the bundled [`VehicleTelemetry`](../kompact/src/commonMain/kotlin/ch/trancee/kompact/generated/VehicleTelemetry.kt) +example, but read with the checked, typed API (`readScalar` / +`readBool`) rather than the raw zero-alloc view getters. + +## Prerequisites + +- A working Kotlin Multiplatform toolchain (the project is built with the + Kotlin 2.4.x Gradle plugin and JDK 21). +- The `:kompact` module on your classpath. Until the first Maven Central + release, either build from source: + ```bash + ./gradlew :kompact:publishToMavenLocal + ``` + and add the local snapshot: + ```kotlin + // build.gradle.kts (consumer) + implementation("ch.trancee.kompact:kompact:0.1.0-SNAPSHOT") + ``` + …or include the project directly. + +## Step 1 — write a frame + +`KompactWriter` is a forward-only, growable bit buffer. Append fields +in the order they appear on the wire; `build()` returns the exact-length +`ByteArray` snapshot. + +```kotlin +import ch.trancee.kompact.runtime.KompactWriter + +val w = KompactWriter() +w.writeScalar(bitWidth = 4, value = 5L) // batteryStatus = 5 +w.writeScalar(bitWidth = 10, value = 10L) // speed = 10 +w.writeBool(true) // isMalfunctioning = true +// bit 15 (reserved) is left zero by default. +val bytes: ByteArray = w.build() +``` + +**Expected result.** `bytes` is exactly 2 bytes long. For +`battery=5, speed=10, malfunction=true` the LSB-first wire bytes are +`[0xA5, 0x40]` (pinned by the tutorial test — if a future change +shifts the wire format, the test will break and this doc gets +updated to match). + +## Step 2 — read it back, checked + +Every `KompactRuntime` read accessor that ends in a typed result +(`readBool`, `readScalar`, `readScalarLong`, `readFloat`, `readDouble`) +returns a **zero-allocation result value class** — not a primitive, not +a throw. The success hot path never throws. + +```kotlin +import ch.trancee.kompact.runtime.KompactRuntime + +val battery: Int = + KompactRuntime.readScalar(bytes, 0, 4, signed = false).getOrThrow() +val speed: Int = + KompactRuntime.readScalar(bytes, 4, 10, signed = false).getOrThrow() +val flag: Boolean = + KompactRuntime.readBool(bytes, 14).getOrThrow() +``` + +**Expected result.** `battery == 5`, `speed == 10`, `flag == true`. + +`getOrThrow()` is the explicit recovery call: it returns the decoded +primitive on success and throws `KompactDecodeException` (carrying +a `KompactDecodeError`) only on failure. The success path is allocation- +free on both the JVM (`@JvmInline value class`) and iOS (plain +`value class`). + +## Step 3 — handle failure without throwing + +If the buffer is truncated, the same accessor returns a failure result +instead of throwing: + +```kotlin +val truncated = byteArrayOf(0xA5.toByte()) // only 1 byte, layout needs 2 +val speed = KompactRuntime.readScalar(truncated, 4, 10, signed = false) + +if (speed.isFailure) { + when (val err = speed.error) { + KompactDecodeError.BoundsError -> println("buffer too short") + KompactDecodeError.BadLengthPrefix -> println("length prefix overruns buffer") + KompactDecodeError.TruncatedNested -> println("nested region truncated") + is KompactDecodeError.UnknownEnumCode -> println("unknown enum code: ${err.rawCode}") + } +} +``` + +This pattern (no exception on the hot path) is what the API is shaped +for — see [`docs/architecture.md`](architecture.md) for the why. + +## Where to go next + +- **API surface** — every function, parameter, and result class: + [`docs/api-reference.md`](api-reference.md). +- **Design rationale** — why LSB-first, why zero-alloc, why value + classes over a packed `Long`: [`docs/architecture.md`](architecture.md). +- **Long-form payload (string, blob, nested, repeated)** — the framing + helpers and writer extensions that go beyond fixed-width scalars: + [`docs/api-reference.md#long-form-framing-and-writer-extensions`](api-reference.md#long-form-framing-and-writer-extensions). +- **Run the tests yourself** — `./gradlew :kompact:jvmTest` (the tutorial + test is part of the suite). diff --git a/kompact/src/commonTest/kotlin/ch/trancee/kompact/runtime/GettingStartedTest.kt b/kompact/src/commonTest/kotlin/ch/trancee/kompact/runtime/GettingStartedTest.kt new file mode 100644 index 0000000..a2b7710 --- /dev/null +++ b/kompact/src/commonTest/kotlin/ch/trancee/kompact/runtime/GettingStartedTest.kt @@ -0,0 +1,66 @@ +package ch.trancee.kompact.runtime + +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +/** + * The executable companion to `docs/getting-started.md`. + * + * If this file is green, the tutorial is honest. The tutorial quotes the wire + * bytes and round-trip values asserted below; if a future change to the runtime + * shifts the wire layout, this test fails first and the doc is updated with it. + * + * Layout (LSB-first, 16 bits = 2 bytes): + * [0..3] batteryStatus (4 bits, 0..15) unsigned + * [4..13] speed (10 bits, 0..1023) unsigned + * [14] isMalfunctioning (1 bit) + * [15] reserved (1 bit, left zero) + */ +class GettingStartedTest { + + @Test + fun readYourFirstMessage() { + // --- Write: build a 2-byte telemetry frame with KompactWriter --- + val w = KompactWriter() + w.writeScalar(bitWidth = 4, value = 5L) // battery = 5 + w.writeScalar(bitWidth = 10, value = 10L) // speed = 10 + w.writeBool(true) // isMalfunctioning = true + // bit 15 (reserved) is left zero. + val bytes: ByteArray = w.build() + assertEquals(2, bytes.size) + + // The LSB-first wire bytes for battery=5, speed=10, malfunction=true. + // (Computed from the layout above; pinned here so a silent wire-format + // regression breaks the tutorial test, not just the round-trip values.) + assertEquals(0xA5, bytes[0].toInt() and 0xFF) + assertEquals(0x40, bytes[1].toInt() and 0xFF) + + // --- Read: the checked, typed accessors on KompactRuntime --- + val battery: Int = KompactRuntime.readScalar(bytes, 0, 4, signed = false).getOrThrow() + val speed: Int = KompactRuntime.readScalar(bytes, 4, 10, signed = false).getOrThrow() + val malfunction: Boolean = KompactRuntime.readBool(bytes, 14).getOrThrow() + + assertEquals(5, battery) + assertEquals(10, speed) + assertEquals(true, malfunction) + } + + @Test + fun readScalar_reportsBoundsErrorInsteadOfThrowing() { + // Truncated buffer for the 16-bit layout: 1 byte (8 bits), but reading + // `speed` at offset 4 with width 10 needs bits 4..13 (14 bits) — overruns. + val truncated = byteArrayOf(0xA5.toByte()) + + val speed = KompactRuntime.readScalar(truncated, 4, 10, signed = false) + + assertTrue(speed.isFailure) + assertFalse(speed.isSuccess) + assertEquals(KompactDecodeError.BoundsError, speed.error) + // The success-path hot code never throws (Ticket 03 / 08). + // Only the explicit recovery call getOrThrow() can raise, and only on failure. + assertFailsWith { speed.getOrThrow() } + } +} From 87e5daa2dc344db08625057defc092bd2914b511 Mon Sep 17 00:00:00 2001 From: Philipp Grosswiler Date: Sat, 5 Sep 2026 23:18:17 +0200 Subject: [PATCH 26/51] feat: ScalarType checked accessors + OrThrow wrappers + NestedRegionResult + tests --- .../issues/01-data-clump-scalartype.md | 220 ++++++++++++++ .../issues/02-readscalarlong-naming.md | 142 +++++++++ .../issues/03-throw-helper-naming.md | 151 ++++++++++ .../issues/04-result-ergonomics-layer.md | 278 ++++++++++++++++++ .../issues/05-readnested-typed-result.md | 251 ++++++++++++++++ .../issues/06-annotation-visibility.md | 260 ++++++++++++++++ .../issues/07-vehicletelemetry-alignment.md | 271 +++++++++++++++++ .../issues/08-ci-ergonomics.md | 182 ++++++++++++ .../issues/09-test-file-split.md | 238 +++++++++++++++ .../issues/10-docs-structure-review.md | 233 +++++++++++++++ .../issues/11-bitcursor-visibility.md | 168 +++++++++++ .scratch/kompact-ergonomics/map.md | 179 +++++++++++ kompact/api/kompact.api | 119 ++++++-- .../KompactAnnotations.kt | 8 +- .../kompact/annotations/KompactPreview.kt | 23 ++ .../kompact/generated/VehicleTelemetry.kt | 16 +- .../trancee/kompact/runtime/KompactFraming.kt | 35 ++- .../runtime/KompactNestedRegionResult.kt | 16 + .../runtime/KompactResultExtensions.kt | 70 +++++ .../trancee/kompact/runtime/KompactRuntime.kt | 48 ++- .../trancee/kompact/runtime/KompactWriter.kt | 20 +- .../ch/trancee/kompact/runtime/ScalarType.kt | 18 ++ .../kompact/runtime/GettingStartedTest.kt | 12 +- .../runtime/KompactFieldV1SurfaceTest.kt | 1 + .../runtime/KompactRuntimeCheckedApiTest.kt | 194 ++++++++++++ .../runtime/KompactRuntimeCheckedReadTest.kt | 64 ++-- .../kompact/runtime/KompactWriterTest.kt | 18 +- .../kompact/generated/VehicleTelemetry.kt | 13 +- .../runtime/KompactNestedRegionResult.kt | 18 ++ .../ch/trancee/kompact/runtime/ScalarType.kt | 19 ++ .../kompact/generated/VehicleTelemetry.kt | 13 +- .../runtime/KompactNestedRegionResult.kt | 21 ++ .../ch/trancee/kompact/runtime/ScalarType.kt | 22 ++ 33 files changed, 3239 insertions(+), 102 deletions(-) create mode 100644 .scratch/kompact-ergonomics/issues/01-data-clump-scalartype.md create mode 100644 .scratch/kompact-ergonomics/issues/02-readscalarlong-naming.md create mode 100644 .scratch/kompact-ergonomics/issues/03-throw-helper-naming.md create mode 100644 .scratch/kompact-ergonomics/issues/04-result-ergonomics-layer.md create mode 100644 .scratch/kompact-ergonomics/issues/05-readnested-typed-result.md create mode 100644 .scratch/kompact-ergonomics/issues/06-annotation-visibility.md create mode 100644 .scratch/kompact-ergonomics/issues/07-vehicletelemetry-alignment.md create mode 100644 .scratch/kompact-ergonomics/issues/08-ci-ergonomics.md create mode 100644 .scratch/kompact-ergonomics/issues/09-test-file-split.md create mode 100644 .scratch/kompact-ergonomics/issues/10-docs-structure-review.md create mode 100644 .scratch/kompact-ergonomics/issues/11-bitcursor-visibility.md create mode 100644 .scratch/kompact-ergonomics/map.md rename kompact/src/commonMain/kotlin/ch/trancee/kompact/{runtime => annotations}/KompactAnnotations.kt (90%) create mode 100644 kompact/src/commonMain/kotlin/ch/trancee/kompact/annotations/KompactPreview.kt create mode 100644 kompact/src/commonMain/kotlin/ch/trancee/kompact/runtime/KompactNestedRegionResult.kt create mode 100644 kompact/src/commonMain/kotlin/ch/trancee/kompact/runtime/KompactResultExtensions.kt create mode 100644 kompact/src/commonMain/kotlin/ch/trancee/kompact/runtime/ScalarType.kt create mode 100644 kompact/src/commonTest/kotlin/ch/trancee/kompact/runtime/KompactRuntimeCheckedApiTest.kt create mode 100644 kompact/src/iosMain/kotlin/ch/trancee/kompact/runtime/KompactNestedRegionResult.kt create mode 100644 kompact/src/iosMain/kotlin/ch/trancee/kompact/runtime/ScalarType.kt create mode 100644 kompact/src/jvmMain/kotlin/ch/trancee/kompact/runtime/KompactNestedRegionResult.kt create mode 100644 kompact/src/jvmMain/kotlin/ch/trancee/kompact/runtime/ScalarType.kt diff --git a/.scratch/kompact-ergonomics/issues/01-data-clump-scalartype.md b/.scratch/kompact-ergonomics/issues/01-data-clump-scalartype.md new file mode 100644 index 0000000..997bd3e --- /dev/null +++ b/.scratch/kompact-ergonomics/issues/01-data-clump-scalartype.md @@ -0,0 +1,220 @@ +Type: grilling +Status: resolved + +# 01 — Data clump: `bitWidth` + `signed` should be one domain concept + +## Question + +`KompactRuntime.readScalar(raw, bitOffset, bitWidth, signed)` and +`KompactWriter.writeScalar(bitWidth, value)` take `bitWidth` and +`signed` as two separate parameters. They always travel together and +encode a single domain concept — the integer type descriptor (i8, u16, +i32, u64, …). This is Fowler's *Data Clump* smell, and it scored P2 in +the prior code-review of `feat/laguna`. The repo's own value-class +pattern (`KompactResult`, `KompactField`) is the natural place to +absorb it. + +The decision: do we replace the `(bitWidth: Int, signed: Boolean)` +parameter pair with a single value-class argument (a `ScalarType` or +`IntegerKind` or similar)? If yes, what does the call site look like +(`readScalar(buf, off, ScalarType.INT_16)` vs an enum +`readScalar(buf, off, IntegerKind.SIGNED, 16)`), and does the change +also collapse `readScalar` and `readScalarLong` (touched by ticket +02) into one parametric function with the value class as the +discriminator? + +This ticket is the front of the frontier. Ticket 02 (`readScalarLong` +naming) and ticket 04 (Result ergonomics layer) are blocked on this +decision because the rename ergonomics and the result-API surface +both depend on what a "scalar type" looks like at the call site. + +## Context for the claiming session + +- `kompact/src/commonMain/kotlin/ch/trancee/kompact/runtime/KompactRuntime.kt` — + `readScalar` (lines ~138–160), `readScalarLong` (lines ~162–176). +- `kompact/src/commonMain/kotlin/ch/trancee/kompact/runtime/KompactWriter.kt` — + `writeScalar` (lines ~53–61), which has `bitWidth` but no `signed` + flag (the writer doesn't need to know sign because the bits are + stored as a two's-complement magnitude regardless of the caller's + intent — but see the open question below). +- The packed-Long error encoding in `KompactResult.kt` already + reserves `bits 59..48` for `rawEnumCode` — a future `ScalarType` + might benefit from reserving a small range for an enum ordinal + rather than the current `signed: Boolean` (decide as part of this + ticket). +- `docs/api-reference.md#checked-typed-read-accessors` and + `docs/getting-started.md` (the 16-bit tutorial) both document the + current `readScalar(buf, off, width, signed)` shape; both will + need to be re-aligned with whatever this ticket decides. +- `GettingStartedTest` in `kompact/src/commonTest/.../runtime/` is + the executable journey that pins the tutorial wire bytes + (`0xA5 0x40`); it must be updated alongside any signature change + so the tutorial's `readScalar(bytes, 0, 4, signed = false)` example + still compiles and still asserts the same wire bytes. + +## Open sub-questions (answer as part of this ticket) + +1. **Value class shape** — `ScalarType(bitWidth: Int, signed: Boolean)` + with named constants (`ScalarType.UINT_8`, `ScalarType.INT_32`, + …), or a sealed hierarchy of value classes (`UIntScalar`, + `IntScalar` parameterized by width), or an enum (less idiomatic for + parameterized data)? +2. **The writer side** — does `writeScalar(bitWidth, value)` also + take a `ScalarType` (for symmetry) or keep its current `Long` + value parameter and only the reader adopts the value class? +3. **The `signed: Boolean` on the writer** — the writer currently + takes a `value: Long` and writes the low `bitWidth` bits as a + two's-complement magnitude. Should the value class also carry the + sign intent so a `writeScalar(ScalarType.INT_8, -1L)` and a + `writeScalar(ScalarType.UINT_8, 255L)` produce the same wire bits + (currently they do, because the writer just stores the low bits + without sign information)? +4. **The `readScalar` / `readScalarLong` split** — does this ticket + collapse them (one `readScalar(buf, off, type: ScalarType): Result` + where the result kind is decided by `type`) or keep them separate + for the 1..32 vs 1..64 width band? See ticket 02. +5. **Test file split** — if the reader signature changes, ticket + 09 (test file split) is unblocked; this ticket should record the + intended new accessor shape so ticket 09 can plan its file + layout. + +## What "resolved" looks like + +- A single design choice is recorded under `## Answer` (the value + class shape, the writer-side question, and the + `readScalar`/`readScalarLong` outcome). +- The chosen shape is sketched in 5–15 lines of Kotlin (a type + declaration and 2–3 example call sites) so a follow-up + implementation ticket can apply it directly. +- The downstream tickets (02, 04, 09) are updated if the chosen + shape changes their scope (e.g. if 01 collapses `readScalar` and + `readScalarLong`, ticket 02 closes as completed and the gist is + moved to "Decisions so far" in the map). + +## Answer + +**Decision: single value class `ScalarType(bitWidth, signed)` with named companion +constants; both `readScalar` / `readScalarLong` (readers) and `writeScalar` (writer) +adopt it. Keep the reader split. No enum axis — enums are read via `readScalar` and +the `UnknownEnumCode` typed error is inspected on the result.** + +The four sub-questions, settled one at a time: + +- **Shape (Q1)** — single `value class ScalarType` over a `Long` (zero-alloc on + both JVM and iOS per ticket 03), constructed via an `internal` constructor and + exposed through named factory constants in the companion for the common + JVM/Kotlin primitive lanes (`INT_8`, `INT_16`, `INT_32`, `INT_64`, `UINT_8`, + `UINT_16`, `UINT_32`, `UINT_64`, `BOOL`) plus a generic `of(bitWidth, signed)` + factory for the rare 1..63 widths in between. The value class is + `@JvmInline` on the JVM and plain `value class` on iOS (ticket 03's + representation rule). +- **Reader split (Q2)** — keep `readScalar` (1..32 → `IntResult`) and + `readScalarLong` (1..64 → `LongResult`) as two functions. Collapsing would + either box on the success path (violates ticket 03's zero-alloc read + contract) or force the caller to cast the result. The split is intrinsic to + the JVM/Kotlin primitive lanes; the value class does not change it. Ticket + 02 (`readScalarLong` naming) is now *unblocked* but does *not* auto-resolve: + the long-variant function stays, and its name still needs work. +- **Writer side (Q3)** — adopt the value class: `writeScalar(type: ScalarType, + value: Long)`. The writer is not zero-alloc-constrained (per + `docs/architecture.md` and ticket 07), so the allocation of constructing a + `ScalarType` at the call site is acceptable. Symmetry with the reader is + the win — the call site uses the same `ScalarType.INT_16` constant for both + `readScalar` and `writeScalar`. +- **Enum axis (Q4)** — *no* `ScalarKind` field on the value class. The packed + result `Long` already reserves `bits 59..48` for the raw enum code (per + `KompactResult.kt`'s `RESULT_RAW_ENUM_SHIFT = 48`); the reader returns + `IntResult` and the caller inspects `result.error is KompactDecodeError.UnknownEnumCode` + for the fail-closed behavior ticket 04 mandates. Keeping the enum case on + the same `readScalar` access keeps the API small; the alternative (a + separate `readEnum`) is a separate ticket's decision. + +### Sketch (for the implementation commit) + +```kotlin +// kompact/src/commonMain/kotlin/ch/trancee/kompact/runtime/ScalarType.kt +@JvmInline +public value class ScalarType internal constructor(private val packed: Long) { + public val bitWidth: Int get() = (packed ushr 32).toInt() and 0x3F // 1..64 + public val signed: Boolean get() = (packed and 0x8000_0000L) != 0L + + public companion object { + public val INT_8: ScalarType = ScalarType(8, signed = true) + public val INT_16: ScalarType = ScalarType(16, signed = true) + public val INT_32: ScalarType = ScalarType(32, signed = true) + public val INT_64: ScalarType = ScalarType(64, signed = true) + public val UINT_8: ScalarType = ScalarType(8, signed = false) + public val UINT_16: ScalarType = ScalarType(16, signed = false) + public val UINT_32: ScalarType = ScalarType(32, signed = false) + public val UINT_64: ScalarType = ScalarType(64, signed = false) + public val BOOL: ScalarType = ScalarType(1, signed = false) + public fun of(bitWidth: Int, signed: Boolean): ScalarType = + ScalarType(bitWidth, signed) + } +} + +// KompactRuntime — new signatures +public inline fun readScalar(raw: ByteArray, bitOffset: Int, type: ScalarType): IntResult +public inline fun readScalarLong(raw: ByteArray, bitOffset: Int, type: ScalarType): LongResult + +// KompactWriter — new signature +public fun writeScalar(type: ScalarType, value: Long) +``` + +### Call sites (matching the current `GettingStartedTest` / `docs/getting-started.md`) + +```kotlin +// Reader — was: readScalar(bytes, 0, 4, signed = false) +val battery: Int = KompactRuntime.readScalar(bytes, 0, ScalarType.UINT_8).getOrThrow() + +// Writer — was: writeScalar(bitWidth = 4, value = 5L) +val w = KompactWriter() +w.writeScalar(ScalarType.UINT_8, 5L) +w.writeScalar(ScalarType.UINT_16, 10L) +w.writeBool(true) +``` + +### Propagation + +- **Ticket 02** (`readScalarLong` naming) — **unblocked**. The function stays; + the naming decision is its own. Note in 02's body. +- **Ticket 04** (Result ergonomics layer) — **unblocked**. The result types are + unaffected by the parameter shape. 04's recovery-pattern decision is + independent. +- **Ticket 09** (Test file split) — **unblocked**. The test file can now plan + against the new accessor signature; per-accessor file names will follow + the new shape (e.g. `KompactRuntimeReadScalarTest` exercises + `readScalar(raw, off, type: ScalarType)` for the common 8/16/32 lanes). +- **Ticket 07** (`VehicleTelemetry` example alignment) — **not auto-resolved**, + but the "teach the public API" choice becomes the natural one: if the + example getters use the new `readScalar` signature, the example teaches + the public API a newcomer will actually use. 07's "raw `readBits` shape" + option is still valid (codegen-output reference) but is less central. + Note in 07's body; do not resolve. + +### Implementation steps (for the follow-up commit) + +1. Add `kompact/src/commonMain/kotlin/ch/trancee/kompact/runtime/ScalarType.kt` + with the value class and the named constants. +2. Update `KompactRuntime.readScalar` / `readScalarLong` signatures to take + `type: ScalarType`. The internals (`readBits` / `readBitsLong` calls) + use `type.bitWidth` and `type.signed`. +3. Update `KompactWriter.writeScalar` signature to take `type: ScalarType`. + The internal dispatch (`writeBits` vs `writeBitsLong`) uses + `type.bitWidth`. +4. Update `KompactRuntimeCheckedReadTest.kt` (441 lines) to use the new + signature. This naturally sets up the ticket 09 split. +5. Update `GettingStartedTest.kt` to use the new signature; re-run + `:kompact:jvmTest` to confirm green and the wire bytes (`0xA5 0x40`) + still match. +6. Update `docs/getting-started.md` and `docs/api-reference.md` to use the + new signature. `VehicleTelemetry` getters stay on the raw `readBits` + path until ticket 07 resolves. +7. Regenerate the iOS klib golden on macOS via the `regen-goldens.yml` + workflow (the public ABI shifts: every `(Int, Boolean)` accessor + signature becomes `(Int, ScalarType)`, plus the new `ScalarType` class + and its `Companion`). +8. Push to `feat/laguna`; CI (`apiCheck` macOS + `jvmTest` Linux) should + stay green after the golden regen. + +## Comments diff --git a/.scratch/kompact-ergonomics/issues/02-readscalarlong-naming.md b/.scratch/kompact-ergonomics/issues/02-readscalarlong-naming.md new file mode 100644 index 0000000..2f9f380 --- /dev/null +++ b/.scratch/kompact-ergonomics/issues/02-readscalarlong-naming.md @@ -0,0 +1,142 @@ +Type: grilling +Status: resolved +Blocked by: — (unblocked 2026-09-05 by ticket 01 — the function stays; the rename is its own decision) + +# 02 — `readScalarLong` name misleads (accepts 1..64, reads as 64-bit) + +## Question + +`KompactRuntime.readScalarLong(raw, bitOffset, bitWidth, signed)` takes +a `bitWidth: Int` in `1..64`, but the name suggests a fixed 64-bit +read (the way the legacy `readInt64` / `readUInt64` overloads used to +work). A caller passing `bitWidth = 8` gets a `LongResult` back, but +the name "Long" reads as a width guarantee, not a width range. This +scored P2 in the prior code-review of `feat/laguna` (Fowler's +*Mysterious Name*). + +The decision: do we rename `readScalarLong` (and its companion +`writeScalar` on the writer, which has the same issue), or does +ticket 01's data-clump resolution collapse the two functions into one +parametric `readScalar` that returns a `Result` chosen by the value +class? If the answer to ticket 01 is "collapse," this ticket +auto-resolves; if the answer is "keep them separate," pick a name +that does not mislead (`readScalarWide`, `readScalar64`, or a +`ScalarType`-driven form). + +## Context for the claiming session + +- `kompact/src/commonMain/kotlin/ch/trancee/kompact/runtime/KompactRuntime.kt` + — `readScalarLong` (lines ~162–176). Signature: + `readScalarLong(raw: ByteArray, bitOffset: Int, bitWidth: Int, signed: Boolean): LongResult`. +- The result class for the 1..64 band is `LongResult`, which uses a + sentinel range near `Long.MIN_VALUE` for failure (see + `docs/architecture.md#runtime-error-encoding`). Renaming the + *function* does not change the *result type*; the question is + about the function's name only. +- `KompactRuntime.readBitsLong` (the raw primitive) has the same + 1..64 width range and is a *raw* accessor (no bounds check, no + typed result). It is documented alongside the checked accessors + in `docs/api-reference.md`. This ticket is about the *checked* + accessor; the raw primitive's name is consistent with the legacy + `readBits` / `readBitsLong` pair and is not in scope. +- The tutorial in `docs/getting-started.md` does not use + `readScalarLong` (it only uses `readScalar` for the 4-bit battery + and 10-bit speed). Renaming is therefore a public-API-only + change with no tutorial edit required. + +## Open sub-questions + +1. If ticket 01's data-clump is resolved by collapsing the two + functions into one parametric `readScalar` (with the result type + chosen by `ScalarType`), this ticket is auto-resolved. Confirm + or deny that dependency. +2. If kept as two functions, is the right rename `readScalarWide` + (matches "wide" as the Kotlin term for ≥33-bit integers), or + `readScalar64` (matches the legacy `Int64` / `UInt64` shape but + is *also* misleading because the function still takes a + `bitWidth` parameter), or some other name? +3. Does the writer's `writeScalar` (single function, takes 1..64 + bits) need a rename for consistency, or is the writer side + fine because the `Long` value type already signals the range? + +## What "resolved" looks like + +- The chosen name is recorded under `## Answer` (or this ticket + auto-resolves with a "see ticket 01" gist if the collapse is + chosen). +- If a rename is chosen, the new name is noted along with the + rationale (so the implementation commit can apply it). +- `docs/api-reference.md` is updated (or scheduled for the + implementation commit) to use the new name. + +## Answer + +**Decision: rename `readScalarLong` → `readScalarAsLong`. The `As` prefix +makes the result type (`LongResult`, backed by a primitive `Long`) +unambiguous and matches Kotlin's "as" pattern (`getOrThrow`, `as` +casts, `asReversed`). The no-suffix `readScalar` (implicit-`Int` +variant, returns `IntResult`) stays — it is not misleading.** + +The four sub-questions, settled: + +- **Does 01's resolution auto-close 02?** No. Ticket 01 explicitly + kept the reader split (`readScalar` 1..32 → `IntResult` vs. + `readScalarLong` 1..64 → `LongResult`); the long-variant + function stays and needs a name that doesn't mislead. +- **The right rename** — `readScalarAsLong` (the `As` prefix + signals "viewed as the destination type," matching the + stdlib-style suffix reading that Kotlin's `Int.toLong()` + already establishes: the suffix is the *result* type, not a + width guarantee). `readScalar` stays as the implicit-`Int` + variant. +- **Writer side** — no rename. The writer's + `writeScalar(type: ScalarType, value: Long)` takes a `Long` + value (zero-extended for widths ≤32, true `Long` for + 32..64); the `Long` here is unambiguously the value type + and is not misleading. +- **Pair symmetry (rejected)** — the alternative + `readScalarInt` + `readScalarLong` (both suffixed) was + considered and rejected: the no-suffix `readScalar` reads + naturally as "the read-scalar function" and is not the one + with a misleading name. Renaming `readScalar` to + `readScalarInt` is a larger public-API break for no clarity + gain. + +### Sketch (for the implementation commit) + +```kotlin +// KompactRuntime — rename only, signature unchanged from ticket 01 +- public inline fun readScalarLong(raw: ByteArray, bitOffset: Int, type: ScalarType): LongResult ++ public inline fun readScalarAsLong(raw: ByteArray, bitOffset: Int, type: ScalarType): LongResult +``` + +The internal dispatch and the `LongResult` failure encoding +(sentinel near `Long.MIN_VALUE`, per `docs/architecture.md#runtime-error-encoding`) +are unchanged. + +### Propagation + +- **Ticket 09** (Test file split) — **unblocked (already was)**. + If 09 splits per-accessor, the long-band test file is named + `KompactRuntimeReadScalarAsLongTest.kt` (not + `…ReadScalarLongTest.kt`). If 09 splits by result kind, the + `LongResult`-coverage file name is unchanged. Note in 09's + body; do not resolve. +- **Writer** — no rename. The writer's `writeScalar(type, value: + Long)` is not misleading (the `Long` is the value type, not + a width). +- **Docs** — `docs/api-reference.md#checked-typed-read-accessors` + table mentions `readScalarLong` by name; the implementation + commit updates it to `readScalarAsLong`. `docs/getting-started.md` + does not use `readScalarLong` (only `readScalar` for the + 4-bit battery and 10-bit speed), so no tutorial edit. +- **Implementation commit scope** — pure rename. No behavior + change. No klib golden regen needed (the *function name* is + part of the public ABI but the change is a one-symbol rename; + BCV's `apiCheck` will flag the diff and the macOS + `regen-goldens.yml` workflow regenerates the klib golden in + the same commit, matching the map's "Standing preference" + rule ("regenerate the goldens in the same commit" — see + `map.md#notes`). + +## Comments diff --git a/.scratch/kompact-ergonomics/issues/03-throw-helper-naming.md b/.scratch/kompact-ergonomics/issues/03-throw-helper-naming.md new file mode 100644 index 0000000..6cf3cf1 --- /dev/null +++ b/.scratch/kompact-ergonomics/issues/03-throw-helper-naming.md @@ -0,0 +1,151 @@ +Type: grilling +Status: resolved + +# 03 — `throwSmallFailure` / `throwLongFailure` / `throwDoubleFailure` leak the encoding + +## Question + +`KompactResult.kt` declares three `internal inline` helpers: +`throwSmallFailure`, `throwLongFailure`, `throwDoubleFailure`. They +each call `throw KompactDecodeException(decodeXxxError(packed))` and +exist so each platform actual's `getOrThrow()` stays one expression +(Ticket 10 deepen). The names describe the internal packed-Long +encoding ("small" = ≤32-bit packed layout, "long" = `LongResult` +sentinel band, "double" = NaN-payload), not the caller's intent. A +reader of `KompactResult.kt` has to know the encoding taxonomy to +understand which helper to call. The repo's decoding helpers follow +the opposite convention (`decodeErrorFromSmallBits`, +`decodeLongError`, `decodeDoubleError` — encoding-aware on the +*decode* side) so the asymmetry is real. + +The decision: do we rename the three helpers to intent-revealing +names (`throwFromPacked`, `throwDecodeFailure`, or a single +`throwFailure(packed, kind: ErrorKind)`), and/or collapse them into +one helper that takes the kind as a parameter? This is Fowler's +*Mysterious Name* and scored P3 in the prior code-review of +`feat/laguna`. + +## Context for the claiming session + +- `kompact/src/commonMain/kotlin/ch/trancee/kompact/runtime/KompactResult.kt` + — `throwSmallFailure` / `throwLongFailure` / `throwDoubleFailure` + (lines ~134–141). Each is `internal inline fun ...(packed: Long): Nothing = throw KompactDecodeException(decodeXxxError(packed))`. +- The decoding helpers they wrap are + `decodeErrorFromSmallBits(packed)`, + `decodeLongError(packed)`, and + `decodeDoubleError(packed)`. All three return a + `KompactDecodeError`. +- The helpers are called from the seven result value class + `getOrThrow()` members in `jvmMain` and `iosMain` + (`kompact/src/jvmMain/.../KompactResult.kt`, + `kompact/src/iosMain/.../KompactResult.kt`). Each `getOrThrow` + does `if (isSuccess) primitive else throwXxxFailure(packed)`. +- These helpers are `internal` — they are not part of the public + API. The public surface is `getOrThrow()` on the seven result + classes. So the rename is an *internal* refactor with no + consumer-visible effect; the cost is the touch on every platform + actual (4 calls in jvmMain, 4 in iosMain). + +## Open sub-questions + +1. **One helper or three?** A single `throwFailure(packed, kind: + ErrorKind)` would force the kind to be a parameter at every call + site, which is what the current three-helper split avoids. The + trade is clarity (one name) vs. terseness at the call site (no + extra arg). +2. **Naming convention** — `throwFromPacked` matches the repo's + `decodeErrorFromSmallBits` pattern. `throwDecodeFailure` matches + the failure semantic. Pick one (or propose a third). +3. **Should the decoding helpers be renamed for symmetry too?** + Out of scope for this ticket (they are already encoding-aware by + design), but the answer here might point at a follow-up. + +## What "resolved" looks like + +- The chosen name (or collapsed-form) is recorded under + `## Answer` with a one-line rationale. +- The four call sites in each platform's `KompactResult.kt` are + noted so the implementation commit can apply the rename + consistently. +- The decision is propagated to ticket 09 (test file split) if + any test file references the old names directly. + +## Answer + +**Decision: rename the three helpers to mirror the existing decode +pattern. `throwSmallFailure` → `throwDecodeErrorFromSmallBits`, +`throwLongFailure` → `throwDecodeErrorFromLong`, +`throwDoubleFailure` → `throwDecodeErrorFromDouble`. Three-helper +split is kept (each picks the right decoder for its encoding); +the rename is internal-only with no consumer-visible effect.** + +The sub-questions, settled: + +- **One helper or three?** Three. A single `throwFromPacked(packed, + kind)` would add a `kind` parameter at every call site; the + three-helper split keeps `getOrThrow()` a one-expression + function per the Ticket 10 deepen note. The three encodings + are genuinely different decoders (`decodeErrorFromSmallBits` / + `decodeLongError` / `decodeDoubleError`) so the split is + intrinsic to the data, not a stylistic choice. +- **Naming convention** — mirror the decode pattern. The + decode helpers are `decodeErrorFrom{SmallBits,Long,Double}`; + the throw helpers become `throwDecodeErrorFrom{SmallBits,Long,Double}`. + The `throw` prefix is the caller's intent (the action); + `DecodeError` is the kind of exception (matching + `KompactDecodeException`'s class name); `From{SmallBits,Long,Double}` + is the encoding suffix (the same suffix as the decode + helper each one wraps). The result is a clean + `{verb}DecodeErrorFrom{Encoding}` pattern where `verb ∈ + {decode, throw}`. +- **Rename the decode helpers too?** No. They are already + encoding-aware by design (`decodeErrorFrom{SmallBits,Long,Double}`); + the new throw names now share the `From{Encoding}` suffix + with them, completing the symmetry. No follow-up needed. + +### Sketch (for the implementation commit) + +```kotlin +// KompactResult.kt — commonMain (lines 136–141) +- internal inline fun throwSmallFailure(packed: Long): Nothing = +- throw KompactDecodeException(decodeErrorFromSmallBits(packed)) +- internal inline fun throwLongFailure(packed: Long): Nothing = +- throw KompactDecodeException(decodeLongError(packed)) +- internal inline fun throwDoubleFailure(packed: Long): Nothing = +- throw KompactDecodeException(decodeDoubleError(packed)) ++ internal inline fun throwDecodeErrorFromSmallBits(packed: Long): Nothing = ++ throw KompactDecodeException(decodeErrorFromSmallBits(packed)) ++ internal inline fun throwDecodeErrorFromLong(packed: Long): Nothing = ++ throw KompactDecodeException(decodeLongError(packed)) ++ internal inline fun throwDecodeErrorFromDouble(packed: Long): Nothing = ++ throw KompactDecodeException(decodeDoubleError(packed)) +``` + +The seven call sites in `jvmMain/.../KompactResult.kt` and +`iosMain/.../KompactResult.kt` (one per result class, in each +class's `getOrThrow()`) update to match. For example, the +`IntResult` `getOrThrow()` in `jvmMain` (line ~55) becomes: + +```kotlin +- else throwSmallFailure(packed) ++ else throwDecodeErrorFromSmallBits(packed) +``` + +### Propagation + +- **No public API change.** The three helpers are `internal`; + the rename is contained to the `:kompact` module. No BCV + golden regen needed. +- **No test changes needed.** The helpers are tested + transitively through `getOrThrow()` (the public surface); + the test assertions (`assertFailsWith`) + are name-agnostic. +- **No docs change.** The helpers are not mentioned in + `docs/api-reference.md` (they are `internal`); the + `docs/architecture.md#runtime-error-encoding` discussion + talks about the packed-Long layout, not the helper names. +- **Implementation commit scope** — pure rename. 3 helper + declarations + 14 call sites (7 in `jvmMain`, 7 in + `iosMain`). No behavior change. No golden regen. + +## Comments diff --git a/.scratch/kompact-ergonomics/issues/04-result-ergonomics-layer.md b/.scratch/kompact-ergonomics/issues/04-result-ergonomics-layer.md new file mode 100644 index 0000000..83206cc --- /dev/null +++ b/.scratch/kompact-ergonomics/issues/04-result-ergonomics-layer.md @@ -0,0 +1,278 @@ +Type: grilling +Status: resolved +Blocked by: — (unblocked 2026-09-05 by ticket 01 — the parameter shape and the result-type shape are independent; 01 settled the parameter only) + +# 04 — Result ergonomics layer: how do consumers recover from failure? + +## Question + +The seven specialized result value classes (`ByteResult`, +`ShortResult`, `IntResult`, `LongResult`, `FloatResult`, +`DoubleResult`, `BooleanResult`) are the central ergonomic choice in +the runtime: they make the success path zero-alloc on the JVM +(`@JvmInline` over a `Long`) and on iOS (plain value class over +`Long`). A consumer today writes: + +```kotlin +val battery: Int = KompactRuntime + .readScalar(bytes, 0, 4, signed = false) + .getOrThrow() +``` + +For a consumer who wants to *handle* failure (not throw), the +`getOrThrow()` call is wrong, and the alternative is verbose: + +```kotlin +val result = KompactRuntime.readScalar(bytes, 0, 4, signed = false) +when (val err = result.error) { + null -> { /* success: use result.getOrThrow() */ } + KompactDecodeError.BoundsError -> { /* ... */ } + KompactDecodeError.BadLengthPrefix -> { /* ... */ } + KompactDecodeError.TruncatedNested -> { /* ... */ } + is KompactDecodeError.UnknownEnumCode -> { /* ... */ } +} +``` + +The decision: do we add a higher-level ergonomics layer to make the +common patterns cheaper, and if so what is it? Options to grill: + +- **A `decodeOrThrow` / `tryDecode` extension on each result type** — + e.g. `val battery: Int = KompactRuntime.readScalar(...).getOrThrow()` + becomes `val battery: Int = KompactRuntime.readScalarOrThrow(...)` + (function-level, returns the primitive, throws on failure). Cheaper + at the call site, but loses the explicit "this returns a typed + result" signal. +- **A single `Result` alias or namespace** that re-exports the + seven result types under one umbrella, so consumers + `import kompact.Result` and get `Result.Int`, `Result.Long`, etc. + Reduces the seven-name surface to one import. +- **A `KompactResult` *sealed* companion type** that wraps the seven + result types behind one type parameter, with a `fold` / + `when`-friendly `getOrElse { error -> ... }` API. Centralizes the + recovery pattern but reintroduces a generic — which is what + ticket 08 in the v1 spec explicitly *rejected* (the rejection + rationale: a generic `KompactDecodeResult` boxes on the success + path on the JVM, breaking the zero-alloc contract). +- **A function-shape ergonomics helper** that does the + `if (result.isFailure) TypedError else use(...)` pattern in one + call, e.g. `KompactFraming.readNestedTyped(raw, off, w): NestedRegionResult` + (see ticket 05). Each framing helper that currently returns + `null` gets a typed-result companion. +- **Nothing — the per-kind specialization *is* the ergonomics.** + The seven result types are the right call; the recovery pattern + is verbose but explicit; this ticket is the user pushing back + on the verbosity only to confirm that it is what we want. + +## Context for the claiming session + +- `kompact/src/commonMain/kotlin/ch/trancee/kompact/runtime/KompactResult.kt` — + the seven `expect value class` declarations and the + `KompactDecodeError` sealed class (`BoundsError`, + `BadLengthPrefix`, `TruncatedNested`, + `UnknownEnumCode(rawCode: Int)`). +- `kompact/src/commonMain/kotlin/ch/trancee/kompact/runtime/KompactDecodeError.kt` — + the sealed error taxonomy. +- `docs/api-reference.md#typed-result-value-classes` — current + reference text for the result types. +- `docs/getting-started.md` — the consumer-facing tutorial that + demonstrates the verbose recovery pattern. Any ergonomics layer + should be reflected there. +- `GettingStartedTest` in `kompact/src/commonTest/.../runtime/` — + the executable journey; if a new helper is added, the test + should also exercise it. +- The v1 spec map's ticket 08 notes ("Runtime error model — + specialized zero-alloc result value classes") — the rejection + rationale for a generic `KompactDecodeResult` is the + zero-alloc-on-success contract. Any new ergonomics layer must + not regress that. + +## Open sub-questions + +1. **What is the *common* recovery pattern in the wild?** The + v1 spec assumes `getOrThrow()` is the success path and the + `error` property is the failure path. If a survey of the + `commonTest` cases shows that 80% of consumers want to + short-circuit on failure, an extension function or helper that + does that in one call is justified. If the survey shows the + pattern is 50/50, two helpers (`getOrThrow` + a + `fold`/`map`/`getOrElse`) is the right answer. +2. **Is the seven-class surface actually confusing for newcomers?** + A new visitor reading `docs/api-reference.md` sees seven types + with the same four members and asks "why seven?" The answer + (zero-alloc on success on both JVM and iOS) is in + `docs/architecture.md`, but the *first impression* is friction. + A single namespace (`Result.Int`, `Result.Long`, …) is cheaper + to learn. Is the friction worth the zero-alloc guarantee? +3. **The writer side** — does the writer surface need an + ergonomics layer? The writer currently throws + `IllegalArgumentException` from `writeLengthPrefix` when the + prefix width is not in `VALID_PREFIX_WIDTHS` (lines ~48–57 of + `KompactFraming.kt`). Should the writer's failure modes also + become typed results? (Out of scope: the writer is not on the + zero-alloc hot path, so throwing is acceptable. The + architectural call here matters for symmetry.) + +## What "resolved" looks like + +- The chosen ergonomics layer (or "nothing") is recorded under + `## Answer` with a one-line rationale and a sketch of the new + surface (3–10 lines of Kotlin). +- If a new layer is added, the call-site shape is shown so the + implementation commit can apply it to `KompactRuntime`, + `KompactFraming`, and the platform actuals. +- The downstream ticket 05 (`readNested` typed-result companion) + is updated to align with the chosen layer. + +## Answer + +**Decision: adopt all three ergonomics additions — `…OrThrow` function-level +wrappers, a `Kompact.Result` namespace re-export, and `getOrElse` / `map` +extension functions on the seven result value classes. All three are +additive (no public-API break, no zero-alloc regression, no BCV regen +needed for the extensions — only the `Kompact.Result` namespace adds +a new top-level public symbol).** + +### What gets added + +1. **`…OrThrow` function-level wrappers** (5 new public functions on + `KompactRuntime`): each returns the primitive directly and throws + `KompactDecodeException` on bounds / encoding failure. The spec's + ticket 08 already contemplated this for Java interop; the + decision is to keep it for all callers (Kotlin and Java). + ```kotlin + public fun readBoolOrThrow(raw: ByteArray, bitOffset: Int): Boolean + public fun readScalarOrThrow(raw: ByteArray, bitOffset: Int, type: ScalarType): Int + public fun readScalarAsLongOrThrow(raw: ByteArray, bitOffset: Int, type: ScalarType): Long + public fun readFloatOrThrow(raw: ByteArray, bitOffset: Int): Float + public fun readDoubleOrThrow(raw: ByteArray, bitOffset: Int): Double + ``` + The internals do the same `fits` check the typed-result accessors + do, then call `readBits` / `readBitsLong` directly and return the + primitive. The `…OrThrow` form is one call site (`KompactRuntime + .readScalarOrThrow(bytes, 0, ScalarType.UINT_8)`); the typed-result + form is two (`KompactRuntime.readScalar(bytes, 0, ScalarType.UINT_8) + .getOrThrow()`). Both are zero-alloc on the success path (the + `…OrThrow` form avoids the value-class construction altogether + but the cost of the value-class was already zero on both JVM and + iOS — the win is call-site ergonomics, not allocation). + +2. **`Kompact.Result` namespace re-export** (1 new top-level public + symbol). `object ch.trancee.kompact.Kompact.Result` exposes the + seven result types as members: `Kompact.Result.Int`, + `Kompact.Result.Long`, `Kompact.Result.Boolean`, etc. The seven + top-level declarations stay (no breaking change); the namespace + is a one-stop import for newcomers who prefer a single import + path. Cost: 7 lines in `KompactResult.kt` (a `typealias`-style + re-export, not new types). + ```kotlin + public object Result { + public typealias Byte = ByteResult + public typealias Short = ShortResult + public typealias Int = IntResult + public typealias Long = LongResult + public typealias Float = FloatResult + public typealias Double = DoubleResult + public typealias Boolean = BooleanResult + } + ``` + +3. **`getOrElse` / `map` extension functions** on the seven result + value classes (14 new top-level public functions in + `commonMain/.../KompactResult.kt`). Mirror stdlib's + `Result` pattern: `getOrElse` takes a `(KompactDecodeError) + -> T` fallback; `map` takes a `(T) -> T` success transform + and returns the same result kind (so the failure propagates + unchanged). Both are pure extension functions; no value-class + change; no zero-alloc regression. + ```kotlin + public inline fun IntResult.getOrElse(fallback: (KompactDecodeError) -> Int): Int + public inline fun IntResult.map(transform: (Int) -> Int): IntResult + // …same for ShortResult, LongResult, FloatResult, DoubleResult, ByteResult, BooleanResult + ``` + +### What does NOT change + +- **The seven specialized result value classes** stay as + `expect value class` in `commonMain` + `@JvmInline actual` on + JVM + plain `actual` on iOS. The spec's ticket 08 zero-alloc + contract is preserved: no generic, no boxing, no throw on + the success path. The new extensions and the namespace do not + touch the encoding or the layout. +- **The four-member public surface** of each result class + (`isSuccess`, `isFailure`, `error`, `getOrThrow()`) stays. + `getOrElse` and `map` are additive; `getOrThrow()` is still + the throw-on-failure recovery call. +- **No `KompactDecodeResult` generic** — explicitly rejected by + the spec's ticket 08 (boxes on the success path on the JVM). + The ergonomics layer is *around* the seven specialized types, + not *instead of* them. + +### Sketch (for the implementation commit) + +```kotlin +// KompactResult.kt — additions to the existing file + +// 1. Namespace re-export +public object Result { + public typealias Byte = ByteResult + public typealias Short = ShortResult + public typealias Int = IntResult + public typealias Long = LongResult + public typealias Float = FloatResult + public typealias Double = DoubleResult + public typealias Boolean = BooleanResult +} + +// 2. getOrElse / map extensions (one per result kind) +public inline fun IntResult.getOrElse(fallback: (KompactDecodeError) -> Int): Int = + if (isSuccess) (packed and RESULT_VALUE_MASK).toInt() else fallback(error!!) +public inline fun IntResult.map(transform: (Int) -> Int): IntResult = + if (isSuccess) IntResult.success(transform((packed and RESULT_VALUE_MASK).toInt())) + else this +// …repeated for ShortResult, LongResult, FloatResult, DoubleResult, ByteResult, BooleanResult + +// KompactRuntime.kt — `…OrThrow` wrappers (additive) +public inline fun readBoolOrThrow(raw: ByteArray, bitOffset: Int): Boolean { + if (!fits(raw, bitOffset, 1)) throwBounds(BoundsError()) + return readBitsBoolean(raw, bitOffset) +} +public inline fun readScalarOrThrow(raw: ByteArray, bitOffset: Int, type: ScalarType): Int { + val w = type.bitWidth + if (!fits(raw, bitOffset, w)) throwBounds(BoundsError()) + val raw_ = readBits(raw, bitOffset, w) + return if (type.signed) signExtend(raw_, w) else raw_ +} +// …similar for readScalarAsLongOrThrow, readFloatOrThrow, readDoubleOrThrow +``` + +### Propagation + +- **Ticket 05** (`readNested` typed-result companion) — **unblocked**. + The ergonomics layer is now settled; 05 picks a name and applies + the same pattern. The recommended shape for 05 is: + - `KompactFraming.readNested(raw, off, prefixBitWidth): NestedRegionResult` + (typed result, with `getOrElse`/`map` extensions on + `NestedRegionResult` mirroring the result-class pattern). + - `KompactFraming.readNestedOrThrow(raw, off, prefixBitWidth): Pair` + (primitive pair, throws on failure). + - `KompactFraming.nestedRegionOrNull(...)` stays as the nullable + hot-path internal (zero-alloc; what the new functions call + internally). Deprecation cycle: the library has not been + released, so it's removed immediately (no deprecation cycle + per the map's "Blast radius" rule). + Note in 05's body; do not resolve. +- **Ticket 09** (Test file split) — note the new public symbols + (`…OrThrow`, `getOrElse`, `map`) need their own test coverage. + If 09 splits per-accessor, the new test files include + `KompactRuntimeReadScalarOrThrowTest.kt` etc. +- **Docs** — `docs/api-reference.md` adds a new subsection under + the runtime for `…OrThrow`, `getOrElse`/`map`, and the + `Kompact.Result` namespace. The `KompactResult` section + (current `## Typed result value classes`) is reorganized to + list the seven types and the new extensions. +- **Public ABI** — adds 1 top-level symbol (`Kompact.Result`), + 5 `…OrThrow` functions, 14 extension functions (7 `getOrElse` + + 7 `map`). BCV will catch the drift; klib golden regen on + macOS via the existing `regen-goldens.yml` workflow. + +## Comments diff --git a/.scratch/kompact-ergonomics/issues/05-readnested-typed-result.md b/.scratch/kompact-ergonomics/issues/05-readnested-typed-result.md new file mode 100644 index 0000000..acc4f2c --- /dev/null +++ b/.scratch/kompact-ergonomics/issues/05-readnested-typed-result.md @@ -0,0 +1,251 @@ +Type: grilling +Status: resolved +Blocked by: — (unblocked 2026-09-05 by ticket 04 — the ergonomics pattern is settled: `…OrThrow` function-level wrappers for the throw-on-failure case, typed-result companion + `getOrElse`/`map` extensions for the recovery case, and a nullable internal for the hot path. 05 picks a name and applies the same pattern.) + +# 05 — `KompactFraming.nestedRegionOrNull` should have a typed-result companion + +## Question + +`KompactFraming.nestedRegionOrNull(raw, bitOffset, prefixBitWidth): Pair?` +returns a nullable pair for a parse-forward nested region. The +nullable return is the right call for the framing hot path (no +allocation, no throw) — but it forces every consumer to repeat the +same `if (region == null) TypedError else use(start, length)` +boilerplate. The writer has a `writeNested` that mirrors the +`nestedRegionOrNull` *shape*; the reader should have a typed-result +companion that mirrors the writer's surface, e.g. +`KompactFraming.nestedRegion(raw, off, w): NestedRegionResult` with +`getOrThrow()` returning the `(startBit, bitLength)` pair. + +The decision: do we add the typed-result companion, and if yes, is +it the same shape as the result ergonomics layer chosen in ticket 04 +(a single `NestedRegionResult` with the `success` / `failure` / +`error` / `getOrThrow` pattern), or does ticket 04's resolution +automatically provide it (e.g. if ticket 04 picks a single +namespace, the framing helper adopts the namespace's `NestedRegion` +result)? + +## Context for the claiming session + +- `kompact/src/commonMain/kotlin/ch/trancee/kompact/runtime/KompactFraming.kt` + — `nestedRegionOrNull` (lines ~66–84). The function reads the + byte-count length prefix at `bitOffset` (width `prefixBitWidth` ∈ + `{8, 16, 32}`), validates against `KompactRuntime.fits`, and + returns the sub-region as `(startBit, bitLength)`. Returns + `null` on overrun (which the caller maps to a typed + `TruncatedNested` / `BadLengthPrefix`). +- The companion reader for the *count* prefix + (`readLengthPrefix`, lines ~32–41) returns `Int` directly (with + `-1` on failure) — also a "no allocation" hot-path pattern. + Should the count-prefix reader also get a typed-result + companion, or is `-1` the right shape for that one (since the + count is a single `Int`, not a pair)? +- The writer's `writeNested` and `writeRepeated` in + `KompactWriter.kt` (lines ~78–107) are the user-facing + symmetric shape. A consumer writing a nested payload with + `writeNested { ... }` and then reading it back with + `nestedRegionOrNull` is crossing the "nullable pair vs. + block" boundary twice; the typed-result companion makes the + read symmetric with the write. +- The v1 spec map's ticket 05 ("Framing — sequential + length-delimited") and ticket 08 ("Runtime error model") are + the locked references. Neither forbids a typed-result framing + helper; both require the success path to be allocation-free. + +## Open sub-questions + +1. **Naming** — `nestedRegion` (vs the current + `nestedRegionOrNull`) is the natural symmetric name, with the + `?` suffix signaling the nullable variant. Confirm or propose + an alternative. +2. **The result type** — a new `NestedRegionResult` value class + wrapping `(startBit: Int, bitLength: Int)` (a `Pair<…>`-shaped + payload), or a different shape (e.g. a `Long` packing + `startBit << 32 | bitLength`, but that's an internal encoding + detail and would leak). +3. **Count-prefix companion** — should `readLengthPrefix`'s `-1` + return also become a typed result, or is that out of scope for + this ticket (the user-facing pair is what newcomers see first)? +4. **`nestedRegionOrNull` deprecation** — once the typed-result + companion lands, does the nullable form stay (as a hot-path + internal), get deprecated (Q7: 1-MINOR cycle), or get removed + immediately? Per the v1 release status (not yet on Maven + Central), the deprecation cycle is cheap; pick the + user-friendliest path. + +## What "resolved" looks like + +- The chosen shape (or "no, the nullable pair is the right call") + is recorded under `## Answer` with a sketch of the new + companion's signature. +- If a new companion is added, the relationship to ticket 04's + ergonomics layer is recorded (does it use the layer's + namespace, or stand alone?). +- The deprecation question (keep / deprecate / remove + `nestedRegionOrNull`) is answered. + +## Answer + +**Decision: full ergonomics pair, plus the count-prefix `…OrThrow` +companion. The nullable `nestedRegionOrNull` demotes to `internal` +(kept as the hot-path internal that the new public functions +delegate to). No deprecation cycle (the library has not been +released, per the map's "Blast radius" rule).** + +### What gets added + +1. **`KompactFraming.readNestedOrThrow(raw, bitOffset, prefixBitWidth): Pair`** + — the throw-on-failure companion. Throws + `KompactDecodeException(BadLengthPrefix)` if `prefixBitWidth` + is not in `VALID_PREFIX_WIDTHS` or the prefix overruns the + buffer; throws `KompactDecodeException(TruncatedNested)` if + the nested region's payload overruns the buffer; throws + `KompactDecodeException(BoundsError)` if the byte count + overflows `Int` (the F-003 boundary check at the original + `nestedRegionOrNull`'s `byteCount > Int.MAX_VALUE / 8` line). +2. **`KompactFraming.readNested(raw, bitOffset, prefixBitWidth): NestedRegionResult`** + — the typed-result companion. Returns a + `NestedRegionResult` value class on success, a + `KompactDecodeError` on failure (zero-alloc on both JVM and + iOS, per the ticket 03 value-class pattern). +3. **`value class NestedRegionResult(val startBit: Int, val bitLength: Int)`** + — declared in `commonMain/.../KompactFraming.kt` (or a new + `NestedRegionResult.kt`). `@JvmInline` on the JVM actual; + plain `value class` on iOS actual. Two `Int` fields; the + field names are self-documenting (no `Pair.first` / + `Pair.second` confusion). The four-member public surface + mirrors the seven result classes: `isSuccess`, `isFailure`, + `error`, `getOrThrow(): Pair`. +4. **`KompactFraming.readLengthPrefixOrThrow(raw, bitOffset, bitWidth): Int`** + — the count-prefix throw-on-failure companion. Throws + `KompactDecodeException(BadLengthPrefix)` on invalid width + or overrun. The `-1`-on-failure form stays for the hot-path + internal; the `…OrThrow` form is the user-facing throw + path. No typed result for the count prefix — a single `Int` + is too small to justify a new value class (the spec's + ticket 08 keeps the seven specialized result value classes + per scalar kind; the count prefix is not a scalar). +5. **`Kompact.Result.NestedRegion` typealias** in + `KompactResult.kt` (the `Kompact.Result` namespace from + ticket 04). Mirrors the `Kompact.Result.Int` / `…Long` / + etc. pattern. +6. **`NestedRegionResult.getOrElse(fallback: (KompactDecodeError) -> Pair): Pair`** + and **`NestedRegionResult.map(transform: (Pair) -> Pair): NestedRegionResult`** + extensions, mirroring the stdlib `Result` pattern from + ticket 04. Pure extensions; no value-class change. + +### What gets demoted + +- **`KompactFraming.nestedRegionOrNull`**: `public inline` → + `internal inline`. The new public functions (`readNested` and + `readNestedOrThrow`) delegate to it; it's the hot-path + internal. No deprecation cycle (the library has not been + released, per the map's "Blast radius" rule). + +### Sketch (for the implementation commit) + +```kotlin +// KompactFraming.kt — commonMain (additions) + +public value class NestedRegionResult internal constructor(private val packed: Long) { + public val startBit: Int get() = (packed ushr 32).toInt() + public val bitLength: Int get() = packed.toInt() and 0xFFFFFFFF.toInt() + // isSuccess / isFailure / error / getOrThrow() — see ticket 03 value-class pattern + // Companion.success(startBit, bitLength) / failure(error) +} + +public inline fun readNestedOrThrow( + raw: ByteArray, bitOffset: Int, prefixBitWidth: Int +): Pair { + val region = nestedRegionOrNull(raw, bitOffset, prefixBitWidth) + ?: throw KompactDecodeException(/* BadLengthPrefix or TruncatedNested or BoundsError */) + return region.first to region.second +} + +public inline fun readNested( + raw: ByteArray, bitOffset: Int, prefixBitWidth: Int +): NestedRegionResult { + val region = nestedRegionOrNull(raw, bitOffset, prefixBitWidth) + return if (region != null) NestedRegionResult.success(region.first, region.second) + else NestedRegionResult.failure(/* BadLengthPrefix or TruncatedNested or BoundsError */) +} + +public inline fun readLengthPrefixOrThrow( + raw: ByteArray, bitOffset: Int, bitWidth: Int +): Int { + if (bitWidth !in VALID_PREFIX_WIDTHS) { + throw KompactDecodeException(KompactDecodeError.BadLengthPrefix) + } + if (!KompactRuntime.fits(raw, bitOffset, bitWidth)) { + throw KompactDecodeException(KompactDecodeError.BadLengthPrefix) + } + return when (bitWidth) { + 8 -> KompactRuntime.readBits(raw, bitOffset, 8) + 16 -> KompactRuntime.readBits(raw, bitOffset, 16) + else -> KompactRuntime.readBitsLong(raw, bitOffset, 32).toInt() + } +} + +// KompactFraming.kt — demoted (was public, now internal) +- public inline fun nestedRegionOrNull( ++ internal inline fun nestedRegionOrNull( + raw: ByteArray, bitOffset: Int, prefixBitWidth: Int +): Pair? { … } + +// KompactResult.kt — additions ++ public typealias NestedRegion = NestedRegionResult // in the Kompact.Result namespace ++ public inline fun NestedRegionResult.getOrElse(fallback: (KompactDecodeError) -> Pair): Pair ++ public inline fun NestedRegionResult.map(transform: (Pair) -> Pair): NestedRegionResult +``` + +### Propagation + +- **Test updates** (in the implementation commit): + - `kompact/src/commonTest/.../KompactFramingTest.kt:88, 102, 124, 127, 131, 134` + — these six call sites use `nestedRegionOrNull`. After the + demotion, they switch to `readNested` (typed result) or + `readNestedOrThrow` (throw). The two test methods named + `nestedRegionOrNull_rejectsIntMaxByteCountPrefix` and + `nestedRegionOrNull32_roundTripsLegitByteCount` rename to + `readNested_rejectsIntMaxByteCountPrefix` and + `readNested_roundTripsLegitByteCount` (the public accessor + is `readNested` now). + - `kompact/src/commonTest/.../KompactWriterTest.kt:105` — uses + `nestedRegionOrNull` to read back the `writeNested` output. + Switches to `readNested(buf, 0, 16).getOrThrow()`. +- **Docs**: + - `docs/api-reference.md#kompactframing` — the public-surface + table currently lists `nestedRegionOrNull`. After the + demotion, the table lists the new public functions + (`readNestedOrThrow`, `readNested`, `readLengthPrefixOrThrow`) + plus the new `NestedRegionResult` value class (with its + four-member surface). `nestedRegionOrNull` is removed + from the public table; a note marks it as the hot-path + internal. + - `docs/architecture.md#framing-contract` — the reference to + `nestedRegionOrNull` in the framing helpers sentence + (`writeLengthPrefix / nestedRegionOrNull object`) updates + to reference the new public surface. + - `docs/getting-started.md` — does not currently use + `nestedRegionOrNull`; the tutorial exercises fixed-width + scalars + the writer's `writeNested` (no read-back of + nested). The tutorial's "long-form payload" section (if + it gets added in a follow-up) can use the new + `readNested` as the symmetric reader. No edit required for + this ticket. +- **Public ABI change** — adds 1 new value class + (`NestedRegionResult`), 3 new public functions + (`readNestedOrThrow`, `readNested`, `readLengthPrefixOrThrow`), + 2 new extensions (`getOrElse`, `map`). Removes 1 public + function (`nestedRegionOrNull`, demoted to `internal`). + Net: 5 new public symbols, 1 demoted. BCV will catch the + drift; klib golden regen on macOS via the existing + `regen-goldens.yml` workflow. +- **Ticket 09** (Test file split) — note the new + `KompactFramingTest` tests for `readNested` / + `readNestedOrThrow` / `readLengthPrefixOrThrow` / + `NestedRegionResult` extensions may live in the framing + test file or a new `KompactFramingNestedRegionTest.kt` per + the split decision. Note in 09's body; do not resolve. + +## Comments diff --git a/.scratch/kompact-ergonomics/issues/06-annotation-visibility.md b/.scratch/kompact-ergonomics/issues/06-annotation-visibility.md new file mode 100644 index 0000000..3ad9770 --- /dev/null +++ b/.scratch/kompact-ergonomics/issues/06-annotation-visibility.md @@ -0,0 +1,260 @@ +Type: research +Status: resolved + +# 06 — `@KompactModel` / `@KompactField`: ship a processor, hide them, or keep as-is? + +## Question + +The annotations `@KompactModel` (class-target, source-retained) and +`@KompactField(bitOffset, bitWidth, lengthPrefixWidth, isNested, +repeatCountWidth, enumWidth, defaultValue, isVersionField)` are +declared in `kompact/src/commonMain/kotlin/ch/trancee/kompact/runtime/KompactAnnotations.kt` +and pinned by `KompactFieldV1SurfaceTest` (compile-time contract that +all eight members exist). They are documented in +`docs/api-reference.md#annotations` and referenced in the bundled +`VehicleTelemetry` example. + +The v1 spec map's ticket 02 ("Code generation — KSP 2.3.9+") +records the *decision* that a KSP processor will generate the +view-class bodies from the annotations, but the processor has +**never been implemented** in this repository. The annotation +surface therefore sits in the public API with no consumer +producers: a newcomer reading the README sees the annotations, +adds them to a model, and gets nothing (the annotations are +source-retained; no runtime effect; no codegen). + +Three options to grill: + +1. **Ship a minimal KSP processor** in a new `:kompact-ksp` module + (per ticket 12, "Module split & publication — split modules, + KSP-safe jar") that generates the `expect value class` + + `@JvmInline actual` / plain `actual` from a `@KompactModel` + value class declaration with `@KompactField` properties. This + makes the annotation surface a real ergonomic affordance: a + consumer writes the model class, the KSP processor generates + the read/write bodies, and the runtime's `KompactRuntime` / + `KompactWriter` calls stay zero-alloc. +2. **Hide the annotations** from the public API until the + processor lands. Move them to an `internal` package + (`ch.trancee.kompact.annotations.internal`) and gate the + public surface on `@RequiresOptIn` or a `Beta` annotation so + consumers don't accidentally use them. The runtime stays + usable by hand-written `KompactRuntime.readBits` calls (the + way `VehicleTelemetry` works today). +3. **Keep as-is.** The annotations are documented as + compile-time-only schema metadata; a newcomer reading the + API reference sees the note "no processor ships in this + repository today" and either writes a hand-written view class + or waits for the processor. Accept the documentation + friction. + +The decision: which of the three, and if (1), what is the +minimum viable processor scope (just `readBits`-shaped getters, +or full Ticket 04+05+07 generation including length prefixes and +nested)? + +## Context for the claiming session + +- `kompact/src/commonMain/kotlin/ch/trancee/kompact/runtime/KompactAnnotations.kt` — + the two annotation declarations (lines ~11–49). +- `kompact/src/commonTest/kotlin/ch/trancee/kompact/runtime/KompactFieldV1SurfaceTest.kt` + — the compile-time contract test. +- `.scratch/kompact-spec/issues/02-generation-strategy.md` — the + locked decision (KSP 2.3.9+, KSP emits whole `value class` source + files into commonMain). +- `.scratch/kompact-spec/issues/12-module-split-and-publication.md` + — the module split (`:kompact` runtime + `:kompact-ksp` processor + + optional gradle plugin). +- `.scratch/kompact-spec/issues/03-value-class-representation.md` + — the expect/actual value-class pattern the processor would + generate. +- `.scratch/kompact-spec/research/ksp-kmp-generation.md` — the + research notes on KSP generation across KMP targets. +- The annotations are referenced in the API reference + (`docs/api-reference.md#annotations`) and in + `VehicleTelemetry.kt` (the bundled example uses + `@KompactModel` + `@KompactField`). + +## Open sub-questions + +1. **Scope of the minimal processor (option 1)** — the v1 type + set is large (signed/unsigned scalars 1–64, booleans, IEEE + floats, strings, blobs, nested, repeated, versioning). A + processor that handles the full set is a large piece of work. + A minimum viable processor that handles only the + fixed-width scalar case (the same case `VehicleTelemetry` + exercises today) is a much smaller lift and would make + *that* model ergonomic. Which scope is the right MVP for this + effort's blast radius? +2. **Module split** — option 1 implies a new `:kompact-ksp` + module. The current `settings.gradle.kts` includes only + `:kompact`. Adding a new module is a non-trivial change to + the build. Is that within this effort's blast radius, or is + it a fresh effort? +3. **`Beta` / `@RequiresOptIn` semantics (option 2)** — Kotlin's + `@RequiresOptIn` is the idiomatic gate. The annotations + *aren't* experimental in the language sense (they're stable + source-retained metadata), so the gate would be a + "we reserve the right to change the schema metadata" notice. + Worth it, or over-engineered? +4. **If option 3 (keep as-is)** — is the friction + (newcomer-added-but-no-processor) the right paper cut, or + should the docs be louder about the hand-written + `VehicleTelemetry` pattern being the intended path until the + processor lands? + +## What "resolved" looks like + +- The chosen path is recorded under `## Answer` with a one-line + rationale. +- If option 1 is chosen, the MVP scope and the module-split + decision are recorded. +- If option 2 is chosen, the `@RequiresOptIn` shape and the + migration plan (how to handle the existing `VehicleTelemetry` + example) are recorded. +- The decision is propagated to ticket 07 + (`VehicleTelemetry` example alignment) and to + `docs/api-reference.md#annotations` (or a follow-up doc + commit). + +## Answer + +**Decision: hide `@KompactModel` and `@KompactField` behind a +`@RequiresOptIn` gate. The annotations move from the public +`ch.trancee.kompact.runtime` package to a preview package +`ch.trancee.kompact.annotations`, marked with a single +`@RequiresOptIn(level = RequiresOptIn.Level.WARNING)` annotation +`@KompactPreview`. Consumers who want the annotations today +opt in with `@OptIn(KompactPreview::class)`; consumers who +don't opt in don't see the annotations (they're "preview" API +and don't appear in IDE completion). Reversible when the +processor ships — re-expose the annotations as public. The +runtime classes stay in `ch.trancee.kompact.runtime`; only the +annotation classes move.** + +### Why this option + +The locked v1 spec (tickets 02, 03, 12, 13) commits the project +to a future `:kompact-ksp` KSP processor, but the processor is +not part of *this* effort's blast radius. The annotations are +`SOURCE`-retained (zero runtime cost) and currently sit in the +public API as a forward-compatibility hook. A newcomer who +reads the docs and adds `@KompactField` to a model gets nothing +today — no runtime effect, no processor. The honest ergonomic +call is to *say so in the compiler* via `@RequiresOptIn`, not +in prose alone. The warning fires at compile time, not at code +review time. + +Hiding the annotations is reversible: when the processor lands +(a separate effort), re-expose the annotations as public and +the opt-in gate is no longer needed. The `KompactFieldV1SurfaceTest` +stays as the compile-time contract test (it moves with the +annotations to the new package and opts in). + +### What gets added + +1. **`kompact/src/commonMain/kotlin/ch/trancee/kompact/annotations/KompactAnnotations.kt`** — + the two annotation classes (`@KompactModel`, `@KompactField`) + in the new preview package, with the same SOURCE retention + and the same eight `@KompactField` members as the locked + spec (per `KompactFieldV1SurfaceTest`'s compile-time contract). +2. **`@RequiresOptIn(level = RequiresOptIn.Level.WARNING)` annotation `KompactPreview`** + — declared in the same file; applied to both annotation + classes. The warning message references the missing KSP + processor and the migration path (hand-written value class + views, or `@OptIn(KompactPreview::class)` to silence). + +### What gets removed + +- **`kompact/src/commonMain/kotlin/ch/trancee/kompact/runtime/KompactAnnotations.kt`** + — the old location. The runtime package no longer carries + the annotation classes. + +### Sketch (for the implementation commit) + +```kotlin +// kompact/src/commonMain/kotlin/ch/trancee/kompact/annotations/KompactAnnotations.kt + +@RequiresOptIn( + level = RequiresOptIn.Level.WARNING, + message = "The @KompactModel / @KompactField schema annotations are a " + + "preview API. No KSP processor ships in this repository yet. " + + "Either hand-write your value class view (see VehicleTelemetry) " + + "or @OptIn(KompactPreview::class) to silence this warning." +) +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.BINARY) // @RequiresOptIn requires BINARY +public annotation class KompactPreview + +@KompactPreview +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.SOURCE) +public annotation class KompactModel + +@KompactPreview +@Target(AnnotationTarget.PROPERTY) +@Retention(AnnotationRetention.SOURCE) +public annotation class KompactField( + public val bitOffset: Int, + public val bitWidth: Int, + public val lengthPrefixWidth: Int = 8, + public val isNested: Boolean = false, + public val repeatCountWidth: Int = 8, + public val enumWidth: Int = 0, + public val defaultValue: String = "", + public val isVersionField: Boolean = false, +) +``` + +### Propagation + +- **`kompact/src/commonTest/.../KompactFieldV1SurfaceTest.kt`** — + updates the import path from + `ch.trancee.kompact.runtime.KompactField` to + `ch.trancee.kompact.annotations.KompactField` and adds + `@OptIn(KompactPreview::class)` to the probe class (or the + test class). The test's purpose — pin the compile-time + contract of `@KompactField`'s eight members — is unchanged. +- **`kompact/src/commonMain/.../generated/VehicleTelemetry.kt`** + (and the jvmMain + iosMain actuals) — updates the import path + and adds `@file:OptIn(KompactPreview::class)` (or + `@OptIn(KompactPreview::class)` on the class). The example + stays annotated; the warning is silenced for the example. + Note in ticket 07's body (the example-alignment ticket) — + 07's "raw `readBits` shape" option is no longer the only + choice, since the annotations can stay on the example. +- **Docs**: + - `docs/api-reference.md#annotations` — the public-surface + table for `@KompactModel` / `@KompactField` is replaced + with a "Preview API" note pointing at the new package, + the `@KompactPreview` opt-in, and the hand-written value + class pattern. + - `docs/architecture.md#value-class-representation` and the + "what is and is not in this repository today" section — + the annotation visibility decision is reflected. + - `README.md` — the "No codegen yet" bullet updates to + "Preview schema annotations (`@KompactModel` / + `@KompactField`) require `@OptIn(KompactPreview::class)`. + No KSP processor ships in this repository today; hand-write + your value class view (see `VehicleTelemetry`)." +- **Ticket 07** (`VehicleTelemetry` example alignment) — the + forward note from ticket 01 still applies. Ticket 06 + confirms that the example's annotations can stay (via + `@OptIn`), so 07's decision is purely about the getter + shape (raw `readBits` vs. public `readScalar` + + `readScalarAsLong`). Note in 07's body; do not resolve. + +### What this does NOT do + +- **No `:kompact-ksp` module is created.** That is a separate + effort; this ticket only hides the annotation surface that + currently pretends to be public. +- **No BCV regen needed** — the annotations are SOURCE-retained + and the preview opt-in is a compile-time feature; both have + zero runtime/ABI impact. The public ABI (the runtime classes + in `ch.trancee.kompact.runtime`) is unchanged. +- **No `KompactFieldV1SurfaceTest` removal** — the test + continues to pin the compile-time contract of + `@KompactField`'s eight members, just in the new package. + +## Comments diff --git a/.scratch/kompact-ergonomics/issues/07-vehicletelemetry-alignment.md b/.scratch/kompact-ergonomics/issues/07-vehicletelemetry-alignment.md new file mode 100644 index 0000000..ad474a6 --- /dev/null +++ b/.scratch/kompact-ergonomics/issues/07-vehicletelemetry-alignment.md @@ -0,0 +1,271 @@ +Type: grilling +Status: resolved +Blocked by: — (note 2026-09-05: ticket 01 settled the parameter shape as `ScalarType(bitWidth, signed)` with no enum axis. Ticket 06 settled the annotation visibility: the example's `@KompactModel` / `@KompactField` annotations are now gated by `@KompactPreview` (`@RequiresOptIn(WARNING)`); the example opts in via `@file:OptIn(KompactPreview::class)`. The "keep the raw `readBits` shape" option remains valid as a codegen-output reference. The "realign to the public `readScalar`" option now has the annotations as a clear `expect` shape (KSP processor's future output) — the example is closer to the codegen target either way. 07's decision is still its own.) +# 07 — `VehicleTelemetry` example: teach the public API or the codegen shape? + +## Question + +The bundled example +`kompact/src/commonMain/kotlin/ch/trancee/kompact/generated/VehicleTelemetry.kt` +is the `expect value class` that the README and `docs/getting-started.md` +reference. Its getter bodies are hand-written using the **raw** +`KompactRuntime.readBits` path: + +```kotlin +@KompactField(bitOffset = 0, bitWidth = 4) +public val batteryStatus: Int + +@KompactField(bitOffset = 4, bitWidth = 10) +public val speed: Int + +@KompactField(bitOffset = 14, bitWidth = 1) +public val isMalfunctioning: Boolean +``` + +…with the platform actuals (`jvmMain` + `iosMain` `VehicleTelemetry.kt`) +implementing the getters as `KompactRuntime.readBits(raw, +bitOffset, bitWidth)` (the raw zero-alloc primitive). This is +the *codegen-output* pattern: the KSP processor that ticket 06 +is about would emit exactly this shape. + +A newcomer reading the example sees two patterns at once: the +**annotation surface** (the `@KompactField` properties) and the +**raw readBits path** (the getter bodies, which are an internal +detail the consumer never writes by hand once the processor +lands). The newcomer may then write a hand-written value class +and not realize they're doing what a future KSP processor will +do for them, OR they may try to use the public +`readScalar` / `readBool` checked accessors and get a different +result (a `KompactResult`, not a primitive). + +The decision: which example teaches the right thing? + +- **Realign the example to use the public checked accessors** + (`readScalar` for the 4-bit and 10-bit fields, `readBool` for + the 1-bit flag). The getters then do + `.getOrThrow()` and return primitives. This teaches the + public API a newcomer will actually use (and what a hand-written + model would look like if the KSP processor never ships). It + costs the zero-alloc read path: the value-class getter now + allocates a `Long` for the result on every access (the + `@JvmInline`/`actual` value class still makes it zero-alloc + on the heap, but the primitive is extracted via + `getOrThrow` which… does not throw on success and returns + inline). + + Wait — the result is inline (`@JvmInline` value class over + `Long`), so the getter is still effectively zero-alloc. The + trade is between the *zero-alloc internal pattern* (raw + `readBits`) and the *public API pattern* (checked + `readScalar`/`readBool` with `.getOrThrow()`). + +- **Keep the raw `readBits` shape and document it as the + codegen-output pattern.** The example then teaches the + internal pattern, with a note "this is what the KSP processor + will emit; hand-write it now only if you want the zero-alloc + read path on the success hot path." The newcomer who doesn't + need the zero-alloc read path writes a different + hand-written model using the public checked accessors; the + docs explain the difference. + +- **Add a second example** (e.g. `VehicleTelemetryChecked.kt`) + that demonstrates the public checked accessors, alongside + the existing raw example. The raw example stays as the + codegen-output reference; the new example is the + newcomer-friendly path. Two examples, two readers. + +## Context for the claiming session + +- `kompact/src/commonMain/kotlin/ch/trancee/kompact/generated/VehicleTelemetry.kt` + — the `expect value class` declaration with the + `@KompactField` annotations (lines ~22–40). +- `kompact/src/jvmMain/kotlin/ch/trancee/kompact/generated/VehicleTelemetry.kt` + — the JVM actual with the raw `KompactRuntime.readBits` + bodies. +- `kompact/src/iosMain/kotlin/ch/trancee/kompact/generated/VehicleTelemetry.kt` + — the iOS actual. +- `kompact/src/commonTest/kotlin/ch/trancee/kompact/generated/VehicleTelemetryTest.kt` + — the test that pins the 16-bit wire bytes + (`0xA5 0x40`) and the round-trip values. +- The README and `docs/getting-started.md` both reference + `VehicleTelemetry` by name. Any example change ripples to + both. +- The `GettingStartedTest` in + `kompact/src/commonTest/.../runtime/` is the + executable-journey test for the tutorial; it uses the + **public** checked accessors and pins its own wire bytes + (`0xA5 0x40`). If the example is realigned to the public + API, the tutorial and the example converge; if the example + keeps the raw shape, the two diverge and the docs need to + explain both. + +## Open sub-questions + +1. **Is the example the right place to teach the codegen-output + pattern?** A newcomer landing on the repo for the first + time is not the audience for "this is what the KSP processor + will emit." The audience for the codegen-output pattern is + the *implementer of the KSP processor* (ticket 06) and + *maintainers of the runtime's zero-alloc contract*. A + separate doc (or research note) might be a better home. +2. **Convergence with the tutorial.** `docs/getting-started.md` + uses the public checked accessors and pins the wire bytes. + The example uses the raw path and pins the same wire bytes. + Converging on one shape makes the docs simpler; diverging + (with a clear "this is the internal pattern" note) preserves + the codegen-output reference. +3. **The annotation question (ticket 06).** If ticket 06 + decides to ship a processor, the raw example becomes the + "what the processor will emit" reference. If ticket 06 + decides to hide the annotations, the example either + drops the annotations (and becomes a plain `expect value class` + over a `ByteArray`) or goes away entirely. + +## What "resolved" looks like + +- The chosen shape (realign / keep / add a second example) is + recorded under `## Answer`. +- The decision is propagated to ticket 06 (annotation + visibility) and to the README and `docs/getting-started.md` + (which currently reference `VehicleTelemetry` by name). +- The wire bytes (`0xA5 0x40`) and the test + (`VehicleTelemetryTest`) stay green regardless of which shape + is chosen. + +## Answer + +**Decision: realign the `VehicleTelemetry` example to use the new +public checked accessors (`readScalar` + `readBool` from ticket 01, +`getOrThrow()` for the success path). The annotations stay on the +example (via `@file:OptIn(KompactPreview::class)` from ticket 06). +The codegen-output reference (raw `readBits` shape) moves to prose +in `docs/architecture.md`. Single canonical model.** + +### Why this option + +The example is the only annotated value class in the source +tree. A newcomer who reads the README → `docs/getting-started.md` +(the tutorial) → `docs/api-reference.md` → and then *opens* the +example sees a different shape from everything else: raw +`readBits` getter bodies, the internal zero-alloc pattern. They +wonder "is *this* the API?" The answer is no — the tutorial and +the API reference use the public checked accessors; the example +is the codegen-output pattern. Two stories, one for the +consumer, one for the future KSP processor implementer. + +The "challenge everything" mandate points at the single +canonical model: the example matches the tutorial. The +codegen-output pattern (raw `readBits` + annotations) is +preserved as prose in `docs/architecture.md` for the +implementer of the future processor; it does not need to live +as runnable source code in the example. + +### Sketch (for the implementation commit) + +```kotlin +// commonMain/.../generated/VehicleTelemetry.kt — the expect value class +@file:OptIn(KompactPreview::class) + +@KompactModel +public expect value class VehicleTelemetry(public val raw: ByteArray) { + + @KompactField(bitOffset = 0, bitWidth = 4) + public val batteryStatus: Int + + @KompactField(bitOffset = 4, bitWidth = 10) + public val speed: Int + + @KompactField(bitOffset = 14, bitWidth = 1) + public val isMalfunctioning: Boolean +} + +// jvmMain/.../generated/VehicleTelemetry.kt — the JVM actual +@file:OptIn(KompactPreview::class) + +@JvmInline +@KompactModel +public actual value class VehicleTelemetry(public actual val raw: ByteArray) { + + init { + require(raw.size >= 2) { /* F-001 fail-fast on truncated buffer */ } + } + + @KompactField(bitOffset = 0, bitWidth = 4) + public actual val batteryStatus: Int + get() = KompactRuntime.readScalar(raw, 0, ScalarType.UINT_8).getOrThrow() + + @KompactField(bitOffset = 4, bitWidth = 10) + public actual val speed: Int + get() = KompactRuntime.readScalar(raw, 4, ScalarType.UINT_16).getOrThrow() + + @KompactField(bitOffset = 14, bitWidth = 1) + public actual val isMalfunctioning: Boolean + get() = KompactRuntime.readBool(raw, 14).getOrThrow() +} + +// iosMain/.../generated/VehicleTelemetry.kt — the iOS actual: same getter +// shape, no @JvmInline (per the ticket 03 value-class pattern). +``` + +### What changes in the test + +`VehicleTelemetryTest.kt` continues to: +- build the buffer with `KompactRuntime.writeBits` (the + write-side stays raw; the write path is not the example's + teaching target), +- assert the wire bytes (`0xA5 0x40`), +- round-trip the values (5, 10, true) through + `VehicleTelemetry(buf)`, +- pin the F-001 constructor validation. + +The test's *read side* (the getters) is unchanged from the +consumer's perspective — the getters still return the same +primitives. The test passes with no edits. + +### What changes in the docs + +- **`docs/architecture.md`** — the "what is in this repository + today" section updates: the example's getter bodies are + the new public-API shape. The *codegen-output* pattern + (raw `readBits` + `@KompactField` annotations) moves to a + new prose section, e.g. a "Codegen output reference" block + that the future KSP processor implementer reads alongside + ticket 02's research notes. +- **`docs/api-reference.md`** — the `## annotations` section's + note that `VehicleTelemetry` "demonstrates the annotation + surface" is unchanged (the annotations are still on the + example). A new line notes "the example's getter bodies + use the public API; the codegen-output shape is documented + in `docs/architecture.md#codegen-output-reference`." +- **`README.md`** — no change (the README does not point at + the example as a learning artifact; the tutorial handles + the consumer's view). + +### What does NOT change + +- The annotations stay on the example (`@KompactModel`, + `@KompactField` × 3). They are gated by `@KompactPreview` + from ticket 06; the example opts in via + `@file:OptIn(KompactPreview::class)`. +- The `expect value class` declaration's `raw: ByteArray` + property and the three `@KompactField` properties stay the + same shape. +- The F-001 constructor validation in the platform actuals + (the `init` block that `require(raw.size >= 2)`) stays. +- The wire bytes (`0xA5 0x40`) and the round-trip values + stay. The test passes with no edits. +- The annotations are still SOURCE-retained (per + `KompactAnnotations.kt`); `@KompactPreview` is + BINARY-retained (required by `@RequiresOptIn`). + +### Propagation + +- **Ticket 09** (Test file split) — the test for the example + is currently `VehicleTelemetryTest.kt` (1 file, ~100 lines, + well within the D9 500-line limit). No split needed. +- **Ticket 10** (Docs layer structure review) — the codegen- + output reference in `docs/architecture.md` is a small prose + addition; ticket 10's "is the layering right?" question is + not affected. + +## Comments diff --git a/.scratch/kompact-ergonomics/issues/08-ci-ergonomics.md b/.scratch/kompact-ergonomics/issues/08-ci-ergonomics.md new file mode 100644 index 0000000..13ec800 --- /dev/null +++ b/.scratch/kompact-ergonomics/issues/08-ci-ergonomics.md @@ -0,0 +1,182 @@ +Type: grilling +Status: resolved + +# 08 — CI ergonomics: should Linux run `jvmApiCheck` too? + +## Question + +The CI workflow (`.github/workflows/ci.yml`) splits across runners: + +- `api-check` on `macos-latest` — runs `:kompact:apiCheck`, which + covers both the JVM ABI (`kompact/api/kompact.api`) and the + merged iOS klib ABI (`kompact/api/kompact.klib.api`). +- `jvm-test` on `ubuntu-latest` — runs `:kompact:jvmTest`. + +The split is necessary because only macOS can infer the iOS klib +ABI (per the v1 spec map's research note). But it has a +downside: a Linux contributor cannot run `apiCheck` locally — +`jvmApiCheck` runs on Linux (it's a JVM ABI check), but the +`klibApiCheck` part is a no-op on non-Apple hosts (a known +behaviour of BCV 0.18.0). A Linux contributor who changes the +public API gets a false green locally and only finds out about +the klib drift on the macOS CI run. + +The decision: do we add a Linux-side `:kompact:jvmApiCheck` job +(sibling of `jvm-test`) that catches the JVM part of the API +drift on every PR, leaving the klib check to the macOS job? Or +is the current split the right shape (one macOS job does both +APIs, one Linux job does the JVM tests, the Linux-API gap is +accepted)? + +## Context for the claiming session + +- `.github/workflows/ci.yml` — the two-job shape. +- `kompact/build.gradle.kts` — `apiValidation { klib { enabled = true } }` + (lines ~43–47) enables klib inference. +- `docs/ci.md` — the user-facing explanation of the current + shape. The "Re-running gates locally" section currently + documents that the iOS half of `apiCheck` is a no-op on + non-Mac hosts. +- The v1 spec map's ticket 13 ("KMP/KSP publication wiring") + resolved the BCV 0.18.0 / klib-enable wiring; the locked + decision is the current shape, not the alternative. + +## Open sub-questions + +1. **What does "Linux catches JVM API drift" actually buy us?** + The macOS `api-check` job already runs `jvmApiCheck` as part + of the combined `apiCheck`; the only thing the new Linux + job would do is *fail the PR earlier* (before the macOS + runner is even scheduled). For a feature branch like + `feat/laguna`, the macOS job is ~6 min and the Linux job + is ~10s. The earlier-fail saves time only on a *failed* + JVM-API check. +2. **Does the new job compose with the existing `jvm-test`?** + The simplest shape is one job that runs both + `jvmTest` and `jvmApiCheck` on Linux. The two tasks share + the JVM compile (so the wall-clock cost is dominated by + `jvmTest`), and a single job is easier to reason about + than two Linux jobs. +3. **Does this change the gate semantics for a contributor?** + Today, a Linux contributor who breaks the JVM API sees + the red on the macOS `api-check` job only (with a + ~6-min queue). With the new Linux job, they see the red + on Linux (no queue). The change is a faster feedback loop + for JVM API drift, not a new gate. +4. **The `regen-goldens.yml` workflow** is `workflow_dispatch` + only and lives on the feature branch. Should it move to + `main` so it's dispatchable as a public escape hatch, or + stay on the feature branch until the first release? + +## What "resolved" looks like + +- The chosen CI shape is recorded under `## Answer` with a + one-line rationale. +- The chosen `regen-goldens.yml` location (feature branch vs. + main) is recorded if the question is in scope. +- `docs/ci.md` is updated (or scheduled for the implementation + commit) to reflect the new shape. +- The implementation commit updates `.github/workflows/ci.yml` + accordingly and re-runs the macOS `api-check` to confirm + green. + +## Answer + +**Decision: fold `:kompact:jvmApiCheck` into the existing +`jvm-test` job (Ubuntu, JDK 21). The macOS `api-check` job +stays as the final gate. `regen-goldens.yml` stays on +`feat/laguna` (manual `workflow_dispatch`; GitHub UI path).** + +### CI shape + +`ci.yml`: +```yaml +jobs: + api-check: + name: apiCheck (macOS) + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-java@v4 + with: { distribution: temurin, java-version: 21, cache: gradle } + - run: ./gradlew :kompact:apiCheck --no-daemon # unchanged + + jvm-test: + name: jvmTest + jvmApiCheck (Linux) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-java@v4 + with: { distribution: temurin, java-version: 21, cache: gradle } + - run: ./gradlew :kompact:jvmTest :kompact:jvmApiCheck --no-daemon +``` + +The `jvm-test` job name updates to `jvmTest + jvmApiCheck (Linux)` +to advertise the two gates in the PR checks list. The two +tasks share the JVM compile (`:kompact:compileKotlinJvm` is +the common prerequisite), so the wall-clock cost of adding +`:kompact:jvmApiCheck` is dominated by the API comparison +(a few seconds). The macOS `api-check` job continues to run +the full `apiCheck`, which transitively includes +`jvmApiCheck` + `klibApiCheck` — so the macOS job is the +*final* gate (it catches everything), and the Linux job is +the *fast* gate (it catches JVM API drift before the macOS +queue). + +### Why this option + +The current split's only reason to exist is the macOS-only klib +inference. The JVM API check has no such constraint and +should run on every host that can run it (every host — it's +a JVM task with no native toolchain). A Linux contributor who +breaks the JVM API today gets a false green on the PR; the +macOS job catches it after a ~6-min queue. Folding `jvmApiCheck` +into the existing `jvm-test` job closes the gap with no extra +wall-clock cost (the JVM compile is shared) and no job sprawl +(one Linux job, one macOS job). + +### What this does NOT change + +- **The macOS `api-check` job stays as the final gate.** It + continues to run the full `apiCheck` (which transitively + includes `jvmApiCheck` + `klibApiCheck`). The macOS job is + the only one that catches klib drift; the Linux job catches + the JVM subset only. +- **No new job.** One Linux job, one macOS job (same as today). +- **No new runner type.** Still Ubuntu for the JVM gate, + macOS for the klib gate. + +### Regen-goldens location + +`regen-goldens.yml` stays on `feat/laguna` only (current +state). The dispatch path is: GitHub UI → Actions → Regen +Goldens → Run workflow → pick `feat/laguna`. The `gh +workflow run` path is still 404 (default-branch lookup), +but the UI path works. Moving the workflow to `main` is a +merge-time concern, not this effort's blast radius. + +### Propagation + +- **`docs/ci.md`** — the "The gates" table updates: the + `jvm-test` job is renamed to `jvmTest + jvmApiCheck + (Linux)` and now runs both tasks. The "Re-running gates + locally" section adds `:kompact:jvmApiCheck` to the local + command. The "regen-goldens" section stays as-is (location + unchanged). +- **The `GettingStartedTest` and `VehicleTelemetryTest` do not + change.** The new `jvmApiCheck` task runs the existing + `kompact/api/kompact.api` golden against the freshly-inferred + JVM ABI; if the implementation commits in this effort + (tickets 01–07) change the public JVM ABI, the golden + needs to be regenerated *before* the Linux job turns + green. The implementation order matters: the public-ABI + changes (tickets 01, 04, 05) commit together with the + regenerated `kompact.api` golden (the existing + `regen-goldens.yml` workflow handles this; the + implementation commit per ticket records "regen the + JVM golden in the same commit" in the map's `Notes`). +- **Ticket 10** (Docs layer structure review) — the + `docs/ci.md` update is a small doc edit; ticket 10's + "is the layering right?" question is not affected. + +## Comments diff --git a/.scratch/kompact-ergonomics/issues/09-test-file-split.md b/.scratch/kompact-ergonomics/issues/09-test-file-split.md new file mode 100644 index 0000000..7ebab98 --- /dev/null +++ b/.scratch/kompact-ergonomics/issues/09-test-file-split.md @@ -0,0 +1,238 @@ +Type: grilling +Status: resolved +Blocked by: — (note 2026-09-05: tickets 01, 02, 04, and 05 settled the runtime + framing public surface. New public symbols to test: `readScalar(raw, off, type: ScalarType)` (ticket 01) + `readScalarAsLong` (ticket 02 rename) + `…OrThrow` wrappers (tickets 04 + 05) + `Kompact.Result` namespace (ticket 04) + `getOrElse`/`map` extensions (tickets 04 + 05) + `NestedRegionResult` (ticket 05) + `KompactFraming.readNested` / `readNestedOrThrow` / `readLengthPrefixOrThrow` (ticket 05). The split boundary (per-accessor vs. per-result-kind) should plan for these new symbols. If splitting per-accessor, the long-band test file is `KompactRuntimeReadScalarAsLongTest.kt`; the framing test file is `KompactFramingReadNestedTest.kt`.) +# 09 — Test file split: `KompactRuntimeCheckedReadTest` is 441 lines + +## Question + +`kompact/src/commonTest/kotlin/ch/trancee/kompact/runtime/KompactRuntimeCheckedReadTest.kt` +is **441 lines** of `@Test` methods covering seven distinct +checked read accessors: + +- `readBool` +- `readScalar` (widths 8, 16, 32; signed + unsigned) +- `readScalarLong` (widths 32, 64; signed + unsigned; the 1..31 + overlap with `readScalar` is intentional per the v1 spec) +- `readFloat` +- `readDouble` +- Boundary hardening tests (F-002 / F-003) + +CONSTITUTION.md rule **D9** says: + +> Maintained source/test file D <=300 lines, R <=500; split by +> responsibility/layer/platform. + +441 lines exceeds the 300-line default and is 41 lines under the +500-line hard limit. The file is trending toward the limit as +new accessors (e.g. the `ScalarType`-shaped `readScalar` from +ticket 01) are added. A new maintainer opening the test +directory sees a wall of `@Test` methods in one file. + +The decision: do we split the file now, and if yes, by what +boundary? + +- **By accessor** — one file per accessor + (`KompactRuntimeReadBoolTest`, + `KompactRuntimeReadScalarTest`, …). Maximum clarity; the + file name announces the surface. Eight files (one per + accessor + one for boundary hardening). +- **By width band** — `ReadScalarIntTest` (1..32), + `ReadScalarLongTest` (1..64), `ReadFloatTest`, `ReadDoubleTest`, + `ReadBoolTest`. Mirrors the result type split (IntResult vs + LongResult). +- **By result kind** — one file per result value class + (`ReadBoolTest`, `ReadScalarIntTest`, `ReadScalarLongTest`, + `ReadFloatTest`, `ReadDoubleTest`, `ReadBytesTest`, + `ReadShortTest`). The result kind is the consumer-facing + surface; tests grouped by the kind of value they exercise + is the most natural navigation. +- **Don't split yet** — wait for ticket 01 to land (the + accessor surface may change) and split in the same commit + that introduces the new accessors. The 441-line file stays + as-is until then. + +## Context for the claiming session + +- `kompact/src/commonTest/kotlin/ch/trancee/kompact/runtime/KompactRuntimeCheckedReadTest.kt` + — the 441-line file. +- `kompact/src/commonTest/kotlin/ch/trancee/kompact/runtime/` + — the other test files in the same package: + - `KompactFieldV1SurfaceTest.kt` (annotation compile-time contract) + - `KompactFramingTest.kt` + - `KompactResultCanonicalizationTest.kt` + - `KompactResultTest.kt` + - `KompactRuntimeLongBitsTest.kt` (raw primitive, not the + checked accessors) + - `KompactRuntimePropertyTest.kt` (round-trip + property-based) + - `KompactRuntimeTest.kt` (raw primitive) + - `KompactWriterTest.kt` + - `GettingStartedTest.kt` (the tutorial's executable journey) +- CONSTITUTION.md rule D9 — the 300/500-line limit. +- The v1 spec map's ticket 10 ("Testing model") requires + commonTest to cover all four categories (round-trip, + property-based, cross-version compat, zero-alloc + assertion). The split must preserve the category coverage; + no `@Test` method should be lost. + +## Open sub-questions + +1. **What's the right boundary for the split?** The four + options above are sketched. Is there a fifth (e.g. by + accessor's `signed`/`unsigned` axis)? +2. **Should the split wait for ticket 01 (data clump / + `ScalarType`)?** The accessor surface may change, and + splitting now means the file names will need to be + renamed in a follow-up commit. Co-splitting with ticket + 01 is cheaper. +3. **The boundary hardening tests (F-002 / F-003)** — are + these per-accessor or per-runtime? They exercise + `KompactRuntime.fits` and the bounds-check code paths + across accessors, so they don't fit cleanly into a + per-accessor file. A separate `KompactRuntimeBoundsHardeningTest` + is the natural home. +4. **The `KompactRuntimeLongBitsTest` and `KompactRuntimeTest`** + — these are *raw primitive* tests (not the checked + accessors). Should they be merged into the new + per-accessor files, or kept separate? The raw primitives + are a different surface (no bounds check, no typed + result) and a separate concern; keep separate is the + simpler answer. + +## What "resolved" looks like + +- The chosen split boundary is recorded under `## Answer` + with the list of new file names. +- The boundary-hardening test's new home is recorded. +- The decision is coordinated with ticket 01 (if 01's + accessor surface changes, the new file names are noted + in 01's resolution so the implementation commit can split + and rename in one pass). + +## Answer + +**Decision: split by accessor, all tests in this one commit. Seven +new test files replace `KompactRuntimeCheckedReadTest.kt` +(which is deleted). Each file tests one accessor + its +`…OrThrow` companion + the matching result value class's +`getOrElse`/`map` extensions. The framing nested tests go in +a new `KompactFramingReadNestedTest.kt`.** + +### The seven new files + +1. **`KompactRuntimeReadBoolTest.kt`** — `readBool` (4 existing + tests with updated signature if needed) + `readBoolOrThrow` + (1 new test: bounds error throws) + `BooleanResult.getOrElse` + + `BooleanResult.map` (2 new tests). ~7 tests, ~60 lines. +2. **`KompactRuntimeReadScalarTest.kt`** — `readScalar` (24 + existing tests with the new `type: ScalarType` signature + from ticket 01) + `readScalarOrThrow` (1 new test) + + `IntResult.getOrElse` + `IntResult.map` (2 new tests). ~27 + tests, ~220 lines. +3. **`KompactRuntimeReadScalarAsLongTest.kt`** — `readScalarAsLong` + (renamed from `readScalarLong` per ticket 02; ~10 existing + tests with the new `type: ScalarType` signature) + + `readScalarAsLongOrThrow` (1 new test) + + `LongResult.getOrElse` + `LongResult.map` (2 new tests; the + `LongResult` sentinel-band failure case is a good place + to assert that `getOrElse` returns the fallback for the + sentinel). ~13 tests, ~110 lines. +4. **`KompactRuntimeReadFloatTest.kt`** — `readFloat` (existing + tests) + `readFloatOrThrow` (1 new test) + + `FloatResult.getOrElse` + `FloatResult.map` (2 new tests; + the NaN-payload failure case is a good place to assert the + fallback). ~4 tests, ~40 lines. +5. **`KompactRuntimeReadDoubleTest.kt`** — `readDouble` (existing + tests) + `readDoubleOrThrow` (1 new test) + + `DoubleResult.getOrElse` + `DoubleResult.map` (2 new tests). + ~4 tests, ~40 lines. +6. **`KompactRuntimeBoundsHardeningTest.kt`** — F-002 / F-003 + cross-accessor boundary tests (4 existing tests; no new + tests, just relocation). ~80 lines. +7. **`KompactFramingReadNestedTest.kt`** (new file) — the + nested-region tests for the new `readNested` / + `readNestedOrNull` (now `internal`) / `NestedRegionResult` + + `NestedRegionResult.getOrElse` / `NestedRegionResult.map` + / `readLengthPrefixOrThrow` (per ticket 05). ~6 tests, + ~80 lines. The existing `KompactFramingTest.kt` stays + (covers `readLengthPrefix` / `writeLengthPrefix` and the + existing nested-region tests; the new file picks up the + new public surface). + +Total: 7 new files, ~63 tests, ~630 lines. The largest single +file (`KompactRuntimeReadScalarTest.kt` at ~220 lines) is under +CONSTITUTION D9's 300-line default and well under the 500-line +hard limit. + +### Migration map (existing tests → new files) + +| Existing test (line in `KompactRuntimeCheckedReadTest.kt`) | New file | +| --- | --- | +| `readBool_*` (4 tests, lines ~20–35) | `KompactRuntimeReadBoolTest.kt` | +| `readScalar_width8_*` (4 tests) | `KompactRuntimeReadScalarTest.kt` | +| `readScalar_width16_*` (4 tests) | `KompactRuntimeReadScalarTest.kt` | +| `readScalar_width32_*` (8 tests) | `KompactRuntimeReadScalarTest.kt` | +| `readScalarLong_width64_*` (renamed to `readScalarAsLong` per ticket 02; ~10 tests) | `KompactRuntimeReadScalarAsLongTest.kt` | +| `readFloat_*` (existing tests) | `KompactRuntimeReadFloatTest.kt` | +| `readDouble_*` (existing tests) | `KompactRuntimeReadDoubleTest.kt` | +| F-002 / F-003 boundary tests (4 tests) | `KompactRuntimeBoundsHardeningTest.kt` | + +The new `…OrThrow` and `getOrElse` / `map` tests (one per +accessor / result kind) are added fresh, not migrated from +existing tests. The `Kompact.Result` namespace typealiases +are tested via the existing per-accessor tests (a single +`assertSame(BooleanResult, Kompact.Result.Boolean)` per +result kind, in the per-accessor file). + +### What this does NOT change + +- **The raw-primitive tests** (`KompactRuntimeTest.kt`, + `KompactRuntimeLongBitsTest.kt`, `KompactRuntimePropertyTest.kt`) + stay as-is. The split is for the *checked* accessors, not + the raw primitives. The raw primitives have their own + well-scoped test files. +- **`KompactResultTest.kt` and `KompactResultCanonicalizationTest.kt`** + stay as-is. The new `getOrElse` / `map` extensions are + tested in the per-accessor files (where the failure + mode is most relevant), not in the result-class test + files (which test the value class mechanics). +- **`KompactFieldV1SurfaceTest.kt`** stays as-is (tests the + annotation compile-time contract from ticket 06). +- **`GettingStartedTest.kt`** stays as-is (the tutorial's + executable journey; the `ScalarType` signature update + per ticket 01 is the only edit needed there). +- **`VehicleTelemetryTest.kt`** stays as-is (the example's + test; the getters use the new public API per ticket 07, + but the test's write side is raw `writeBits`, which is + unchanged). + +### Propagation + +- **`docs/ci.md`** — no change (the test files are + implementation detail; the CI shape from ticket 08 already + covers them). +- **Ticket 10** (Docs layer structure review) — no change + (tests are not part of the user-facing docs layer). +- **The implementation commit** is one mechanical refactor + + additive test commit: + 1. Apply tickets 01 + 02 + 04 + 05 + 07's source changes + (the runtime / writer / framing / result / annotation + public surfaces + the example realignment). + 2. Update the 46 existing test methods to use the new + signatures (`readScalar(raw, off, type: ScalarType)`, + `readScalarAsLong`, etc.). + 3. Create the 7 new test files (6 per-accessor runtime + + 1 framing). + 4. Add the new test methods (`…OrThrow`, `getOrElse`, + `map`, `NestedRegionResult` extensions, framing nested). + 5. Delete the old `KompactRuntimeCheckedReadTest.kt`. + 6. Update `GettingStartedTest.kt` to use the new + `ScalarType` signature (the tutorial's wire bytes + `0xA5 0x40` stay pinned). + 7. Update `VehicleTelemetryTest.kt` if the example's + getter shape changes (per ticket 07; the test reads + via the getters, so the assertions stay the same). + 8. Regenerate the klib golden on macOS via the + `regen-goldens.yml` workflow. + 9. Push to `feat/laguna`; CI should stay green. + +## Comments diff --git a/.scratch/kompact-ergonomics/issues/10-docs-structure-review.md b/.scratch/kompact-ergonomics/issues/10-docs-structure-review.md new file mode 100644 index 0000000..d53af1e --- /dev/null +++ b/.scratch/kompact-ergonomics/issues/10-docs-structure-review.md @@ -0,0 +1,233 @@ +Type: grilling +Status: resolved + +# 10 — Is the docs/ layer the right shape? + +## Question + +The docs/ layer was just laid down in this effort as +`tutorial + reference + architecture + ci + navigation index`: + +- `docs/getting-started.md` — the executable tutorial (a + 16-bit telemetry frame, wire bytes pinned, verified by + `GettingStartedTest`). +- `docs/api-reference.md` — the public-API reference + (KompactRuntime / KompactWriter / KompactFraming / the + result value classes / KompactDecodeError / annotations). +- `docs/architecture.md` — the design rationale (LSB-first, + zero-alloc, value classes, packed-Long error encoding, + framing, versioning). +- `docs/ci.md` — the how-to for the two CI workflows. +- `docs/README.md` — the navigation index. + +The five-file shape follows the Diátaxis tutorial / +reference / explanation / how-to split, plus a navigation +index. The decision: is the layering itself the right one, +or is one of the splits off? + +- **"How to use it" vs. "how it works"** — is the tutorial + (`getting-started.md`) the right place for the "first + example" or should that be in the README and the + tutorial be a more advanced "here's the typed-result + pattern" walkthrough? Today the README has a 5-line + excerpt of the tutorial and links to it; the tutorial + has the full example. Is the layering correct? +- **"What can I call" vs. "what does it do"** — is the + reference (`api-reference.md`) the right place for + "every function and parameter," or should the + reference be the *narrow* surface (the seven public + read/write entry points) and the rationale for each + one live in `architecture.md`? Today the reference + is detailed (per-function tables) and the + architecture doc covers the *design* rationale. A + consumer reading the reference gets the signature + and a one-line description; a consumer reading the + architecture doc gets the *why*. The boundary is + right but the reference is currently 11k of tables + that duplicate information that could be a + KDoc-generated reference (e.g. via Dokka). +- **"How the CI works"** — is `docs/ci.md` the right + place, or should CI live in the project root + (`CONTRIBUTING.md` is the conventional home, but + this repo doesn't have one)? Today `docs/ci.md` is + a how-to for the two workflows; a contributor who + wants to know "how do I run the tests locally" reads + `docs/ci.md`. A contributor who wants to know "what + is the project's contribution workflow" reads + `AGENTS.md` (the AI policy, which is human-readable + but not contributor-focused). +- **The "what is in this repo" map** — `docs/README.md` + is the navigation index. Should this be a separate + page, or is it overkill for five docs? + +## Context for the claiming session + +- `README.md` — the root index, ~3k, with a where-to-go-next + table. +- `docs/getting-started.md` — ~5k, tutorial. +- `docs/api-reference.md` — ~12k, reference. +- `docs/architecture.md` — ~11k, explanation. +- `docs/ci.md` — ~4k, how-to. +- `docs/README.md` — ~2k, navigation index. +- The repo's existing human-readable docs (before this + effort): none for consumers. The only docs were + `AGENTS.md` / `CONSTITUTION.md` (AI policy) and + `.scratch/kompact-spec/` (internal spec). +- The Diátaxis skill's form definitions (tutorial, + how-to, reference, explanation) are the reference for + "is the layering right?" — each form has a defined + mode and a contract; if any of the five docs fails the + contract for its form, it's a layering problem. +- The repo's `AGENTS.md` says (in the "Agent skills" + section): "Issues and spec tickets live as markdown + files under `.scratch//`; no GitHub Issues + used. See `docs/agents/issue-tracker.md`." This is + *process* documentation, not consumer docs, and + doesn't compete with the docs/ layer. + +## Open sub-questions + +1. **Is the reference doc too long for a v1 library?** 12k + of per-function tables is fine for a stable, mature + API; for a v1 that's still being shaped by tickets + 01–10, the reference churns every commit. A + KDoc-generated reference (Dokka) might be the right + answer for v1 (less hand-maintained) with the + rationale content in `architecture.md`. +2. **Should the tutorial and the README's code excerpt + converge into one example?** Today the README has + a 5-line excerpt of the tutorial; the tutorial has + the full 16-bit example. If the layering changes + (tutorial moves to README, or README moves into + the tutorial), the convergence is implicit. +3. **The "contributor vs. consumer" split.** A + contributor's needs (run the tests, regenerate the + goldens, understand the spec) overlap with a + consumer's needs (use the API, understand the wire + format) but are not identical. The current docs + layer is consumer-first. A future "Contributor's + guide" might be a separate file in `docs/` or a + `CONTRIBUTING.md` at the root. +4. **The "what is in this repo" map.** `docs/README.md` + is the navigation index. Is this overkill, or is + it the natural entry point for someone who arrived + at `docs/` from a link in the README? + +## What "resolved" looks like + +- The chosen docs shape is recorded under `## Answer` + with a one-line rationale. +- If a file is renamed, merged, or split, the new + layout is listed. +- The decision is propagated to the README (which + currently links to the five docs) and to any + cross-references in `docs/architecture.md` and + `docs/api-reference.md` (which link to each other + and to the tutorial). +- If a `CONTRIBUTING.md` is added, its scope and + shape are noted. + +## Answer + +**Decisions** (one per open sub-question): + +- **Reference doc length** — **add Dokka now** (the user's + pick). Add the `dokka` Gradle plugin to `:kompact`, + configure `dokkaGfm` (Markdown output) to produce a per-file + reference in `docs/api/`, and replace the hand-maintained + per-function tables in `docs/api-reference.md` with a pointer + to the generated output. The narrative content (the + "Long" suffix explanation on `readScalarAsLong`, the + writer's growable-buffer note, the cross-references to the + architecture doc) stays hand-maintained in + `docs/api-reference.md` as the curated *overview*; the + per-function detail (signature, params, returns, throws, + warnings) is generated from KDoc. The implementation is a + follow-up commit: the *decision* is recorded here; the build + wiring (plugin, version catalog, config) is a separate + effort that doesn't block the other ergonomics tickets. +- **Tutorial vs. README excerpt** — **keep the current + shape**. The README's 5-line code excerpt is a *taste*, not + a *tutorial*; the tutorial (`docs/getting-started.md`) is + the canonical walkthrough with the wire bytes (`0xA5 0x40`) + and the expected output. Standard layered approach. +- **CI doc location** — **keep `docs/ci.md`**. The current + scope (two workflows + local-run instructions) fits a + single page. A future `CONTRIBUTING.md` at the root is the + right home for *broader* contributor docs (release process, + issue tracker, code review conventions) when they + accumulate; for now, CI alone doesn't justify a root-level + contributor doc. +- **`docs/README.md` index** — **keep the separate index**. + Two pages (root `README.md` + `docs/README.md`) is the + standard layered approach. The index lists the user-facing + docs and points at the internal docs (AGENTS.md, + CONSTITUTION.md, `docs/agents/*`). The index is a + navigation hub, not a duplication of the root. + +### What this means for the implementation commit + +The single-actionable decision is the Dokka pick. The +implementation is a follow-up commit (this ticket records the +*decision*; the build wiring is separate): + +1. Add the `dokka` plugin to `gradle/libs.versions.toml` (a + version-catalog entry for `org.jetbrains.dokka:dokka-gradle-plugin`). +2. Add the plugin to `kompact/build.gradle.kts` and configure + `dokkaGfm` (Markdown output) to produce per-file output at + `docs/api/`. +3. Update the `.gitignore` (or the `:kompact:clean` task) to + exclude the generated `docs/api/` from source control + (the output is a build artifact; the *source* of truth is + the KDoc comments). Alternatively, commit the generated + output and let CI re-verify it on every change; the user's + call (the simpler path is to commit the output so the docs + PR review is self-contained). +4. Replace the per-function tables in `docs/api-reference.md` + with a curated overview (the narrative currently in the + file: the "Long" explanation, the writer's growable-buffer + note, the cross-references) + a pointer to the generated + `docs/api/`. +5. Re-render the generated output on every `:kompact:apiDump` + or as a separate `:kompact:dokkaGfm` task that the CI + runs (the existing `apiCheck` job on macOS can be extended + to also regenerate the docs; the Linux `jvm-test` job can + run `:kompact:dokkaGfm` as an additional task if the + output is committed and needs to stay in sync with the + source). + +### What does NOT change + +- **The tutorial stays hand-maintained.** Tutorials are + acquisition-form (Diátaxis) and don't lend themselves to + generation; the wire-byte pinning (`0xA5 0x40`) and the + expected-output assertions are the tutorial's value, and + those are hand-written prose around the verified + `GettingStartedTest`. +- **The architecture doc stays hand-maintained.** It covers + the *why* (LSB-first, zero-alloc, value classes, error + encoding, framing, versioning), which is explanation-form + (Diátaxis) and is not what Dokka generates. +- **`docs/README.md` stays as the navigation index.** No + change. +- **`docs/ci.md` stays as the CI how-to.** No change. + +### Propagation + +- **Ticket 11** (`KompactWriter.bitCursor` visibility) — the + `docs/api-reference.md` rewrite (when it lands as part of + the Dokka follow-up) will reflect whatever 11 decides + (the `bitCursor` row in the public-surface table is added + or removed based on the visibility decision). Note in + 11's body; do not resolve. +- **The follow-up Dokka commit** depends on the public surface + stabilizing. The implementation commits for tickets 01 + + 02 + 04 + 05 + 07 land the new public symbols + (`ScalarType`, `readScalarAsLong`, the `…OrThrow` + wrappers, the `Kompact.Result` namespace, the `getOrElse` / + `map` extensions, `NestedRegionResult`, the framing + companion functions). The Dokka follow-up runs *after* + those land, so the generated reference is in sync from the + start. + +## Comments diff --git a/.scratch/kompact-ergonomics/issues/11-bitcursor-visibility.md b/.scratch/kompact-ergonomics/issues/11-bitcursor-visibility.md new file mode 100644 index 0000000..ee8328d --- /dev/null +++ b/.scratch/kompact-ergonomics/issues/11-bitcursor-visibility.md @@ -0,0 +1,168 @@ +Type: grilling +Status: resolved +Blocked by: — (note 2026-09-05: ticket 10 settled the docs layer — add Dokka in a follow-up commit (after the public surface from tickets 01 + 02 + 04 + 05 + 07 lands); `docs/api-reference.md` becomes the curated overview pointing at the generated `docs/api/`. The `bitCursor` row in the public-surface table is added or removed based on 11's decision; if 11 demotes `bitCursor` to `internal`, the row is removed from the curated overview and the generated reference will not include it (since `@internal` is excluded from the public output by default).) +# 11 — `KompactWriter.bitCursor`: ergonomic surface or implementation detail? + +## Question + +`KompactWriter` currently exposes a public read-only `var bitCursor: Int` +(`kompact/src/commonMain/kotlin/ch/trancee/kompact/runtime/KompactWriter.kt:24`), +documented as: "Exposed so nested/repeat assembly can reason about +bit alignment without re-deriving it (PROMPT §1: forward-only)." + +The decision: is `bitCursor` a real ergonomic surface that consumers +writing multi-step payloads will read, or an implementation detail +that should be `internal`? Three options to grill: + +- **Keep `bitCursor` public** — the documented rationale (nested + /repeat assembly) is real: a consumer writing `writeNested { ... }` + followed by more `writeScalar` calls may want to inspect the + cursor to know where they are. The public surface grows by + one property, but the use case is genuine. +- **Demote `bitCursor` to `internal`** — the cursor is the + writer's implementation detail; consumers write a multi-step + payload by appending values and calling `build()`, not by + inspecting the cursor. The "nested/repeat assembly" use case + is satisfied by `writeNested { ... }` (the cursor is + advanced *inside* the block; the consumer doesn't read it). + Reducing the public surface to the minimum. +- **Replace `bitCursor` with a `position()` / `mark()` / + `seek()` API** — if "inspect the cursor" is a real need, + a richer API (cursor + markers + seeking) is more useful + than a single read-only `var`. The writer becomes more + powerful but the public surface grows. + +This ticket is on the frontier now because the writer signature +stabilized after tickets 01 (`writeScalar(type, value)`) + 04 (the +`…OrThrow` pattern applies to readers, not writers) + 05 +(NestedRegionResult is a read-side concern). The question can be +asked concretely without speculating about the writer's final +shape. + +## Context for the claiming session + +- `kompact/src/commonMain/kotlin/ch/trancee/kompact/runtime/KompactWriter.kt` + — the `bitCursor` declaration (lines ~22–25): + ```kotlin + public var bitCursor: Int = 0 + private set + ``` + Read-only (the setter is `private`); advanced by `writeBits` / + `writeBitsLong` / `writeBool` / `writeScalar` / `writeString` / + `writeBlob` / `writeNested` / `writeRepeated` (each of these + appends and advances the cursor). +- The cursor is consulted internally by `ensureCapacityBits` + (line ~125) and `appendBytes` (line ~133). Both are `private`. + The consumer-visible uses of `bitCursor` are: (a) reading the + current position to decide what to write next, (b) reading the + current position to know the byte length of a nested region + (e.g. for a length prefix the consumer wants to write later). +- The writer's `writeNested` signature: + `writeNested(lengthPrefixWidth: Int = 16, block: KompactWriter.() -> Unit)`. + The block runs against the *parent* writer (the cursor is + advanced inside the block, but the consumer's writes are + applied to the parent; the child writer is internal). A + consumer who wants to know "how many bits did I write in this + nested block?" would need to read the cursor, but the + `writeNested` API doesn't expose that — the consumer is given + a single `lengthPrefixWidth` and the block; the block's + bit-length is computed internally. So the documented + rationale (Ticket 07) for `bitCursor` is the *internal* use + case, not the consumer's. +- The writer's `build(): ByteArray` returns the exact-length + snapshot. The consumer who wants to know the bit-length of + the accumulated payload reads the returned `ByteArray`'s + `size * 8` (or the cursor before calling `build()`). The + `bitCursor` is a convenient property for that. + +## Open sub-questions + +1. **Is the "nested/repeat assembly" use case real for + consumers?** The internal use (the writer's own bookkeeping) + is real, but the consumer's use is less clear. The current + `writeNested` API doesn't expose the cursor inside the block. + If a consumer wants to know "how much did I write?", they + read the cursor. But the *common* pattern is "write a + payload, call `build()`, get the bytes" — the cursor is + an internal implementation detail of that pattern. +2. **Is the cursor a stable contract?** If a future KSP + processor generates writer code (e.g. `writeScalar` calls + for a `@KompactField`-annotated class), the generated code + would not consult `bitCursor` — the layout is compile-time. + The cursor is for *hand-written* multi-step payloads. As + hand-written payloads become less common (the processor + takes over), the cursor's audience shrinks. +3. **The `Kompact.Result.NestedRegion` typealias + the + `readNested` typed result (ticket 05) cover the read side + of nested regions.** The write side (`writeNested`) is a + block, not a cursor-driven API. So the read/write symmetry + is on the *block* pattern, not the cursor. + +## What "resolved" looks like + +- The chosen path is recorded under `## Answer` with a + one-line rationale. +- The chosen path propagates to `docs/api-reference.md` (the + public-surface table for `KompactWriter` lists or omits + `bitCursor`). +- The chosen path propagates to the implementation commit: + the visibility modifier on the declaration + any test + updates + any doc updates. + +## Answer + +**Decision: demote `bitCursor` to `internal`.** + +Rationale (three converging facts): + +1. **No consumer reads it.** A repo-wide grep for `bitCursor` shows + references only inside `KompactWriter.kt`'s own methods + (`writeBits`/`writeBool`/`writeString`/`writeBlob`/`writeNested`/ + `writeRepeated`/`build`/`ensureCapacityBits`/`appendBytes`). The + `GettingStartedTest` (the only consumer-facing writer test) calls + `writeScalar`/`writeBool`/`writeString` + `build()` and never reads + the cursor. The `VehicleTelemetry` example is a *reader*; it doesn't + touch the writer at all. +2. **The documented rationale is internal.** The "nested/repeat + assembly can reason about bit alignment" use case is satisfied by + `writeNested(lengthPrefixWidth, block)` (compute-first, no back- + patch) and `writeRepeated(count, countWidth, block)` — the + consumer's bit-length is computed *inside* the block and never + exposed. The consumer's pattern is "write a payload, call + `build()`, get the bytes" — the cursor is the writer's internal + bookkeeping. +3. **The future KSP processor won't use it.** A generated writer + (from `@KompactField` annotations) computes the field layout at + compile time; it calls `writeScalar`/`writeBool`/etc. directly and + never inspects the cursor. The cursor's audience shrinks to zero + as hand-written payloads disappear. + +The "replace with a `position()` / `mark()` / `seek()` API" option is +**rejected** — speculative, no demonstrated consumer need, and it +grows the surface (3+ new functions) instead of shrinking it. The +map favors the "smallest simplification that makes the surface +easier." + +### Propagation + +This decision lands in the **implementation commit** (the single commit +that applies tickets 01 + 02 + 04 + 05 + 06 + 11 to the source): + +- **Source**: `KompactWriter.kt` line 24 changes from `public var + bitCursor` to `internal var bitCursor`. The KDoc drops the "Exposed + so nested/repeat assembly..." sentence (it is now internal). No + other source change — the property is read+written only within + `KompactWriter`. +- **Docs**: remove the `bitCursor` row from `docs/api-reference.md`'s + `KompactWriter` public-surface table. +- **Golden**: the post-implementation `kompact.klib.api` will not list + `bitCursor` (regenerated on macOS via `regen-goldens.yml`). + +### Override + +If a streaming / multi-step writer use case emerges where a consumer +needs to read the cursor to "decide what to write next," re-open this + ticket and keep `bitCursor` public. The `position()` richer API can + then be revisited with a concrete consumer. + +## Comments diff --git a/.scratch/kompact-ergonomics/map.md b/.scratch/kompact-ergonomics/map.md new file mode 100644 index 0000000..9e7947e --- /dev/null +++ b/.scratch/kompact-ergonomics/map.md @@ -0,0 +1,179 @@ +Status: wayfinder:map +Type: wayfinder:map + +# Map: kompact ergonomics + +> **Wayfinding operations** (this repo's local-markdown tracker): +> map is this file; child tickets live at +> `issues/NN-.md`, numbered from `01`, with a `Type:` line +> (`research` / `prototype` / `grilling` / `task`) and a `Status:` line +> (`claimed` / `resolved`). Blocking is a `Blocked by: NN, NN` line near +> the top of each ticket. The frontier is the open, unblocked, unclaimed +> children, first by number wins. See `docs/agents/issue-tracker.md`. + +## Destination + +Reach a v1 consumer surface of `:kompact` that is as easy to use as +possible with the least impact on complexity. "Easy to use" means a +newcomer reading the README can write a packed frame, read it back with +typed results, and recover from malformed input without learning the +internal packed-Long encoding. "Least impact on complexity" means we +simplify the surface without removing the v1 capability set the locked +spec commits to: signed/unsigned scalars 1–64, booleans, IEEE-754 +floats, length-delimited strings/blobs/nested/repeated, the seven +specialized result value classes, and the `@KompactModel` / `@KompactField` +schema annotations. + +The blast radius is everything in this repository except the locked +v1 spec tickets 01–13 (those carry user-decided scope and may not be +reopened by this effort — see `Out of scope`). The public ABI is free +to change; the library has not been released. Commits land on +`feat/laguna` behind the BCV gate (`apiCheck` on macOS, `jvmTest` on +Linux), with the klib golden regenerated via the +`regen-goldens.yml` workflow. + +## Notes + +- **Consult first**: `docs/architecture.md` (the current design rationale + and the packed-Long layout that several tickets challenge), the v1 + spec map at `.scratch/kompact-spec/map.md` (so we don't accidentally + drift from the locked decisions), and the recent code-review report + on `feat/laguna` (the user-facing review that surfaced several of + these threads; see the prior review history for the P1/P2/P3 + findings). +- **Standing preference**: when a ticket resolves with a rename or + refactor that breaks the public ABI, regenerate the goldens + (`apiCheck` + `regen-goldens.yml`) in the same commit. Do not leave + the gate red between commits. +- **Tone**: the user asked us to "challenge everything." The route + favors the smallest simplification that makes the surface easier, + even at the cost of an internal rename. Do not preserve complexity + for symmetry's sake. +- **What "resolved" means here**: a ticket is resolved when the + decision is recorded on the ticket under `## Answer`, the ticket + status is `resolved`, the map's Decisions-so-far is updated with + a one-line gist + the ticket link, and any newly-surfaced ticket + has been created and wired. A follow-up implementation commit is + expected but is not part of resolving the decision ticket itself. + +## Decisions so far + + + +- [Data clump → `ScalarType` value class](issues/01-data-clump-scalartype.md): `value class ScalarType(bitWidth, signed)` with named companion constants (`INT_8`…`UINT_64` + `BOOL` + `of(w, signed)`); adopted by `readScalar`, `readScalarLong`, and `writeScalar`. The reader split (`readScalar` 1..32 / `readScalarLong` 1..64) is kept — the value class doesn't change the two `Int`/`Long` result lanes. No enum axis: enums are read via `readScalar` and inspected for `UnknownEnumCode` on the result. Unblocks tickets 02, 04, 09. +- [`readScalarLong` → `readScalarAsLong`](issues/02-readscalarlong-naming.md): pure rename; the `As` prefix makes the result type (`LongResult`) unambiguous and matches Kotlin's "as" pattern (`getOrThrow`, `as`, `asReversed`). `readScalar` (implicit-`Int` variant) stays. Writer side unchanged. Doc edit in `docs/api-reference.md#checked-typed-read-accessors`; tutorial not affected. +- [Throw helpers mirror the decode pattern](issues/03-throw-helper-naming.md): rename `throwSmallFailure` → `throwDecodeErrorFromSmallBits`, `throwLongFailure` → `throwDecodeErrorFromLong`, `throwDoubleFailure` → `throwDecodeErrorFromDouble`. Three-helper split kept (each picks the right decoder). Internal-only; no public-API change, no BCV regen, no test changes. 3 helper declarations + 14 call sites (7 in `jvmMain` + 7 in `iosMain`). +- [Result ergonomics layer: all three additions](issues/04-result-ergonomics-layer.md): (1) `…OrThrow` function-level wrappers on `KompactRuntime` (5 new public fns: `readBoolOrThrow`, `readScalarOrThrow`, `readScalarAsLongOrThrow`, `readFloatOrThrow`, `readDoubleOrThrow`); (2) `Kompact.Result` namespace re-exporting the seven result value classes as `typealias` members; (3) `getOrElse` / `map` extension functions on the seven result classes (14 new fns) mirroring stdlib's `Result`. All additive. The seven specialized result value classes and the four-member public surface stay unchanged. Unblocks ticket 05. +- [Framing: full ergonomics pair + count-prefix `…OrThrow`](issues/05-readnested-typed-result.md): add `KompactFraming.readNestedOrThrow(raw, off, prefixBitWidth): Pair` (throws on overrun), `KompactFraming.readNested(raw, off, prefixBitWidth): NestedRegionResult` (typed result), and `KompactFraming.readLengthPrefixOrThrow(raw, off, bitWidth): Int` (count-prefix throw-on-failure). `NestedRegionResult` is a new `value class NestedRegionResult(val startBit: Int, val bitLength: Int)` (zero-alloc, ticket 03 pattern) with `isSuccess`/`isFailure`/`error`/`getOrThrow()` and `getOrElse`/`map` extensions, plus `Kompact.Result.NestedRegion` typealias. The existing `nestedRegionOrNull` demotes to `internal inline` (hot-path internal; the new functions delegate to it). 6 source consumers in `KompactFramingTest.kt` + `KompactWriterTest.kt` switch to the new public form. +- [Annotations hidden behind `@RequiresOptIn`](issues/06-annotation-visibility.md): `@KompactModel` and `@KompactField` move from the public `ch.trancee.kompact.runtime` package to a preview `ch.trancee.kompact.annotations` package, gated by a `@KompactPreview` `@RequiresOptIn(level = WARNING)` annotation. Consumers opt in with `@OptIn(KompactPreview::class)`. Reversible when the locked `:kompact-ksp` processor (ticket 12) ships. `KompactFieldV1SurfaceTest` moves with the annotations and opts in. `VehicleTelemetry` example updates the import path and opts in. No runtime/ABI impact (annotations are SOURCE-retained; `@RequiresOptIn` is compile-time only). No BCV regen. +- [`VehicleTelemetry` example: realign to public API](issues/07-vehicletelemetry-alignment.md): the example's getter bodies use the new public checked accessors (`readScalar(raw, bitOffset, type: ScalarType).getOrThrow()` for the 4-bit and 10-bit fields, `readBool(raw, bitOffset).getOrThrow()` for the 1-bit flag). The annotations stay (via `@file:OptIn(KompactPreview::class)` from ticket 06). The example matches the tutorial (`docs/getting-started.md`) and the API reference. The codegen-output reference (raw `readBits` shape) moves to prose in `docs/architecture.md`. Test (`VehicleTelemetryTest`) is unchanged: the getters still return the same primitives; the wire bytes (`0xA5 0x40`) and round-trip values (5, 10, true) stay. +- [CI: Linux catches JVM API drift](issues/08-ci-ergonomics.md): fold `:kompact:jvmApiCheck` into the existing `jvm-test` job (Ubuntu, JDK 21). The job is renamed `jvmTest + jvmApiCheck (Linux)` and runs both tasks (`:kompact:jvmTest :kompact:jvmApiCheck`); the two tasks share the JVM compile, so the wall-clock cost is dominated by the API comparison. The macOS `api-check` job stays as the final gate (full `apiCheck` = `jvmApiCheck` + `klibApiCheck`). A Linux contributor who breaks the JVM API now gets the red on the PR *before* the macOS job is scheduled. `regen-goldens.yml` stays on `feat/laguna` (manual `workflow_dispatch` via the GitHub UI). +- [Test file split: per-accessor, all in one commit](issues/09-test-file-split.md): seven new test files replace the 441-line `KompactRuntimeCheckedReadTest.kt`: `KompactRuntimeReadBoolTest`, `KompactRuntimeReadScalarTest`, `KompactRuntimeReadScalarAsLongTest` (renamed from `…ReadScalarLong…` per ticket 02), `KompactRuntimeReadFloatTest`, `KompactRuntimeReadDoubleTest`, `KompactRuntimeBoundsHardeningTest` (F-002/F-003), and `KompactFramingReadNestedTest` (new from ticket 05). Each per-accessor file tests the accessor + its `…OrThrow` companion + the matching result class's `getOrElse`/`map` extensions. Total: ~63 tests, ~630 lines; the largest file is ~220 lines (under D9's 300-line default). The implementation commit moves the 46 existing tests to the new files (with the new `ScalarType` / `readScalarAsLong` signatures from tickets 01 + 02), adds the new tests for the new public symbols, and deletes the old file. +- [Docs layer: add Dokka, keep tutorial/README/ci/index as-is](issues/10-docs-structure-review.md): add the `dokka` Gradle plugin to `:kompact` (Markdown output via `dokkaGfm`, output to `docs/api/`) and replace the per-function tables in `docs/api-reference.md` with a pointer to the generated output. The narrative content (the "Long" suffix explanation, the writer's growable-buffer note, the cross-references to the architecture doc) stays hand-maintained in `docs/api-reference.md` as the curated overview. The tutorial (`docs/getting-started.md`), `docs/ci.md`, and `docs/README.md` stay as hand-maintained pages. The implementation is a follow-up commit (this ticket records the *decision*; the build wiring is separate, after the tickets 01 + 02 + 04 + 05 + 07 land). +- [KompactWriter.bitCursor: demote to `internal`](issues/11-bitcursor-visibility.md): a repo-wide grep confirms `bitCursor` is read+written only inside `KompactWriter.kt`'s own methods; no test, example, or consumer reads it. The documented "nested/repeat assembly" rationale is satisfied by `writeNested`/`writeRepeated` (compute-first, no back-patch); the future KSP processor won't consult the cursor. Demote `bitCursor` to `internal` (the writer's internal bookkeeping); reject the speculative `position()`/`mark()`/`seek()` richer API. The klib golden (regenerated on macOS) will no longer list it. + +## Frontier + +> All 11 design tickets (01–11) are resolved. The wayfinder frontier is +> **exhausted**. The next phase is the **implementation commit** — the single +> atomic commit that applies tickets 01 + 02 + 04 + 05 + 06 + 11 to the source, + splits the tests per ticket 09, and regenerates the goldens. It is tracked + by the implementation todo (below), not by a wayfinder ticket. The klib + golden regen is the final macOS-gated step (dispatch `regen-goldens.yml` + via the GitHub web UI). + +## Implementation + +> The implementation commit is **not** a wayfinder ticket — it is the +> mechanical realization of tickets 01 + 02 + 04 + 05 + 06 + 11 (decided) +> + ticket 09 (split plan). One atomic commit. Tracked by the `todo` +> tool list, not by a ticket file. + +The 9-step plan from ticket 09's `## Answer`, with the source-edit +precedence that the decisions impose: + +1. **Source: scalars** — ticket 01 (`ScalarType` value class + + companion constants `INT_8`…`UINT_64` + `BOOL` + `of`); ticket + 02 (`readScalarLong` → `readScalarAsLong`). `readScalar(raw, off, + type: ScalarType): IntResult`, `readScalarAsLong(raw, off, type: + ScalarType): LongResult`, `KompactWriter.writeScalar(type, value)`. + The two Int/Long result lanes are unchanged. +2. **Source: result ergonomics** — ticket 04 (`readBoolOrThrow` / + `readScalarOrThrow` / `readScalarAsLongOrThrow` / `readFloatOrThrow` + / `readDoubleOrThrow`; `getOrElse`/`map` extensions on the 7 result + classes; `Kompact.Result` namespace). Ticket 03 pattern (zero-alloc + value-class wrapping a Long) applies to `NestedRegionResult`. +3. **Source: framing** — ticket 05 (`readLengthPrefixOrThrow`, + `readNested`, `readNestedOrThrow`; `nestedRegionOrNull` → internal; + `NestedRegionResult` value class + `isSuccess`/`isFailure`/`error`/ + `getOrThrow()` + `getOrElse`/`map`; `Kompact.Result.NestedRegion`). +4. **Source: writer** — ticket 11 (`bitCursor` → `internal`; drop the + "Exposed so nested/repeat assembly..." KDoc). `writeScalar` takes + `ScalarType`. +5. **Source: annotations** — ticket 06 (`@KompactModel`/`@KompactField` + → `ch.trancee.kompact.annotations`; `@KompactPreview` + `@RequiresOptIn(WARNING)`; `KompactFieldV1SurfaceTest` opts in). +6. **Source: example** — ticket 07 (`VehicleTelemetry` getters use + `readScalar(raw, off, type).getOrThrow()` / `readBool(raw, off) + .getOrThrow()`; annotations opt in). +7. **Tests** — ticket 09 (7 files, ~63 tests; relocate 46 existing + + add `…OrThrow` / `getOrElse` / `map` / framing tests; update tutorial + + example tests for the new signatures; delete the old file). +8. **Golden** — regen JVM golden (`apiDump`) on Linux (host-capable); + regen klib golden on macOS via `regen-goldens.yml` web-UI dispatch + (Linux cannot infer it). Verify `jvmApiCheck` + `jvmTest` green on + Linux; `apiCheck` green on macOS. +9. **Commit + push** — Conventional Commit ("refactor:" scopes the + signature rename + surface addition); push to `feat/laguna`; CI green + (Linux `jvmTest + jvmApiCheck` + macOS `apiCheck`). + +Sizing: ~6 source edits (3 files × 2 platforms for the actuals) + 1 +annotation file + 1 example (3 platform actuals) + 7 test files + 2 +goldens. The source edits are mechanical (signature changes + additive +declarations); the test files are the bulk (rewrite the read tests + new +ergonomics tests). The klib golden is the final macOS-gated step. + +## Not yet specified + + + + + tests per ticket 09 + regenerating goldens) has been applied + (`13f2f5b` + `cf7dde5`); the closeout of issues 04 + 09 was + committed (`445397d`). Revisit this section only if a follow-up + (08 CI ergonomics, 10 docs structure) surfaces new in-scope fog. --> + + + + + + + + + + + + + + + + + + + + + + + diff --git a/kompact/docs/api/images/abstract-class.svg b/kompact/docs/api/images/abstract-class.svg new file mode 100644 index 0000000..ead94b8 --- /dev/null +++ b/kompact/docs/api/images/abstract-class.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/kompact/docs/api/images/anchor-copy-icon.svg b/kompact/docs/api/images/anchor-copy-icon.svg new file mode 100644 index 0000000..dd43e76 --- /dev/null +++ b/kompact/docs/api/images/anchor-copy-icon.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/kompact/docs/api/images/annotation-kotlin.svg b/kompact/docs/api/images/annotation-kotlin.svg new file mode 100644 index 0000000..b748242 --- /dev/null +++ b/kompact/docs/api/images/annotation-kotlin.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + diff --git a/kompact/docs/api/images/annotation.svg b/kompact/docs/api/images/annotation.svg new file mode 100644 index 0000000..76c2f4b --- /dev/null +++ b/kompact/docs/api/images/annotation.svg @@ -0,0 +1,9 @@ + + + + + + + diff --git a/kompact/docs/api/images/arrow-down.svg b/kompact/docs/api/images/arrow-down.svg new file mode 100644 index 0000000..ca54658 --- /dev/null +++ b/kompact/docs/api/images/arrow-down.svg @@ -0,0 +1,3 @@ + + + diff --git a/kompact/docs/api/images/burger.svg b/kompact/docs/api/images/burger.svg new file mode 100644 index 0000000..a26f194 --- /dev/null +++ b/kompact/docs/api/images/burger.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/kompact/docs/api/images/check.svg b/kompact/docs/api/images/check.svg new file mode 100644 index 0000000..291e5ee --- /dev/null +++ b/kompact/docs/api/images/check.svg @@ -0,0 +1,3 @@ + + + diff --git a/kompact/docs/api/images/checkbox-off.svg b/kompact/docs/api/images/checkbox-off.svg new file mode 100644 index 0000000..a5117e8 --- /dev/null +++ b/kompact/docs/api/images/checkbox-off.svg @@ -0,0 +1,3 @@ + + + diff --git a/kompact/docs/api/images/checkbox-on.svg b/kompact/docs/api/images/checkbox-on.svg new file mode 100644 index 0000000..71891d5 --- /dev/null +++ b/kompact/docs/api/images/checkbox-on.svg @@ -0,0 +1,3 @@ + + + diff --git a/kompact/docs/api/images/class-kotlin.svg b/kompact/docs/api/images/class-kotlin.svg new file mode 100644 index 0000000..dc23120 --- /dev/null +++ b/kompact/docs/api/images/class-kotlin.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + diff --git a/kompact/docs/api/images/class.svg b/kompact/docs/api/images/class.svg new file mode 100644 index 0000000..2447cf7 --- /dev/null +++ b/kompact/docs/api/images/class.svg @@ -0,0 +1,9 @@ + + + + + + + diff --git a/kompact/docs/api/images/copy-icon.svg b/kompact/docs/api/images/copy-icon.svg new file mode 100644 index 0000000..abeb27e --- /dev/null +++ b/kompact/docs/api/images/copy-icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/kompact/docs/api/images/cross.svg b/kompact/docs/api/images/cross.svg new file mode 100644 index 0000000..232069f --- /dev/null +++ b/kompact/docs/api/images/cross.svg @@ -0,0 +1,3 @@ + + + diff --git a/kompact/docs/api/images/enum-kotlin.svg b/kompact/docs/api/images/enum-kotlin.svg new file mode 100644 index 0000000..207a0ef --- /dev/null +++ b/kompact/docs/api/images/enum-kotlin.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + diff --git a/kompact/docs/api/images/enum.svg b/kompact/docs/api/images/enum.svg new file mode 100644 index 0000000..2deba5f --- /dev/null +++ b/kompact/docs/api/images/enum.svg @@ -0,0 +1,9 @@ + + + + + + + diff --git a/kompact/docs/api/images/exception-class.svg b/kompact/docs/api/images/exception-class.svg new file mode 100644 index 0000000..1bb4f88 --- /dev/null +++ b/kompact/docs/api/images/exception-class.svg @@ -0,0 +1,9 @@ + + + + + + + diff --git a/kompact/docs/api/images/field-value.svg b/kompact/docs/api/images/field-value.svg new file mode 100644 index 0000000..3aa75be --- /dev/null +++ b/kompact/docs/api/images/field-value.svg @@ -0,0 +1,10 @@ + + + + + + + + diff --git a/kompact/docs/api/images/field-variable.svg b/kompact/docs/api/images/field-variable.svg new file mode 100644 index 0000000..7a8af50 --- /dev/null +++ b/kompact/docs/api/images/field-variable.svg @@ -0,0 +1,10 @@ + + + + + + + + diff --git a/kompact/docs/api/images/filter.svg b/kompact/docs/api/images/filter.svg new file mode 100644 index 0000000..be62ea5 --- /dev/null +++ b/kompact/docs/api/images/filter.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/kompact/docs/api/images/function.svg b/kompact/docs/api/images/function.svg new file mode 100644 index 0000000..e56a417 --- /dev/null +++ b/kompact/docs/api/images/function.svg @@ -0,0 +1,9 @@ + + + + + + + diff --git a/kompact/docs/api/images/go-to-top-icon.svg b/kompact/docs/api/images/go-to-top-icon.svg new file mode 100644 index 0000000..62df4c7 --- /dev/null +++ b/kompact/docs/api/images/go-to-top-icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/kompact/docs/api/images/homepage.svg b/kompact/docs/api/images/homepage.svg new file mode 100644 index 0000000..9a8927a --- /dev/null +++ b/kompact/docs/api/images/homepage.svg @@ -0,0 +1,3 @@ + + + diff --git a/kompact/docs/api/images/interface-kotlin.svg b/kompact/docs/api/images/interface-kotlin.svg new file mode 100644 index 0000000..e5273f0 --- /dev/null +++ b/kompact/docs/api/images/interface-kotlin.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + diff --git a/kompact/docs/api/images/interface.svg b/kompact/docs/api/images/interface.svg new file mode 100644 index 0000000..0637028 --- /dev/null +++ b/kompact/docs/api/images/interface.svg @@ -0,0 +1,9 @@ + + + + + + + diff --git a/kompact/docs/api/images/logo-icon.svg b/kompact/docs/api/images/logo-icon.svg new file mode 100644 index 0000000..ea6f37e --- /dev/null +++ b/kompact/docs/api/images/logo-icon.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + diff --git a/kompact/docs/api/images/object.svg b/kompact/docs/api/images/object.svg new file mode 100644 index 0000000..5f960b7 --- /dev/null +++ b/kompact/docs/api/images/object.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + diff --git a/kompact/docs/api/images/placeholder.svg b/kompact/docs/api/images/placeholder.svg new file mode 100644 index 0000000..e812962 --- /dev/null +++ b/kompact/docs/api/images/placeholder.svg @@ -0,0 +1,3 @@ + + + diff --git a/kompact/docs/api/images/success-icon.svg b/kompact/docs/api/images/success-icon.svg new file mode 100644 index 0000000..76945eb --- /dev/null +++ b/kompact/docs/api/images/success-icon.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/kompact/docs/api/images/theme-toggle.svg b/kompact/docs/api/images/theme-toggle.svg new file mode 100644 index 0000000..490263f --- /dev/null +++ b/kompact/docs/api/images/theme-toggle.svg @@ -0,0 +1,3 @@ + + + diff --git a/kompact/docs/api/images/typealias-kotlin.svg b/kompact/docs/api/images/typealias-kotlin.svg new file mode 100644 index 0000000..73a0885 --- /dev/null +++ b/kompact/docs/api/images/typealias-kotlin.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + diff --git a/kompact/docs/api/index.html b/kompact/docs/api/index.html new file mode 100644 index 0000000..2b8fb0b --- /dev/null +++ b/kompact/docs/api/index.html @@ -0,0 +1,228 @@ + + + + + kompact + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

kompact

+
+

Packages

+
+
+
+
+
+ + +
Link copied to clipboard
+
+
+
+
common
+
+
+
+
+
+
+ +
+
+
+
+ + +
Link copied to clipboard
+
+
+
+
common
+
+
+
+
+
+
+ +
+
+
+
+ + +
Link copied to clipboard
+
+
+
+
common
+
ios
+ +
+
+
+
+
+
+ +
+
+
+
+ + +
Link copied to clipboard
+
+
+
+
common
+
ios
+ +
+
+
+
+
+
+
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.annotations/-kompact-field/bit-offset.html b/kompact/docs/api/kompact/ch.trancee.kompact.annotations/-kompact-field/bit-offset.html new file mode 100644 index 0000000..582dba8 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.annotations/-kompact-field/bit-offset.html @@ -0,0 +1,131 @@ + + + + + bitOffset + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

bitOffset

+
+

Parameters

bitOffset

zero-based LSB-first start bit of the field

+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.annotations/-kompact-field/bit-width.html b/kompact/docs/api/kompact/ch.trancee.kompact.annotations/-kompact-field/bit-width.html new file mode 100644 index 0000000..289963e --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.annotations/-kompact-field/bit-width.html @@ -0,0 +1,131 @@ + + + + + bitWidth + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

bitWidth

+
+

Parameters

bitWidth

number of bits occupied by the field (1..64; for 32-bit use 32)

+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.annotations/-kompact-field/default-value.html b/kompact/docs/api/kompact/ch.trancee.kompact.annotations/-kompact-field/default-value.html new file mode 100644 index 0000000..90e5877 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.annotations/-kompact-field/default-value.html @@ -0,0 +1,131 @@ + + + + + defaultValue + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

defaultValue

+
+

Parameters

defaultValue

string-encoded default used by the generated ctor/accessor when the backing region is absent or zero-filled (Ticket 04)

+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.annotations/-kompact-field/enum-width.html b/kompact/docs/api/kompact/ch.trancee.kompact.annotations/-kompact-field/enum-width.html new file mode 100644 index 0000000..47c2b4f --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.annotations/-kompact-field/enum-width.html @@ -0,0 +1,131 @@ + + + + + enumWidth + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

enumWidth

+
+
val enumWidth: Int = 0

Parameters

enumWidth

bit width of an enum/ordinal (0 = not an enum)

+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.annotations/-kompact-field/index.html b/kompact/docs/api/kompact/ch.trancee.kompact.annotations/-kompact-field/index.html new file mode 100644 index 0000000..b169c75 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.annotations/-kompact-field/index.html @@ -0,0 +1,275 @@ + + + + + KompactField + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

KompactField

+
@Target(allowedTargets = [AnnotationTarget.PROPERTY])
annotation class KompactField(val bitOffset: Int, val bitWidth: Int, val signed: Boolean = false, val lengthPrefixWidth: Int = 8, val isNested: Boolean = false, val repeatCountWidth: Int = 8, val enumWidth: Int = 0, val defaultValue: String = "", val isVersionField: Boolean = false)

Documents a property's bit position and width in the packed ByteArray.

The Kompact KSP processor reads these to generate the backing read/write logic (Tickets 04, 05, 06, 09). Offsets are LSB-first (Ticket 01) and must be densely packed with no gaps or overlaps (Ticket 06: the processor enforces this).

The length-prefix / nesting / repeat / enum / version members are v1 schema metadata consumed by codegen; they carry safe defaults so a plain @KompactField(bitOffset, bitWidth) scalar declaration remains valid.

Parameters

bitOffset

zero-based LSB-first start bit of the field

bitWidth

number of bits occupied by the field (1..64; for 32-bit use 32)

signed

true for two's-complement, false for unsigned magnitude (v1-spec-04: type set)

lengthPrefixWidth

fixed-width LE byte-count prefix width in {8,16,32} used when the field is a string/blob/nested/repeat (Ticket 05)

isNested

true when the field is a length-delimited composite region

repeatCountWidth

fixed-width LE count prefix width in {8,16,32} for repeated fields

enumWidth

bit width of an enum/ordinal (0 = not an enum)

defaultValue

string-encoded default used by the generated ctor/accessor when the backing region is absent or zero-filled (Ticket 04)

isVersionField

true for the schema-evolution version-tag field (Ticket 09)

+
+
+
+
+
+

Properties

+
+
+
+
+ + +
Link copied to clipboard
+
+
+
+ +
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+ +
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+ +
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
val enumWidth: Int = 0
+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
val isNested: Boolean = false
+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
val isVersionField: Boolean = false
+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+ +
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+ +
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
val signed: Boolean = false
+
+
+
+
+
+
+
+
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.annotations/-kompact-field/is-nested.html b/kompact/docs/api/kompact/ch.trancee.kompact.annotations/-kompact-field/is-nested.html new file mode 100644 index 0000000..119794b --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.annotations/-kompact-field/is-nested.html @@ -0,0 +1,131 @@ + + + + + isNested + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

isNested

+
+
val isNested: Boolean = false

Parameters

isNested

true when the field is a length-delimited composite region

+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.annotations/-kompact-field/is-version-field.html b/kompact/docs/api/kompact/ch.trancee.kompact.annotations/-kompact-field/is-version-field.html new file mode 100644 index 0000000..73f974a --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.annotations/-kompact-field/is-version-field.html @@ -0,0 +1,131 @@ + + + + + isVersionField + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

isVersionField

+
+
val isVersionField: Boolean = false

Parameters

isVersionField

true for the schema-evolution version-tag field (Ticket 09)

+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.annotations/-kompact-field/length-prefix-width.html b/kompact/docs/api/kompact/ch.trancee.kompact.annotations/-kompact-field/length-prefix-width.html new file mode 100644 index 0000000..a5a56e4 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.annotations/-kompact-field/length-prefix-width.html @@ -0,0 +1,131 @@ + + + + + lengthPrefixWidth + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

lengthPrefixWidth

+
+

Parameters

lengthPrefixWidth

fixed-width LE byte-count prefix width in {8,16,32} used when the field is a string/blob/nested/repeat (Ticket 05)

+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.annotations/-kompact-field/repeat-count-width.html b/kompact/docs/api/kompact/ch.trancee.kompact.annotations/-kompact-field/repeat-count-width.html new file mode 100644 index 0000000..fe8bd7f --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.annotations/-kompact-field/repeat-count-width.html @@ -0,0 +1,131 @@ + + + + + repeatCountWidth + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

repeatCountWidth

+
+

Parameters

repeatCountWidth

fixed-width LE count prefix width in {8,16,32} for repeated fields

+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.annotations/-kompact-field/signed.html b/kompact/docs/api/kompact/ch.trancee.kompact.annotations/-kompact-field/signed.html new file mode 100644 index 0000000..c351329 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.annotations/-kompact-field/signed.html @@ -0,0 +1,131 @@ + + + + + signed + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

signed

+
+
val signed: Boolean = false

Parameters

signed

true for two's-complement, false for unsigned magnitude (v1-spec-04: type set)

+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.annotations/-kompact-model/index.html b/kompact/docs/api/kompact/ch.trancee.kompact.annotations/-kompact-model/index.html new file mode 100644 index 0000000..1816880 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.annotations/-kompact-model/index.html @@ -0,0 +1,135 @@ + + + + + KompactModel + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

KompactModel

+
@Target(allowedTargets = [AnnotationTarget.CLASS])
annotation class KompactModel

Marks a value class as a Kompact binary schema.

A Kompact schema is a multiplatform value class over a single ByteArray. The processor reads this annotation to validate field layout at compile time (Ticket 06) and is retained only at source level — it is compile-time metadata, not a runtime dependency (PROMPT §2).

+
+
+
+
+
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.annotations/-kompact-preview/index.html b/kompact/docs/api/kompact/ch.trancee.kompact.annotations/-kompact-preview/index.html new file mode 100644 index 0000000..09912bb --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.annotations/-kompact-preview/index.html @@ -0,0 +1,135 @@ + + + + + KompactPreview + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

KompactPreview

+
@RequiresOptIn(message = "Kompact codegen APIs are API-preview: expect binary/source changes across releases.", level = RequiresOptIn.Level.WARNING)
annotation class KompactPreview

API-preview marker for the Kompact codegen surface (model layout contracts and field metadata). The surface is not yet binary/source-stable (Ticket 07): generated declarations carry this marker via @file:OptIn(KompactPreview::class); hand-written code that references the markers is warned until graduation.

@RequiresOptIn markers may not target FILE or TYPE_USAGE; this marker is confined to class/function/property declarations (the codegen annotations that propagate it).

+
+
+
+
+
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.annotations/index.html b/kompact/docs/api/kompact/ch.trancee.kompact.annotations/index.html new file mode 100644 index 0000000..6ae1fd2 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.annotations/index.html @@ -0,0 +1,184 @@ + + + + + ch.trancee.kompact.annotations + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

Package-level declarations

+
+
+
+
+
+

Types

+
+
+
+
+ + +
Link copied to clipboard
+
+
+
+
@Target(allowedTargets = [AnnotationTarget.PROPERTY])
annotation class KompactField(val bitOffset: Int, val bitWidth: Int, val signed: Boolean = false, val lengthPrefixWidth: Int = 8, val isNested: Boolean = false, val repeatCountWidth: Int = 8, val enumWidth: Int = 0, val defaultValue: String = "", val isVersionField: Boolean = false)

Documents a property's bit position and width in the packed ByteArray.

+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
@Target(allowedTargets = [AnnotationTarget.CLASS])
annotation class KompactModel

Marks a value class as a Kompact binary schema.

+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
@RequiresOptIn(message = "Kompact codegen APIs are API-preview: expect binary/source changes across releases.", level = RequiresOptIn.Level.WARNING)
annotation class KompactPreview

API-preview marker for the Kompact codegen surface (model layout contracts and field metadata). The surface is not yet binary/source-stable (Ticket 07): generated declarations carry this marker via @file:OptIn(KompactPreview::class); hand-written code that references the markers is warned until graduation.

+
+
+
+
+
+
+
+
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.generated/-vehicle-telemetry/-companion/create.html b/kompact/docs/api/kompact/ch.trancee.kompact.generated/-vehicle-telemetry/-companion/create.html new file mode 100644 index 0000000..b7506c5 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.generated/-vehicle-telemetry/-companion/create.html @@ -0,0 +1,153 @@ + + + + + create + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

create

+
+
+
+
expect fun create(batteryStatus: Int, speed: Int, isMalfunctioning: Boolean): VehicleTelemetry

Creates a fully-encoded frame from individual field values. Allocates on the write path (KompactWriter's growable buffer); use this for outbound frames, not the read hot path.

actual fun create(batteryStatus: Int, speed: Int, isMalfunctioning: Boolean): VehicleTelemetry

Creates a fully-encoded frame from individual field values. Allocates on the write path (KompactWriter's growable buffer); use this for outbound frames, not the read hot path.

actual fun create(batteryStatus: Int, speed: Int, isMalfunctioning: Boolean): VehicleTelemetry

Creates a fully-encoded frame from individual field values. Allocates on the write path (KompactWriter's growable buffer); use this for outbound frames, not the read hot path.

+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.generated/-vehicle-telemetry/-companion/index.html b/kompact/docs/api/kompact/ch.trancee.kompact.generated/-vehicle-telemetry/-companion/index.html new file mode 100644 index 0000000..57ed705 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.generated/-vehicle-telemetry/-companion/index.html @@ -0,0 +1,179 @@ + + + + + Companion + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

Companion

+
+
+
expect object Companion
actual object Companion
actual object Companion
+
+
+
+
+
+

Functions

+
+
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
expect fun create(batteryStatus: Int, speed: Int, isMalfunctioning: Boolean): VehicleTelemetry

Creates a fully-encoded frame from individual field values. Allocates on the write path (KompactWriter's growable buffer); use this for outbound frames, not the read hot path.

actual fun create(batteryStatus: Int, speed: Int, isMalfunctioning: Boolean): VehicleTelemetry

Creates a fully-encoded frame from individual field values. Allocates on the write path (KompactWriter's growable buffer); use this for outbound frames, not the read hot path.

actual fun create(batteryStatus: Int, speed: Int, isMalfunctioning: Boolean): VehicleTelemetry

Creates a fully-encoded frame from individual field values. Allocates on the write path (KompactWriter's growable buffer); use this for outbound frames, not the read hot path.

+
+
+
+
+
+
+
+
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.generated/-vehicle-telemetry/-vehicle-telemetry.html b/kompact/docs/api/kompact/ch.trancee.kompact.generated/-vehicle-telemetry/-vehicle-telemetry.html new file mode 100644 index 0000000..6fd5932 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.generated/-vehicle-telemetry/-vehicle-telemetry.html @@ -0,0 +1,153 @@ + + + + + VehicleTelemetry + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

VehicleTelemetry

+
+
+
+
expect constructor(raw: ByteArray)
actual constructor(raw: ByteArray)
actual constructor(raw: ByteArray)
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.generated/-vehicle-telemetry/battery-status.html b/kompact/docs/api/kompact/ch.trancee.kompact.generated/-vehicle-telemetry/battery-status.html new file mode 100644 index 0000000..c37e10c --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.generated/-vehicle-telemetry/battery-status.html @@ -0,0 +1,153 @@ + + + + + batteryStatus + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

batteryStatus

+
+
+
+
expect var batteryStatus: Int
actual var batteryStatus: Int
actual var batteryStatus: Int
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.generated/-vehicle-telemetry/index.html b/kompact/docs/api/kompact/ch.trancee.kompact.generated/-vehicle-telemetry/index.html new file mode 100644 index 0000000..cb5ba1e --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.generated/-vehicle-telemetry/index.html @@ -0,0 +1,272 @@ + + + + + VehicleTelemetry + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

VehicleTelemetry

+
+
+
expect value class VehicleTelemetry(val raw: ByteArray)

Concrete shared example (PROMPT §3), realized as an expect value class per Ticket 03: a plain value class in common (no @JvmInline, per PROMPT §1) backed by a single ByteArray. Platform actuals provide the member bodies; the JVM actual is @JvmInline for zero-allocation wrapping, iOS uses a plain actual value class (Ticket 03 reconciliation).

Layout matrix (LSB-first), packed into 16 bits:

  • 0..3 (4 bits) : Battery Status Enum (0-15)

  • 4..13 (10 bits): Speed integer (0-1023)

  • 14..14 (1 bit) : Is Engine Malfunction Active (Boolean)

  • 15..15 (1 bit) : Reserved/Unused

The ByteArray is the wire format. A producer builds it via KompactWriter or VehicleTelemetry.create(...); a consumer reads fields via the @KompactField-annotated properties. Properties have write-through setters that modify the backing ByteArray in place, so you can read from a BLE characteristic, modify a field, and re-send the same buffer — no intermediate objects, no allocation on the read hot path.

actual value class VehicleTelemetry(val raw: ByteArray)

iOS actual: a plain value class (Kotlin/Native) with identical field layout.

actual value class VehicleTelemetry(val raw: ByteArray)

JVM actual: @JvmInline yields a zero-allocation inline class (Ticket 03).

+
+
+
+
+
+

Constructors

+
+
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
expect constructor(raw: ByteArray)
actual constructor(raw: ByteArray)
actual constructor(raw: ByteArray)
+
+
+
+
+
+
+
+

Types

+
+
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
expect object Companion
actual object Companion
actual object Companion
+
+
+
+
+
+
+
+

Properties

+
+
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
expect var batteryStatus: Int
actual var batteryStatus: Int
actual var batteryStatus: Int
+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
expect val raw: ByteArray
actual val raw: ByteArray
actual val raw: ByteArray
+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
expect var speed: Int
actual var speed: Int
actual var speed: Int
+
+
+
+
+
+
+
+
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.generated/-vehicle-telemetry/is-malfunctioning.html b/kompact/docs/api/kompact/ch.trancee.kompact.generated/-vehicle-telemetry/is-malfunctioning.html new file mode 100644 index 0000000..2787f86 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.generated/-vehicle-telemetry/is-malfunctioning.html @@ -0,0 +1,153 @@ + + + + + isMalfunctioning + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

isMalfunctioning

+
+
+
+
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.generated/-vehicle-telemetry/raw.html b/kompact/docs/api/kompact/ch.trancee.kompact.generated/-vehicle-telemetry/raw.html new file mode 100644 index 0000000..6c791b7 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.generated/-vehicle-telemetry/raw.html @@ -0,0 +1,153 @@ + + + + + raw + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

raw

+
+
+
+
expect val raw: ByteArray
actual val raw: ByteArray
actual val raw: ByteArray
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.generated/-vehicle-telemetry/speed.html b/kompact/docs/api/kompact/ch.trancee.kompact.generated/-vehicle-telemetry/speed.html new file mode 100644 index 0000000..02abedc --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.generated/-vehicle-telemetry/speed.html @@ -0,0 +1,153 @@ + + + + + speed + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

speed

+
+
+
+
expect var speed: Int
actual var speed: Int
actual var speed: Int
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.generated/index.html b/kompact/docs/api/kompact/ch.trancee.kompact.generated/index.html new file mode 100644 index 0000000..0da98d0 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.generated/index.html @@ -0,0 +1,176 @@ + + + + + ch.trancee.kompact.generated + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

Package-level declarations

+
+
+
+
+
+

Types

+
+
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
expect value class VehicleTelemetry(val raw: ByteArray)

Concrete shared example (PROMPT §3), realized as an expect value class per Ticket 03: a plain value class in common (no @JvmInline, per PROMPT §1) backed by a single ByteArray. Platform actuals provide the member bodies; the JVM actual is @JvmInline for zero-allocation wrapping, iOS uses a plain actual value class (Ticket 03 reconciliation).

actual value class VehicleTelemetry(val raw: ByteArray)

iOS actual: a plain value class (Kotlin/Native) with identical field layout.

actual value class VehicleTelemetry(val raw: ByteArray)

JVM actual: @JvmInline yields a zero-allocation inline class (Ticket 03).

+
+
+
+
+
+
+
+
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-boolean-result/-boolean-result.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-boolean-result/-boolean-result.html new file mode 100644 index 0000000..92db5f9 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-boolean-result/-boolean-result.html @@ -0,0 +1,153 @@ + + + + + BooleanResult + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

BooleanResult

+
+
+
+
expect constructor(packed: Long)
actual constructor(packed: Long)
actual constructor(packed: Long)
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-boolean-result/-companion/failure.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-boolean-result/-companion/failure.html new file mode 100644 index 0000000..6a501e1 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-boolean-result/-companion/failure.html @@ -0,0 +1,153 @@ + + + + + failure + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

failure

+
+
+
+
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-boolean-result/-companion/index.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-boolean-result/-companion/index.html new file mode 100644 index 0000000..9bef468 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-boolean-result/-companion/index.html @@ -0,0 +1,196 @@ + + + + + Companion + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

Companion

+
+
+
expect object Companion
actual object Companion
actual object Companion
+
+
+
+
+
+

Functions

+
+
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
expect fun success(value: Boolean): BooleanResult
actual fun success(value: Boolean): BooleanResult
actual fun success(value: Boolean): BooleanResult
+
+
+
+
+
+
+
+
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-boolean-result/-companion/success.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-boolean-result/-companion/success.html new file mode 100644 index 0000000..138fc8f --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-boolean-result/-companion/success.html @@ -0,0 +1,153 @@ + + + + + success + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

success

+
+
+
+
expect fun success(value: Boolean): BooleanResult
actual fun success(value: Boolean): BooleanResult
actual fun success(value: Boolean): BooleanResult
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-boolean-result/error.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-boolean-result/error.html new file mode 100644 index 0000000..effd000 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-boolean-result/error.html @@ -0,0 +1,153 @@ + + + + + error + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

error

+
+
+
+
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-boolean-result/get-or-throw.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-boolean-result/get-or-throw.html new file mode 100644 index 0000000..1167355 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-boolean-result/get-or-throw.html @@ -0,0 +1,153 @@ + + + + + getOrThrow + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

getOrThrow

+
+
+
+
expect fun getOrThrow(): Boolean
actual fun getOrThrow(): Boolean
actual fun getOrThrow(): Boolean
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-boolean-result/index.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-boolean-result/index.html new file mode 100644 index 0000000..ac84482 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-boolean-result/index.html @@ -0,0 +1,323 @@ + + + + + BooleanResult + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

BooleanResult

+
+
+
expect value class BooleanResult(val packed: Long)
actual value class BooleanResult(val packed: Long)
actual value class BooleanResult(val packed: Long)
+
+
+
+
+
+

Constructors

+
+
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
expect constructor(packed: Long)
actual constructor(packed: Long)
actual constructor(packed: Long)
+
+
+
+
+
+
+
+

Types

+
+
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
expect object Companion
actual object Companion
actual object Companion
+
+
+
+
+
+
+
+

Properties

+
+
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
expect val isFailure: Boolean
actual val isFailure: Boolean
actual val isFailure: Boolean
+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
expect val isSuccess: Boolean
actual val isSuccess: Boolean
actual val isSuccess: Boolean
+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
expect val packed: Long
actual val packed: Long
actual val packed: Long
+
+
+
+
+
+
+
+

Functions

+
+
+
+
+ + +
Link copied to clipboard
+
+
+
+ +
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
expect fun getOrThrow(): Boolean
actual fun getOrThrow(): Boolean
actual fun getOrThrow(): Boolean
+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
inline fun BooleanResult.map(transform: (Boolean) -> Boolean): BooleanResult
+
+
+
+
+
+
+
+
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-boolean-result/is-failure.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-boolean-result/is-failure.html new file mode 100644 index 0000000..d66833f --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-boolean-result/is-failure.html @@ -0,0 +1,153 @@ + + + + + isFailure + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

isFailure

+
+
+
+
expect val isFailure: Boolean
actual val isFailure: Boolean
actual val isFailure: Boolean
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-boolean-result/is-success.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-boolean-result/is-success.html new file mode 100644 index 0000000..e81bc8b --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-boolean-result/is-success.html @@ -0,0 +1,153 @@ + + + + + isSuccess + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

isSuccess

+
+
+
+
expect val isSuccess: Boolean
actual val isSuccess: Boolean
actual val isSuccess: Boolean
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-boolean-result/packed.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-boolean-result/packed.html new file mode 100644 index 0000000..69deb26 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-boolean-result/packed.html @@ -0,0 +1,153 @@ + + + + + packed + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

packed

+
+
+
+
expect val packed: Long
actual val packed: Long
actual val packed: Long
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-byte-result/-byte-result.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-byte-result/-byte-result.html new file mode 100644 index 0000000..cbd084e --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-byte-result/-byte-result.html @@ -0,0 +1,153 @@ + + + + + ByteResult + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

ByteResult

+
+
+
+
expect constructor(packed: Long)
actual constructor(packed: Long)
actual constructor(packed: Long)
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-byte-result/-companion/failure.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-byte-result/-companion/failure.html new file mode 100644 index 0000000..fad3467 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-byte-result/-companion/failure.html @@ -0,0 +1,153 @@ + + + + + failure + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

failure

+
+
+
+
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-byte-result/-companion/index.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-byte-result/-companion/index.html new file mode 100644 index 0000000..a843df7 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-byte-result/-companion/index.html @@ -0,0 +1,196 @@ + + + + + Companion + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

Companion

+
+
+
expect object Companion
actual object Companion
actual object Companion
+
+
+
+
+
+

Functions

+
+
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
expect fun success(value: Byte): ByteResult
actual fun success(value: Byte): ByteResult
actual fun success(value: Byte): ByteResult
+
+
+
+
+
+
+
+
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-byte-result/-companion/success.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-byte-result/-companion/success.html new file mode 100644 index 0000000..dd69a4a --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-byte-result/-companion/success.html @@ -0,0 +1,153 @@ + + + + + success + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

success

+
+
+
+
expect fun success(value: Byte): ByteResult
actual fun success(value: Byte): ByteResult
actual fun success(value: Byte): ByteResult
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-byte-result/error.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-byte-result/error.html new file mode 100644 index 0000000..e5909cb --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-byte-result/error.html @@ -0,0 +1,153 @@ + + + + + error + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

error

+
+
+
+
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-byte-result/get-or-throw.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-byte-result/get-or-throw.html new file mode 100644 index 0000000..27f1f59 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-byte-result/get-or-throw.html @@ -0,0 +1,153 @@ + + + + + getOrThrow + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

getOrThrow

+
+
+
+
expect fun getOrThrow(): Byte
actual fun getOrThrow(): Byte
actual fun getOrThrow(): Byte
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-byte-result/index.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-byte-result/index.html new file mode 100644 index 0000000..0deda71 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-byte-result/index.html @@ -0,0 +1,323 @@ + + + + + ByteResult + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

ByteResult

+
+
+
expect value class ByteResult(val packed: Long)
actual value class ByteResult(val packed: Long)
actual value class ByteResult(val packed: Long)
+
+
+
+
+
+

Constructors

+
+
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
expect constructor(packed: Long)
actual constructor(packed: Long)
actual constructor(packed: Long)
+
+
+
+
+
+
+
+

Types

+
+
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
expect object Companion
actual object Companion
actual object Companion
+
+
+
+
+
+
+
+

Properties

+
+
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
expect val isFailure: Boolean
actual val isFailure: Boolean
actual val isFailure: Boolean
+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
expect val isSuccess: Boolean
actual val isSuccess: Boolean
actual val isSuccess: Boolean
+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
expect val packed: Long
actual val packed: Long
actual val packed: Long
+
+
+
+
+
+
+
+

Functions

+
+
+
+
+ + +
Link copied to clipboard
+
+
+
+
inline fun ByteResult.getOrElse(fallback: (KompactDecodeError) -> Byte): Byte
+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
expect fun getOrThrow(): Byte
actual fun getOrThrow(): Byte
actual fun getOrThrow(): Byte
+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
inline fun ByteResult.map(transform: (Byte) -> Byte): ByteResult
+
+
+
+
+
+
+
+
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-byte-result/is-failure.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-byte-result/is-failure.html new file mode 100644 index 0000000..ec94f95 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-byte-result/is-failure.html @@ -0,0 +1,153 @@ + + + + + isFailure + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

isFailure

+
+
+
+
expect val isFailure: Boolean
actual val isFailure: Boolean
actual val isFailure: Boolean
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-byte-result/is-success.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-byte-result/is-success.html new file mode 100644 index 0000000..bd7149e --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-byte-result/is-success.html @@ -0,0 +1,153 @@ + + + + + isSuccess + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

isSuccess

+
+
+
+
expect val isSuccess: Boolean
actual val isSuccess: Boolean
actual val isSuccess: Boolean
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-byte-result/packed.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-byte-result/packed.html new file mode 100644 index 0000000..9d9be50 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-byte-result/packed.html @@ -0,0 +1,153 @@ + + + + + packed + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

packed

+
+
+
+
expect val packed: Long
actual val packed: Long
actual val packed: Long
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-double-result/-companion/failure.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-double-result/-companion/failure.html new file mode 100644 index 0000000..5461ae5 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-double-result/-companion/failure.html @@ -0,0 +1,153 @@ + + + + + failure + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

failure

+
+
+
+
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-double-result/-companion/index.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-double-result/-companion/index.html new file mode 100644 index 0000000..d700101 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-double-result/-companion/index.html @@ -0,0 +1,196 @@ + + + + + Companion + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

Companion

+
+
+
expect object Companion
actual object Companion
actual object Companion
+
+
+
+
+
+

Functions

+
+
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
expect fun success(value: Double): DoubleResult
actual fun success(value: Double): DoubleResult
actual fun success(value: Double): DoubleResult
+
+
+
+
+
+
+
+
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-double-result/-companion/success.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-double-result/-companion/success.html new file mode 100644 index 0000000..d0fba50 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-double-result/-companion/success.html @@ -0,0 +1,153 @@ + + + + + success + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

success

+
+
+
+
expect fun success(value: Double): DoubleResult
actual fun success(value: Double): DoubleResult
actual fun success(value: Double): DoubleResult
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-double-result/-double-result.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-double-result/-double-result.html new file mode 100644 index 0000000..f4ff359 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-double-result/-double-result.html @@ -0,0 +1,153 @@ + + + + + DoubleResult + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

DoubleResult

+
+
+
+
expect constructor(packed: Long)
actual constructor(packed: Long)
actual constructor(packed: Long)
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-double-result/error.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-double-result/error.html new file mode 100644 index 0000000..40d1d8f --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-double-result/error.html @@ -0,0 +1,153 @@ + + + + + error + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

error

+
+
+
+
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-double-result/get-or-throw.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-double-result/get-or-throw.html new file mode 100644 index 0000000..e82162c --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-double-result/get-or-throw.html @@ -0,0 +1,153 @@ + + + + + getOrThrow + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

getOrThrow

+
+
+
+
expect fun getOrThrow(): Double
actual fun getOrThrow(): Double
actual fun getOrThrow(): Double
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-double-result/index.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-double-result/index.html new file mode 100644 index 0000000..d94d723 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-double-result/index.html @@ -0,0 +1,323 @@ + + + + + DoubleResult + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

DoubleResult

+
+
+
expect value class DoubleResult(val packed: Long)
actual value class DoubleResult(val packed: Long)
actual value class DoubleResult(val packed: Long)
+
+
+
+
+
+

Constructors

+
+
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
expect constructor(packed: Long)
actual constructor(packed: Long)
actual constructor(packed: Long)
+
+
+
+
+
+
+
+

Types

+
+
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
expect object Companion
actual object Companion
actual object Companion
+
+
+
+
+
+
+
+

Properties

+
+
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
expect val isFailure: Boolean
actual val isFailure: Boolean
actual val isFailure: Boolean
+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
expect val isSuccess: Boolean
actual val isSuccess: Boolean
actual val isSuccess: Boolean
+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
expect val packed: Long
actual val packed: Long
actual val packed: Long
+
+
+
+
+
+
+
+

Functions

+
+
+
+
+ + +
Link copied to clipboard
+
+
+
+ +
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
expect fun getOrThrow(): Double
actual fun getOrThrow(): Double
actual fun getOrThrow(): Double
+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
inline fun DoubleResult.map(transform: (Double) -> Double): DoubleResult
+
+
+
+
+
+
+
+
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-double-result/is-failure.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-double-result/is-failure.html new file mode 100644 index 0000000..5716259 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-double-result/is-failure.html @@ -0,0 +1,153 @@ + + + + + isFailure + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

isFailure

+
+
+
+
expect val isFailure: Boolean
actual val isFailure: Boolean
actual val isFailure: Boolean
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-double-result/is-success.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-double-result/is-success.html new file mode 100644 index 0000000..34326e7 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-double-result/is-success.html @@ -0,0 +1,153 @@ + + + + + isSuccess + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

isSuccess

+
+
+
+
expect val isSuccess: Boolean
actual val isSuccess: Boolean
actual val isSuccess: Boolean
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-double-result/packed.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-double-result/packed.html new file mode 100644 index 0000000..8da4c6b --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-double-result/packed.html @@ -0,0 +1,153 @@ + + + + + packed + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

packed

+
+
+
+
expect val packed: Long
actual val packed: Long
actual val packed: Long
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-float-result/-companion/failure.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-float-result/-companion/failure.html new file mode 100644 index 0000000..0c3f59d --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-float-result/-companion/failure.html @@ -0,0 +1,153 @@ + + + + + failure + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

failure

+
+
+
+
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-float-result/-companion/index.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-float-result/-companion/index.html new file mode 100644 index 0000000..595e993 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-float-result/-companion/index.html @@ -0,0 +1,196 @@ + + + + + Companion + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

Companion

+
+
+
expect object Companion
actual object Companion
actual object Companion
+
+
+
+
+
+

Functions

+
+
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
expect fun success(value: Float): FloatResult
actual fun success(value: Float): FloatResult
actual fun success(value: Float): FloatResult
+
+
+
+
+
+
+
+
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-float-result/-companion/success.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-float-result/-companion/success.html new file mode 100644 index 0000000..7e4cb4b --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-float-result/-companion/success.html @@ -0,0 +1,153 @@ + + + + + success + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

success

+
+
+
+
expect fun success(value: Float): FloatResult
actual fun success(value: Float): FloatResult
actual fun success(value: Float): FloatResult
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-float-result/-float-result.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-float-result/-float-result.html new file mode 100644 index 0000000..71b8eb3 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-float-result/-float-result.html @@ -0,0 +1,153 @@ + + + + + FloatResult + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

FloatResult

+
+
+
+
expect constructor(packed: Long)
actual constructor(packed: Long)
actual constructor(packed: Long)
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-float-result/error.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-float-result/error.html new file mode 100644 index 0000000..c0a0050 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-float-result/error.html @@ -0,0 +1,153 @@ + + + + + error + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

error

+
+
+
+
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-float-result/get-or-throw.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-float-result/get-or-throw.html new file mode 100644 index 0000000..68263d7 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-float-result/get-or-throw.html @@ -0,0 +1,153 @@ + + + + + getOrThrow + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

getOrThrow

+
+
+
+
expect fun getOrThrow(): Float
actual fun getOrThrow(): Float
actual fun getOrThrow(): Float
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-float-result/index.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-float-result/index.html new file mode 100644 index 0000000..a9c0395 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-float-result/index.html @@ -0,0 +1,323 @@ + + + + + FloatResult + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

FloatResult

+
+
+
expect value class FloatResult(val packed: Long)
actual value class FloatResult(val packed: Long)
actual value class FloatResult(val packed: Long)
+
+
+
+
+
+

Constructors

+
+
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
expect constructor(packed: Long)
actual constructor(packed: Long)
actual constructor(packed: Long)
+
+
+
+
+
+
+
+

Types

+
+
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
expect object Companion
actual object Companion
actual object Companion
+
+
+
+
+
+
+
+

Properties

+
+
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
expect val isFailure: Boolean
actual val isFailure: Boolean
actual val isFailure: Boolean
+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
expect val isSuccess: Boolean
actual val isSuccess: Boolean
actual val isSuccess: Boolean
+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
expect val packed: Long
actual val packed: Long
actual val packed: Long
+
+
+
+
+
+
+
+

Functions

+
+
+
+
+ + +
Link copied to clipboard
+
+
+
+
inline fun FloatResult.getOrElse(fallback: (KompactDecodeError) -> Float): Float
+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
expect fun getOrThrow(): Float
actual fun getOrThrow(): Float
actual fun getOrThrow(): Float
+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
inline fun FloatResult.map(transform: (Float) -> Float): FloatResult
+
+
+
+
+
+
+
+
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-float-result/is-failure.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-float-result/is-failure.html new file mode 100644 index 0000000..b186358 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-float-result/is-failure.html @@ -0,0 +1,153 @@ + + + + + isFailure + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

isFailure

+
+
+
+
expect val isFailure: Boolean
actual val isFailure: Boolean
actual val isFailure: Boolean
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-float-result/is-success.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-float-result/is-success.html new file mode 100644 index 0000000..af8e036 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-float-result/is-success.html @@ -0,0 +1,153 @@ + + + + + isSuccess + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

isSuccess

+
+
+
+
expect val isSuccess: Boolean
actual val isSuccess: Boolean
actual val isSuccess: Boolean
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-float-result/packed.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-float-result/packed.html new file mode 100644 index 0000000..95c8d66 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-float-result/packed.html @@ -0,0 +1,153 @@ + + + + + packed + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

packed

+
+
+
+
expect val packed: Long
actual val packed: Long
actual val packed: Long
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-int-result/-companion/failure.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-int-result/-companion/failure.html new file mode 100644 index 0000000..cb62761 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-int-result/-companion/failure.html @@ -0,0 +1,153 @@ + + + + + failure + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

failure

+
+
+
+
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-int-result/-companion/index.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-int-result/-companion/index.html new file mode 100644 index 0000000..db530a7 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-int-result/-companion/index.html @@ -0,0 +1,196 @@ + + + + + Companion + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

Companion

+
+
+
expect object Companion
actual object Companion
actual object Companion
+
+
+
+
+
+

Functions

+
+
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
expect fun success(value: Int): IntResult
actual fun success(value: Int): IntResult
actual fun success(value: Int): IntResult
+
+
+
+
+
+
+
+
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-int-result/-companion/success.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-int-result/-companion/success.html new file mode 100644 index 0000000..7b73c81 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-int-result/-companion/success.html @@ -0,0 +1,153 @@ + + + + + success + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

success

+
+
+
+
expect fun success(value: Int): IntResult
actual fun success(value: Int): IntResult
actual fun success(value: Int): IntResult
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-int-result/-int-result.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-int-result/-int-result.html new file mode 100644 index 0000000..ba51fc6 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-int-result/-int-result.html @@ -0,0 +1,153 @@ + + + + + IntResult + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

IntResult

+
+
+
+
expect constructor(packed: Long)
actual constructor(packed: Long)
actual constructor(packed: Long)
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-int-result/error.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-int-result/error.html new file mode 100644 index 0000000..a4c7b10 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-int-result/error.html @@ -0,0 +1,153 @@ + + + + + error + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

error

+
+
+
+
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-int-result/get-or-throw.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-int-result/get-or-throw.html new file mode 100644 index 0000000..ec53a53 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-int-result/get-or-throw.html @@ -0,0 +1,153 @@ + + + + + getOrThrow + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

getOrThrow

+
+
+
+
expect fun getOrThrow(): Int
actual fun getOrThrow(): Int
actual fun getOrThrow(): Int
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-int-result/index.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-int-result/index.html new file mode 100644 index 0000000..22aa919 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-int-result/index.html @@ -0,0 +1,323 @@ + + + + + IntResult + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

IntResult

+
+
+
expect value class IntResult(val packed: Long)
actual value class IntResult(val packed: Long)
actual value class IntResult(val packed: Long)
+
+
+
+
+
+

Constructors

+
+
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
expect constructor(packed: Long)
actual constructor(packed: Long)
actual constructor(packed: Long)
+
+
+
+
+
+
+
+

Types

+
+
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
expect object Companion
actual object Companion
actual object Companion
+
+
+
+
+
+
+
+

Properties

+
+
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
expect val isFailure: Boolean
actual val isFailure: Boolean
actual val isFailure: Boolean
+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
expect val isSuccess: Boolean
actual val isSuccess: Boolean
actual val isSuccess: Boolean
+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
expect val packed: Long
actual val packed: Long
actual val packed: Long
+
+
+
+
+
+
+
+

Functions

+
+
+
+
+ + +
Link copied to clipboard
+
+
+
+
inline fun IntResult.getOrElse(fallback: (KompactDecodeError) -> Int): Int
+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
expect fun getOrThrow(): Int
actual fun getOrThrow(): Int
actual fun getOrThrow(): Int
+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
inline fun IntResult.map(transform: (Int) -> Int): IntResult
+
+
+
+
+
+
+
+
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-int-result/is-failure.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-int-result/is-failure.html new file mode 100644 index 0000000..fb17796 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-int-result/is-failure.html @@ -0,0 +1,153 @@ + + + + + isFailure + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

isFailure

+
+
+
+
expect val isFailure: Boolean
actual val isFailure: Boolean
actual val isFailure: Boolean
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-int-result/is-success.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-int-result/is-success.html new file mode 100644 index 0000000..f32c449 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-int-result/is-success.html @@ -0,0 +1,153 @@ + + + + + isSuccess + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

isSuccess

+
+
+
+
expect val isSuccess: Boolean
actual val isSuccess: Boolean
actual val isSuccess: Boolean
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-int-result/packed.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-int-result/packed.html new file mode 100644 index 0000000..230a9ff --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-int-result/packed.html @@ -0,0 +1,153 @@ + + + + + packed + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

packed

+
+
+
+
expect val packed: Long
actual val packed: Long
actual val packed: Long
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-decode-error/-bad-length-prefix/index.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-decode-error/-bad-length-prefix/index.html new file mode 100644 index 0000000..f52b868 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-decode-error/-bad-length-prefix/index.html @@ -0,0 +1,135 @@ + + + + + BadLengthPrefix + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

BadLengthPrefix

+ +
+
+
+
+
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-decode-error/-bounds-error/index.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-decode-error/-bounds-error/index.html new file mode 100644 index 0000000..0a56e5a --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-decode-error/-bounds-error/index.html @@ -0,0 +1,135 @@ + + + + + BoundsError + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

BoundsError

+ +
+
+
+
+
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-decode-error/-truncated-nested/index.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-decode-error/-truncated-nested/index.html new file mode 100644 index 0000000..981fe14 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-decode-error/-truncated-nested/index.html @@ -0,0 +1,135 @@ + + + + + TruncatedNested + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

TruncatedNested

+ +
+
+
+
+
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-decode-error/-unknown-enum-code/-unknown-enum-code.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-decode-error/-unknown-enum-code/-unknown-enum-code.html new file mode 100644 index 0000000..1a76a0d --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-decode-error/-unknown-enum-code/-unknown-enum-code.html @@ -0,0 +1,131 @@ + + + + + UnknownEnumCode + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

UnknownEnumCode

+
+
constructor(rawCode: Int)
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-decode-error/-unknown-enum-code/index.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-decode-error/-unknown-enum-code/index.html new file mode 100644 index 0000000..97053fe --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-decode-error/-unknown-enum-code/index.html @@ -0,0 +1,174 @@ + + + + + UnknownEnumCode + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

UnknownEnumCode

+
data class UnknownEnumCode(val rawCode: Int) : KompactDecodeError
+
+
+
+
+
+

Constructors

+
+
+
+
+ + +
Link copied to clipboard
+
+
+
+
constructor(rawCode: Int)
+
+
+
+
+
+
+
+

Properties

+
+
+
+
+ + +
Link copied to clipboard
+
+
+
+ +
+
+
+
+
+
+
+
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-decode-error/-unknown-enum-code/raw-code.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-decode-error/-unknown-enum-code/raw-code.html new file mode 100644 index 0000000..4791e8d --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-decode-error/-unknown-enum-code/raw-code.html @@ -0,0 +1,131 @@ + + + + + rawCode + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

rawCode

+
+ +
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-decode-error/index.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-decode-error/index.html new file mode 100644 index 0000000..b64f5c4 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-decode-error/index.html @@ -0,0 +1,200 @@ + + + + + KompactDecodeError + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

KompactDecodeError

+
sealed class KompactDecodeError

Runtime decode error taxonomy (Ticket 06).

Returned (never thrown) on the read path: a checked accessor yields a typed Kompact*Result value class whose packed encodes the error kind. Accessing .error reconstructs the concrete case lazily — singletons on the common path, UnknownEnumCode allocates only the data-class payload.

Inheritors

+
+
+
+
+
+

Types

+
+
+
+
+ + +
Link copied to clipboard
+
+ +
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+ +
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+ +
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
data class UnknownEnumCode(val rawCode: Int) : KompactDecodeError
+
+
+
+
+
+
+
+
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-decode-exception/-kompact-decode-exception.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-decode-exception/-kompact-decode-exception.html new file mode 100644 index 0000000..f2da891 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-decode-exception/-kompact-decode-exception.html @@ -0,0 +1,131 @@ + + + + + KompactDecodeException + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

KompactDecodeException

+
+
constructor(error: KompactDecodeError)
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-decode-exception/error.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-decode-exception/error.html new file mode 100644 index 0000000..972e9a8 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-decode-exception/error.html @@ -0,0 +1,131 @@ + + + + + error + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

error

+
+ +
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-decode-exception/index.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-decode-exception/index.html new file mode 100644 index 0000000..a515540 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-decode-exception/index.html @@ -0,0 +1,204 @@ + + + + + KompactDecodeException + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

KompactDecodeException

+

Thrown by getOrThrow() / readOrThrow() on the failure path. The success hot-path never throws (Ticket 03 zero-alloc). Allocation of this exception is acceptable because it only occurs on an explicit recovery call.

+
+
+
+
+
+

Constructors

+
+
+
+
+ + +
Link copied to clipboard
+
+
+
+
constructor(error: KompactDecodeError)
+
+
+
+
+
+
+
+

Properties

+
+
+
+
+ + +
Link copied to clipboard
+
+
+
+
expect open val cause: Throwable?
+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+ +
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
expect open val message: String?
+
+
+
+
+
+
+
+
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-framing/-i-n-v-a-l-i-d_-l-e-n-g-t-h_-p-r-e-f-i-x.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-framing/-i-n-v-a-l-i-d_-l-e-n-g-t-h_-p-r-e-f-i-x.html new file mode 100644 index 0000000..b81f5a3 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-framing/-i-n-v-a-l-i-d_-l-e-n-g-t-h_-p-r-e-f-i-x.html @@ -0,0 +1,131 @@ + + + + + INVALID_LENGTH_PREFIX + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

INVALID_LENGTH_PREFIX

+
+

Sentinel returned by readLengthPrefix when bitWidth is invalid or the prefix field overruns raw (Q9: name the length-prefix failure sentinel rather than scattering a bare -1). This is the only value readLengthPrefix returns on failure; it is never a valid (non-negative) byte count.

+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-framing/-v-a-l-i-d_-p-r-e-f-i-x_-w-i-d-t-h-s.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-framing/-v-a-l-i-d_-p-r-e-f-i-x_-w-i-d-t-h-s.html new file mode 100644 index 0000000..35b44e9 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-framing/-v-a-l-i-d_-p-r-e-f-i-x_-w-i-d-t-h-s.html @@ -0,0 +1,131 @@ + + + + + VALID_PREFIX_WIDTHS + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

VALID_PREFIX_WIDTHS

+
+

Valid length-prefix bit widths (Ticket 06 invariant matrix).

+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-framing/index.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-framing/index.html new file mode 100644 index 0000000..02eb43b --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-framing/index.html @@ -0,0 +1,249 @@ + + + + + KompactFraming + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

KompactFraming

+

Sequential, length-delimited framing (Ticket 05) and repeat/count handling.

Wire shape, read forward (no random access):

  • Length prefix — a fixed-width (8/16/32-bit) little-endian byte count placed at bitOffset; the prefixed payload follows immediately at bitOffset + prefixBitWidth.

  • Nested composite — a length-delimited sub-region: read the prefix to learn the byte count, then consume prefixBitWidth + count * 8 bits and hand the caller the sub-region's [startBit, bitLength).

  • Repeated fields — one fixed-width count prefix, then count elements in sequence (the count width is the field's declared prefix width).

Reads never throw on the hot path (Ticket 06): a prefix that overruns the buffer is surfaced via nestedRegionOrNull's nullable return so the caller can map it to a typed BadLengthPrefix result (Ticket 06/09: a length-prefix that exceeds remaining bytes is BadLengthPrefix; skew is fail-fast, never silent).

+
+
+
+
+
+

Properties

+
+
+
+
+ + +
Link copied to clipboard
+
+
+
+

Sentinel returned by readLengthPrefix when bitWidth is invalid or the prefix field overruns raw (Q9: name the length-prefix failure sentinel rather than scattering a bare -1). This is the only value readLengthPrefix returns on failure; it is never a valid (non-negative) byte count.

+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+

Valid length-prefix bit widths (Ticket 06 invariant matrix).

+
+
+
+
+
+
+
+

Functions

+
+
+
+
+ + +
Link copied to clipboard
+
+
+
+
fun readLengthPrefix(raw: ByteArray, bitOffset: Int, bitWidth: Int): Int

Reads a fixed-width (8/16/32-bit) little-endian byte count at bitOffset. Unsigned magnitude via the raw bit primitives; returns INVALID_LENGTH_PREFIX (-1) when bitWidth is invalid or the region overruns raw (caller maps to a typed error — never throws on the read path, Ticket 06).

+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
fun readLengthPrefixOrThrow(raw: ByteArray, bitOffset: Int, bitWidth: Int): Int

Throwing variant of readLengthPrefix: throws KompactDecodeException on a bad prefix (Ticket 05).

+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
fun readNested(raw: ByteArray, bitOffset: Int, prefixBitWidth: Int): NestedRegionResult

Typed NestedRegionResult variant of nestedRegionOrNull (Ticket 05).

+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
fun readNestedOrThrow(raw: ByteArray, bitOffset: Int, prefixBitWidth: Int): NestedRegion

Throwing variant of readNested: throws KompactDecodeException on failure (Ticket 05).

+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
fun writeLengthPrefix(raw: ByteArray, bitOffset: Int, bitWidth: Int, length: Int)

Writes length as a fixed-width little-endian byte count at bitOffset. Mirrors readLengthPrefix (Ticket 07: the writer selects the per-field prefix width at codegen time; it must be one of VALID_PREFIX_WIDTHS).

+
+
+
+
+
+
+
+
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-framing/read-length-prefix-or-throw.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-framing/read-length-prefix-or-throw.html new file mode 100644 index 0000000..6558f36 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-framing/read-length-prefix-or-throw.html @@ -0,0 +1,131 @@ + + + + + readLengthPrefixOrThrow + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

readLengthPrefixOrThrow

+
+
fun readLengthPrefixOrThrow(raw: ByteArray, bitOffset: Int, bitWidth: Int): Int

Throwing variant of readLengthPrefix: throws KompactDecodeException on a bad prefix (Ticket 05).

+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-framing/read-length-prefix.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-framing/read-length-prefix.html new file mode 100644 index 0000000..13c8aff --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-framing/read-length-prefix.html @@ -0,0 +1,131 @@ + + + + + readLengthPrefix + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

readLengthPrefix

+
+
fun readLengthPrefix(raw: ByteArray, bitOffset: Int, bitWidth: Int): Int

Reads a fixed-width (8/16/32-bit) little-endian byte count at bitOffset. Unsigned magnitude via the raw bit primitives; returns INVALID_LENGTH_PREFIX (-1) when bitWidth is invalid or the region overruns raw (caller maps to a typed error — never throws on the read path, Ticket 06).

+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-framing/read-nested-or-throw.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-framing/read-nested-or-throw.html new file mode 100644 index 0000000..c473622 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-framing/read-nested-or-throw.html @@ -0,0 +1,131 @@ + + + + + readNestedOrThrow + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

readNestedOrThrow

+
+
fun readNestedOrThrow(raw: ByteArray, bitOffset: Int, prefixBitWidth: Int): NestedRegion

Throwing variant of readNested: throws KompactDecodeException on failure (Ticket 05).

+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-framing/read-nested.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-framing/read-nested.html new file mode 100644 index 0000000..e8f8549 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-framing/read-nested.html @@ -0,0 +1,131 @@ + + + + + readNested + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

readNested

+
+
fun readNested(raw: ByteArray, bitOffset: Int, prefixBitWidth: Int): NestedRegionResult

Typed NestedRegionResult variant of nestedRegionOrNull (Ticket 05).

Parses the prefixBitWidth length-prefix at bitOffset and, on success, returns the (startBit, bitLength) of the payload region. On a bad prefix width, an unreadable prefix, an overflowing count (F-003), or a length-prefix that exceeds the remaining buffer, returns a typed KompactDecodeError.BadLengthPrefix failure — never null — per the Ticket 06/09 invariant (length-prefix > remaining bytes -> BadLengthPrefix); skew is fail-fast, never silent.

Delegates to nestedRegionOrNull (no guard duplication needed since this function is no longer inline, so it can call internal helpers; see Q9 note on sentinel-named failure paths).

+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-framing/write-length-prefix.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-framing/write-length-prefix.html new file mode 100644 index 0000000..9afa9a0 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-framing/write-length-prefix.html @@ -0,0 +1,131 @@ + + + + + writeLengthPrefix + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

writeLengthPrefix

+
+
fun writeLengthPrefix(raw: ByteArray, bitOffset: Int, bitWidth: Int, length: Int)

Writes length as a fixed-width little-endian byte count at bitOffset. Mirrors readLengthPrefix (Ticket 07: the writer selects the per-field prefix width at codegen time; it must be one of VALID_PREFIX_WIDTHS).

+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-runtime/fits.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-runtime/fits.html new file mode 100644 index 0000000..c419c72 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-runtime/fits.html @@ -0,0 +1,131 @@ + + + + + fits + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

fits

+
+
fun fits(raw: ByteArray, bitOffset: Int, bitWidth: Int): Boolean

Bounds-check: true iff bitOffset+bitWidth fits in raw.

+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-runtime/index.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-runtime/index.html new file mode 100644 index 0000000..12a3476 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-runtime/index.html @@ -0,0 +1,395 @@ + + + + + KompactRuntime + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

KompactRuntime

+

Bit-stream primitives for Kompact.

Bit order is LSB-first (little-endian bit packing): byte 0 holds the field's bits 0-7, byte 1 holds bits 8-15, and bit 0 of each byte is the least-significant bit of the field value (Ticket 01). Every Byte is masked with and 0xFF before ushr/shl/or, so assembly is identical on the JVM and Kotlin/Native regardless of platform endianness (PROMPT §1).

These primitives are small, side-effect-free, and reference-free: a value-class getter delegates to them with no heap allocation on Kotlin/Native (value classes over primitive Long are unboxed) and no heap allocation on the JVM (the JIT scalar-replaces the @JvmInline wrapper) (Ticket 03).

+
+
+
+
+
+

Functions

+
+
+
+
+ + +
Link copied to clipboard
+
+
+
+
fun fits(raw: ByteArray, bitOffset: Int, bitWidth: Int): Boolean

Bounds-check: true iff bitOffset+bitWidth fits in raw.

+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
fun readBits(raw: ByteArray, bitOffset: Int, bitWidth: Int): Int

Reads bitWidth bits (1..31) from raw starting at bitOffset, LSB-first.

+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
fun readBitsBoolean(raw: ByteArray, bitOffset: Int): Boolean

Reads a single bit at bitOffset as a Boolean.

+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
fun readBitsLong(raw: ByteArray, bitOffset: Int, bitWidth: Int): Long

Reads bitWidth bits (1..64) from raw starting at bitOffset, LSB-first.

+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
fun readBool(raw: ByteArray, bitOffset: Int): BooleanResult

Reads 1 bit at bitOffset as a checked BooleanResult.

+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
fun readBoolOrThrow(raw: ByteArray, bitOffset: Int): Boolean

Throws KompactDecodeException on a bounds error; otherwise reads 1 bit as a Boolean (Ticket 04).

+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
fun readDouble(raw: ByteArray, bitOffset: Int): DoubleResult

Reads 64 bits at bitOffset as a checked DoubleResult. NaN is canonicalized (Ticket 04).

+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
fun readDoubleOrThrow(raw: ByteArray, bitOffset: Int): Double

Throws KompactDecodeException on a bounds error; otherwise reads 64 bits as a Double (Ticket 04).

+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
fun readFloat(raw: ByteArray, bitOffset: Int): FloatResult

Reads 32 bits at bitOffset as a checked FloatResult. NaN is canonicalized (Ticket 04).

+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
fun readFloatOrThrow(raw: ByteArray, bitOffset: Int): Float

Throws KompactDecodeException on a bounds error; otherwise reads 32 bits as a Float (Ticket 04).

+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
fun readScalar(raw: ByteArray, bitOffset: Int, type: ScalarType): IntResult

Reads up to ScalarType.bitWidth bits of type as a checked IntResult. The width (1..32) and signedness come from type, so a single accessor replaces the 8 per-width readInt8/16/32 and readUInt8/16/32 overloads (ergonomics-01: ScalarType consolidation). Sign extension uses Long-arithmetic shifts, bit-identical to the legacy accessors. Callers pass a ScalarType; see readScalarOrThrow for the exceptions variant.

+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
fun readScalarAsLong(raw: ByteArray, bitOffset: Int, type: ScalarType): LongResult

Reads up to ScalarType.bitWidth bits of type as a checked LongResult (1..64). Width/signedness derive from type; sign extension (two's-complement) uses Long-arithmetic shifts. Replaces readScalarLong(w, b, signed); callers pass a ScalarType carrying the UInt64/Int64 bands (ergonomics-01: ScalarType consolidation). See readScalarAsLongOrThrow for the exceptions variant.

+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
fun readScalarAsLongOrThrow(raw: ByteArray, bitOffset: Int, type: ScalarType): Long

Throws KompactDecodeException on a bounds error; otherwise decodes type bits as a Long (Ticket 04).

+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
fun readScalarOrThrow(raw: ByteArray, bitOffset: Int, type: ScalarType): Int

Throws KompactDecodeException on a bounds error; otherwise decodes type bits as an Int (Ticket 04).

+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
fun writeBits(raw: ByteArray, bitOffset: Int, bitWidth: Int, value: Int)

Writes the low bitWidth bits (1..31) of value into raw at bitOffset, LSB-first.

+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
fun writeBitsBoolean(raw: ByteArray, bitOffset: Int, value: Boolean)

Writes value as a single bit at bitOffset.

+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
fun writeBitsLong(raw: ByteArray, bitOffset: Int, bitWidth: Int, value: Long)

Writes the low bitWidth bits (1..64) of value into raw at bitOffset, LSB-first.

+
+
+
+
+
+
+
+
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-runtime/read-bits-boolean.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-runtime/read-bits-boolean.html new file mode 100644 index 0000000..258591b --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-runtime/read-bits-boolean.html @@ -0,0 +1,131 @@ + + + + + readBitsBoolean + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

readBitsBoolean

+
+
fun readBitsBoolean(raw: ByteArray, bitOffset: Int): Boolean

Reads a single bit at bitOffset as a Boolean.

+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-runtime/read-bits-long.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-runtime/read-bits-long.html new file mode 100644 index 0000000..c405f6b --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-runtime/read-bits-long.html @@ -0,0 +1,131 @@ + + + + + readBitsLong + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

readBitsLong

+
+
fun readBitsLong(raw: ByteArray, bitOffset: Int, bitWidth: Int): Long

Reads bitWidth bits (1..64) from raw starting at bitOffset, LSB-first.

+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-runtime/read-bits.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-runtime/read-bits.html new file mode 100644 index 0000000..359b618 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-runtime/read-bits.html @@ -0,0 +1,131 @@ + + + + + readBits + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

readBits

+
+
fun readBits(raw: ByteArray, bitOffset: Int, bitWidth: Int): Int

Reads bitWidth bits (1..31) from raw starting at bitOffset, LSB-first.

+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-runtime/read-bool-or-throw.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-runtime/read-bool-or-throw.html new file mode 100644 index 0000000..fcd3dcc --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-runtime/read-bool-or-throw.html @@ -0,0 +1,131 @@ + + + + + readBoolOrThrow + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

readBoolOrThrow

+
+
fun readBoolOrThrow(raw: ByteArray, bitOffset: Int): Boolean

Throws KompactDecodeException on a bounds error; otherwise reads 1 bit as a Boolean (Ticket 04).

+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-runtime/read-bool.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-runtime/read-bool.html new file mode 100644 index 0000000..ad8bb05 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-runtime/read-bool.html @@ -0,0 +1,131 @@ + + + + + readBool + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

readBool

+
+
fun readBool(raw: ByteArray, bitOffset: Int): BooleanResult

Reads 1 bit at bitOffset as a checked BooleanResult.

+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-runtime/read-double-or-throw.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-runtime/read-double-or-throw.html new file mode 100644 index 0000000..7029c31 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-runtime/read-double-or-throw.html @@ -0,0 +1,131 @@ + + + + + readDoubleOrThrow + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

readDoubleOrThrow

+
+
fun readDoubleOrThrow(raw: ByteArray, bitOffset: Int): Double

Throws KompactDecodeException on a bounds error; otherwise reads 64 bits as a Double (Ticket 04).

+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-runtime/read-double.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-runtime/read-double.html new file mode 100644 index 0000000..99f4856 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-runtime/read-double.html @@ -0,0 +1,131 @@ + + + + + readDouble + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

readDouble

+
+
fun readDouble(raw: ByteArray, bitOffset: Int): DoubleResult

Reads 64 bits at bitOffset as a checked DoubleResult. NaN is canonicalized (Ticket 04).

+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-runtime/read-float-or-throw.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-runtime/read-float-or-throw.html new file mode 100644 index 0000000..9e4ea92 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-runtime/read-float-or-throw.html @@ -0,0 +1,131 @@ + + + + + readFloatOrThrow + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

readFloatOrThrow

+
+
fun readFloatOrThrow(raw: ByteArray, bitOffset: Int): Float

Throws KompactDecodeException on a bounds error; otherwise reads 32 bits as a Float (Ticket 04).

+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-runtime/read-float.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-runtime/read-float.html new file mode 100644 index 0000000..86d9184 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-runtime/read-float.html @@ -0,0 +1,131 @@ + + + + + readFloat + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

readFloat

+
+
fun readFloat(raw: ByteArray, bitOffset: Int): FloatResult

Reads 32 bits at bitOffset as a checked FloatResult. NaN is canonicalized (Ticket 04).

+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-runtime/read-scalar-as-long-or-throw.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-runtime/read-scalar-as-long-or-throw.html new file mode 100644 index 0000000..b50f806 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-runtime/read-scalar-as-long-or-throw.html @@ -0,0 +1,131 @@ + + + + + readScalarAsLongOrThrow + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

readScalarAsLongOrThrow

+
+
fun readScalarAsLongOrThrow(raw: ByteArray, bitOffset: Int, type: ScalarType): Long

Throws KompactDecodeException on a bounds error; otherwise decodes type bits as a Long (Ticket 04).

+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-runtime/read-scalar-as-long.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-runtime/read-scalar-as-long.html new file mode 100644 index 0000000..230e3f7 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-runtime/read-scalar-as-long.html @@ -0,0 +1,131 @@ + + + + + readScalarAsLong + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

readScalarAsLong

+
+
fun readScalarAsLong(raw: ByteArray, bitOffset: Int, type: ScalarType): LongResult

Reads up to ScalarType.bitWidth bits of type as a checked LongResult (1..64). Width/signedness derive from type; sign extension (two's-complement) uses Long-arithmetic shifts. Replaces readScalarLong(w, b, signed); callers pass a ScalarType carrying the UInt64/Int64 bands (ergonomics-01: ScalarType consolidation). See readScalarAsLongOrThrow for the exceptions variant.

+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-runtime/read-scalar-or-throw.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-runtime/read-scalar-or-throw.html new file mode 100644 index 0000000..cd0d419 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-runtime/read-scalar-or-throw.html @@ -0,0 +1,131 @@ + + + + + readScalarOrThrow + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

readScalarOrThrow

+
+
fun readScalarOrThrow(raw: ByteArray, bitOffset: Int, type: ScalarType): Int

Throws KompactDecodeException on a bounds error; otherwise decodes type bits as an Int (Ticket 04).

+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-runtime/read-scalar.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-runtime/read-scalar.html new file mode 100644 index 0000000..f6c24cc --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-runtime/read-scalar.html @@ -0,0 +1,131 @@ + + + + + readScalar + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

readScalar

+
+
fun readScalar(raw: ByteArray, bitOffset: Int, type: ScalarType): IntResult

Reads up to ScalarType.bitWidth bits of type as a checked IntResult. The width (1..32) and signedness come from type, so a single accessor replaces the 8 per-width readInt8/16/32 and readUInt8/16/32 overloads (ergonomics-01: ScalarType consolidation). Sign extension uses Long-arithmetic shifts, bit-identical to the legacy accessors. Callers pass a ScalarType; see readScalarOrThrow for the exceptions variant.

+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-runtime/write-bits-boolean.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-runtime/write-bits-boolean.html new file mode 100644 index 0000000..15e0c18 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-runtime/write-bits-boolean.html @@ -0,0 +1,131 @@ + + + + + writeBitsBoolean + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

writeBitsBoolean

+
+
fun writeBitsBoolean(raw: ByteArray, bitOffset: Int, value: Boolean)

Writes value as a single bit at bitOffset.

+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-runtime/write-bits-long.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-runtime/write-bits-long.html new file mode 100644 index 0000000..2ca0748 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-runtime/write-bits-long.html @@ -0,0 +1,131 @@ + + + + + writeBitsLong + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

writeBitsLong

+
+
fun writeBitsLong(raw: ByteArray, bitOffset: Int, bitWidth: Int, value: Long)

Writes the low bitWidth bits (1..64) of value into raw at bitOffset, LSB-first.

+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-runtime/write-bits.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-runtime/write-bits.html new file mode 100644 index 0000000..8b628ba --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-runtime/write-bits.html @@ -0,0 +1,131 @@ + + + + + writeBits + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

writeBits

+
+
fun writeBits(raw: ByteArray, bitOffset: Int, bitWidth: Int, value: Int)

Writes the low bitWidth bits (1..31) of value into raw at bitOffset, LSB-first.

+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-writer/-companion/index.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-writer/-companion/index.html new file mode 100644 index 0000000..7181464 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-writer/-companion/index.html @@ -0,0 +1,135 @@ + + + + + Companion + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

Companion

+
object Companion
+
+
+
+
+
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-writer/-kompact-writer.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-writer/-kompact-writer.html new file mode 100644 index 0000000..21c592d --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-writer/-kompact-writer.html @@ -0,0 +1,131 @@ + + + + + KompactWriter + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

KompactWriter

+
+
constructor()
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-writer/build.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-writer/build.html new file mode 100644 index 0000000..aefb366 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-writer/build.html @@ -0,0 +1,131 @@ + + + + + build + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

build

+
+

Returns an exact-length snapshot of the accumulated bits. Calling afterwards is allowed but yields an empty buffer (single-shot by design).

+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-writer/index.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-writer/index.html new file mode 100644 index 0000000..f85a9fd --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-writer/index.html @@ -0,0 +1,313 @@ + + + + + KompactWriter + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

KompactWriter

+

Forward-only, growable write builder for Kompact wire output (Ticket 07).

The write path is not bound by the zero-allocation hot-path discipline (Ticket 03) — allocation/lambda overhead is acceptable here. The binary shape is a straight translation of Ticket 05's framing: fixed-width LE length prefixes, length-delimited nested sub-regions (child length computed first, then prefix + bytes — no back-patch), and count-prefixed repeats <count><elem₀><elem₁>….

build() returns an exact-length snapshot; the backing buffer is not exposed, so the writer remains single-use forward-only (PROMPT §1).

+
+
+
+
+
+

Constructors

+
+
+
+
+ + +
Link copied to clipboard
+
+
+
+
constructor()
+
+
+
+
+
+
+
+

Types

+
+
+
+
+ + +
Link copied to clipboard
+
+
+
+
object Companion
+
+
+
+
+
+
+
+

Functions

+
+
+
+
+ + +
Link copied to clipboard
+
+
+
+

Returns an exact-length snapshot of the accumulated bits. Calling afterwards is allowed but yields an empty buffer (single-shot by design).

+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
fun writeBits(bitWidth: Int, value: Int)

Appends bitWidth low bits of value (two's-complement magnitude).

+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
fun writeBitsLong(bitWidth: Int, value: Long)

Appends bitWidth low bits of value (64-bit, for UInt64/Int64).

+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
fun writeBlob(countWidth: Int, bytes: ByteArray)

Writes a length-prefixed blob: <prefix><bytes> (Ticket 05).

+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
fun writeBool(value: Boolean)

Writes a single bit (true = 1, false = 0).

+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
fun writeNested(lengthPrefixWidth: Int = 16, block: KompactWriter.() -> Unit)

Writes a nested sub-region: a child KompactWriter drains block, then the child's byte length is emitted as a lengthPrefixWidth-bit LE prefix immediately followed by the child bytes (forward-only, compute-first — Ticket 07). The child region begins byte-aligned after the prefix.

+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
fun writeRepeated(count: Int, countWidth: Int = 8, block: KompactWriter.() -> Unit)

Writes a count-prefixed repeat: <count><elem₀>…<elem_{count-1}> where each element is produced by one invocation of block against this writer (Ticket 05). countWidth must be one of KompactFraming.VALID_PREFIX_WIDTHS.

+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
fun writeScalar(type: ScalarType, value: Long)

Writes value under type: ScalarType.bitWidth low bits as a two's-complement magnitude (1..64), dispatched to writeBits (<=31) / writeBitsLong (32..64). Replaces the writeInt/writeUInt/writeInt64/writeEnum overloads — one accessor per width-band (ergonomics-01: ScalarType consolidation). Pass a ScalarType carrying the band.

+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
fun writeString(countWidth: Int, value: String)

Writes a length-prefixed UTF-8 string: <prefix><bytes> (Ticket 05).

+
+
+
+
+
+
+
+
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-writer/write-bits-long.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-writer/write-bits-long.html new file mode 100644 index 0000000..5ead464 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-writer/write-bits-long.html @@ -0,0 +1,131 @@ + + + + + writeBitsLong + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

writeBitsLong

+
+
fun writeBitsLong(bitWidth: Int, value: Long)

Appends bitWidth low bits of value (64-bit, for UInt64/Int64).

+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-writer/write-bits.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-writer/write-bits.html new file mode 100644 index 0000000..e12393e --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-writer/write-bits.html @@ -0,0 +1,131 @@ + + + + + writeBits + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

writeBits

+
+
fun writeBits(bitWidth: Int, value: Int)

Appends bitWidth low bits of value (two's-complement magnitude).

+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-writer/write-blob.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-writer/write-blob.html new file mode 100644 index 0000000..1b40e12 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-writer/write-blob.html @@ -0,0 +1,131 @@ + + + + + writeBlob + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

writeBlob

+
+
fun writeBlob(countWidth: Int, bytes: ByteArray)

Writes a length-prefixed blob: <prefix><bytes> (Ticket 05).

+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-writer/write-bool.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-writer/write-bool.html new file mode 100644 index 0000000..b6d76bf --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-writer/write-bool.html @@ -0,0 +1,131 @@ + + + + + writeBool + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

writeBool

+
+
fun writeBool(value: Boolean)

Writes a single bit (true = 1, false = 0).

+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-writer/write-nested.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-writer/write-nested.html new file mode 100644 index 0000000..167ec87 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-writer/write-nested.html @@ -0,0 +1,131 @@ + + + + + writeNested + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

writeNested

+
+
fun writeNested(lengthPrefixWidth: Int = 16, block: KompactWriter.() -> Unit)

Writes a nested sub-region: a child KompactWriter drains block, then the child's byte length is emitted as a lengthPrefixWidth-bit LE prefix immediately followed by the child bytes (forward-only, compute-first — Ticket 07). The child region begins byte-aligned after the prefix.

+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-writer/write-repeated.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-writer/write-repeated.html new file mode 100644 index 0000000..e8b253e --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-writer/write-repeated.html @@ -0,0 +1,131 @@ + + + + + writeRepeated + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

writeRepeated

+
+
fun writeRepeated(count: Int, countWidth: Int = 8, block: KompactWriter.() -> Unit)

Writes a count-prefixed repeat: <count><elem₀>…<elem_{count-1}> where each element is produced by one invocation of block against this writer (Ticket 05). countWidth must be one of KompactFraming.VALID_PREFIX_WIDTHS.

+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-writer/write-scalar.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-writer/write-scalar.html new file mode 100644 index 0000000..2a12a7c --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-writer/write-scalar.html @@ -0,0 +1,131 @@ + + + + + writeScalar + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

writeScalar

+
+
fun writeScalar(type: ScalarType, value: Long)

Writes value under type: ScalarType.bitWidth low bits as a two's-complement magnitude (1..64), dispatched to writeBits (<=31) / writeBitsLong (32..64). Replaces the writeInt/writeUInt/writeInt64/writeEnum overloads — one accessor per width-band (ergonomics-01: ScalarType consolidation). Pass a ScalarType carrying the band.

+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-writer/write-string.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-writer/write-string.html new file mode 100644 index 0000000..a4c83a4 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-kompact-writer/write-string.html @@ -0,0 +1,131 @@ + + + + + writeString + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

writeString

+
+
fun writeString(countWidth: Int, value: String)

Writes a length-prefixed UTF-8 string: <prefix><bytes> (Ticket 05).

+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-long-result/-companion/failure.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-long-result/-companion/failure.html new file mode 100644 index 0000000..a9ac280 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-long-result/-companion/failure.html @@ -0,0 +1,153 @@ + + + + + failure + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

failure

+
+
+
+
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-long-result/-companion/index.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-long-result/-companion/index.html new file mode 100644 index 0000000..ed57b10 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-long-result/-companion/index.html @@ -0,0 +1,196 @@ + + + + + Companion + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

Companion

+
+
+
expect object Companion
actual object Companion
actual object Companion
+
+
+
+
+
+

Functions

+
+
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
expect fun success(value: Long): LongResult
actual fun success(value: Long): LongResult
actual fun success(value: Long): LongResult
+
+
+
+
+
+
+
+
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-long-result/-companion/success.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-long-result/-companion/success.html new file mode 100644 index 0000000..ef3159d --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-long-result/-companion/success.html @@ -0,0 +1,153 @@ + + + + + success + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

success

+
+
+
+
expect fun success(value: Long): LongResult
actual fun success(value: Long): LongResult
actual fun success(value: Long): LongResult
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-long-result/-long-result.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-long-result/-long-result.html new file mode 100644 index 0000000..502c4d1 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-long-result/-long-result.html @@ -0,0 +1,153 @@ + + + + + LongResult + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

LongResult

+
+
+
+
expect constructor(packed: Long)
actual constructor(packed: Long)
actual constructor(packed: Long)
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-long-result/error.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-long-result/error.html new file mode 100644 index 0000000..8bbcaec --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-long-result/error.html @@ -0,0 +1,153 @@ + + + + + error + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

error

+
+
+
+
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-long-result/get-or-throw.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-long-result/get-or-throw.html new file mode 100644 index 0000000..c89d5ea --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-long-result/get-or-throw.html @@ -0,0 +1,153 @@ + + + + + getOrThrow + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

getOrThrow

+
+
+
+
expect fun getOrThrow(): Long
actual fun getOrThrow(): Long
actual fun getOrThrow(): Long
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-long-result/index.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-long-result/index.html new file mode 100644 index 0000000..68f2cf7 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-long-result/index.html @@ -0,0 +1,323 @@ + + + + + LongResult + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

LongResult

+
+
+
expect value class LongResult(val packed: Long)

Checked 64-bit integer result (Ticket 08).

Because every 64-bit Long bit-pattern is a valid signed value, success and failure cannot be distinguished without reserving a sentinel band. success therefore treats a compact range near Long.MIN_VALUE (bit 63 set with bits 62..58 clear, i.e. Long.MIN_VALUE through Long.MIN_VALUE + (1L shl 58) - 1) as the failure sentinel — these values are not representable as success. The first representable negative success value is Long.MIN_VALUE + (1L shl 58) (bit 58 set, outside the sentinel mask). This is the documented tradeoff of packing a typed result into a single Long without boxing; see Ticket 08.

actual value class LongResult(val packed: Long)

Checked 64-bit integer result (Ticket 08).

Because every 64-bit Long bit-pattern is a valid signed value, success and failure cannot be distinguished without reserving a sentinel band. success therefore treats a compact range near Long.MIN_VALUE (bit 63 set with bits 62..58 clear, i.e. Long.MIN_VALUE through Long.MIN_VALUE + (1L shl 58) - 1) as the failure sentinel — these values are not representable as success. The first representable negative success value is Long.MIN_VALUE + (1L shl 58) (bit 58 set, outside the sentinel mask). This is the documented tradeoff of packing a typed result into a single Long without boxing; see Ticket 08.

actual value class LongResult(val packed: Long)

Checked 64-bit integer result (Ticket 08).

Because every 64-bit Long bit-pattern is a valid signed value, success and failure cannot be distinguished without reserving a sentinel band. success therefore treats a compact range near Long.MIN_VALUE (bit 63 set with bits 62..58 clear, i.e. Long.MIN_VALUE through Long.MIN_VALUE + (1L shl 58) - 1) as the failure sentinel — these values are not representable as success. The first representable negative success value is Long.MIN_VALUE + (1L shl 58) (bit 58 set, outside the sentinel mask). This is the documented tradeoff of packing a typed result into a single Long without boxing; see Ticket 08.

+
+
+
+
+
+

Constructors

+
+
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
expect constructor(packed: Long)
actual constructor(packed: Long)
actual constructor(packed: Long)
+
+
+
+
+
+
+
+

Types

+
+
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
expect object Companion
actual object Companion
actual object Companion
+
+
+
+
+
+
+
+

Properties

+
+
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
expect val isFailure: Boolean
actual val isFailure: Boolean
actual val isFailure: Boolean
+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
expect val isSuccess: Boolean
actual val isSuccess: Boolean
actual val isSuccess: Boolean
+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
expect val packed: Long
actual val packed: Long
actual val packed: Long
+
+
+
+
+
+
+
+

Functions

+
+
+
+
+ + +
Link copied to clipboard
+
+
+
+
inline fun LongResult.getOrElse(fallback: (KompactDecodeError) -> Long): Long
+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
expect fun getOrThrow(): Long
actual fun getOrThrow(): Long
actual fun getOrThrow(): Long
+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
inline fun LongResult.map(transform: (Long) -> Long): LongResult
+
+
+
+
+
+
+
+
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-long-result/is-failure.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-long-result/is-failure.html new file mode 100644 index 0000000..ef93c2a --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-long-result/is-failure.html @@ -0,0 +1,153 @@ + + + + + isFailure + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

isFailure

+
+
+
+
expect val isFailure: Boolean
actual val isFailure: Boolean
actual val isFailure: Boolean
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-long-result/is-success.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-long-result/is-success.html new file mode 100644 index 0000000..92551dd --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-long-result/is-success.html @@ -0,0 +1,153 @@ + + + + + isSuccess + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

isSuccess

+
+
+
+
expect val isSuccess: Boolean
actual val isSuccess: Boolean
actual val isSuccess: Boolean
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-long-result/packed.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-long-result/packed.html new file mode 100644 index 0000000..aba7a1d --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-long-result/packed.html @@ -0,0 +1,153 @@ + + + + + packed + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

packed

+
+
+
+
expect val packed: Long
actual val packed: Long
actual val packed: Long
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-nested-region-result/-companion/failure.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-nested-region-result/-companion/failure.html new file mode 100644 index 0000000..c76aaf4 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-nested-region-result/-companion/failure.html @@ -0,0 +1,153 @@ + + + + + failure + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

failure

+
+ +
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-nested-region-result/-companion/index.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-nested-region-result/-companion/index.html new file mode 100644 index 0000000..5c25cc0 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-nested-region-result/-companion/index.html @@ -0,0 +1,196 @@ + + + + + Companion + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

Companion

+
+
+
expect object Companion
actual object Companion
actual object Companion
+
+
+
+
+
+

Functions

+
+
+
+
+ + +
Link copied to clipboard
+
+
+
+ +
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
expect fun success(startBit: Int, bitLength: Int): NestedRegionResult
actual fun success(startBit: Int, bitLength: Int): NestedRegionResult
actual fun success(startBit: Int, bitLength: Int): NestedRegionResult
+
+
+
+
+
+
+
+
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-nested-region-result/-companion/success.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-nested-region-result/-companion/success.html new file mode 100644 index 0000000..989d877 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-nested-region-result/-companion/success.html @@ -0,0 +1,153 @@ + + + + + success + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

success

+
+
+
+
expect fun success(startBit: Int, bitLength: Int): NestedRegionResult
actual fun success(startBit: Int, bitLength: Int): NestedRegionResult
actual fun success(startBit: Int, bitLength: Int): NestedRegionResult
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-nested-region-result/-nested-region-result.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-nested-region-result/-nested-region-result.html new file mode 100644 index 0000000..de1f45b --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-nested-region-result/-nested-region-result.html @@ -0,0 +1,153 @@ + + + + + NestedRegionResult + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

NestedRegionResult

+
+
+
+
expect constructor(packed: Long)
actual constructor(packed: Long)
actual constructor(packed: Long)
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-nested-region-result/bit-length.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-nested-region-result/bit-length.html new file mode 100644 index 0000000..2740db0 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-nested-region-result/bit-length.html @@ -0,0 +1,153 @@ + + + + + bitLength + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

bitLength

+
+
+
+
expect val bitLength: Int
actual val bitLength: Int
actual val bitLength: Int
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-nested-region-result/error.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-nested-region-result/error.html new file mode 100644 index 0000000..2da8f1f --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-nested-region-result/error.html @@ -0,0 +1,153 @@ + + + + + error + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

error

+
+
+
+
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-nested-region-result/get-or-throw.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-nested-region-result/get-or-throw.html new file mode 100644 index 0000000..3f141bf --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-nested-region-result/get-or-throw.html @@ -0,0 +1,153 @@ + + + + + getOrThrow + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

getOrThrow

+
+
+
+
expect fun getOrThrow(): NestedRegion
actual fun getOrThrow(): NestedRegion
actual fun getOrThrow(): NestedRegion
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-nested-region-result/index.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-nested-region-result/index.html new file mode 100644 index 0000000..dc9db1e --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-nested-region-result/index.html @@ -0,0 +1,357 @@ + + + + + NestedRegionResult + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

NestedRegionResult

+
+
+
expect value class NestedRegionResult(val packed: Long)
actual value class NestedRegionResult(val packed: Long)
actual value class NestedRegionResult(val packed: Long)
+
+
+
+
+
+

Constructors

+
+
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
expect constructor(packed: Long)
actual constructor(packed: Long)
actual constructor(packed: Long)
+
+
+
+
+
+
+
+

Types

+
+
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
expect object Companion
actual object Companion
actual object Companion
+
+
+
+
+
+
+
+

Properties

+
+
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
expect val bitLength: Int
actual val bitLength: Int
actual val bitLength: Int
+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
expect val isFailure: Boolean
actual val isFailure: Boolean
actual val isFailure: Boolean
+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
expect val isSuccess: Boolean
actual val isSuccess: Boolean
actual val isSuccess: Boolean
+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
expect val packed: Long
actual val packed: Long
actual val packed: Long
+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
expect val startBit: Int
actual val startBit: Int
actual val startBit: Int
+
+
+
+
+
+
+
+

Functions

+
+
+
+
+ + +
Link copied to clipboard
+
+ +
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
expect fun getOrThrow(): NestedRegion
actual fun getOrThrow(): NestedRegion
actual fun getOrThrow(): NestedRegion
+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+ +
+
+
+
+
+
+
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-nested-region-result/is-failure.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-nested-region-result/is-failure.html new file mode 100644 index 0000000..97e97b4 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-nested-region-result/is-failure.html @@ -0,0 +1,153 @@ + + + + + isFailure + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

isFailure

+
+
+
+
expect val isFailure: Boolean
actual val isFailure: Boolean
actual val isFailure: Boolean
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-nested-region-result/is-success.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-nested-region-result/is-success.html new file mode 100644 index 0000000..db33063 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-nested-region-result/is-success.html @@ -0,0 +1,153 @@ + + + + + isSuccess + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

isSuccess

+
+
+
+
expect val isSuccess: Boolean
actual val isSuccess: Boolean
actual val isSuccess: Boolean
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-nested-region-result/packed.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-nested-region-result/packed.html new file mode 100644 index 0000000..b64fe1a --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-nested-region-result/packed.html @@ -0,0 +1,153 @@ + + + + + packed + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

packed

+
+
+
+
expect val packed: Long
actual val packed: Long
actual val packed: Long
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-nested-region-result/start-bit.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-nested-region-result/start-bit.html new file mode 100644 index 0000000..13dfb41 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-nested-region-result/start-bit.html @@ -0,0 +1,153 @@ + + + + + startBit + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

startBit

+
+
+
+
expect val startBit: Int
actual val startBit: Int
actual val startBit: Int
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-nested-region/index.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-nested-region/index.html new file mode 100644 index 0000000..28b971a --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-nested-region/index.html @@ -0,0 +1,135 @@ + + + + + NestedRegion + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

NestedRegion

+
typealias NestedRegion = Pair<Int, Int>
+
+
+
+
+
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-scalar-type/-companion/-b-o-o-l.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-scalar-type/-companion/-b-o-o-l.html new file mode 100644 index 0000000..3d54662 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-scalar-type/-companion/-b-o-o-l.html @@ -0,0 +1,153 @@ + + + + + BOOL + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

BOOL

+
+
+
+
expect val BOOL: ScalarType
actual val BOOL: ScalarType
actual val BOOL: ScalarType
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-scalar-type/-companion/-i-n-t_16.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-scalar-type/-companion/-i-n-t_16.html new file mode 100644 index 0000000..d09c948 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-scalar-type/-companion/-i-n-t_16.html @@ -0,0 +1,153 @@ + + + + + INT_16 + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

INT_16

+
+
+
+
expect val INT_16: ScalarType
actual val INT_16: ScalarType
actual val INT_16: ScalarType
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-scalar-type/-companion/-i-n-t_32.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-scalar-type/-companion/-i-n-t_32.html new file mode 100644 index 0000000..e25c79c --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-scalar-type/-companion/-i-n-t_32.html @@ -0,0 +1,153 @@ + + + + + INT_32 + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

INT_32

+
+
+
+
expect val INT_32: ScalarType
actual val INT_32: ScalarType
actual val INT_32: ScalarType
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-scalar-type/-companion/-i-n-t_64.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-scalar-type/-companion/-i-n-t_64.html new file mode 100644 index 0000000..ddd5f2e --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-scalar-type/-companion/-i-n-t_64.html @@ -0,0 +1,153 @@ + + + + + INT_64 + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

INT_64

+
+
+
+
expect val INT_64: ScalarType
actual val INT_64: ScalarType
actual val INT_64: ScalarType
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-scalar-type/-companion/-i-n-t_8.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-scalar-type/-companion/-i-n-t_8.html new file mode 100644 index 0000000..9369447 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-scalar-type/-companion/-i-n-t_8.html @@ -0,0 +1,153 @@ + + + + + INT_8 + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

INT_8

+
+
+
+
expect val INT_8: ScalarType
actual val INT_8: ScalarType
actual val INT_8: ScalarType
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-scalar-type/-companion/-u-i-n-t_16.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-scalar-type/-companion/-u-i-n-t_16.html new file mode 100644 index 0000000..93be749 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-scalar-type/-companion/-u-i-n-t_16.html @@ -0,0 +1,153 @@ + + + + + UINT_16 + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

UINT_16

+
+
+
+
expect val UINT_16: ScalarType
actual val UINT_16: ScalarType
actual val UINT_16: ScalarType
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-scalar-type/-companion/-u-i-n-t_32.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-scalar-type/-companion/-u-i-n-t_32.html new file mode 100644 index 0000000..1074686 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-scalar-type/-companion/-u-i-n-t_32.html @@ -0,0 +1,153 @@ + + + + + UINT_32 + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

UINT_32

+
+
+
+
expect val UINT_32: ScalarType
actual val UINT_32: ScalarType
actual val UINT_32: ScalarType
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-scalar-type/-companion/-u-i-n-t_64.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-scalar-type/-companion/-u-i-n-t_64.html new file mode 100644 index 0000000..9de348f --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-scalar-type/-companion/-u-i-n-t_64.html @@ -0,0 +1,153 @@ + + + + + UINT_64 + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

UINT_64

+
+
+
+
expect val UINT_64: ScalarType
actual val UINT_64: ScalarType
actual val UINT_64: ScalarType
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-scalar-type/-companion/-u-i-n-t_8.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-scalar-type/-companion/-u-i-n-t_8.html new file mode 100644 index 0000000..fe198f6 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-scalar-type/-companion/-u-i-n-t_8.html @@ -0,0 +1,153 @@ + + + + + UINT_8 + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

UINT_8

+
+
+
+
expect val UINT_8: ScalarType
actual val UINT_8: ScalarType
actual val UINT_8: ScalarType
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-scalar-type/-companion/index.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-scalar-type/-companion/index.html new file mode 100644 index 0000000..f831b29 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-scalar-type/-companion/index.html @@ -0,0 +1,336 @@ + + + + + Companion + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

Companion

+
+
+
expect object Companion
actual object Companion
actual object Companion
+
+
+
+
+
+

Properties

+
+
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
expect val BOOL: ScalarType
actual val BOOL: ScalarType
actual val BOOL: ScalarType
+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
expect val INT_16: ScalarType
actual val INT_16: ScalarType
actual val INT_16: ScalarType
+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
expect val INT_32: ScalarType
actual val INT_32: ScalarType
actual val INT_32: ScalarType
+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
expect val INT_64: ScalarType
actual val INT_64: ScalarType
actual val INT_64: ScalarType
+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
expect val INT_8: ScalarType
actual val INT_8: ScalarType
actual val INT_8: ScalarType
+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
expect val UINT_16: ScalarType
actual val UINT_16: ScalarType
actual val UINT_16: ScalarType
+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
expect val UINT_32: ScalarType
actual val UINT_32: ScalarType
actual val UINT_32: ScalarType
+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
expect val UINT_64: ScalarType
actual val UINT_64: ScalarType
actual val UINT_64: ScalarType
+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
expect val UINT_8: ScalarType
actual val UINT_8: ScalarType
actual val UINT_8: ScalarType
+
+
+
+
+
+
+
+

Functions

+
+
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
expect fun of(bitWidth: Int, signed: Boolean): ScalarType
actual fun of(bitWidth: Int, signed: Boolean): ScalarType
actual fun of(bitWidth: Int, signed: Boolean): ScalarType
+
+
+
+
+
+
+
+
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-scalar-type/-companion/of.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-scalar-type/-companion/of.html new file mode 100644 index 0000000..055147d --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-scalar-type/-companion/of.html @@ -0,0 +1,153 @@ + + + + + of + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

of

+
+
+
+
expect fun of(bitWidth: Int, signed: Boolean): ScalarType
actual fun of(bitWidth: Int, signed: Boolean): ScalarType
actual fun of(bitWidth: Int, signed: Boolean): ScalarType
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-scalar-type/-scalar-type.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-scalar-type/-scalar-type.html new file mode 100644 index 0000000..c481b8f --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-scalar-type/-scalar-type.html @@ -0,0 +1,153 @@ + + + + + ScalarType + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

ScalarType

+
+
+
+
expect constructor(packed: Int)
actual constructor(packed: Int)
actual constructor(packed: Int)
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-scalar-type/bit-width.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-scalar-type/bit-width.html new file mode 100644 index 0000000..ac1ff53 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-scalar-type/bit-width.html @@ -0,0 +1,153 @@ + + + + + bitWidth + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

bitWidth

+
+
+
+
expect val bitWidth: Int
actual val bitWidth: Int
actual val bitWidth: Int
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-scalar-type/index.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-scalar-type/index.html new file mode 100644 index 0000000..5ec5722 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-scalar-type/index.html @@ -0,0 +1,255 @@ + + + + + ScalarType + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

ScalarType

+
+
+
expect value class ScalarType(val packed: Int)
actual value class ScalarType(val packed: Int)
actual value class ScalarType(val packed: Int)
+
+
+
+
+
+

Constructors

+
+
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
expect constructor(packed: Int)
actual constructor(packed: Int)
actual constructor(packed: Int)
+
+
+
+
+
+
+
+

Types

+
+
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
expect object Companion
actual object Companion
actual object Companion
+
+
+
+
+
+
+
+

Properties

+
+
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
expect val bitWidth: Int
actual val bitWidth: Int
actual val bitWidth: Int
+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
expect val packed: Int
actual val packed: Int
actual val packed: Int
+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
expect val signed: Boolean
actual val signed: Boolean
actual val signed: Boolean
+
+
+
+
+
+
+
+
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-scalar-type/packed.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-scalar-type/packed.html new file mode 100644 index 0000000..6abc483 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-scalar-type/packed.html @@ -0,0 +1,153 @@ + + + + + packed + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

packed

+
+
+
+
expect val packed: Int
actual val packed: Int
actual val packed: Int
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-scalar-type/signed.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-scalar-type/signed.html new file mode 100644 index 0000000..1558fb2 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-scalar-type/signed.html @@ -0,0 +1,153 @@ + + + + + signed + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

signed

+
+
+
+
expect val signed: Boolean
actual val signed: Boolean
actual val signed: Boolean
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-short-result/-companion/failure.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-short-result/-companion/failure.html new file mode 100644 index 0000000..faf6134 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-short-result/-companion/failure.html @@ -0,0 +1,153 @@ + + + + + failure + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

failure

+
+
+
+
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-short-result/-companion/index.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-short-result/-companion/index.html new file mode 100644 index 0000000..475c5f3 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-short-result/-companion/index.html @@ -0,0 +1,196 @@ + + + + + Companion + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

Companion

+
+
+
expect object Companion
actual object Companion
actual object Companion
+
+
+
+
+
+

Functions

+
+
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
expect fun success(value: Short): ShortResult
actual fun success(value: Short): ShortResult
actual fun success(value: Short): ShortResult
+
+
+
+
+
+
+
+
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-short-result/-companion/success.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-short-result/-companion/success.html new file mode 100644 index 0000000..f0158e5 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-short-result/-companion/success.html @@ -0,0 +1,153 @@ + + + + + success + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

success

+
+
+
+
expect fun success(value: Short): ShortResult
actual fun success(value: Short): ShortResult
actual fun success(value: Short): ShortResult
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-short-result/-short-result.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-short-result/-short-result.html new file mode 100644 index 0000000..39e6e90 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-short-result/-short-result.html @@ -0,0 +1,153 @@ + + + + + ShortResult + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

ShortResult

+
+
+
+
expect constructor(packed: Long)
actual constructor(packed: Long)
actual constructor(packed: Long)
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-short-result/error.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-short-result/error.html new file mode 100644 index 0000000..6c07d00 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-short-result/error.html @@ -0,0 +1,153 @@ + + + + + error + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

error

+
+
+
+
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-short-result/get-or-throw.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-short-result/get-or-throw.html new file mode 100644 index 0000000..6929c10 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-short-result/get-or-throw.html @@ -0,0 +1,153 @@ + + + + + getOrThrow + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

getOrThrow

+
+
+
+
expect fun getOrThrow(): Short
actual fun getOrThrow(): Short
actual fun getOrThrow(): Short
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-short-result/index.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-short-result/index.html new file mode 100644 index 0000000..20cc5b7 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-short-result/index.html @@ -0,0 +1,323 @@ + + + + + ShortResult + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

ShortResult

+
+
+
expect value class ShortResult(val packed: Long)
actual value class ShortResult(val packed: Long)
actual value class ShortResult(val packed: Long)
+
+
+
+
+
+

Constructors

+
+
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
expect constructor(packed: Long)
actual constructor(packed: Long)
actual constructor(packed: Long)
+
+
+
+
+
+
+
+

Types

+
+
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
expect object Companion
actual object Companion
actual object Companion
+
+
+
+
+
+
+
+

Properties

+
+
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
expect val isFailure: Boolean
actual val isFailure: Boolean
actual val isFailure: Boolean
+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
expect val isSuccess: Boolean
actual val isSuccess: Boolean
actual val isSuccess: Boolean
+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
expect val packed: Long
actual val packed: Long
actual val packed: Long
+
+
+
+
+
+
+
+

Functions

+
+
+
+
+ + +
Link copied to clipboard
+
+
+
+
inline fun ShortResult.getOrElse(fallback: (KompactDecodeError) -> Short): Short
+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
expect fun getOrThrow(): Short
actual fun getOrThrow(): Short
actual fun getOrThrow(): Short
+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
inline fun ShortResult.map(transform: (Short) -> Short): ShortResult
+
+
+
+
+
+
+
+
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-short-result/is-failure.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-short-result/is-failure.html new file mode 100644 index 0000000..16d9191 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-short-result/is-failure.html @@ -0,0 +1,153 @@ + + + + + isFailure + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

isFailure

+
+
+
+
expect val isFailure: Boolean
actual val isFailure: Boolean
actual val isFailure: Boolean
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-short-result/is-success.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-short-result/is-success.html new file mode 100644 index 0000000..003f41d --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-short-result/is-success.html @@ -0,0 +1,153 @@ + + + + + isSuccess + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

isSuccess

+
+
+
+
expect val isSuccess: Boolean
actual val isSuccess: Boolean
actual val isSuccess: Boolean
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-short-result/packed.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-short-result/packed.html new file mode 100644 index 0000000..ea24f52 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/-short-result/packed.html @@ -0,0 +1,153 @@ + + + + + packed + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

packed

+
+
+
+
expect val packed: Long
actual val packed: Long
actual val packed: Long
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/get-or-else.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/get-or-else.html new file mode 100644 index 0000000..c28e54b --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/get-or-else.html @@ -0,0 +1,131 @@ + + + + + getOrElse + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

getOrElse

+
+ +
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/index.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/index.html new file mode 100644 index 0000000..25d0e82 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/index.html @@ -0,0 +1,436 @@ + + + + + ch.trancee.kompact.runtime + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

Package-level declarations

+
+
+
+
+
+

Types

+
+
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
expect value class BooleanResult(val packed: Long)
actual value class BooleanResult(val packed: Long)
actual value class BooleanResult(val packed: Long)
+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
expect value class ByteResult(val packed: Long)
actual value class ByteResult(val packed: Long)
actual value class ByteResult(val packed: Long)
+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
expect value class DoubleResult(val packed: Long)
actual value class DoubleResult(val packed: Long)
actual value class DoubleResult(val packed: Long)
+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
expect value class FloatResult(val packed: Long)
actual value class FloatResult(val packed: Long)
actual value class FloatResult(val packed: Long)
+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
expect value class IntResult(val packed: Long)
actual value class IntResult(val packed: Long)
actual value class IntResult(val packed: Long)
+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
sealed class KompactDecodeError

Runtime decode error taxonomy (Ticket 06).

+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+

Thrown by getOrThrow() / readOrThrow() on the failure path. The success hot-path never throws (Ticket 03 zero-alloc). Allocation of this exception is acceptable because it only occurs on an explicit recovery call.

+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+

Sequential, length-delimited framing (Ticket 05) and repeat/count handling.

+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+

Bit-stream primitives for Kompact.

+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+

Forward-only, growable write builder for Kompact wire output (Ticket 07).

+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
expect value class LongResult(val packed: Long)

Checked 64-bit integer result (Ticket 08).

actual value class LongResult(val packed: Long)

Checked 64-bit integer result (Ticket 08).

actual value class LongResult(val packed: Long)

Checked 64-bit integer result (Ticket 08).

+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
typealias NestedRegion = Pair<Int, Int>
+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
expect value class NestedRegionResult(val packed: Long)
actual value class NestedRegionResult(val packed: Long)
actual value class NestedRegionResult(val packed: Long)
+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
expect value class ScalarType(val packed: Int)
actual value class ScalarType(val packed: Int)
actual value class ScalarType(val packed: Int)
+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
+
+
expect value class ShortResult(val packed: Long)
actual value class ShortResult(val packed: Long)
actual value class ShortResult(val packed: Long)
+
+
+
+
+
+
+
+

Functions

+
+
+
+
+ + +
Link copied to clipboard
+
+ +
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
inline fun BooleanResult.map(transform: (Boolean) -> Boolean): BooleanResult
inline fun ByteResult.map(transform: (Byte) -> Byte): ByteResult
inline fun DoubleResult.map(transform: (Double) -> Double): DoubleResult
inline fun FloatResult.map(transform: (Float) -> Float): FloatResult
inline fun IntResult.map(transform: (Int) -> Int): IntResult
inline fun LongResult.map(transform: (Long) -> Long): LongResult
inline fun ShortResult.map(transform: (Short) -> Short): ShortResult
+
+
+
+
+
+
+
+
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact.runtime/map.html b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/map.html new file mode 100644 index 0000000..1165958 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact.runtime/map.html @@ -0,0 +1,131 @@ + + + + + map + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

map

+
+
inline fun ByteResult.map(transform: (Byte) -> Byte): ByteResult
inline fun ShortResult.map(transform: (Short) -> Short): ShortResult
inline fun IntResult.map(transform: (Int) -> Int): IntResult
inline fun LongResult.map(transform: (Long) -> Long): LongResult
inline fun FloatResult.map(transform: (Float) -> Float): FloatResult
inline fun DoubleResult.map(transform: (Double) -> Double): DoubleResult
inline fun BooleanResult.map(transform: (Boolean) -> Boolean): BooleanResult
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact/-kompact/-result/-boolean/index.html b/kompact/docs/api/kompact/ch.trancee.kompact/-kompact/-result/-boolean/index.html new file mode 100644 index 0000000..db35aee --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact/-kompact/-result/-boolean/index.html @@ -0,0 +1,135 @@ + + + + + Boolean + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

Boolean

+ +
+
+
+
+
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact/-kompact/-result/-byte/index.html b/kompact/docs/api/kompact/ch.trancee.kompact/-kompact/-result/-byte/index.html new file mode 100644 index 0000000..b2006b6 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact/-kompact/-result/-byte/index.html @@ -0,0 +1,135 @@ + + + + + Byte + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

Byte

+
typealias Byte = ByteResult
+
+
+
+
+
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact/-kompact/-result/-double/index.html b/kompact/docs/api/kompact/ch.trancee.kompact/-kompact/-result/-double/index.html new file mode 100644 index 0000000..a3652b2 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact/-kompact/-result/-double/index.html @@ -0,0 +1,135 @@ + + + + + Double + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

Double

+
typealias Double = DoubleResult
+
+
+
+
+
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact/-kompact/-result/-float/index.html b/kompact/docs/api/kompact/ch.trancee.kompact/-kompact/-result/-float/index.html new file mode 100644 index 0000000..57317f9 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact/-kompact/-result/-float/index.html @@ -0,0 +1,135 @@ + + + + + Float + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

Float

+
typealias Float = FloatResult
+
+
+
+
+
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact/-kompact/-result/-int/index.html b/kompact/docs/api/kompact/ch.trancee.kompact/-kompact/-result/-int/index.html new file mode 100644 index 0000000..10012c2 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact/-kompact/-result/-int/index.html @@ -0,0 +1,135 @@ + + + + + Int + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

Int

+
typealias Int = IntResult
+
+
+
+
+
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact/-kompact/-result/-long/index.html b/kompact/docs/api/kompact/ch.trancee.kompact/-kompact/-result/-long/index.html new file mode 100644 index 0000000..8ab31df --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact/-kompact/-result/-long/index.html @@ -0,0 +1,135 @@ + + + + + Long + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

Long

+
typealias Long = LongResult
+
+
+
+
+
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact/-kompact/-result/-short/index.html b/kompact/docs/api/kompact/ch.trancee.kompact/-kompact/-result/-short/index.html new file mode 100644 index 0000000..d64a2b4 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact/-kompact/-result/-short/index.html @@ -0,0 +1,135 @@ + + + + + Short + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

Short

+
typealias Short = ShortResult
+
+
+
+
+
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact/-kompact/-result/index.html b/kompact/docs/api/kompact/ch.trancee.kompact/-kompact/-result/index.html new file mode 100644 index 0000000..5237de4 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact/-kompact/-result/index.html @@ -0,0 +1,245 @@ + + + + + Result + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

Result

+
object Result
+
+
+
+
+
+

Types

+
+
+
+
+ + +
Link copied to clipboard
+
+
+
+ +
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
typealias Byte = ByteResult
+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
typealias Double = DoubleResult
+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
typealias Float = FloatResult
+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
typealias Int = IntResult
+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
typealias Long = LongResult
+
+
+
+
+ +
+
+
+ + +
Link copied to clipboard
+
+
+
+
typealias Short = ShortResult
+
+
+
+
+
+
+
+
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact/-kompact/index.html b/kompact/docs/api/kompact/ch.trancee.kompact/-kompact/index.html new file mode 100644 index 0000000..95745e9 --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact/-kompact/index.html @@ -0,0 +1,155 @@ + + + + + Kompact + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

Kompact

+
object Kompact

Top-level namespace for the Kompact runtime.

Result re-exports the seven specialized typed-result value classes under one import path — Kompact.Result.Int, Kompact.Result.Boolean, … — so a newcomer can import ch.trancee.kompact.Kompact instead of naming all seven result types. The seven top-level declarations stay; this is purely a one-stop re-export (additive; zero-alloc on the success path).

See KompactResult.kt for the packed-Long encodings of each result kind.

+
+
+
+
+
+

Types

+
+
+
+
+ + +
Link copied to clipboard
+
+
+
+
object Result
+
+
+
+
+
+
+
+
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/ch.trancee.kompact/index.html b/kompact/docs/api/kompact/ch.trancee.kompact/index.html new file mode 100644 index 0000000..bb2215f --- /dev/null +++ b/kompact/docs/api/kompact/ch.trancee.kompact/index.html @@ -0,0 +1,154 @@ + + + + + ch.trancee.kompact + + + + + + + + + + + + + + + + + + +
+ +
+ +
+
+
+ +
+

Package-level declarations

+
+
+
+
+
+

Types

+
+
+
+
+ + +
Link copied to clipboard
+
+
+
+
object Kompact

Top-level namespace for the Kompact runtime.

+
+
+
+
+
+
+
+
+
+ +
+
+
+ + \ No newline at end of file diff --git a/kompact/docs/api/kompact/package-list b/kompact/docs/api/kompact/package-list new file mode 100644 index 0000000..e7ed2d7 --- /dev/null +++ b/kompact/docs/api/kompact/package-list @@ -0,0 +1,202 @@ +$dokka.format:html-v1 +$dokka.linkExtension:html +$dokka.location:ch.trancee.kompact.annotations////PointingToDeclaration/kompact/ch.trancee.kompact.annotations/index.html +$dokka.location:ch.trancee.kompact.annotations/KompactField///PointingToDeclaration/kompact/ch.trancee.kompact.annotations/-kompact-field/index.html +$dokka.location:ch.trancee.kompact.annotations/KompactField/bitOffset/#/PointingToDeclaration/kompact/ch.trancee.kompact.annotations/-kompact-field/bit-offset.html +$dokka.location:ch.trancee.kompact.annotations/KompactField/bitWidth/#/PointingToDeclaration/kompact/ch.trancee.kompact.annotations/-kompact-field/bit-width.html +$dokka.location:ch.trancee.kompact.annotations/KompactField/defaultValue/#/PointingToDeclaration/kompact/ch.trancee.kompact.annotations/-kompact-field/default-value.html +$dokka.location:ch.trancee.kompact.annotations/KompactField/enumWidth/#/PointingToDeclaration/kompact/ch.trancee.kompact.annotations/-kompact-field/enum-width.html +$dokka.location:ch.trancee.kompact.annotations/KompactField/isNested/#/PointingToDeclaration/kompact/ch.trancee.kompact.annotations/-kompact-field/is-nested.html +$dokka.location:ch.trancee.kompact.annotations/KompactField/isVersionField/#/PointingToDeclaration/kompact/ch.trancee.kompact.annotations/-kompact-field/is-version-field.html +$dokka.location:ch.trancee.kompact.annotations/KompactField/lengthPrefixWidth/#/PointingToDeclaration/kompact/ch.trancee.kompact.annotations/-kompact-field/length-prefix-width.html +$dokka.location:ch.trancee.kompact.annotations/KompactField/repeatCountWidth/#/PointingToDeclaration/kompact/ch.trancee.kompact.annotations/-kompact-field/repeat-count-width.html +$dokka.location:ch.trancee.kompact.annotations/KompactField/signed/#/PointingToDeclaration/kompact/ch.trancee.kompact.annotations/-kompact-field/signed.html +$dokka.location:ch.trancee.kompact.annotations/KompactModel///PointingToDeclaration/kompact/ch.trancee.kompact.annotations/-kompact-model/index.html +$dokka.location:ch.trancee.kompact.annotations/KompactPreview///PointingToDeclaration/kompact/ch.trancee.kompact.annotations/-kompact-preview/index.html +$dokka.location:ch.trancee.kompact.generated////PointingToDeclaration/kompact/ch.trancee.kompact.generated/index.html +$dokka.location:ch.trancee.kompact.generated/VehicleTelemetry.Companion///PointingToDeclaration/kompact/ch.trancee.kompact.generated/-vehicle-telemetry/-companion/index.html +$dokka.location:ch.trancee.kompact.generated/VehicleTelemetry.Companion/create/#kotlin.Int#kotlin.Int#kotlin.Boolean/PointingToDeclaration/kompact/ch.trancee.kompact.generated/-vehicle-telemetry/-companion/create.html +$dokka.location:ch.trancee.kompact.generated/VehicleTelemetry///PointingToDeclaration/kompact/ch.trancee.kompact.generated/-vehicle-telemetry/index.html +$dokka.location:ch.trancee.kompact.generated/VehicleTelemetry/VehicleTelemetry/#kotlin.ByteArray/PointingToDeclaration/kompact/ch.trancee.kompact.generated/-vehicle-telemetry/-vehicle-telemetry.html +$dokka.location:ch.trancee.kompact.generated/VehicleTelemetry/batteryStatus/#/PointingToDeclaration/kompact/ch.trancee.kompact.generated/-vehicle-telemetry/battery-status.html +$dokka.location:ch.trancee.kompact.generated/VehicleTelemetry/isMalfunctioning/#/PointingToDeclaration/kompact/ch.trancee.kompact.generated/-vehicle-telemetry/is-malfunctioning.html +$dokka.location:ch.trancee.kompact.generated/VehicleTelemetry/raw/#/PointingToDeclaration/kompact/ch.trancee.kompact.generated/-vehicle-telemetry/raw.html +$dokka.location:ch.trancee.kompact.generated/VehicleTelemetry/speed/#/PointingToDeclaration/kompact/ch.trancee.kompact.generated/-vehicle-telemetry/speed.html +$dokka.location:ch.trancee.kompact.runtime////PointingToDeclaration/kompact/ch.trancee.kompact.runtime/index.html +$dokka.location:ch.trancee.kompact.runtime//getOrElse/ch.trancee.kompact.runtime.BooleanResult#kotlin.Function1[ch.trancee.kompact.runtime.KompactDecodeError,kotlin.Boolean]/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/get-or-else.html +$dokka.location:ch.trancee.kompact.runtime//getOrElse/ch.trancee.kompact.runtime.ByteResult#kotlin.Function1[ch.trancee.kompact.runtime.KompactDecodeError,kotlin.Byte]/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/get-or-else.html +$dokka.location:ch.trancee.kompact.runtime//getOrElse/ch.trancee.kompact.runtime.DoubleResult#kotlin.Function1[ch.trancee.kompact.runtime.KompactDecodeError,kotlin.Double]/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/get-or-else.html +$dokka.location:ch.trancee.kompact.runtime//getOrElse/ch.trancee.kompact.runtime.FloatResult#kotlin.Function1[ch.trancee.kompact.runtime.KompactDecodeError,kotlin.Float]/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/get-or-else.html +$dokka.location:ch.trancee.kompact.runtime//getOrElse/ch.trancee.kompact.runtime.IntResult#kotlin.Function1[ch.trancee.kompact.runtime.KompactDecodeError,kotlin.Int]/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/get-or-else.html +$dokka.location:ch.trancee.kompact.runtime//getOrElse/ch.trancee.kompact.runtime.LongResult#kotlin.Function1[ch.trancee.kompact.runtime.KompactDecodeError,kotlin.Long]/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/get-or-else.html +$dokka.location:ch.trancee.kompact.runtime//getOrElse/ch.trancee.kompact.runtime.NestedRegionResult#kotlin.Function1[ch.trancee.kompact.runtime.KompactDecodeError,kotlin.Pair[kotlin.Int,kotlin.Int]]/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/get-or-else.html +$dokka.location:ch.trancee.kompact.runtime//getOrElse/ch.trancee.kompact.runtime.ShortResult#kotlin.Function1[ch.trancee.kompact.runtime.KompactDecodeError,kotlin.Short]/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/get-or-else.html +$dokka.location:ch.trancee.kompact.runtime//map/ch.trancee.kompact.runtime.BooleanResult#kotlin.Function1[kotlin.Boolean,kotlin.Boolean]/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/map.html +$dokka.location:ch.trancee.kompact.runtime//map/ch.trancee.kompact.runtime.ByteResult#kotlin.Function1[kotlin.Byte,kotlin.Byte]/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/map.html +$dokka.location:ch.trancee.kompact.runtime//map/ch.trancee.kompact.runtime.DoubleResult#kotlin.Function1[kotlin.Double,kotlin.Double]/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/map.html +$dokka.location:ch.trancee.kompact.runtime//map/ch.trancee.kompact.runtime.FloatResult#kotlin.Function1[kotlin.Float,kotlin.Float]/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/map.html +$dokka.location:ch.trancee.kompact.runtime//map/ch.trancee.kompact.runtime.IntResult#kotlin.Function1[kotlin.Int,kotlin.Int]/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/map.html +$dokka.location:ch.trancee.kompact.runtime//map/ch.trancee.kompact.runtime.LongResult#kotlin.Function1[kotlin.Long,kotlin.Long]/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/map.html +$dokka.location:ch.trancee.kompact.runtime//map/ch.trancee.kompact.runtime.NestedRegionResult#kotlin.Function1[kotlin.Pair[kotlin.Int,kotlin.Int],kotlin.Pair[kotlin.Int,kotlin.Int]]/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/map.html +$dokka.location:ch.trancee.kompact.runtime//map/ch.trancee.kompact.runtime.ShortResult#kotlin.Function1[kotlin.Short,kotlin.Short]/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/map.html +$dokka.location:ch.trancee.kompact.runtime/BooleanResult.Companion///PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-boolean-result/-companion/index.html +$dokka.location:ch.trancee.kompact.runtime/BooleanResult.Companion/failure/#ch.trancee.kompact.runtime.KompactDecodeError/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-boolean-result/-companion/failure.html +$dokka.location:ch.trancee.kompact.runtime/BooleanResult.Companion/success/#kotlin.Boolean/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-boolean-result/-companion/success.html +$dokka.location:ch.trancee.kompact.runtime/BooleanResult///PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-boolean-result/index.html +$dokka.location:ch.trancee.kompact.runtime/BooleanResult/BooleanResult/#kotlin.Long/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-boolean-result/-boolean-result.html +$dokka.location:ch.trancee.kompact.runtime/BooleanResult/error/#/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-boolean-result/error.html +$dokka.location:ch.trancee.kompact.runtime/BooleanResult/getOrThrow/#/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-boolean-result/get-or-throw.html +$dokka.location:ch.trancee.kompact.runtime/BooleanResult/isFailure/#/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-boolean-result/is-failure.html +$dokka.location:ch.trancee.kompact.runtime/BooleanResult/isSuccess/#/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-boolean-result/is-success.html +$dokka.location:ch.trancee.kompact.runtime/BooleanResult/packed/#/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-boolean-result/packed.html +$dokka.location:ch.trancee.kompact.runtime/ByteResult.Companion///PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-byte-result/-companion/index.html +$dokka.location:ch.trancee.kompact.runtime/ByteResult.Companion/failure/#ch.trancee.kompact.runtime.KompactDecodeError/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-byte-result/-companion/failure.html +$dokka.location:ch.trancee.kompact.runtime/ByteResult.Companion/success/#kotlin.Byte/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-byte-result/-companion/success.html +$dokka.location:ch.trancee.kompact.runtime/ByteResult///PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-byte-result/index.html +$dokka.location:ch.trancee.kompact.runtime/ByteResult/ByteResult/#kotlin.Long/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-byte-result/-byte-result.html +$dokka.location:ch.trancee.kompact.runtime/ByteResult/error/#/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-byte-result/error.html +$dokka.location:ch.trancee.kompact.runtime/ByteResult/getOrThrow/#/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-byte-result/get-or-throw.html +$dokka.location:ch.trancee.kompact.runtime/ByteResult/isFailure/#/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-byte-result/is-failure.html +$dokka.location:ch.trancee.kompact.runtime/ByteResult/isSuccess/#/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-byte-result/is-success.html +$dokka.location:ch.trancee.kompact.runtime/ByteResult/packed/#/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-byte-result/packed.html +$dokka.location:ch.trancee.kompact.runtime/DoubleResult.Companion///PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-double-result/-companion/index.html +$dokka.location:ch.trancee.kompact.runtime/DoubleResult.Companion/failure/#ch.trancee.kompact.runtime.KompactDecodeError/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-double-result/-companion/failure.html +$dokka.location:ch.trancee.kompact.runtime/DoubleResult.Companion/success/#kotlin.Double/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-double-result/-companion/success.html +$dokka.location:ch.trancee.kompact.runtime/DoubleResult///PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-double-result/index.html +$dokka.location:ch.trancee.kompact.runtime/DoubleResult/DoubleResult/#kotlin.Long/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-double-result/-double-result.html +$dokka.location:ch.trancee.kompact.runtime/DoubleResult/error/#/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-double-result/error.html +$dokka.location:ch.trancee.kompact.runtime/DoubleResult/getOrThrow/#/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-double-result/get-or-throw.html +$dokka.location:ch.trancee.kompact.runtime/DoubleResult/isFailure/#/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-double-result/is-failure.html +$dokka.location:ch.trancee.kompact.runtime/DoubleResult/isSuccess/#/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-double-result/is-success.html +$dokka.location:ch.trancee.kompact.runtime/DoubleResult/packed/#/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-double-result/packed.html +$dokka.location:ch.trancee.kompact.runtime/FloatResult.Companion///PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-float-result/-companion/index.html +$dokka.location:ch.trancee.kompact.runtime/FloatResult.Companion/failure/#ch.trancee.kompact.runtime.KompactDecodeError/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-float-result/-companion/failure.html +$dokka.location:ch.trancee.kompact.runtime/FloatResult.Companion/success/#kotlin.Float/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-float-result/-companion/success.html +$dokka.location:ch.trancee.kompact.runtime/FloatResult///PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-float-result/index.html +$dokka.location:ch.trancee.kompact.runtime/FloatResult/FloatResult/#kotlin.Long/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-float-result/-float-result.html +$dokka.location:ch.trancee.kompact.runtime/FloatResult/error/#/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-float-result/error.html +$dokka.location:ch.trancee.kompact.runtime/FloatResult/getOrThrow/#/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-float-result/get-or-throw.html +$dokka.location:ch.trancee.kompact.runtime/FloatResult/isFailure/#/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-float-result/is-failure.html +$dokka.location:ch.trancee.kompact.runtime/FloatResult/isSuccess/#/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-float-result/is-success.html +$dokka.location:ch.trancee.kompact.runtime/FloatResult/packed/#/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-float-result/packed.html +$dokka.location:ch.trancee.kompact.runtime/IntResult.Companion///PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-int-result/-companion/index.html +$dokka.location:ch.trancee.kompact.runtime/IntResult.Companion/failure/#ch.trancee.kompact.runtime.KompactDecodeError/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-int-result/-companion/failure.html +$dokka.location:ch.trancee.kompact.runtime/IntResult.Companion/success/#kotlin.Int/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-int-result/-companion/success.html +$dokka.location:ch.trancee.kompact.runtime/IntResult///PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-int-result/index.html +$dokka.location:ch.trancee.kompact.runtime/IntResult/IntResult/#kotlin.Long/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-int-result/-int-result.html +$dokka.location:ch.trancee.kompact.runtime/IntResult/error/#/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-int-result/error.html +$dokka.location:ch.trancee.kompact.runtime/IntResult/getOrThrow/#/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-int-result/get-or-throw.html +$dokka.location:ch.trancee.kompact.runtime/IntResult/isFailure/#/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-int-result/is-failure.html +$dokka.location:ch.trancee.kompact.runtime/IntResult/isSuccess/#/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-int-result/is-success.html +$dokka.location:ch.trancee.kompact.runtime/IntResult/packed/#/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-int-result/packed.html +$dokka.location:ch.trancee.kompact.runtime/KompactDecodeError.BadLengthPrefix///PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-kompact-decode-error/-bad-length-prefix/index.html +$dokka.location:ch.trancee.kompact.runtime/KompactDecodeError.BoundsError///PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-kompact-decode-error/-bounds-error/index.html +$dokka.location:ch.trancee.kompact.runtime/KompactDecodeError.TruncatedNested///PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-kompact-decode-error/-truncated-nested/index.html +$dokka.location:ch.trancee.kompact.runtime/KompactDecodeError.UnknownEnumCode///PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-kompact-decode-error/-unknown-enum-code/index.html +$dokka.location:ch.trancee.kompact.runtime/KompactDecodeError.UnknownEnumCode/UnknownEnumCode/#kotlin.Int/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-kompact-decode-error/-unknown-enum-code/-unknown-enum-code.html +$dokka.location:ch.trancee.kompact.runtime/KompactDecodeError.UnknownEnumCode/rawCode/#/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-kompact-decode-error/-unknown-enum-code/raw-code.html +$dokka.location:ch.trancee.kompact.runtime/KompactDecodeError///PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-kompact-decode-error/index.html +$dokka.location:ch.trancee.kompact.runtime/KompactDecodeException///PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-kompact-decode-exception/index.html +$dokka.location:ch.trancee.kompact.runtime/KompactDecodeException/KompactDecodeException/#ch.trancee.kompact.runtime.KompactDecodeError/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-kompact-decode-exception/-kompact-decode-exception.html +$dokka.location:ch.trancee.kompact.runtime/KompactDecodeException/error/#/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-kompact-decode-exception/error.html +$dokka.location:ch.trancee.kompact.runtime/KompactFraming///PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-kompact-framing/index.html +$dokka.location:ch.trancee.kompact.runtime/KompactFraming/INVALID_LENGTH_PREFIX/#/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-kompact-framing/-i-n-v-a-l-i-d_-l-e-n-g-t-h_-p-r-e-f-i-x.html +$dokka.location:ch.trancee.kompact.runtime/KompactFraming/VALID_PREFIX_WIDTHS/#/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-kompact-framing/-v-a-l-i-d_-p-r-e-f-i-x_-w-i-d-t-h-s.html +$dokka.location:ch.trancee.kompact.runtime/KompactFraming/readLengthPrefix/#kotlin.ByteArray#kotlin.Int#kotlin.Int/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-kompact-framing/read-length-prefix.html +$dokka.location:ch.trancee.kompact.runtime/KompactFraming/readLengthPrefixOrThrow/#kotlin.ByteArray#kotlin.Int#kotlin.Int/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-kompact-framing/read-length-prefix-or-throw.html +$dokka.location:ch.trancee.kompact.runtime/KompactFraming/readNested/#kotlin.ByteArray#kotlin.Int#kotlin.Int/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-kompact-framing/read-nested.html +$dokka.location:ch.trancee.kompact.runtime/KompactFraming/readNestedOrThrow/#kotlin.ByteArray#kotlin.Int#kotlin.Int/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-kompact-framing/read-nested-or-throw.html +$dokka.location:ch.trancee.kompact.runtime/KompactFraming/writeLengthPrefix/#kotlin.ByteArray#kotlin.Int#kotlin.Int#kotlin.Int/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-kompact-framing/write-length-prefix.html +$dokka.location:ch.trancee.kompact.runtime/KompactRuntime///PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-kompact-runtime/index.html +$dokka.location:ch.trancee.kompact.runtime/KompactRuntime/fits/#kotlin.ByteArray#kotlin.Int#kotlin.Int/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-kompact-runtime/fits.html +$dokka.location:ch.trancee.kompact.runtime/KompactRuntime/readBits/#kotlin.ByteArray#kotlin.Int#kotlin.Int/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-kompact-runtime/read-bits.html +$dokka.location:ch.trancee.kompact.runtime/KompactRuntime/readBitsBoolean/#kotlin.ByteArray#kotlin.Int/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-kompact-runtime/read-bits-boolean.html +$dokka.location:ch.trancee.kompact.runtime/KompactRuntime/readBitsLong/#kotlin.ByteArray#kotlin.Int#kotlin.Int/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-kompact-runtime/read-bits-long.html +$dokka.location:ch.trancee.kompact.runtime/KompactRuntime/readBool/#kotlin.ByteArray#kotlin.Int/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-kompact-runtime/read-bool.html +$dokka.location:ch.trancee.kompact.runtime/KompactRuntime/readBoolOrThrow/#kotlin.ByteArray#kotlin.Int/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-kompact-runtime/read-bool-or-throw.html +$dokka.location:ch.trancee.kompact.runtime/KompactRuntime/readDouble/#kotlin.ByteArray#kotlin.Int/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-kompact-runtime/read-double.html +$dokka.location:ch.trancee.kompact.runtime/KompactRuntime/readDoubleOrThrow/#kotlin.ByteArray#kotlin.Int/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-kompact-runtime/read-double-or-throw.html +$dokka.location:ch.trancee.kompact.runtime/KompactRuntime/readFloat/#kotlin.ByteArray#kotlin.Int/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-kompact-runtime/read-float.html +$dokka.location:ch.trancee.kompact.runtime/KompactRuntime/readFloatOrThrow/#kotlin.ByteArray#kotlin.Int/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-kompact-runtime/read-float-or-throw.html +$dokka.location:ch.trancee.kompact.runtime/KompactRuntime/readScalar/#kotlin.ByteArray#kotlin.Int#ch.trancee.kompact.runtime.ScalarType/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-kompact-runtime/read-scalar.html +$dokka.location:ch.trancee.kompact.runtime/KompactRuntime/readScalarAsLong/#kotlin.ByteArray#kotlin.Int#ch.trancee.kompact.runtime.ScalarType/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-kompact-runtime/read-scalar-as-long.html +$dokka.location:ch.trancee.kompact.runtime/KompactRuntime/readScalarAsLongOrThrow/#kotlin.ByteArray#kotlin.Int#ch.trancee.kompact.runtime.ScalarType/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-kompact-runtime/read-scalar-as-long-or-throw.html +$dokka.location:ch.trancee.kompact.runtime/KompactRuntime/readScalarOrThrow/#kotlin.ByteArray#kotlin.Int#ch.trancee.kompact.runtime.ScalarType/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-kompact-runtime/read-scalar-or-throw.html +$dokka.location:ch.trancee.kompact.runtime/KompactRuntime/writeBits/#kotlin.ByteArray#kotlin.Int#kotlin.Int#kotlin.Int/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-kompact-runtime/write-bits.html +$dokka.location:ch.trancee.kompact.runtime/KompactRuntime/writeBitsBoolean/#kotlin.ByteArray#kotlin.Int#kotlin.Boolean/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-kompact-runtime/write-bits-boolean.html +$dokka.location:ch.trancee.kompact.runtime/KompactRuntime/writeBitsLong/#kotlin.ByteArray#kotlin.Int#kotlin.Int#kotlin.Long/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-kompact-runtime/write-bits-long.html +$dokka.location:ch.trancee.kompact.runtime/KompactWriter.Companion///PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-kompact-writer/-companion/index.html +$dokka.location:ch.trancee.kompact.runtime/KompactWriter///PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-kompact-writer/index.html +$dokka.location:ch.trancee.kompact.runtime/KompactWriter/KompactWriter/#/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-kompact-writer/-kompact-writer.html +$dokka.location:ch.trancee.kompact.runtime/KompactWriter/build/#/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-kompact-writer/build.html +$dokka.location:ch.trancee.kompact.runtime/KompactWriter/writeBits/#kotlin.Int#kotlin.Int/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-kompact-writer/write-bits.html +$dokka.location:ch.trancee.kompact.runtime/KompactWriter/writeBitsLong/#kotlin.Int#kotlin.Long/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-kompact-writer/write-bits-long.html +$dokka.location:ch.trancee.kompact.runtime/KompactWriter/writeBlob/#kotlin.Int#kotlin.ByteArray/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-kompact-writer/write-blob.html +$dokka.location:ch.trancee.kompact.runtime/KompactWriter/writeBool/#kotlin.Boolean/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-kompact-writer/write-bool.html +$dokka.location:ch.trancee.kompact.runtime/KompactWriter/writeNested/#kotlin.Int#kotlin.Function1[ch.trancee.kompact.runtime.KompactWriter,kotlin.Unit]/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-kompact-writer/write-nested.html +$dokka.location:ch.trancee.kompact.runtime/KompactWriter/writeRepeated/#kotlin.Int#kotlin.Int#kotlin.Function1[ch.trancee.kompact.runtime.KompactWriter,kotlin.Unit]/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-kompact-writer/write-repeated.html +$dokka.location:ch.trancee.kompact.runtime/KompactWriter/writeScalar/#ch.trancee.kompact.runtime.ScalarType#kotlin.Long/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-kompact-writer/write-scalar.html +$dokka.location:ch.trancee.kompact.runtime/KompactWriter/writeString/#kotlin.Int#kotlin.String/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-kompact-writer/write-string.html +$dokka.location:ch.trancee.kompact.runtime/LongResult.Companion///PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-long-result/-companion/index.html +$dokka.location:ch.trancee.kompact.runtime/LongResult.Companion/failure/#ch.trancee.kompact.runtime.KompactDecodeError/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-long-result/-companion/failure.html +$dokka.location:ch.trancee.kompact.runtime/LongResult.Companion/success/#kotlin.Long/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-long-result/-companion/success.html +$dokka.location:ch.trancee.kompact.runtime/LongResult///PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-long-result/index.html +$dokka.location:ch.trancee.kompact.runtime/LongResult/LongResult/#kotlin.Long/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-long-result/-long-result.html +$dokka.location:ch.trancee.kompact.runtime/LongResult/error/#/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-long-result/error.html +$dokka.location:ch.trancee.kompact.runtime/LongResult/getOrThrow/#/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-long-result/get-or-throw.html +$dokka.location:ch.trancee.kompact.runtime/LongResult/isFailure/#/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-long-result/is-failure.html +$dokka.location:ch.trancee.kompact.runtime/LongResult/isSuccess/#/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-long-result/is-success.html +$dokka.location:ch.trancee.kompact.runtime/LongResult/packed/#/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-long-result/packed.html +$dokka.location:ch.trancee.kompact.runtime/NestedRegion///PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-nested-region/index.html +$dokka.location:ch.trancee.kompact.runtime/NestedRegionResult.Companion///PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-nested-region-result/-companion/index.html +$dokka.location:ch.trancee.kompact.runtime/NestedRegionResult.Companion/failure/#ch.trancee.kompact.runtime.KompactDecodeError/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-nested-region-result/-companion/failure.html +$dokka.location:ch.trancee.kompact.runtime/NestedRegionResult.Companion/success/#kotlin.Int#kotlin.Int/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-nested-region-result/-companion/success.html +$dokka.location:ch.trancee.kompact.runtime/NestedRegionResult///PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-nested-region-result/index.html +$dokka.location:ch.trancee.kompact.runtime/NestedRegionResult/NestedRegionResult/#kotlin.Long/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-nested-region-result/-nested-region-result.html +$dokka.location:ch.trancee.kompact.runtime/NestedRegionResult/bitLength/#/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-nested-region-result/bit-length.html +$dokka.location:ch.trancee.kompact.runtime/NestedRegionResult/error/#/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-nested-region-result/error.html +$dokka.location:ch.trancee.kompact.runtime/NestedRegionResult/getOrThrow/#/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-nested-region-result/get-or-throw.html +$dokka.location:ch.trancee.kompact.runtime/NestedRegionResult/isFailure/#/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-nested-region-result/is-failure.html +$dokka.location:ch.trancee.kompact.runtime/NestedRegionResult/isSuccess/#/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-nested-region-result/is-success.html +$dokka.location:ch.trancee.kompact.runtime/NestedRegionResult/packed/#/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-nested-region-result/packed.html +$dokka.location:ch.trancee.kompact.runtime/NestedRegionResult/startBit/#/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-nested-region-result/start-bit.html +$dokka.location:ch.trancee.kompact.runtime/ScalarType.Companion///PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-scalar-type/-companion/index.html +$dokka.location:ch.trancee.kompact.runtime/ScalarType.Companion/BOOL/#/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-scalar-type/-companion/-b-o-o-l.html +$dokka.location:ch.trancee.kompact.runtime/ScalarType.Companion/INT_16/#/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-scalar-type/-companion/-i-n-t_16.html +$dokka.location:ch.trancee.kompact.runtime/ScalarType.Companion/INT_32/#/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-scalar-type/-companion/-i-n-t_32.html +$dokka.location:ch.trancee.kompact.runtime/ScalarType.Companion/INT_64/#/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-scalar-type/-companion/-i-n-t_64.html +$dokka.location:ch.trancee.kompact.runtime/ScalarType.Companion/INT_8/#/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-scalar-type/-companion/-i-n-t_8.html +$dokka.location:ch.trancee.kompact.runtime/ScalarType.Companion/UINT_16/#/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-scalar-type/-companion/-u-i-n-t_16.html +$dokka.location:ch.trancee.kompact.runtime/ScalarType.Companion/UINT_32/#/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-scalar-type/-companion/-u-i-n-t_32.html +$dokka.location:ch.trancee.kompact.runtime/ScalarType.Companion/UINT_64/#/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-scalar-type/-companion/-u-i-n-t_64.html +$dokka.location:ch.trancee.kompact.runtime/ScalarType.Companion/UINT_8/#/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-scalar-type/-companion/-u-i-n-t_8.html +$dokka.location:ch.trancee.kompact.runtime/ScalarType.Companion/of/#kotlin.Int#kotlin.Boolean/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-scalar-type/-companion/of.html +$dokka.location:ch.trancee.kompact.runtime/ScalarType///PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-scalar-type/index.html +$dokka.location:ch.trancee.kompact.runtime/ScalarType/ScalarType/#kotlin.Int/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-scalar-type/-scalar-type.html +$dokka.location:ch.trancee.kompact.runtime/ScalarType/bitWidth/#/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-scalar-type/bit-width.html +$dokka.location:ch.trancee.kompact.runtime/ScalarType/packed/#/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-scalar-type/packed.html +$dokka.location:ch.trancee.kompact.runtime/ScalarType/signed/#/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-scalar-type/signed.html +$dokka.location:ch.trancee.kompact.runtime/ShortResult.Companion///PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-short-result/-companion/index.html +$dokka.location:ch.trancee.kompact.runtime/ShortResult.Companion/failure/#ch.trancee.kompact.runtime.KompactDecodeError/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-short-result/-companion/failure.html +$dokka.location:ch.trancee.kompact.runtime/ShortResult.Companion/success/#kotlin.Short/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-short-result/-companion/success.html +$dokka.location:ch.trancee.kompact.runtime/ShortResult///PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-short-result/index.html +$dokka.location:ch.trancee.kompact.runtime/ShortResult/ShortResult/#kotlin.Long/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-short-result/-short-result.html +$dokka.location:ch.trancee.kompact.runtime/ShortResult/error/#/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-short-result/error.html +$dokka.location:ch.trancee.kompact.runtime/ShortResult/getOrThrow/#/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-short-result/get-or-throw.html +$dokka.location:ch.trancee.kompact.runtime/ShortResult/isFailure/#/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-short-result/is-failure.html +$dokka.location:ch.trancee.kompact.runtime/ShortResult/isSuccess/#/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-short-result/is-success.html +$dokka.location:ch.trancee.kompact.runtime/ShortResult/packed/#/PointingToDeclaration/kompact/ch.trancee.kompact.runtime/-short-result/packed.html +$dokka.location:ch.trancee.kompact////PointingToDeclaration/kompact/ch.trancee.kompact/index.html +$dokka.location:ch.trancee.kompact/Kompact.Result.Boolean///PointingToDeclaration/kompact/ch.trancee.kompact/-kompact/-result/-boolean/index.html +$dokka.location:ch.trancee.kompact/Kompact.Result.Byte///PointingToDeclaration/kompact/ch.trancee.kompact/-kompact/-result/-byte/index.html +$dokka.location:ch.trancee.kompact/Kompact.Result.Double///PointingToDeclaration/kompact/ch.trancee.kompact/-kompact/-result/-double/index.html +$dokka.location:ch.trancee.kompact/Kompact.Result.Float///PointingToDeclaration/kompact/ch.trancee.kompact/-kompact/-result/-float/index.html +$dokka.location:ch.trancee.kompact/Kompact.Result.Int///PointingToDeclaration/kompact/ch.trancee.kompact/-kompact/-result/-int/index.html +$dokka.location:ch.trancee.kompact/Kompact.Result.Long///PointingToDeclaration/kompact/ch.trancee.kompact/-kompact/-result/-long/index.html +$dokka.location:ch.trancee.kompact/Kompact.Result.Short///PointingToDeclaration/kompact/ch.trancee.kompact/-kompact/-result/-short/index.html +$dokka.location:ch.trancee.kompact/Kompact.Result///PointingToDeclaration/kompact/ch.trancee.kompact/-kompact/-result/index.html +$dokka.location:ch.trancee.kompact/Kompact///PointingToDeclaration/kompact/ch.trancee.kompact/-kompact/index.html +ch.trancee.kompact +ch.trancee.kompact.annotations +ch.trancee.kompact.generated +ch.trancee.kompact.runtime diff --git a/kompact/docs/api/navigation.html b/kompact/docs/api/navigation.html new file mode 100644 index 0000000..5259623 --- /dev/null +++ b/kompact/docs/api/navigation.html @@ -0,0 +1,150 @@ + diff --git a/kompact/docs/api/scripts/main.js b/kompact/docs/api/scripts/main.js new file mode 100644 index 0000000..a38279f --- /dev/null +++ b/kompact/docs/api/scripts/main.js @@ -0,0 +1,45 @@ +(()=>{var e={1817:e=>{e.exports=''},4811:e=>{e.exports=''},5742:e=>{e.exports=''},7112:e=>{e.exports=''},8420:e=>{e.exports=''},7004:e=>{e.exports=''},7222:(e,n,t)=>{"use strict";t.r(n),t.d(n,{default:()=>s});var r=t(1404),o=t.n(r),i=t(7156),a=t.n(i),l=t(5280),c=a()(o());c.i(l.A),c.push([e.id,'.avatar_d716 {\n display: inline-block;\n -o-object-fit: cover;\n object-fit: cover;\n -o-object-position: center;\n object-position: center;\n\n /* This is a "graceful degradation" fallback, while the real value is controlled by JS */\n\n border-radius: var(--ring-border-radius);\n}\n\n.subavatar_b10d {\n position: absolute;\n top: 15px;\n left: 27px;\n\n border: 1px var(--ring-content-background-color) solid;\n}\n\n.empty_a151 {\n display: inline-block;\n\n box-sizing: border-box;\n\n border: 1px solid var(--ring-borders-color);\n}\n',"",{version:3,sources:["webpack://./node_modules/@jetbrains/ring-ui/components/avatar/avatar.css"],names:[],mappings:"AAEA;EACE,qBAAqB;EACrB,oBAAiB;KAAjB,iBAAiB;EACjB,0BAAuB;KAAvB,uBAAuB;;EAEvB,wFAAwF;;EAExF,wCAAwC;AAC1C;;AAEA;EACE,kBAAkB;EAClB,SAAS;EACT,UAAU;;EAEV,sDAAsD;AACxD;;AAEA;EACE,qBAAqB;;EAErB,sBAAsB;;EAEtB,2CAA2C;AAC7C",sourcesContent:['@import "../global/variables.css";\n\n.avatar {\n display: inline-block;\n object-fit: cover;\n object-position: center;\n\n /* This is a "graceful degradation" fallback, while the real value is controlled by JS */\n\n border-radius: var(--ring-border-radius);\n}\n\n.subavatar {\n position: absolute;\n top: 15px;\n left: 27px;\n\n border: 1px var(--ring-content-background-color) solid;\n}\n\n.empty {\n display: inline-block;\n\n box-sizing: border-box;\n\n border: 1px solid var(--ring-borders-color);\n}\n'],sourceRoot:""}]),c.locals={avatar:"avatar_d716",subavatar:"subavatar_b10d",empty:"empty_a151"};const s=c},9892:(e,n,t)=>{"use strict";t.r(n),t.d(n,{default:()=>u});var r=t(1404),o=t.n(r),i=t(7156),a=t.n(i),l=t(9106),c=t(5280),s=a()(o());s.i(c.A),s.i(l.default,"",!0),s.push([e.id,'.heightS_b28d {\n --ring-button-height: 24px;\n --ring-button-font-size: var(--ring-font-size-smaller);\n}\n\n.heightM_dfd3 {\n --ring-button-height: 28px;\n --ring-button-font-size: var(--ring-font-size);\n}\n\n.heightL_a4d3 {\n --ring-button-height: 32px;\n --ring-button-font-size: var(--ring-font-size);\n}\n\n.button_aba4 {\n position: relative;\n\n display: inline-block;\n\n box-sizing: border-box;\n height: var(--ring-button-height);\n margin: 0;\n padding: 0 16px;\n\n cursor: pointer;\n transition: color var(--ring-ease), background-color var(--ring-ease), box-shadow var(--ring-ease);\n text-decoration: none;\n\n color: var(--ring-text-color);\n\n border: 0;\n border-radius: var(--ring-border-radius);\n outline: 0;\n background-color: var(--ring-content-background-color);\n box-shadow: inset 0 0 0 1px var(--ring-borders-color);\n\n font-family: var(--ring-font-family);\n font-size: var(--ring-button-font-size);\n\n line-height: var(--ring-button-height);\n}\n\n@media (hover: hover), (-moz-touch-enabled: 0), (-ms-high-contrast: none), (-ms-high-contrast: active) {.button_aba4:hover {\n transition: none;\n\n box-shadow: inset 0 0 0 1px var(--ring-border-hover-color);\n }}\n\n.button_aba4:active {\n transition: none;\n\n background-color: var(--ring-selected-background-color);\n box-shadow: inset 0 0 0 1px var(--ring-border-hover-color);\n }\n\n.button_aba4:focus-visible {\n transition: none;\n\n box-shadow: inset 0 0 0 1px var(--ring-border-hover-color), 0 0 0 1px var(--ring-border-hover-color);\n }\n\n.button_aba4.active_bbe6 {\n transition: none;\n\n background-color: var(--ring-hover-background-color);\n box-shadow: inset 0 0 0 1px var(--ring-main-color);\n }\n\n.button_aba4.active_bbe6:focus-visible {\n box-shadow: inset 0 0 0 2px var(--ring-main-color), 0 0 0 1px var(--ring-border-hover-color);\n }\n\n.button_aba4[disabled] {\n pointer-events: none;\n\n background-color: var(--ring-disabled-background-color);\n box-shadow: inset 0 0 0 1px var(--ring-border-disabled-color);\n }\n\n.button_aba4.active_bbe6[disabled] {\n background-color: var(--ring-disabled-selected-background-color);\n box-shadow: inset 0 0 0 1px var(--ring-border-selected-disabled-color);\n }\n\n.button_aba4[disabled],\n .button_aba4.withIcon_ef77[disabled] {\n color: var(--ring-disabled-color);\n }\n\n.button_aba4[disabled] .icon_e878 {\n color: var(--ring-icon-disabled-color);\n }\n\n.button_aba4::-moz-focus-inner {\n padding: 0;\n\n border: 0;\n outline: 0;\n }\n\n.withIcon_ef77 {\n color: var(--ring-secondary-color);\n}\n\n.primary_ddae {\n color: var(--ring-white-text-color);\n background-color: var(--ring-main-color);\n box-shadow: none;\n}\n\n@media (hover: hover), (-moz-touch-enabled: 0), (-ms-high-contrast: none), (-ms-high-contrast: active) {.primary_ddae:hover {\n transition: none;\n\n background-color: var(--ring-main-hover-color);\n box-shadow: none;\n }}\n\n.primary_ddae.withIcon_ef77,\n .primary_ddae.withIcon_ef77:active,\n .primary_ddae.withIcon_ef77.active_bbe6 {\n color: var(--ring-action-link-color);\n }\n\n.primary_ddae:focus-visible,\n .primary_ddae:active,\n .primary_ddae.active_bbe6 {\n background-color: var(--ring-button-primary-background-color);\n }\n\n.primary_ddae:active,\n .primary_ddae.active_bbe6 {\n box-shadow: inset 0 0 0 1px var(--ring-button-primary-border-color);\n }\n\n.primary_ddae[disabled] {\n background-color: var(--ring-disabled-background-color);\n box-shadow: inset 0 0 0 1px var(--ring-border-disabled-color);\n }\n\n.primary_ddae.loader_cbfc[disabled] {\n color: var(--ring-white-text-color);\n }\n\n.primary_ddae .loaderBackground_d9f5 {\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n\n border-radius: var(--ring-border-radius);\n }\n\n.primary_ddae .loaderBackground_d9f5::before {\n background-image:\n linear-gradient(\n to right,\n var(--ring-main-color),\n var(--ring-button-loader-background) 40%,\n var(--ring-main-color) 80%\n );\n }\n\n@media (hover: hover), (-moz-touch-enabled: 0), (-ms-high-contrast: none), (-ms-high-contrast: active) {.danger_bcea:hover {\n transition: none;\n }}\n\n@media (hover: hover), (-moz-touch-enabled: 0), (-ms-high-contrast: none), (-ms-high-contrast: active) {.danger_bcea:hover {\n box-shadow: inset 0 0 0 1px var(--ring-button-danger-hover-color);\n }}\n\n.danger_bcea,\n .danger_bcea.withIcon_ef77,\n .danger_bcea.withIcon_ef77:active,\n .danger_bcea.withIcon_ef77.active_bbe6,\n .danger_bcea.text_fc2a,\n .danger_bcea.text_fc2a:active,\n .danger_bcea.text_fc2a.active_bbe6 {\n color: var(--ring-error-color);\n }\n\n.danger_bcea:active,\n .danger_bcea.active_bbe6 {\n background-color: var(--ring-button-danger-active-color);\n }\n\n.danger_bcea:active,\n .danger_bcea.active_bbe6,\n .danger_bcea:focus-visible {\n box-shadow: inset 0 0 0 1px var(--ring-button-danger-hover-color);\n }\n\n.danger_bcea:focus-visible {\n transition: none;\n }\n\n.text_fc2a.text_fc2a,\n.withIcon_ef77.withIcon_ef77 {\n background-color: transparent;\n box-shadow: none;\n}\n\n@media (hover: hover), (-moz-touch-enabled: 0), (-ms-high-contrast: none), (-ms-high-contrast: active) {.text_fc2a.text_fc2a:hover, .withIcon_ef77.withIcon_ef77:hover {\n transition: none;\n }}\n\n.text_fc2a.text_fc2a:active,\n .withIcon_ef77.withIcon_ef77:active,\n .text_fc2a.text_fc2a.active_bbe6,\n .withIcon_ef77.withIcon_ef77.active_bbe6 {\n background-color: transparent;\n box-shadow: none;\n }\n\n.text_fc2a.text_fc2a:focus-visible, .withIcon_ef77.withIcon_ef77:focus-visible {\n box-shadow: inset 0 0 0 2px var(--ring-border-hover-color);\n }\n\n.loader_cbfc.text_fc2a > .content_b2b8 {\n animation-name: text-loading_d1b4;\n animation-duration: 1200ms;\n animation-iteration-count: infinite;\n}\n\n@media (hover: hover), (-moz-touch-enabled: 0), (-ms-high-contrast: none), (-ms-high-contrast: active) {.text_fc2a.text_fc2a:hover {\n background-color: transparent;\n box-shadow: none;\n}}\n\n@media (hover: hover), (-moz-touch-enabled: 0), (-ms-high-contrast: none), (-ms-high-contrast: active) {.withIcon_ef77:hover:not(:focus-visible) {\n background-color: transparent;\n box-shadow: none;\n}}\n\n.text_fc2a {\n color: var(--ring-action-link-color);\n}\n\n.inline_b4a2 {\n display: inline-block;\n\n margin: 0;\n padding: 0;\n\n font-size: var(--ring-font-size);\n}\n\n.withIcon_ef77 {\n padding: 0 8px;\n}\n\n.text_fc2a:active,\n .text_fc2a.active_bbe6 {\n color: var(--ring-link-hover-color);\n }\n\n.withIcon_ef77:active,\n .withIcon_ef77.active_bbe6 {\n color: var(--ring-action-link-color);\n }\n\n@media (hover: hover), (-moz-touch-enabled: 0), (-ms-high-contrast: none), (-ms-high-contrast: active) {.withIcon_ef77:hover {\n color: var(--ring-link-hover-color);\n}}\n\n@media (hover: hover), (-moz-touch-enabled: 0), (-ms-high-contrast: none), (-ms-high-contrast: active) {.text_fc2a:hover {\n color: var(--ring-link-hover-color);\n}}\n\n.icon_e878 {\n color: inherit;\n\n line-height: normal;\n}\n\n.icon_e878:not(:last-child) {\n margin-right: 4px;\n }\n\n.withNormalIcon_aaca .icon_e878 {\n transition: color var(--ring-ease);\n\n color: var(--ring-icon-color);\n}\n\n.withNormalIcon_aaca:active,\n.withNormalIcon_aaca.active_bbe6 {\n color: var(--ring-main-color);\n}\n\n.withNormalIcon_aaca:active .icon_e878, .withNormalIcon_aaca.active_bbe6 .icon_e878 {\n transition: none;\n\n color: inherit;\n }\n\n@media (hover: hover), (-moz-touch-enabled: 0), (-ms-high-contrast: none), (-ms-high-contrast: active) {.withNormalIcon_aaca:hover .icon_e878,\n.withDangerIcon_e3ca:hover .icon_e878 {\n transition: none;\n\n color: inherit;\n}}\n\n.withDangerIcon_e3ca .icon_e878,\n.withDangerIcon_e3ca:active .icon_e878 {\n color: var(--ring-icon-error-color);\n}\n\n.loader_cbfc {\n position: relative;\n z-index: 0;\n\n pointer-events: none;\n\n background-color: transparent;\n}\n\n.loaderBackground_d9f5 {\n position: absolute;\n z-index: -1;\n top: 1px;\n right: 1px;\n bottom: 1px;\n left: 1px;\n\n overflow: hidden;\n\n border-radius: var(--ring-border-radius-small);\n}\n\n.loaderBackground_d9f5::before {\n display: block;\n\n width: calc(100% + 64px);\n height: 100%;\n\n content: "";\n animation: progress_ed8f 1s linear infinite;\n\n background-image:\n linear-gradient(\n to right,\n var(--ring-content-background-color),\n var(--ring-selected-background-color) 40%,\n var(--ring-content-background-color) 80%\n );\n\n background-repeat: repeat;\n background-size: 64px;\n }\n\n.delayed_d562 .content_b2b8::after {\n content: "…";\n}\n\n.short_a07a {\n width: 32px;\n padding: 0;\n}\n\n.dropdownIcon_e982 {\n margin-right: -2px;\n\n margin-left: 2px;\n\n transition: color var(--ring-ease);\n\n color: var(--ring-icon-secondary-color);\n\n line-height: normal;\n}\n\n@media (hover: hover), (-moz-touch-enabled: 0), (-ms-high-contrast: none), (-ms-high-contrast: active) {.button_aba4:hover .dropdownIcon_e982 {\n transition: none;\n\n color: var(--ring-main-color);\n}}\n\n@keyframes progress_ed8f {\n from {\n transform: translateX(-64px);\n }\n\n to {\n transform: translateX(0);\n }\n}\n\n@keyframes text-loading_d1b4 {\n 50% {\n opacity: 0.5;\n }\n}\n',"",{version:3,sources:["webpack://./node_modules/@jetbrains/ring-ui/components/button/button.css",""],names:[],mappings:"AAOA;EACE,0BAAoC;EACpC,sDAAsD;AACxD;;AAEA;EACE,0BAAsC;EACtC,8CAA8C;AAChD;;AAEA;EACE,0BAAoC;EACpC,8CAA8C;AAChD;;AAEA;EACE,kBAAkB;;EAElB,qBAAqB;;EAErB,sBAAsB;EACtB,iCAAc;EACd,SAAS;EACT,eAAyB;;EAEzB,eAAe;EACf,kGAAkG;EAClG,qBAAqB;;EAErB,6BAA6B;;EAE7B,SAAS;EACT,wCAAwC;EACxC,UAAU;EACV,sDAAsD;EACtD,qDAAmD;;EAEnD,oCAAoC;EACpC,uCAAuC;;EAEvC,sCAAmB;AA2DrB;;AC1GA,wGAAA;IAAA,iBAAA;;IAAA,2DAAA;GAAA,CAAA;;ADuDE;IACE,gBAAgB;;IAEhB,uDAAuD;IACvD,0DAAwD;EAC1D;;AAEA;IACE,gBAAgB;;IAEhB,oGAAkG;EACpG;;AAEA;IACE,gBAAgB;;IAEhB,oDAAoD;IACpD,kDAAgD;EAClD;;AAEA;IACE,4FAA4F;EAC9F;;AAEA;IACE,oBAAoB;;IAEpB,uDAAuD;IACvD,6DAA2D;EAC7D;;AAEA;IACE,gEAAgE;IAChE,sEAAoE;EACtE;;AAEA;;IAEE,iCAAiC;EACnC;;AAEA;IACE,sCAAsC;EACxC;;AAEA;IACE,UAAU;;IAEV,SAAS;IACT,UAAU;EACZ;;AAGF;EACE,kCAAkC;AACpC;;AAEA;EACE,mCAAmC;EACnC,wCAAwC;EACxC,gBAAgB;AAqDlB;;ACxKA,wGAAA;IAAA,iBAAA;;IAAA,+CAAA;IAAA,iBAAA;GAAA,CAAA;;AD4HE;;;IAGE,oCAAoC;EACtC;;AAEA;;;IAGE,6DAA6D;EAC/D;;AAEA;;IAEE,mEAAiE;EACnE;;AAEA;IACE,uDAAuD;IACvD,6DAA2D;EAC7D;;AAEA;IACE,mCAAmC;EACrC;;AAEA;IACE,MAAM;IACN,QAAQ;IACR,SAAS;IACT,OAAO;;IAEP,wCAAwC;EAW1C;;AATE;MACE;;;;;;SAMG;IACL;;ACtKJ,wGAAA;IAAA,iBAAA;GAAA,CAAA;;AAAA,wGAAA;IAAA,kEAAA;GAAA,CAAA;;AD2KE;;;;;;;IAOE,8BAA8B;EAChC;;AAEA;;IAEE,wDAAwD;EAC1D;;AAEA;;;IAIE,iEAA+D;EACjE;;AAEA;IAEE,gBAAgB;EAClB;;AAGF;;EAEE,6BAA6B;EAC7B,gBAAgB;AAelB;;ACzNA,wGAAA;IAAA,iBAAA;GAAA,CAAA;;ADgNE;;;;IAEE,6BAA6B;IAC7B,gBAAgB;EAClB;;AAEA;IACE,0DAA0D;EAC5D;;AAGF;EACE,iCAA4B;EAC5B,0BAA0B;EAC1B,mCAAmC;AACrC;;AC/NA,wGAAA;EAAA,8BAAA;EAAA,iBAAA;CAAA,CAAA;;AAAA,wGAAA;EAAA,8BAAA;EAAA,iBAAA;CAAA,CAAA;;AD2OA;EACE,oCAAoC;AACtC;;AAEA;EACE,qBAAqB;;EAErB,SAAS;EACT,UAAU;;EAEV,gCAAgC;AAClC;;AAEA;EACE,cAAe;AACjB;;AAGE;;IAEE,mCAAmC;EACrC;;AAIA;;IAEE,oCAAoC;EACtC;;ACvQF,wGAAA;EAAA,oCAAA;CAAA,CAAA;;AAAA,wGAAA;EAAA,oCAAA;CAAA,CAAA;;ADkRA;EACE,cAAc;;EAEd,mBAAmB;AAKrB;;AAHE;IACE,iBAA8B;EAChC;;AAGF;EACE,kCAAkC;;EAElC,6BAA6B;AAC/B;;AAEA;;EAEE,6BAA6B;AAO/B;;AALE;IACE,gBAAgB;;IAEhB,cAAc;EAChB;;AC1SF,wGAAA;;EAAA,iBAAA;;EAAA,eAAA;CAAA,CAAA;;ADoTA;;EAEE,mCAAmC;AACrC;;AAEA;EACE,kBAAkB;EAClB,UAAU;;EAEV,oBAAoB;;EAEpB,6BAA6B;AAC/B;;AAEA;EACE,kBAAkB;EAClB,WAAW;EACX,QAAQ;EACR,UAAU;EACV,WAAW;EACX,SAAS;;EAET,gBAAgB;;EAEhB,8CAA8C;AAsBhD;;AApBE;IACE,cAAc;;IAEd,wBAA+B;IAC/B,YAAY;;IAEZ,WAAW;IACX,2CAAsC;;IAEtC;;;;;;OAMG;;IAEH,yBAAyB;IACzB,qBAA4B;EAC9B;;AAGF;EACE,YAAY;AACd;;AAEA;EACE,WAAqB;EACrB,UAAU;AACZ;;AAEA;EACE,kBAAkB;;EAElB,gBAAgB;;EAEhB,kCAAkC;;EAElC,uCAAuC;;EAEvC,mBAAmB;AACrB;;ACvXA,wGAAA;EAAA,iBAAA;;EAAA,8BAAA;CAAA,CAAA;;AD+XA;EACE;IACE,4BAA4C;EAC9C;;EAEA;IACE,wBAAwB;EAC1B;AACF;;AAEA;EACE;IACE,YAAY;EACd;AACF",sourcesContent:['@import "../global/variables.css";\n\n@value unit from "../global/global.css";\n@value button-shadow: inset 0 0 0 1px;\n@value height: var(--ring-button-height);\n@value loaderWidth: calc(unit * 8);\n\n.heightS {\n --ring-button-height: calc(unit * 3);\n --ring-button-font-size: var(--ring-font-size-smaller);\n}\n\n.heightM {\n --ring-button-height: calc(unit * 3.5);\n --ring-button-font-size: var(--ring-font-size);\n}\n\n.heightL {\n --ring-button-height: calc(unit * 4);\n --ring-button-font-size: var(--ring-font-size);\n}\n\n.button {\n position: relative;\n\n display: inline-block;\n\n box-sizing: border-box;\n height: height;\n margin: 0;\n padding: 0 calc(unit * 2);\n\n cursor: pointer;\n transition: color var(--ring-ease), background-color var(--ring-ease), box-shadow var(--ring-ease);\n text-decoration: none;\n\n color: var(--ring-text-color);\n\n border: 0;\n border-radius: var(--ring-border-radius);\n outline: 0;\n background-color: var(--ring-content-background-color);\n box-shadow: button-shadow var(--ring-borders-color);\n\n font-family: var(--ring-font-family);\n font-size: var(--ring-button-font-size);\n\n line-height: height;\n\n &:hover {\n transition: none;\n\n box-shadow: button-shadow var(--ring-border-hover-color);\n }\n\n &:active {\n transition: none;\n\n background-color: var(--ring-selected-background-color);\n box-shadow: button-shadow var(--ring-border-hover-color);\n }\n\n &:focus-visible {\n transition: none;\n\n box-shadow: button-shadow var(--ring-border-hover-color), 0 0 0 1px var(--ring-border-hover-color);\n }\n\n &.active {\n transition: none;\n\n background-color: var(--ring-hover-background-color);\n box-shadow: button-shadow var(--ring-main-color);\n }\n\n &:focus-visible.active {\n box-shadow: inset 0 0 0 2px var(--ring-main-color), 0 0 0 1px var(--ring-border-hover-color);\n }\n\n &[disabled] {\n pointer-events: none;\n\n background-color: var(--ring-disabled-background-color);\n box-shadow: button-shadow var(--ring-border-disabled-color);\n }\n\n &[disabled].active {\n background-color: var(--ring-disabled-selected-background-color);\n box-shadow: button-shadow var(--ring-border-selected-disabled-color);\n }\n\n &[disabled],\n &[disabled].withIcon {\n color: var(--ring-disabled-color);\n }\n\n &[disabled] .icon {\n color: var(--ring-icon-disabled-color);\n }\n\n &::-moz-focus-inner {\n padding: 0;\n\n border: 0;\n outline: 0;\n }\n}\n\n.withIcon {\n color: var(--ring-secondary-color);\n}\n\n.primary {\n color: var(--ring-white-text-color);\n background-color: var(--ring-main-color);\n box-shadow: none;\n\n &:hover {\n transition: none;\n\n background-color: var(--ring-main-hover-color);\n box-shadow: none;\n }\n\n &.withIcon,\n &.withIcon:active,\n &.withIcon.active {\n color: var(--ring-action-link-color);\n }\n\n &:focus-visible,\n &:active,\n &.active {\n background-color: var(--ring-button-primary-background-color);\n }\n\n &:active,\n &.active {\n box-shadow: button-shadow var(--ring-button-primary-border-color);\n }\n\n &[disabled] {\n background-color: var(--ring-disabled-background-color);\n box-shadow: button-shadow var(--ring-border-disabled-color);\n }\n\n &[disabled].loader {\n color: var(--ring-white-text-color);\n }\n\n & .loaderBackground {\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n\n border-radius: var(--ring-border-radius);\n\n &::before {\n background-image:\n linear-gradient(\n to right,\n var(--ring-main-color),\n var(--ring-button-loader-background) 40%,\n var(--ring-main-color) 80%\n );\n }\n }\n}\n\n.danger {\n &,\n &.withIcon,\n &.withIcon:active,\n &.withIcon.active,\n &.text,\n &.text:active,\n &.text.active {\n color: var(--ring-error-color);\n }\n\n &:active,\n &.active {\n background-color: var(--ring-button-danger-active-color);\n }\n\n &:active,\n &.active,\n &:focus-visible,\n &:hover {\n box-shadow: button-shadow var(--ring-button-danger-hover-color);\n }\n\n &:focus-visible,\n &:hover {\n transition: none;\n }\n}\n\n.text.text,\n.withIcon.withIcon {\n background-color: transparent;\n box-shadow: none;\n\n &:hover {\n transition: none;\n }\n\n &:active,\n &.active {\n background-color: transparent;\n box-shadow: none;\n }\n\n &:focus-visible {\n box-shadow: inset 0 0 0 2px var(--ring-border-hover-color);\n }\n}\n\n.loader.text > .content {\n animation-name: text-loading;\n animation-duration: 1200ms;\n animation-iteration-count: infinite;\n}\n\n.text.text:hover {\n background-color: transparent;\n box-shadow: none;\n}\n\n.withIcon:hover:not(:focus-visible) {\n background-color: transparent;\n box-shadow: none;\n}\n\n.text {\n color: var(--ring-action-link-color);\n}\n\n.inline {\n display: inline-block;\n\n margin: 0;\n padding: 0;\n\n font-size: var(--ring-font-size);\n}\n\n.withIcon {\n padding: 0 unit;\n}\n\n.text {\n &:active,\n &.active {\n color: var(--ring-link-hover-color);\n }\n}\n\n.withIcon {\n &:active,\n &.active {\n color: var(--ring-action-link-color);\n }\n}\n\n.withIcon:hover {\n color: var(--ring-link-hover-color);\n}\n\n.text:hover {\n color: var(--ring-link-hover-color);\n}\n\n.icon {\n color: inherit;\n\n line-height: normal;\n\n &:not(:last-child) {\n margin-right: calc(unit * 0.5);\n }\n}\n\n.withNormalIcon .icon {\n transition: color var(--ring-ease);\n\n color: var(--ring-icon-color);\n}\n\n.withNormalIcon:active,\n.withNormalIcon.active {\n color: var(--ring-main-color);\n\n & .icon {\n transition: none;\n\n color: inherit;\n }\n}\n\n.withNormalIcon:hover .icon,\n.withDangerIcon:hover .icon {\n transition: none;\n\n color: inherit;\n}\n\n.withDangerIcon .icon,\n.withDangerIcon:active .icon {\n color: var(--ring-icon-error-color);\n}\n\n.loader {\n position: relative;\n z-index: 0;\n\n pointer-events: none;\n\n background-color: transparent;\n}\n\n.loaderBackground {\n position: absolute;\n z-index: -1;\n top: 1px;\n right: 1px;\n bottom: 1px;\n left: 1px;\n\n overflow: hidden;\n\n border-radius: var(--ring-border-radius-small);\n\n &::before {\n display: block;\n\n width: calc(100% + loaderWidth);\n height: 100%;\n\n content: "";\n animation: progress 1s linear infinite;\n\n background-image:\n linear-gradient(\n to right,\n var(--ring-content-background-color),\n var(--ring-selected-background-color) 40%,\n var(--ring-content-background-color) 80%\n );\n\n background-repeat: repeat;\n background-size: loaderWidth;\n }\n}\n\n.delayed .content::after {\n content: "…";\n}\n\n.short {\n width: calc(unit * 4);\n padding: 0;\n}\n\n.dropdownIcon {\n margin-right: -2px;\n\n margin-left: 2px;\n\n transition: color var(--ring-ease);\n\n color: var(--ring-icon-secondary-color);\n\n line-height: normal;\n}\n\n.button:hover .dropdownIcon {\n transition: none;\n\n color: var(--ring-main-color);\n}\n\n@keyframes progress {\n from {\n transform: translateX(calc(0 - loaderWidth));\n }\n\n to {\n transform: translateX(0);\n }\n}\n\n@keyframes text-loading {\n 50% {\n opacity: 0.5;\n }\n}\n',null],sourceRoot:""}]),s.locals={unit:`${l.default.locals.unit}`,"button-shadow":"inset 0 0 0 1px",height:"var(--ring-button-height)",loaderWidth:"64px",heightS:"heightS_b28d",heightM:"heightM_dfd3",heightL:"heightL_a4d3",button:"button_aba4",active:"active_bbe6",withIcon:"withIcon_ef77",icon:"icon_e878",primary:"primary_ddae",loader:"loader_cbfc",loaderBackground:"loaderBackground_d9f5",danger:"danger_bcea",text:"text_fc2a",content:"content_b2b8","text-loading":"text-loading_d1b4",inline:"inline_b4a2",withNormalIcon:"withNormalIcon_aaca",withDangerIcon:"withDangerIcon_e3ca",progress:"progress_ed8f",delayed:"delayed_d562",short:"short_a07a",dropdownIcon:"dropdownIcon_e982"};const u=s},1866:(e,n,t)=>{"use strict";t.r(n),t.d(n,{default:()=>u});var r=t(1404),o=t.n(r),i=t(7156),a=t.n(i),l=t(9106),c=t(5280),s=a()(o());s.i(c.A),s.i(l.default,"",!0),s.push([e.id,".checkbox_dccf {\n position: relative;\n\n display: inline-block;\n\n text-align: left;\n\n color: var(--ring-text-color);\n outline: none;\n}\n\n@media (hover: hover), (-moz-touch-enabled: 0), (-ms-high-contrast: none), (-ms-high-contrast: active) {.checkbox_dccf:hover .cell_edda {\n transition: background-color var(--ring-ease);\n\n border-color: var(--ring-border-hover-color);\n }}\n\n.cell_edda {\n position: relative;\n top: -2px;\n\n display: inline-block;\n\n box-sizing: border-box;\n width: 14px;\n height: 14px;\n\n -webkit-user-select: none;\n\n -moz-user-select: none;\n\n user-select: none;\n transition: border-color var(--ring-ease), background-color var(--ring-ease), box-shadow var(--ring-ease);\n vertical-align: middle;\n pointer-events: none;\n\n border: 1px solid var(--ring-borders-color);\n border-radius: var(--ring-border-radius-small);\n background-color: var(--ring-content-background-color);\n}\n\n.icon_b476.icon_b476 {\n position: absolute;\n\n top: -1px;\n left: -1px;\n\n width: 16px;\n height: 16px;\n\n opacity: 0;\n color: var(--ring-white-text-color);\n}\n\n.icon_b476.icon_b476 svg {\n position: absolute;\n top: 0;\n left: 0;\n }\n\n.check_a219 {\n}\n\n.minus_de65 {\n}\n\n.input_a330 {\n position: absolute;\n top: 0;\n left: 0;\n\n width: 100%;\n height: 100%;\n margin: 0;\n\n cursor: pointer;\n\n opacity: 0;\n\n /* stylelint-disable-next-line selector-max-specificity */\n}\n\n.input_a330:checked + .cell_edda,\n .input_a330:indeterminate + .cell_edda {\n border-color: transparent;\n background-color: var(--ring-main-color);\n }\n\n/* stylelint-disable-next-line selector-max-specificity */\n\n.input_a330:checked + .cell_edda .check_a219 {\n opacity: 1;\n }\n\n.input_a330:focus-visible + .cell_edda,\n .input_a330.focus_eaa3 + .cell_edda {\n transition: background-color var(--ring-ease);\n\n border-color: var(--ring-border-hover-color);\n box-shadow: 0 0 0 1px var(--ring-border-hover-color);\n }\n\n/* stylelint-disable-next-line selector-max-specificity */\n\n.input_a330:indeterminate + .cell_edda .minus_de65 {\n opacity: 1;\n }\n\n.input_a330[disabled] {\n pointer-events: none;\n }\n\n/* stylelint-disable-next-line selector-max-specificity */\n\n.input_a330[disabled][disabled] + .cell_edda {\n border-color: var(--ring-border-disabled-color);\n background-color: var(--ring-disabled-background-color);\n }\n\n/* stylelint-disable-next-line selector-max-specificity */\n\n.input_a330[disabled]:checked + .cell_edda,\n .input_a330[disabled]:indeterminate + .cell_edda {\n border-color: var(--ring-border-selected-disabled-color);\n }\n\n/* stylelint-disable-next-line selector-max-specificity */\n\n.input_a330[disabled]:checked + .cell_edda .check_a219,\n .input_a330[disabled]:indeterminate + .cell_edda .minus_de65 {\n color: var(--ring-icon-disabled-color);\n }\n\n/* stylelint-disable-next-line selector-max-specificity */\n\n.input_a330:indeterminate:indeterminate + .cell_edda .check_a219 {\n transition: none;\n\n opacity: 0;\n }\n\n.input_a330[disabled] ~ .label_dcc7 {\n color: var(--ring-disabled-color);\n }\n\n.label_dcc7 {\n margin-left: 8px;\n\n line-height: normal;\n}\n","",{version:3,sources:["webpack://./node_modules/@jetbrains/ring-ui/components/checkbox/checkbox.css",""],names:[],mappings:"AAKA;EACE,kBAAkB;;EAElB,qBAAqB;;EAErB,gBAAgB;;EAEhB,6BAA6B;EAC7B,aAAa;AAOf;;ACpBA,wGAAA;IAAA,8CAAA;;IAAA,6CAAA;GAAA,CAAA;;ADsBA;EACE,kBAAkB;EAClB,SAAS;;EAET,qBAAqB;;EAErB,sBAAsB;EACtB,WAAmB;EACnB,YAAoB;;EAEpB,yBAAiB;;KAAjB,sBAAiB;;UAAjB,iBAAiB;EACjB,yGAAyG;EACzG,sBAAsB;EACtB,oBAAoB;;EAEpB,2CAA2C;EAC3C,8CAA8C;EAC9C,sDAAsD;AACxD;;AAEA;EACE,kBAAkB;;EAElB,SAAS;EACT,UAAU;;EAEV,WAAqB;EACrB,YAAsB;;EAEtB,UAAU;EACV,mCAAmC;AAOrC;;AALE;IACE,kBAAkB;IAClB,MAAM;IACN,OAAO;EACT;;AAGF;AAEA;;AAEA;AAEA;;AAEA;EACE,kBAAkB;EAClB,MAAM;EACN,OAAO;;EAEP,WAAW;EACX,YAAY;EACZ,SAAS;;EAET,eAAe;;EAEf,UAAU;;EAEV,yDAAyD;AAyD3D;;AAxDE;;IAEE,yBAAyB;IACzB,wCAAwC;EAC1C;;AAEA,yDAAyD;;AACzD;IACE,UAAU;EACZ;;AAEA;;IAEE,6CAA6C;;IAE7C,4CAA4C;IAC5C,oDAAoD;EACtD;;AAEA,yDAAyD;;AACzD;IACE,UAAU;EACZ;;AAEA;IACE,oBAAoB;EACtB;;AAEA,yDAAyD;;AACzD;IACE,+CAA+C;IAC/C,uDAAuD;EACzD;;AAEA,yDAAyD;;AACzD;;IAEE,wDAAwD;EAC1D;;AAEA,yDAAyD;;AACzD;;IAEE,sCAAsC;EACxC;;AAEA,yDAAyD;;AACzD;IACE,gBAAgB;;IAEhB,UAAU;EACZ;;AAEA;IACE,iCAAiC;EACnC;;AAGF;EACE,gBAAiB;;EAEjB,mBAAmB;AACrB",sourcesContent:['@import "../global/variables.css";\n\n@value unit from "../global/global.css";\n@value checkboxSize: 14px;\n\n.checkbox {\n position: relative;\n\n display: inline-block;\n\n text-align: left;\n\n color: var(--ring-text-color);\n outline: none;\n\n &:hover .cell {\n transition: background-color var(--ring-ease);\n\n border-color: var(--ring-border-hover-color);\n }\n}\n\n.cell {\n position: relative;\n top: -2px;\n\n display: inline-block;\n\n box-sizing: border-box;\n width: checkboxSize;\n height: checkboxSize;\n\n user-select: none;\n transition: border-color var(--ring-ease), background-color var(--ring-ease), box-shadow var(--ring-ease);\n vertical-align: middle;\n pointer-events: none;\n\n border: 1px solid var(--ring-borders-color);\n border-radius: var(--ring-border-radius-small);\n background-color: var(--ring-content-background-color);\n}\n\n.icon.icon {\n position: absolute;\n\n top: -1px;\n left: -1px;\n\n width: calc(unit * 2);\n height: calc(unit * 2);\n\n opacity: 0;\n color: var(--ring-white-text-color);\n\n & svg {\n position: absolute;\n top: 0;\n left: 0;\n }\n}\n\n.check {\n composes: icon;\n}\n\n.minus {\n composes: icon;\n}\n\n.input {\n position: absolute;\n top: 0;\n left: 0;\n\n width: 100%;\n height: 100%;\n margin: 0;\n\n cursor: pointer;\n\n opacity: 0;\n\n /* stylelint-disable-next-line selector-max-specificity */\n &:checked + .cell,\n &:indeterminate + .cell {\n border-color: transparent;\n background-color: var(--ring-main-color);\n }\n\n /* stylelint-disable-next-line selector-max-specificity */\n &:checked + .cell .check {\n opacity: 1;\n }\n\n &:focus-visible + .cell,\n &.focus + .cell {\n transition: background-color var(--ring-ease);\n\n border-color: var(--ring-border-hover-color);\n box-shadow: 0 0 0 1px var(--ring-border-hover-color);\n }\n\n /* stylelint-disable-next-line selector-max-specificity */\n &:indeterminate + .cell .minus {\n opacity: 1;\n }\n\n &[disabled] {\n pointer-events: none;\n }\n\n /* stylelint-disable-next-line selector-max-specificity */\n &[disabled][disabled] + .cell {\n border-color: var(--ring-border-disabled-color);\n background-color: var(--ring-disabled-background-color);\n }\n\n /* stylelint-disable-next-line selector-max-specificity */\n &[disabled]:checked + .cell,\n &[disabled]:indeterminate + .cell {\n border-color: var(--ring-border-selected-disabled-color);\n }\n\n /* stylelint-disable-next-line selector-max-specificity */\n &[disabled]:checked + .cell .check,\n &[disabled]:indeterminate + .cell .minus {\n color: var(--ring-icon-disabled-color);\n }\n\n /* stylelint-disable-next-line selector-max-specificity */\n &:indeterminate:indeterminate + .cell .check {\n transition: none;\n\n opacity: 0;\n }\n\n &[disabled] ~ .label {\n color: var(--ring-disabled-color);\n }\n}\n\n.label {\n margin-left: unit;\n\n line-height: normal;\n}\n',null],sourceRoot:""}]),s.locals={unit:`${l.default.locals.unit}`,checkboxSize:"14px",checkbox:"checkbox_dccf",cell:"cell_edda",icon:"icon_b476",check:"check_a219 icon_b476",minus:"minus_de65 icon_b476",input:"input_a330",focus:"focus_eaa3",label:"label_dcc7"};const u=s},5486:(e,n,t)=>{"use strict";t.r(n),t.d(n,{default:()=>l});var r=t(1404),o=t.n(r),i=t(7156),a=t.n(i)()(o());a.push([e.id,".label_bed7 {\n display: block;\n\n margin-bottom: calc(var(--ring-unit)*0.5);\n}\n\n.formLabel_f9ba {\n color: var(--ring-text-color);\n\n font-size: var(--ring-font-size);\n line-height: var(--ring-line-height);\n}\n\n.secondaryLabel_e8a1 {\n color: var(--ring-secondary-color);\n\n font-size: var(--ring-font-size-smaller);\n line-height: var(--ring-line-height-lowest);\n}\n\n.disabledLabel_e4c1 {\n color: var(--ring-disabled-color);\n}\n","",{version:3,sources:["webpack://./node_modules/@jetbrains/ring-ui/components/control-label/control-label.css"],names:[],mappings:"AAAA;EACE,cAAc;;EAEd,yCAA2C;AAC7C;;AAEA;EACE,6BAA6B;;EAE7B,gCAAgC;EAChC,oCAAoC;AACtC;;AAEA;EACE,kCAAkC;;EAElC,wCAAwC;EACxC,2CAA2C;AAC7C;;AAEA;EACE,iCAAiC;AACnC",sourcesContent:[".label {\n display: block;\n\n margin-bottom: calc(var(--ring-unit) * 0.5);\n}\n\n.formLabel {\n color: var(--ring-text-color);\n\n font-size: var(--ring-font-size);\n line-height: var(--ring-line-height);\n}\n\n.secondaryLabel {\n color: var(--ring-secondary-color);\n\n font-size: var(--ring-font-size-smaller);\n line-height: var(--ring-line-height-lowest);\n}\n\n.disabledLabel {\n color: var(--ring-disabled-color);\n}\n"],sourceRoot:""}]),a.locals={label:"label_bed7",formLabel:"formLabel_f9ba",secondaryLabel:"secondaryLabel_e8a1",disabledLabel:"disabledLabel_e4c1"};const l=a},6506:(e,n,t)=>{"use strict";t.r(n),t.d(n,{default:()=>s});var r=t(1404),o=t.n(r),i=t(7156),a=t.n(i),l=t(5280),c=a()(o());c.i(l.A),c.push([e.id,".dropdown_a1de {\n display: inline-block;\n}\n\n.anchor_fdbe.anchor_fdbe {\n margin: 0 -3px;\n padding: 0 3px;\n\n font: inherit;\n}\n\n.chevron_ffc6 {\n margin-left: 2px;\n\n line-height: normal;\n}\n","",{version:3,sources:["webpack://./node_modules/@jetbrains/ring-ui/components/dropdown/dropdown.css"],names:[],mappings:"AAEA;EACE,qBAAqB;AACvB;;AAEA;EACE,cAAc;EACd,cAAc;;EAEd,aAAa;AACf;;AAEA;EACE,gBAAgB;;EAEhB,mBAAmB;AACrB",sourcesContent:['@import "../global/variables.css";\n\n.dropdown {\n display: inline-block;\n}\n\n.anchor.anchor {\n margin: 0 -3px;\n padding: 0 3px;\n\n font: inherit;\n}\n\n.chevron {\n margin-left: 2px;\n\n line-height: normal;\n}\n'],sourceRoot:""}]),c.locals={dropdown:"dropdown_a1de",anchor:"anchor_fdbe",chevron:"chevron_ffc6"};const s=c},9106:(e,n,t)=>{"use strict";t.r(n),t.d(n,{default:()=>l});var r=t(1404),o=t.n(r),i=t(7156),a=t.n(i)()(o());a.push([e.id,'/* https://readymag.com/artemtiunov/RingUILanguage/colours/ */\n\n/*\nUnit shouldn\'t be CSS custom property because it is not intended to change\nAlso it won\'t form in FF47 https://bugzilla.mozilla.org/show_bug.cgi?id=594933\n*/\n\n.clearfix_c694::after {\n display: block;\n clear: both;\n\n content: "";\n }\n\n.font_a1f6 {\n font-family: var(--ring-font-family);\n font-size: var(--ring-font-size);\n line-height: var(--ring-line-height);\n}\n\n.font-lower_c3c9 {\n\n line-height: var(--ring-line-height-lower);\n}\n\n.font-smaller_d963 {\n\n font-size: var(--ring-font-size-smaller);\n}\n\n.font-smaller-lower_ff5f {\n\n line-height: var(--ring-line-height-lowest);\n}\n\n.font-larger-lower_b336 {\n\n font-size: var(--ring-font-size-larger);\n}\n\n.font-larger_f035 {\n\n line-height: var(--ring-line-height-taller);\n}\n\n/* To be used at large sizes */\n/* As close as possible to Helvetica Neue Thin (to replace Gotham) */\n.thin-font_de5b {\n font-family: "Segoe UI", "Helvetica Neue", Helvetica, Arial, sans-serif;\n font-size: var(--ring-font-size);\n font-weight: 100; /* Renders Helvetica Neue UltraLight on OS X */\n}\n\n.monospace-font_ac33 {\n font-family: var(--ring-font-family-monospace);\n font-size: var(--ring-font-size-smaller);\n}\n\n.ellipsis_e43b {\n overflow: hidden;\n\n white-space: nowrap;\n text-overflow: ellipsis;\n}\n\n.resetButton_ddd2 {\n overflow: visible;\n\n padding: 0;\n\n text-align: left;\n\n color: inherit;\n border: 0;\n\n background-color: transparent;\n\n font: inherit;\n}\n\n.resetButton_ddd2::-moz-focus-inner {\n padding: 0;\n\n border: 0;\n }\n\n/* Note: footer also has top margin which isn\'t taken into account here */\n\n/* Media breakpoints (minimal values) */\n\n/* Media queries */\n',"",{version:3,sources:["webpack://./node_modules/@jetbrains/ring-ui/components/global/global.css"],names:[],mappings:"AAAA,6DAA6D;;AAE7D;;;CAGC;;AAIC;IACE,cAAc;IACd,WAAW;;IAEX,WAAW;EACb;;AAGF;EACE,oCAAoC;EACpC,gCAAgC;EAChC,oCAAoC;AACtC;;AAEA;;EAGE,0CAA0C;AAC5C;;AAEA;;EAGE,wCAAwC;AAC1C;;AAEA;;EAGE,2CAA2C;AAC7C;;AAEA;;EAGE,uCAAuC;AACzC;;AAEA;;EAGE,2CAA2C;AAC7C;;AAEA,8BAA8B;AAC9B,oEAAoE;AACpE;EACE,uEAAuE;EACvE,gCAAgC;EAChC,gBAAgB,EAAE,+CAA+C;AACnE;;AAEA;EACE,8CAA8C;EAC9C,wCAAwC;AAC1C;;AAEA;EACE,gBAAgB;;EAEhB,mBAAmB;EACnB,uBAAuB;AACzB;;AAEA;EACE,iBAAiB;;EAEjB,UAAU;;EAEV,gBAAgB;;EAEhB,cAAc;EACd,SAAS;;EAET,6BAA6B;;EAE7B,aAAa;AAOf;;AALE;IACE,UAAU;;IAEV,SAAS;EACX;;AAGF,yEAAyE;;AAGzE,uCAAuC;;AAKvC,kBAAkB",sourcesContent:['/* https://readymag.com/artemtiunov/RingUILanguage/colours/ */\n\n/*\nUnit shouldn\'t be CSS custom property because it is not intended to change\nAlso it won\'t form in FF47 https://bugzilla.mozilla.org/show_bug.cgi?id=594933\n*/\n@value unit: 8px;\n\n.clearfix {\n &::after {\n display: block;\n clear: both;\n\n content: "";\n }\n}\n\n.font {\n font-family: var(--ring-font-family);\n font-size: var(--ring-font-size);\n line-height: var(--ring-line-height);\n}\n\n.font-lower {\n composes: font;\n\n line-height: var(--ring-line-height-lower);\n}\n\n.font-smaller {\n composes: font-lower;\n\n font-size: var(--ring-font-size-smaller);\n}\n\n.font-smaller-lower {\n composes: font-smaller;\n\n line-height: var(--ring-line-height-lowest);\n}\n\n.font-larger-lower {\n composes: font-lower;\n\n font-size: var(--ring-font-size-larger);\n}\n\n.font-larger {\n composes: font-larger-lower;\n\n line-height: var(--ring-line-height-taller);\n}\n\n/* To be used at large sizes */\n/* As close as possible to Helvetica Neue Thin (to replace Gotham) */\n.thin-font {\n font-family: "Segoe UI", "Helvetica Neue", Helvetica, Arial, sans-serif;\n font-size: var(--ring-font-size);\n font-weight: 100; /* Renders Helvetica Neue UltraLight on OS X */\n}\n\n.monospace-font {\n font-family: var(--ring-font-family-monospace);\n font-size: var(--ring-font-size-smaller);\n}\n\n.ellipsis {\n overflow: hidden;\n\n white-space: nowrap;\n text-overflow: ellipsis;\n}\n\n.resetButton {\n overflow: visible;\n\n padding: 0;\n\n text-align: left;\n\n color: inherit;\n border: 0;\n\n background-color: transparent;\n\n font: inherit;\n\n &::-moz-focus-inner {\n padding: 0;\n\n border: 0;\n }\n}\n\n/* Note: footer also has top margin which isn\'t taken into account here */\n@value footer-height: calc(unit * 8);\n\n/* Media breakpoints (minimal values) */\n@value breakpoint-small: 640px;\n@value breakpoint-middle: 960px;\n@value breakpoint-large: 1200px;\n\n/* Media queries */\n@value extra-small-screen-media: (max-width: calc(breakpoint-small - 1px));\n@value small-screen-media: (min-width: breakpoint-small) and (max-width: calc(breakpoint-middle - 1px));\n@value middle-screen-media: (min-width: breakpoint-middle) and (max-width: calc(breakpoint-large - 1px));\n@value large-screen-media: (min-width: breakpoint-large);\n'],sourceRoot:""}]),a.locals={unit:"8px","footer-height":"64px","breakpoint-small":"640px","breakpoint-middle":"960px","breakpoint-large":"1200px","extra-small-screen-media":"(max-width: 639px)","small-screen-media":"(min-width: 640px) and (max-width: 959px)","middle-screen-media":"(min-width: 960px) and (max-width: 1199px)","large-screen-media":"(min-width: 1200px)",clearfix:"clearfix_c694",font:"font_a1f6","font-lower":"font-lower_c3c9 font_a1f6","font-smaller":"font-smaller_d963 font-lower_c3c9 font_a1f6","font-smaller-lower":"font-smaller-lower_ff5f font-smaller_d963 font-lower_c3c9 font_a1f6","font-larger-lower":"font-larger-lower_b336 font-lower_c3c9 font_a1f6","font-larger":"font-larger_f035 font-larger-lower_b336 font-lower_c3c9 font_a1f6","thin-font":"thin-font_de5b","monospace-font":"monospace-font_ac33",ellipsis:"ellipsis_e43b",resetButton:"resetButton_ddd2"};const l=a},5280:(e,n,t)=>{"use strict";t.d(n,{A:()=>l});var r=t(1404),o=t.n(r),i=t(7156),a=t.n(i)()(o());a.push([e.id,'/* stylelint-disable color-no-hex */\n\n.light_f331,\n:root {\n --ring-unit: 8px;\n\n /* Element */\n --ring-line-components: 223, 229, 235;\n --ring-line-color: rgb(var(--ring-line-components)); /* #dfe5eb */\n --ring-borders-components: 197, 209, 219;\n --ring-borders-color: rgb(var(--ring-borders-components)); /* #c5d1db */\n --ring-icon-components: 184, 209, 229;\n --ring-icon-color: rgb(var(--ring-icon-components)); /* #b8d1e5 */\n --ring-icon-secondary-components: 153, 153, 153;\n --ring-icon-secondary-color: rgb(var(--ring-icon-secondary-components)); /* #999 */\n --ring-border-disabled-components: 232, 232, 232;\n --ring-border-disabled-color: rgb(var(--ring-border-disabled-components)); /* #e8e8e8 */\n --ring-border-selected-disabled-components: 212, 212, 212;\n --ring-border-selected-disabled-color: rgb(var(--ring-border-selected-disabled-components)); /* #d4d4d4 */\n --ring-border-unselected-disabled-components: 232, 232, 232;\n --ring-border-unselected-disabled-color: rgb(var(--ring-border-unselected-disabled-components)); /* #e8e8e8 */ /* TODO remove in 6.0 */\n --ring-icon-disabled-components: 212, 212, 212;\n --ring-icon-disabled-color: rgb(var(--ring-icon-disabled-components)); /* #d4d4d4 */\n --ring-border-hover-components: 128, 198, 255;\n --ring-border-hover-color: rgb(var(--ring-border-hover-components)); /* #80c6ff */\n --ring-icon-hover-components: var(--ring-link-hover-color);\n --ring-icon-hover-color: var(--ring-link-hover-color);\n --ring-main-components: 0, 128, 229;\n --ring-main-color: rgb(var(--ring-main-components)); /* #0080e5 */\n --ring-action-link-components: var(--ring-main-components);\n --ring-action-link-color: rgb(var(--ring-main-components)); /* #0080e5 */\n --ring-main-hover-components: 0, 112, 204;\n --ring-main-hover-color: rgb(var(--ring-main-hover-components)); /* #0070cc */\n --ring-icon-error-components: 219, 88, 96;\n --ring-icon-error-color: rgb(var(--ring-icon-error-components)); /* #db5860 */\n --ring-icon-warning-components: 237, 162, 0;\n --ring-icon-warning-color: rgb(var(--ring-icon-warning-components)); /* #eda200 */\n --ring-icon-success-components: 89, 168, 105;\n --ring-icon-success-color: rgb(var(--ring-icon-success-components)); /* #59a869 */\n --ring-pale-control-components: 207, 219, 229;\n --ring-pale-control-color: rgb(var(--ring-pale-control-components)); /* #cfdbe5 */\n --ring-popup-border-components: 0, 28, 54;\n --ring-popup-border-color: var(--ring-line-color);\n --ring-popup-shadow-components: rgba(var(--ring-popup-border-components), 0.1);\n --ring-popup-shadow-color: rgba(var(--ring-popup-border-components), 0.1);\n --ring-popup-secondary-shadow-color: rgba(var(--ring-popup-border-components), 0.04);\n --ring-message-shadow-color: rgba(var(--ring-popup-border-components), 0.3);\n --ring-pinned-shadow-components: 115, 117, 119;\n --ring-pinned-shadow-color: rgb(var(--ring-pinned-shadow-components)); /* #737577 */\n --ring-button-danger-hover-components: var(--ring-icon-error-color);\n --ring-button-danger-hover-color: var(--ring-icon-error-color);\n --ring-button-primary-border-components: 0, 98, 178;\n --ring-button-primary-border-color: rgb(var(--ring-button-primary-border-components)); /* #0062b2 */\n --ring-popup-shadow: 0 2px 8px var(--ring-popup-shadow-color), 0 1px 2px var(--ring-popup-secondary-shadow-color);\n --ring-dialog-shadow: 0 4px 24px var(--ring-popup-shadow-color), 0 2px 6px var(--ring-popup-secondary-shadow-color);\n\n /* Text */\n --ring-search-components: 102, 158, 204;\n --ring-search-color: rgb(var(--ring-search-components)); /* #669ecc */\n --ring-hint-components: 64, 99, 128;\n --ring-hint-color: rgb(var(--ring-hint-components)); /* #406380 */\n --ring-link-components: 15, 91, 153;\n --ring-link-color: rgb(var(--ring-link-components)); /* #0f5b99 */\n --ring-link-hover-components: 255, 0, 140;\n --ring-link-hover-color: rgb(var(--ring-link-hover-components)); /* #ff008c */\n --ring-error-components: 169, 15, 26;\n --ring-error-color: rgb(var(--ring-error-components)); /* #a90f1a */\n --ring-warning-components: 178, 92, 0;\n --ring-warning-color: rgb(var(--ring-warning-components)); /* #b25c00 */\n --ring-success-components: 12, 117, 35;\n --ring-success-color: rgb(var(--ring-success-components)); /* #0c7523 */\n --ring-text-components: 31, 35, 38;\n --ring-text-color: rgb(var(--ring-text-components)); /* #1f2326 */\n --ring-active-text-color: var(--ring-text-color);\n --ring-white-text-components: 255, 255, 255;\n --ring-white-text-color: rgb(var(--ring-white-text-components)); /* #fff */\n --ring-heading-color: var(--ring-text-color);\n --ring-secondary-components: 115, 117, 119;\n --ring-secondary-color: rgb(var(--ring-secondary-components)); /* #737577 */\n --ring-disabled-components: 153, 153, 153;\n --ring-disabled-color: rgb(var(--ring-disabled-components)); /* #999 */\n\n /* Background */\n --ring-content-background-components: 255, 255, 255;\n --ring-content-background-color: rgb(var(--ring-content-background-components)); /* #fff */\n --ring-popup-background-components: 255, 255, 255;\n --ring-popup-background-color: rgb(var(--ring-popup-background-components)); /* #fff */\n --ring-sidebar-background-components: 247, 249, 250;\n --ring-sidebar-background-color: rgb(var(--ring-sidebar-background-components)); /* #f7f9fa */\n --ring-selected-background-components: 212, 237, 255;\n --ring-selected-background-color: rgb(var(--ring-selected-background-components)); /* #d4edff */\n --ring-hover-background-components: 235, 246, 255;\n --ring-hover-background-color: rgb(var(--ring-hover-background-components)); /* #ebf6ff */\n --ring-navigation-background-components: 255, 255, 255;\n --ring-navigation-background-color: rgb(var(--ring-navigation-background-components)); /* #fff */\n --ring-tag-background-components: 230, 236, 242;\n --ring-tag-background-color: rgb(var(--ring-tag-background-components)); /* #e6ecf2 */\n --ring-tag-hover-background-components: 211, 218, 224;\n --ring-tag-hover-background-color: rgb(var(--ring-tag-hover-background-components)); /* #d3dae0 */\n --ring-removed-background-components: 255, 213, 203;\n --ring-removed-background-color: rgb(var(--ring-removed-background-components)); /* #ffd5cb */\n --ring-warning-background-components: 250, 236, 205;\n --ring-warning-background-color: rgb(var(--ring-warning-background-components)); /* #faeccd */\n --ring-added-background-components: 216, 240, 216;\n --ring-added-background-color: rgb(var(--ring-added-background-components)); /* #d8f0d8 */\n --ring-disabled-background-components: 245, 245, 245;\n --ring-disabled-background-color: rgb(var(--ring-disabled-background-components)); /* #f5f5f5 */\n --ring-disabled-selected-background-components: 232, 232, 232;\n --ring-disabled-selected-background-color: rgb(var(--ring-disabled-selected-background-components)); /* #e8e8e8 */\n --ring-button-danger-active-components: 255, 231, 232;\n --ring-button-danger-active-color: rgb(var(--ring-button-danger-active-components)); /* #ffe7e8 */\n --ring-button-loader-background-components: 51, 163, 255;\n --ring-button-loader-background: rgb(var(--ring-button-loader-background-components)); /* #33a3ff */\n --ring-button-primary-background-components: 26, 152, 255;\n --ring-button-primary-background-color: rgb(var(--ring-button-primary-background-components)); /* #1a98ff */\n --ring-table-loader-background-color: rgba(var(--ring-content-background-components), 0.5); /* #ffffff80 */\n\n /* Code */\n --ring-code-background-color: var(--ring-content-background-color);\n --ring-code-components: 0, 0, 0;\n --ring-code-color: rgb(var(--ring-code-components)); /* #000 */\n --ring-code-comment-components: 112, 112, 112;\n --ring-code-comment-color: rgb(var(--ring-code-comment-components)); /* #707070 */\n --ring-code-meta-components: 112, 112, 112;\n --ring-code-meta-color: rgb(var(--ring-code-meta-components)); /* #707070 */\n --ring-code-keyword-components: 0, 0, 128;\n --ring-code-keyword-color: rgb(var(--ring-code-keyword-components)); /* #000080 */\n --ring-code-tag-background-components: 239, 239, 239;\n --ring-code-tag-background-color: rgb(var(--ring-code-tag-background-components)); /* #efefef */\n --ring-code-tag-color: var(--ring-code-keyword-color);\n --ring-code-tag-font-weight: bold;\n --ring-code-field-components: 102, 14, 122;\n --ring-code-field-color: rgb(var(--ring-code-field-components)); /* #660e7a */\n --ring-code-attribute-components: 0, 0, 255;\n --ring-code-attribute-color: rgb(var(--ring-code-attribute-components)); /* #00f */\n --ring-code-number-color: var(--ring-code-attribute-color);\n --ring-code-string-components: 0, 122, 0;\n --ring-code-string-color: rgb(var(--ring-code-string-components)); /* #007a00 */\n --ring-code-addition-components: 170, 222, 170;\n --ring-code-addition-color: rgb(var(--ring-code-addition-components)); /* #aadeaa */\n --ring-code-deletion-components: 200, 200, 200;\n --ring-code-deletion-color: rgb(var(--ring-code-deletion-components)); /* #c8c8c8 */\n\n /* Metrics */\n --ring-border-radius: 4px;\n --ring-border-radius-small: 2px;\n --ring-font-size-larger: 15px;\n --ring-font-size: 14px;\n --ring-font-size-smaller: 12px;\n --ring-line-height-taller: 21px;\n --ring-line-height: 20px;\n --ring-line-height-lower: 18px;\n --ring-line-height-lowest: 16px;\n --ring-ease: 0.3s ease-out;\n --ring-fast-ease: 0.15s ease-out;\n --ring-font-family: system-ui, -apple-system, Segoe UI, Roboto, Noto Sans, Ubuntu, Cantarell, Helvetica Neue, Arial, sans-serif;\n --ring-font-family-monospace:\n Menlo,\n "Bitstream Vera Sans Mono",\n "Ubuntu Mono",\n Consolas,\n "Courier New",\n Courier,\n monospace;\n\n /* Common z-index-values */\n\n /* Invisible element is an absolutely positioned element which should be below */\n /* all other elements on the page */\n --ring-invisible-element-z-index: -1;\n\n /* z-index for position: fixed elements */\n --ring-fixed-z-index: 1;\n\n /* Elements that should overlay all other elements on the page */\n --ring-overlay-z-index: 5;\n\n /* Alerts should de displayed above overlays */\n --ring-alert-z-index: 6;\n}\n',"",{version:3,sources:["webpack://./node_modules/@jetbrains/ring-ui/components/global/variables.css"],names:[],mappings:"AAAA,mCAAmC;;AAEnC;;EAEE,gBAAgB;;EAEhB,YAAY;EACZ,qCAAqC;EACrC,mDAAmD,EAAE,YAAY;EACjE,wCAAwC;EACxC,yDAAyD,EAAE,YAAY;EACvE,qCAAqC;EACrC,mDAAmD,EAAE,YAAY;EACjE,+CAA+C;EAC/C,uEAAuE,EAAE,SAAS;EAClF,gDAAgD;EAChD,yEAAyE,EAAE,YAAY;EACvF,yDAAyD;EACzD,2FAA2F,EAAE,YAAY;EACzG,2DAA2D;EAC3D,+FAA+F,EAAE,YAAY,EAAE,uBAAuB;EACtI,8CAA8C;EAC9C,qEAAqE,EAAE,YAAY;EACnF,6CAA6C;EAC7C,mEAAmE,EAAE,YAAY;EACjF,0DAA0D;EAC1D,qDAAqD;EACrD,mCAAmC;EACnC,mDAAmD,EAAE,YAAY;EACjE,0DAA0D;EAC1D,0DAA0D,EAAE,YAAY;EACxE,yCAAyC;EACzC,+DAA+D,EAAE,YAAY;EAC7E,yCAAyC;EACzC,+DAA+D,EAAE,YAAY;EAC7E,2CAA2C;EAC3C,mEAAmE,EAAE,YAAY;EACjF,4CAA4C;EAC5C,mEAAmE,EAAE,YAAY;EACjF,6CAA6C;EAC7C,mEAAmE,EAAE,YAAY;EACjF,yCAAyC;EACzC,iDAAiD;EACjD,8EAA8E;EAC9E,yEAAyE;EACzE,oFAAoF;EACpF,2EAA2E;EAC3E,8CAA8C;EAC9C,qEAAqE,EAAE,YAAY;EACnF,mEAAmE;EACnE,8DAA8D;EAC9D,mDAAmD;EACnD,qFAAqF,EAAE,YAAY;EACnG,iHAAiH;EACjH,mHAAmH;;EAEnH,SAAS;EACT,uCAAuC;EACvC,uDAAuD,EAAE,YAAY;EACrE,mCAAmC;EACnC,mDAAmD,EAAE,YAAY;EACjE,mCAAmC;EACnC,mDAAmD,EAAE,YAAY;EACjE,yCAAyC;EACzC,+DAA+D,EAAE,YAAY;EAC7E,oCAAoC;EACpC,qDAAqD,EAAE,YAAY;EACnE,qCAAqC;EACrC,yDAAyD,EAAE,YAAY;EACvE,sCAAsC;EACtC,yDAAyD,EAAE,YAAY;EACvE,kCAAkC;EAClC,mDAAmD,EAAE,YAAY;EACjE,gDAAgD;EAChD,2CAA2C;EAC3C,+DAA+D,EAAE,SAAS;EAC1E,4CAA4C;EAC5C,0CAA0C;EAC1C,6DAA6D,EAAE,YAAY;EAC3E,yCAAyC;EACzC,2DAA2D,EAAE,SAAS;;EAEtE,eAAe;EACf,mDAAmD;EACnD,+EAA+E,EAAE,SAAS;EAC1F,iDAAiD;EACjD,2EAA2E,EAAE,SAAS;EACtF,mDAAmD;EACnD,+EAA+E,EAAE,YAAY;EAC7F,oDAAoD;EACpD,iFAAiF,EAAE,YAAY;EAC/F,iDAAiD;EACjD,2EAA2E,EAAE,YAAY;EACzF,sDAAsD;EACtD,qFAAqF,EAAE,SAAS;EAChG,+CAA+C;EAC/C,uEAAuE,EAAE,YAAY;EACrF,qDAAqD;EACrD,mFAAmF,EAAE,YAAY;EACjG,mDAAmD;EACnD,+EAA+E,EAAE,YAAY;EAC7F,mDAAmD;EACnD,+EAA+E,EAAE,YAAY;EAC7F,iDAAiD;EACjD,2EAA2E,EAAE,YAAY;EACzF,oDAAoD;EACpD,iFAAiF,EAAE,YAAY;EAC/F,6DAA6D;EAC7D,mGAAmG,EAAE,YAAY;EACjH,qDAAqD;EACrD,mFAAmF,EAAE,YAAY;EACjG,wDAAwD;EACxD,qFAAqF,EAAE,YAAY;EACnG,yDAAyD;EACzD,6FAA6F,EAAE,YAAY;EAC3G,0FAA0F,EAAE,cAAc;;EAE1G,SAAS;EACT,kEAAkE;EAClE,+BAA+B;EAC/B,mDAAmD,EAAE,SAAS;EAC9D,6CAA6C;EAC7C,mEAAmE,EAAE,YAAY;EACjF,0CAA0C;EAC1C,6DAA6D,EAAE,YAAY;EAC3E,yCAAyC;EACzC,mEAAmE,EAAE,YAAY;EACjF,oDAAoD;EACpD,iFAAiF,EAAE,YAAY;EAC/F,qDAAqD;EACrD,iCAAiC;EACjC,0CAA0C;EAC1C,+DAA+D,EAAE,YAAY;EAC7E,2CAA2C;EAC3C,uEAAuE,EAAE,SAAS;EAClF,0DAA0D;EAC1D,wCAAwC;EACxC,iEAAiE,EAAE,YAAY;EAC/E,8CAA8C;EAC9C,qEAAqE,EAAE,YAAY;EACnF,8CAA8C;EAC9C,qEAAqE,EAAE,YAAY;;EAEnF,YAAY;EACZ,yBAAyB;EACzB,+BAA+B;EAC/B,6BAA6B;EAC7B,sBAAsB;EACtB,8BAA8B;EAC9B,+BAA+B;EAC/B,wBAAwB;EACxB,8BAA8B;EAC9B,+BAA+B;EAC/B,0BAA0B;EAC1B,gCAAgC;EAChC,+HAAgD;EAChD;;;;;;;aAOW;;EAEX,0BAA0B;;EAE1B,gFAAgF;EAChF,mCAAmC;EACnC,oCAAoC;;EAEpC,yCAAyC;EACzC,uBAAuB;;EAEvB,gEAAgE;EAChE,yBAAyB;;EAEzB,8CAA8C;EAC9C,uBAAuB;AACzB",sourcesContent:['/* stylelint-disable color-no-hex */\n\n.light,\n:root {\n --ring-unit: 8px;\n\n /* Element */\n --ring-line-components: 223, 229, 235;\n --ring-line-color: rgb(var(--ring-line-components)); /* #dfe5eb */\n --ring-borders-components: 197, 209, 219;\n --ring-borders-color: rgb(var(--ring-borders-components)); /* #c5d1db */\n --ring-icon-components: 184, 209, 229;\n --ring-icon-color: rgb(var(--ring-icon-components)); /* #b8d1e5 */\n --ring-icon-secondary-components: 153, 153, 153;\n --ring-icon-secondary-color: rgb(var(--ring-icon-secondary-components)); /* #999 */\n --ring-border-disabled-components: 232, 232, 232;\n --ring-border-disabled-color: rgb(var(--ring-border-disabled-components)); /* #e8e8e8 */\n --ring-border-selected-disabled-components: 212, 212, 212;\n --ring-border-selected-disabled-color: rgb(var(--ring-border-selected-disabled-components)); /* #d4d4d4 */\n --ring-border-unselected-disabled-components: 232, 232, 232;\n --ring-border-unselected-disabled-color: rgb(var(--ring-border-unselected-disabled-components)); /* #e8e8e8 */ /* TODO remove in 6.0 */\n --ring-icon-disabled-components: 212, 212, 212;\n --ring-icon-disabled-color: rgb(var(--ring-icon-disabled-components)); /* #d4d4d4 */\n --ring-border-hover-components: 128, 198, 255;\n --ring-border-hover-color: rgb(var(--ring-border-hover-components)); /* #80c6ff */\n --ring-icon-hover-components: var(--ring-link-hover-color);\n --ring-icon-hover-color: var(--ring-link-hover-color);\n --ring-main-components: 0, 128, 229;\n --ring-main-color: rgb(var(--ring-main-components)); /* #0080e5 */\n --ring-action-link-components: var(--ring-main-components);\n --ring-action-link-color: rgb(var(--ring-main-components)); /* #0080e5 */\n --ring-main-hover-components: 0, 112, 204;\n --ring-main-hover-color: rgb(var(--ring-main-hover-components)); /* #0070cc */\n --ring-icon-error-components: 219, 88, 96;\n --ring-icon-error-color: rgb(var(--ring-icon-error-components)); /* #db5860 */\n --ring-icon-warning-components: 237, 162, 0;\n --ring-icon-warning-color: rgb(var(--ring-icon-warning-components)); /* #eda200 */\n --ring-icon-success-components: 89, 168, 105;\n --ring-icon-success-color: rgb(var(--ring-icon-success-components)); /* #59a869 */\n --ring-pale-control-components: 207, 219, 229;\n --ring-pale-control-color: rgb(var(--ring-pale-control-components)); /* #cfdbe5 */\n --ring-popup-border-components: 0, 28, 54;\n --ring-popup-border-color: var(--ring-line-color);\n --ring-popup-shadow-components: rgba(var(--ring-popup-border-components), 0.1);\n --ring-popup-shadow-color: rgba(var(--ring-popup-border-components), 0.1);\n --ring-popup-secondary-shadow-color: rgba(var(--ring-popup-border-components), 0.04);\n --ring-message-shadow-color: rgba(var(--ring-popup-border-components), 0.3);\n --ring-pinned-shadow-components: 115, 117, 119;\n --ring-pinned-shadow-color: rgb(var(--ring-pinned-shadow-components)); /* #737577 */\n --ring-button-danger-hover-components: var(--ring-icon-error-color);\n --ring-button-danger-hover-color: var(--ring-icon-error-color);\n --ring-button-primary-border-components: 0, 98, 178;\n --ring-button-primary-border-color: rgb(var(--ring-button-primary-border-components)); /* #0062b2 */\n --ring-popup-shadow: 0 2px 8px var(--ring-popup-shadow-color), 0 1px 2px var(--ring-popup-secondary-shadow-color);\n --ring-dialog-shadow: 0 4px 24px var(--ring-popup-shadow-color), 0 2px 6px var(--ring-popup-secondary-shadow-color);\n\n /* Text */\n --ring-search-components: 102, 158, 204;\n --ring-search-color: rgb(var(--ring-search-components)); /* #669ecc */\n --ring-hint-components: 64, 99, 128;\n --ring-hint-color: rgb(var(--ring-hint-components)); /* #406380 */\n --ring-link-components: 15, 91, 153;\n --ring-link-color: rgb(var(--ring-link-components)); /* #0f5b99 */\n --ring-link-hover-components: 255, 0, 140;\n --ring-link-hover-color: rgb(var(--ring-link-hover-components)); /* #ff008c */\n --ring-error-components: 169, 15, 26;\n --ring-error-color: rgb(var(--ring-error-components)); /* #a90f1a */\n --ring-warning-components: 178, 92, 0;\n --ring-warning-color: rgb(var(--ring-warning-components)); /* #b25c00 */\n --ring-success-components: 12, 117, 35;\n --ring-success-color: rgb(var(--ring-success-components)); /* #0c7523 */\n --ring-text-components: 31, 35, 38;\n --ring-text-color: rgb(var(--ring-text-components)); /* #1f2326 */\n --ring-active-text-color: var(--ring-text-color);\n --ring-white-text-components: 255, 255, 255;\n --ring-white-text-color: rgb(var(--ring-white-text-components)); /* #fff */\n --ring-heading-color: var(--ring-text-color);\n --ring-secondary-components: 115, 117, 119;\n --ring-secondary-color: rgb(var(--ring-secondary-components)); /* #737577 */\n --ring-disabled-components: 153, 153, 153;\n --ring-disabled-color: rgb(var(--ring-disabled-components)); /* #999 */\n\n /* Background */\n --ring-content-background-components: 255, 255, 255;\n --ring-content-background-color: rgb(var(--ring-content-background-components)); /* #fff */\n --ring-popup-background-components: 255, 255, 255;\n --ring-popup-background-color: rgb(var(--ring-popup-background-components)); /* #fff */\n --ring-sidebar-background-components: 247, 249, 250;\n --ring-sidebar-background-color: rgb(var(--ring-sidebar-background-components)); /* #f7f9fa */\n --ring-selected-background-components: 212, 237, 255;\n --ring-selected-background-color: rgb(var(--ring-selected-background-components)); /* #d4edff */\n --ring-hover-background-components: 235, 246, 255;\n --ring-hover-background-color: rgb(var(--ring-hover-background-components)); /* #ebf6ff */\n --ring-navigation-background-components: 255, 255, 255;\n --ring-navigation-background-color: rgb(var(--ring-navigation-background-components)); /* #fff */\n --ring-tag-background-components: 230, 236, 242;\n --ring-tag-background-color: rgb(var(--ring-tag-background-components)); /* #e6ecf2 */\n --ring-tag-hover-background-components: 211, 218, 224;\n --ring-tag-hover-background-color: rgb(var(--ring-tag-hover-background-components)); /* #d3dae0 */\n --ring-removed-background-components: 255, 213, 203;\n --ring-removed-background-color: rgb(var(--ring-removed-background-components)); /* #ffd5cb */\n --ring-warning-background-components: 250, 236, 205;\n --ring-warning-background-color: rgb(var(--ring-warning-background-components)); /* #faeccd */\n --ring-added-background-components: 216, 240, 216;\n --ring-added-background-color: rgb(var(--ring-added-background-components)); /* #d8f0d8 */\n --ring-disabled-background-components: 245, 245, 245;\n --ring-disabled-background-color: rgb(var(--ring-disabled-background-components)); /* #f5f5f5 */\n --ring-disabled-selected-background-components: 232, 232, 232;\n --ring-disabled-selected-background-color: rgb(var(--ring-disabled-selected-background-components)); /* #e8e8e8 */\n --ring-button-danger-active-components: 255, 231, 232;\n --ring-button-danger-active-color: rgb(var(--ring-button-danger-active-components)); /* #ffe7e8 */\n --ring-button-loader-background-components: 51, 163, 255;\n --ring-button-loader-background: rgb(var(--ring-button-loader-background-components)); /* #33a3ff */\n --ring-button-primary-background-components: 26, 152, 255;\n --ring-button-primary-background-color: rgb(var(--ring-button-primary-background-components)); /* #1a98ff */\n --ring-table-loader-background-color: rgba(var(--ring-content-background-components), 0.5); /* #ffffff80 */\n\n /* Code */\n --ring-code-background-color: var(--ring-content-background-color);\n --ring-code-components: 0, 0, 0;\n --ring-code-color: rgb(var(--ring-code-components)); /* #000 */\n --ring-code-comment-components: 112, 112, 112;\n --ring-code-comment-color: rgb(var(--ring-code-comment-components)); /* #707070 */\n --ring-code-meta-components: 112, 112, 112;\n --ring-code-meta-color: rgb(var(--ring-code-meta-components)); /* #707070 */\n --ring-code-keyword-components: 0, 0, 128;\n --ring-code-keyword-color: rgb(var(--ring-code-keyword-components)); /* #000080 */\n --ring-code-tag-background-components: 239, 239, 239;\n --ring-code-tag-background-color: rgb(var(--ring-code-tag-background-components)); /* #efefef */\n --ring-code-tag-color: var(--ring-code-keyword-color);\n --ring-code-tag-font-weight: bold;\n --ring-code-field-components: 102, 14, 122;\n --ring-code-field-color: rgb(var(--ring-code-field-components)); /* #660e7a */\n --ring-code-attribute-components: 0, 0, 255;\n --ring-code-attribute-color: rgb(var(--ring-code-attribute-components)); /* #00f */\n --ring-code-number-color: var(--ring-code-attribute-color);\n --ring-code-string-components: 0, 122, 0;\n --ring-code-string-color: rgb(var(--ring-code-string-components)); /* #007a00 */\n --ring-code-addition-components: 170, 222, 170;\n --ring-code-addition-color: rgb(var(--ring-code-addition-components)); /* #aadeaa */\n --ring-code-deletion-components: 200, 200, 200;\n --ring-code-deletion-color: rgb(var(--ring-code-deletion-components)); /* #c8c8c8 */\n\n /* Metrics */\n --ring-border-radius: 4px;\n --ring-border-radius-small: 2px;\n --ring-font-size-larger: 15px;\n --ring-font-size: 14px;\n --ring-font-size-smaller: 12px;\n --ring-line-height-taller: 21px;\n --ring-line-height: 20px;\n --ring-line-height-lower: 18px;\n --ring-line-height-lowest: 16px;\n --ring-ease: 0.3s ease-out;\n --ring-fast-ease: 0.15s ease-out;\n --ring-font-family: system-ui, Arial, sans-serif;\n --ring-font-family-monospace:\n Menlo,\n "Bitstream Vera Sans Mono",\n "Ubuntu Mono",\n Consolas,\n "Courier New",\n Courier,\n monospace;\n\n /* Common z-index-values */\n\n /* Invisible element is an absolutely positioned element which should be below */\n /* all other elements on the page */\n --ring-invisible-element-z-index: -1;\n\n /* z-index for position: fixed elements */\n --ring-fixed-z-index: 1;\n\n /* Elements that should overlay all other elements on the page */\n --ring-overlay-z-index: 5;\n\n /* Alerts should de displayed above overlays */\n --ring-alert-z-index: 6;\n}\n'],sourceRoot:""}]),a.locals={light:"light_f331"};const l=a},9173:(e,n,t)=>{"use strict";t.d(n,{A:()=>l});var r=t(1404),o=t.n(r),i=t(7156),a=t.n(i)()(o());a.push([e.id,"/* stylelint-disable color-no-hex */\n\n.ring-ui-theme-dark,\n.dark_d4a9,\n:root.dark_d4a9 {\n --ring-line-components: 71, 81, 89;\n --ring-line-color: rgb(var(--ring-line-components)); /* #475159 */\n --ring-borders-components: 64, 99, 128;\n --ring-borders-color: rgb(var(--ring-borders-components)); /* #406380 */\n --ring-icon-components: 128, 146, 157;\n --ring-icon-color: rgb(var(--ring-icon-components)); /* #80929d */\n --ring-icon-secondary-components: 128, 146, 157;\n --ring-icon-secondary-color: rgb(var(--ring-icon-secondary-components)); /* #80929d */\n --ring-border-disabled-components: 54, 54, 54;\n --ring-border-disabled-color: rgb(var(--ring-border-disabled-components)); /* #363636 */\n --ring-border-selected-disabled-components: 54, 54, 54;\n --ring-border-selected-disabled-color: rgb(var(--ring-border-selected-disabled-components)); /* #363636 */\n --ring-border-unselected-disabled-components: 54, 54, 54;\n --ring-border-unselected-disabled-color: rgb(var(--ring-border-unselected-disabled-components)); /* #363636 */ /* TODO remove in 6.0 */\n --ring-icon-disabled-components: 80, 82, 83;\n --ring-icon-disabled-color: rgb(var(--ring-icon-disabled-components)); /* #505253 */\n --ring-border-hover-components: 112, 177, 230;\n --ring-border-hover-color: rgb(var(--ring-border-hover-components)); /* #70b1e6 */\n --ring-main-components: 0, 142, 255;\n --ring-main-color: rgb(var(--ring-main-components)); /* #008eff */\n --ring-action-link-components: var(--ring-main-components);\n --ring-action-link-color: rgb(var(--ring-main-components)); /* #008eff */\n --ring-main-hover-components: 0, 126, 229;\n --ring-main-hover-color: rgb(var(--ring-main-hover-components)); /* #007ee5 */\n --ring-icon-error-components: 219, 88, 96;\n --ring-icon-error-color: rgb(var(--ring-icon-error-components)); /* #db5860 */\n --ring-icon-warning-components: 237, 162, 0;\n --ring-icon-warning-color: rgb(var(--ring-icon-warning-components)); /* #eda200 */\n --ring-icon-success-components: 71, 212, 100;\n --ring-icon-success-color: rgb(var(--ring-icon-success-components)); /* #47d464 */\n --ring-popup-border-components: 0, 42, 76;\n --ring-popup-border-color: rgba(var(--ring-popup-border-components), 0.1);\n --ring-popup-shadow-color: rgba(var(--ring-popup-border-components), 0.15);\n --ring-message-shadow-color: rgba(var(--ring-popup-border-components), 0.3);\n --ring-pinned-shadow-components: 0, 0, 0;\n --ring-pinned-shadow-color: rgb(var(--ring-pinned-shadow-components)); /* #000 */\n --ring-button-danger-hover-color: var(--ring-error-color);\n --ring-button-primary-border-components: 128, 198, 255;\n --ring-button-primary-border-color: rgb(var(--ring-button-primary-border-components)); /* #80c6ff */\n\n /* Text */\n --ring-hint-components: 128, 146, 157;\n --ring-hint-color: rgb(var(--ring-hint-components)); /* #80929d */\n --ring-link-components: 112, 177, 230;\n --ring-link-color: rgb(var(--ring-link-components)); /* #70b1e6 */\n --ring-error-components: 219, 88, 96;\n --ring-error-color: rgb(var(--ring-error-components)); /* #db5860 */\n --ring-warning-components: 237, 162, 0;\n --ring-warning-color: rgb(var(--ring-warning-components)); /* #eda200 */\n --ring-success-components: 71, 212, 100;\n --ring-success-color: rgb(var(--ring-success-components)); /* #47d464 */\n --ring-text-components: 187, 187, 187;\n --ring-text-color: rgb(var(--ring-text-components)); /* #bbb */\n --ring-active-text-components: 255, 255, 255;\n --ring-active-text-color: rgb(var(--ring-active-text-components)); /* #fff */\n --ring-heading-color: var(--ring-text-color);\n --ring-secondary-components: 128, 146, 157;\n --ring-secondary-color: rgb(var(--ring-secondary-components)); /* #80929d */\n --ring-disabled-components: 81, 95, 104;\n --ring-disabled-color: rgb(var(--ring-disabled-components)); /* #515F68 */\n\n /* Background */\n --ring-content-background-components: 35, 39, 43;\n --ring-content-background-color: rgb(var(--ring-content-background-components)); /* #23272b */\n --ring-popup-background-components: 17, 19, 20;\n --ring-popup-background-color: rgb(var(--ring-popup-background-components)); /* #111314 */\n --ring-sidebar-background-components: 40, 52, 61;\n --ring-sidebar-background-color: rgb(var(--ring-sidebar-background-components)); /* #28343d */\n --ring-selected-background-components: 6, 38, 64;\n --ring-selected-background-color: rgb(var(--ring-selected-background-components)); /* #062640 */\n --ring-hover-background-components: 11, 26, 38;\n --ring-hover-background-color: rgb(var(--ring-hover-background-components)); /* #0b1a26 */\n --ring-navigation-background-components: 17, 19, 20;\n --ring-navigation-background-color: rgb(var(--ring-navigation-background-components)); /* #111314 */\n --ring-tag-background-components: 62, 77, 89;\n --ring-tag-background-color: rgb(var(--ring-tag-background-components)); /* #3e4d59 */\n --ring-tag-hover-background-components: 51, 62, 71;\n --ring-tag-hover-background-color: rgb(var(--ring-tag-hover-background-components)); /* #333e47 */\n --ring-removed-background-components: 143, 82, 71;\n --ring-removed-background-color: rgb(var(--ring-removed-background-components)); /* #8f5247 */\n --ring-warning-background-components: 89, 61, 1;\n --ring-warning-background-color: rgb(var(--ring-warning-background-components)); /* #593d01 */\n --ring-added-background-components: 54, 89, 71;\n --ring-added-background-color: rgb(var(--ring-added-background-components)); /* #365947 */\n --ring-disabled-background-components: 44, 47, 51;\n --ring-disabled-background-color: rgb(var(--ring-disabled-background-components)); /* #2C2F33 */\n --ring-disabled-selected-background-components: 44, 47, 51;\n --ring-disabled-selected-background-color: rgb(var(--ring-disabled-selected-background-components)); /* #2C2F33 */\n --ring-button-danger-active-components: 38, 8, 10;\n --ring-button-danger-active-color: rgb(var(--ring-button-danger-active-components)); /* #26080a */\n --ring-button-primary-background-components: 0, 126, 229;\n --ring-button-primary-background-color: rgb(var(--ring-button-primary-background-components)); /* #007ee5 */\n --ring-table-loader-background-color: rgba(var(--ring-content-background-components), 0.5); /* #23272b80 */\n\n /* Code */\n --ring-code-background-components: 43, 43, 43;\n --ring-code-background-color: rgb(var(--ring-code-background-components)); /* #2b2b2b */\n --ring-code-components: 169, 183, 198;\n --ring-code-color: rgb(var(--ring-code-components)); /* #a9b7c6 */\n --ring-code-meta-components: 187, 181, 41;\n --ring-code-meta-color: rgb(var(--ring-code-meta-components)); /* #bbb529 */\n --ring-code-keyword-components: 204, 120, 50;\n --ring-code-keyword-color: rgb(var(--ring-code-keyword-components)); /* #cc7832 */\n --ring-code-tag-background-components: 43, 43, 43;\n --ring-code-tag-background-color: rgb(var(--ring-code-tag-background-components)); /* #2b2b2b */\n --ring-code-tag-components: 232, 191, 106;\n --ring-code-tag-color: rgb(var(--ring-code-tag-components)); /* #e8bf6a */\n --ring-code-tag-font-weight: normal;\n --ring-code-field-components: 152, 118, 170;\n --ring-code-field-color: rgb(var(--ring-code-tag-font-weight)); /* #9876aa */\n --ring-code-attribute-components: 186, 186, 186;\n --ring-code-attribute-color: rgb(var(--ring-code-attribute-components)); /* #bababa */\n --ring-code-number-components: 104, 151, 187;\n --ring-code-number-color: rgb(var(--ring-code-number-components)); /* #6897bb */\n --ring-code-string-components: 106, 135, 89;\n --ring-code-string-color: rgb(var(--ring-code-string-components)); /* #6a8759 */\n --ring-code-addition-components: 68, 113, 82;\n --ring-code-addition-color: rgb(var(--ring-code-addition-components)); /* #447152 */\n --ring-code-deletion-components: 101, 110, 118;\n --ring-code-deletion-color: rgb(var(--ring-code-deletion-components)); /* #656e76 */\n\n color-scheme: dark;\n}\n","",{version:3,sources:["webpack://./node_modules/@jetbrains/ring-ui/components/global/variables_dark.css"],names:[],mappings:"AAAA,mCAAmC;;AAEnC;;;EAGE,kCAAkC;EAClC,mDAAmD,EAAE,YAAY;EACjE,sCAAsC;EACtC,yDAAyD,EAAE,YAAY;EACvE,qCAAqC;EACrC,mDAAmD,EAAE,YAAY;EACjE,+CAA+C;EAC/C,uEAAuE,EAAE,YAAY;EACrF,6CAA6C;EAC7C,yEAAyE,EAAE,YAAY;EACvF,sDAAsD;EACtD,2FAA2F,EAAE,YAAY;EACzG,wDAAwD;EACxD,+FAA+F,EAAE,YAAY,EAAE,uBAAuB;EACtI,2CAA2C;EAC3C,qEAAqE,EAAE,YAAY;EACnF,6CAA6C;EAC7C,mEAAmE,EAAE,YAAY;EACjF,mCAAmC;EACnC,mDAAmD,EAAE,YAAY;EACjE,0DAA0D;EAC1D,0DAA0D,EAAE,YAAY;EACxE,yCAAyC;EACzC,+DAA+D,EAAE,YAAY;EAC7E,yCAAyC;EACzC,+DAA+D,EAAE,YAAY;EAC7E,2CAA2C;EAC3C,mEAAmE,EAAE,YAAY;EACjF,4CAA4C;EAC5C,mEAAmE,EAAE,YAAY;EACjF,yCAAyC;EACzC,yEAAyE;EACzE,0EAA0E;EAC1E,2EAA2E;EAC3E,wCAAwC;EACxC,qEAAqE,EAAE,SAAS;EAChF,yDAAyD;EACzD,sDAAsD;EACtD,qFAAqF,EAAE,YAAY;;EAEnG,SAAS;EACT,qCAAqC;EACrC,mDAAmD,EAAE,YAAY;EACjE,qCAAqC;EACrC,mDAAmD,EAAE,YAAY;EACjE,oCAAoC;EACpC,qDAAqD,EAAE,YAAY;EACnE,sCAAsC;EACtC,yDAAyD,EAAE,YAAY;EACvE,uCAAuC;EACvC,yDAAyD,EAAE,YAAY;EACvE,qCAAqC;EACrC,mDAAmD,EAAE,SAAS;EAC9D,4CAA4C;EAC5C,iEAAiE,EAAE,SAAS;EAC5E,4CAA4C;EAC5C,0CAA0C;EAC1C,6DAA6D,EAAE,YAAY;EAC3E,uCAAuC;EACvC,2DAA2D,EAAE,YAAY;;EAEzE,eAAe;EACf,gDAAgD;EAChD,+EAA+E,EAAE,YAAY;EAC7F,8CAA8C;EAC9C,2EAA2E,EAAE,YAAY;EACzF,gDAAgD;EAChD,+EAA+E,EAAE,YAAY;EAC7F,gDAAgD;EAChD,iFAAiF,EAAE,YAAY;EAC/F,8CAA8C;EAC9C,2EAA2E,EAAE,YAAY;EACzF,mDAAmD;EACnD,qFAAqF,EAAE,YAAY;EACnG,4CAA4C;EAC5C,uEAAuE,EAAE,YAAY;EACrF,kDAAkD;EAClD,mFAAmF,EAAE,YAAY;EACjG,iDAAiD;EACjD,+EAA+E,EAAE,YAAY;EAC7F,+CAA+C;EAC/C,+EAA+E,EAAE,YAAY;EAC7F,8CAA8C;EAC9C,2EAA2E,EAAE,YAAY;EACzF,iDAAiD;EACjD,iFAAiF,EAAE,YAAY;EAC/F,0DAA0D;EAC1D,mGAAmG,EAAE,YAAY;EACjH,iDAAiD;EACjD,mFAAmF,EAAE,YAAY;EACjG,wDAAwD;EACxD,6FAA6F,EAAE,YAAY;EAC3G,0FAA0F,EAAE,cAAc;;EAE1G,SAAS;EACT,6CAA6C;EAC7C,yEAAyE,EAAE,YAAY;EACvF,qCAAqC;EACrC,mDAAmD,EAAE,YAAY;EACjE,yCAAyC;EACzC,6DAA6D,EAAE,YAAY;EAC3E,4CAA4C;EAC5C,mEAAmE,EAAE,YAAY;EACjF,iDAAiD;EACjD,iFAAiF,EAAE,YAAY;EAC/F,yCAAyC;EACzC,2DAA2D,EAAE,YAAY;EACzE,mCAAmC;EACnC,2CAA2C;EAC3C,8DAA8D,EAAE,YAAY;EAC5E,+CAA+C;EAC/C,uEAAuE,EAAE,YAAY;EACrF,4CAA4C;EAC5C,iEAAiE,EAAE,YAAY;EAC/E,2CAA2C;EAC3C,iEAAiE,EAAE,YAAY;EAC/E,4CAA4C;EAC5C,qEAAqE,EAAE,YAAY;EACnF,8CAA8C;EAC9C,qEAAqE,EAAE,YAAY;;EAEnF,kBAAkB;AACpB",sourcesContent:["/* stylelint-disable color-no-hex */\n\n:global(.ring-ui-theme-dark),\n.dark,\n:root.dark {\n --ring-line-components: 71, 81, 89;\n --ring-line-color: rgb(var(--ring-line-components)); /* #475159 */\n --ring-borders-components: 64, 99, 128;\n --ring-borders-color: rgb(var(--ring-borders-components)); /* #406380 */\n --ring-icon-components: 128, 146, 157;\n --ring-icon-color: rgb(var(--ring-icon-components)); /* #80929d */\n --ring-icon-secondary-components: 128, 146, 157;\n --ring-icon-secondary-color: rgb(var(--ring-icon-secondary-components)); /* #80929d */\n --ring-border-disabled-components: 54, 54, 54;\n --ring-border-disabled-color: rgb(var(--ring-border-disabled-components)); /* #363636 */\n --ring-border-selected-disabled-components: 54, 54, 54;\n --ring-border-selected-disabled-color: rgb(var(--ring-border-selected-disabled-components)); /* #363636 */\n --ring-border-unselected-disabled-components: 54, 54, 54;\n --ring-border-unselected-disabled-color: rgb(var(--ring-border-unselected-disabled-components)); /* #363636 */ /* TODO remove in 6.0 */\n --ring-icon-disabled-components: 80, 82, 83;\n --ring-icon-disabled-color: rgb(var(--ring-icon-disabled-components)); /* #505253 */\n --ring-border-hover-components: 112, 177, 230;\n --ring-border-hover-color: rgb(var(--ring-border-hover-components)); /* #70b1e6 */\n --ring-main-components: 0, 142, 255;\n --ring-main-color: rgb(var(--ring-main-components)); /* #008eff */\n --ring-action-link-components: var(--ring-main-components);\n --ring-action-link-color: rgb(var(--ring-main-components)); /* #008eff */\n --ring-main-hover-components: 0, 126, 229;\n --ring-main-hover-color: rgb(var(--ring-main-hover-components)); /* #007ee5 */\n --ring-icon-error-components: 219, 88, 96;\n --ring-icon-error-color: rgb(var(--ring-icon-error-components)); /* #db5860 */\n --ring-icon-warning-components: 237, 162, 0;\n --ring-icon-warning-color: rgb(var(--ring-icon-warning-components)); /* #eda200 */\n --ring-icon-success-components: 71, 212, 100;\n --ring-icon-success-color: rgb(var(--ring-icon-success-components)); /* #47d464 */\n --ring-popup-border-components: 0, 42, 76;\n --ring-popup-border-color: rgba(var(--ring-popup-border-components), 0.1);\n --ring-popup-shadow-color: rgba(var(--ring-popup-border-components), 0.15);\n --ring-message-shadow-color: rgba(var(--ring-popup-border-components), 0.3);\n --ring-pinned-shadow-components: 0, 0, 0;\n --ring-pinned-shadow-color: rgb(var(--ring-pinned-shadow-components)); /* #000 */\n --ring-button-danger-hover-color: var(--ring-error-color);\n --ring-button-primary-border-components: 128, 198, 255;\n --ring-button-primary-border-color: rgb(var(--ring-button-primary-border-components)); /* #80c6ff */\n\n /* Text */\n --ring-hint-components: 128, 146, 157;\n --ring-hint-color: rgb(var(--ring-hint-components)); /* #80929d */\n --ring-link-components: 112, 177, 230;\n --ring-link-color: rgb(var(--ring-link-components)); /* #70b1e6 */\n --ring-error-components: 219, 88, 96;\n --ring-error-color: rgb(var(--ring-error-components)); /* #db5860 */\n --ring-warning-components: 237, 162, 0;\n --ring-warning-color: rgb(var(--ring-warning-components)); /* #eda200 */\n --ring-success-components: 71, 212, 100;\n --ring-success-color: rgb(var(--ring-success-components)); /* #47d464 */\n --ring-text-components: 187, 187, 187;\n --ring-text-color: rgb(var(--ring-text-components)); /* #bbb */\n --ring-active-text-components: 255, 255, 255;\n --ring-active-text-color: rgb(var(--ring-active-text-components)); /* #fff */\n --ring-heading-color: var(--ring-text-color);\n --ring-secondary-components: 128, 146, 157;\n --ring-secondary-color: rgb(var(--ring-secondary-components)); /* #80929d */\n --ring-disabled-components: 81, 95, 104;\n --ring-disabled-color: rgb(var(--ring-disabled-components)); /* #515F68 */\n\n /* Background */\n --ring-content-background-components: 35, 39, 43;\n --ring-content-background-color: rgb(var(--ring-content-background-components)); /* #23272b */\n --ring-popup-background-components: 17, 19, 20;\n --ring-popup-background-color: rgb(var(--ring-popup-background-components)); /* #111314 */\n --ring-sidebar-background-components: 40, 52, 61;\n --ring-sidebar-background-color: rgb(var(--ring-sidebar-background-components)); /* #28343d */\n --ring-selected-background-components: 6, 38, 64;\n --ring-selected-background-color: rgb(var(--ring-selected-background-components)); /* #062640 */\n --ring-hover-background-components: 11, 26, 38;\n --ring-hover-background-color: rgb(var(--ring-hover-background-components)); /* #0b1a26 */\n --ring-navigation-background-components: 17, 19, 20;\n --ring-navigation-background-color: rgb(var(--ring-navigation-background-components)); /* #111314 */\n --ring-tag-background-components: 62, 77, 89;\n --ring-tag-background-color: rgb(var(--ring-tag-background-components)); /* #3e4d59 */\n --ring-tag-hover-background-components: 51, 62, 71;\n --ring-tag-hover-background-color: rgb(var(--ring-tag-hover-background-components)); /* #333e47 */\n --ring-removed-background-components: 143, 82, 71;\n --ring-removed-background-color: rgb(var(--ring-removed-background-components)); /* #8f5247 */\n --ring-warning-background-components: 89, 61, 1;\n --ring-warning-background-color: rgb(var(--ring-warning-background-components)); /* #593d01 */\n --ring-added-background-components: 54, 89, 71;\n --ring-added-background-color: rgb(var(--ring-added-background-components)); /* #365947 */\n --ring-disabled-background-components: 44, 47, 51;\n --ring-disabled-background-color: rgb(var(--ring-disabled-background-components)); /* #2C2F33 */\n --ring-disabled-selected-background-components: 44, 47, 51;\n --ring-disabled-selected-background-color: rgb(var(--ring-disabled-selected-background-components)); /* #2C2F33 */\n --ring-button-danger-active-components: 38, 8, 10;\n --ring-button-danger-active-color: rgb(var(--ring-button-danger-active-components)); /* #26080a */\n --ring-button-primary-background-components: 0, 126, 229;\n --ring-button-primary-background-color: rgb(var(--ring-button-primary-background-components)); /* #007ee5 */\n --ring-table-loader-background-color: rgba(var(--ring-content-background-components), 0.5); /* #23272b80 */\n\n /* Code */\n --ring-code-background-components: 43, 43, 43;\n --ring-code-background-color: rgb(var(--ring-code-background-components)); /* #2b2b2b */\n --ring-code-components: 169, 183, 198;\n --ring-code-color: rgb(var(--ring-code-components)); /* #a9b7c6 */\n --ring-code-meta-components: 187, 181, 41;\n --ring-code-meta-color: rgb(var(--ring-code-meta-components)); /* #bbb529 */\n --ring-code-keyword-components: 204, 120, 50;\n --ring-code-keyword-color: rgb(var(--ring-code-keyword-components)); /* #cc7832 */\n --ring-code-tag-background-components: 43, 43, 43;\n --ring-code-tag-background-color: rgb(var(--ring-code-tag-background-components)); /* #2b2b2b */\n --ring-code-tag-components: 232, 191, 106;\n --ring-code-tag-color: rgb(var(--ring-code-tag-components)); /* #e8bf6a */\n --ring-code-tag-font-weight: normal;\n --ring-code-field-components: 152, 118, 170;\n --ring-code-field-color: rgb(var(--ring-code-tag-font-weight)); /* #9876aa */\n --ring-code-attribute-components: 186, 186, 186;\n --ring-code-attribute-color: rgb(var(--ring-code-attribute-components)); /* #bababa */\n --ring-code-number-components: 104, 151, 187;\n --ring-code-number-color: rgb(var(--ring-code-number-components)); /* #6897bb */\n --ring-code-string-components: 106, 135, 89;\n --ring-code-string-color: rgb(var(--ring-code-string-components)); /* #6a8759 */\n --ring-code-addition-components: 68, 113, 82;\n --ring-code-addition-color: rgb(var(--ring-code-addition-components)); /* #447152 */\n --ring-code-deletion-components: 101, 110, 118;\n --ring-code-deletion-color: rgb(var(--ring-code-deletion-components)); /* #656e76 */\n\n color-scheme: dark;\n}\n"],sourceRoot:""}]),a.locals={dark:"dark_d4a9"};const l=a},5066:(e,n,t)=>{"use strict";t.r(n),t.d(n,{default:()=>u});var r=t(1404),o=t.n(r),i=t(7156),a=t.n(i),l=t(9106),c=t(5280),s=a()(o());s.i(c.A),s.i(l.default,"",!0),s.push([e.id,'.icon_aaa7 {\n display: inline-block;\n\n fill: currentColor;\n}\n\n.glyph_f986 {\n display: inline-flex;\n\n margin-right: -1px;\n margin-left: -1px;\n\n pointer-events: none;\n}\n\n.glyph_f986[width="10"] {\n vertical-align: -1px;\n }\n\n.glyph_f986[width="14"] {\n margin-right: -2px;\n margin-left: 0;\n\n vertical-align: -3px;\n }\n\n.glyph_f986[width="16"] {\n vertical-align: -3px;\n }\n\n.glyph_f986[width="20"] {\n vertical-align: -2px;\n }\n\n.glyph_f986.compatibilityMode_d631 {\n width: 16px;\n height: 16px;\n margin-right: 0;\n margin-left: 0;\n }\n\n/* HACK: This media query hack makes styles applied for WebKit browsers only */\n/* stylelint-disable-next-line media-feature-name-no-vendor-prefix */\n@media screen and (-webkit-min-device-pixel-ratio: 0) {\n .glyph_f986 {\n width: auto; /* Safari size bug workaround, see https://youtrack.jetbrains.com/issue/RG-1983 */\n }\n}\n\n.gray_f6a8 {\n color: var(--ring-icon-secondary-color);\n}\n\n.hover_fc27 {\n color: var(--ring-icon-hover-color);\n}\n\n.green_bfb1 {\n color: var(--ring-icon-success-color);\n}\n\n.magenta_b045 {\n color: var(--ring-link-hover-color);\n}\n\n.red_a7ec {\n color: var(--ring-icon-error-color);\n}\n\n.blue_ec1e {\n color: var(--ring-main-color);\n}\n\n.white_c896 {\n color: var(--ring-white-text-color);\n}\n\n.loading_c5e2 {\n animation-name: icon-loading_fe22;\n animation-duration: 1200ms;\n animation-iteration-count: infinite;\n}\n\n@keyframes icon-loading_fe22 {\n 0% {\n transform: scale(1);\n }\n\n 50% {\n transform: scale(0.9);\n\n opacity: 0.5;\n }\n\n 100% {\n transform: scale(1);\n }\n}\n',"",{version:3,sources:["webpack://./node_modules/@jetbrains/ring-ui/components/icon/icon.css"],names:[],mappings:"AAIA;EACE,qBAAqB;;EAErB,kBAAkB;AACpB;;AAEA;EACE,oBAAoB;;EAEpB,kBAAkB;EAClB,iBAAiB;;EAEjB,oBAAoB;AA2BtB;;AAzBE;IACE,oBAAoB;EACtB;;AAEA;IACE,kBAAkB;IAClB,cAAc;;IAEd,oBAAoB;EACtB;;AAEA;IACE,oBAAoB;EACtB;;AAEA;IACE,oBAAoB;EACtB;;AAEA;IACE,WAAqB;IACrB,YAAsB;IACtB,eAAe;IACf,cAAc;EAChB;;AAGF,8EAA8E;AAC9E,oEAAoE;AACpE;EACE;IACE,WAAW,EAAE,iFAAiF;EAChG;AACF;;AAEA;EACE,uCAAuC;AACzC;;AAEA;EACE,mCAAmC;AACrC;;AAEA;EACE,qCAAqC;AACvC;;AAEA;EACE,mCAAmC;AACrC;;AAEA;EACE,mCAAmC;AACrC;;AAEA;EACE,6BAA6B;AAC/B;;AAEA;EACE,mCAAmC;AACrC;;AAEA;EACE,iCAA4B;EAC5B,0BAA0B;EAC1B,mCAAmC;AACrC;;AAEA;EACE;IACE,mBAAmB;EACrB;;EAEA;IACE,qBAAqB;;IAErB,YAAY;EACd;;EAEA;IACE,mBAAmB;EACrB;AACF",sourcesContent:['@import "../global/variables.css";\n\n@value unit from "../global/global.css";\n\n.icon {\n display: inline-block;\n\n fill: currentColor;\n}\n\n.glyph {\n display: inline-flex;\n\n margin-right: -1px;\n margin-left: -1px;\n\n pointer-events: none;\n\n &[width="10"] {\n vertical-align: -1px;\n }\n\n &[width="14"] {\n margin-right: -2px;\n margin-left: 0;\n\n vertical-align: -3px;\n }\n\n &[width="16"] {\n vertical-align: -3px;\n }\n\n &[width="20"] {\n vertical-align: -2px;\n }\n\n &.compatibilityMode {\n width: calc(unit * 2);\n height: calc(unit * 2);\n margin-right: 0;\n margin-left: 0;\n }\n}\n\n/* HACK: This media query hack makes styles applied for WebKit browsers only */\n/* stylelint-disable-next-line media-feature-name-no-vendor-prefix */\n@media screen and (-webkit-min-device-pixel-ratio: 0) {\n .glyph {\n width: auto; /* Safari size bug workaround, see https://youtrack.jetbrains.com/issue/RG-1983 */\n }\n}\n\n.gray {\n color: var(--ring-icon-secondary-color);\n}\n\n.hover {\n color: var(--ring-icon-hover-color);\n}\n\n.green {\n color: var(--ring-icon-success-color);\n}\n\n.magenta {\n color: var(--ring-link-hover-color);\n}\n\n.red {\n color: var(--ring-icon-error-color);\n}\n\n.blue {\n color: var(--ring-main-color);\n}\n\n.white {\n color: var(--ring-white-text-color);\n}\n\n.loading {\n animation-name: icon-loading;\n animation-duration: 1200ms;\n animation-iteration-count: infinite;\n}\n\n@keyframes icon-loading {\n 0% {\n transform: scale(1);\n }\n\n 50% {\n transform: scale(0.9);\n\n opacity: 0.5;\n }\n\n 100% {\n transform: scale(1);\n }\n}\n'],sourceRoot:""}]),s.locals={unit:`${l.default.locals.unit}`,icon:"icon_aaa7",glyph:"glyph_f986",compatibilityMode:"compatibilityMode_d631",gray:"gray_f6a8",hover:"hover_fc27",green:"green_bfb1",magenta:"magenta_b045",red:"red_a7ec",blue:"blue_ec1e",white:"white_c896",loading:"loading_c5e2","icon-loading":"icon-loading_fe22"};const u=s},8976:(e,n,t)=>{"use strict";t.r(n),t.d(n,{default:()=>u});var r=t(1404),o=t.n(r),i=t(7156),a=t.n(i),l=t(9106),c=t(5280),s=a()(o());s.i(c.A),s.i(l.default,"",!0),s.push([e.id,":root {\n --ring-input-xs: 96px;\n --ring-input-s: 96px;\n --ring-input-m: 240px;\n --ring-input-l: 400px;\n}\n\n/**\n * @name Input Sizes\n */\n\n/* XS */\n\n.ring-input-size_xs.ring-input-size_xs {\n display: inline-block;\n\n width: 96px;\n\n width: var(--ring-input-xs);\n}\n\n.ring-input-size_xs.ring-input-size_xs ~ .ring-error-bubble {\n left: 98px;\n left: calc(var(--ring-input-xs) + 2px);\n}\n\n/* S */\n\n.ring-input-size_s.ring-input-size_s {\n display: inline-block;\n\n width: 96px;\n\n width: var(--ring-input-s);\n}\n\n.ring-input-size_s.ring-input-size_s ~ .ring-error-bubble {\n left: 98px;\n left: calc(var(--ring-input-s) + 2px);\n}\n\n/* M */\n\n.ring-input-size_m.ring-input-size_m {\n display: inline-block;\n\n width: 240px;\n\n width: var(--ring-input-m);\n}\n\n.ring-input-size_m.ring-input-size_m ~ .ring-error-bubble {\n left: 242px;\n left: calc(var(--ring-input-m) + 2px);\n}\n\n.ring-input-size_md.ring-input-size_md {\n display: inline-block;\n\n width: 240px;\n\n width: var(--ring-input-m);\n}\n\n.ring-input-size_md.ring-input-size_md ~ .ring-error-bubble {\n left: 242px;\n left: calc(var(--ring-input-m) + 2px);\n}\n\n/* L */\n\n.ring-input-size_l.ring-input-size_l {\n display: inline-block;\n\n width: 400px;\n\n width: var(--ring-input-l);\n}\n\n.ring-input-size_l.ring-input-size_l ~ .ring-error-bubble {\n left: 402px;\n left: calc(var(--ring-input-l) + 2px);\n}\n\n.ring-input-height_s.ring-input-height_s {\n --ring-input-padding-block: 1px;\n}\n\n.ring-input-height_m.ring-input-height_m {\n --ring-input-padding-block: 3px;\n}\n\n.ring-input-height_l.ring-input-height_l {\n --ring-input-padding-block: 5px;\n}\n","",{version:3,sources:["webpack://./node_modules/@jetbrains/ring-ui/components/input-size/input-size.css"],names:[],mappings:"AAIA;EACE,qBAAgC;EAChC,oBAA+B;EAC/B,qBAA+B;EAC/B,qBAA+B;AACjC;;AAEA;;EAEE;;AAEF,OAAO;;AAEP;EACE,qBAAqB;;EAErB,WAA2B;;EAA3B,2BAA2B;AAC7B;;AAEA;EACE,UAAsC;EAAtC,sCAAsC;AACxC;;AAEA,MAAM;;AAEN;EACE,qBAAqB;;EAErB,WAA0B;;EAA1B,0BAA0B;AAC5B;;AAEA;EACE,UAAqC;EAArC,qCAAqC;AACvC;;AAEA,MAAM;;AAEN;EACE,qBAAqB;;EAErB,YAA0B;;EAA1B,0BAA0B;AAC5B;;AAEA;EACE,WAAqC;EAArC,qCAAqC;AACvC;;AAEA;EACE,qBAAqB;;EAErB,YAA0B;;EAA1B,0BAA0B;AAC5B;;AAEA;EACE,WAAqC;EAArC,qCAAqC;AACvC;;AAEA,MAAM;;AAEN;EACE,qBAAqB;;EAErB,YAA0B;;EAA1B,0BAA0B;AAC5B;;AAEA;EACE,WAAqC;EAArC,qCAAqC;AACvC;;AAEA;EACE,+BAA+B;AACjC;;AAEA;EACE,+BAA+B;AACjC;;AAEA;EACE,+BAA+B;AACjC",sourcesContent:['@import "../global/variables.css";\n\n@value unit from "../global/global.css";\n\n:root {\n --ring-input-xs: calc(unit * 12);\n --ring-input-s: calc(unit * 12);\n --ring-input-m: calc(unit * 30);\n --ring-input-l: calc(unit * 50);\n}\n\n/**\n * @name Input Sizes\n */\n\n/* XS */\n\n:global(.ring-input-size_xs.ring-input-size_xs) {\n display: inline-block;\n\n width: var(--ring-input-xs);\n}\n\n:global(.ring-input-size_xs.ring-input-size_xs ~ .ring-error-bubble) {\n left: calc(var(--ring-input-xs) + 2px);\n}\n\n/* S */\n\n:global(.ring-input-size_s.ring-input-size_s) {\n display: inline-block;\n\n width: var(--ring-input-s);\n}\n\n:global(.ring-input-size_s.ring-input-size_s ~ .ring-error-bubble) {\n left: calc(var(--ring-input-s) + 2px);\n}\n\n/* M */\n\n:global(.ring-input-size_m.ring-input-size_m) {\n display: inline-block;\n\n width: var(--ring-input-m);\n}\n\n:global(.ring-input-size_m.ring-input-size_m ~ .ring-error-bubble) {\n left: calc(var(--ring-input-m) + 2px);\n}\n\n:global(.ring-input-size_md.ring-input-size_md) {\n display: inline-block;\n\n width: var(--ring-input-m);\n}\n\n:global(.ring-input-size_md.ring-input-size_md ~ .ring-error-bubble) {\n left: calc(var(--ring-input-m) + 2px);\n}\n\n/* L */\n\n:global(.ring-input-size_l.ring-input-size_l) {\n display: inline-block;\n\n width: var(--ring-input-l);\n}\n\n:global(.ring-input-size_l.ring-input-size_l ~ .ring-error-bubble) {\n left: calc(var(--ring-input-l) + 2px);\n}\n\n:global(.ring-input-height_s.ring-input-height_s) {\n --ring-input-padding-block: 1px;\n}\n\n:global(.ring-input-height_m.ring-input-height_m) {\n --ring-input-padding-block: 3px;\n}\n\n:global(.ring-input-height_l.ring-input-height_l) {\n --ring-input-padding-block: 5px;\n}\n'],sourceRoot:""}]),s.locals={unit:`${l.default.locals.unit}`};const u=s},8266:(e,n,t)=>{"use strict";t.r(n),t.d(n,{default:()=>p});var r=t(1404),o=t.n(r),i=t(7156),a=t.n(i),l=t(9106),c=t(5280),s=t(9892),u=a()(o());u.i(c.A),u.i(s.default),u.i(l.default,"",!0),u.push([e.id,'.outerContainer_cb70 {\n --ring-input-icon-offset: 20px;\n --ring-input-padding-inline: 8px;\n --ring-input-background-color: var(--ring-content-background-color);\n}\n\n.borderless_f79b {\n /* stylelint-disable-next-line length-zero-no-unit */\n --ring-input-padding-inline: 0px;\n}\n\n.container_ee33 {\n position: relative;\n\n box-sizing: border-box;\n\n font-size: var(--ring-font-size);\n line-height: var(--ring-line-height);\n}\n\n.container_ee33 * {\n box-sizing: border-box;\n }\n\n.input_f220 {\n --ring-input-padding-start: var(--ring-input-padding-inline);\n --ring-input-padding-end: var(--ring-input-padding-inline);\n\n width: 100%;\n\n margin: 0;\n padding-top: var(--ring-input-padding-block);\n padding-right: var(--ring-input-padding-end);\n padding-bottom: var(--ring-input-padding-block);\n padding-left: var(--ring-input-padding-start);\n\n transition: border-color var(--ring-ease);\n\n color: var(--ring-text-color);\n border: 1px solid var(--ring-borders-color);\n border-radius: var(--ring-border-radius);\n outline: none;\n background-color: var(--ring-input-background-color);\n\n font: inherit;\n\n caret-color: var(--ring-main-color);\n}\n\n[dir="rtl"] .input_f220 {\n padding-right: var(--ring-input-padding-start);\n padding-left: var(--ring-input-padding-end);\n }\n\n@media (hover: hover), (-moz-touch-enabled: 0), (-ms-high-contrast: none), (-ms-high-contrast: active) {.input_f220:hover {\n transition: none;\n\n border-color: var(--ring-border-hover-color);\n }}\n\n.error_ff90 .input_f220 {\n border-color: var(--ring-icon-error-color);\n }\n\n.input_f220:focus {\n transition: none;\n\n border-color: var(--ring-main-color);\n }\n\n.input_f220[disabled] {\n color: var(--ring-disabled-color);\n border-color: var(--ring-border-disabled-color);\n background-color: var(--ring-disabled-background-color);\n\n -webkit-text-fill-color: var(--ring-disabled-color); /* Required for Safari, see RG-2063 for details */\n }\n\n/*\n Kill yellow/blue webkit autocomplete\n https://css-tricks.com/snippets/css/change-autocomplete-styles-webkit-browsers/\n */\n\n@media (hover: hover), (-moz-touch-enabled: 0), (-ms-high-contrast: none), (-ms-high-contrast: active) {.input_f220:-webkit-autofill:hover {\n -webkit-transition: background-color 50000s ease-in-out 0s;\n transition: background-color 50000s ease-in-out 0s;\n }}\n\n.input_f220:-webkit-autofill,\n .input_f220:-webkit-autofill:focus {\n -webkit-transition: background-color 50000s ease-in-out 0s;\n transition: background-color 50000s ease-in-out 0s;\n }\n\n.borderless_f79b .input_f220 {\n border-color: transparent;\n background-color: transparent;\n}\n\n.withIcon_f066 .input_f220 {\n --ring-input-padding-start: calc(var(--ring-input-padding-inline) + var(--ring-input-icon-offset));\n}\n\n.clearable_fd1e .input_f220 {\n --ring-input-padding-end: calc(var(--ring-input-padding-inline) + var(--ring-input-icon-offset));\n}\n\n.icon_e49c {\n position: absolute;\n top: calc(var(--ring-input-padding-block) + 1px);\n left: var(--ring-input-padding-inline);\n\n pointer-events: none;\n\n color: var(--ring-icon-secondary-color);\n}\n\n[dir="rtl"] .icon_e49c {\n right: 8px;\n left: auto;\n }\n\n.clear_ffc3 {\n position: absolute;\n top: calc(var(--ring-input-padding-block) + 2px);\n right: var(--ring-input-padding-inline);\n\n height: auto;\n\n padding-right: 0;\n\n line-height: inherit;\n}\n\n.empty_cc0d .clear_ffc3 {\n display: none;\n }\n\n[dir="rtl"] .clear_ffc3 {\n right: auto;\n left: 8px;\n }\n\ntextarea.input_f220 {\n overflow: hidden;\n\n box-sizing: border-box;\n\n resize: none;\n}\n\n.input_f220::-moz-placeholder {\n color: var(--ring-disabled-color);\n}\n\n.input_f220::placeholder {\n color: var(--ring-disabled-color);\n}\n\n.input_f220::-webkit-search-cancel-button {\n -webkit-appearance: none;\n}\n\n.errorText_e447 {\n margin-top: 4px;\n\n color: var(--ring-error-color);\n\n font-size: var(--ring-font-size-smaller);\n line-height: var(--ring-line-height-lowest);\n}\n\n.sizeS_c560 {\n width: 96px;\n}\n\n.sizeM_aee6 {\n width: 240px;\n}\n\n.sizeL_b0ca {\n width: 400px;\n}\n\n.sizeFULL_f4f9 {\n width: 100%;\n}\n\n.heightS_a68d {\n --ring-input-padding-block: 1px;\n}\n\n.heightM_bc35 {\n --ring-input-padding-block: 3px;\n}\n\n.heightL_f82d {\n --ring-input-padding-block: 5px;\n}\n',"",{version:3,sources:["webpack://./node_modules/@jetbrains/ring-ui/components/input/input.css",""],names:[],mappings:"AAKA;EACE,8BAA0C;EAC1C,gCAAiC;EACjC,mEAAmE;AACrE;;AAEA;EACE,oDAAoD;EACpD,gCAAgC;AAClC;;AAEA;EACE,kBAAkB;;EAElB,sBAAsB;;EAEtB,gCAAgC;EAChC,oCAAoC;AAKtC;;AAHE;IACE,sBAAsB;EACxB;;AAGF;EACE,4DAA4D;EAC5D,0DAA0D;;EAE1D,WAAW;;EAEX,SAAS;EACT,4CAA4C;EAC5C,4CAA4C;EAC5C,+CAA+C;EAC/C,6CAA6C;;EAE7C,yCAAyC;;EAEzC,6BAA6B;EAC7B,2CAA2C;EAC3C,wCAAwC;EACxC,aAAa;EACb,oDAAoD;;EAEpD,aAAa;;EAEb,mCAAmC;AA0CrC;;AAxCE;IACE,8CAA8C;IAC9C,2CAA2C;EAC7C;;ACxDF,wGAAA;IAAA,iBAAA;;IAAA,6CAAA;GAAA,CAAA;;ADgEE;IACE,0CAA0C;EAC5C;;AAEA;IACE,gBAAgB;;IAEhB,oCAAoC;EACtC;;AAEA;IACE,iCAAiC;IACjC,+CAA+C;IAC/C,uDAAuD;;IAEvD,mDAAmD,EAAE,iDAAiD;EACxG;;AAEA;;;GAGC;;ACrFH,wGAAA;MAAA,2DAAA;MAAA,mDAAA;KAAA,CAAA;;ADuFI;;MAGE,0DAAkD;MAAlD,kDAAkD;IACpD;;AAIJ;EACE,yBAAyB;EACzB,6BAA6B;AAC/B;;AAEA;EACE,kGAAkG;AACpG;;AAEA;EACE,gGAAgG;AAClG;;AAEA;EACE,kBAAkB;EAClB,gDAAgD;EAChD,sCAAsC;;EAEtC,oBAAoB;;EAEpB,uCAAuC;AAMzC;;AAJE;IACE,UAAW;IACX,UAAU;EACZ;;AAGF;EACE,kBAAkB;EAClB,gDAAgD;EAChD,uCAAuC;;EAEvC,YAAY;;EAEZ,gBAAgB;;EAEhB,oBAAoB;AAUtB;;AARE;IACE,aAAa;EACf;;AAEA;IACE,WAAW;IACX,SAAU;EACZ;;AAGF;EACE,gBAAgB;;EAEhB,sBAAsB;;EAEtB,YAAY;AACd;;AAEA;EACE,iCAAiC;AACnC;;AAFA;EACE,iCAAiC;AACnC;;AAEA;EACE,wBAAwB;AAC1B;;AAEA;EACE,eAA0B;;EAE1B,8BAA8B;;EAE9B,wCAAwC;EACxC,2CAA2C;AAC7C;;AAEA;EACE,WAAsB;AACxB;;AAEA;EACE,YAAsB;AACxB;;AAEA;EACE,YAAsB;AACxB;;AAEA;EACE,WAAW;AACb;;AAEA;EACE,+BAA+B;AACjC;;AAEA;EACE,+BAA+B;AACjC;;AAEA;EACE,+BAA+B;AACjC",sourcesContent:['@import "../global/variables.css";\n@import "../button/button.css";\n\n@value unit from "../global/global.css";\n\n.outerContainer {\n --ring-input-icon-offset: calc(unit * 2.5);\n --ring-input-padding-inline: unit;\n --ring-input-background-color: var(--ring-content-background-color);\n}\n\n.borderless {\n /* stylelint-disable-next-line length-zero-no-unit */\n --ring-input-padding-inline: 0px;\n}\n\n.container {\n position: relative;\n\n box-sizing: border-box;\n\n font-size: var(--ring-font-size);\n line-height: var(--ring-line-height);\n\n & * {\n box-sizing: border-box;\n }\n}\n\n.input {\n --ring-input-padding-start: var(--ring-input-padding-inline);\n --ring-input-padding-end: var(--ring-input-padding-inline);\n\n width: 100%;\n\n margin: 0;\n padding-top: var(--ring-input-padding-block);\n padding-right: var(--ring-input-padding-end);\n padding-bottom: var(--ring-input-padding-block);\n padding-left: var(--ring-input-padding-start);\n\n transition: border-color var(--ring-ease);\n\n color: var(--ring-text-color);\n border: 1px solid var(--ring-borders-color);\n border-radius: var(--ring-border-radius);\n outline: none;\n background-color: var(--ring-input-background-color);\n\n font: inherit;\n\n caret-color: var(--ring-main-color);\n\n [dir="rtl"] & {\n padding-right: var(--ring-input-padding-start);\n padding-left: var(--ring-input-padding-end);\n }\n\n &:hover {\n transition: none;\n\n border-color: var(--ring-border-hover-color);\n }\n\n .error & {\n border-color: var(--ring-icon-error-color);\n }\n\n &:focus {\n transition: none;\n\n border-color: var(--ring-main-color);\n }\n\n &[disabled] {\n color: var(--ring-disabled-color);\n border-color: var(--ring-border-disabled-color);\n background-color: var(--ring-disabled-background-color);\n\n -webkit-text-fill-color: var(--ring-disabled-color); /* Required for Safari, see RG-2063 for details */\n }\n\n /*\n Kill yellow/blue webkit autocomplete\n https://css-tricks.com/snippets/css/change-autocomplete-styles-webkit-browsers/\n */\n &:-webkit-autofill {\n &,\n &:hover,\n &:focus {\n transition: background-color 50000s ease-in-out 0s;\n }\n }\n}\n\n.borderless .input {\n border-color: transparent;\n background-color: transparent;\n}\n\n.withIcon .input {\n --ring-input-padding-start: calc(var(--ring-input-padding-inline) + var(--ring-input-icon-offset));\n}\n\n.clearable .input {\n --ring-input-padding-end: calc(var(--ring-input-padding-inline) + var(--ring-input-icon-offset));\n}\n\n.icon {\n position: absolute;\n top: calc(var(--ring-input-padding-block) + 1px);\n left: var(--ring-input-padding-inline);\n\n pointer-events: none;\n\n color: var(--ring-icon-secondary-color);\n\n [dir="rtl"] & {\n right: unit;\n left: auto;\n }\n}\n\n.clear {\n position: absolute;\n top: calc(var(--ring-input-padding-block) + 2px);\n right: var(--ring-input-padding-inline);\n\n height: auto;\n\n padding-right: 0;\n\n line-height: inherit;\n\n .empty & {\n display: none;\n }\n\n [dir="rtl"] & {\n right: auto;\n left: unit;\n }\n}\n\ntextarea.input {\n overflow: hidden;\n\n box-sizing: border-box;\n\n resize: none;\n}\n\n.input::placeholder {\n color: var(--ring-disabled-color);\n}\n\n.input::-webkit-search-cancel-button {\n -webkit-appearance: none;\n}\n\n.errorText {\n margin-top: calc(unit / 2);\n\n color: var(--ring-error-color);\n\n font-size: var(--ring-font-size-smaller);\n line-height: var(--ring-line-height-lowest);\n}\n\n.sizeS {\n width: calc(unit * 12);\n}\n\n.sizeM {\n width: calc(unit * 30);\n}\n\n.sizeL {\n width: calc(unit * 50);\n}\n\n.sizeFULL {\n width: 100%;\n}\n\n.heightS {\n --ring-input-padding-block: 1px;\n}\n\n.heightM {\n --ring-input-padding-block: 3px;\n}\n\n.heightL {\n --ring-input-padding-block: 5px;\n}\n',null],sourceRoot:""}]),u.locals={unit:`${l.default.locals.unit}`,outerContainer:"outerContainer_cb70",borderless:"borderless_f79b",container:"container_ee33",input:"input_f220",error:"error_ff90",withIcon:"withIcon_f066",clearable:"clearable_fd1e",icon:"icon_e49c",clear:"clear_ffc3",empty:"empty_cc0d",errorText:"errorText_e447",sizeS:"sizeS_c560",sizeM:"sizeM_aee6",sizeL:"sizeL_b0ca",sizeFULL:"sizeFULL_f4f9",heightS:"heightS_a68d",heightM:"heightM_bc35",heightL:"heightL_f82d"};const p=u},6960:(e,n,t)=>{"use strict";t.r(n),t.d(n,{default:()=>s});var r=t(1404),o=t.n(r),i=t(7156),a=t.n(i),l=t(5280),c=a()(o());c.i(l.A),c.push([e.id,".link_e6e5 {\n cursor: pointer;\n transition: color var(--ring-fast-ease);\n\n color: var(--ring-link-color);\n\n outline: none;\n}\n\n@media (hover: hover), (-moz-touch-enabled: 0), (-ms-high-contrast: none), (-ms-high-contrast: active) {.link_e6e5:hover {\n transition: none;\n\n color: var(--ring-link-hover-color);\n }}\n\n@media (hover: hover), (-moz-touch-enabled: 0), (-ms-high-contrast: none), (-ms-high-contrast: active) {.link_e6e5:hover {\n text-decoration: none;\n }}\n\n.link_e6e5 {\n text-decoration: none;\n }\n\n.link_e6e5.hover_bed7 {\n transition: none;\n\n color: var(--ring-link-hover-color);\n }\n\n@media (hover: hover), (-moz-touch-enabled: 0), (-ms-high-contrast: none), (-ms-high-contrast: active) {.link_e6e5:hover .inner_e3ba {\n border-width: 0;\n border-bottom: 2px solid;\n border-image-source: linear-gradient(currentcolor 50%, transparent 50%);\n border-image-slice: 0 0 100% 0;\n }}\n\n.link_e6e5.active_f804 {\n color: inherit;\n }\n\n@media (hover: hover), (-moz-touch-enabled: 0), (-ms-high-contrast: none), (-ms-high-contrast: active) {.link_e6e5.compatibilityUnderlineMode_e7a0:hover {\n text-decoration: underline;\n\n /* stylelint-disable-next-line selector-max-specificity */\n }\n .link_e6e5.compatibilityUnderlineMode_e7a0:hover .inner_e3ba {\n border: none;\n }}\n\n@media (hover: hover), (-moz-touch-enabled: 0), (-ms-high-contrast: none), (-ms-high-contrast: active) {.link_e6e5.pseudo_d9ae:hover {\n text-decoration: none;\n\n /* stylelint-disable-next-line selector-max-specificity */\n }\n .link_e6e5.pseudo_d9ae:hover .inner_e3ba {\n border: none;\n }}\n\n.link_e6e5:focus-visible {\n box-shadow: 0 0 0 2px var(--ring-border-hover-color);\n }\n\n@media (min-resolution: 2dppx) {@media (hover: hover), (-moz-touch-enabled: 0), (-ms-high-contrast: none), (-ms-high-contrast: active) {.link_e6e5:hover .inner_e3ba {\n border-bottom-width: 1px;\n }}\n}\n\n.text_e98a {\n border-radius: var(--ring-border-radius);\n}\n\n@media (hover: hover), (-moz-touch-enabled: 0), (-ms-high-contrast: none), (-ms-high-contrast: active) {.inherit_d267:not(:hover) {\n color: inherit;\n}}\n\n.pseudo_d9ae {\n margin: 0;\n padding: 0;\n\n text-align: left;\n\n border: 0;\n\n background: transparent;\n\n font: inherit;\n}\n\n.pseudo_d9ae::-moz-focus-inner {\n padding: 0;\n\n border: 0;\n }\n","",{version:3,sources:["webpack://./node_modules/@jetbrains/ring-ui/components/link/link.css",""],names:[],mappings:"AAEA;EACE,eAAe;EACf,uCAAuC;;EAEvC,6BAA6B;;EA2C7B,aAAa;AAKf;;ACtDA,wGAAA;IAAA,iBAAA;;IAAA,oCAAA;GAAA,CAAA;;AAAA,wGAAA;IAAA,sBAAA;GAAA,CAAA;;ADQE;IAEE,qBAAqB;EACvB;;AAEA;IAEE,gBAAgB;;IAEhB,mCAAmC;EACrC;;AClBF,wGAAA;IAAA,gBAAA;IAAA,yBAAA;IAAA,wEAAA;IAAA,+BAAA;GAAA,CAAA;;AD2BE;IACE,cAAc;EAChB;;AC7BF,wGAAA;IAAA,2BAAA;;IAAA,0DAAA;GAAA;IAAA;MAAA,aAAA;KAAA,CAAA;;AAAA,wGAAA;IAAA,sBAAA;;IAAA,0DAAA;GAAA;IAAA;MAAA,aAAA;KAAA,CAAA;;ADmDE;IACE,oDAAoD;EACtD;;AAGF,gCCxDA,wGAAA;IAAA,yBAAA;GAAA,CAAA;AD4DA;;AAEA;EACE,wCAAwC;AAC1C;;AChEA,wGAAA;EAAA,eAAA;CAAA,CAAA;;ADsEA;EACE,SAAS;EACT,UAAU;;EAEV,gBAAgB;;EAEhB,SAAS;;EAET,uBAAuB;;EAEvB,aAAa;AAOf;;AALE;IACE,UAAU;;IAEV,SAAS;EACX",sourcesContent:['@import "../global/variables.css";\n\n.link {\n cursor: pointer;\n transition: color var(--ring-fast-ease);\n\n color: var(--ring-link-color);\n\n &,\n &:hover {\n text-decoration: none;\n }\n\n &:hover,\n &.hover {\n transition: none;\n\n color: var(--ring-link-hover-color);\n }\n\n &:hover .inner {\n border-width: 0;\n border-bottom: 2px solid;\n border-image-source: linear-gradient(currentcolor 50%, transparent 50%);\n border-image-slice: 0 0 100% 0;\n }\n\n &.active {\n color: inherit;\n }\n\n &.compatibilityUnderlineMode:hover {\n text-decoration: underline;\n\n /* stylelint-disable-next-line selector-max-specificity */\n & .inner {\n border: none;\n }\n }\n\n &.pseudo:hover {\n text-decoration: none;\n\n /* stylelint-disable-next-line selector-max-specificity */\n & .inner {\n border: none;\n }\n }\n\n outline: none;\n\n &:focus-visible {\n box-shadow: 0 0 0 2px var(--ring-border-hover-color);\n }\n}\n\n@media (min-resolution: 2dppx) {\n .link:hover .inner {\n border-bottom-width: 1px;\n }\n}\n\n.text {\n border-radius: var(--ring-border-radius);\n}\n\n.inherit:not(:hover) {\n color: inherit;\n}\n\n.pseudo {\n margin: 0;\n padding: 0;\n\n text-align: left;\n\n border: 0;\n\n background: transparent;\n\n font: inherit;\n\n &::-moz-focus-inner {\n padding: 0;\n\n border: 0;\n }\n}\n',null],sourceRoot:""}]),c.locals={link:"link_e6e5",hover:"hover_bed7",inner:"inner_e3ba",active:"active_f804",compatibilityUnderlineMode:"compatibilityUnderlineMode_e7a0",pseudo:"pseudo_d9ae",text:"text_e98a",inherit:"inherit_d267"};const s=c},480:(e,n,t)=>{"use strict";t.r(n),t.d(n,{default:()=>u});var r=t(1404),o=t.n(r),i=t(7156),a=t.n(i),l=t(9106),c=t(5280),s=a()(o());s.i(c.A),s.i(l.default,"",!0),s.push([e.id,'.list_a01c {\n position: relative;\n\n z-index: 1;\n\n border-radius: var(--ring-border-radius);\n\n line-height: normal;\n}\n\n.simpleInner_a4f8 {\n overflow: auto;\n}\n\n.scrolling_a910 {\n pointer-events: none;\n}\n\n.separator_c26e {\n display: block;\n\n min-height: 8px;\n\n margin-top: 8px;\n padding: 0 16px 1px;\n\n text-align: right;\n white-space: nowrap;\n\n color: var(--ring-secondary-color);\n border-top: 1px solid var(--ring-line-color);\n\n font-size: var(--ring-font-size-smaller);\n line-height: var(--ring-line-height-lower);\n}\n\n.separator_first_ec9e {\n margin-top: 0;\n padding-top: 0;\n\n border: none;\n}\n\n.item_eadd {\n display: block;\n\n box-sizing: border-box;\n\n width: 100%;\n\n text-align: left;\n vertical-align: bottom;\n white-space: nowrap;\n text-decoration: none;\n\n outline: none;\n\n font-size: var(--ring-font-size);\n}\n\n.item_eadd.item_eadd {\n padding: 3px 16px 5px;\n\n line-height: 24px;\n}\n\n.itemContainer_f365 {\n position: relative;\n}\n\n.compact_efa8 {\n line-height: 16px;\n}\n\n.error_aa15 {\n cursor: default;\n}\n\n@media (hover: hover), (-moz-touch-enabled: 0), (-ms-high-contrast: none), (-ms-high-contrast: active) {.error_aa15:hover {\n color: var(--ring-error-color);\n }}\n\n/* Override ring-link */\n\n.error_aa15,\n .error_aa15:focus,\n .error_aa15:visited {\n color: var(--ring-error-color);\n }\n\n.add_a8da {\n padding: 8px 16px;\n\n line-height: 32px;\n}\n\n.top_c4d5 {\n display: flex;\n align-items: baseline;\n flex-direction: row;\n}\n\n.left_ea6b {\n align-self: center;\n flex-shrink: 0;\n}\n\n.label_dac9 {\n overflow: hidden;\n flex-grow: 1;\n flex-shrink: 1;\n\n text-align: left;\n white-space: nowrap;\n text-overflow: ellipsis;\n}\n\n[dir="rtl"] .label_dac9 {\n text-align: right;\n direction: ltr;\n }\n\n.description_efcc {\n overflow: hidden;\n flex-shrink: 100;\n\n padding-left: 8px;\n\n text-align: right;\n white-space: nowrap;\n text-overflow: ellipsis;\n\n color: var(--ring-secondary-color);\n\n font-size: var(--ring-font-size-smaller);\n font-weight: 400;\n line-height: var(--ring-line-height-lowest);\n}\n\n.right_df77 {\n display: flex;\n align-items: center;\n align-self: center;\n flex-direction: row;\n flex-shrink: 0;\n}\n\n.details_a2b7 {\n margin-bottom: 6px;\n\n white-space: normal;\n\n color: var(--ring-secondary-color);\n\n font-size: var(--ring-font-size-smaller);\n line-height: var(--ring-line-height-lowest);\n}\n\n.padded_a74d {\n margin-left: 20px;\n}\n\n/* Override :last-child */\n.hint_d29d.hint_d29d {\n margin-bottom: 0;\n\n border-top: 1px solid var(--ring-line-color);\n background-color: var(--ring-sidebar-background-color);\n\n font-size: var(--ring-font-size-smaller);\n}\n\n.action_d10e {\n cursor: pointer;\n\n color: var(--ring-text-color);\n}\n\n/* override link */\n.actionLink_a4c7.actionLink_a4c7 {\n transition: none;\n}\n\n.hover_a4cd:not(.error_aa15) {\n background-color: var(--ring-selected-background-color);\n}\n\n.icon_f1f3 {\n display: inline-block;\n\n width: 20px;\n height: 20px;\n margin-left: 16px;\n\n background-repeat: no-repeat;\n background-position: center;\n\n background-size: contain;\n}\n\n.highlight_e4dd {\n color: var(--ring-link-hover-color);\n}\n\n.service_a4fc {\n color: var(--ring-secondary-color);\n}\n\n.glyph_dfd5 {\n float: left;\n\n width: 20px;\n\n margin-right: 8px;\n\n color: var(--ring-icon-secondary-color);\n}\n\n.avatar_f258 {\n\n top: 0;\n\n height: 20px;\n\n -o-object-fit: cover;\n\n object-fit: cover;\n -o-object-position: center;\n object-position: center;\n}\n\n.rightGlyph_fb77 {\n\n float: right;\n\n margin-right: 0;\n margin-left: 16px;\n}\n\n.checkboxContainer_c949 {\n position: absolute;\n top: 7px;\n left: 19px;\n\n width: 20px;\n height: 20px;\n margin-right: 8px;\n}\n\n.compact_efa8 .checkboxContainer_c949 {\n top: 0;\n\n width: 16px;\n height: 16px;\n}\n\n.title_e1bf {\n display: block;\n\n margin-top: 10px;\n margin-bottom: 6px;\n padding: 8px 16px 0;\n\n text-align: left;\n}\n\n[dir="rtl"] .title_e1bf {\n text-align: right;\n direction: ltr;\n }\n\n.title_first_ac55 {\n margin-top: 0;\n}\n\n.text_fe0e {\n letter-spacing: 1.5px;\n text-transform: uppercase;\n\n color: var(--ring-secondary-color);\n\n font-size: var(--ring-font-size-smaller);\n}\n\n.fade_d35c {\n position: absolute;\n bottom: 0;\n\n width: 100%;\n height: 24px;\n\n pointer-events: none;\n\n background: linear-gradient(to bottom, rgba(255, 255, 255, 0), var(--ring-content-background-color));\n}\n\n.disabled_c3d8 {\n pointer-events: none;\n\n color: var(--ring-disabled-color);\n}\n',"",{version:3,sources:["webpack://./node_modules/@jetbrains/ring-ui/components/list/list.css",""],names:[],mappings:"AAKA;EACE,kBAAkB;;EAElB,UAAU;;EAEV,wCAAwC;;EAExC,mBAAmB;AACrB;;AAEA;EACE,cAAc;AAChB;;AAEA;EACE,oBAAoB;AACtB;;AAEA;EACE,cAAc;;EAEd,eAAuB;;EAEvB,eAAuB;EACvB,mBAA6B;;EAE7B,iBAAiB;EACjB,mBAAmB;;EAEnB,kCAAkC;EAClC,4CAA4C;;EAE5C,wCAAwC;EACxC,0CAA0C;AAC5C;;AAEA;EACE,aAAa;EACb,cAAc;;EAEd,YAAY;AACd;;AAEA;EACE,cAAc;;EAEd,sBAAsB;;EAEtB,WAAW;;EAEX,gBAAgB;EAChB,sBAAsB;EACtB,mBAAmB;EACnB,qBAAqB;;EAErB,aAAa;;EAEb,gCAAgC;AAClC;;AAEA;EACE,qBAA+B;;EAE/B,iBAA2B;AAC7B;;AAEA;EACE,kBAAkB;AACpB;;AAEA;EACE,iBAA2B;AAC7B;;AAEA;EACE,eAAe;AASjB;;ACzFA,wGAAA;IAAA,+BAAA;GAAA,CAAA;;ADkFE,uBAAuB;;AACvB;;;IAIE,8BAA8B;EAChC;;AAGF;EACE,iBAA4B;;EAE5B,iBAA2B;AAC7B;;AAEA;EACE,aAAa;EACb,qBAAqB;EACrB,mBAAmB;AACrB;;AAEA;EACE,kBAAkB;EAClB,cAAc;AAChB;;AAEA;EACE,gBAAgB;EAChB,YAAY;EACZ,cAAc;;EAEd,gBAAgB;EAChB,mBAAmB;EACnB,uBAAuB;AAMzB;;AAJE;IACE,iBAAiB;IACjB,cAAc;EAChB;;AAGF;EACE,gBAAgB;EAChB,gBAAgB;;EAEhB,iBAAkB;;EAElB,iBAAiB;EACjB,mBAAmB;EACnB,uBAAuB;;EAEvB,kCAAkC;;EAElC,wCAAwC;EACxC,gBAAgB;EAChB,2CAA2C;AAC7C;;AAEA;EACE,aAAa;EACb,mBAAmB;EACnB,kBAAkB;EAClB,mBAAmB;EACnB,cAAc;AAChB;;AAEA;EACE,kBAAkB;;EAElB,mBAAmB;;EAEnB,kCAAkC;;EAElC,wCAAwC;EACxC,2CAA2C;AAC7C;;AAEA;EACE,iBAAiB;AACnB;;AAEA,yBAAyB;AACzB;EACE,gBAAgB;;EAEhB,4CAA4C;EAC5C,sDAAsD;;EAEtD,wCAAwC;AAC1C;;AAEA;EACE,eAAe;;EAEf,6BAA6B;AAC/B;;AAEA,kBAAkB;AAClB;EACE,gBAAgB;AAClB;;AAEA;EACE,uDAAuD;AACzD;;AAEA;EACE,qBAAqB;;EAErB,WAAW;EACX,YAAY;EACZ,iBAA2B;;EAE3B,4BAA4B;EAC5B,2BAA2B;;EAE3B,wBAAwB;AAC1B;;AAEA;EACE,mCAAmC;AACrC;;AAEA;EACE,kCAAkC;AACpC;;AAEA;EACE,WAAW;;EAEX,WAAW;;EAEX,iBAAkB;;EAElB,uCAAuC;AACzC;;AAEA;;EAGE,MAAM;;EAEN,YAAY;;EAEZ,oBAAiB;;KAAjB,iBAAiB;EACjB,0BAAuB;KAAvB,uBAAuB;AACzB;;AAEA;;EAGE,YAAY;;EAEZ,eAAe;EACf,iBAA2B;AAC7B;;AAEA;EACE,kBAAkB;EAClB,QAAQ;EACR,UAAU;;EAEV,WAAW;EACX,YAAY;EACZ,iBAAkB;AACpB;;AAEA;EACE,MAAM;;EAEN,WAAqB;EACrB,YAAsB;AACxB;;AAEA;EACE,cAAc;;EAEd,gBAAgB;EAChB,kBAAkB;EAClB,mBAAqC;;EAErC,gBAAgB;AAMlB;;AAJE;IACE,iBAAiB;IACjB,cAAc;EAChB;;AAGF;EACE,aAAa;AACf;;AAEA;EACE,qBAAqB;EACrB,yBAAyB;;EAEzB,kCAAkC;;EAElC,wCAAwC;AAC1C;;AAEA;EACE,kBAAkB;EAClB,SAAS;;EAET,WAAW;EACX,YAAsB;;EAEtB,oBAAoB;;EAEpB,oGAAoG;AACtG;;AAEA;EACE,oBAAoB;;EAEpB,iCAAiC;AACnC",sourcesContent:['@import "../global/variables.css";\n\n@value unit from "../global/global.css";\n@value listSpacing: unit;\n\n.list {\n position: relative;\n\n z-index: 1;\n\n border-radius: var(--ring-border-radius);\n\n line-height: normal;\n}\n\n.simpleInner {\n overflow: auto;\n}\n\n.scrolling {\n pointer-events: none;\n}\n\n.separator {\n display: block;\n\n min-height: listSpacing;\n\n margin-top: listSpacing;\n padding: 0 calc(unit * 2) 1px;\n\n text-align: right;\n white-space: nowrap;\n\n color: var(--ring-secondary-color);\n border-top: 1px solid var(--ring-line-color);\n\n font-size: var(--ring-font-size-smaller);\n line-height: var(--ring-line-height-lower);\n}\n\n.separator_first {\n margin-top: 0;\n padding-top: 0;\n\n border: none;\n}\n\n.item {\n display: block;\n\n box-sizing: border-box;\n\n width: 100%;\n\n text-align: left;\n vertical-align: bottom;\n white-space: nowrap;\n text-decoration: none;\n\n outline: none;\n\n font-size: var(--ring-font-size);\n}\n\n.item.item {\n padding: 3px calc(unit * 2) 5px;\n\n line-height: calc(unit * 3);\n}\n\n.itemContainer {\n position: relative;\n}\n\n.compact {\n line-height: calc(unit * 2);\n}\n\n.error {\n cursor: default;\n\n /* Override ring-link */\n &,\n &:hover,\n &:focus,\n &:visited {\n color: var(--ring-error-color);\n }\n}\n\n.add {\n padding: unit calc(2 * unit);\n\n line-height: calc(4 * unit);\n}\n\n.top {\n display: flex;\n align-items: baseline;\n flex-direction: row;\n}\n\n.left {\n align-self: center;\n flex-shrink: 0;\n}\n\n.label {\n overflow: hidden;\n flex-grow: 1;\n flex-shrink: 1;\n\n text-align: left;\n white-space: nowrap;\n text-overflow: ellipsis;\n\n [dir="rtl"] & {\n text-align: right;\n direction: ltr;\n }\n}\n\n.description {\n overflow: hidden;\n flex-shrink: 100;\n\n padding-left: unit;\n\n text-align: right;\n white-space: nowrap;\n text-overflow: ellipsis;\n\n color: var(--ring-secondary-color);\n\n font-size: var(--ring-font-size-smaller);\n font-weight: 400;\n line-height: var(--ring-line-height-lowest);\n}\n\n.right {\n display: flex;\n align-items: center;\n align-self: center;\n flex-direction: row;\n flex-shrink: 0;\n}\n\n.details {\n margin-bottom: 6px;\n\n white-space: normal;\n\n color: var(--ring-secondary-color);\n\n font-size: var(--ring-font-size-smaller);\n line-height: var(--ring-line-height-lowest);\n}\n\n.padded {\n margin-left: 20px;\n}\n\n/* Override :last-child */\n.hint.hint {\n margin-bottom: 0;\n\n border-top: 1px solid var(--ring-line-color);\n background-color: var(--ring-sidebar-background-color);\n\n font-size: var(--ring-font-size-smaller);\n}\n\n.action {\n cursor: pointer;\n\n color: var(--ring-text-color);\n}\n\n/* override link */\n.actionLink.actionLink {\n transition: none;\n}\n\n.hover:not(.error) {\n background-color: var(--ring-selected-background-color);\n}\n\n.icon {\n display: inline-block;\n\n width: 20px;\n height: 20px;\n margin-left: calc(unit * 2);\n\n background-repeat: no-repeat;\n background-position: center;\n\n background-size: contain;\n}\n\n.highlight {\n color: var(--ring-link-hover-color);\n}\n\n.service {\n color: var(--ring-secondary-color);\n}\n\n.glyph {\n float: left;\n\n width: 20px;\n\n margin-right: unit;\n\n color: var(--ring-icon-secondary-color);\n}\n\n.avatar {\n composes: glyph;\n\n top: 0;\n\n height: 20px;\n\n object-fit: cover;\n object-position: center;\n}\n\n.rightGlyph {\n composes: glyph;\n\n float: right;\n\n margin-right: 0;\n margin-left: calc(unit * 2);\n}\n\n.checkboxContainer {\n position: absolute;\n top: 7px;\n left: 19px;\n\n width: 20px;\n height: 20px;\n margin-right: unit;\n}\n\n.compact .checkboxContainer {\n top: 0;\n\n width: calc(unit * 2);\n height: calc(unit * 2);\n}\n\n.title {\n display: block;\n\n margin-top: 10px;\n margin-bottom: 6px;\n padding: listSpacing calc(unit * 2) 0;\n\n text-align: left;\n\n [dir="rtl"] & {\n text-align: right;\n direction: ltr;\n }\n}\n\n.title_first {\n margin-top: 0;\n}\n\n.text {\n letter-spacing: 1.5px;\n text-transform: uppercase;\n\n color: var(--ring-secondary-color);\n\n font-size: var(--ring-font-size-smaller);\n}\n\n.fade {\n position: absolute;\n bottom: 0;\n\n width: 100%;\n height: calc(unit * 3);\n\n pointer-events: none;\n\n background: linear-gradient(to bottom, rgba(255, 255, 255, 0), var(--ring-content-background-color));\n}\n\n.disabled {\n pointer-events: none;\n\n color: var(--ring-disabled-color);\n}\n',null],sourceRoot:""}]),s.locals={unit:`${l.default.locals.unit}`,listSpacing:"8px",list:"list_a01c",simpleInner:"simpleInner_a4f8",scrolling:"scrolling_a910",separator:"separator_c26e",separator_first:"separator_first_ec9e",item:"item_eadd",itemContainer:"itemContainer_f365",compact:"compact_efa8",error:"error_aa15",add:"add_a8da",top:"top_c4d5",left:"left_ea6b",label:"label_dac9",description:"description_efcc",right:"right_df77",details:"details_a2b7",padded:"padded_a74d",hint:"hint_d29d",action:"action_d10e",actionLink:"actionLink_a4c7",hover:"hover_a4cd",icon:"icon_f1f3",highlight:"highlight_e4dd",service:"service_a4fc",glyph:"glyph_dfd5",avatar:"avatar_f258 glyph_dfd5",rightGlyph:"rightGlyph_fb77 glyph_dfd5",checkboxContainer:"checkboxContainer_c949",title:"title_e1bf",title_first:"title_first_ac55",text:"text_fe0e",fade:"fade_d35c",disabled:"disabled_c3d8"};const u=s},1586:(e,n,t)=>{"use strict";t.r(n),t.d(n,{default:()=>p});var r=t(1404),o=t.n(r),i=t(7156),a=t.n(i),l=t(9173),c=t(9106),s=t(5280),u=a()(o());u.i(s.A),u.i(l.A,"",!0),u.i(c.default,"",!0),u.push([e.id,`:root {\n /* stylelint-disable-next-line color-no-hex */\n --ring-loader-inline-stops: #ff00eb, #bd3bff, #008eff, #58ba00, #f48700, #ff00eb;\n}\n\n.${l.A.locals.dark},\n.ring-ui-theme-dark {\n /* stylelint-disable-next-line color-no-hex */\n --ring-loader-inline-stops: #ff2eef, #d178ff, #289fff, #88d444, #ffe000, #ff2eef;\n}\n\n@keyframes spin_ad60 {\n 0% {\n transform: rotate(0);\n }\n\n 100% {\n transform: rotate(360deg);\n }\n}\n\n@keyframes pulse_c906 {\n 0% {\n transform: scale(1);\n }\n\n 100% {\n transform: scale(1.41667);\n }\n}\n\n.loader_d294,\n.ring-loader-inline {\n /* needed for better backward-compatibility */\n\n position: relative;\n\n display: inline-block;\n\n overflow: hidden;\n\n transform: rotate(0);\n animation: spin_ad60 1s linear infinite;\n vertical-align: -3px;\n\n border-radius: 8px;\n}\n\n.loader_d294,\n .ring-loader-inline,\n .loader_d294::after,\n .ring-loader-inline::after {\n transform-origin: 50% 50%;\n }\n\n.loader_d294::after, .ring-loader-inline::after {\n display: block;\n\n width: 16px;\n height: 16px;\n\n content: "";\n animation: pulse_c906 0.85s cubic-bezier(0.68, 0, 0.74, 0.74) infinite alternate;\n\n background-image: conic-gradient(#ff00eb, #bd3bff, #008eff, #58ba00, #f48700, #ff00eb);\n\n background-image: conic-gradient(var(--ring-loader-inline-stops));\n -webkit-mask-image: radial-gradient(8px, transparent 71.875%, var(--ring-content-background-color) 71.875%);\n mask-image: radial-gradient(8px, transparent 71.875%, var(--ring-content-background-color) 71.875%);\n }\n\n.children_ece6 {\n margin-left: 4px;\n}\n`,"",{version:3,sources:["webpack://./node_modules/@jetbrains/ring-ui/components/loader-inline/loader-inline.css"],names:[],mappings:"AAKA;EACE,6CAA6C;EAC7C,gFAAgF;AAClF;;AAEA;;EAEE,6CAA6C;EAC7C,gFAAgF;AAClF;;AAEA;EACE;IACE,oBAAoB;EACtB;;EAEA;IACE,yBAAyB;EAC3B;AACF;;AAEA;EACE;IACE,mBAAmB;EACrB;;EAEA;IACE,yBAA+B;EACjC;AACF;;AAEA;;EAEE,6CAA6C;;EAE7C,kBAAkB;;EAElB,qBAAqB;;EAErB,gBAAgB;;EAEhB,oBAAoB;EACpB,uCAAkC;EAClC,oBAAoB;;EAEpB,kBAAmB;AAmBrB;;AAjBE;;;;IAEE,yBAAyB;EAC3B;;AAEA;IACE,cAAc;;IAEd,WAAqB;IACrB,YAAsB;;IAEtB,WAAW;IACX,gFAA2E;;IAE3E,sFAAiE;;IAAjE,iEAAiE;IACjE,2GAAoG;YAApG,mGAAoG;EACtG;;AAGF;EACE,gBAA2B;AAC7B",sourcesContent:['@import "../global/variables.css";\n\n@value dark from "../global/variables_dark.css";\n@value unit from "../global/global.css";\n\n:root {\n /* stylelint-disable-next-line color-no-hex */\n --ring-loader-inline-stops: #ff00eb, #bd3bff, #008eff, #58ba00, #f48700, #ff00eb;\n}\n\n.dark,\n:global(.ring-ui-theme-dark) {\n /* stylelint-disable-next-line color-no-hex */\n --ring-loader-inline-stops: #ff2eef, #d178ff, #289fff, #88d444, #ffe000, #ff2eef;\n}\n\n@keyframes spin {\n 0% {\n transform: rotate(0);\n }\n\n 100% {\n transform: rotate(360deg);\n }\n}\n\n@keyframes pulse {\n 0% {\n transform: scale(1);\n }\n\n 100% {\n transform: scale(calc(17 / 12));\n }\n}\n\n.loader,\n:global(.ring-loader-inline) {\n /* needed for better backward-compatibility */\n\n position: relative;\n\n display: inline-block;\n\n overflow: hidden;\n\n transform: rotate(0);\n animation: spin 1s linear infinite;\n vertical-align: -3px;\n\n border-radius: unit;\n\n &,\n &::after {\n transform-origin: 50% 50%;\n }\n\n &::after {\n display: block;\n\n width: calc(unit * 2);\n height: calc(unit * 2);\n\n content: "";\n animation: pulse 0.85s cubic-bezier(0.68, 0, 0.74, 0.74) infinite alternate;\n\n background-image: conic-gradient(var(--ring-loader-inline-stops));\n mask-image: radial-gradient(unit, transparent 71.875%, var(--ring-content-background-color) 71.875%);\n }\n}\n\n.children {\n margin-left: calc(unit / 2);\n}\n'],sourceRoot:""}]),u.locals={dark:`${l.A.locals.dark}`,unit:`${c.default.locals.unit}`,loader:"loader_d294",spin:"spin_ad60",pulse:"pulse_c906",children:"children_ece6"};const p=u},8890:(e,n,t)=>{"use strict";t.r(n),t.d(n,{default:()=>u});var r=t(1404),o=t.n(r),i=t(7156),a=t.n(i),l=t(9106),c=t(5280),s=a()(o());s.i(c.A),s.i(l.default,"",!0),s.push([e.id,".popup_f35e {\n\n position: fixed;\n z-index: var(--ring-overlay-z-index);\n top: -100vh;\n left: -100vw;\n\n overflow-y: auto;\n\n box-sizing: border-box;\n\n border: 1px solid var(--ring-popup-border-color);\n border-radius: var(--ring-border-radius);\n\n background-color: var(--ring-popup-background-color);\n box-shadow: var(--ring-popup-shadow);\n}\n\n.hidden_c587 {\n display: none;\n}\n\n.showing_b07a {\n opacity: 0;\n}\n\n.attached_ea95 {\n border-top: 0;\n border-top-left-radius: 0;\n border-top-right-radius: 0;\n}\n","",{version:3,sources:["webpack://./node_modules/@jetbrains/ring-ui/components/popup/popup.css"],names:[],mappings:"AAEA;;EAGE,eAAe;EACf,oCAAoC;EACpC,WAAW;EACX,YAAY;;EAEZ,gBAAgB;;EAEhB,sBAAsB;;EAEtB,gDAAgD;EAChD,wCAAwC;;EAExC,oDAAoD;EACpD,oCAAoC;AACtC;;AAEA;EACE,aAAa;AACf;;AAEA;EACE,UAAU;AACZ;;AAEA;EACE,aAAa;EACb,yBAAyB;EACzB,0BAA0B;AAC5B",sourcesContent:['@import "../global/variables.css";\n\n.popup {\n composes: font from "../global/global.css";\n\n position: fixed;\n z-index: var(--ring-overlay-z-index);\n top: -100vh;\n left: -100vw;\n\n overflow-y: auto;\n\n box-sizing: border-box;\n\n border: 1px solid var(--ring-popup-border-color);\n border-radius: var(--ring-border-radius);\n\n background-color: var(--ring-popup-background-color);\n box-shadow: var(--ring-popup-shadow);\n}\n\n.hidden {\n display: none;\n}\n\n.showing {\n opacity: 0;\n}\n\n.attached {\n border-top: 0;\n border-top-left-radius: 0;\n border-top-right-radius: 0;\n}\n'],sourceRoot:""}]),s.locals={popup:`popup_f35e ${l.default.locals.font}`,hidden:"hidden_c587",showing:"showing_b07a",attached:"attached_ea95"};const u=s},4481:(e,n,t)=>{"use strict";t.r(n),t.d(n,{default:()=>u});var r=t(1404),o=t.n(r),i=t(7156),a=t.n(i),l=t(9106),c=t(5280),s=a()(o());s.i(c.A),s.i(l.default,"",!0),s.push([e.id,'@media (hover: hover), (-moz-touch-enabled: 0), (-ms-high-contrast: none), (-ms-high-contrast: active) {.filterWithTagsFocused_ffbf.filterWithTagsFocused_ffbf:hover {\n border-color: var(--ring-main-color);\n}}\n\n.filterWithTags_ff56 {\n overflow: hidden;\n\n margin: 16px 8px 0;\n padding: 3px;\n\n text-align: left;\n\n border: 1px solid var(--ring-borders-color);\n border-radius: var(--ring-border-radius);\n}\n\n.filterWithTags_ff56 .filterWrapper_dd63 {\n padding-right: 0;\n padding-left: 0;\n\n border-bottom: none;\n }\n\n@media (hover: hover), (-moz-touch-enabled: 0), (-ms-high-contrast: none), (-ms-high-contrast: active) {.filterWithTags_ff56:hover {\n border-color: var(--ring-border-hover-color);\n }}\n\n.filterWithTagsFocused_ffbf {\n border-color: var(--ring-main-color);\n}\n\n.filterWithTagsInput_ab94 {\n padding: 0;\n\n border: none;\n}\n\n.filter_deda {\n flex-grow: 1;\n\n width: 0;\n}\n\n.popup_f21d {\n overscroll-behavior: contain;\n}\n\n.filterWrapper_dd63 {\n position: relative;\n\n display: flex;\n\n margin: 0;\n padding-right: 8px;\n padding-left: 44px;\n\n border-bottom: 1px solid var(--ring-borders-color);\n}\n\n[dir="rtl"] .filterWrapper_dd63 {\n padding-right: 44px;\n padding-left: 8px;\n }\n\n.filterIcon_b648 {\n position: absolute;\n top: 7px;\n left: 16px;\n\n color: var(--ring-icon-color);\n}\n\n[dir="rtl"] .filterIcon_b648 {\n right: 16px;\n left: auto;\n }\n\n.bottomLine_c880 {\n text-align: center;\n}\n\n.bottomLine_c880.bottomLineOverItem_dfb4 {\n position: relative;\n\n z-index: var(--ring-fixed-z-index);\n\n margin-top: -36px;\n\n background-color: var(--ring-content-background-color);\n }\n\n.message_ccdf {\n display: inline-block;\n\n margin: 8px 0;\n padding: 0 16px;\n}\n\n.selectAll_ff5e {\n display: flex;\n justify-content: space-between;\n\n padding: 8px 16px 0;\n}\n',"",{version:3,sources:["","webpack://./node_modules/@jetbrains/ring-ui/components/select/select-popup.css"],names:[],mappings:"AAAA,wGAAA;EAAA,qCAAA;CAAA,CAAA;;ACIA;EACE,gBAAgB;;EAEhB,kBAA6B;EAC7B,YAAY;;EAEZ,gBAAgB;;EAEhB,2CAA2C;EAC3C,wCAAwC;AAY1C;;AAVE;IACE,gBAAgB;IAChB,eAAe;;IAEf,mBAAmB;EACrB;;ADpBF,wGAAA;IAAA,6CAAA;GAAA,CAAA;;AC2BA;EAEE,oCAAoC;AACtC;;AAEA;EACE,UAAU;;EAEV,YAAY;AACd;;AAEA;EACE,YAAY;;EAEZ,QAAQ;AACV;;AAEA;EACE,4BAA4B;AAC9B;;AAEA;EACE,kBAAkB;;EAElB,aAAa;;EAEb,SAAS;EACT,kBAAmB;EACnB,kBAA8B;;EAE9B,kDAAkD;AAMpD;;AAJE;IACE,mBAA+B;IAC/B,iBAAkB;EACpB;;AAGF;EACE,kBAAkB;EAClB,QAAQ;EACR,UAAoB;;EAEpB,6BAA6B;AAM/B;;AAJE;IACE,WAAqB;IACrB,UAAU;EACZ;;AAGF;EACE,kBAAkB;AAWpB;;AATE;IACE,kBAAkB;;IAElB,kCAAkC;;IAElC,iBAAiB;;IAEjB,sDAAsD;EACxD;;AAGF;EACE,qBAAqB;;EAErB,aAAc;EACd,eAAyB;AAC3B;;AAEA;EACE,aAAa;EACb,8BAA8B;;EAE9B,mBAAmB;AACrB",sourcesContent:[null,'@import "../global/variables.css";\n\n@value unit from "../global/global.css";\n\n.filterWithTags {\n overflow: hidden;\n\n margin: calc(unit * 2) unit 0;\n padding: 3px;\n\n text-align: left;\n\n border: 1px solid var(--ring-borders-color);\n border-radius: var(--ring-border-radius);\n\n & .filterWrapper {\n padding-right: 0;\n padding-left: 0;\n\n border-bottom: none;\n }\n\n &:hover {\n border-color: var(--ring-border-hover-color);\n }\n}\n\n.filterWithTagsFocused,\n.filterWithTagsFocused.filterWithTagsFocused:hover {\n border-color: var(--ring-main-color);\n}\n\n.filterWithTagsInput {\n padding: 0;\n\n border: none;\n}\n\n.filter {\n flex-grow: 1;\n\n width: 0;\n}\n\n.popup {\n overscroll-behavior: contain;\n}\n\n.filterWrapper {\n position: relative;\n\n display: flex;\n\n margin: 0;\n padding-right: unit;\n padding-left: calc(unit * 5.5);\n\n border-bottom: 1px solid var(--ring-borders-color);\n\n [dir="rtl"] & {\n padding-right: calc(unit * 5.5);\n padding-left: unit;\n }\n}\n\n.filterIcon {\n position: absolute;\n top: 7px;\n left: calc(unit * 2);\n\n color: var(--ring-icon-color);\n\n [dir="rtl"] & {\n right: calc(unit * 2);\n left: auto;\n }\n}\n\n.bottomLine {\n text-align: center;\n\n &.bottomLineOverItem {\n position: relative;\n\n z-index: var(--ring-fixed-z-index);\n\n margin-top: -36px;\n\n background-color: var(--ring-content-background-color);\n }\n}\n\n.message {\n display: inline-block;\n\n margin: unit 0;\n padding: 0 calc(2 * unit);\n}\n\n.selectAll {\n display: flex;\n justify-content: space-between;\n\n padding: 8px 16px 0;\n}\n'],sourceRoot:""}]),s.locals={unit:`${l.default.locals.unit}`,filterWithTagsFocused:"filterWithTagsFocused_ffbf",filterWithTags:"filterWithTags_ff56",filterWrapper:"filterWrapper_dd63",filterWithTagsInput:"filterWithTagsInput_ab94",filter:"filter_deda",popup:"popup_f21d",filterIcon:"filterIcon_b648",bottomLine:"bottomLine_c880",bottomLineOverItem:"bottomLineOverItem_dfb4",message:"message_ccdf",selectAll:"selectAll_ff5e"};const u=s},2636:(e,n,t)=>{"use strict";t.r(n),t.d(n,{default:()=>p});var r=t(1404),o=t.n(r),i=t(7156),a=t.n(i),l=t(9106),c=t(9892),s=t(5280),u=a()(o());u.i(s.A),u.i(l.default,"",!0),u.i(c.default,"",!0),u.push([e.id,'@media (hover: hover), (-moz-touch-enabled: 0), (-ms-high-contrast: none), (-ms-high-contrast: active) {.select_e2a5:hover .value_b3a3,\n.select_e2a5:hover .icons_c4a9 {\n transition: none;\n\n color: var(--ring-main-color);\n}}\n\n.select_e2a5 {\n position: relative;\n\n display: inline-block;\n\n white-space: nowrap;\n\n color: var(--ring-text-color);\n}\n\n.toolbar_d3be {\n border-top: 1px solid var(--ring-line-color);\n}\n\n.button_ef00 {\n width: 100%;\n padding: 0;\n\n text-align: left;\n}\n\n[dir="rtl"] .button_ef00 {\n text-align: right;\n direction: ltr;\n }\n\n.toolbar_d3be .button_ef00 {\n height: 32px;\n margin: 8px 0;\n }\n\n.button_ef00.buttonSpaced_f316 {\n padding: 0 16px;\n }\n\n.icons_c4a9 {\n position: absolute;\n top: 0;\n right: 5px;\n bottom: 0;\n\n transition: color var(--ring-ease);\n\n color: var(--ring-icon-secondary-color);\n\n line-height: normal;\n}\n\n.inputMode_a6f6 .icons_c4a9 {\n font-size: var(--ring-font-size);\n }\n\n.selectedIcon_a62c {\n\n position: relative;\n top: 3px;\n\n display: inline-block;\n\n width: 16px;\n height: 16px;\n margin: 0 4px;\n\n background-repeat: no-repeat;\n background-position: center;\n\n background-size: contain;\n}\n\n.clearIcon_c750 {\n padding: 0 3px;\n\n vertical-align: -2px;\n}\n\n.sizeS_e8c3 {\n width: 96px;\n}\n\n.sizeM_ed34 {\n width: 240px;\n}\n\n.sizeL_c053 {\n width: 400px;\n}\n\n.sizeFULL_c585 {\n width: 100%;\n}\n\n.sizeAUTO_a07c {\n max-width: 100%;\n}\n\n.buttonMode_dd69 {\n position: relative;\n\n cursor: pointer;\n}\n\n.value_b3a3 {\n\n display: inline-block;\n\n box-sizing: border-box;\n width: 100%;\n height: 33px;\n padding: 0 0 3px;\n\n cursor: pointer;\n transition: color var(--ring-ease), border-color var(--ring-ease);\n text-align: left;\n vertical-align: top;\n\n color: var(--ring-text-color);\n\n border: none;\n border-bottom: 1px solid var(--ring-borders-color);\n outline: none;\n background: transparent;\n}\n\n.value_b3a3:focus {\n border-color: var(--ring-main-color);\n }\n\n.value_b3a3.open_f1b1,\n .value_b3a3:active {\n border-color: transparent;\n }\n\n.value_b3a3::-moz-focus-inner {\n padding: 0;\n\n border: 0;\n outline: 0;\n }\n\n.buttonContainer_b2b9 {\n position: relative;\n\n font-size: var(--ring-font-size);\n}\n\n.buttonValue_b4ad {\n\n display: block;\n\n width: 100%;\n padding-left: 8px;\n\n text-align: left;\n vertical-align: -8px;\n}\n\n.buttonValue_b4ad:focus-visible {\n box-shadow: inset 0 0 0 1px var(--ring-main-color);\n}\n\n.buttonValueOpen_d9d3.buttonValueOpen_d9d3 {\n box-shadow: inset 0 0 0 1px var(--ring-main-color);\n}\n\n.buttonValueEmpty_e6b3.buttonValueEmpty_e6b3 {\n color: var(--ring-disabled-color);\n}\n\n.heightS_b721 .buttonValue_b4ad {\n font-size: var(--ring-font-size);\n}\n\n.label_e56f {\n position: relative;\n\n color: var(--ring-secondary-color);\n}\n\n:focus-visible + .icons_c4a9,\n.value_b3a3:focus,\n.value_b3a3:focus + .icons_c4a9,\n.open_f1b1,\n.open_f1b1 + .icons_c4a9,\n.buttonValueOpen_d9d3 + .icons_c4a9 {\n transition: none;\n\n color: var(--ring-main-color);\n}\n\n.disabled_b89f {\n pointer-events: none;\n\n color: var(--ring-disabled-color);\n}\n\n.disabled_b89f .value_b3a3 {\n color: var(--ring-disabled-color);\n border-bottom-style: dashed;\n }\n\n.avatar_f4dd {\n margin-right: 4px;\n\n vertical-align: -5px;\n}\n\n.popup_acec {\n min-width: 240px;\n max-width: 320px;\n}\n\n.chevron_d51f.chevron_d51f {\n padding: 0 3px;\n\n transition: none;\n vertical-align: -1px;\n\n color: inherit;\n}\n\n.chevronIcon_f6cf.chevronIcon_f6cf {\n transition: none;\n\n color: inherit;\n}\n',"",{version:3,sources:["","webpack://./node_modules/@jetbrains/ring-ui/components/select/select.css"],names:[],mappings:"AAAA,wGAAA;;EAAA,iBAAA;;EAAA,8BAAA;CAAA,CAAA;;ACKA;EACE,kBAAkB;;EAElB,qBAAqB;;EAErB,mBAAmB;;EAEnB,6BAA6B;AAC/B;;AAEA;EACE,4CAA4C;AAC9C;;AAEA;EACE,WAAW;EACX,UAAU;;EAEV,gBAAgB;AAelB;;AAbE;IACE,iBAAiB;IACjB,cAAc;EAChB;;AAEA;IACE,YAAsB;IACtB,aAAc;EAChB;;AAEA;IACE,eAAyB;EAC3B;;AAGF;EACE,kBAAkB;EAClB,MAAM;EACN,UAAU;EACV,SAAS;;EAET,kCAAkC;;EAElC,uCAAuC;;EAEvC,mBAAmB;AAKrB;;AAHE;IACE,gCAAgC;EAClC;;AAGF;;EAGE,kBAAkB;EAClB,QAAQ;;EAER,qBAAqB;;EAErB,WAAqB;EACrB,YAAsB;EACtB,aAAa;;EAEb,4BAA4B;EAC5B,2BAA2B;;EAE3B,wBAAwB;AAC1B;;AAEA;EACE,cAAc;;EAEd,oBAAoB;AACtB;;AAEA;EACE,WAAsB;AACxB;;AAEA;EACE,YAAsB;AACxB;;AAEA;EACE,YAAsB;AACxB;;AAEA;EACE,WAAW;AACb;;AAEA;EACE,eAAe;AACjB;;AAEA;EACE,kBAAkB;;EAElB,eAAe;AACjB;;AAEA;;EAIE,qBAAqB;;EAErB,sBAAsB;EACtB,WAAW;EACX,YAA4B;EAC5B,gBAAgB;;EAEhB,eAAe;EACf,iEAAiE;EACjE,gBAAgB;EAChB,mBAAmB;;EAEnB,6BAA6B;;EAE7B,YAAY;EACZ,kDAAkD;EAClD,aAAa;EACb,uBAAuB;AAiBzB;;AAfE;IACE,oCAAoC;EACtC;;AAEA;;IAEE,yBAAyB;EAC3B;;AAEA;IACE,UAAU;;IAEV,SAAS;IACT,UAAU;EACZ;;AAGF;EACE,kBAAkB;;EAElB,gCAAgC;AAClC;;AAEA;;EAGE,cAAc;;EAEd,WAAW;EACX,iBAAkB;;EAElB,gBAAgB;EAChB,oBAA8B;AAChC;;AAEA;EACE,kDAAgD;AAClD;;AAEA;EACE,kDAAgD;AAClD;;AAEA;EACE,iCAAiC;AACnC;;AAEA;EACE,gCAAgC;AAClC;;AAEA;EACE,kBAAkB;;EAElB,kCAAkC;AACpC;;AAEA;;;;;;EAQE,gBAAgB;;EAEhB,6BAA6B;AAC/B;;AAEA;EACE,oBAAoB;;EAEpB,iCAAiC;AAMnC;;AAJE;IACE,iCAAiC;IACjC,2BAA2B;EAC7B;;AAGF;EACE,iBAAiB;;EAEjB,oBAAoB;AACtB;;AAEA;EACE,gBAA0B;EAC1B,gBAA0B;AAC5B;;AAEA;EACE,cAAc;;EAEd,gBAAgB;EAChB,oBAAoB;;EAEpB,cAAc;AAChB;;AAEA;EACE,gBAAgB;;EAEhB,cAAc;AAChB",sourcesContent:[null,'@import "../global/variables.css";\n\n@value unit from "../global/global.css";\n@value button-shadow from "../button/button.css";\n\n.select {\n position: relative;\n\n display: inline-block;\n\n white-space: nowrap;\n\n color: var(--ring-text-color);\n}\n\n.toolbar {\n border-top: 1px solid var(--ring-line-color);\n}\n\n.button {\n width: 100%;\n padding: 0;\n\n text-align: left;\n\n [dir="rtl"] & {\n text-align: right;\n direction: ltr;\n }\n\n .toolbar & {\n height: calc(4 * unit);\n margin: unit 0;\n }\n\n &.buttonSpaced {\n padding: 0 calc(2 * unit);\n }\n}\n\n.icons {\n position: absolute;\n top: 0;\n right: 5px;\n bottom: 0;\n\n transition: color var(--ring-ease);\n\n color: var(--ring-icon-secondary-color);\n\n line-height: normal;\n\n .inputMode & {\n font-size: var(--ring-font-size);\n }\n}\n\n.selectedIcon {\n composes: resetButton from "../global/global.css";\n\n position: relative;\n top: 3px;\n\n display: inline-block;\n\n width: calc(2 * unit);\n height: calc(2 * unit);\n margin: 0 4px;\n\n background-repeat: no-repeat;\n background-position: center;\n\n background-size: contain;\n}\n\n.clearIcon {\n padding: 0 3px;\n\n vertical-align: -2px;\n}\n\n.sizeS {\n width: calc(unit * 12);\n}\n\n.sizeM {\n width: calc(unit * 30);\n}\n\n.sizeL {\n width: calc(unit * 50);\n}\n\n.sizeFULL {\n width: 100%;\n}\n\n.sizeAUTO {\n max-width: 100%;\n}\n\n.buttonMode {\n position: relative;\n\n cursor: pointer;\n}\n\n.value {\n composes: ellipsis from "../global/global.css";\n composes: font from "../global/global.css";\n\n display: inline-block;\n\n box-sizing: border-box;\n width: 100%;\n height: calc(unit * 4 + 1px);\n padding: 0 0 3px;\n\n cursor: pointer;\n transition: color var(--ring-ease), border-color var(--ring-ease);\n text-align: left;\n vertical-align: top;\n\n color: var(--ring-text-color);\n\n border: none;\n border-bottom: 1px solid var(--ring-borders-color);\n outline: none;\n background: transparent;\n\n &:focus {\n border-color: var(--ring-main-color);\n }\n\n &.open,\n &:active {\n border-color: transparent;\n }\n\n &::-moz-focus-inner {\n padding: 0;\n\n border: 0;\n outline: 0;\n }\n}\n\n.buttonContainer {\n position: relative;\n\n font-size: var(--ring-font-size);\n}\n\n.buttonValue {\n composes: ellipsis from "../global/global.css";\n\n display: block;\n\n width: 100%;\n padding-left: unit;\n\n text-align: left;\n vertical-align: calc(0 - unit);\n}\n\n.buttonValue:focus-visible {\n box-shadow: button-shadow var(--ring-main-color);\n}\n\n.buttonValueOpen.buttonValueOpen {\n box-shadow: button-shadow var(--ring-main-color);\n}\n\n.buttonValueEmpty.buttonValueEmpty {\n color: var(--ring-disabled-color);\n}\n\n.heightS .buttonValue {\n font-size: var(--ring-font-size);\n}\n\n.label {\n position: relative;\n\n color: var(--ring-secondary-color);\n}\n\n.select:hover .value,\n.select:hover .icons,\n:focus-visible + .icons,\n.value:focus,\n.value:focus + .icons,\n.open,\n.open + .icons,\n.buttonValueOpen + .icons {\n transition: none;\n\n color: var(--ring-main-color);\n}\n\n.disabled {\n pointer-events: none;\n\n color: var(--ring-disabled-color);\n\n & .value {\n color: var(--ring-disabled-color);\n border-bottom-style: dashed;\n }\n}\n\n.avatar {\n margin-right: 4px;\n\n vertical-align: -5px;\n}\n\n.popup {\n min-width: calc(unit * 30);\n max-width: calc(unit * 40);\n}\n\n.chevron.chevron {\n padding: 0 3px;\n\n transition: none;\n vertical-align: -1px;\n\n color: inherit;\n}\n\n.chevronIcon.chevronIcon {\n transition: none;\n\n color: inherit;\n}\n'],sourceRoot:""}]),u.locals={unit:`${l.default.locals.unit}`,"button-shadow":`${c.default.locals["button-shadow"]}`,select:"select_e2a5",value:`value_b3a3 ${l.default.locals.ellipsis} ${l.default.locals.font}`,icons:"icons_c4a9",toolbar:"toolbar_d3be",button:"button_ef00",buttonSpaced:"buttonSpaced_f316",inputMode:"inputMode_a6f6",selectedIcon:`selectedIcon_a62c ${l.default.locals.resetButton}`,clearIcon:"clearIcon_c750",sizeS:"sizeS_e8c3",sizeM:"sizeM_ed34",sizeL:"sizeL_c053",sizeFULL:"sizeFULL_c585",sizeAUTO:"sizeAUTO_a07c",buttonMode:"buttonMode_dd69",open:"open_f1b1",buttonContainer:"buttonContainer_b2b9",buttonValue:`buttonValue_b4ad ${l.default.locals.ellipsis}`,buttonValueOpen:"buttonValueOpen_d9d3",buttonValueEmpty:"buttonValueEmpty_e6b3",heightS:"heightS_b721",label:"label_e56f",disabled:"disabled_b89f",avatar:"avatar_f4dd",popup:"popup_acec",chevron:"chevron_d51f",chevronIcon:"chevronIcon_f6cf"};const p=u},8102:(e,n,t)=>{"use strict";t.r(n),t.d(n,{default:()=>s});var r=t(1404),o=t.n(r),i=t(7156),a=t.n(i),l=t(5280),c=a()(o());c.i(l.A),c.push([e.id,".trapButton_c32e {\n position: absolute;\n left: -9999px;\n}\n","",{version:3,sources:["webpack://./node_modules/@jetbrains/ring-ui/components/tab-trap/tab-trap.css"],names:[],mappings:"AAEA;EACE,kBAAkB;EAClB,aAAa;AACf",sourcesContent:['@import "../global/variables.css";\n\n.trapButton {\n position: absolute;\n left: -9999px;\n}\n'],sourceRoot:""}]),c.locals={trapButton:"trapButton_c32e"};const s=c},4561:(e,n,t)=>{"use strict";t.r(n),t.d(n,{default:()=>u});var r=t(1404),o=t.n(r),i=t(7156),a=t.n(i),l=t(9106),c=t(5280),s=a()(o());s.i(c.A),s.i(l.default,"",!0),s.push([e.id,'@media (hover: hover), (-moz-touch-enabled: 0), (-ms-high-contrast: none), (-ms-high-contrast: active) {.tag_b7aa:hover,\n.tagAngled_c869:hover::before {\n transition: none;\n\n background-color: var(--ring-tag-hover-background-color);\n}}\n\n.tag_b7aa {\n\n position: relative;\n z-index: 1;\n\n display: inline-flex;\n\n box-sizing: border-box;\n max-width: 100%;\n height: 20px;\n\n padding: 0 8px;\n\n cursor: pointer;\n\n vertical-align: top;\n\n color: var(--ring-text-color);\n\n border: none;\n border-radius: var(--ring-border-radius);\n\n font-size: 12px;\n line-height: var(--ring-line-height);\n}\n\n.tag_b7aa,\n.tagAngled_c869::before {\n transition: background-color var(--ring-ease);\n\n background-color: var(--ring-tag-background-color);\n}\n\n.withRemove_c0a5 {\n padding-right: 22px;\n}\n\n.container_cb34 {\n position: relative;\n\n display: inline-block;\n\n max-width: calc(100% - 4px);\n\n margin-right: 4px;\n\n white-space: nowrap;\n}\n\n.focused_fd92,\n.tag_b7aa:focus-visible {\n position: relative;\n\n outline: none;\n box-shadow: 0 0 0 2px var(--ring-border-hover-color);\n}\n\n.focused_fd92,\n.focused_fd92.tagAngled_c869::before,\n.tag_b7aa:focus-visible,\n.tagAngled_c869:focus-visible::before {\n transition: none;\n\n background-color: var(--ring-tag-hover-background-color);\n}\n\n.tagAngled_c869 {\n /* it needs to fix vertical alignment broken by "overflow: hidden". Remove this class, when IE11 will be deprecated */\n\n margin-bottom: -5px !important;\n\n margin-left: 8px;\n padding-left: 4px;\n\n border-top-left-radius: 0;\n border-bottom-left-radius: 0;\n}\n\n.tagAngled_c869::before {\n position: absolute;\n z-index: -1;\n top: 0;\n left: 0;\n\n box-sizing: border-box;\n width: 12px;\n height: 12px;\n\n content: "";\n transform: scaleY(1.177) rotate(45deg);\n transform-origin: 0 0;\n\n border: none;\n }\n\n.tagAngled_c869.focused_fd92,\n .tagAngled_c869:focus {\n box-shadow: 0 0 0 1px var(--ring-border-hover-color) inset, 0 0 0 1px var(--ring-border-hover-color);\n }\n\n.tagAngled_c869:focus::before {\n box-shadow:\n 1px -1px var(--ring-border-hover-color) inset,\n -0.8px 0.8px 0 0.5px var(--ring-border-hover-color);\n }\n\n.content_a838 {\n}\n\n.disabled_b740.tag_b7aa,\n.disabled_b740.tagAngled_c869::before {\n pointer-events: none;\n\n color: var(--ring-disabled-color);\n background-color: var(--ring-disabled-background-color);\n}\n\n.remove_eff8 {\n position: absolute;\n z-index: 1;\n top: 2px;\n right: 0;\n\n height: auto;\n padding: 0 4px;\n\n line-height: 16px;\n}\n\n.removeIcon_accf.removeIcon_accf {\n color: var(--ring-icon-secondary-color);\n}\n\n.icon_e877 {\n margin-right: 6px;\n\n color: var(--ring-icon-secondary-color);\n}\n\n.icon_e877 svg {\n vertical-align: -3px;\n }\n\n.avatarContainer_ee1b {\n display: inline-block;\n overflow: hidden;\n\n box-sizing: border-box;\n width: 20px;\n height: 20px;\n margin-right: 4px;\n margin-left: -8px;\n\n vertical-align: top;\n\n border-top-left-radius: var(--ring-border-radius);\n border-bottom-left-radius: var(--ring-border-radius);\n}\n\n.customIcon_ac93 {\n max-width: 16px;\n max-height: 16px;\n\n margin-right: 4px;\n\n vertical-align: bottom;\n}\n\n.avatarIcon_a8ff {\n width: 20px;\n\n margin-right: -4px;\n\n -o-object-fit: contain;\n\n object-fit: contain;\n -o-object-position: center;\n object-position: center;\n}\n',"",{version:3,sources:["","webpack://./node_modules/@jetbrains/ring-ui/components/tag/tag.css"],names:[],mappings:"AAAA,wGAAA;;EAAA,iBAAA;;EAAA,yDAAA;CAAA,CAAA;;ACKA;;EAGE,kBAAkB;EAClB,UAAU;;EAEV,oBAAoB;;EAEpB,sBAAsB;EACtB,eAAe;EACf,YAAkB;;EAElB,cAAe;;EAEf,eAAe;;EAEf,mBAAmB;;EAEnB,6BAA6B;;EAE7B,YAAY;EACZ,wCAAwC;;EAExC,eAAe;EACf,oCAAoC;AACtC;;AAEA;;EAEE,6CAA6C;;EAE7C,kDAAkD;AACpD;;AAEA;EACE,mBAAmB;AACrB;;AAEA;EACE,kBAAkB;;EAElB,qBAAqB;;EAErB,2BAAgC;;EAEhC,iBAA4B;;EAE5B,mBAAmB;AACrB;;AAEA;;EAEE,kBAAkB;;EAElB,aAAa;EACb,oDAAoD;AACtD;;AAEA;;;;EAME,gBAAgB;;EAEhB,wDAAwD;AAC1D;;AAEA;EACE,qHAAqH;;EAErH,8BAA8B;;EAE9B,gBAAiB;EACjB,iBAA4B;;EAE5B,yBAAyB;EACzB,4BAA4B;AA6B9B;;AA3BE;IACE,kBAAkB;IAClB,WAAW;IACX,MAAM;IACN,OAAO;;IAEP,sBAAsB;IACtB,WAAW;IACX,YAAY;;IAEZ,WAAW;IACX,sCAAsC;IACtC,qBAAqB;;IAErB,YAAY;EACd;;AAEA;;IAEE,oGAAoG;EACtG;;AAEA;IACE;;yDAEqD;EACvD;;AAGF;AAEA;;AAEA;;EAEE,oBAAoB;;EAEpB,iCAAiC;EACjC,uDAAuD;AACzD;;AAEA;EACE,kBAAkB;EAClB,UAAU;EACV,QAAQ;EACR,QAAQ;;EAER,YAAY;EACZ,cAAyB;;EAEzB,iBAA2B;AAC7B;;AAEA;EACE,uCAAuC;AACzC;;AAEA;EACE,iBAAiB;;EAEjB,uCAAuC;AAKzC;;AAHE;IACE,oBAAoB;EACtB;;AAGF;EACE,qBAAqB;EACrB,gBAAgB;;EAEhB,sBAAsB;EACtB,WAAiB;EACjB,YAAkB;EAClB,iBAA4B;EAC5B,iBAA2B;;EAE3B,mBAAmB;;EAEnB,iDAAiD;EACjD,oDAAoD;AACtD;;AAEA;EACE,eAAyB;EACzB,gBAA0B;;EAE1B,iBAA4B;;EAE5B,sBAAsB;AACxB;;AAEA;EACE,WAAiB;;EAEjB,kBAAkB;;EAElB,sBAAmB;;KAAnB,mBAAmB;EACnB,0BAAuB;KAAvB,uBAAuB;AACzB",sourcesContent:[null,'@import "../global/variables.css";\n\n@value unit from "../global/global.css";\n@value max-height: 20px;\n\n.tag {\n composes: resetButton from "../global/global.css";\n\n position: relative;\n z-index: 1;\n\n display: inline-flex;\n\n box-sizing: border-box;\n max-width: 100%;\n height: max-height;\n\n padding: 0 unit;\n\n cursor: pointer;\n\n vertical-align: top;\n\n color: var(--ring-text-color);\n\n border: none;\n border-radius: var(--ring-border-radius);\n\n font-size: 12px;\n line-height: var(--ring-line-height);\n}\n\n.tag,\n.tagAngled::before {\n transition: background-color var(--ring-ease);\n\n background-color: var(--ring-tag-background-color);\n}\n\n.withRemove {\n padding-right: 22px;\n}\n\n.container {\n position: relative;\n\n display: inline-block;\n\n max-width: calc(100% - unit / 2);\n\n margin-right: calc(unit / 2);\n\n white-space: nowrap;\n}\n\n.focused,\n.tag:focus-visible {\n position: relative;\n\n outline: none;\n box-shadow: 0 0 0 2px var(--ring-border-hover-color);\n}\n\n.focused,\n.focused.tagAngled::before,\n.tag:focus-visible,\n.tagAngled:focus-visible::before,\n.tag:hover,\n.tagAngled:hover::before {\n transition: none;\n\n background-color: var(--ring-tag-hover-background-color);\n}\n\n.tagAngled {\n /* it needs to fix vertical alignment broken by "overflow: hidden". Remove this class, when IE11 will be deprecated */\n\n margin-bottom: -5px !important;\n\n margin-left: unit;\n padding-left: calc(unit / 2);\n\n border-top-left-radius: 0;\n border-bottom-left-radius: 0;\n\n &::before {\n position: absolute;\n z-index: -1;\n top: 0;\n left: 0;\n\n box-sizing: border-box;\n width: 12px;\n height: 12px;\n\n content: "";\n transform: scaleY(1.177) rotate(45deg);\n transform-origin: 0 0;\n\n border: none;\n }\n\n &.focused,\n &:focus {\n box-shadow: 0 0 0 1px var(--ring-border-hover-color) inset, 0 0 0 1px var(--ring-border-hover-color);\n }\n\n &:focus::before {\n box-shadow:\n 1px -1px var(--ring-border-hover-color) inset,\n -0.8px 0.8px 0 0.5px var(--ring-border-hover-color);\n }\n}\n\n.content {\n composes: ellipsis from "../global/global.css";\n}\n\n.disabled.tag,\n.disabled.tagAngled::before {\n pointer-events: none;\n\n color: var(--ring-disabled-color);\n background-color: var(--ring-disabled-background-color);\n}\n\n.remove {\n position: absolute;\n z-index: 1;\n top: 2px;\n right: 0;\n\n height: auto;\n padding: 0 calc(unit / 2);\n\n line-height: calc(unit * 2);\n}\n\n.removeIcon.removeIcon {\n color: var(--ring-icon-secondary-color);\n}\n\n.icon {\n margin-right: 6px;\n\n color: var(--ring-icon-secondary-color);\n\n & svg {\n vertical-align: -3px;\n }\n}\n\n.avatarContainer {\n display: inline-block;\n overflow: hidden;\n\n box-sizing: border-box;\n width: max-height;\n height: max-height;\n margin-right: calc(unit / 2);\n margin-left: calc(0 - unit);\n\n vertical-align: top;\n\n border-top-left-radius: var(--ring-border-radius);\n border-bottom-left-radius: var(--ring-border-radius);\n}\n\n.customIcon {\n max-width: calc(unit * 2);\n max-height: calc(unit * 2);\n\n margin-right: calc(unit / 2);\n\n vertical-align: bottom;\n}\n\n.avatarIcon {\n width: max-height;\n\n margin-right: -4px;\n\n object-fit: contain;\n object-position: center;\n}\n'],sourceRoot:""}]),s.locals={unit:`${l.default.locals.unit}`,"max-height":"20px",tag:`tag_b7aa ${l.default.locals.resetButton}`,tagAngled:"tagAngled_c869",withRemove:"withRemove_c0a5",container:"container_cb34",focused:"focused_fd92",content:`content_a838 ${l.default.locals.ellipsis}`,disabled:"disabled_b740",remove:"remove_eff8",removeIcon:"removeIcon_accf",icon:"icon_e877",avatarContainer:"avatarContainer_ee1b",customIcon:"customIcon_ac93",avatarIcon:"avatarIcon_a8ff"};const u=s},6162:(e,n,t)=>{"use strict";t.r(n),t.d(n,{default:()=>s});var r=t(1404),o=t.n(r),i=t(7156),a=t.n(i),l=t(5280),c=a()(o());c.i(l.A),c.push([e.id,".text_f1dc {\n color: var(--ring-text-color);\n}\n\n.sizeS_b3aa {\n font-size: var(--ring-font-size-smaller);\n}\n\n.sizeM_ae72 {\n font-size: var(--ring-font-size);\n}\n\n.sizeL_f259 {\n font-size: var(--ring-font-size-larger);\n}\n\n.info_c0a4 {\n color: var(--ring-secondary-color);\n}\n","",{version:3,sources:["webpack://./node_modules/@jetbrains/ring-ui/components/text/text.css"],names:[],mappings:"AAEA;EACE,6BAA6B;AAC/B;;AAEA;EACE,wCAAwC;AAC1C;;AAEA;EACE,gCAAgC;AAClC;;AAEA;EACE,uCAAuC;AACzC;;AAEA;EACE,kCAAkC;AACpC",sourcesContent:['@import "../global/variables.css";\n\n.text {\n color: var(--ring-text-color);\n}\n\n.sizeS {\n font-size: var(--ring-font-size-smaller);\n}\n\n.sizeM {\n font-size: var(--ring-font-size);\n}\n\n.sizeL {\n font-size: var(--ring-font-size-larger);\n}\n\n.info {\n color: var(--ring-secondary-color);\n}\n'],sourceRoot:""}]),c.locals={text:"text_f1dc",sizeS:"sizeS_b3aa",sizeM:"sizeM_ae72",sizeL:"sizeL_f259",info:"info_c0a4"};const s=c},938:(e,n,t)=>{"use strict";t.r(n),t.d(n,{default:()=>u});var r=t(1404),o=t.n(r),i=t(7156),a=t.n(i),l=t(9106),c=t(5280),s=a()(o());s.i(c.A),s.i(l.default,"",!0),s.push([e.id,".tooltip_fbfb {\n max-width: 400px;\n padding: 8px;\n\n text-align: left;\n\n color: var(--ring-text-color);\n}\n\n.long_b7a5 {\n padding: 8px 12px;\n\n font-size: var(--ring-font-size-smaller);\n line-height: var(--ring-line-height-lowest);\n}\n","",{version:3,sources:["webpack://./node_modules/@jetbrains/ring-ui/components/tooltip/tooltip.css"],names:[],mappings:"AAIA;EACE,gBAA0B;EAC1B,YAAa;;EAEb,gBAAgB;;EAEhB,6BAA6B;AAC/B;;AAEA;EACE,iBAA8B;;EAE9B,wCAAwC;EACxC,2CAA2C;AAC7C",sourcesContent:['@import "../global/variables.css";\n\n@value unit from "../global/global.css";\n\n.tooltip {\n max-width: calc(unit * 50);\n padding: unit;\n\n text-align: left;\n\n color: var(--ring-text-color);\n}\n\n.long {\n padding: unit calc(unit * 1.5);\n\n font-size: var(--ring-font-size-smaller);\n line-height: var(--ring-line-height-lowest);\n}\n'],sourceRoot:""}]),s.locals={unit:`${l.default.locals.unit}`,tooltip:"tooltip_fbfb",long:"long_b7a5"};const u=s},7156:e=>{"use strict";e.exports=function(e){var n=[];return n.toString=function(){return this.map((function(n){var t="",r=void 0!==n[5];return n[4]&&(t+="@supports (".concat(n[4],") {")),n[2]&&(t+="@media ".concat(n[2]," {")),r&&(t+="@layer".concat(n[5].length>0?" ".concat(n[5]):""," {")),t+=e(n),r&&(t+="}"),n[2]&&(t+="}"),n[4]&&(t+="}"),t})).join("")},n.i=function(e,t,r,o,i){"string"==typeof e&&(e=[[null,e,void 0]]);var a={};if(r)for(var l=0;l0?" ".concat(u[5]):""," {").concat(u[1],"}")),u[5]=i),t&&(u[2]?(u[1]="@media ".concat(u[2]," {").concat(u[1],"}"),u[2]=t):u[2]=t),o&&(u[4]?(u[1]="@supports (".concat(u[4],") {").concat(u[1],"}"),u[4]=o):u[4]="".concat(o)),n.push(u))}},n}},1404:e=>{"use strict";e.exports=function(e){var n=e[1],t=e[3];if(!t)return n;if("function"==typeof btoa){var r=btoa(unescape(encodeURIComponent(JSON.stringify(t)))),o="sourceMappingURL=data:application/json;charset=utf-8;base64,".concat(r),i="/*# ".concat(o," */");return[n].concat([i]).join("\n")}return[n].join("\n")}},4504:(e,n,t)=>{var r=t(8298),o=t(5163),i=t(2729),a=t(9986),l=t(9742),c=t(6291),s=t(7222);s=s.__esModule?s.default:s;var u={};u.styleTagTransform=c,u.setAttributes=a,u.insert=i.bind(null,"head"),u.domAPI=o,u.insertStyleElement=l;r(s,u);e.exports=s&&s.locals||{}},9102:(e,n,t)=>{var r=t(8298),o=t(5163),i=t(2729),a=t(9986),l=t(9742),c=t(6291),s=t(9892);s=s.__esModule?s.default:s;var u={};u.styleTagTransform=c,u.setAttributes=a,u.insert=i.bind(null,"head"),u.domAPI=o,u.insertStyleElement=l;r(s,u);e.exports=s&&s.locals||{}},6860:(e,n,t)=>{var r=t(8298),o=t(5163),i=t(2729),a=t(9986),l=t(9742),c=t(6291),s=t(1866);s=s.__esModule?s.default:s;var u={};u.styleTagTransform=c,u.setAttributes=a,u.insert=i.bind(null,"head"),u.domAPI=o,u.insertStyleElement=l;r(s,u);e.exports=s&&s.locals||{}},3912:(e,n,t)=>{var r=t(8298),o=t(5163),i=t(2729),a=t(9986),l=t(9742),c=t(6291),s=t(5486);s=s.__esModule?s.default:s;var u={};u.styleTagTransform=c,u.setAttributes=a,u.insert=i.bind(null,"head"),u.domAPI=o,u.insertStyleElement=l;r(s,u);e.exports=s&&s.locals||{}},8764:(e,n,t)=>{var r=t(8298),o=t(5163),i=t(2729),a=t(9986),l=t(9742),c=t(6291),s=t(6506);s=s.__esModule?s.default:s;var u={};u.styleTagTransform=c,u.setAttributes=a,u.insert=i.bind(null,"head"),u.domAPI=o,u.insertStyleElement=l;r(s,u);e.exports=s&&s.locals||{}},6620:(e,n,t)=>{var r=t(8298),o=t(5163),i=t(2729),a=t(9986),l=t(9742),c=t(6291),s=t(9106);s=s.__esModule?s.default:s;var u={};u.styleTagTransform=c,u.setAttributes=a,u.insert=i.bind(null,"head"),u.domAPI=o,u.insertStyleElement=l;r(s,u);e.exports=s&&s.locals||{}},9468:(e,n,t)=>{var r=t(8298),o=t(5163),i=t(2729),a=t(9986),l=t(9742),c=t(6291),s=t(5066);s=s.__esModule?s.default:s;var u={};u.styleTagTransform=c,u.setAttributes=a,u.insert=i.bind(null,"head"),u.domAPI=o,u.insertStyleElement=l;r(s,u);e.exports=s&&s.locals||{}},274:(e,n,t)=>{var r=t(8298),o=t(5163),i=t(2729),a=t(9986),l=t(9742),c=t(6291),s=t(8976);s=s.__esModule?s.default:s;var u={};u.styleTagTransform=c,u.setAttributes=a,u.insert=i.bind(null,"head"),u.domAPI=o,u.insertStyleElement=l;r(s,u);e.exports=s&&s.locals||{}},5924:(e,n,t)=>{var r=t(8298),o=t(5163),i=t(2729),a=t(9986),l=t(9742),c=t(6291),s=t(8266);s=s.__esModule?s.default:s;var u={};u.styleTagTransform=c,u.setAttributes=a,u.insert=i.bind(null,"head"),u.domAPI=o,u.insertStyleElement=l;r(s,u);e.exports=s&&s.locals||{}},7826:(e,n,t)=>{var r=t(8298),o=t(5163),i=t(2729),a=t(9986),l=t(9742),c=t(6291),s=t(6960);s=s.__esModule?s.default:s;var u={};u.styleTagTransform=c,u.setAttributes=a,u.insert=i.bind(null,"head"),u.domAPI=o,u.insertStyleElement=l;r(s,u);e.exports=s&&s.locals||{}},1914:(e,n,t)=>{var r=t(8298),o=t(5163),i=t(2729),a=t(9986),l=t(9742),c=t(6291),s=t(480);s=s.__esModule?s.default:s;var u={};u.styleTagTransform=c,u.setAttributes=a,u.insert=i.bind(null,"head"),u.domAPI=o,u.insertStyleElement=l;r(s,u);e.exports=s&&s.locals||{}},8130:(e,n,t)=>{var r=t(8298),o=t(5163),i=t(2729),a=t(9986),l=t(9742),c=t(6291),s=t(1586);s=s.__esModule?s.default:s;var u={};u.styleTagTransform=c,u.setAttributes=a,u.insert=i.bind(null,"head"),u.domAPI=o,u.insertStyleElement=l;r(s,u);e.exports=s&&s.locals||{}},1564:(e,n,t)=>{var r=t(8298),o=t(5163),i=t(2729),a=t(9986),l=t(9742),c=t(6291),s=t(8890);s=s.__esModule?s.default:s;var u={};u.styleTagTransform=c,u.setAttributes=a,u.insert=i.bind(null,"head"),u.domAPI=o,u.insertStyleElement=l;r(s,u);e.exports=s&&s.locals||{}},5103:(e,n,t)=>{var r=t(8298),o=t(5163),i=t(2729),a=t(9986),l=t(9742),c=t(6291),s=t(4481);s=s.__esModule?s.default:s;var u={};u.styleTagTransform=c,u.setAttributes=a,u.insert=i.bind(null,"head"),u.domAPI=o,u.insertStyleElement=l;r(s,u);e.exports=s&&s.locals||{}},3006:(e,n,t)=>{var r=t(8298),o=t(5163),i=t(2729),a=t(9986),l=t(9742),c=t(6291),s=t(2636);s=s.__esModule?s.default:s;var u={};u.styleTagTransform=c,u.setAttributes=a,u.insert=i.bind(null,"head"),u.domAPI=o,u.insertStyleElement=l;r(s,u);e.exports=s&&s.locals||{}},9344:(e,n,t)=>{var r=t(8298),o=t(5163),i=t(2729),a=t(9986),l=t(9742),c=t(6291),s=t(8102);s=s.__esModule?s.default:s;var u={};u.styleTagTransform=c,u.setAttributes=a,u.insert=i.bind(null,"head"),u.domAPI=o,u.insertStyleElement=l;r(s,u);e.exports=s&&s.locals||{}},4512:(e,n,t)=>{var r=t(8298),o=t(5163),i=t(2729),a=t(9986),l=t(9742),c=t(6291),s=t(4561);s=s.__esModule?s.default:s;var u={};u.styleTagTransform=c,u.setAttributes=a,u.insert=i.bind(null,"head"),u.domAPI=o,u.insertStyleElement=l;r(s,u);e.exports=s&&s.locals||{}},6932:(e,n,t)=>{var r=t(8298),o=t(5163),i=t(2729),a=t(9986),l=t(9742),c=t(6291),s=t(6162);s=s.__esModule?s.default:s;var u={};u.styleTagTransform=c,u.setAttributes=a,u.insert=i.bind(null,"head"),u.domAPI=o,u.insertStyleElement=l;r(s,u);e.exports=s&&s.locals||{}},8132:(e,n,t)=>{var r=t(8298),o=t(5163),i=t(2729),a=t(9986),l=t(9742),c=t(6291),s=t(938);s=s.__esModule?s.default:s;var u={};u.styleTagTransform=c,u.setAttributes=a,u.insert=i.bind(null,"head"),u.domAPI=o,u.insertStyleElement=l;r(s,u);e.exports=s&&s.locals||{}},8298:e=>{"use strict";var n=[];function t(e){for(var t=-1,r=0;r{"use strict";var n={};e.exports=function(e,t){var r=function(e){if(void 0===n[e]){var t=document.querySelector(e);if(window.HTMLIFrameElement&&t instanceof window.HTMLIFrameElement)try{t=t.contentDocument.head}catch(e){t=null}n[e]=t}return n[e]}(e);if(!r)throw new Error("Couldn't find a style target. This probably means that the value for the 'insert' parameter is invalid.");r.appendChild(t)}},9742:e=>{"use strict";e.exports=function(e){var n=document.createElement("style");return e.setAttributes(n,e.attributes),e.insert(n,e.options),n}},9986:(e,n,t)=>{"use strict";e.exports=function(e){var n=t.nc;n&&e.setAttribute("nonce",n)}},5163:e=>{"use strict";e.exports=function(e){if("undefined"==typeof document)return{update:function(){},remove:function(){}};var n=e.insertStyleElement(e);return{update:function(t){!function(e,n,t){var r="";t.supports&&(r+="@supports (".concat(t.supports,") {")),t.media&&(r+="@media ".concat(t.media," {"));var o=void 0!==t.layer;o&&(r+="@layer".concat(t.layer.length>0?" ".concat(t.layer):""," {")),r+=t.css,o&&(r+="}"),t.media&&(r+="}"),t.supports&&(r+="}");var i=t.sourceMap;i&&"undefined"!=typeof btoa&&(r+="\n/*# sourceMappingURL=data:application/json;base64,".concat(btoa(unescape(encodeURIComponent(JSON.stringify(i))))," */")),n.styleTagTransform(r,e,n.options)}(n,e,t)},remove:function(){!function(e){if(null===e.parentNode)return!1;e.parentNode.removeChild(e)}(n)}}}},6291:e=>{"use strict";e.exports=function(e,n){if(n.styleSheet)n.styleSheet.cssText=e;else{for(;n.firstChild;)n.removeChild(n.firstChild);n.appendChild(document.createTextNode(e))}}},9511:(e,n,t)=>{"use strict";var r=t(6556)("ArrayBuffer.prototype.byteLength",!0),o=t(4670);e.exports=function(e){return o(e)?r?r(e):e.byteLength:NaN}},3144:(e,n,t)=>{"use strict";var r=t(6743),o=t(1002),i=t(76),a=t(7119);e.exports=a||r.call(i,o)},2205:(e,n,t)=>{"use strict";var r=t(6743),o=t(1002),i=t(3144);e.exports=function(){return i(r,o,arguments)}},1002:e=>{"use strict";e.exports=Function.prototype.apply},76:e=>{"use strict";e.exports=Function.prototype.call},3126:(e,n,t)=>{"use strict";var r=t(6743),o=t(9675),i=t(76),a=t(3144);e.exports=function(e){if(e.length<1||"function"!=typeof e[0])throw new o("a function is required");return a(r,i,e)}},7119:e=>{"use strict";e.exports="undefined"!=typeof Reflect&&Reflect&&Reflect.apply},8075:(e,n,t)=>{"use strict";var r=t(453),o=t(487),i=o(r("String.prototype.indexOf"));e.exports=function(e,n){var t=r(e,!!n);return"function"==typeof t&&i(e,".prototype.")>-1?o(t):t}},487:(e,n,t)=>{"use strict";var r=t(6897),o=t(3036),i=t(3126),a=t(2205);e.exports=function(e){var n=i(arguments),t=e.length-(arguments.length-1);return r(n,1+(t>0?t:0),!0)},o?o(e.exports,"apply",{value:a}):e.exports.apply=a},6556:(e,n,t)=>{"use strict";var r=t(453),o=t(3126),i=o([r("%String.prototype.indexOf%")]);e.exports=function(e,n){var t=r(e,!!n);return"function"==typeof t&&i(e,".prototype.")>-1?o([t]):t}},5888:(e,n,t)=>{"use strict";e.exports=function(e,n){var t=this,r=t.constructor;return t.options=Object.assign({storeInstancesGlobally:!0},n||{}),t.callbacks={},t.directMap={},t.sequenceLevels={},t.resetTimer=null,t.ignoreNextKeyup=!1,t.ignoreNextKeypress=!1,t.nextExpectedAction=!1,t.element=e,t.addEvents(),t.options.storeInstancesGlobally&&r.instances.push(t),t},e.exports.prototype.bind=t(1210),e.exports.prototype.bindMultiple=t(4382),e.exports.prototype.unbind=t(3709),e.exports.prototype.trigger=t(3149),e.exports.prototype.reset=t(6726),e.exports.prototype.stopCallback=t(4446),e.exports.prototype.handleKey=t(4320),e.exports.prototype.addEvents=t(6687),e.exports.prototype.bindSingle=t(2214),e.exports.prototype.getKeyInfo=t(4174),e.exports.prototype.pickBestAction=t(6004),e.exports.prototype.getReverseMap=t(5193),e.exports.prototype.getMatches=t(9132),e.exports.prototype.resetSequences=t(3229),e.exports.prototype.fireCallback=t(7922),e.exports.prototype.bindSequence=t(3256),e.exports.prototype.resetSequenceTimer=t(602),e.exports.prototype.detach=t(3502),e.exports.instances=[],e.exports.reset=t(6255),e.exports.REVERSE_MAP=null},6687:(e,n,t)=>{"use strict";e.exports=function(){var e=this,n=t(2904),r=e.element;e.eventHandler=t(8178).bind(e),n(r,"keypress",e.eventHandler),n(r,"keydown",e.eventHandler),n(r,"keyup",e.eventHandler)}},1210:e=>{"use strict";e.exports=function(e,n,t){return e=e instanceof Array?e:[e],this.bindMultiple(e,n,t),this}},4382:e=>{"use strict";e.exports=function(e,n,t){for(var r=0;r{"use strict";e.exports=function(e,n,r,o){var i=this;function a(n){return function(){i.nextExpectedAction=n,++i.sequenceLevels[e],i.resetSequenceTimer()}}function l(n){var a;i.fireCallback(r,n,e),"keyup"!==o&&(a=t(3970),i.ignoreNextKeyup=a(n)),setTimeout((function(){i.resetSequences()}),10)}i.sequenceLevels[e]=0;for(var c=0;c{"use strict";e.exports=function(e,n,t,r,o){var i=this;i.directMap[e+":"+t]=n;var a,l=(e=e.replace(/\s+/g," ")).split(" ");l.length>1?i.bindSequence(e,l,n,t):(a=i.getKeyInfo(e,t),i.callbacks[a.key]=i.callbacks[a.key]||[],i.getMatches(a.key,a.modifiers,{type:a.action},r,e,o),i.callbacks[a.key][r?"unshift":"push"]({callback:n,modifiers:a.modifiers,action:a.action,seq:r,level:o,combo:e}))}},3502:(e,n,t)=>{var r=t(2904).off;e.exports=function(){var e=this,n=e.element;r(n,"keypress",e.eventHandler),r(n,"keydown",e.eventHandler),r(n,"keyup",e.eventHandler)}},2904:e=>{function n(e,n,t,r){return!e.addEventListener&&(n="on"+n),(e.addEventListener||e.attachEvent).call(e,n,t,r),t}e.exports=n,e.exports.on=n,e.exports.off=function(e,n,t,r){return!e.removeEventListener&&(n="on"+n),(e.removeEventListener||e.detachEvent).call(e,n,t,r),t}},7922:(e,n,t)=>{"use strict";e.exports=function(e,n,r,o){this.stopCallback(n,n.target||n.srcElement,r,o)||!1===e(n,r)&&(t(2156)(n),t(1849)(n))}},4174:(e,n,t)=>{"use strict";e.exports=function(e,n){var r,o,i,a,l,c,s=[];for(r=t(7486)(e),a=t(7641),l=t(7984),c=t(5962),i=0;i{"use strict";e.exports=function(e,n,r,o,i,a){var l,c,s,u,p=this,f=[],d=r.type;"keypress"!==d||r.code&&"Arrow"===r.code.slice(0,5)||(p.callbacks["any-character"]||[]).forEach((function(e){f.push(e)}));if(!p.callbacks[e])return f;for(s=t(5962),"keyup"===d&&s(e)&&(n=[e]),l=0;l{"use strict";e.exports=function(){var e,n=this.constructor;if(!n.REVERSE_MAP)for(var r in n.REVERSE_MAP={},e=t(6814))r>95&&r<112||e.hasOwnProperty(r)&&(n.REVERSE_MAP[e[r]]=r);return n.REVERSE_MAP}},4320:(e,n,t)=>{"use strict";e.exports=function(e,n,r){var o,i,a,l,c=this,s={},u=0,p=!1;for(o=c.getMatches(e,n,r),i=0;i{"use strict";e.exports=function(e){var n,r=this;"number"!=typeof e.which&&(e.which=e.keyCode);var o=t(3970)(e);void 0!==o&&("keyup"!==e.type||r.ignoreNextKeyup!==o?(n=t(5273),r.handleKey(o,n(e),e)):r.ignoreNextKeyup=!1)}},7238:e=>{"use strict";e.exports=function(e,n){return e.sort().join(",")===n.sort().join(",")}},6004:e=>{"use strict";e.exports=function(e,n,t){return t||(t=this.getReverseMap()[e]?"keydown":"keypress"),"keypress"===t&&n.length&&(t="keydown"),t}},6726:e=>{"use strict";e.exports=function(){return this.callbacks={},this.directMap={},this}},602:e=>{"use strict";e.exports=function(){var e=this;clearTimeout(e.resetTimer),e.resetTimer=setTimeout((function(){e.resetSequences()}),1e3)}},3229:e=>{"use strict";e.exports=function(e){var n=this;e=e||{};var t,r=!1;for(t in n.sequenceLevels)e[t]?r=!0:n.sequenceLevels[t]=0;r||(n.nextExpectedAction=!1)}},4446:e=>{"use strict";e.exports=function(e,n){if((" "+n.className+" ").indexOf(" combokeys ")>-1)return!1;var t=n.tagName.toLowerCase();return"input"===t||"select"===t||"textarea"===t||n.isContentEditable}},3149:e=>{"use strict";e.exports=function(e,n){return this.directMap[e+":"+n]&&this.directMap[e+":"+n]({},e),this}},3709:e=>{"use strict";e.exports=function(e,n){return this.bind(e,(function(){}),n)}},6255:e=>{"use strict";e.exports=function(){this.instances.forEach((function(e){e.reset()}))}},3970:(e,n,t)=>{"use strict";e.exports=function(e){var n,r;if(n=t(6814),r=t(4082),"keypress"===e.type){var o=String.fromCharCode(e.which);return e.shiftKey||(o=o.toLowerCase()),o}return void 0!==n[e.which]?n[e.which]:void 0!==r[e.which]?r[e.which]:String.fromCharCode(e.which).toLowerCase()}},5273:e=>{"use strict";e.exports=function(e){var n=[];return e.shiftKey&&n.push("shift"),e.altKey&&n.push("alt"),e.ctrlKey&&n.push("ctrl"),e.metaKey&&n.push("meta"),n}},5962:e=>{"use strict";e.exports=function(e){return"shift"===e||"ctrl"===e||"alt"===e||"meta"===e}},7486:e=>{"use strict";e.exports=function(e){return"+"===e?["+"]:e.split("+")}},2156:e=>{"use strict";e.exports=function(e){e.preventDefault?e.preventDefault():e.returnValue=!1}},7984:e=>{"use strict";e.exports={"~":"`","!":"1","@":"2","#":"3",$:"4","%":"5","^":"6","&":"7","*":"8","(":"9",")":"0",_:"-","+":"=",":":";",'"':"'","<":",",">":".","?":"/","|":"\\"}},7641:e=>{"use strict";e.exports={option:"alt",command:"meta",return:"enter",escape:"esc",mod:/Mac|iPod|iPhone|iPad/.test(navigator.platform)?"meta":"ctrl"}},4082:e=>{"use strict";e.exports={106:"*",107:"plus",109:"minus",110:".",111:"/",186:";",187:"=",188:",",189:"-",190:".",191:"/",192:"`",219:"[",220:"\\",221:"]",222:"'"}},6814:e=>{"use strict";e.exports={8:"backspace",9:"tab",13:"enter",16:"shift",17:"ctrl",18:"alt",20:"capslock",27:"esc",32:"space",33:"pageup",34:"pagedown",35:"end",36:"home",37:"left",38:"up",39:"right",40:"down",45:"ins",46:"del",91:"meta",93:"meta",173:"minus",187:"plus",189:"minus",224:"meta"};for(var n=1;n<20;++n)e.exports[111+n]="f"+n;for(n=0;n<=9;++n)e.exports[n+96]=n},1849:e=>{"use strict";e.exports=function(e){e.stopPropagation?e.stopPropagation():e.cancelBubble=!0}},4982:(e,n,t)=>{"use strict";var r=t(6525),o=t(8075),i=t(1589),a=t(453),l=t(4552),c=t(920),s=t(7653),u=t(7244),p=t(4634),f=t(4670),d=t(2120),g=t(4035),h=t(7070),A=t(1189),b=t(1539),v=t(593),m=t(5767),y=t(9511),E=o("SharedArrayBuffer.prototype.byteLength",!0),C=o("Date.prototype.getTime"),w=Object.getPrototypeOf,x=o("Object.prototype.toString"),S=a("%Set%",!0),_=o("Map.prototype.has",!0),k=o("Map.prototype.get",!0),O=o("Map.prototype.size",!0),B=o("Set.prototype.add",!0),P=o("Set.prototype.delete",!0),T=o("Set.prototype.has",!0),I=o("Set.prototype.size",!0);function j(e,n,t,r){for(var o,i=l(e);(o=i.next())&&!o.done;)if(N(n,o.value,t,r))return P(e,o.value),!0;return!1}function z(e){return void 0===e?null:"object"!=typeof e?"symbol"!=typeof e&&("string"!=typeof e&&"number"!=typeof e||+e==+e):void 0}function D(e,n,t,o,i,a){var l=z(t);if(null!=l)return l;var c=k(n,l),s=r({},i,{strict:!1});return!(void 0===c&&!_(n,l)||!N(o,c,s,a))&&(!_(e,l)&&N(o,c,s,a))}function R(e,n,t){var r=z(t);return null!=r?r:T(n,r)&&!T(e,r)}function M(e,n,t,r,o,i){for(var a,c,s=l(e);(a=s.next())&&!a.done;)if(N(t,c=a.value,o,i)&&N(r,k(n,c),o,i))return P(e,c),!0;return!1}function N(e,n,t,o){var a=t||{};if(a.strict?s(e,n):e===n)return!0;if(b(e)!==b(n))return!1;if(!e||!n||"object"!=typeof e&&"object"!=typeof n)return a.strict?s(e,n):e==n;var c,P=o.has(e),z=o.has(n);if(P&&z){if(o.get(e)===o.get(n))return!0}else c={};return P||o.set(e,c),z||o.set(n,c),function(e,n,t,o){var a,c;if(typeof e!=typeof n)return!1;if(null==e||null==n)return!1;if(x(e)!==x(n))return!1;if(u(e)!==u(n))return!1;var s=p(e),b=p(n);if(s!==b)return!1;var P=e instanceof Error,z=n instanceof Error;if(P!==z)return!1;if((P||z)&&(e.name!==n.name||e.message!==n.message))return!1;var L=g(e),H=g(n);if(L!==H)return!1;if((L||H)&&(e.source!==n.source||i(e)!==i(n)))return!1;var U=d(e),W=d(n);if(U!==W)return!1;if((U||W)&&C(e)!==C(n))return!1;if(t.strict&&w&&w(e)!==w(n))return!1;var G=m(e),Y=m(n);if(G!==Y)return!1;if(G||Y){if(e.length!==n.length)return!1;for(a=0;a=0;a--)if(Z[a]!=J[a])return!1;for(a=Z.length-1;a>=0;a--)if(!N(e[c=Z[a]],n[c],t,o))return!1;var ee=v(e),ne=v(n);if(ee!==ne)return!1;if("Set"===ee||"Set"===ne)return function(e,n,t,r){if(I(e)!==I(n))return!1;var o,i,a,c=l(e),s=l(n);for(;(o=c.next())&&!o.done;)if(o.value&&"object"==typeof o.value)a||(a=new S),B(a,o.value);else if(!T(n,o.value)){if(t.strict)return!1;if(!R(e,n,o.value))return!1;a||(a=new S),B(a,o.value)}if(a){for(;(i=s.next())&&!i.done;)if(i.value&&"object"==typeof i.value){if(!j(a,i.value,t.strict,r))return!1}else if(!t.strict&&!T(e,i.value)&&!j(a,i.value,t.strict,r))return!1;return 0===I(a)}return!0}(e,n,t,o);if("Map"===ee)return function(e,n,t,o){if(O(e)!==O(n))return!1;var i,a,c,s,u,p,f=l(e),d=l(n);for(;(i=f.next())&&!i.done;)if(s=i.value[0],u=i.value[1],s&&"object"==typeof s)c||(c=new S),B(c,s);else if(void 0===(p=k(n,s))&&!_(n,s)||!N(u,p,t,o)){if(t.strict)return!1;if(!D(e,n,s,u,t,o))return!1;c||(c=new S),B(c,s)}if(c){for(;(a=d.next())&&!a.done;)if(s=a.value[0],p=a.value[1],s&&"object"==typeof s){if(!M(c,e,s,p,t,o))return!1}else if(!(t.strict||e.has(s)&&N(k(e,s),p,t,o)||M(c,e,s,p,r({},t,{strict:!1}),o)))return!1;return 0===I(c)}return!0}(e,n,t,o);return!0}(e,n,a,o)}function F(e){return!(!e||"object"!=typeof e||"number"!=typeof e.length)&&("function"==typeof e.copy&&"function"==typeof e.slice&&(!(e.length>0&&"number"!=typeof e[0])&&!!(e.constructor&&e.constructor.isBuffer&&e.constructor.isBuffer(e))))}e.exports=function(e,n,t){return N(e,n,t,c())}},41:(e,n,t)=>{"use strict";var r=t(3036),o=t(8068),i=t(9675),a=t(5795);e.exports=function(e,n,t){if(!e||"object"!=typeof e&&"function"!=typeof e)throw new i("`obj` must be an object or a function`");if("string"!=typeof n&&"symbol"!=typeof n)throw new i("`property` must be a string or a symbol`");if(arguments.length>3&&"boolean"!=typeof arguments[3]&&null!==arguments[3])throw new i("`nonEnumerable`, if provided, must be a boolean or null");if(arguments.length>4&&"boolean"!=typeof arguments[4]&&null!==arguments[4])throw new i("`nonWritable`, if provided, must be a boolean or null");if(arguments.length>5&&"boolean"!=typeof arguments[5]&&null!==arguments[5])throw new i("`nonConfigurable`, if provided, must be a boolean or null");if(arguments.length>6&&"boolean"!=typeof arguments[6])throw new i("`loose`, if provided, must be a boolean");var l=arguments.length>3?arguments[3]:null,c=arguments.length>4?arguments[4]:null,s=arguments.length>5?arguments[5]:null,u=arguments.length>6&&arguments[6],p=!!a&&a(e,n);if(r)r(e,n,{configurable:null===s&&p?p.configurable:!s,enumerable:null===l&&p?p.enumerable:!l,value:t,writable:null===c&&p?p.writable:!c});else{if(!u&&(l||c||s))throw new o("This environment does not support defining a property as non-configurable, non-writable, or non-enumerable.");e[n]=t}}},8452:(e,n,t)=>{"use strict";var r=t(1189),o="function"==typeof Symbol&&"symbol"==typeof Symbol("foo"),i=Object.prototype.toString,a=Array.prototype.concat,l=t(41),c=t(592)(),s=function(e,n,t,r){if(n in e)if(!0===r){if(e[n]===t)return}else if("function"!=typeof(o=r)||"[object Function]"!==i.call(o)||!r())return;var o;c?l(e,n,t,!0):l(e,n,t)},u=function(e,n){var t=arguments.length>2?arguments[2]:{},i=r(n);o&&(i=a.call(i,Object.getOwnPropertySymbols(n)));for(var l=0;l{"use strict";var r,o=t(3126),i=t(5795);try{r=[].__proto__===Array.prototype}catch(e){if(!e||"object"!=typeof e||!("code"in e)||"ERR_PROTO_ACCESS"!==e.code)throw e}var a=!!r&&i&&i(Object.prototype,"__proto__"),l=Object,c=l.getPrototypeOf;e.exports=a&&"function"==typeof a.get?o([a.get]):"function"==typeof c&&function(e){return c(null==e?e:l(e))}},3036:e=>{"use strict";var n=Object.defineProperty||!1;if(n)try{n({},"a",{value:1})}catch(e){n=!1}e.exports=n},1237:e=>{"use strict";e.exports=EvalError},9383:e=>{"use strict";e.exports=Error},9290:e=>{"use strict";e.exports=RangeError},9538:e=>{"use strict";e.exports=ReferenceError},8068:e=>{"use strict";e.exports=SyntaxError},9675:e=>{"use strict";e.exports=TypeError},5345:e=>{"use strict";e.exports=URIError},9612:e=>{"use strict";e.exports=Object},2682:(e,n,t)=>{"use strict";var r=t(9600),o=Object.prototype.toString,i=Object.prototype.hasOwnProperty;e.exports=function(e,n,t){if(!r(n))throw new TypeError("iterator must be a function");var a,l;arguments.length>=3&&(a=t),l=e,"[object Array]"===o.call(l)?function(e,n,t){for(var r=0,o=e.length;r{"use strict";var n=Object.prototype.toString,t=Math.max,r=function(e,n){for(var t=[],r=0;r{"use strict";var r=t(9353);e.exports=Function.prototype.bind||r},4462:e=>{"use strict";var n=function(){return"string"==typeof function(){}.name},t=Object.getOwnPropertyDescriptor;if(t)try{t([],"length")}catch(e){t=null}n.functionsHaveConfigurableNames=function(){if(!n()||!t)return!1;var e=t((function(){}),"name");return!!e&&!!e.configurable};var r=Function.prototype.bind;n.boundFunctionsHaveNames=function(){return n()&&"function"==typeof r&&""!==function(){}.bind().name},e.exports=n},453:(e,n,t)=>{"use strict";var r,o=t(9612),i=t(9383),a=t(1237),l=t(9290),c=t(9538),s=t(8068),u=t(9675),p=t(5345),f=t(1514),d=t(8968),g=t(6188),h=t(8002),A=t(5880),b=t(414),v=t(3093),m=Function,y=function(e){try{return m('"use strict"; return ('+e+").constructor;")()}catch(e){}},E=t(5795),C=t(3036),w=function(){throw new u},x=E?function(){try{return w}catch(e){try{return E(arguments,"callee").get}catch(e){return w}}}():w,S=t(4039)(),_=t(3628),k=t(1064),O=t(8648),B=t(1002),P=t(76),T={},I="undefined"!=typeof Uint8Array&&_?_(Uint8Array):r,j={__proto__:null,"%AggregateError%":"undefined"==typeof AggregateError?r:AggregateError,"%Array%":Array,"%ArrayBuffer%":"undefined"==typeof ArrayBuffer?r:ArrayBuffer,"%ArrayIteratorPrototype%":S&&_?_([][Symbol.iterator]()):r,"%AsyncFromSyncIteratorPrototype%":r,"%AsyncFunction%":T,"%AsyncGenerator%":T,"%AsyncGeneratorFunction%":T,"%AsyncIteratorPrototype%":T,"%Atomics%":"undefined"==typeof Atomics?r:Atomics,"%BigInt%":"undefined"==typeof BigInt?r:BigInt,"%BigInt64Array%":"undefined"==typeof BigInt64Array?r:BigInt64Array,"%BigUint64Array%":"undefined"==typeof BigUint64Array?r:BigUint64Array,"%Boolean%":Boolean,"%DataView%":"undefined"==typeof DataView?r:DataView,"%Date%":Date,"%decodeURI%":decodeURI,"%decodeURIComponent%":decodeURIComponent,"%encodeURI%":encodeURI,"%encodeURIComponent%":encodeURIComponent,"%Error%":i,"%eval%":eval,"%EvalError%":a,"%Float16Array%":"undefined"==typeof Float16Array?r:Float16Array,"%Float32Array%":"undefined"==typeof Float32Array?r:Float32Array,"%Float64Array%":"undefined"==typeof Float64Array?r:Float64Array,"%FinalizationRegistry%":"undefined"==typeof FinalizationRegistry?r:FinalizationRegistry,"%Function%":m,"%GeneratorFunction%":T,"%Int8Array%":"undefined"==typeof Int8Array?r:Int8Array,"%Int16Array%":"undefined"==typeof Int16Array?r:Int16Array,"%Int32Array%":"undefined"==typeof Int32Array?r:Int32Array,"%isFinite%":isFinite,"%isNaN%":isNaN,"%IteratorPrototype%":S&&_?_(_([][Symbol.iterator]())):r,"%JSON%":"object"==typeof JSON?JSON:r,"%Map%":"undefined"==typeof Map?r:Map,"%MapIteratorPrototype%":"undefined"!=typeof Map&&S&&_?_((new Map)[Symbol.iterator]()):r,"%Math%":Math,"%Number%":Number,"%Object%":o,"%Object.getOwnPropertyDescriptor%":E,"%parseFloat%":parseFloat,"%parseInt%":parseInt,"%Promise%":"undefined"==typeof Promise?r:Promise,"%Proxy%":"undefined"==typeof Proxy?r:Proxy,"%RangeError%":l,"%ReferenceError%":c,"%Reflect%":"undefined"==typeof Reflect?r:Reflect,"%RegExp%":RegExp,"%Set%":"undefined"==typeof Set?r:Set,"%SetIteratorPrototype%":"undefined"!=typeof Set&&S&&_?_((new Set)[Symbol.iterator]()):r,"%SharedArrayBuffer%":"undefined"==typeof SharedArrayBuffer?r:SharedArrayBuffer,"%String%":String,"%StringIteratorPrototype%":S&&_?_(""[Symbol.iterator]()):r,"%Symbol%":S?Symbol:r,"%SyntaxError%":s,"%ThrowTypeError%":x,"%TypedArray%":I,"%TypeError%":u,"%Uint8Array%":"undefined"==typeof Uint8Array?r:Uint8Array,"%Uint8ClampedArray%":"undefined"==typeof Uint8ClampedArray?r:Uint8ClampedArray,"%Uint16Array%":"undefined"==typeof Uint16Array?r:Uint16Array,"%Uint32Array%":"undefined"==typeof Uint32Array?r:Uint32Array,"%URIError%":p,"%WeakMap%":"undefined"==typeof WeakMap?r:WeakMap,"%WeakRef%":"undefined"==typeof WeakRef?r:WeakRef,"%WeakSet%":"undefined"==typeof WeakSet?r:WeakSet,"%Function.prototype.call%":P,"%Function.prototype.apply%":B,"%Object.defineProperty%":C,"%Object.getPrototypeOf%":k,"%Math.abs%":f,"%Math.floor%":d,"%Math.max%":g,"%Math.min%":h,"%Math.pow%":A,"%Math.round%":b,"%Math.sign%":v,"%Reflect.getPrototypeOf%":O};if(_)try{null.error}catch(e){var z=_(_(e));j["%Error.prototype%"]=z}var D=function e(n){var t;if("%AsyncFunction%"===n)t=y("async function () {}");else if("%GeneratorFunction%"===n)t=y("function* () {}");else if("%AsyncGeneratorFunction%"===n)t=y("async function* () {}");else if("%AsyncGenerator%"===n){var r=e("%AsyncGeneratorFunction%");r&&(t=r.prototype)}else if("%AsyncIteratorPrototype%"===n){var o=e("%AsyncGenerator%");o&&_&&(t=_(o.prototype))}return j[n]=t,t},R={__proto__:null,"%ArrayBufferPrototype%":["ArrayBuffer","prototype"],"%ArrayPrototype%":["Array","prototype"],"%ArrayProto_entries%":["Array","prototype","entries"],"%ArrayProto_forEach%":["Array","prototype","forEach"],"%ArrayProto_keys%":["Array","prototype","keys"],"%ArrayProto_values%":["Array","prototype","values"],"%AsyncFunctionPrototype%":["AsyncFunction","prototype"],"%AsyncGenerator%":["AsyncGeneratorFunction","prototype"],"%AsyncGeneratorPrototype%":["AsyncGeneratorFunction","prototype","prototype"],"%BooleanPrototype%":["Boolean","prototype"],"%DataViewPrototype%":["DataView","prototype"],"%DatePrototype%":["Date","prototype"],"%ErrorPrototype%":["Error","prototype"],"%EvalErrorPrototype%":["EvalError","prototype"],"%Float32ArrayPrototype%":["Float32Array","prototype"],"%Float64ArrayPrototype%":["Float64Array","prototype"],"%FunctionPrototype%":["Function","prototype"],"%Generator%":["GeneratorFunction","prototype"],"%GeneratorPrototype%":["GeneratorFunction","prototype","prototype"],"%Int8ArrayPrototype%":["Int8Array","prototype"],"%Int16ArrayPrototype%":["Int16Array","prototype"],"%Int32ArrayPrototype%":["Int32Array","prototype"],"%JSONParse%":["JSON","parse"],"%JSONStringify%":["JSON","stringify"],"%MapPrototype%":["Map","prototype"],"%NumberPrototype%":["Number","prototype"],"%ObjectPrototype%":["Object","prototype"],"%ObjProto_toString%":["Object","prototype","toString"],"%ObjProto_valueOf%":["Object","prototype","valueOf"],"%PromisePrototype%":["Promise","prototype"],"%PromiseProto_then%":["Promise","prototype","then"],"%Promise_all%":["Promise","all"],"%Promise_reject%":["Promise","reject"],"%Promise_resolve%":["Promise","resolve"],"%RangeErrorPrototype%":["RangeError","prototype"],"%ReferenceErrorPrototype%":["ReferenceError","prototype"],"%RegExpPrototype%":["RegExp","prototype"],"%SetPrototype%":["Set","prototype"],"%SharedArrayBufferPrototype%":["SharedArrayBuffer","prototype"],"%StringPrototype%":["String","prototype"],"%SymbolPrototype%":["Symbol","prototype"],"%SyntaxErrorPrototype%":["SyntaxError","prototype"],"%TypedArrayPrototype%":["TypedArray","prototype"],"%TypeErrorPrototype%":["TypeError","prototype"],"%Uint8ArrayPrototype%":["Uint8Array","prototype"],"%Uint8ClampedArrayPrototype%":["Uint8ClampedArray","prototype"],"%Uint16ArrayPrototype%":["Uint16Array","prototype"],"%Uint32ArrayPrototype%":["Uint32Array","prototype"],"%URIErrorPrototype%":["URIError","prototype"],"%WeakMapPrototype%":["WeakMap","prototype"],"%WeakSetPrototype%":["WeakSet","prototype"]},M=t(6743),N=t(9957),F=M.call(P,Array.prototype.concat),L=M.call(B,Array.prototype.splice),H=M.call(P,String.prototype.replace),U=M.call(P,String.prototype.slice),W=M.call(P,RegExp.prototype.exec),G=/[^%.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|%$))/g,Y=/\\(\\)?/g,q=function(e,n){var t,r=e;if(N(R,r)&&(r="%"+(t=R[r])[0]+"%"),N(j,r)){var o=j[r];if(o===T&&(o=D(r)),void 0===o&&!n)throw new u("intrinsic "+e+" exists, but is not available. Please file an issue!");return{alias:t,name:r,value:o}}throw new s("intrinsic "+e+" does not exist!")};e.exports=function(e,n){if("string"!=typeof e||0===e.length)throw new u("intrinsic name must be a non-empty string");if(arguments.length>1&&"boolean"!=typeof n)throw new u('"allowMissing" argument must be a boolean');if(null===W(/^%?[^%]*%?$/,e))throw new s("`%` may not be present anywhere but at the beginning and end of the intrinsic name");var t=function(e){var n=U(e,0,1),t=U(e,-1);if("%"===n&&"%"!==t)throw new s("invalid intrinsic syntax, expected closing `%`");if("%"===t&&"%"!==n)throw new s("invalid intrinsic syntax, expected opening `%`");var r=[];return H(e,G,(function(e,n,t,o){r[r.length]=t?H(o,Y,"$1"):n||e})),r}(e),r=t.length>0?t[0]:"",o=q("%"+r+"%",n),i=o.name,a=o.value,l=!1,c=o.alias;c&&(r=c[0],L(t,F([0,1],c)));for(var p=1,f=!0;p=t.length){var A=E(a,d);a=(f=!!A)&&"get"in A&&!("originalValue"in A.get)?A.get:a[d]}else f=N(a,d),a=a[d];f&&!l&&(j[i]=a)}}return a}},1064:(e,n,t)=>{"use strict";var r=t(9612);e.exports=r.getPrototypeOf||null},8648:e=>{"use strict";e.exports="undefined"!=typeof Reflect&&Reflect.getPrototypeOf||null},3628:(e,n,t)=>{"use strict";var r=t(8648),o=t(1064),i=t(7176);e.exports=r?function(e){return r(e)}:o?function(e){if(!e||"object"!=typeof e&&"function"!=typeof e)throw new TypeError("getProto: not an object");return o(e)}:i?function(e){return i(e)}:null},6549:e=>{"use strict";e.exports=Object.getOwnPropertyDescriptor},5795:(e,n,t)=>{"use strict";var r=t(6549);if(r)try{r([],"length")}catch(e){r=null}e.exports=r},9790:e=>{"use strict";var n="undefined"!=typeof BigInt&&BigInt;e.exports=function(){return"function"==typeof n&&"function"==typeof BigInt&&"bigint"==typeof n(42)&&"bigint"==typeof BigInt(42)}},592:(e,n,t)=>{"use strict";var r=t(3036),o=function(){return!!r};o.hasArrayLengthDefineBug=function(){if(!r)return null;try{return 1!==r([],"length",{value:1}).length}catch(e){return!0}},e.exports=o},4039:(e,n,t)=>{"use strict";var r="undefined"!=typeof Symbol&&Symbol,o=t(1333);e.exports=function(){return"function"==typeof r&&("function"==typeof Symbol&&("symbol"==typeof r("foo")&&("symbol"==typeof Symbol("bar")&&o())))}},1333:e=>{"use strict";e.exports=function(){if("function"!=typeof Symbol||"function"!=typeof Object.getOwnPropertySymbols)return!1;if("symbol"==typeof Symbol.iterator)return!0;var e={},n=Symbol("test"),t=Object(n);if("string"==typeof n)return!1;if("[object Symbol]"!==Object.prototype.toString.call(n))return!1;if("[object Symbol]"!==Object.prototype.toString.call(t))return!1;for(var r in e[n]=42,e)return!1;if("function"==typeof Object.keys&&0!==Object.keys(e).length)return!1;if("function"==typeof Object.getOwnPropertyNames&&0!==Object.getOwnPropertyNames(e).length)return!1;var o=Object.getOwnPropertySymbols(e);if(1!==o.length||o[0]!==n)return!1;if(!Object.prototype.propertyIsEnumerable.call(e,n))return!1;if("function"==typeof Object.getOwnPropertyDescriptor){var i=Object.getOwnPropertyDescriptor(e,n);if(42!==i.value||!0!==i.enumerable)return!1}return!0}},9092:(e,n,t)=>{"use strict";var r=t(1333);e.exports=function(){return r()&&!!Symbol.toStringTag}},9957:(e,n,t)=>{"use strict";var r=Function.prototype.call,o=Object.prototype.hasOwnProperty,i=t(6743);e.exports=i.call(r,o)},63:(e,n,t)=>{"use strict";var r=t(9957),o=t(920)(),i=t(9675),a={assert:function(e,n){if(!e||"object"!=typeof e&&"function"!=typeof e)throw new i("`O` is not an object");if("string"!=typeof n)throw new i("`slot` must be a string");if(o.assert(e),!a.has(e,n))throw new i("`"+n+"` is not present on `O`")},get:function(e,n){if(!e||"object"!=typeof e&&"function"!=typeof e)throw new i("`O` is not an object");if("string"!=typeof n)throw new i("`slot` must be a string");var t=o.get(e);return t&&t["$"+n]},has:function(e,n){if(!e||"object"!=typeof e&&"function"!=typeof e)throw new i("`O` is not an object");if("string"!=typeof n)throw new i("`slot` must be a string");var t=o.get(e);return!!t&&r(t,"$"+n)},set:function(e,n,t){if(!e||"object"!=typeof e&&"function"!=typeof e)throw new i("`O` is not an object");if("string"!=typeof n)throw new i("`slot` must be a string");var r=o.get(e);r||(r={},o.set(e,r)),r["$"+n]=t}};Object.freeze&&Object.freeze(a),e.exports=a},7244:(e,n,t)=>{"use strict";var r=t(9092)(),o=t(6556)("Object.prototype.toString"),i=function(e){return!(r&&e&&"object"==typeof e&&Symbol.toStringTag in e)&&"[object Arguments]"===o(e)},a=function(e){return!!i(e)||null!==e&&"object"==typeof e&&"length"in e&&"number"==typeof e.length&&e.length>=0&&"[object Array]"!==o(e)&&"callee"in e&&"[object Function]"===o(e.callee)},l=function(){return i(arguments)}();i.isLegacyArguments=a,e.exports=l?i:a},4670:(e,n,t)=>{"use strict";var r=t(487),o=t(6556),i=t(453)("%ArrayBuffer%",!0),a=o("ArrayBuffer.prototype.byteLength",!0),l=o("Object.prototype.toString"),c=!!i&&!a&&new i(0).slice,s=!!c&&r(c);e.exports=a||s?function(e){if(!e||"object"!=typeof e)return!1;try{return a?a(e):s(e,0),!0}catch(e){return!1}}:i?function(e){return"[object ArrayBuffer]"===l(e)}:function(e){return!1}},9803:(e,n,t)=>{"use strict";if(t(9790)()){var r=BigInt.prototype.valueOf;e.exports=function(e){return null!=e&&"boolean"!=typeof e&&"string"!=typeof e&&"number"!=typeof e&&"symbol"!=typeof e&&"function"!=typeof e&&("bigint"==typeof e||function(e){try{return r.call(e),!0}catch(e){}return!1}(e))}}else e.exports=function(e){return!1}},5128:(e,n,t)=>{"use strict";var r=t(6556),o=r("Boolean.prototype.toString"),i=r("Object.prototype.toString"),a=t(9092)();e.exports=function(e){return"boolean"==typeof e||null!==e&&"object"==typeof e&&(a?function(e){try{return o(e),!0}catch(e){return!1}}(e):"[object Boolean]"===i(e))}},9600:e=>{"use strict";var n,t,r=Function.prototype.toString,o="object"==typeof Reflect&&null!==Reflect&&Reflect.apply;if("function"==typeof o&&"function"==typeof Object.defineProperty)try{n=Object.defineProperty({},"length",{get:function(){throw t}}),t={},o((function(){throw 42}),null,n)}catch(e){e!==t&&(o=null)}else o=null;var i=/^\s*class\b/,a=function(e){try{var n=r.call(e);return i.test(n)}catch(e){return!1}},l=function(e){try{return!a(e)&&(r.call(e),!0)}catch(e){return!1}},c=Object.prototype.toString,s="function"==typeof Symbol&&!!Symbol.toStringTag,u=!(0 in[,]),p=function(){return!1};if("object"==typeof document){var f=document.all;c.call(f)===c.call(document.all)&&(p=function(e){if((u||!e)&&(void 0===e||"object"==typeof e))try{var n=c.call(e);return("[object HTMLAllCollection]"===n||"[object HTML document.all class]"===n||"[object HTMLCollection]"===n||"[object Object]"===n)&&null==e("")}catch(e){}return!1})}e.exports=o?function(e){if(p(e))return!0;if(!e)return!1;if("function"!=typeof e&&"object"!=typeof e)return!1;try{o(e,null,n)}catch(e){if(e!==t)return!1}return!a(e)&&l(e)}:function(e){if(p(e))return!0;if(!e)return!1;if("function"!=typeof e&&"object"!=typeof e)return!1;if(s)return l(e);if(a(e))return!1;var n=c.call(e);return!("[object Function]"!==n&&"[object GeneratorFunction]"!==n&&!/^\[object HTML/.test(n))&&l(e)}},2120:(e,n,t)=>{"use strict";var r=t(6556),o=r("Date.prototype.getDay"),i=r("Object.prototype.toString"),a=t(9092)();e.exports=function(e){return"object"==typeof e&&null!==e&&(a?function(e){try{return o(e),!0}catch(e){return!1}}(e):"[object Date]"===i(e))}},1421:e=>{"use strict";var n,t="function"==typeof Map&&Map.prototype?Map:null,r="function"==typeof Set&&Set.prototype?Set:null;t||(n=function(e){return!1});var o=t?Map.prototype.has:null,i=r?Set.prototype.has:null;n||o||(n=function(e){return!1}),e.exports=n||function(e){if(!e||"object"!=typeof e)return!1;try{if(o.call(e),i)try{i.call(e)}catch(e){return!0}return e instanceof t}catch(e){}return!1}},1703:(e,n,t)=>{"use strict";var r=t(6556),o=r("Number.prototype.toString"),i=r("Object.prototype.toString"),a=t(9092)();e.exports=function(e){return"number"==typeof e||!(!e||"object"!=typeof e)&&(a?function(e){try{return o(e),!0}catch(e){return!1}}(e):"[object Number]"===i(e))}},4035:(e,n,t)=>{"use strict";var r,o=t(6556),i=t(9092)(),a=t(9957),l=t(5795);if(i){var c=o("RegExp.prototype.exec"),s={},u=function(){throw s},p={toString:u,valueOf:u};"symbol"==typeof Symbol.toPrimitive&&(p[Symbol.toPrimitive]=u),r=function(e){if(!e||"object"!=typeof e)return!1;var n=l(e,"lastIndex");if(!(n&&a(n,"value")))return!1;try{c(e,p)}catch(e){return e===s}}}else{var f=o("Object.prototype.toString");r=function(e){return!(!e||"object"!=typeof e&&"function"!=typeof e)&&"[object RegExp]"===f(e)}}e.exports=r},256:e=>{"use strict";var n,t="function"==typeof Map&&Map.prototype?Map:null,r="function"==typeof Set&&Set.prototype?Set:null;r||(n=function(e){return!1});var o=t?Map.prototype.has:null,i=r?Set.prototype.has:null;n||i||(n=function(e){return!1}),e.exports=n||function(e){if(!e||"object"!=typeof e)return!1;try{if(i.call(e),o)try{o.call(e)}catch(e){return!0}return e instanceof r}catch(e){}return!1}},7070:(e,n,t)=>{"use strict";var r=t(6556)("SharedArrayBuffer.prototype.byteLength",!0);e.exports=r?function(e){if(!e||"object"!=typeof e)return!1;try{return r(e),!0}catch(e){return!1}}:function(e){return!1}},4761:(e,n,t)=>{"use strict";var r=t(6556),o=r("String.prototype.valueOf"),i=r("Object.prototype.toString"),a=t(9092)();e.exports=function(e){return"string"==typeof e||!(!e||"object"!=typeof e)&&(a?function(e){try{return o(e),!0}catch(e){return!1}}(e):"[object String]"===i(e))}},3612:(e,n,t)=>{"use strict";var r=t(6556),o=r("Object.prototype.toString"),i=t(4039)(),a=t(9721);if(i){var l=r("Symbol.prototype.toString"),c=a(/^Symbol\(.*\)$/);e.exports=function(e){if("symbol"==typeof e)return!0;if(!e||"object"!=typeof e||"[object Symbol]"!==o(e))return!1;try{return function(e){return"symbol"==typeof e.valueOf()&&c(l(e))}(e)}catch(e){return!1}}}else e.exports=function(e){return!1}},7842:e=>{"use strict";var n,t="function"==typeof WeakMap&&WeakMap.prototype?WeakMap:null,r="function"==typeof WeakSet&&WeakSet.prototype?WeakSet:null;t||(n=function(e){return!1});var o=t?t.prototype.has:null,i=r?r.prototype.has:null;n||o||(n=function(e){return!1}),e.exports=n||function(e){if(!e||"object"!=typeof e)return!1;try{if(o.call(e,o),i)try{i.call(e,i)}catch(e){return!0}return e instanceof t}catch(e){}return!1}},2648:(e,n,t)=>{"use strict";var r=t(453),o=t(6556),i=r("%WeakSet%",!0),a=o("WeakSet.prototype.has",!0);if(a){var l=o("WeakMap.prototype.has",!0);e.exports=function(e){if(!e||"object"!=typeof e)return!1;try{if(a(e,a),l)try{l(e,l)}catch(e){return!0}return e instanceof i}catch(e){}return!1}}else e.exports=function(e){return!1}},4634:e=>{var n={}.toString;e.exports=Array.isArray||function(e){return"[object Array]"==n.call(e)}},1514:e=>{"use strict";e.exports=Math.abs},8968:e=>{"use strict";e.exports=Math.floor},4459:e=>{"use strict";e.exports=Number.isNaN||function(e){return e!=e}},6188:e=>{"use strict";e.exports=Math.max},8002:e=>{"use strict";e.exports=Math.min},5880:e=>{"use strict";e.exports=Math.pow},414:e=>{"use strict";e.exports=Math.round},3093:(e,n,t)=>{"use strict";var r=t(4459);e.exports=function(e){return r(e)||0===e?e:e<0?-1:1}},5228:e=>{"use strict"; +/* +object-assign +(c) Sindre Sorhus +@license MIT +*/var n=Object.getOwnPropertySymbols,t=Object.prototype.hasOwnProperty,r=Object.prototype.propertyIsEnumerable;e.exports=function(){try{if(!Object.assign)return!1;var e=new String("abc");if(e[5]="de","5"===Object.getOwnPropertyNames(e)[0])return!1;for(var n={},t=0;t<10;t++)n["_"+String.fromCharCode(t)]=t;if("0123456789"!==Object.getOwnPropertyNames(n).map((function(e){return n[e]})).join(""))return!1;var r={};return"abcdefghijklmnopqrst".split("").forEach((function(e){r[e]=e})),"abcdefghijklmnopqrst"===Object.keys(Object.assign({},r)).join("")}catch(e){return!1}}()?Object.assign:function(e,o){for(var i,a,l=function(e){if(null==e)throw new TypeError("Object.assign cannot be called with null or undefined");return Object(e)}(e),c=1;c{var r="function"==typeof Map&&Map.prototype,o=Object.getOwnPropertyDescriptor&&r?Object.getOwnPropertyDescriptor(Map.prototype,"size"):null,i=r&&o&&"function"==typeof o.get?o.get:null,a=r&&Map.prototype.forEach,l="function"==typeof Set&&Set.prototype,c=Object.getOwnPropertyDescriptor&&l?Object.getOwnPropertyDescriptor(Set.prototype,"size"):null,s=l&&c&&"function"==typeof c.get?c.get:null,u=l&&Set.prototype.forEach,p="function"==typeof WeakMap&&WeakMap.prototype?WeakMap.prototype.has:null,f="function"==typeof WeakSet&&WeakSet.prototype?WeakSet.prototype.has:null,d="function"==typeof WeakRef&&WeakRef.prototype?WeakRef.prototype.deref:null,g=Boolean.prototype.valueOf,h=Object.prototype.toString,A=Function.prototype.toString,b=String.prototype.match,v=String.prototype.slice,m=String.prototype.replace,y=String.prototype.toUpperCase,E=String.prototype.toLowerCase,C=RegExp.prototype.test,w=Array.prototype.concat,x=Array.prototype.join,S=Array.prototype.slice,_=Math.floor,k="function"==typeof BigInt?BigInt.prototype.valueOf:null,O=Object.getOwnPropertySymbols,B="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?Symbol.prototype.toString:null,P="function"==typeof Symbol&&"object"==typeof Symbol.iterator,T="function"==typeof Symbol&&Symbol.toStringTag&&(typeof Symbol.toStringTag===P||"symbol")?Symbol.toStringTag:null,I=Object.prototype.propertyIsEnumerable,j=("function"==typeof Reflect?Reflect.getPrototypeOf:Object.getPrototypeOf)||([].__proto__===Array.prototype?function(e){return e.__proto__}:null);function z(e,n){if(e===1/0||e===-1/0||e!=e||e&&e>-1e3&&e<1e3||C.call(/e/,n))return n;var t=/[0-9](?=(?:[0-9]{3})+(?![0-9]))/g;if("number"==typeof e){var r=e<0?-_(-e):_(e);if(r!==e){var o=String(r),i=v.call(n,o.length+1);return m.call(o,t,"$&_")+"."+m.call(m.call(i,/([0-9]{3})/g,"$&_"),/_$/,"")}}return m.call(n,t,"$&_")}var D=t(2634),R=D.custom,M=Y(R)?R:null,N={__proto__:null,double:'"',single:"'"},F={__proto__:null,double:/(["\\])/g,single:/(['\\])/g};function L(e,n,t){var r=t.quoteStyle||n,o=N[r];return o+e+o}function H(e){return m.call(String(e),/"/g,""")}function U(e){return!T||!("object"==typeof e&&(T in e||void 0!==e[T]))}function W(e){return"[object Array]"===$(e)&&U(e)}function G(e){return"[object RegExp]"===$(e)&&U(e)}function Y(e){if(P)return e&&"object"==typeof e&&e instanceof Symbol;if("symbol"==typeof e)return!0;if(!e||"object"!=typeof e||!B)return!1;try{return B.call(e),!0}catch(e){}return!1}e.exports=function e(n,r,o,l){var c=r||{};if(V(c,"quoteStyle")&&!V(N,c.quoteStyle))throw new TypeError('option "quoteStyle" must be "single" or "double"');if(V(c,"maxStringLength")&&("number"==typeof c.maxStringLength?c.maxStringLength<0&&c.maxStringLength!==1/0:null!==c.maxStringLength))throw new TypeError('option "maxStringLength", if provided, must be a positive integer, Infinity, or `null`');var h=!V(c,"customInspect")||c.customInspect;if("boolean"!=typeof h&&"symbol"!==h)throw new TypeError("option \"customInspect\", if provided, must be `true`, `false`, or `'symbol'`");if(V(c,"indent")&&null!==c.indent&&"\t"!==c.indent&&!(parseInt(c.indent,10)===c.indent&&c.indent>0))throw new TypeError('option "indent" must be "\\t", an integer > 0, or `null`');if(V(c,"numericSeparator")&&"boolean"!=typeof c.numericSeparator)throw new TypeError('option "numericSeparator", if provided, must be `true` or `false`');var y=c.numericSeparator;if(void 0===n)return"undefined";if(null===n)return"null";if("boolean"==typeof n)return n?"true":"false";if("string"==typeof n)return Q(n,c);if("number"==typeof n){if(0===n)return 1/0/n>0?"0":"-0";var C=String(n);return y?z(n,C):C}if("bigint"==typeof n){var _=String(n)+"n";return y?z(n,_):_}var O=void 0===c.depth?5:c.depth;if(void 0===o&&(o=0),o>=O&&O>0&&"object"==typeof n)return W(n)?"[Array]":"[Object]";var R=function(e,n){var t;if("\t"===e.indent)t="\t";else{if(!("number"==typeof e.indent&&e.indent>0))return null;t=x.call(Array(e.indent+1)," ")}return{base:t,prev:x.call(Array(n+1),t)}}(c,o);if(void 0===l)l=[];else if(K(l,n)>=0)return"[Circular]";function F(n,t,r){if(t&&(l=S.call(l)).push(t),r){var i={depth:c.depth};return V(c,"quoteStyle")&&(i.quoteStyle=c.quoteStyle),e(n,i,o+1,l)}return e(n,c,o+1,l)}if("function"==typeof n&&!G(n)){var q=function(e){if(e.name)return e.name;var n=b.call(A.call(e),/^function\s*([\w$]+)/);if(n)return n[1];return null}(n),X=te(n,F);return"[Function"+(q?": "+q:" (anonymous)")+"]"+(X.length>0?" { "+x.call(X,", ")+" }":"")}if(Y(n)){var re=P?m.call(String(n),/^(Symbol\(.*\))_[^)]*$/,"$1"):B.call(n);return"object"!=typeof n||P?re:Z(re)}if(function(e){if(!e||"object"!=typeof e)return!1;if("undefined"!=typeof HTMLElement&&e instanceof HTMLElement)return!0;return"string"==typeof e.nodeName&&"function"==typeof e.getAttribute}(n)){for(var oe="<"+E.call(String(n.nodeName)),ie=n.attributes||[],ae=0;ae"}if(W(n)){if(0===n.length)return"[]";var le=te(n,F);return R&&!function(e){for(var n=0;n=0)return!1;return!0}(le)?"["+ne(le,R)+"]":"[ "+x.call(le,", ")+" ]"}if(function(e){return"[object Error]"===$(e)&&U(e)}(n)){var ce=te(n,F);return"cause"in Error.prototype||!("cause"in n)||I.call(n,"cause")?0===ce.length?"["+String(n)+"]":"{ ["+String(n)+"] "+x.call(ce,", ")+" }":"{ ["+String(n)+"] "+x.call(w.call("[cause]: "+F(n.cause),ce),", ")+" }"}if("object"==typeof n&&h){if(M&&"function"==typeof n[M]&&D)return D(n,{depth:O-o});if("symbol"!==h&&"function"==typeof n.inspect)return n.inspect()}if(function(e){if(!i||!e||"object"!=typeof e)return!1;try{i.call(e);try{s.call(e)}catch(e){return!0}return e instanceof Map}catch(e){}return!1}(n)){var se=[];return a&&a.call(n,(function(e,t){se.push(F(t,n,!0)+" => "+F(e,n))})),ee("Map",i.call(n),se,R)}if(function(e){if(!s||!e||"object"!=typeof e)return!1;try{s.call(e);try{i.call(e)}catch(e){return!0}return e instanceof Set}catch(e){}return!1}(n)){var ue=[];return u&&u.call(n,(function(e){ue.push(F(e,n))})),ee("Set",s.call(n),ue,R)}if(function(e){if(!p||!e||"object"!=typeof e)return!1;try{p.call(e,p);try{f.call(e,f)}catch(e){return!0}return e instanceof WeakMap}catch(e){}return!1}(n))return J("WeakMap");if(function(e){if(!f||!e||"object"!=typeof e)return!1;try{f.call(e,f);try{p.call(e,p)}catch(e){return!0}return e instanceof WeakSet}catch(e){}return!1}(n))return J("WeakSet");if(function(e){if(!d||!e||"object"!=typeof e)return!1;try{return d.call(e),!0}catch(e){}return!1}(n))return J("WeakRef");if(function(e){return"[object Number]"===$(e)&&U(e)}(n))return Z(F(Number(n)));if(function(e){if(!e||"object"!=typeof e||!k)return!1;try{return k.call(e),!0}catch(e){}return!1}(n))return Z(F(k.call(n)));if(function(e){return"[object Boolean]"===$(e)&&U(e)}(n))return Z(g.call(n));if(function(e){return"[object String]"===$(e)&&U(e)}(n))return Z(F(String(n)));if("undefined"!=typeof window&&n===window)return"{ [object Window] }";if("undefined"!=typeof globalThis&&n===globalThis||void 0!==t.g&&n===t.g)return"{ [object globalThis] }";if(!function(e){return"[object Date]"===$(e)&&U(e)}(n)&&!G(n)){var pe=te(n,F),fe=j?j(n)===Object.prototype:n instanceof Object||n.constructor===Object,de=n instanceof Object?"":"null prototype",ge=!fe&&T&&Object(n)===n&&T in n?v.call($(n),8,-1):de?"Object":"",he=(fe||"function"!=typeof n.constructor?"":n.constructor.name?n.constructor.name+" ":"")+(ge||de?"["+x.call(w.call([],ge||[],de||[]),": ")+"] ":"");return 0===pe.length?he+"{}":R?he+"{"+ne(pe,R)+"}":he+"{ "+x.call(pe,", ")+" }"}return String(n)};var q=Object.prototype.hasOwnProperty||function(e){return e in this};function V(e,n){return q.call(e,n)}function $(e){return h.call(e)}function K(e,n){if(e.indexOf)return e.indexOf(n);for(var t=0,r=e.length;tn.maxStringLength){var t=e.length-n.maxStringLength,r="... "+t+" more character"+(t>1?"s":"");return Q(v.call(e,0,n.maxStringLength),n)+r}var o=F[n.quoteStyle||"single"];return o.lastIndex=0,L(m.call(m.call(e,o,"\\$1"),/[\x00-\x1f]/g,X),"single",n)}function X(e){var n=e.charCodeAt(0),t={8:"b",9:"t",10:"n",12:"f",13:"r"}[n];return t?"\\"+t:"\\x"+(n<16?"0":"")+y.call(n.toString(16))}function Z(e){return"Object("+e+")"}function J(e){return e+" { ? }"}function ee(e,n,t,r){return e+" ("+n+") {"+(r?ne(t,r):x.call(t,", "))+"}"}function ne(e,n){if(0===e.length)return"";var t="\n"+n.prev+n.base;return t+x.call(e,","+t)+"\n"+n.prev}function te(e,n){var t=W(e),r=[];if(t){r.length=e.length;for(var o=0;o{"use strict";var n=function(e){return e!=e};e.exports=function(e,t){return 0===e&&0===t?1/e==1/t:e===t||!(!n(e)||!n(t))}},7653:(e,n,t)=>{"use strict";var r=t(8452),o=t(487),i=t(9211),a=t(9394),l=t(6576),c=o(a(),Object);r(c,{getPolyfill:a,implementation:i,shim:l}),e.exports=c},9394:(e,n,t)=>{"use strict";var r=t(9211);e.exports=function(){return"function"==typeof Object.is?Object.is:r}},6576:(e,n,t)=>{"use strict";var r=t(9394),o=t(8452);e.exports=function(){var e=r();return o(Object,{is:e},{is:function(){return Object.is!==e}}),e}},8875:(e,n,t)=>{"use strict";var r;if(!Object.keys){var o=Object.prototype.hasOwnProperty,i=Object.prototype.toString,a=t(1093),l=Object.prototype.propertyIsEnumerable,c=!l.call({toString:null},"toString"),s=l.call((function(){}),"prototype"),u=["toString","toLocaleString","valueOf","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","constructor"],p=function(e){var n=e.constructor;return n&&n.prototype===e},f={$applicationCache:!0,$console:!0,$external:!0,$frame:!0,$frameElement:!0,$frames:!0,$innerHeight:!0,$innerWidth:!0,$onmozfullscreenchange:!0,$onmozfullscreenerror:!0,$outerHeight:!0,$outerWidth:!0,$pageXOffset:!0,$pageYOffset:!0,$parent:!0,$scrollLeft:!0,$scrollTop:!0,$scrollX:!0,$scrollY:!0,$self:!0,$webkitIndexedDB:!0,$webkitStorageInfo:!0,$window:!0},d=function(){if("undefined"==typeof window)return!1;for(var e in window)try{if(!f["$"+e]&&o.call(window,e)&&null!==window[e]&&"object"==typeof window[e])try{p(window[e])}catch(e){return!0}}catch(e){return!0}return!1}();r=function(e){var n=null!==e&&"object"==typeof e,t="[object Function]"===i.call(e),r=a(e),l=n&&"[object String]"===i.call(e),f=[];if(!n&&!t&&!r)throw new TypeError("Object.keys called on a non-object");var g=s&&t;if(l&&e.length>0&&!o.call(e,0))for(var h=0;h0)for(var A=0;A{"use strict";var r=Array.prototype.slice,o=t(1093),i=Object.keys,a=i?function(e){return i(e)}:t(8875),l=Object.keys;a.shim=function(){if(Object.keys){var e=function(){var e=Object.keys(arguments);return e&&e.length===arguments.length}(1,2);e||(Object.keys=function(e){return o(e)?l(r.call(e)):l(e)})}else Object.keys=a;return Object.keys||a},e.exports=a},1093:e=>{"use strict";var n=Object.prototype.toString;e.exports=function(e){var t=n.call(e),r="[object Arguments]"===t;return r||(r="[object Array]"!==t&&null!==e&&"object"==typeof e&&"number"==typeof e.length&&e.length>=0&&"[object Function]"===n.call(e.callee)),r}},8403:(e,n,t)=>{"use strict";var r=t(1189),o=t(1333)(),i=t(6556),a=t(9612),l=i("Array.prototype.push"),c=i("Object.prototype.propertyIsEnumerable"),s=o?a.getOwnPropertySymbols:null;e.exports=function(e,n){if(null==e)throw new TypeError("target must be an object");var t=a(e);if(1===arguments.length)return t;for(var i=1;i{"use strict";var r=t(8452),o=t(487),i=t(8403),a=t(9133),l=t(984),c=o.apply(a()),s=function(e,n){return c(Object,arguments)};r(s,{getPolyfill:a,implementation:i,shim:l}),e.exports=s},9133:(e,n,t)=>{"use strict";var r=t(8403);e.exports=function(){return Object.assign?function(){if(!Object.assign)return!1;for(var e="abcdefghijklmnopqrst",n=e.split(""),t={},r=0;r{"use strict";var r=t(8452),o=t(9133);e.exports=function(){var e=o();return r(Object,{assign:e},{assign:function(){return Object.assign!==e}}),e}},6578:e=>{"use strict";e.exports=["Float16Array","Float32Array","Float64Array","Int8Array","Int16Array","Int32Array","Uint8Array","Uint8ClampedArray","Uint16Array","Uint32Array","BigInt64Array","BigUint64Array"]},2694:(e,n,t)=>{"use strict";var r=t(6925);function o(){}function i(){}i.resetWarningCache=o,e.exports=function(){function e(e,n,t,o,i,a){if(a!==r){var l=new Error("Calling PropTypes validators directly is not supported by the `prop-types` package. Use PropTypes.checkPropTypes() to call them. Read more at http://fb.me/use-check-prop-types");throw l.name="Invariant Violation",l}}function n(){return e}e.isRequired=e;var t={array:e,bigint:e,bool:e,func:e,number:e,object:e,string:e,symbol:e,any:e,arrayOf:n,element:e,elementType:e,instanceOf:n,node:e,objectOf:n,oneOf:n,oneOfType:n,shape:n,exact:n,checkPropTypes:i,resetWarningCache:o};return t.PropTypes=t,t}},5556:(e,n,t)=>{e.exports=t(2694)()},6925:e=>{"use strict";e.exports="SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED"},2551:(e,n,t)=>{"use strict";var r=t(6540),o=t(5228),i=t(9982); +/** @license React v17.0.2 + * react-dom.production.min.js + * + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */function a(e){for(var n="https://reactjs.org/docs/error-decoder.html?invariant="+e,t=1;t