synheart_core 0.13.0
synheart_core: ^0.13.0 copied to clipboard
Flutter SDK for the Human State Interface (HSI) 1.3 — unified collection of wearable, behavior, and phone signals with consent-gated cloud upload.
Changelog #
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
Unreleased #
0.13.0 - 2026-09-04 #
Changed — BREAKING #
- Potentially blocking Device Sync APIs now return
Future<Map<String, dynamic>?>and execute their native FFI work on a tracked background isolate:syncCreateSpace,syncGeneratePairing,syncJoinSpace,syncRecoverSpace,syncLeaveSpace,syncListDevices,syncRevokeDevice,syncDeleteSpace, andsyncClearLocalSpace. Callers mustawaitthese methods. Sync operations are serialized per runtime handle so lifecycle mutations, roster reads, andsyncNowcannot race. Fast local status and readiness snapshots remain synchronous.
Added #
- Core v0.24 device identity lifecycle support:
Synheart.reattestDeviceAuth()refreshes attestation without rotating the installed device identity, andSynheart.logoutDeviceAuth()clears that identity and its sync membership. Instance-based hosts can usereattestDevice()andlogoutDevice(). The new native operations require Core v0.24 ABI support. Synheart.ensureDeviceAuthRegisteredOrThrow()preserves typed native failures, including account mismatch, so hosts can route recovery correctly.
Changed #
Synheart.logout()now awaits native device logout before wiping local SDK data and revoking consent. Hosts must await it before removing their own account credentials. Older runtimes retain the legacy local-wipe fallback.reregisterDeviceAuth()is deprecated in favor ofreattestDeviceAuth(). Registration is for first-time setup; repair preserves the existing identity.
Fixed #
- Coalesce concurrent device-auth initialization and serialize native registration, re-attestation, logout, and sync operations per runtime handle.
- Compare restored and configured canonical subjects to avoid treating a restored identity as a different account and registering it again.
- Preserve typed native identity errors instead of masking them with the unsigned development fallback; surface device-auth worker failures explicitly.
- Keep native handles alive until tracked background FFI calls finish during disposal, preventing use-after-free while sync work is in flight.
0.12.0 - 2026-08-24 #
Added #
- Device-signed, non-streaming Syni chat and session APIs under
Synheart.syni?.service: chat, list/get sessions, list messages, and close. - Typed Syni service responses/errors, sticky session continuity, serialized
chat turns, and delivery-unknown signaling for safe reconciliation. The
service requires a native Core runtime that exports the
synheart_core_syni_*ABI (runtime 0.21.0 or newer); older runtimes remain usable and report the service as unavailable.
0.11.1 - 2026-08-20 #
Fixed #
-
Android
compileSdkraised from 34 to 36. The package sat below the compileSdk of the plugins it ships alongside and below Flutter's own default, which made it the odd one out in any consumer's build graph.This does not change the minimum supported Android version:
minSdkstays at 24.compileSdkis what the module is built against, not what it requires at runtime.If your build fails with
requires libraries and applications that depend on it to compile against version 36 or later, upgrading this package alone will not fix it. That check runs per consuming module, and the artifacts naming it are transitive AndroidX dependencies (androidx.health.connect,androidx.browser,androidx.core) pulled in by the sibling plugins. Your app module needs the newer compileSdk:// android/app/build.gradle.kts compileSdk = flutter.compileSdkVersion // 36 on current Flutter
0.11.0 - 2026-08-14 #
Changed — BREAKING #
-
The SDK no longer carries a built-in API host.
ApiEndpoints.defaultBaseUrlwashttps://api.synheart.ai; it now defaults to empty and comes fromSYNHEART_BASE_URL. Publishing a company host inside every copy of the package made it the destination for any build that forgot to name one, including forks and self-hosted deployments.Action: set
SYNHEART_BASE_URL(dart-define, or--dart-define-from-file) on any build that talks to the cloud. Apps that already set it are unaffected.With nothing configured,
api_base_urlis now omitted from the runtime config rather than sent empty, and the native runtime applies its own default.apiBaseUrlConfigured(config)reports whether an explicit origin was supplied.
Added #
HSIAxes.focusQuality,interruptionPressure,interactionModeandhasDigital, parsed fromaxes.digital[]. These are derived from interaction rather than physiology, so they resolve on hardware with no biosignal source. Previously the whole domain was discarded at the parse boundary.SyncNativeError.reason,retryAfterMsanddetail, withisUnsupported/isMisconfigured/isPolicyRefusal.reasonnow appears intoString().doc/INTEGRATION.md— ordered walkthrough from platform prerequisites to first upload.
Fixed #
startSession()reported success when the native runtime failed to open a session. A null native result fell through to the Dart-only path, minting acore_<millis>handle — so the host saw a session id and acollectingstate for a session the runtime never opened, with no HSI and no error. It now throws aStateError; the Dart-only path is reached only when no native runtime is present.flushIfEligiblereported "cloudUpload consent not granted" wheneverhasConsentreturned false. Once cloud is configured the runtime denies every consent type until a consent token is issued, so the message blamed a consent screen that was already correct. It now names the real cause.hasConsentaccepts either consent-key spelling; the Dart fallback path only understood camelCase.- The Syni cloud origin resolved through a hard-coded literal instead of
ApiEndpoints, overridingSYNHEART_BASE_URLfor that one caller.
Hardening release from a full-repo review. No new features.
Released as a MINOR, not a patch: this removes and changes public API, and
^0.10.1 would have resolved a patch automatically into apps that use it.
Headline: the SDK could not initialise at all without cloud credentials.
initialize() returned a null native runtime — no HSI, no consent store, no
storage — and then logged "Initialization complete". Local-only operation, a
documented configuration, has never worked in this line until now.
Fixed #
- Local-only initialization was completely broken.
_configurehardcodedingest: {enabled: true, ...}, so a host with noCloudConfigfailed insidesynheart_core_newwithERR_NOT_CONFIGURED: cloud connector org_id must not be empty when HSI ingest is enabled. The handle came back null and the SDK continued as if nothing were wrong.device_auth.enabledwas hardcoded the same way, making the runtime reject crypto-callback registration with a misleading ERROR for every local-only host. Both are now gated on being configured. Hosts that supply aCloudConfigwith anorgId, or aDeviceAuthConfig, are unaffected — the gates evaluate to the previous values. - A dev build with no dart-defines silently talked to production.
SyncConfig.baseUrldefaults toApiEndpoints.defaultAuthBaseUrl, a const alias for theSYNHEART_AUTH_BASE_URLdefine, which is empty unless set. The empty value made the runtime fall back to its own hardcoded production URL.api_base_urlis now resolved explicitly throughresolvedAuthBaseUrl. Same endpoint, no longer by accident. - Packaging: the published archive was 70 MB and contained the proprietary
native runtime.
.pubignorereplaces.gitignorefor publishing, so the.gitignorerules coveringios/SynheartCoreRuntime.xcframework(a symlink the podspec creates into the consumer app — pub follows it) andexample/synheart/vendor/did not apply. The archive carried both framework slices plus their dSYM DWARF blobs. Now ~610 KB, with a CI check that fails the publish job if native artifacts or an oversized archive reappear. flushUploadsblocked the UI isolate. The native call performs its HTTP round-trip underblock_on; it now runs on a background isolate like every other network-touching FFI call in the bridge.- A failed
initialize()could never be retried. The init completer was left in place after an error, so every subsequent call received the same failed future — a transient cause was permanent short ofdispose(). - HSI reached only the
onHsicallback, neveronHSIUpdate/onStateUpdate, for hosts pushing throughpushWearHr/pushVendorHrv. Both producers now share one consent-gated delivery path. (The long-standing claim that the native callback "doesn't fire on iOS" no longer holds against runtime 0.19.2 — verified on device — which is why that path needs the duplicate suppression below.) - Duplicate HSI delivery.
ingest_batch_jsonBOTH broadcasts on the runtime'sstate_tx— which drivessetHsiCallback— and returns the same payload to Dart. With both producers wired to one delivery path, every window completed by a per-event push arrived twice: doubleonHSIUpdate/onStateUpdate, double session-buffer entries, double downstream counters. Now suppressed byHsiDeliveryDeduperonmeta.ids.hsi_id(RFC-IDENTITY-0001), the same key the runtime's own ingest connector dedupes on. It keeps a bounded set of recent ids rather than a single last-id slot, because the producers do not always interleave asA, A— the sync return and the async callback can be separated by a background-tick window, givingA, B, A. A payload without an id is delivered rather than dropped: losing a window is worse than repeating one. - A session could start with nothing able to collect. The runtime path of
startSession()performed none of the checks the Dart fallback path did.startSession()now requires at least one enabled feature whose matching consent is granted — both halves of a pair. Consent alone is not enough: enabling onlywearConfigwhile granting onlybehaviorleaves wear permitted-but-not-enabled and behavior enabled-but-not-permitted, so nothing collects. Cloud upload, vendor sync, research and syni never qualify; they govern what happens to data once gathered. - Session buffers grew without bound.
getSessionHsiWindows()andgetSessionWearSamples()retained every entry for the session's life (24h by default) and cleared only on the nextstartSession(). Both are now capped ring buffers — seemaxSessionHsiWindows/maxSessionWearSamples. isSessionRunningignored the runtime. It returned the Dart module flag; the runtime's ownis_runningwas never read anywhere in the SDK. A session ending natively left hosts reporting "collecting" indefinitely. It now prefers the runtime, falling back to the Dart flag when the bridge is absent.- Configuration errors named a field but not a fix.
validate()threw bare assertions like "appId must not be empty". The messages now say what to set, what a good value looks like, and why — including that passinguserId:toinitialize()does not populateconfig.subjectId, which is the most common way to hit the error. Failures are also logged before the throw, since hosts commonly catch and render a short toast that discards the detail. - One failing module killed the whole session, and orphaned a native one.
ModuleManager.startAll()awaitedmodule.start()unguarded, so the first throw ended the loop and every later module was skipped —initializeAll()andstopAll()were already per-module resilient. Modules start wear-first, so on an iOS build without the HealthKit entitlement nothing started rather than everything-but-biosignals. Separately,startSession()opened the native session before starting modules, so a throw escaped leaving a session the host did not know about; the next call then gotSessionActive, returned null, and fell through to the Dart-only path with acore_<ts>handle for a session the runtime never opened.startAll()is now resilient and reportsmoduleId -> error, andstartSession()rolls the native session back on failure. runtimeDiagnostics()['lastQuality']always reported0.0; it read a native symbol the runtime has never exported outside the edge variant. Removed.
Added #
runtimeDiagnostics()['missingSymbols']— optional native symbols the loaded runtime does not export. ~20 lookups previously failed silently, so a runtime one release behind disabled recovery scores, readiness, breathing, backfill, priority resolution, data deletion, research enrolment, sync-space management and cloud HSI fetch with nothing in the logs. Each miss now logs once, naming the symbol.runtimeDiagnostics(probeAll: true)andprobedSymbols. Optional bindings resolve lazily, so a diagnostics screen readingmissingSymbolsbefore anything used those features reports an empty list and looks healthy while having checked nothing.probeAllforces a full audit. Note the list covers only the guarded bindings: most lab ABI calls are bound eagerly and throw on first access when absent — checkisLabAvailablebefore using them.HSIState.parseError/hasParseError— a parse failure previously returned empty axes, indistinguishable from a window the engine had not populated.buildRuntimeConfigMap(internal) — one source of truth for the JSON handed tosynheart_core_new, replacing three drifting copies, and unit-testable without a native runtime.
Changed #
- BREAKING (direct
CoreRuntimeBridgeusers only):CoreRuntimeBridge.flushUploads()returnsFuture<Map<String, dynamic>?>. Addawait.Synheart.ingestioncallers are unaffected. Synheart.deleteCloudData()now throwsUnsupportedError. It never deleted anything — it logged twice and returned — so a host could have wired it to a "delete my cloud data" control and shipped a promise the SDK did not keep. UserequestDataDeletion()andwipeLocalData().getSyncStatus()reflects real engine/config state instead of a hardcodedfalse.WearModule(useSynheartWear: false)now yields no sources instead of a mock.- Error messages and doc comments no longer cite "native runtime 5.4.0+"; that
scheme does not match the runtime's actual
0.xversioning. They name the remedy (synheart install runtime) instead.
Deprecated #
The native runtime removed bundle-secret configuration as a security fix, and these were never forwarded to it. All are scheduled for removal in 0.12.0.
SynheartConfig.capabilityToken,SynheartConfig.capabilitySecretCloudConfig.apiKey,ConsentConfig.appApiKeySynheart.deleteCloudData(),Synheart.getSyncStatus()Synheart.runtimeBaselineSummary(duplicate ofruntimeBaselinesJson)WearModule(useSynheartWear:)
Removed #
MockWearSourceHandler. It synthesised heart rate, HRV, RR intervals and sleep stages fromRandom(). Those samples were indistinguishable from real ones downstream and fed the runtime's longitudinal baselines, so a host passinguseSynheartWear: falsesilently corrupted the user's on-device reference ranges with invented numbers.WearModule(sources:)already accepts an injected handler, which is the supported way to supply a fake.HsiWindowArtifact,TombstoneArtifact,CapabilityTokenFetcher— no producers, consumers, or implementations.ios/Runner/(host-app scaffolding in a plugin package) andios/synheart_ffi_symbols.txt(an ld64 keep-list referenced by nothing and 68 symbols stale — obsolete since the runtime became a dynamic framework).- A no-op
loadCapabilityToken('{}', secret)call that could never load a capability.
Example app #
Rebuilt as a lean reference: 6,555 lines across 17 files and three competing entry points, down to ~1,600 across 8.
- Consent now uses the canonical runtime editable-form flow
(
consentGetEditableFormTyped→consentSubmitFormTyped→consentEffectiveStateTyped). The old app drove the legacy Dart-side helpers thatSDK_CONSENT_FLOW.md§7-G lists as superseded. - No longer reaches into
package:synheart_core/src/. - Local-only by default — runs with no credentials.
- Never fabricates biosignals, and distinguishes sources that feed the runtime (wear, behavior) from ones that only collect locally (phone context).
example.dart, the snippet pub.dev renders, previously omitted the requiredappId/subjectIdand could not run.
0.10.1 - 2026-07-31 #
Added #
Synheart.pushRrBatch(...)— push a batch of RR intervals anchored to a single timestamp, instead of one call per interval.- Buffered runtime logging (
synheart_core_init_logging_buffered+ log drain) so runtime diagnostics survive a crash. Falls back gracefully when the vendored runtime does not export the symbols.
0.10.0 - 2026-07-29 #
Changed #
- Reorganized and updated the SDK documentation to match the current public API, configuration, native runtime layout, platform requirements, and examples.
0.9.0 - 2026-07-21 #
Added #
Synheart.recordStudyConsent(...)— record a durable study-consent record via the core runtime (newsynheart_core_record_study_consentFFI). Includes a matchingstudyConsentPathendpoint constant.
0.8.2 - 2026-07-07 #
Fixed #
- Sync the
synheartCoreVersionconstant with the package version; 0.8.1 inadvertently shipped it as0.8.0.
0.8.1 - 2026-07-07 #
Changed #
- Widen the
synheart_wearconstraint to>=0.4.0 <0.6.0so the 0.5.x line resolves.synheart_wear0.5.0 is additive and backward compatible (adds an optional per-request auth-header signer for the cloud providers), so the re-exported workout and provider surface is unchanged and no code updates are required.
0.8.0 - 2026-06-29 #
Added #
Synheart.rebindSubjectId(subject)— rebind the subject when the signed-in identity changes: the native runtime atomically re-points consent and the cloud connector, then the SDK syncs its subject and re-mints cloud consent if needed — without a full dispose/reinit.
Changed #
- The native runtime is now the source of truth for the canonical subject id.
Synheart.subjectIdprefers the runtime's value (captured after init) over the configured one, andconsentTokenSubjectStalecompares against it. - Subject FFI degrades softly: on a runtime build that predates the new symbols, subject sync/rebind fall back to the configured value instead of failing init.
0.7.3 - 2026-06-28 #
Changed #
- Bump
synheart_authto^0.1.7. This adds a 30-second timeout to Android Play Integrity device attestation (and pulls in the matching native-SDK timeouts). A stalled attestation — e.g. anIntegrityServicebind that never resolves on a device without Play Store — now fails fast and falls through to the existing unsigned-capabilities path instead of leaving device registration hung.
0.7.2 - 2026-06-28 #
Fixed #
- Cloud consent token is kept scoped to the current subject. A consent token
is issued for a specific
subjectId; reusing one issued for a previous subject (e.g. a different signed-in account, or a token minted before the account was known) would attribute uploads to the wrong subject.ensureCloudConsentReadynow treats a token whoseuser_idclaim differs from the currentsubjectIdas not-ready and reissues it for the current subject, and on (re)init the SDK self-heals by reissuing under the current subject so uploads resume immediately rather than waiting for the next consent change. Best-effort and idempotent — no behavior change for a stable, matching subject. AddsSynheart.consentTokenSubjectStale().
0.7.1 - 2026-06-25 #
Fixed #
syncNownow runs on a background isolate. It previously executed the blocking native sync (a network round-trip) inline on the calling isolate, so a slow or stalled sync froze that isolate until it completed or timed out. Routed through the sameIsolate.runoffload the other network-bound calls use; the publicSynheart.syncNow()already returned aFuture, so this is not an API change.
0.7.0 - 2026-06-17 #
Added #
HSIAxes.stress— typed accessor for the engine's multimodal stress reading (engine v0.10.0; HSI 1.3axes.affective[].stress). Hosts get a named field instead of digging throughrawJson. Resolves tonullon the legacy 1.2 path that never carried it. Parity with the Kotlin/Swift bindings.EdgeIngest— canonical phone-side consumer of the Synheart edge wire contract (watch → phone). Pure Dart (no Flutter import), unit-tests underdart test/flutter test. Parseshr_sample/bio_sample/hsi_artifact/ session events and, for artifacts, dedupes byartifact_id, verifiespayload_hash_sha256== sha256(payload_json), validates the innerhsi_versionagainst the supported set, and produces theartifact_ackbody. Public surface:- Sealed
EdgeEventfamily (HrEvent | BioEvent | ArtifactEvent | SessionEventWrap) plus the typed models (HrSample,BioSample,HsiArtifact,Accel) and theEdgeOutcomeresult family. - Reactive
eventsbroadcastStream<EdgeEvent>, emitting in lock-step with the optionalEdgeIngestListenercallbacks (parity with the KotlinSharedFlowand Swifteventspublisher). - ACK helpers
drainPendingAcks()/buildAckBody(...)/drainAckBody(). dispose()— Flutter-only lifecycle that closes the broadcastStreamController(idempotent). The Kotlin/Swift hot streams have no equivalent. Hosts usingEdgeIngest.eventsmust calldispose()when done.- The Kotlin-only
onUnsupportedHsiVersion/onHashMismatchhooks are folded into theEdgeOutcomereturn value (ArtifactHashMismatch) and logging, plus the optional poison-pill / dead-letter hookEdgeIngestListener.onPoisonPill(artifactId, expected, actual, attempts). - Delivery hardening (the watch outbox is delete-on-ACK):
- Duplicate re-ack — a duplicate
artifact_idis not re-surfaced (ArtifactDuplicate) but is re-queued for ACK, so a lost ACK no longer makes the watch resend forever. - Bounded dedupe set — the seen-artifact set is a bounded LRU
(
seenLruCapacity), keeping memory flat over a long-lived process. - Poison-pill dead-letter — an artifact that fails hash verification
poisonPillThreshold(3) times for the same id is dead-lettered (ArtifactDeadLettered, theonPoisonPillhook, and ack-to-discard) so a deterministically-corrupt artifact stops blocking the outbox.
- Duplicate re-ack — a duplicate
- Sealed
Notes #
- Requires the Synheart Runtime. This package is a thin Flutter wrapper; the
on-device logic lives in the Synheart Runtime native binary, which is installed
separately via the Synheart CLI (
synheart install runtime). See the README's "Native Runtime Setup".
0.6.3 - 2026-06-07 #
Added #
Synheart.requestStudyDataDeletion({bool dryRun}): request erasure of the data the participant contributed to their study for this app — the deletion the consent copy promises alongside withdrawal. No identifiers are passed; the participant and app come from the device's signed cloud credential.dryRunreturns an inventory preview without deleting; a real request is accepted asynchronously and carries arequest_id. Idempotent.
0.6.2 - 2026-06-07 #
Added #
- Research-study enrolment API:
Synheart.enrolResearchStudy(...),Synheart.validateResearchStudyCodes(...), andSynheart.withdrawResearchStudy(). Enrolment rides the device's signed cloud credential — no tokens are handled by the app. Withdrawal is idempotent. DeviceAuthConfig.packageName: the app's package / bundle id, passed through so device registration can be bound to the app. Optional (defaults to empty).
0.6.1 - 2026-05-24 #
Added #
- Consent surface for Syni.
ConsentForm.syni, the parameterSynheart.grantConsent(..., syni: bool), andConsentSnapshot.syniare now exposed end-to-end so hosts can track and persist the user's consent for on-device or cloud LLM processing alongside the other channels. Backward compatible —synidefaults tofalseeverywhere so existing callers compile unchanged. - Canonical channel iteration. New
ConsentTypeMetaextension onConsentTypeexposeswireKey,displayName,valueOn(typed accessor forConsentEffectiveState), andvalueOnForm(typed accessor forConsentForm). PlusConsentEffectiveState.toChannelMap()/ConsentForm.toChannelMap()andConsentForm.fromChannelMap(...). Hosts can now iterateConsentType.valuesrather than hardcoding each channel — adding a new channel becomes a single change in the SDK enum plus its metadata; consumers pick it up automatically. Synheart.consentChanges— public broadcast stream ofConsentSnapshots. Emits on any grant or revoke so hosts that render consent in multiple places (e.g. a profile counter alongside a consent screen) can stay in sync without pollingconsentEffectiveStateTypedor re-dispatching reads.Synheart.syni.bindPersona(SyniPersona)(passthrough topackage:syni's newSyniAgent.bindPersona). Cloud chat needs only a persona plus a configured cloud client; hosts that pickSyniExecutionMode.cloudOnlycan call this once instead of runninginstall()purely to attach a persona. Requiressyni: ^0.3.2.
Changed #
synidependency bumped to^0.3.2(carriesbindPersona+ the fix that letscloudOnlychat run without a local install).ConsentEffectiveState.hasAnyGrantnow includessyni— previously the flag was tracked through the rest of the state but missing from this convenience getter.
Fixed #
consentSubmitFormTypednow propagates the form'ssynivalue to the runtime via the per-type grant/revoke FFI when the runtime's form parser doesn't carry the key directly. Without this a host settingsynion the typed form would see the other channels update butsynistay at its previous value.
0.6.0 - 2026-05-23 #
Added #
Synheart.configure()now wires Syni's hybrid router to the cloud — it builds theSyniCloudConfigand authenticates Syni cloud-chat requests as device-attested cloud requests.Synheart.synicloud chat needs no extra host-app setup.DeviceAuthProvider.signUrl— device-attests an absolute URL (no base-URL prefixing);signRequestnow delegates to it.
Changed #
- Bumped the
synidependency to^0.3.0, whoseSyniCloudConfigreplaced the staticauthTokenwith the request-awareauthHeaders. Consumers building aSyniCloudConfigdirectly need to update to the new shape; consumers usingSynheart.configure()are unaffected. - The SDK-side default
SyniCloudConfig.authHeadersclosure lazily device-attests each request — works whether device auth resolves through the DartDeviceAuthProvideror the native runtime.
0.5.3 - 2026-05-21 #
Fixed #
- iOS: the native runtime can now start on the ONNX-backed tiers.
The podspec force-loads ONNX Runtime into the host app binary. The
runtime framework resolves
OrtGetApiBaseat load time, andonnxruntime-cships ONNX as a static archive — nothing in the host app references it directly, so the linker dead-stripped the whole archive and the runtime crashed on first use. Consumers no longer need any per-app linker configuration; depending onsynheart_coreis enough. iOS build configuration only — no Dart API change.
0.5.2 - 2026-05-20 #
Changed #
- Bumped the
synheart_behaviordependency to^0.4.0. The behavior SDK is now an event producer — a session summary may arrive with nobehavioralMetrics.BehaviorSessionResults.fromSummaryalready treats those metrics as optional and defaults each to 0 when absent.
0.5.1 - 2026-05-19 #
Changed #
- Scrubbed internal references from the public package surface — dartdoc comments, section header dividers, and barrel-file comments no longer cite internal identifiers or internal documentation paths. Comments and doc strings only; no code or API changes.
0.5.0 - 2026-05-19 #
Added #
- Cross-device baseline sync.
Synheart.syncCreateSpace,syncJoinSpace,syncGeneratePairing,syncStatus— pair two devices and replicate baselines in either direction with end-to-end encryption. - Offline export / import.
Synheart.baselineExportOffline(passphrase)produces an encrypted.srm.synheartbundle (6-word passphrase, never sent to any server).baselineImportOffline(passphrase, bytes)returns{imported, skipped, errors}. Lets users move baselines across devices without the cloud. BaselineLocalHydratorfacade —wireLocalHydrator(...)is the new entry point for hydrating baselines into the SDK from device-local sources.
Fixed #
- iOS native runtime loading. Replaced the relative-path framework
open with
DynamicLibrary.process(), which resolves symbols from the auto-loaded embedded framework. The previous form silently failed and surfaced only as a generic "Native runtime not loaded" warning. - Runtime initialization failures are now visible. When the native runtime rejects a configuration on iOS, the host now sees the actual reason instead of a silent fallback.
Changed (breaking) #
wireCloud(...)removed. Migrate towireLocalHydrator(...). The separate baseline cloud uploader is retired; baselines now ride the cross-device sync path.- iOS install model. Podspec moves to a vendored dynamic framework
installed via the synheart CLI (
synheart install runtime), rather than a static library bundled with the package. Consumer Podfile gets aprepare_commandsymlink pointing at the CLI-installed vendor dir.
Other #
- Logging hygiene: consent change events emit one structured line instead of an 8-line block; native bridge startup is silent on the happy path and warns only on real failures; baseline scoring no longer dumps the full engine input JSON.
Requires #
- The matching Synheart native runtime release (v0.10.0).
0.4.0 - 2026-05-16 #
Added #
Synheart.syni— gated Syni client surface with install lifecycle + chat + chatStream, delegating topackage:syni'sSyniAgent.SyniContextBuilder— projects this SDK's HSI (live state + stored session history) into the runtime's conditioning contract. HSI-version-agnostic; iterates whatever axes the runtime emitted.- Trivial-message context skip — for greetings / acks, ship only the persona prefix instead of full HSI + history (~30–50% prefill reduction on short messages).
SyniSpecPersonare-exported frompackage:syniso consumers canSyniSpecPersona.load('focus.coach.v1')for canonical prompts.Synheart.{closeOrphanSession, sweepOrphanSessions}— host-side orphan cleanup. Sweep on app start closesstate='active'sessions older than 6h that the runtime never finalized (force-kill, OS reclaim, sudden reboot).Synheart.configureSyniCloud(...)— injects cloud client config;Synheart.syni.hasCloud+ per-callSyniExecutionModeroutes between local and cloud.- Cold-start restore —
SyniAgent.restoreInstallIfReadychecks disk before download flow; consumers don't re-prompt for an install when the model is already cached.
Changed #
synidependency switched frompath: ../syni-flutterto^0.1.0(now published on pub.dev).SessionSummaryArtifactparser rewritten to match the runtime's actual wire format (nestedheaderblock,started_at_ms/ended_at_mskeys, nullable per-axis aggregates, structuredSessionAggregateskeyed by axis name).SessionRecordnow readsstarted_at_msfrom the FFI (was missing — surfaced as 1970-01-01 timestamps in downstream digests).stateandendedAtUtcexposed for the sweep's filter.
0.3.0 - 2026-05-14 #
Added #
Synheart.labReenqueueSession(String sessionJson)— replay a previously-finalized lab session payload through the cloud connector. Useful when the initial upload was dropped on a 4xx (typically a cloud schema mismatch — the runtime removes those rows from the in-memory upload queue). Host reads the persisted JSON from app-side storage and passes it back.Synheart.isLabReenqueueAvailable— feature-detect whether the linked runtime binary exports the symbol (engine v0.8.1+).LabReenqueueResultenum — mirrors the FFI return codes (queued,researchNotAllowed,cloudNotConfigured,parseError,invalidArgument,unsupported).CoreRuntimeBridge.labReenqueueSessioninstance method + companionisLabReenqueueAvailablegetter; new_LabReenqueueC/_LabReenqueueDarttypedefs inffi_bindings.dart. Lookup is optional so older runtime binaries (pre-v0.8.1) keep loading without errors.- Customer-facing data deletion API (
requestDataDeletion,getDataDeletion,listDataDeletions) andDataDeletion*models for GDPR Article 17.
Runtime compatibility #
- Requires the Synheart native runtime v0.8.1+ for
labReenqueueSessionto actually invoke the FFI. Older binaries returnLabReenqueueResult.unsupported.
0.2.0 - 2026-05-09 #
Added #
- HSI 1.3 envelope parsing in
HSIStateandHSIPayload. Producers emit the closed 5-axis domain set (physiological,kinematic,digital,cognitive,affective) with deterministic UUIDv5hsi_id. The SDK now parses the new shape and exposes the digital readings (focus_quality,interruption_pressure,interaction_mode) alongside the existing physiological / cognitive / affective fields. The 1.2 wire shape is still accepted as a fallback; consumers should not rely on that path long-term.
Fixed #
BehaviorModule._convertSynheartEventnow forwardsBehaviorEventType.app_switchto the runtime instead of dropping it via the default-arm. The runtime needsapp_switchto detect notification responses (an app switch shortly after a notification) and to anchor session boundaries between foreground events of different apps. Without this forward, the digital readings on the HSI 1.3 envelope (axes.digital[]—focus_quality,interruption_pressure,interaction_mode) were silent on iOS and Android.
0.1.1 - 2026-05-08 #
Changed #
- Bumped
synheart_authdep to^0.1.2. Picks up the Maven Centralai.synheart:synheart-auth:0.1.1upgrade (clock-skew auto-apply, register/rotate race fix, HTTP timeouts, audit-log PII redaction).
0.1.0 - 2026-05-08 #
First public open-source release on pub.dev. The SDK is now a thin FFI shell over the native Synheart Runtime — storage, crypto, sync, consent, the artifact pipeline, the cloud connector, and SRM live in the runtime, and this package exposes them through a Dart surface.
The native runtime is installed via the
Synheart CLI
(synheart install runtime); see the README.
This release consolidates the OSS-launch refactors plus the
breaking change to processVendorEvent.
Breaking #
Synheart.processVendorEvent(...)andWearModule.processVendorEvent(...)now returnFuture<CanonicalWearableEvent?>instead ofFuture<void>. The previous void return discarded the canonical event the vendor payload was mapped to. Mirrors the Swift and Kotlin counterparts.- Removed
CloudConfig.tenantId. Drop the argument —app_idis the only identifier the SDK sends. - Removed
CloudConfig.hmacSecret. Request signing uses the device key;authProvideris now optional. - Removed
InvalidTenantError. ConsentFormshape is flat (profile_id,biosignals,phone_context,behavior,consent_tier,allow_cloud,allow_research,allow_vendor_sync) to mirror the runtime. Hosts that previously builtcategories[] → channels[]structures must migrate to the flat form.- Consent type strings are snake_case at the runtime boundary
(
phone_context/cloud_upload/vendor_sync). Flutter still accepts camelCase on its public API. - Removed
ConsentCategoryandConsentChanneltypes — channel-level truth is owned by the runtime.
Added #
Synheart.rawRamenEvents— broadcastStream<RamenEvent>re-exported fromsynheart_wear, surfacing every RAMEN event with its capability-flavoreddeliveryHint.app_id/user_idfrom the activestartVendorSyncconfig are stamped onto each event soRamenEventDispatchercan drive REST pulls without extra plumbing.- Offline-first consent FFI surface:
consentConfigureCloud,consentGetEditableForm,consentSubmitForm,consentEffectiveState,consentStatus,consentNeedsTokenRefresh,consentClearStored. Local choice is persisted immediately; cloud sync is best-effort. - Typed consent form accessors:
consentGetEditableFormTyped()andconsentSubmitFormTyped({ required ConsentForm form, … }). - Consent coverage for
vendorSyncandresearchthroughhasConsent()/getConsentStatusMap()and the granular grant API. - Durable
data_dirfor the native runtime viapath_provider. Fixes SRM snapshots and the artifact SQLite landing instd::env::temp_dir()(cleaned up by the OS), which broke baseline persistence across app restarts.
Changed #
setStreamCallbackauto-route now skipsdelivery_hint == "ping"events. Their inlinepayload_jsonis empty by design (Garmin / Oura / Fitbit only send a notification), so apps must subscribe torawRamenEventsand useRamenEventDispatcherto fetch the full record before re-feeding the canonical pipeline.- README rewritten to match the actual public API.
- Dependencies on sibling Synheart SDKs are now hosted on pub.dev
(
synheart_wear ^0.4.0,synheart_session ^0.2.0,synheart_behavior ^0.3.0,synheart_auth ^0.1.1) instead of git refs.