invercargill-jsonFriction points surfaced by the Percipio migration that must be fixed upstream of
Statum, i.e. in the invercargill-1 / invercargill-json libraries (declared as
dependencies in meson.build).
All of the items below are correctness/crash bugs in Invercargill's JSON mapping
layer — specifically the ValueElement → from_element → JsonElement /
from_properties path. Statum merely calls these APIs (see call sites below), so
it can only work around them, not fix them. Item numbers in parentheses refer to
the original Percipio friction report.
int64 values segfault during JSON serialisation (orig #7)RESOLVED (verified 2026-08-31, WP1 server-side effort). The dispatch fix landed upstream in Invercargill-Json commit
fc38c4c("Fix bool and int serialisation issues") —from_elementnow reads the rawGValuebehind aValueElementand dispatches on the fundamental type. This effort did not need to modify Invercargill-Json;Statum/testsround-tripsint64(full 2^53+ precision) through both the public snapshot path andEncryptionProvider.author_properties/read_properties(the Percipio login crash path).
GObjectMapping.to_properties() wraps each scalar GObject
property in new ValueElement(value) (src/GObjectMapping.vala:85). When that
PropertyDictionary is later serialised via JsonElement.from_properties() —
e.g. in EncryptionProvider.author_properties
(src/Cryptography/EncryptionProvider.vala:77) or in the snapshot-to-client
path — the int64 branch of from_element crashes at
node.set_int(element.as<int64?>()).encryption_provider_author_properties → JsonElement.from_properties.int64 was purged from slot state. Single
most dangerous footgun.int64 path in
from_element / ValueElement.as<int64?>() so a legitimately-held int64
ValueElement produces a valid Json.Node via set_int instead of crashing.bool values segfault on the encryption serialisation path (orig #9)RESOLVED (verified 2026-08-31, WP1 server-side effort). Same upstream fix as I-1 (Invercargill-Json
fc38c4c); verified through theset_private_typed→author_properties→read_propertiespath byStatum/tests. A related Statum-side lifetime bug found while testing this path —EncryptionProvider.read_propertiesreturned aJsonObjectwrapping memory owned by a temporaryJsonElement(use-after-free) — was fixed in Statum; it now returns values re-parsed into aPropertyDictionaryit owns.
from_properties/from_element family crashes on a
bool ValueElement. The public path does not crash on bools (it
serialises the snapshot DTO through a different, robust serialiser), but the
encryption/private path — set_private_typed
(src/DirectiveBuilder.vala:56) → to_properties →
EncryptionProvider.author_properties
(src/Cryptography/EncryptionProvider.vala:77) → from_properties — does.bools out of AuthPrivate and derive admin
status from the signed public role instead.from_element/from_properties handle bool
ValueElements without crashing, so the private/encryption path is as robust
as the public path. (Same root cause as I-1: the ValueElement type dispatch
in from_element is incomplete.)int (32-bit) serialises as a JSON string, not a number (orig #8)RESOLVED (verified 2026-08-31, WP1 server-side effort). Same upstream fix as I-1 (Invercargill-Json
fc38c4c);Statum/testsassertsintfields serialise as unquoted JSON numbers and round-trip on both paths.
to_properties on a GObject with an int field produces a
JSON string ("best_score": "-1") instead of a number. The int32 branch in
from_element is not reached for ValueElements; it falls through to the
assignable_to<string>() transform fallback.get_int on a
round-tripped value fails because the encrypted JSON stored a string. JS
coercion mostly hides it for display, but it breaks round-tripping.from_element emit numbers for numeric
ValueElements consistently (reach the int32/int64 branches rather than the
string fallback).Note: I-1, I-2 and I-3 share a single root cause — the
ValueElement→from_elementtype dispatch is unreliable for non-string scalars. Fixing the dispatch holistically would close all three at once.
Properties interface has no typed setter / set_json (orig #16, secondary)Mitigated Statum-side (2026-08-31):
GObjectMapping.to_propertiesnow returnsPropertyDictionarydirectly (seestatum-issues.mdS-13), so Statum callers no longer need the cast. The upstream interface enhancement remains open.
GObjectMapping.to_properties() returns the Properties
interface (src/GObjectMapping.vala:57), but adding a list/nested value
requires casting to the concrete PropertyDictionary to use its indexer
((PropertyDictionary) to_properties(pub)).Properties interface a typed setter (e.g.
set_json(string key, Element element)) so callers need not cast to
PropertyDictionary.
statum-issues.md S-13: change to_properties to return PropertyDictionary
directly); this item is the upstream API enhancement that would remove the
cast at the source.PropertyMapperBuilder's default constructor closure dangled after buildRESOLVED (2026-08-31, WP1 server-side effort). Fixed in this repository's copy of Invercargill as a pure bug fix — no API change. The builder now installs no default constructor closure, and
PropertyMapper.materialise()falls back toObject.new(typeof(T))when no constructor was set explicitly.
PropertyMapperBuilder<T>'s constructor defaulted its
constructor field to a closure capturing the builder itself. The builder
is usually transient (built and dropped inside PropertyMapper.build_for),
while the returned PropertyMapper lives on, so every later
materialise() call went through a closure pointing at freed memory — a
use-after-free that surfaced as crashes/garbage instances when mappers were
resolved and used after the building scope had ended.PropertyMapper.build_for that did not call set_constructor explicitly
(the common case).constructor null by default
and let materialise() construct through Object.new(typeof(T)) — no
closure outlives the builder.| Bug | Statum call site | Invercargill symbol that crashes/misbehaves |
|---|---|---|
| I-1 | GObjectMapping.vala:85 (new ValueElement(value)) → EncryptionProvider.vala:77 (JsonElement.from_properties) |
ValueElement.as<int64?>() → Json.Node.set_int |
| I-2 | DirectiveBuilder.vala:56 → EncryptionProvider.vala:77 |
from_properties / from_element on a bool |
| I-3 | GObjectMapping.vala:85 → any from_properties/stringify |
from_element falls through to assignable_to<string>() for int32 |
| I-4 | GObjectMapping.vala:57 (return type) |
Properties interface lacks a setter |