EdgeIngest class

Transport-independent, pure-Dart consumer of the Synheart edge wire contract (watch → phone). This is the phone-side counterpart to the watch producer; it exists so every Flutter app stops re-implementing watch→phone ingest — the canonical parse/validate/dedupe/ack logic lives here once.

It is the Dart member of the cross-platform EdgeIngest matrix and mirrors the Kotlin (ai.synheart.core.edge.EdgeIngest) and Swift (SynheartCore.EdgeIngest) implementations for behaviour parity: same callback names, same dedupe-by-artifact_id, same lowercase-hex SHA-256 hash policy, same absent/out-of-set hsi_version flagging, and the same { "command": "artifact_ack", "artifact_ids": [...] } ACK body shape.

It holds no Flutter import so it compiles and unit-tests standalone (dart test / flutter test). Logging goes through dart:developer log by default, overridable via logSink so a host can route into SynheartLogger without this core taking a Flutter dependency.

Contract mapping (Synheart edge wire contract)

Malformed or unknown bodies are dropped + logged; this class never throws.

Constructors

EdgeIngest({EdgeIngestListener? listener, Set<String> supportedHsiVersions = EdgeIngest.supportedHsiVersions, void logSink(String message)?})

Properties

events → Stream<EdgeEvent>
Broadcast stream of typed edge events (Flutter ergonomics). Fires in lock-step with the listener callbacks.
no setter
hashCode → int
The hash code for this object.
no setterinherited
listener → EdgeIngestListener?
Optional typed callbacks (parity with the Swift Delegate / Kotlin Listener). All hooks are optional.
final
pendingAckIds → List<String>
Ids accepted but not yet drained into an ACK (read-only snapshot).
no setter
runtimeType → Type
A representation of the runtime type of the object.
no setterinherited

Methods

buildAckBody(List<String> artifactIds) → Map<String, dynamic>?
Build the §4/§5 ACK command body for artifactIds: { "command": "artifact_ack", "artifact_ids": [...] }. Returns null when artifactIds is empty (nothing to ack).
dispose() → Future<void>
Release the broadcast stream controller. Idempotent.
drainAckBody() → Map<String, dynamic>?
Drain pending acks and build the ACK body in one step. Null when empty.
drainPendingAcks() → List<String>
Snapshot the ids accepted since the last drain and clear the pending set, in arrival order. Use with buildAckBody (or drainAckBody) to send the ACK on the command channel; ids are considered handed off once drained.
ingest(Map<String, dynamic> body) → EdgeOutcome
Consume one decoded wire body. Routes by type (§1). Never throws: malformed/unknown bodies return Dropped and are logged.
ingestRaw(String json) → EdgeOutcome
Convenience: parse a raw JSON string body and dispatch. Never throws.
noSuchMethod(Invocation invocation) → dynamic
Invoked when a nonexistent method or property is accessed.
inherited
seenCount() → int
Number of distinct artifact ids seen (accepted) so far.
toString() → String
A string representation of this object.
inherited

Operators

operator ==(Object other) → bool
The equality operator.
inherited

Static Methods

sha256Hex(String payloadJson) → String
Lowercase hex SHA-256 of the UTF-8 bytes of payloadJson — identical to the producer's HsiArtifactEnvelope.wrap (and the Kotlin/Swift sha256Hex): sha256(payload_json_bytes).

Constants

commandArtifactAck → const String
poisonPillThreshold → const int
After this many hash mismatches for the SAME artifact_id, the artifact is dead-lettered (hard error + ack-to-discard) so a deterministically-corrupt artifact stops resending forever (contract §5).
seenLruCapacity → const int
Cap of the seen-artifact LRU. Re-acking a long-evicted stray is harmless per contract §5, so eviction is safe.
supportedHsiVersions → const Set<String>
EDGE-WIRE-CONTRACT.md §0 — supported inner HSI payload versions for consumers in this generation. A payload outside this set (or absent) is flagged/logged as producer drift but still surfaced.
typeBioSample → const String
typeHrSample → const String
typeHsiArtifact → const String