From 5a5dc57d121178eaf07f93806c69280af74bc7fd Mon Sep 17 00:00:00 2001 From: Karl Wikman Date: Sat, 10 Oct 2026 14:45:24 +0200 Subject: [PATCH 1/2] Add automatic GrowthTrack session sync and website integration Browser PKCE approval enables same-account sync of new real finished sessions. Keep prior/disconnected history local, with encrypted queues, stable IDs, recorded-phase technique mapping and persisted network retry jobs. Add optional onboarding, developer handoff and verification, and the compact website section with the three user-approved screenshots. Validation: 2,140 JVM tests passed, unchanged wiring and migration passed, plus Java 17 syntax, manifest, website JS and whitespace checks. Android API compilation, lint and device integration still require verification. --- AGENTS.md | 5 +- ARCHITECTURE.md | 8 +- README.md | 6 +- SAFETY.md | 6 +- app/src/debug/AndroidManifest.xml | 5 +- app/src/main/AndroidManifest.xml | 24 +- .../org/openpump/GrowthTrackActivity.java | 335 ++++++++++++++ .../openpump/GrowthTrackCallbackActivity.java | 35 ++ .../java/org/openpump/GrowthTrackHttp.java | 58 +++ .../java/org/openpump/GrowthTrackManager.java | 122 +++++ .../java/org/openpump/GrowthTrackStore.java | 72 +++ .../org/openpump/GrowthTrackSyncService.java | 35 ++ .../java/org/openpump/SessionActivity.java | 21 +- .../java/org/openpump/SettingsScreen.java | 6 + .../main/java/org/openpump/SetupScreen.java | 30 ++ app/src/main/java/org/openpump/Store.java | 1 + .../main/java/org/openpump/SummaryScreen.java | 5 + core/src/main/java/org/openpump/AsRun.java | 10 +- .../java/org/openpump/GrowthTrackClient.java | 407 +++++++++++++++++ .../org/openpump/GrowthTrackOnboarding.java | 13 + .../org/openpump/GrowthTrackProtocol.java | 127 ++++++ .../java/org/openpump/GrowthTrackSession.java | 172 +++++++ .../java/org/openpump/GrowthTrackTiming.java | 73 +++ core/src/main/java/org/openpump/Model.java | 26 +- core/src/test/java/Migrate.java | 36 ++ .../org/openpump/GrowthTrackAutoSyncTest.java | 116 +++++ .../openpump/GrowthTrackIntegrationTest.java | 430 ++++++++++++++++++ .../openpump/GrowthTrackOnboardingTest.java | 37 ++ docs/growthtrack-developer-handoff.md | 136 ++++++ docs/growthtrack-developer-message-draft.md | 19 + docs/growthtrack-verification.md | 44 ++ .../growthtrack/GTdoseresponsegirthwork.png | Bin 0 -> 60276 bytes .../growthtrack/GTdoseresponselengthwork.png | Bin 0 -> 53865 bytes docs/site/assets/growthtrack/GTscreen.png | Bin 0 -> 154386 bytes docs/site/index.html | 40 +- docs/verification.md | 2 + 36 files changed, 2441 insertions(+), 21 deletions(-) create mode 100644 app/src/main/java/org/openpump/GrowthTrackActivity.java create mode 100644 app/src/main/java/org/openpump/GrowthTrackCallbackActivity.java create mode 100644 app/src/main/java/org/openpump/GrowthTrackHttp.java create mode 100644 app/src/main/java/org/openpump/GrowthTrackManager.java create mode 100644 app/src/main/java/org/openpump/GrowthTrackStore.java create mode 100644 app/src/main/java/org/openpump/GrowthTrackSyncService.java create mode 100644 core/src/main/java/org/openpump/GrowthTrackClient.java create mode 100644 core/src/main/java/org/openpump/GrowthTrackOnboarding.java create mode 100644 core/src/main/java/org/openpump/GrowthTrackProtocol.java create mode 100644 core/src/main/java/org/openpump/GrowthTrackSession.java create mode 100644 core/src/main/java/org/openpump/GrowthTrackTiming.java create mode 100644 core/src/test/java/org/openpump/GrowthTrackAutoSyncTest.java create mode 100644 core/src/test/java/org/openpump/GrowthTrackIntegrationTest.java create mode 100644 core/src/test/java/org/openpump/GrowthTrackOnboardingTest.java create mode 100644 docs/growthtrack-developer-handoff.md create mode 100644 docs/growthtrack-developer-message-draft.md create mode 100644 docs/growthtrack-verification.md create mode 100644 docs/site/assets/growthtrack/GTdoseresponsegirthwork.png create mode 100644 docs/site/assets/growthtrack/GTdoseresponselengthwork.png create mode 100644 docs/site/assets/growthtrack/GTscreen.png diff --git a/AGENTS.md b/AGENTS.md index e07a77b..0eec6df 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -29,7 +29,10 @@ a run stops as safety-critical (see SAFETY.md). 1. Never weaken, skip or delete a check to make a change pass. Fix the code or explain. 2. Never bypass the stop/vent confirmation, the ceiling clamp, or the START gate. 3. Saved-data changes must keep old files loading; add a `Migrate` case. -4. No network code in release builds; no data leaves the phone unless the user opts in. +4. GrowthTrack is the optional release network integration: public-client PKCE, secure tokens, + browser approval of an account connection enables automatic transfer of every new real + finished session. Disconnected and unmarked earlier history never upload; pending work + stays bound to its original grant. No per-session opt-in is required. Diagnostic log upload remains debug-only. 5. UI: Java-built views (no XML layouts), 48 dp minimum touch targets, colours and sizes from `Look`, dialogs through `Ui.dialog()` + `Ui.dress()`. 6. New logic that can be pure goes in `core/` with a JUnit test. diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 0b7f125..6faf50d 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -9,8 +9,11 @@ so that most of the logic can be understood, changed and tested without a phone. app/ Android shell — screens, the foreground run service, Bluetooth (PumpLink), notifications, widget, file providers. Depends on core. app/src/debug is in debug builds only: the diagnostic console (MainActivity, - its own Bluetooth connection and raw frames) and the INTERNET permission its - log upload needs. The release APK carries neither. + its own Bluetooth connection and raw frames) and cleartext diagnostic log upload. + The release APK carries no diagnostic console. Optional + GrowthTrack has INTERNET/ACCESS_NETWORK_STATE in main. Browser approval enables + automatic same-account session sync, with a separate Activity/task, encrypted + no-backup state and persisted network-constrained retry jobs. core/ Plain Java — no Android imports, enforced by ArchitectureTest. Training plan, session engine, data model and persistence, share codes, progress/analytics, and the ZD21 pump protocol (Proto) with its simulator; @@ -25,6 +28,7 @@ is being removed — see the roadmap). | You want to… | Look in | |---|---| +| Change the optional GrowthTrack connection | `core/./GrowthTrack*.java`, `app/./GrowthTrack*.java`; [developer handoff](docs/growthtrack-developer-handoff.md) | | Change a screen | `app/src/main/java/org/openpump/*Screen.java`, `SessionActivity.java` | | Change the training plan | `core/…/Plan.java`, `RxBuild.java`, `Mint.java`, `Deload.java` | | Change how a run is recorded or saved | `core/…/AsRun.java`, `RunEdit.java`, `Session.java` | diff --git a/README.md b/README.md index d1fce38..2e001d0 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@

Licence: AGPL-3.0 Android 7.0 or newer - No account, no server, no analytics + Optional GrowthTrack connection, automatic session sync Latest release: 0.10.0

@@ -36,7 +36,9 @@ The goal is bigger than any one feature: the best app there is to pump, track yo - **Works with** the Epic Hydro PE Pump over Bluetooth. It's the one pump checked on real hardware so far. Others can be added by the people who own them. - **No pump?** Practice mode runs everything against a simulated pump, and marks those runs as simulated. -- **Your data** stays on your phone. No account, no server, no analytics. +- **Your data** stays on your phone until you export/share it or connect your own GrowthTrack account. Connection enables automatic sync of new real finished sessions; disconnected and preconnection history does not send. Measurements, photos and notes are excluded from this optional connection. + + This feature branch adds the GrowthTrack connection and read-only routine previews. Android build/device validation is still required before release; see the [developer handoff](docs/growthtrack-developer-handoff.md) and [verification record](docs/growthtrack-verification.md). - **You need** Android 7.0 or newer. - **It's free** and open source, under AGPL-3.0. diff --git a/SAFETY.md b/SAFETY.md index 7a25e71..b33816f 100644 --- a/SAFETY.md +++ b/SAFETY.md @@ -53,7 +53,11 @@ own risk — the full terms, including the warranty and liability disclaimer, ar or leaving a screen never orphans a pump under pressure. 6. **Practice mode is never mistaken for a pump.** Simulated runs are labelled everywhere and filed as simulated. -7. **Nothing is sent on the user's behalf.** No network traffic in release builds. +7. **Connection is optional and authorizes automatic sync.** GrowthTrack linking uses + the system browser; every new real finished session transfers to that grant. Disconnected + and preconnection history never sends. No measurements, photos or notes are uploaded. + Account-bound encrypted pending work retries automatically. Data transfer owns no pump connection and + cannot start hardware. The diagnostic log console remains debug-only. These are enforced by code review, by `WiringCheck` (static checks over the source), by the self-test, and — as pump drivers arrive — by a conformance kit every driver must pass. diff --git a/app/src/debug/AndroidManifest.xml b/app/src/debug/AndroidManifest.xml index c4a1f5a..62530b7 100644 --- a/app/src/debug/AndroidManifest.xml +++ b/app/src/debug/AndroidManifest.xml @@ -7,8 +7,9 @@ builds developers make themselves. SessionActivity reaches it by class name, and only when BuildConfig.DEBUG says it is here. - INTERNET and cleartext traffic are the console's log upload to a developer's own - computer. Release builds never hold the INTERNET permission. --> + Cleartext traffic is for the console's log upload to a developer's own computer. + INTERNET is also declared in main for optional GrowthTrack account connection and automatic session sync. + Release builds forbid cleartext and carry no diagnostic console. --> diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 0dbe550..db16f21 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,6 +1,10 @@ + + + + @@ -161,7 +165,8 @@ android:icon="@mipmap/ic_launcher" android:roundIcon="@mipmap/ic_launcher_round" android:theme="@android:style/Theme.DeviceDefault.NoActionBar" - android:allowBackup="false"> + android:allowBackup="false" + android:usesCleartextTraffic="false"> + + + + + + + + + + + + diff --git a/app/src/main/java/org/openpump/GrowthTrackActivity.java b/app/src/main/java/org/openpump/GrowthTrackActivity.java new file mode 100644 index 0000000..c47acce --- /dev/null +++ b/app/src/main/java/org/openpump/GrowthTrackActivity.java @@ -0,0 +1,335 @@ +package org.openpump; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.KeyguardManager; +import android.content.DialogInterface; +import android.content.Intent; +import android.os.Bundle; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.CheckBox; +import android.widget.LinearLayout; +import android.widget.ScrollView; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import org.json.JSONArray; +import org.json.JSONObject; + +/** Optional account connection and automatic-sync status. This activity never owns a pump link. */ +public final class GrowthTrackActivity extends Activity { + private static final int UNLOCK = 721; + private GrowthTrackManager manager; + private Model model; + private LinearLayout body; + private GrowthTrackClient.Snapshot snapshot; + private final Map receipts = new LinkedHashMap(); + private String onlyId, page = "home", message = ""; + private boolean busy, unlocked, askingUnlock; + private JSONArray library; + private JSONObject routine; + + /** Main-thread observation only: refusing this handoff never commands or stops hardware. */ + static boolean refuseHandOffUnderHold(Activity from) { + SessionActivity owner = SessionActivity.liveForDataHandoff(); + if ((owner != null && owner.stillUnsafe()) || RunService.isRunning()) { + if (owner != null) owner.toast("Vent the pump before opening GrowthTrack or its browser"); + return true; + } + return false; + } + static void open(SessionActivity owner, Model.Sess session) { + if (refuseHandOffUnderHold(owner)) return; + Intent intent = new Intent(owner, GrowthTrackActivity.class).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + if (session != null) intent.putExtra("session_id", session.id); + owner.startActivity(intent); + } + static final class OpenTap implements View.OnClickListener { + private final SessionActivity owner; private final Model.Sess session; + OpenTap(SessionActivity owner, Model.Sess session) { this.owner = owner; this.session = session; } + @Override public void onClick(View v) { open(owner, session); } + } + static final class InformationTap implements View.OnClickListener { + final SessionActivity owner; + InformationTap(SessionActivity owner) { this.owner = owner; } + @Override public void onClick(View view) { + if (refuseHandOffUnderHold(owner)) return; + owner.startActivity(new Intent(owner, GrowthTrackActivity.class) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK).putExtra("information", true)); + } + } + @Override protected void onCreate(Bundle b) { + super.onCreate(b); manager = GrowthTrackManager.get(this); model = Store.load(this, false); + SessionActivity.applySecureFlag(getWindow(), model); + onlyId = b == null ? getIntent().getStringExtra("session_id") : null; + if (getIntent().getBooleanExtra("information", false)) page = "about"; + message = getIntent().getStringExtra("connection_message"); if (message == null) message = ""; + buildRoot(); + } + @Override protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); setIntent(intent); model = Store.load(this, false); + SessionActivity.applySecureFlag(getWindow(), model); + page = "home"; onlyId = intent.getStringExtra("session_id"); + if (intent.getBooleanExtra("information", false)) page = "about"; + message = intent.getStringExtra("connection_message"); if (message == null) message = ""; + refresh(); + } + private void buildRoot() { + ScrollView scroll = new ScrollView(this); scroll.setFillViewport(true); scroll.setBackgroundColor(Ui.BG); + body = Ui.col(this); int padding = Ui.dp(this, Look.S5); body.setPadding(padding, padding, padding, padding); + scroll.addView(body, new ScrollView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); setContentView(scroll); + } + @Override protected void onResume() { + super.onResume(); model = Store.load(this, false); SessionActivity.applySecureFlag(getWindow(), model); + if (needsUnlock() && !unlocked) { lockScreen(); return; } refresh(); + } + private boolean needsUnlock() { return model.appLockOn && (model.appLockWholeApp || model.appLockSettings || model.appLockSessions); } + private void lockScreen() { + body.removeAllViews(); Ui.head(this, body, "GrowthTrack"); Ui.note(this, body, "Unlock to manage your connection and view sync status."); + button("Unlock", new Tap(Action.UNLOCK)); button("Back", new Tap(Action.FINISH)); + } + private void askUnlock() { + if (refuseHandOffUnderHold(this)) return; + KeyguardManager keyguard = (KeyguardManager) getSystemService(KEYGUARD_SERVICE); + Intent intent = keyguard == null ? null : keyguard.createConfirmDeviceCredentialIntent("Unlock", "Review your connected app and sessions"); + if (intent == null) { Ui.note(this, body, "Set a device PIN, pattern or password before opening this locked area."); return; } + askingUnlock = true; startActivityForResult(intent, UNLOCK); + } + @Override protected void onActivityResult(int request, int result, Intent data) { + super.onActivityResult(request, result, data); + if (request == UNLOCK) { askingUnlock = false; unlocked = result == RESULT_OK; if (unlocked) refresh(); else lockScreen(); } + } + @Override protected void onStop() { + super.onStop(); + if (RunService.isRunning()) { library = null; routine = null; } // Private previews yield; automatic sync never owns the pump. + if (!askingUnlock) { unlocked = false; if (needsUnlock()) body.removeAllViews(); } + library = null; routine = null; + if ("library".equals(page) || "routine".equals(page)) { page = "home"; body.removeAllViews(); } + } + private void refresh() { + if (needsUnlock() && !unlocked) return; + List ids = new ArrayList(); for (Model.Sess s : model.sessLog.all) ids.add(s.id); + manager.run(new ReadState(ids), new Completed(null, true, false)); + } + private static final class StateView { + final GrowthTrackClient.Snapshot snapshot; final Map receipts; + StateView(GrowthTrackClient.Snapshot snapshot, Map receipts) { this.snapshot = snapshot; this.receipts = receipts; } + } + private static final class ReadState implements GrowthTrackManager.Work { + final List ids; ReadState(List ids) { this.ids = ids; } + @Override public Object run(GrowthTrackClient client) { + Map statuses = new LinkedHashMap(); + for (String id : ids) try { statuses.put(id, client.receipt(GrowthTrackProtocol.stableId(id))); } catch (IllegalArgumentException ignored) { } + return new StateView(client.snapshot(), statuses); + } + } + private void work(GrowthTrackManager.Work task, String next) { busy = true; render(); manager.run(task, new Completed(next, false, false)); } + private final class Completed implements GrowthTrackManager.Listener { + final String next; final boolean readState, reset; + Completed(String next, boolean readState, boolean reset) { this.next = next; this.readState = readState; this.reset = reset; } + @Override public void done(Object result, String error) { runOnUiThread(new Delivered(result, error, next, readState, reset)); } + } + private final class Delivered implements Runnable { + final Object result; final String error, next; final boolean readState, reset; + Delivered(Object result, String error, String next, boolean readState, boolean reset) { this.result = result; this.error = error; this.next = next; this.readState = readState; this.reset = reset; } + @Override public void run() { + if (isFinishing() || isDestroyed()) return; + if (readState) { + if (result instanceof StateView) { StateView state = (StateView) result; snapshot = state.snapshot; receipts.clear(); receipts.putAll(state.receipts); } + if (error != null) message = error; if (!needsUnlock() || unlocked) render(); return; + } + busy = false; + if (error != null) message = error; else { message = reset ? "Disconnected on this device." : ""; if (next != null) page = next; } + if (reset) { snapshot = null; library = null; routine = null; } + if (result instanceof String && error == null) { + if (hasWindowFocus() && (!needsUnlock() || unlocked)) openBrowser((String) result); + else message = "Connection prepared. Return and tap Connect to open your browser."; + } + if (result instanceof JSONObject && error == null) { + JSONObject json = (JSONObject) result; + // No private response cache survives backgrounding or a locked screen. + if (hasWindowFocus() && (!needsUnlock() || unlocked)) { + if (json.optJSONArray("routines") != null) library = json.optJSONArray("routines"); else routine = json.optJSONObject("routine"); + } else page = "home"; + } + refresh(); + } + } + private enum Operation { BEGIN, CANCEL_AUTH, ROUTINES } + private static final class Task implements GrowthTrackManager.Work { + final Operation operation; final boolean read; final String id; final List payloads; + Task(Operation operation) { this(operation, false, null, null); } + Task(Operation operation, boolean read, String id, List payloads) { this.operation = operation; this.read = read; this.id = id; this.payloads = payloads; } + @Override public Object run(GrowthTrackClient client) throws Exception { + switch (operation) { + case BEGIN: return client.begin(read); + case CANCEL_AUTH: client.cancelAuthorization(); break; + case ROUTINES: return client.routines(id); + default: throw new IllegalStateException(); + } + return null; + } + } + private void render() { + if (needsUnlock() && !unlocked) { lockScreen(); return; } + body.removeAllViews(); Ui.header(this, body, "GrowthTrack", new Tap(Action.BACK)); + if (!message.isEmpty()) Ui.noteInfo(this, body, "Connection update", "GrowthTrack", message); + if (busy) Ui.note(this, body, "Working."); + if ("status".equals(page)) statusScreen(); + else if ("library".equals(page)) libraryScreen(); else if ("routine".equals(page)) routineScreen(); + else if ("about".equals(page)) informationScreen(); else homeScreen(); + } + private void homeScreen() { + Ui.head(this, body, "Your GrowthTrack account"); + button("About GrowthTrack and this connection", new Tap(Action.ABOUT)); + if (snapshot == null) Ui.note(this, body, "Reading connection state."); + else Ui.noteInfo(this, body, snapshot.connected ? "Connected" : snapshot.pending ? "Waiting for browser approval" : "Not connected", "Connection status", snapshot.message); + Ui.noteInfo(this, body, "Connected: new sessions sync automatically.", "Connecting your account", + "Connection enables automatic transfer of every new real finished session to the account you approve in GrowthTrack. Disconnected sessions and earlier history stay on your phone. There is no per-session selection or Send step. Unsupported recordings show an explicit sync error."); + CheckBox read = check("Also allow reading my GrowthTrack routines", snapshot != null && snapshot.routines); + button(snapshot != null && snapshot.connected ? "Reconnect / change account" : "Connect to GrowthTrack", new ConnectTap(read)); + if (snapshot != null && snapshot.pending) button("Cancel connection request", new Tap(Action.CANCEL_AUTH)); + if (snapshot != null && snapshot.connected) { + button("Session sync status", new Tap(Action.STATUS)); + if (snapshot.queued > 0) { + Ui.note(this, body, snapshot.queued + " sessions are pending. OpenPump retries them automatically when the connection is available."); + long wait = Math.max(0, snapshot.retryAt - System.currentTimeMillis()); + Button retry = button(snapshot.paused ? "Reconnect to restore sync" : wait > 0 ? "Retry in " + ((wait + 999) / 1000) + " seconds" : "Retry sync", new Tap(Action.RETRY)); + retry.setEnabled(!busy && wait == 0 && !snapshot.paused); + if (wait > 0) body.postDelayed(new RetryDisplay(), Math.min(wait + 100, 60_000)); + } + button("Preview GrowthTrack routines", new Tap(Action.LIBRARY)); + } + button("Manage access in GrowthTrack", new Tap(Action.MANAGE)); button("Disconnect on this device", new Tap(Action.DISCONNECT)); + } + private final class RetryDisplay implements Runnable { + @Override public void run() { if (!isFinishing() && !isDestroyed() && hasWindowFocus() && "home".equals(page)) refresh(); } + } + private final class ConnectTap implements View.OnClickListener { + final CheckBox read; ConnectTap(CheckBox read) { this.read = read; } + @Override public void onClick(View view) { + dress(Ui.dialog(GrowthTrackActivity.this).setTitle("Connect to GrowthTrack?") + .setMessage("Choose your own GrowthTrack account in the system browser. OpenPump can add training sessions" + (read.isChecked() ? " and read your routine library" : "") + ". Every new real finished session syncs automatically to this account. Earlier history is not uploaded. It cannot read measurements or session history. Reconnecting discards pending transfers for the previous approval.") + .setPositiveButton("Continue", new Decision(Action.CONNECT, read.isChecked(), null)).setNegativeButton("Cancel", null).show()); + } + } + private enum Action { UNLOCK, FINISH, BACK, CONNECT, CANCEL_AUTH, STATUS, RETRY, LIBRARY, MANAGE, DISCONNECT, RESET, LIBRARY_BACK, ABOUT, PROJECT, HOME } + private final class Tap implements View.OnClickListener { + final Action action; Tap(Action action) { this.action = action; } + @Override public void onClick(View view) { act(action, false, null); } + } + private final class Decision implements DialogInterface.OnClickListener { + final Action action; final boolean read; final List payloads; + Decision(Action action, boolean read, List payloads) { this.action = action; this.read = read; this.payloads = payloads; } + @Override public void onClick(DialogInterface dialog, int which) { act(action, read, payloads); } + } + private void act(Action action, boolean read, List payloads) { + if (busy && action != Action.BACK) return; + switch (action) { + case ABOUT: page = "about"; render(); break; + case PROJECT: openBrowser("https://pe-growth-track.com/"); break; + case HOME: page = "home"; render(); break; + case UNLOCK: askUnlock(); break; + case FINISH: finish(); break; + case BACK: onBackPressed(); break; + case CONNECT: work(new Task(Operation.BEGIN, read, null, null), "home"); break; + case CANCEL_AUTH: work(new Task(Operation.CANCEL_AUTH), "home"); break; + case STATUS: page = "status"; refresh(); break; + case RETRY: busy = true; render(); manager.startSync(new Completed("home", false, false)); break; + case LIBRARY: + if (snapshot == null || !snapshot.routines) { message = "Reconnect with the routine permission selected to preview your library."; render(); } + else work(new Task(Operation.ROUTINES), "library"); break; + case MANAGE: openBrowser(GrowthTrackProtocol.CONNECTED_APPS); break; + case DISCONNECT: + dress(Ui.dialog(this).setTitle("Disconnect GrowthTrack?") + .setMessage("Remove tokens and stop automatic sync and discard pending transfers on this device. Sessions already received remain in GrowthTrack. Also remove OpenPump from GrowthTrack Connected apps to revoke its server approval.") + .setPositiveButton("Disconnect", new Decision(Action.RESET, false, null)).setNegativeButton("Cancel", null).show()); break; + case RESET: busy = true; render(); manager.reset(new Completed("home", false, true)); break; + case LIBRARY_BACK: routine = null; page = "library"; render(); break; + default: throw new IllegalStateException(); + } + } + private void dress(AlertDialog dialog) { Ui.dress(this, dialog); SessionActivity.applySecureFlag(dialog.getWindow(), model); } + private void informationScreen() { + Ui.head(this, body, "GrowthTrack, by your choice"); + Ui.note(this, body, "GrowthTrack is a separate PE progress tracker for recording training sessions, routines and measurements in your own account."); + Ui.noteInfo(this, body, "Connect now or later in Settings.", "How to connect", + "Visit GrowthTrack to create or sign in to your account. Back here, choose Connect and approve OpenPump in your system browser. Check the account shown there. You can decline or cancel and keep using OpenPump."); + Ui.noteInfo(this, body, "Connect once; every new session syncs.", "What leaves your phone", + "Your connection authorizes automatic transfer of every real finished session recorded while connected. Sync includes IDs, original times, routine names, fixed automatic technique labels, set durations and recorded peak/net time. Photos, measurements and notes stay out. Earlier history is not uploaded. Offline sessions retry automatically; an unrepresentable recording shows why it cannot sync."); + Ui.noteInfo(this, body, "Routine previews are optional.", "Routine permission", + "With extra permission you can read your GrowthTrack routine library. These are previews only; they do not import executable pump routines or start the pump."); + Ui.noteInfo(this, body, "You control disconnection.", "Remove access", + "Disconnect clears local tokens and pending sends. Remove OpenPump in GrowthTrack Connected apps to revoke server approval. Records already sent remain in your GrowthTrack account."); + button("Visit GrowthTrack", new Tap(Action.PROJECT)); + button("Connection and session controls", new Tap(Action.HOME)); + button("Back to OpenPump", new Tap(Action.FINISH)); + } + private void statusScreen() { + Ui.head(this, body, "Session sync status"); + Ui.noteInfo(this, body, "New sessions sync automatically while connected.", "Session sync", "Every new real finished session syncs while connected. No sessions are selected individually. Earlier or disconnected history is not uploaded."); + int shown = 0; + for (Model.Sess session : model.sessLog.all) { + if (onlyId != null && !onlyId.equals(session.id)) continue; + shown++; + String status = receipts.getOrDefault(session.id, ""); + if (session.sim) status = "Not synced: simulated recording"; + else if (status.startsWith("Received")) { /* Keep confirmed receipts visible after access expires. */ } + else if (session.growthTrackConnectionId.isEmpty()) status = "Not synced: recorded before connection or while disconnected"; + else if (snapshot == null || !snapshot.connected || !session.growthTrackConnectionId.equals(snapshot.connectionId)) + status = "Previous or disconnected approval: current sync stopped; previously sent records may remain in that account"; + else if (status.isEmpty()) status = "Waiting for automatic sync"; + Ui.noteInfo(this, body, session.routineName == null ? "OpenPump session" : session.routineName, "Session sync", status); + } + if (shown == 0) Ui.note(this, body, "There are no recorded sessions here."); + Ui.noteInfo(this, body, "Technique labels are fixed.", "Automatic technique rules", + "Positive confirmed holds up to 6 seconds are Milking; above 6 through 30 are RIP; above 30 through 120 are Interval pumping; longer holds are Static pumping. Release and rest do not determine the label. The recorded Length track takes precedence. Unsupported timing or missing original timestamps are reported, never invented."); + button("Refresh status", new Tap(Action.STATUS)); + } + private void libraryScreen() { + Ui.head(this, body, "GrowthTrack routine library"); Ui.note(this, body, "Read-only previews. A recipe's intensity hint is not an OpenPump machine setting."); + if (library == null) Ui.note(this, body, "Refresh to read the library again."); + else for (int i = 0; i < library.length(); i++) { + JSONObject item = library.optJSONObject(i); if (item != null) button(item.optString("name", "Routine") + " / " + item.optString("access") + " / " + item.optInt("step_count") + " steps", new RoutineTap(item.optString("id"))); + } + button("Refresh library", new Tap(Action.LIBRARY)); + } + private final class RoutineTap implements View.OnClickListener { + final String id; RoutineTap(String id) { this.id = id; } + @Override public void onClick(View v) { work(new Task(Operation.ROUTINES, false, id, null), "routine"); } + } + private void routineScreen() { + if (routine == null) { Ui.note(this, body, "This preview is no longer available. Refresh the library."); return; } + Ui.head(this, body, routine.optString("name", "GrowthTrack routine")); Ui.note(this, body, "Revision " + routine.optString("revision") + " / " + routine.optString("access")); + JSONArray steps = routine.optJSONArray("steps"); + if (steps != null) for (int i = 0; i < steps.length(); i++) { + JSONObject step = steps.optJSONObject(i); if (step == null) continue; + Ui.fieldLabel(this, body, step.optInt("position", i + 1) + ". " + step.optString("exercise_name", "Exercise"), null); + Ui.note(this, body, step.optInt("sets", 1) + " sets / " + (step.isNull("work_seconds_per_set") ? "untimed / repetitions" : step.optInt("work_seconds_per_set") + " seconds per set")); + Ui.note(this, body, "Rest: " + step.optInt("rest_seconds_between_sets", 0) + " seconds between sets" + (i < steps.length() - 1 ? " and before the next step" : "; no trailing rest")); + if (!step.isNull("reps")) Ui.note(this, body, "Repetitions: " + step.optInt("reps")); + if (!step.isNull("intensity_hint")) Ui.noteInfo(this, body, "Author's intensity guidance", "Intensity hint", step.optString("intensity_hint")); + if (step.optBoolean("heat") || step.optBoolean("vibration")) Ui.note(this, body, "Author suggests " + (step.optBoolean("heat") ? "heat " : "") + (step.optBoolean("vibration") ? "vibration" : "")); + if ("own".equals(routine.optString("access")) && !step.isNull("notes")) Ui.noteInfo(this, body, "Your step notes", "Notes", step.optString("notes")); + } + Ui.noteInfo(this, body, "This preview cannot start an OpenPump routine.", "Device settings still need your review", + "Blocks are already expanded in this order. This device-neutral recipe does not supply the vacuum, release and cycle settings needed for a runnable OpenPump routine. Review and enter those settings in OpenPump's editor; this preview never starts hardware or silently substitutes values."); + button("Back to library", new Tap(Action.LIBRARY_BACK)); + } + private CheckBox check(String text, boolean checked) { + CheckBox box = new CheckBox(this); box.setText(text); box.setTextColor(Ui.TEXT); box.setTextSize(Look.SP_BODY); box.setMinHeight(Ui.dp(this, 48)); box.setChecked(checked); box.setEnabled(!busy); + body.addView(box, new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); return box; + } + private Button button(String text, View.OnClickListener listener) { Button b = Ui.flat(this, body, text); b.setMinimumHeight(Ui.dp(this, 48)); b.setEnabled(!busy); b.setOnClickListener(listener); return b; } + private void openBrowser(String url) { + if (refuseHandOffUnderHold(this)) return; + try { startActivity(new Intent(Intent.ACTION_VIEW, android.net.Uri.parse(url))); } + catch (android.content.ActivityNotFoundException e) { message = "No system browser is available. Enable a browser to connect."; manager.run(new Task(Operation.CANCEL_AUTH), new Completed(null, true, false)); render(); } + } + @Override public void onBackPressed() { + if (!"home".equals(page)) { page = "home"; render(); } else super.onBackPressed(); + } +} diff --git a/app/src/main/java/org/openpump/GrowthTrackCallbackActivity.java b/app/src/main/java/org/openpump/GrowthTrackCallbackActivity.java new file mode 100644 index 0000000..45fe19e --- /dev/null +++ b/app/src/main/java/org/openpump/GrowthTrackCallbackActivity.java @@ -0,0 +1,35 @@ +package org.openpump; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.widget.LinearLayout; + +/** Exported callback: no history and no credential-bearing URI forwarded to another activity. */ +public final class GrowthTrackCallbackActivity extends Activity { + @Override protected void onCreate(Bundle saved) { + super.onCreate(saved); SessionActivity.applySecureFlag(getWindow(), Store.load(this, false)); + LinearLayout body = Ui.col(this); body.setBackgroundColor(Ui.BG); body.setPadding(Ui.dp(this, 24), Ui.dp(this, 24), Ui.dp(this, 24), Ui.dp(this, 24)); + Ui.head(this, body, "Connecting to GrowthTrack."); setContentView(body); + String callback = getIntent().getDataString(); getIntent().setData(null); + if (!Intent.ACTION_VIEW.equals(getIntent().getAction()) || callback == null) { finish(); return; } + GrowthTrackManager.get(this).run(new Exchange(callback), new Completed()); + } + private static final class Exchange implements GrowthTrackManager.Work { + final String callback; Exchange(String callback) { this.callback = callback; } + @Override public Object run(GrowthTrackClient client) throws Exception { client.callback(callback); return null; } + } + private final class Completed implements GrowthTrackManager.Listener { + @Override public void done(Object result, String error) { runOnUiThread(new Delivered(error)); } + } + private final class Delivered implements Runnable { + final String error; Delivered(String error) { this.error = error; } + @Override public void run() { + if (isFinishing() || isDestroyed()) return; + if (GrowthTrackActivity.refuseHandOffUnderHold(GrowthTrackCallbackActivity.this)) return; + Intent open = new Intent(GrowthTrackCallbackActivity.this, GrowthTrackActivity.class).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + open.putExtra("connection_message", error == null ? "GrowthTrack connection response received. No sessions were sent." : error); + startActivity(open); finish(); + } + } +} diff --git a/app/src/main/java/org/openpump/GrowthTrackHttp.java b/app/src/main/java/org/openpump/GrowthTrackHttp.java new file mode 100644 index 0000000..f6f7fa1 --- /dev/null +++ b/app/src/main/java/org/openpump/GrowthTrackHttp.java @@ -0,0 +1,58 @@ +package org.openpump; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.Map; +import javax.net.ssl.HttpsURLConnection; + +/** Fixed-origin HTTPS only, with no redirect forwarding, cookies, caches or payload logging. */ +final class GrowthTrackHttp implements GrowthTrackClient.Transport { + private volatile HttpsURLConnection active; + private android.net.Network network; + synchronized void useNetwork(android.net.Network value) { network = value; } + private long generation, operationGeneration; + synchronized long ticket() { return generation; } + synchronized void enter(long ticket) throws IOException { + if (ticket != generation) throw new IOException("GrowthTrack operation was cancelled before it started"); + operationGeneration = ticket; + } + synchronized void cancel() { generation++; HttpsURLConnection connection = active; if (connection != null) connection.disconnect(); } + @Override public GrowthTrackClient.Response request(String method, String path, Map headers, String body) throws IOException { + if (!(path.equals("/oauth-token") || path.equals("/partner-ingest") || path.matches("/partner-routines(?:\\?id=[0-9a-fA-F-]{36})?"))) + throw new IOException("Unsupported GrowthTrack endpoint"); + if (!("GET".equals(method) || "POST".equals(method))) throw new IOException("Unsupported GrowthTrack operation"); + HttpsURLConnection connection; + synchronized (this) { + // A queued Send may enter approve() after a Back/Cancel. Its old operation + // ticket still cannot open HTTP, even if the client's approval resets a flag. + if (operationGeneration != generation) throw new IOException("GrowthTrack operation was cancelled"); + URL endpoint = new URL(GrowthTrackProtocol.API + path); + connection = (HttpsURLConnection) (network == null ? endpoint.openConnection() : network.openConnection(endpoint)); active = connection; + } + try { + connection.setInstanceFollowRedirects(false); connection.setUseCaches(false); connection.setConnectTimeout(15_000); connection.setReadTimeout(20_000); + connection.setRequestMethod(method); + for (Map.Entry h : headers.entrySet()) connection.setRequestProperty(h.getKey(), h.getValue()); + if (body != null) { + byte[] bytes = body.getBytes(StandardCharsets.UTF_8); + if (bytes.length > GrowthTrackProtocol.MAX_BYTES) throw new IOException("GrowthTrack request exceeds its byte limit"); + connection.setDoOutput(true); connection.setFixedLengthStreamingMode(bytes.length); + try (java.io.OutputStream out = connection.getOutputStream()) { out.write(bytes); } + } + int status = connection.getResponseCode(); + if (status >= 300 && status < 400) throw new IOException("GrowthTrack unexpectedly redirected the request"); + InputStream stream = status >= 400 ? connection.getErrorStream() : connection.getInputStream(); + String response = ""; + if (stream != null) try (InputStream in = stream; ByteArrayOutputStream out = new ByteArrayOutputStream()) { + byte[] buffer = new byte[8192]; int n; + while ((n = in.read(buffer)) != -1) { if (out.size() + n > 2_000_000) throw new IOException("GrowthTrack response exceeds its byte limit"); out.write(buffer, 0, n); } + response = new String(out.toByteArray(), StandardCharsets.UTF_8); + } + return new GrowthTrackClient.Response(status, response, connection.getHeaderField("Retry-After")); + } catch (IOException e) { throw new IOException("GrowthTrack network request failed"); } + finally { connection.disconnect(); if (active == connection) active = null; } + } +} diff --git a/app/src/main/java/org/openpump/GrowthTrackManager.java b/app/src/main/java/org/openpump/GrowthTrackManager.java new file mode 100644 index 0000000..e946045 --- /dev/null +++ b/app/src/main/java/org/openpump/GrowthTrackManager.java @@ -0,0 +1,122 @@ +package org.openpump; + +import android.app.job.JobInfo; +import android.app.job.JobScheduler; +import android.content.ComponentName; +import android.content.Context; +import android.os.Handler; +import android.os.Looper; +import java.io.IOException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +/** One serial worker per process: rotating refresh credentials can never race. */ +final class GrowthTrackManager { + interface Work { Object run(GrowthTrackClient client) throws Exception; } + interface Listener { void done(Object result, String error); } + static final class SyncRequest { volatile boolean stopped; } + private volatile SyncRequest activeSync; + private final Handler main = new Handler(Looper.getMainLooper()); + private static final int SYNC_JOB = 72041; + private static GrowthTrackManager instance; + private final ExecutorService worker = Executors.newSingleThreadExecutor(); + private final Context context; + private final GrowthTrackStore store; + private final GrowthTrackHttp http = new GrowthTrackHttp(); + private volatile GrowthTrackClient client; + private volatile boolean blocked; + private volatile String connectedGrant = ""; + static synchronized GrowthTrackManager get(Context c) { if (instance == null) instance = new GrowthTrackManager(c.getApplicationContext()); return instance; } + private GrowthTrackManager(Context c) { context = c; store = new GrowthTrackStore(c); } + private GrowthTrackClient client() throws IOException { + if (client == null) client = new GrowthTrackClient(store, http, new GrowthTrackClient.Clock() { @Override public long now() { return System.currentTimeMillis(); } }); + return client; + } + /** Main-thread observation only; this non-secret identity is frozen in the filed record. */ + String connectionId() { return blocked ? "" : connectedGrant; } + private void cacheConnection(GrowthTrackClient ready) { + GrowthTrackClient.Snapshot state = ready.snapshot(); + connectedGrant = !blocked && state.connected ? state.connectionId : ""; + } + void run(final Work work, final Listener listener) { dispatch(work, listener, null); } + private void dispatch(final Work work, final Listener listener, final SyncRequest request) { + final long ticket = http.ticket(); + worker.execute(new Runnable() { @Override public void run() { + Object result = null; String error = null; GrowthTrackClient ready = null; + try { + if (request != null && request.stopped) throw new IOException("Background sync was stopped"); + activeSync = request; + if (blocked) throw new IOException("Connection cleanup is in progress"); + http.enter(ticket); ready = client(); http.enter(ticket); cacheConnection(ready); + if (request != null && request.stopped) throw new IOException("Background sync was stopped"); + http.useNetwork(null); result = work.run(ready); + } + catch (GrowthTrackClient.Problem e) { error = e.getMessage(); } + catch (IOException e) { error = e.getMessage(); } + catch (Exception e) { error = "Could not complete the GrowthTrack operation. No credential details are logged."; } + http.useNetwork(null); activeSync = null; + if (ready != null) cacheConnection(ready); + // Finish a running JobService before scheduling its next one-shot job. + if (listener != null) listener.done(result, error); + if (request != null && !request.stopped && ready != null && !blocked) { + try { http.enter(ticket); schedule(ready); } catch (IOException ignored) { } + } + } }); + } + /** Only records marked at completion for this exact grant can recover into the encrypted queue. */ + SyncRequest startSync(final Listener listener) { return startSync(listener, null); } + SyncRequest startSync(final Listener listener, final android.net.Network network) { + final SyncRequest request = new SyncRequest(); + dispatch(new Work() { @Override public Object run(GrowthTrackClient ready) throws Exception { + GrowthTrackClient.Snapshot state = ready.snapshot(); + if (!state.connected) return null; + http.useNetwork(network); ready.prepareAutomaticSync(); + Model recorded = Store.load(context, false); + for (Model.Sess session : recorded.sessLog.all) { + if (state.connectionId.equals(session.growthTrackConnectionId)) + ready.recordCompleted(session, Store.loadAsRun(context, session.ts), recorded, session.growthTrackConnectionId); + } + ready.syncPending(); return null; + } }, listener, request); + return request; + } + private JobScheduler jobs() { return (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE); } + private void schedule(final GrowthTrackClient ready) { + final long due = ready.automaticRetryAt(); final long ticket = http.ticket(); + // jobFinished posts its completion on the main looper. Schedule after that + // message so replacing this job ID cannot stop the job that just finished. + main.post(new Runnable() { @Override public void run() { + if (blocked) return; + try { http.enter(ticket); } catch (IOException ignored) { return; } + JobScheduler scheduler = jobs(); + if (due < 0) { if (scheduler != null) scheduler.cancel(SYNC_JOB); return; } + int result = JobScheduler.RESULT_FAILURE; + try { + if (scheduler != null) result = scheduler.schedule(new JobInfo.Builder(SYNC_JOB, new ComponentName(context, GrowthTrackSyncService.class)) + .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY) + .setMinimumLatency(Math.max(1000L, due - System.currentTimeMillis())) + .setBackoffCriteria(30_000L, JobInfo.BACKOFF_POLICY_EXPONENTIAL) + .setPersisted(true).build()); + } catch (RuntimeException ignored) { /* A scheduler refusal remains visible in sync status. */ } + if (result != JobScheduler.RESULT_SUCCESS) worker.execute(new Runnable() { @Override public void run() { + if (!blocked && client == ready) try { ready.backgroundUnavailable(); } catch (IOException ignored) { } + } }); + } }); + } + void cancelSync(SyncRequest request) { + if (request == null) return; request.stopped = true; + // A stopped queued job must not cancel an unrelated OAuth/routine operation. + if (activeSync == request) cancel(); + } + void cancel() { GrowthTrackClient c = client; if (c != null) c.cancelRequests(); http.cancel(); } + void reset(final Listener listener) { + blocked = true; connectedGrant = ""; cancel(); JobScheduler scheduler = jobs(); if (scheduler != null) scheduler.cancel(SYNC_JOB); + worker.execute(new Runnable() { @Override public void run() { + String error = null; + try { store.erase(); client = null; blocked = false; } + catch (IOException e) { error = e.getMessage(); } // Keep requests blocked until reset succeeds. + if (listener != null) listener.done(null, error); + } }); + } + static synchronized void eraseWithAppData(Context c) { get(c).reset(null); } +} diff --git a/app/src/main/java/org/openpump/GrowthTrackStore.java b/app/src/main/java/org/openpump/GrowthTrackStore.java new file mode 100644 index 0000000..7abd48f --- /dev/null +++ b/app/src/main/java/org/openpump/GrowthTrackStore.java @@ -0,0 +1,72 @@ +package org.openpump; + +import android.content.Context; +import android.security.keystore.KeyGenParameterSpec; +import android.security.keystore.KeyProperties; +import android.util.AtomicFile; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.security.KeyStore; +import javax.crypto.Cipher; +import javax.crypto.KeyGenerator; +import javax.crypto.SecretKey; +import javax.crypto.spec.GCMParameterSpec; +import org.json.JSONObject; + +/** Keystore-backed AES-GCM; tokens and approved records never enter preferences or backups. */ +final class GrowthTrackStore implements GrowthTrackClient.Store { + private static final String ALIAS = "org.openpump.growthtrack.v1"; + private static final int MAX_STORED_BYTES = 16_000_000; + private final AtomicFile file; + GrowthTrackStore(Context context) { file = new AtomicFile(new File(context.getNoBackupFilesDir(), "growthtrack-v1.enc")); } + @Override public JSONObject read() throws IOException { + if (!file.getBaseFile().exists() && !new File(file.getBaseFile().getPath() + ".bak").exists()) return new JSONObject(); + try (FileInputStream in = file.openRead()) { + ByteArrayOutputStream out = new ByteArrayOutputStream(); byte[] buffer = new byte[8192]; int n; + while ((n = in.read(buffer)) != -1) { if (out.size() + n > MAX_STORED_BYTES) throw new IOException(); out.write(buffer, 0, n); } + byte[] blob = out.toByteArray(); + if (blob.length < 32 || blob[0] != 'G' || blob[1] != 'T' || blob[2] != 1) throw new IOException(); + byte[] iv = java.util.Arrays.copyOfRange(blob, 3, 15); + Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); cipher.init(Cipher.DECRYPT_MODE, key(false), new GCMParameterSpec(128, iv)); + cipher.updateAAD(new byte[]{'G', 'T', 1}); + byte[] clear = cipher.doFinal(blob, 15, blob.length - 15); + try { return new JSONObject(new String(clear, StandardCharsets.UTF_8)); } + finally { java.util.Arrays.fill(clear, (byte) 0); } + } catch (Exception e) { throw new IOException("Secure GrowthTrack storage is unavailable. Disconnect/reset the connection to link again."); } + } + @Override public void write(JSONObject state) throws IOException { + FileOutputStream out = null; byte[] clear = state.toString().getBytes(StandardCharsets.UTF_8); + try { + if (clear.length > MAX_STORED_BYTES - 32) throw new IOException(); + Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding"); cipher.init(Cipher.ENCRYPT_MODE, key(true)); + cipher.updateAAD(new byte[]{'G', 'T', 1}); byte[] iv = cipher.getIV(); + if (iv.length != 12) throw new IOException(); + byte[] encrypted = cipher.doFinal(clear); + out = file.startWrite(); out.write(new byte[]{'G', 'T', 1}); out.write(iv); out.write(encrypted); file.finishWrite(out); out = null; + } catch (Exception e) { if (out != null) file.failWrite(out); throw new IOException("Could not securely save the GrowthTrack connection. No plain-text fallback is used."); } + finally { java.util.Arrays.fill(clear, (byte) 0); } + } + private static SecretKey key(boolean create) throws Exception { + KeyStore store = KeyStore.getInstance("AndroidKeyStore"); store.load(null); + if (store.containsAlias(ALIAS)) return (SecretKey) store.getKey(ALIAS, null); + if (!create) throw new IOException(); + KeyGenerator generator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore"); + generator.init(new KeyGenParameterSpec.Builder(ALIAS, KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) + .setBlockModes(KeyProperties.BLOCK_MODE_GCM).setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE) + .setKeySize(256).setRandomizedEncryptionRequired(true).build()); + return generator.generateKey(); + } + void erase() throws IOException { + file.delete(); + boolean remains = file.getBaseFile().exists() || new File(file.getBaseFile().getPath() + ".bak").exists() + || new File(file.getBaseFile().getPath() + ".new").exists(); + // Destroy the decryption key even if the filesystem could not remove an old blob. + try { KeyStore store = KeyStore.getInstance("AndroidKeyStore"); store.load(null); if (store.containsAlias(ALIAS)) store.deleteEntry(ALIAS); } + catch (Exception e) { throw new IOException("Could not remove the GrowthTrack encryption key"); } + if (remains) throw new IOException("Could not remove GrowthTrack connection storage. Retry reset before linking again."); + } +} diff --git a/app/src/main/java/org/openpump/GrowthTrackSyncService.java b/app/src/main/java/org/openpump/GrowthTrackSyncService.java new file mode 100644 index 0000000..aaa72a6 --- /dev/null +++ b/app/src/main/java/org/openpump/GrowthTrackSyncService.java @@ -0,0 +1,35 @@ +package org.openpump; + +import android.app.job.JobParameters; +import android.app.job.JobService; +import android.os.Handler; +import android.os.Looper; + +/** Android network-constrained retry. No UI, new OAuth grant, or pump access. */ +public final class GrowthTrackSyncService extends JobService { + private JobParameters active; + private GrowthTrackManager.SyncRequest request; + private final Handler main = new Handler(Looper.getMainLooper()); + @Override public boolean onStartJob(JobParameters params) { + active = params; + request = GrowthTrackManager.get(this).startSync(new Finished(params), android.os.Build.VERSION.SDK_INT >= 28 ? params.getNetwork() : null); + return true; + } + private final class Finished implements GrowthTrackManager.Listener { + final JobParameters params; Finished(JobParameters params) { this.params = params; } + @Override public void done(Object result, String error) { + // The worker needs jobFinished to precede replacement scheduling. Android's + // JobService API accepts this callback from the worker; no view is touched. + jobFinished(params, false); + main.post(new Clear(params)); + } + } + private final class Clear implements Runnable { + final JobParameters params; Clear(JobParameters params) { this.params = params; } + @Override public void run() { if (active == params) active = null; } + } + @Override public boolean onStopJob(JobParameters params) { + if (active == params) { active = null; GrowthTrackManager.get(this).cancelSync(request); request = null; } + return true; + } +} diff --git a/app/src/main/java/org/openpump/SessionActivity.java b/app/src/main/java/org/openpump/SessionActivity.java index 5af510f..b638121 100644 --- a/app/src/main/java/org/openpump/SessionActivity.java +++ b/app/src/main/java/org/openpump/SessionActivity.java @@ -540,6 +540,7 @@ private Model.Set manualSourceSet() { Model.Fmt.unit = model.unit; // keep the formatter in step with what was saved Model.Fmt.sizeUnit = model.sizeUnit; // ...and the SIZE formatter, the same way Model.Fmt.loadUnit = model.loadUnit; // ...and the LOAD formatter (2026-09-30) + GrowthTrackManager.get(this).startSync(null); openLog(); storeUnreadable = Store.storeWasUnreadable(); // KEEP THE PHOTOS OUT OF THE PHONE'S GALLERY. The directory below is @@ -868,6 +869,12 @@ private Model.Set manualSourceSet() { * The running screen, for the guard at the top of onCreate. Main thread only; set as a * screen is created and released in its onDestroy only if it is still this one. */ private static SessionActivity liveScreen; + + /** Main-thread, read-only guard for optional connected-app handoffs. */ + static SessionActivity liveForDataHandoff() { + SessionActivity s = liveScreen; + return s != null && !s.isFinishing() && !s.isDestroyed() ? s : null; + } /** This instance was created over a running screen and finished at once: its onDestroy * must touch nothing (no last stop, no clearLive, no service stop). */ private boolean duplicateScreen; @@ -2711,7 +2718,7 @@ void applySecureWindow() { * its switch is on. Incognito (0.10): the switch is "Hide from recent apps" in Settings › * Privacy - the same field, the same flag, and ONLY this switch sets it (WiringCheck * invariant 193). */ - void applySecureFlag(android.view.Window w) { + static void applySecureFlag(android.view.Window w, Model model) { if (w == null) return; if (model != null && model.secureWindow) w.setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE); @@ -2719,6 +2726,8 @@ void applySecureFlag(android.view.Window w) { w.clearFlags(WindowManager.LayoutParams.FLAG_SECURE); } + void applySecureFlag(android.view.Window w) { applySecureFlag(w, model); } + /* ================================================================ INCOGNITO (0.10) * * The part of Settings › Privacy that lives on this screen. The rules are Incognito's @@ -27953,6 +27962,12 @@ private void fileSession(boolean aborted, long now) { // ...and with the earlier parts, the session lasts them all and starts when the first // did - before anything below keys on rec.ts (the as-run, the traces, the day). RunParts.foldTime(parts, rec); + // Data-only wall-clock facts: do not reinterpret old history's end/start key. + // A rejoined record lacks independently preserved wall-clock bounds for all parts. + if (parts == null || parts.playedMs == 0) { + rec.recordedStartMs = session.runStartedAtOrZero(); + rec.recordedEndMs = now; + } el = rec.durSec; sessionElapsedSec = el; rec.completed = !aborted; @@ -28213,7 +28228,10 @@ private void fileSession(boolean aborted, long now) { // it is where the summary's Done goes, and the summary is drawn after this runs. manualSource = null; model.adhoc.clear(); + GrowthTrackManager sync = GrowthTrackManager.get(this); + rec.growthTrackConnectionId = sync.connectionId(); Store.save(this, model); + sync.startSync(null); // The run is over: stop attributing samples to it and stop its clock. What it // accumulated stays readable (the summary is redrawn after the after-measurement @@ -35336,6 +35354,7 @@ void refreshWidgetIdle() { /** Set at load, so the warning survives the rest of onCreate deciding what to show. */ private boolean storeUnreadable = false; + boolean dataStoreUnreadable() { return storeUnreadable; } /** * THE SCREEN THAT USED TO NOT EXIST (audit A33). A model.json that could not be read diff --git a/app/src/main/java/org/openpump/SettingsScreen.java b/app/src/main/java/org/openpump/SettingsScreen.java index bf4201d..72d3c52 100644 --- a/app/src/main/java/org/openpump/SettingsScreen.java +++ b/app/src/main/java/org/openpump/SettingsScreen.java @@ -1029,6 +1029,12 @@ void showSettings() { * out of sight. */ // Whether this build has the diagnostic console at all: only a debug build does (see // the Diagnostics card below). + LinearLayout gConnected = Ui.cardGroup(a, a.body, "Connected apps", null, Ui.DIM); + Ui.note(a, gConnected, "GrowthTrack is optional. Connected: new sessions sync automatically. " + + "Disconnected: nothing sends."); + Button growthTrack = Ui.flat(a, gConnected, "GrowthTrack >"); + growthTrack.setOnClickListener(new GrowthTrackActivity.OpenTap(a, null)); + boolean console = SessionActivity.hasConsole(); /* ===== DEVICE & DEVELOPER — deliberately last ================================== */ diff --git a/app/src/main/java/org/openpump/SetupScreen.java b/app/src/main/java/org/openpump/SetupScreen.java index aaf2d5d..c3bda2f 100644 --- a/app/src/main/java/org/openpump/SetupScreen.java +++ b/app/src/main/java/org/openpump/SetupScreen.java @@ -80,6 +80,7 @@ static final class Walk { final HashSet open = new HashSet(); /** The battery row as last drawn, so a resume redraws only when it changed. */ boolean drawnExempt; + boolean growthTrackVisible; Walk() { // The one explanation open from the start: the battery's, on Ready. @@ -932,6 +933,11 @@ private int starterMinutes() { private void ready() { Walk w = w(); + if (GrowthTrackOnboarding.shouldOffer(a.model, RunService.isRunning(), a.dataStoreUnreadable())) { + GrowthTrackOnboarding.presented(a.model); + Store.save(a, a.model); + w.growthTrackVisible = true; + } TextView h = text(SetupText.READY_TITLE, 22f, Ui.TEXT); h.setTypeface(Typeface.DEFAULT_BOLD); if (android.os.Build.VERSION.SDK_INT >= 28) h.setAccessibilityHeading(true); @@ -939,6 +945,20 @@ private void ready() { hl.bottomMargin = dp(Look.S4); page.addView(h, hl); + if (w.growthTrackVisible) { + LinearLayout gt = card(); + Ui.head(a, gt, "Connect your own GrowthTrack account?"); + Ui.note(a, gt, "GrowthTrack records PE training sessions, routines and measurements. Connection is optional; you can skip and connect later in Settings."); + Ui.noteInfo(a, gt, "Nothing uploads just by connecting.", "Your choice", + "Use your own account and approve in your browser. While connected, every new real finished session syncs automatically. Disconnected sessions and earlier history stay on your phone. Skipping, cancelling or going back leaves OpenPump ready to use."); + Button connect = Ui.flat(a, gt, "Connect to GrowthTrack"); + connect.setOnClickListener(new GrowthTrackChoice(true)); + Button info = Ui.flat(a, gt, "About GrowthTrack and account setup"); + info.setOnClickListener(new GrowthTrackActivity.InformationTap(a)); + Button skip = Ui.flat(a, gt, "Skip for now"); + skip.setOnClickListener(new GrowthTrackChoice(false)); + } + // THE BATTERY: the one explanation open from the start, and the one row that goes // out to a system screen - so the answer is read again on the way back (onResume). boolean exempt = a.isBatteryExempt(); @@ -982,6 +1002,16 @@ private void ready() { page.addView(opts, ol); } + private final class GrowthTrackChoice implements View.OnClickListener { + final boolean connect; + GrowthTrackChoice(boolean connect) { this.connect = connect; } + @Override public void onClick(View view) { + w().growthTrackVisible = false; + show(); + if (connect) GrowthTrackActivity.open(a, null); + } + } + private void summary(LinearLayout parent) { Model m = a.model; String size = Model.Fmt.S_IN.equals(m.sizeUnit) ? SetupText.SIZE_IN : SetupText.SIZE_CM; diff --git a/app/src/main/java/org/openpump/Store.java b/app/src/main/java/org/openpump/Store.java index f18c8aa..6ca1bba 100644 --- a/app/src/main/java/org/openpump/Store.java +++ b/app/src/main/java/org/openpump/Store.java @@ -538,6 +538,7 @@ public static void deleteTau(Context c, long sessTs) { * the same either way, and a half-completed erase must still leave the app usable. */ public static boolean deleteAll(Context c) { + GrowthTrackManager.eraseWithAppData(c); // stop transfers and remove tokens outside backups too File dir = c.getFilesDir(); boolean ok = true; ok &= gone(new File(dir, FILE)); diff --git a/app/src/main/java/org/openpump/SummaryScreen.java b/app/src/main/java/org/openpump/SummaryScreen.java index afc9692..896b311 100644 --- a/app/src/main/java/org/openpump/SummaryScreen.java +++ b/app/src/main/java/org/openpump/SummaryScreen.java @@ -314,6 +314,11 @@ void renderSummary() { a.streakBlock(st.streak, "day streak", sub, st.weekDays, st.scheduledDays); } + if (s != null && !s.sim) { + Button growthTrack = Ui.flat(a, a.body, "GrowthTrack sync status"); + growthTrack.setOnClickListener(new GrowthTrackActivity.OpenTap(a, s)); + } + boolean man = s != null && s.manual; // The after-session measurement now leads the summary rather than closing it - see // measureAfterCard(). One action, one place: a second identical button down here diff --git a/core/src/main/java/org/openpump/AsRun.java b/core/src/main/java/org/openpump/AsRun.java index c12c1cd..dce6949 100644 --- a/core/src/main/java/org/openpump/AsRun.java +++ b/core/src/main/java/org/openpump/AsRun.java @@ -71,6 +71,9 @@ public static final class Row { /** Position of the set within its stage (Preset#pos) and position of the preset * within the set's ladder (Preset#ordinal). */ public int pos, ordinal; + /** Frozen stitch boundary: -1 on old recordings, 0 a complete preset, 1 a + * continuation chunk. Data only; never changes how a preset is driven. */ + public int cyclePart = -1; public int up, upReq, lo, uhWire, uhReq, lh, sp; /** False when the pump never acknowledged the write this row records (§2) — the * card then says "the pump didn't confirm this change". */ @@ -113,6 +116,7 @@ public JSONObject toJson() throws JSONException { // shim has no optLong, and one reader for both JVMs is the point of the shim. o.put("t0", String.valueOf(t0)); o.put("t1", String.valueOf(t1)); o.put("st", stageIdx); o.put("pos", pos); o.put("ord", ordinal); + if (cyclePart >= 0) o.put("cyclePart", cyclePart); o.put("sid", setId == null ? "" : setId); o.put("up", up); o.put("upr", upReq); o.put("lo", lo); o.put("uhw", uhWire); o.put("uhr", uhReq); o.put("lh", lh); o.put("sp", sp); @@ -128,6 +132,8 @@ public static Row fromJson(JSONObject o) { r.t0 = parseLong(o.optString("t0", "0"), 0L); r.t1 = parseLong(o.optString("t1", "-1"), -1L); r.stageIdx = o.optInt("st", 0); r.pos = o.optInt("pos", 0); r.ordinal = o.optInt("ord", 0); + int part = o.optInt("cyclePart", -1); + r.cyclePart = part == 0 || part == 1 ? part : -1; r.setId = o.optString("sid", ""); r.up = o.optInt("up", 0); // upReq absent (a row written before it existed) → what went on the wire: no @@ -248,9 +254,11 @@ public Row openRow() { */ public Row open(int kind, long t0, Model.Preset p, int up, int upReq, int lo, int uhWire, int uhReq, int lh, int sp) { - return open(kind, t0, p == null ? 0 : p.stageIdx, p == null ? 0 : p.pos, + Row row = open(kind, t0, p == null ? 0 : p.stageIdx, p == null ? 0 : p.pos, p == null ? "" : p.setId, p == null ? 0 : p.ordinal, up, upReq, lo, uhWire, uhReq, lh, sp); + if (p != null && row.hasValues()) row.cyclePart = p.cyclePart ? 1 : 0; + return row; } /** {@link #open(int, long, Model.Preset, int, int, int, int, int, int, int)} with the diff --git a/core/src/main/java/org/openpump/GrowthTrackClient.java b/core/src/main/java/org/openpump/GrowthTrackClient.java new file mode 100644 index 0000000..6251f9f --- /dev/null +++ b/core/src/main/java/org/openpump/GrowthTrackClient.java @@ -0,0 +1,407 @@ +package org.openpump; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +/** Serialized OAuth and upload state machine. Transport and encrypted storage are Android seams. */ +public final class GrowthTrackClient { + public interface Store { JSONObject read() throws IOException; void write(JSONObject state) throws IOException; } + public interface Clock { long now(); } + public interface Transport { Response request(String method, String path, Map headers, String body) throws IOException; } + public static final class Response { + public final int status; + public final String body, retryAfter; + public Response(int status, String body) { this(status, body, null); } + public Response(int status, String body, String retryAfter) { this.status = status; this.body = body; this.retryAfter = retryAfter; } + } + public static final class Problem extends IOException { + public Problem(String message) { super(message); } + } + public static final class Snapshot { + public final boolean connected, routines, pending, paused; + public final int queued; + public final long retryAt; + public final String message; + public final String connectionId; + Snapshot(boolean connected, boolean routines, boolean pending, int queued, long retryAt, String message, String connectionId, boolean paused) { + this.connected = connected; this.routines = routines; this.pending = pending; + this.queued = queued; this.retryAt = retryAt; this.message = message; + this.connectionId = connectionId; this.paused = paused; + } + } + private final Store store; + private final Transport transport; + private final Clock clock; + private JSONObject state; + private volatile boolean cancelled; + private boolean storageBroken; + + public GrowthTrackClient(Store store, Transport transport, Clock clock) throws IOException { + this.store = store; this.transport = transport; this.clock = clock; + state = store.read(); + if (state == null) state = new JSONObject(); + if (state.optInt("version", 1) != 1) throw new Problem("This connection storage needs a newer OpenPump version"); + if (state.optBoolean("refreshing") || state.optBoolean("exchanging")) { + // A rotating token may have been consumed before the process died. Never replay it. + clearAuthority("Connection interrupted while exchanging credentials. Connect again."); save(); + } + if (state.optJSONArray("approved") == null) put("approved", new JSONArray()); + if (state.optJSONObject("receipts") == null) put("receipts", new JSONObject()); + if (state.optJSONObject("tokens") != null && state.optString("connection_id").isEmpty()) { + clearAuthority("This older connection needs fresh browser approval."); save(); + } + if (state.optJSONObject("tokens") != null && !state.optBoolean("automatic_sync")) { + clearAuthority("Connect again to enable automatic transfer of every new session."); save(); + } + if (approved().length() > 0 && !state.optString("connection_id").equals(state.optString("approved_connection_id"))) { + put("approved", new JSONArray()); put("message", "The account approval changed. Pending sessions for the previous account were discarded."); save(); + } + } + public synchronized Snapshot snapshot() { + JSONObject t = state.optJSONObject("tokens"); + return new Snapshot(t != null && !storageBroken, t != null && !storageBroken && hasScope(t.optString("scope"), GrowthTrackProtocol.READ), + state.optJSONObject("pending") != null, approved().length(), state.optLong("retry_at", 0), state.optString("message", "Not connected"), state.optString("connection_id"), state.optBoolean("sync_paused")); + } + public synchronized String receipt(String id) { return state.optJSONObject("receipts").optString(id, ""); } + public void cancelRequests() { cancelled = true; } + private void checkCancelled() throws Problem { if (storageBroken) throw new Problem("Secure storage failed. Disconnect/reset this connection before continuing."); if (cancelled) throw new Problem("Sending stopped. An in-flight request may already have arrived; stable IDs make a later retry safe."); } + + /** Changing account invalidates every old local approval; only future completions belong to the new grant. */ + public synchronized String begin(boolean routines) throws IOException { + if (storageBroken) throw new Problem("Secure storage failed. Disconnect/reset this connection before continuing."); + cancelled = false; + clearAuthority("Waiting for GrowthTrack approval in your browser"); + put("receipts", new JSONObject()); + put("pending", pendingJson(GrowthTrackProtocol.pending(routines, clock.now()))); save(); + try { return GrowthTrackProtocol.Pending.from(state.getJSONObject("pending")).authorizeUrl(); } + catch (JSONException e) { throw new Problem("Could not create the connection request"); } + } + public synchronized void cancelAuthorization() throws IOException { + state.remove("pending"); put("message", "Connection cancelled. No sessions were sent."); save(); + } + public synchronized void callback(String uri) throws IOException { + GrowthTrackProtocol.Pending pending; + GrowthTrackProtocol.Callback callback; + try { + pending = state.optJSONObject("pending") == null ? null : GrowthTrackProtocol.Pending.from(state.getJSONObject("pending")); + callback = GrowthTrackProtocol.callback(uri, pending, clock.now()); + } catch (JSONException | IllegalArgumentException e) { throw new Problem(e.getMessage()); } + checkCancelled(); + state.remove("pending"); + if (callback.error != null) { put("message", "access_denied".equals(callback.error) ? "GrowthTrack approval was declined. No sessions were sent." : "GrowthTrack could not approve this connection. Connect again."); save(); return; } + put("exchanging", true); save(); + Map form = new LinkedHashMap(); + form.put("grant_type", "authorization_code"); form.put("code", callback.code); + form.put("code_verifier", pending.verifier); form.put("client_id", GrowthTrackProtocol.CLIENT_ID); form.put("redirect_uri", GrowthTrackProtocol.REDIRECT); + try { + Response response = tokenRequest(form); checkCancelled(); + if (response.status != 200) throw new Problem(tokenError(response)); + put("tokens", tokens(response.body, pending.scope)); put("exchanging", false); + put("connection_id", pending.state); put("automatic_sync", true); put("sync_paused", false); + put("message", "Connected to the GrowthTrack account you approved in the browser. Every new real finished session will sync automatically. Earlier history stays on this device."); save(); + } catch (IOException e) { clearAuthority("Could not complete the connection. Connect again; the authorization code will not be replayed."); save(); throw e; } + } + public synchronized void disconnect() throws IOException { + cancelled = true; clearAuthority("Disconnected on this device. Remove OpenPump in GrowthTrack Connected apps to revoke its server approval."); + put("receipts", new JSONObject()); save(); + } + public synchronized void cancelApproved() throws IOException { + put("approved", new JSONArray()); put("retry_at", 0); put("attempts", 0); + put("message", "Sending stopped. Sessions already received by GrowthTrack remain there."); save(); + } + + /** Completion consent is the browser grant captured when the record was filed. + * Persisted record markers recover the tiny filing/queue crash window, without + * admitting disconnected history or records belonging to another account. */ + public synchronized boolean recordCompleted(Model.Sess session, AsRun run, Model model, String completionConnectionId) throws IOException { + if (!snapshot().connected || completionConnectionId == null || completionConnectionId.isEmpty() + || !completionConnectionId.equals(state.optString("connection_id"))) return false; + checkCancelled(); + String id; + try { id = GrowthTrackProtocol.stableId(session == null ? "" : session.id); } + catch (IllegalArgumentException e) { put("message", "Not synced: a finished record has no stable identifier."); save(); return false; } + if (!receipt(id).isEmpty() || ids(approved()).contains(id)) return false; + JSONObject payload; + try { + payload = GrowthTrackSession.draft(session, run, model).payload(); validateSession(payload); + if (envelope(new JSONArray().put(payload)).getBytes(StandardCharsets.UTF_8).length > GrowthTrackProtocol.MAX_BYTES) + throw new IllegalArgumentException("The recording exceeds GrowthTrack's request size limit"); + } catch (IllegalArgumentException | JSONException e) { + receiptPut(id, "Not synced: " + safeReason(e.getMessage() == null ? "Invalid recording" : e.getMessage())); + put("message", "A finished recording cannot be sent. See session sync status for the reason."); save(); return false; + } + approved().put(payload); put("approved_connection_id", completionConnectionId); + put("message", approved().length() + " sessions waiting for automatic sync"); save(); return true; + } + /** Read-only scheduling decision: a paused permission error never loops in background. */ + public synchronized long automaticRetryAt() { + Snapshot s = snapshot(); return s.connected && s.queued > 0 && !s.paused ? Math.max(clock.now(), s.retryAt) : -1; + } + public synchronized void backgroundUnavailable() throws IOException { + put("message", "Background sync could not be scheduled. Pending sessions are secure; reopen OpenPump or tap Retry to resume."); save(); + } + /** Called on the serial native worker after a fresh transport generation is entered. */ + public synchronized void prepareAutomaticSync() throws IOException { + if (storageBroken) throw new Problem("Secure storage failed. Disconnect/reset before continuing."); + cancelled = false; + } + /** Starts no OAuth and scans no unmarked history. Same-grant durable pending work only. */ + public synchronized void syncPending() throws IOException { + if (automaticRetryAt() < 0 || clock.now() < state.optLong("retry_at", 0)) return; + cancelled = false; sendApproved(); + } + + /** A detached, allowlisted snapshot of exactly the records the user just reviewed. */ + public synchronized void approve(List sessions) throws IOException { + approve(sessions, state.optString("connection_id")); + } + /** The review must still refer to the same browser grant, including across relinking. */ + public synchronized void approve(List sessions, String reviewedConnectionId) throws IOException { + checkLinked(GrowthTrackProtocol.WRITE); cancelled = false; + if (reviewedConnectionId == null || reviewedConnectionId.isEmpty() || !reviewedConnectionId.equals(state.optString("connection_id"))) + throw new Problem("The GrowthTrack account approval changed. Review these sessions again before sending."); + if (sessions == null || sessions.isEmpty()) throw new Problem("Select at least one session"); + if (approved().length() != 0) throw new Problem("Retry or cancel the existing approved send first"); + JSONArray queue = new JSONArray(); Set ids = new HashSet(); + try { + for (JSONObject s : sessions) { + validateSession(s); + String id = s.getString("external_session_id"); + if (!ids.add(id)) throw new Problem("The selection contains a duplicate session"); + if (envelope(new JSONArray().put(s)).getBytes(StandardCharsets.UTF_8).length > GrowthTrackProtocol.MAX_BYTES) + throw new Problem("A selected session exceeds GrowthTrack's request size limit"); + queue.put(new JSONObject(s.toString())); + } + } catch (JSONException | IllegalArgumentException e) { throw new Problem("The selected session payload is invalid"); } + put("approved", queue); put("attempts", 0); put("retry_at", 0); put("batch_limit", GrowthTrackProtocol.BATCH_SIZE); + put("approved_connection_id", reviewedConnectionId); + put("message", queue.length() + " reviewed sessions approved for sending"); save(); + } + /** Retry uses the same durable, account-bound queue; startup and scheduled jobs call syncPending(). */ + public synchronized void resumeApproved() throws IOException { put("sync_paused", false); cancelled = false; sendApproved(); } + public synchronized void sendApproved() throws IOException { + checkCancelled(); checkLinked(GrowthTrackProtocol.WRITE); + if (approved().length() > 0 && !state.optString("connection_id").equals(state.optString("approved_connection_id"))) + throw new Problem("The approved send belongs to an earlier connection. Cancel it and review your sessions again."); + if (clock.now() < state.optLong("retry_at", 0)) throw new Problem("Please wait for the retry delay before trying again"); + while (approved().length() > 0) { + checkCancelled(); + JSONArray batch = batch(); String body = envelope(batch); + Response response; + try { response = authorized("POST", "/partner-ingest", body, GrowthTrackProtocol.WRITE); } + catch (Problem e) { throw e; } + catch (IOException e) { retry(null); throw new Problem("Offline or network error. Your sessions are saved for automatic retry."); } + checkCancelled(); + if (response.status == 413 && batch.length() > 1) { + put("batch_limit", Math.max(1, batch.length() / 2)); save(); continue; + } + if (response.status == 408 || response.status == 429 || response.status >= 500) { + retry(response.retryAfter); throw new Problem("GrowthTrack is unavailable or busy. Your sessions are saved for automatic retry."); + } + if (response.status != 200) { + if (response.status == 400 || response.status == 413 || response.status == 422) { + for (int i = 0; i < batch.length(); i++) receiptPut(batch.optJSONObject(i).optString("external_session_id"), "Needs review: GrowthTrack rejected the request (" + response.status + ")"); + removeIds(ids(batch)); put("message", "GrowthTrack rejected the request. Review the selected sessions before sending again."); save(); + } + if (response.status != 400 && response.status != 413 && response.status != 422) { + put("sync_paused", true); put("message", "Sync paused: GrowthTrack rejected the request (" + response.status + "). Check the connection."); save(); + } + throw new Problem("GrowthTrack rejected the request (" + response.status + "). See sync status."); + } + try { applyResults(batch, new JSONObject(response.body)); } + catch (JSONException | IllegalArgumentException e) { retry(null); throw new Problem("GrowthTrack returned an incomplete result. The approved batch is kept; retrying is safe."); } + if (state.optLong("retry_at", 0) > clock.now()) return; + } + } + + public synchronized JSONObject routines(String id) throws IOException { + cancelled = false; + if (id != null && !id.matches("[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}")) + throw new Problem("Invalid GrowthTrack routine ID"); + Response r = authorized("GET", "/partner-routines" + (id == null ? "" : "?id=" + id), null, GrowthTrackProtocol.READ); + checkCancelled(); + if (r.status == 404) throw new Problem("This routine is no longer available. Refresh the library."); + if (r.status != 200) throw new Problem("Could not read the GrowthTrack library (" + r.status + "). Try again later."); + try { + JSONObject o = new JSONObject(r.body); + if (!"gt-routine".equals(o.getString("format")) || o.getInt("schema_version") != 1) + throw new Problem("This GrowthTrack routine format is not supported"); + if (id == null) { if (o.getJSONArray("routines").length() > 1000) throw new JSONException("Too many routines"); } + else { JSONObject routine = o.getJSONObject("routine"); if (!id.equals(routine.getString("external_id")) || routine.getJSONArray("steps").length() > 1000) throw new JSONException("Invalid routine"); } + return o; + } catch (JSONException e) { throw new Problem("GrowthTrack returned an invalid routine response"); } + } + + private Response authorized(String method, String path, String body, String scope) throws IOException { + checkLinked(scope); + JSONObject t = state.optJSONObject("tokens"); + boolean refreshed = false; + if (clock.now() + 30_000 >= t.optLong("expires_at")) { refresh(); refreshed = true; } + Response r = authenticatedRequest(method, path, body); + if (r.status == 401 && !refreshed) { refresh(); r = authenticatedRequest(method, path, body); } + if (r.status == 401 || r.status == 410) { + clearAuthority("GrowthTrack access expired or was removed. Automatic sync stopped; connect again."); save(); + throw new Problem("GrowthTrack access expired or was removed. Connect again."); + } + if (r.status == 403) { put("sync_paused", true); put("message", "Sync paused: GrowthTrack has not granted " + scope + ". Reconnect to restore access."); save(); throw new Problem(state.optString("message")); } + return r; + } + private Response authenticatedRequest(String method, String path, String body) throws IOException { + checkCancelled(); JSONObject t = state.optJSONObject("tokens"); + Map headers = new LinkedHashMap(); + headers.put("Authorization", "Bearer " + t.optString("access_token")); + headers.put("Accept", "application/json"); headers.put("Cache-Control", "no-store"); + if (body != null) headers.put("Content-Type", "application/json; charset=utf-8"); + return transport.request(method, path, headers, body); + } + private void refresh() throws IOException { + checkCancelled(); JSONObject old = state.optJSONObject("tokens"); + if (old == null) throw new Problem("Connect to GrowthTrack first"); + put("refreshing", true); save(); + Map form = new LinkedHashMap(); + form.put("grant_type", "refresh_token"); form.put("refresh_token", old.optString("refresh_token")); form.put("client_id", GrowthTrackProtocol.CLIENT_ID); + try { + Response r = tokenRequest(form); checkCancelled(); + if (r.status != 200) throw new Problem(tokenError(r)); + JSONObject replacement = tokens(r.body, old.optString("scope")); + if (old.optString("refresh_token").equals(replacement.optString("refresh_token"))) throw new Problem("GrowthTrack did not rotate the refresh credential"); + put("tokens", replacement); put("refreshing", false); save(); + } catch (IOException e) { + clearAuthority("Could not safely renew GrowthTrack access. Connect again; the old refresh credential will not be replayed."); save(); + throw new Problem("Could not safely renew GrowthTrack access. Connect again."); + } + } + private Response tokenRequest(Map form) throws IOException { + Map headers = new LinkedHashMap(); + headers.put("Content-Type", "application/x-www-form-urlencoded"); headers.put("Accept", "application/json"); headers.put("Cache-Control", "no-store"); + return transport.request("POST", "/oauth-token", headers, GrowthTrackProtocol.form(form)); + } + private JSONObject tokens(String body, String requestedScope) throws Problem { + try { + JSONObject o = new JSONObject(body); String access = o.getString("access_token"), refresh = o.getString("refresh_token"); + int expiry = o.getInt("expires_in"); String scope = o.getString("scope"); + if (!access.matches("gta_[A-Za-z0-9_-]{20,256}") || !refresh.matches("gtr_[A-Za-z0-9_-]{20,256}") + || !"Bearer".equalsIgnoreCase(o.getString("token_type")) || expiry < 1 || expiry > 900 + || !hasScope(scope, GrowthTrackProtocol.WRITE)) throw new JSONException("Invalid credentials"); + for (String s : scope.trim().split("\\s+")) if (!hasScope(requestedScope, s)) throw new JSONException("Unexpected permission"); + if (hasScope(requestedScope, GrowthTrackProtocol.READ) && !hasScope(scope, GrowthTrackProtocol.READ)) + throw new JSONException("Missing permission"); + return new JSONObject().put("access_token", access).put("refresh_token", refresh).put("scope", scope) + .put("expires_at", clock.now() + expiry * 1000L); + } catch (JSONException e) { throw new Problem("GrowthTrack returned invalid credentials. Connect again."); } + } + private String tokenError(Response r) { + String code = ""; try { code = new JSONObject(r.body).optString("error"); } catch (JSONException ignored) { } + if ("invalid_client".equals(code)) return "GrowthTrack has not enabled this OpenPump client. Contact the integration maintainer."; + if ("invalid_grant".equals(code)) return "GrowthTrack authorization expired or was removed. Connect again."; + return "GrowthTrack could not complete authorization (" + r.status + "). Connect again."; + } + private void applyResults(JSONArray batch, JSONObject response) throws JSONException, IOException { + int accepted = count(response, "accepted"), duplicates = count(response, "duplicates"); + JSONArray rejected = response.getJSONArray("rejected"); Set submitted = ids(batch), seen = new HashSet(); + Map reasons = new LinkedHashMap(); + for (int i = 0; i < rejected.length(); i++) { + JSONObject item = rejected.getJSONObject(i); String id = item.getString("external_session_id"); + if (!submitted.contains(id) || !seen.add(id)) throw new JSONException("Invalid result IDs"); + String reason = item.getString("reason"); + reasons.put(id, reason.equals("Insert failed") || reason.contains("retry later") ? "retry" : "Needs review: " + safeReason(reason)); + } + if ((long) accepted + duplicates + rejected.length() != batch.length()) throw new JSONException("Incomplete result counts"); + Set finished = new HashSet(); boolean retry = false; + for (String id : submitted) { + String reason = reasons.get(id); + if ("retry".equals(reason)) { retry = true; continue; } + receiptPut(id, reason == null ? "Received by GrowthTrack (sent or already present)" : reason); finished.add(id); + } + removeIds(finished); + put("message", accepted + " accepted, " + duplicates + " already present, " + rejected.length() + " rejected."); + if (!retry) put("attempts", 0); + put("retry_at", 0); save(); + if (retry) retry(null); + } + private static int count(JSONObject o, String field) throws JSONException { + Object value = o.get(field); if (!(value instanceof Number)) throw new JSONException("Invalid count"); + double n = ((Number) value).doubleValue(); if (!Double.isFinite(n) || n < 0 || n > Integer.MAX_VALUE || Math.floor(n) != n) throw new JSONException("Invalid count"); + return (int) n; + } + private JSONArray batch() throws IOException { + JSONArray out = new JSONArray(); int limit = Math.max(1, state.optInt("batch_limit", GrowthTrackProtocol.BATCH_SIZE)); + for (int i = 0; i < approved().length() && i < limit; i++) { + out.put(approved().optJSONObject(i)); + if (envelope(out).getBytes(StandardCharsets.UTF_8).length > GrowthTrackProtocol.MAX_BYTES) { + JSONArray smaller = new JSONArray(); for (int j = 0; j < out.length() - 1; j++) smaller.put(out.optJSONObject(j)); + if (smaller.length() == 0) throw new Problem("An approved session exceeds the request size limit. Cancel and review it."); + return smaller; + } + } + return out; + } + private String envelope(JSONArray list) throws Problem { try { return new JSONObject().put("schema_version", 1).put("sessions", list).toString(); } catch (JSONException e) { throw new Problem("Could not prepare the upload"); } } + private static Set ids(JSONArray rows) { Set out = new HashSet(); for (int i = 0; i < rows.length(); i++) out.add(rows.optJSONObject(i).optString("external_session_id")); return out; } + private JSONArray approved() { return state.optJSONArray("approved"); } + private void removeIds(Set ids) throws Problem { JSONArray remaining = new JSONArray(); for (int i = 0; i < approved().length(); i++) if (!ids.contains(approved().optJSONObject(i).optString("external_session_id"))) remaining.put(approved().optJSONObject(i)); put("approved", remaining); } + private void receiptPut(String id, String text) throws Problem { try { state.optJSONObject("receipts").put(id, text); } catch (JSONException e) { throw new Problem("Could not save the upload result"); } } + private void retry(String retryAfter) throws IOException { + int attempts = Math.min(10, state.optInt("attempts", 0) + 1); long delay = Math.min(3_600_000L, 5000L << (attempts - 1)); + if (retryAfter != null) try { delay = Math.max(delay, Math.min(86_400L, Math.max(0, Long.parseLong(retryAfter))) * 1000L); } catch (NumberFormatException ignored) { } + put("attempts", attempts); put("retry_at", clock.now() + delay); put("message", "Sessions are securely queued. Automatic retry is waiting for the connection or retry delay."); save(); + } + private void checkLinked(String scope) throws Problem { if (storageBroken) throw new Problem("Secure storage failed. Disconnect/reset this connection before continuing."); JSONObject t = state.optJSONObject("tokens"); if (t == null) throw new Problem("Connect to GrowthTrack first"); if (!hasScope(t.optString("scope"), scope)) throw new Problem("Connect again to allow " + scope); } + private static boolean hasScope(String scopes, String scope) { for (String s : scopes.trim().split("\\s+")) if (scope.equals(s)) return true; return false; } + private void clearAuthority(String message) throws Problem { state.remove("tokens"); state.remove("pending"); state.remove("connection_id"); state.remove("approved_connection_id"); put("refreshing", false); put("exchanging", false); put("automatic_sync", false); put("sync_paused", false); put("approved", new JSONArray()); put("retry_at", 0); put("message", message); } + private JSONObject pendingJson(GrowthTrackProtocol.Pending p) throws Problem { try { return p.json(); } catch (JSONException e) { throw new Problem("Could not save authorization"); } } + private void put(String key, Object value) throws Problem { try { state.put(key, value); } catch (JSONException e) { throw new Problem("Could not save connection state"); } } + private void save() throws IOException { put("version", 1); try { store.write(new JSONObjectCopy(state).value); } catch (IOException e) { storageBroken = true; cancelled = true; throw e; } } + private static final class JSONObjectCopy { final JSONObject value; JSONObjectCopy(JSONObject original) throws Problem { try { value = new JSONObject(original.toString()); } catch (JSONException e) { throw new Problem("Could not save connection state"); } } } + private static String safeReason(String reason) { return reason.replaceAll("(?:gta|gtr|gtc|gtp)_[A-Za-z0-9_-]+", "[redacted]").substring(0, Math.min(200, reason.replaceAll("(?:gta|gtr|gtc|gtp)_[A-Za-z0-9_-]+", "[redacted]").length())); } + private static void validateSession(JSONObject s) throws JSONException { + Set allowed = new HashSet(java.util.Arrays.asList("external_session_id", "started_at", "completed_at", "routine_name", "total_duration_seconds", "time_under_tension_seconds", "pressure_peak_inhg", "method", "sets")); + for (java.util.Iterator it = s.keys(); it.hasNext();) if (!allowed.contains(it.next())) throw new JSONException("Unapproved session field"); + if (s.getString("external_session_id").length() > 128 || s.getString("external_session_id").isEmpty() + || s.getString("routine_name").isEmpty() || s.getString("routine_name").length() > 200) throw new JSONException("Invalid session identity"); + long started = timestamp(s.getString("started_at")), completed = timestamp(s.getString("completed_at")); + if (completed <= started) throw new JSONException("Invalid session timestamps"); + int seconds = count(s, "total_duration_seconds"); if (seconds < 1 || seconds > 86400) throw new JSONException("Invalid duration"); + if (s.has("time_under_tension_seconds")) { int tension = count(s, "time_under_tension_seconds"); if (tension < 1 || tension > seconds) throw new JSONException("Invalid time under tension"); } + if (s.has("pressure_peak_inhg")) { + Object value = s.get("pressure_peak_inhg"); + if (!(value instanceof Number)) throw new JSONException("Invalid pressure"); + double peak = ((Number) value).doubleValue(); + if (!Double.isFinite(peak) || peak < 0 || peak > 29.92) throw new JSONException("Invalid pressure"); + } + JSONArray sets = s.optJSONArray("sets"); + if (sets == null || sets.length() == 0) { if (!GrowthTrackSession.method(s.getString("method"))) throw new JSONException("Invalid method"); } + else { + if (sets.length() > 500) throw new JSONException("Too many sets"); + long totalSetSeconds = 0; Set indexes = new HashSet(); + for (int i = 0; i < sets.length(); i++) { + JSONObject set = sets.getJSONObject(i); + for (java.util.Iterator it = set.keys(); it.hasNext();) if (!java.util.Arrays.asList("method", "set_index", "duration_seconds").contains(it.next())) throw new JSONException("Unapproved set field"); + int index = count(set, "set_index"); + if (index < 1 || !indexes.add(index) || !GrowthTrackSession.method(set.getString("method"))) throw new JSONException("Invalid set"); + if (!set.isNull("duration_seconds")) { + int duration = count(set, "duration_seconds"); if (duration < 1 || duration > seconds) throw new JSONException("Invalid set duration"); + totalSetSeconds += duration; + } + } + if (totalSetSeconds > seconds) throw new JSONException("Set durations exceed the session duration"); + } + } + private static long timestamp(String text) throws JSONException { + if (!text.matches("\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{3}Z")) throw new JSONException("Invalid timestamp"); + java.text.SimpleDateFormat format = new java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", java.util.Locale.ROOT); + format.setLenient(false); format.setTimeZone(java.util.TimeZone.getTimeZone("UTC")); + java.text.ParsePosition pos = new java.text.ParsePosition(0); java.util.Date date = format.parse(text, pos); + if (date == null || pos.getIndex() != text.length()) throw new JSONException("Invalid timestamp"); + return date.getTime(); + } +} diff --git a/core/src/main/java/org/openpump/GrowthTrackOnboarding.java b/core/src/main/java/org/openpump/GrowthTrackOnboarding.java new file mode 100644 index 0000000..c89e56d --- /dev/null +++ b/core/src/main/java/org/openpump/GrowthTrackOnboarding.java @@ -0,0 +1,13 @@ +package org.openpump; + +/** A one-time invitation only. It grants no authority and never queues or starts a run. */ +public final class GrowthTrackOnboarding { + private GrowthTrackOnboarding() { } + public static boolean shouldOffer(Model m, boolean runLive, boolean unreadable) { + if (m.growthTrackOfferSeen || runLive || unreadable) return false; + for (Model.Sess s : m.sessLog.all) if (!s.sim) return false; + return true; + } + /** Mark when displayed: Back, dismissal or process death must not become repeated nags. */ + public static void presented(Model m) { m.growthTrackOfferSeen = true; } +} diff --git a/core/src/main/java/org/openpump/GrowthTrackProtocol.java b/core/src/main/java/org/openpump/GrowthTrackProtocol.java new file mode 100644 index 0000000..153cc6a --- /dev/null +++ b/core/src/main/java/org/openpump/GrowthTrackProtocol.java @@ -0,0 +1,127 @@ +package org.openpump; + +import java.net.URI; +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.SecureRandom; +import java.util.LinkedHashMap; +import java.util.Map; +import org.json.JSONException; +import org.json.JSONObject; + +/** The public native-client contract. No partner key or GrowthTrack user JWT belongs here. */ +public final class GrowthTrackProtocol { + private GrowthTrackProtocol() { } + public static final String API = "https://rxtqbktyeetmbigbpbdd.supabase.co/functions/v1"; + public static final String CLIENT_ID = "gtc_bfa5809efbd314f246dd5d0f"; + public static final String REDIRECT = "gt-openpump://oauth/callback"; + public static final String CONNECTED_APPS = "https://pe-growth-track.com/connected-apps"; + public static final String WRITE = "sessions:write", READ = "routines:read"; + public static final int MAX_BYTES = 1_000_000; + // Below both the older pilot's 100 and the current handler's 500-session limit. + public static final int BATCH_SIZE = 100; + public static final long AUTH_LIFETIME_MS = 15 * 60_000L; + + public static final class Pending { + public final String state, verifier, scope; + public final long createdAt; + Pending(String state, String verifier, String scope, long createdAt) { + this.state = state; this.verifier = verifier; this.scope = scope; this.createdAt = createdAt; + } + public JSONObject json() throws JSONException { + return new JSONObject().put("state", state).put("verifier", verifier) + .put("scope", scope).put("created_at", createdAt); + } + public static Pending from(JSONObject o) throws JSONException { + String state = o.getString("state"), verifier = o.getString("verifier"); + if (!state.matches("[a-f0-9]{64}") || !verifier.matches("[A-Za-z0-9._~-]{43,128}")) + throw new JSONException("Invalid saved authorization"); + String scope = o.getString("scope"); + if (!WRITE.equals(scope) && !(WRITE + " " + READ).equals(scope)) + throw new JSONException("Invalid saved scope"); + return new Pending(state, verifier, scope, o.getLong("created_at")); + } + public String authorizeUrl() { + Map p = new LinkedHashMap(); + p.put("response_type", "code"); p.put("client_id", CLIENT_ID); p.put("redirect_uri", REDIRECT); + p.put("scope", scope); p.put("state", state); p.put("code_challenge", challenge(verifier)); + p.put("code_challenge_method", "S256"); + return API + "/oauth-authorize?" + form(p); + } + } + + public static Pending pending(boolean routines, long now) { + SecureRandom random = new SecureRandom(); + byte[] a = new byte[32], b = new byte[32]; random.nextBytes(a); random.nextBytes(b); + return new Pending(hex(a), hex(b), WRITE + (routines ? " " + READ : ""), now); + } + public static String challenge(String verifier) { + if (verifier == null || !verifier.matches("[A-Za-z0-9._~-]{43,128}")) + throw new IllegalArgumentException("Invalid PKCE verifier"); + return base64Url(digest(verifier)); + } + public static String stableId(String id) { + if (id == null || id.trim().isEmpty()) throw new IllegalArgumentException("This record has no stable session ID"); + return id.length() <= 125 ? "op_" + id : "op_" + hex(digest(id)); + } + + public static final class Callback { + public final String code, error; + Callback(String code, String error) { this.code = code; this.error = error; } + } + /** Validate the exact callback and state before consuming anything or making a request. */ + public static Callback callback(String value, Pending pending, long now) { + if (pending == null) throw new IllegalArgumentException("No connection request is waiting"); + if (now < pending.createdAt || now - pending.createdAt > AUTH_LIFETIME_MS) + throw new IllegalArgumentException("Connection request expired. Connect again"); + if (value == null || value.length() > 8192) throw new IllegalArgumentException("Invalid connection callback"); + try { + URI u = new URI(value); + if (!"gt-openpump".equals(u.getScheme()) || !"oauth".equals(u.getHost()) + || !"/callback".equals(u.getRawPath()) || u.getPort() != -1 + || u.getUserInfo() != null || u.getFragment() != null) + throw new IllegalArgumentException("Unexpected connection callback"); + Map p = new LinkedHashMap(); + if (u.getRawQuery() == null) throw new IllegalArgumentException("Missing connection response"); + for (String pair : u.getRawQuery().split("&", -1)) { + String[] parts = pair.split("=", 2); + String key = decode(parts[0]), val = parts.length == 2 ? decode(parts[1]) : ""; + if (!("state".equals(key) || "code".equals(key) || "error".equals(key) || "error_description".equals(key)) + || p.put(key, val) != null) throw new IllegalArgumentException("Invalid connection response parameters"); + } + String state = p.get("state"); + if (state == null || !MessageDigest.isEqual(pending.state.getBytes(StandardCharsets.UTF_8), + state.getBytes(StandardCharsets.UTF_8))) throw new IllegalArgumentException("Connection response did not match this request"); + String code = p.get("code"), error = p.get("error"); + if ((code == null) == (error == null)) throw new IllegalArgumentException("Invalid connection response"); + if (code != null && !code.matches("gtc_[A-Za-z0-9_-]{20,256}")) + throw new IllegalArgumentException("Invalid authorization code"); + if (error != null && !error.matches("[a-z_]{1,64}")) throw new IllegalArgumentException("Invalid authorization response"); + return new Callback(code, error); + } catch (IllegalArgumentException e) { throw e; } + catch (Exception e) { throw new IllegalArgumentException("Invalid connection callback"); } + } + + public static String form(Map values) { + StringBuilder out = new StringBuilder(); + for (Map.Entry e : values.entrySet()) { + if (out.length() != 0) out.append('&'); + out.append(encode(e.getKey())).append('=').append(encode(e.getValue())); + } + return out.toString(); + } + private static String encode(String s) { try { return URLEncoder.encode(s, "UTF-8"); } catch (Exception e) { throw new AssertionError(e); } } + private static String decode(String s) { try { return URLDecoder.decode(s, "UTF-8"); } catch (Exception e) { throw new IllegalArgumentException("Invalid callback encoding"); } } + private static byte[] digest(String s) { try { return MessageDigest.getInstance("SHA-256").digest(s.getBytes(StandardCharsets.UTF_8)); } catch (Exception e) { throw new AssertionError(e); } } + private static String hex(byte[] bytes) { StringBuilder s = new StringBuilder(); for (byte b : bytes) s.append(String.format(java.util.Locale.ROOT, "%02x", b & 255)); return s.toString(); } + // java.util.Base64 is API 26; the application also supports Android 24 and 25. + private static String base64Url(byte[] data) { + String alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; + StringBuilder out = new StringBuilder(); int bits = 0, buffer = 0; + for (byte b : data) { buffer = (buffer << 8) | (b & 255); bits += 8; while (bits >= 6) { bits -= 6; out.append(alphabet.charAt((buffer >>> bits) & 63)); } } + if (bits > 0) out.append(alphabet.charAt((buffer << (6 - bits)) & 63)); + return out.toString(); + } +} diff --git a/core/src/main/java/org/openpump/GrowthTrackSession.java b/core/src/main/java/org/openpump/GrowthTrackSession.java new file mode 100644 index 0000000..9dc350f --- /dev/null +++ b/core/src/main/java/org/openpump/GrowthTrackSession.java @@ -0,0 +1,172 @@ +package org.openpump; + +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Locale; +import java.util.TimeZone; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +/** An allowlist mapper, deliberately independent of Model's broad persistence/export JSON. */ +public final class GrowthTrackSession { + private GrowthTrackSession() { } + public static final String[] METHODS = { + "static_pumping", "interval_pumping", "rip", "sleeved_pumping", "length_pumping", + "milking", "high_pressure_milking", "pac", "airlock_clamping", "clamping", "hard_clamping", + "soft_clamping", "hypoxic_clamping", "manual_clamping", "clamped_ulis", "ulis", + "hanging", "static_hanging", "interval_hanging", "bundle_hanging", "fulcrum_hanging", "rapid_interval_hanging", + "extending", "static_extending", "interval_extending", "bundle_extending", "rapid_interval_vacuum_extending", + "jelqing", "manuals", "fowfers", "bundled_stretching", "mandingo_stretch", "fulcrum_bends", + "semi_erect_bends", "vibra_tugging", "kegels", "reverse_kegels", "angion_1", "angion_2", "angion_3", "angion_4", + "sabre", "warm_up", "massage", "edging", "ads", "bfr", "fire_goat_rolls", "tunica_shears" + }; + public static boolean method(String slug) { for (String m : METHODS) if (m.equals(slug)) return true; return false; } + + /** Positive UPPER hold duration, excluding release/rest. Classify before any wire rounding. */ + public static String methodForHoldMillis(double holdMs) { + if (!Double.isFinite(holdMs) || holdMs <= 0) throw new IllegalArgumentException("A positive recorded hold duration is required"); + if (holdMs <= 6000) return "milking"; + if (holdMs <= 30000) return "rip"; + if (holdMs <= 120000) return "interval_pumping"; + return "static_pumping"; + } + public static String methodLabel(String method) { + if ("milking".equals(method)) return "Milking"; + if ("rip".equals(method)) return "RIP"; + if ("interval_pumping".equals(method)) return "Interval pumping"; + if ("static_pumping".equals(method)) return "Static pumping"; + if ("length_pumping".equals(method)) return "Length pumping"; + return method.replace('_', ' '); + } + + public static final class Part { + public final String label, method; + public final int seconds; + public final long recordedMillis, holdMillis; + Part(String label, long recordedMillis, long holdMillis, String method) { + this.label = label; this.recordedMillis = recordedMillis; this.seconds = (int) (recordedMillis / 1000L); + this.holdMillis = holdMillis; this.method = method; + } + } + public static final class Draft { + public final String id, name, startedAt, completedAt; + public final int seconds; + public final Double observedPeakInhg; + public final Integer tensionSeconds; + public final List parts = new ArrayList(); + public final List warnings = new ArrayList(); + private String wholeMethod; + public String methodForWholeSession() { return wholeMethod; } + Draft(Model.Sess s) { + if (s == null || s.sim) throw new IllegalArgumentException("Simulated sessions cannot be sent to GrowthTrack"); + id = GrowthTrackProtocol.stableId(s.id); + if (s.recordedStartMs <= 0 || s.recordedEndMs <= s.recordedStartMs || s.durSec < 1 || s.durSec > 86400 + || s.durSec * 1000L > s.recordedEndMs - s.recordedStartMs + 1000L) + throw new IllegalArgumentException("Original wall-clock session boundaries are unavailable or inconsistent; older or rejoined history cannot be sent"); + name = s.routineName == null || s.routineName.trim().isEmpty() ? "OpenPump session" : s.routineName.trim(); + if (name.length() > 200) throw new IllegalArgumentException("Routine name is longer than GrowthTrack's 200-character limit"); + seconds = (int) s.durSec; + // Only explicitly recorded wall-clock facts, never the ambiguous legacy day/key stamp. + startedAt = iso(s.recordedStartMs); completedAt = iso(s.recordedEndMs); + double peak = s.peakKpa == null ? Double.NaN : s.peakKpa / 3.38639; + observedPeakInhg = Double.isFinite(peak) && peak >= 0 && peak <= 29.92 ? peak : null; + if (s.peakKpa != null && observedPeakInhg == null) warnings.add("The implausible observed peak is omitted."); + // Gross is sealed elapsed time INCLUDING releases. Net is the saved measured, + // phase-filtered training time; never substitute gross or commanded duty cycle. + double tup = s.netTupSec == null ? Double.NaN : s.netTupSec; + tensionSeconds = Double.isFinite(tup) && tup >= 1 && tup <= seconds ? (int) Math.floor(tup) : null; + if (s.netTupSec != null && tensionSeconds == null) warnings.add("The unavailable or inconsistent measured net time under pressure is omitted."); + if (!s.completed) warnings.add("This was stopped early. Only its recorded elapsed work is sent."); + } + public JSONObject payload() throws JSONException { + if (parts.isEmpty() && wholeMethod == null) throw new IllegalArgumentException("No classifiable recorded work exists for this session"); + JSONObject o = new JSONObject().put("external_session_id", id).put("started_at", startedAt) + .put("completed_at", completedAt).put("routine_name", name).put("total_duration_seconds", seconds); + if (observedPeakInhg != null) o.put("pressure_peak_inhg", observedPeakInhg); + if (tensionSeconds != null) o.put("time_under_tension_seconds", tensionSeconds); + if (wholeMethod != null) { o.put("method", wholeMethod); return o; } + JSONArray sets = new JSONArray(); + for (int i = 0; i < parts.size(); i++) { + Part part = parts.get(i); + JSONObject set = new JSONObject().put("set_index", i + 1).put("method", part.method); + // GT v1 only accepts whole positive seconds. Null preserves a subsecond + // segment's technique without fabricating a second of delivered work. + set.put("duration_seconds", part.seconds > 0 ? part.seconds : JSONObject.NULL); + sets.put(set); + } + o.put("sets", sets); + return o; + } + } + + public static Draft draft(Model.Sess s, AsRun run) { + return draft(s, run, null); + } + public static Draft draft(Model.Sess s, AsRun run, Model model) { + Draft draft = new Draft(s); + Model.AsRunSnapshot snap = run != null && run.snap != null ? run.snap : s.asRunSnapshot; + boolean length = snap != null && snap.trainerTrack == Plan.TRACK_LENGTH; + // Legacy snapshots have no frozen marker. Only use a structured marker on a + // routine whose full saved structure/values still match, never a free-text name. + if (snap != null && snap.trainerTrack < 0 && model != null) { + Model.Routine routine = model.routine(s.routineId); + length = routine != null && routine.trainerTrack == Plan.TRACK_LENGTH && snap.matches(routine, model); + } + if (run == null || run.rows.isEmpty()) { + if (length) { + draft.wholeMethod = "length_pumping"; + draft.warnings.add("The recorded Length track labels this sitting. No detailed set breakdown exists; only saved session elapsed/net figures are sent, with no invented work set."); + return draft; + } + throw new IllegalArgumentException("Detailed hold recording is unavailable; an automatic technique cannot be determined"); + } + LinkedHashMap durations = new LinkedHashMap(); + LinkedHashMap names = new LinkedHashMap(); + LinkedHashMap holds = new LinkedHashMap(); + LinkedHashMap methods = new LinkedHashMap(); + long previousEnd = 0; + for (AsRun.Row row : run.rows) { + if (row.kind == AsRun.SKIPPED) continue; + if (row.t0 < 0 || row.t1 < row.t0 || row.t0 < previousEnd || row.t1 > s.durSec * 1000L + 1000L) + throw new IllegalArgumentException("The detailed recording has inconsistent timing"); + previousEnd = row.t1; + if (!row.hasValues() || row.span() == 0) continue; + if (!row.confirmed) { draft.warnings.add("An unconfirmed pump change is excluded from the set breakdown."); continue; } + if (row.up <= 0) { draft.warnings.add("A zero-pressure part is excluded from the work breakdown."); continue; } + } + for (GrowthTrackTiming.Span span : GrowthTrackTiming.read(run, snap, length)) { + AsRun.Row row = span.row; + long holdMs = span.holdMs; + String method = length ? "length_pumping" : methodForHoldMillis(holdMs); + String key = row.stageIdx + ":" + row.pos + ":" + row.setId + ":" + method + ":" + holdMs; + Long before = durations.get(key); + durations.put(key, (before == null ? 0L : before) + span.workMs); + String label = "Recorded set " + (row.pos + 1); + if (snap != null && row.stageIdx >= 0 && row.stageIdx < snap.stages.size()) { + Model.AsRunSnapshot.StageSnap stage = snap.stages.get(row.stageIdx); + Model.AsRunSnapshot.SetSnap set = stage.setAt(row.pos); + label = stage.name + (set != null && set.name != null && !set.name.isEmpty() ? " / " + set.name : ""); + } + names.put(key, label); + holds.put(key, holdMs); methods.put(key, method); + } + int total = 0; + for (String key : durations.keySet()) { + int seconds = (int) (durations.get(key) / 1000L); + draft.parts.add(new Part(names.get(key), durations.get(key), holds.get(key), methods.get(key))); total += seconds; + } + if (draft.parts.size() > 500 || total > draft.seconds) throw new IllegalArgumentException("The recorded set breakdown exceeds GrowthTrack's limits"); + if (draft.parts.isEmpty()) throw new IllegalArgumentException("No confirmed positive-pressure work exists to send"); + draft.warnings.add(length ? "The structured Length track overrides hold-duration labels for every part." : "Techniques use confirmed command intervals, clipped to delivered recording time and joined across verified stitch boundaries: up to 6 s Milking; up to 30 s RIP; up to 120 s Interval pumping; longer Static pumping. Release/rest do not affect this label."); + draft.warnings.add("Set durations retain recorded work spans, including their release halves. Separate rest and interrupted periods are excluded. Measured net time under pressure excludes release/rest; gross sealed time is not substituted."); + draft.warnings.add("Command phases are reconstructed from confirmed holds/releases and elapsed spans, not sampled pressure plateaus. Ambiguous paused, lost-link or unverified stitched recordings are unavailable for automatic export. No measured plateau or pressure average is invented. GrowthTrack's whole-second durations round down; subsecond set duration is left unknown."); + return draft; + } + private static String iso(long millis) { + SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.ROOT); + f.setTimeZone(TimeZone.getTimeZone("UTC")); return f.format(new java.util.Date(millis)); + } +} diff --git a/core/src/main/java/org/openpump/GrowthTrackTiming.java b/core/src/main/java/org/openpump/GrowthTrackTiming.java new file mode 100644 index 0000000..115df35 --- /dev/null +++ b/core/src/main/java/org/openpump/GrowthTrackTiming.java @@ -0,0 +1,73 @@ +package org.openpump; + +import java.util.ArrayList; +import java.util.List; + +/** Reads confirmed command intervals; it does not infer measured pressure plateaus. */ +final class GrowthTrackTiming { + static final class Span { + final AsRun.Row row; final long workMs, holdMs; + Span(AsRun.Row row, long workMs, long holdMs) { this.row = row; this.workMs = workMs; this.holdMs = holdMs; } + } + static List read(AsRun run, Model.AsRunSnapshot snap, boolean length) { + List out = new ArrayList(); + List planned = Model.planFromSnapshot(snap); + long carryHold = 0, carryWork = 0; + AsRun.Row previous = null, carryRow = null; + for (AsRun.Row row : run.rows) { + if (row.kind == AsRun.SKIPPED) continue; + if (!length && row.span() > 0 && (row.kind == AsRun.HOLD || row.kind == AsRun.LOST)) + throw unknown("a pause or lost link does not retain the pressure-phase timeline"); + if (!row.hasValues() || row.span() == 0 || !row.confirmed || row.up <= 0) { + if (carryHold > 0) throw unknown("a stitch continuation is missing or unconfirmed"); + continue; + } + if (length) { out.add(new Span(row, row.span(), 0)); continue; } + if (row.uhWire <= 0 || row.lh < 0) throw unknown("a positive upper hold is unavailable"); + int part = part(row, planned); + if (part < 0 && row.uhWire == Proto.WIRE_HOLD_MAX && row.lh == 1 && row.lo == row.up - 1) + throw unknown("this older wire-sized hold has no verified stitch boundary"); + if (carryHold > 0 && (previous == null || row.t0 != previous.t1 || row.up != previous.up + || row.stageIdx != previous.stageIdx || row.pos != previous.pos || !row.setId.equals(previous.setId) + || row.ordinal != previous.ordinal + 1 || row.kind != AsRun.PLAN)) + throw unknown("a stitched hold was interrupted or changed"); + if (part == 1) { + if (row.kind != AsRun.PLAN || row.uhWire != Proto.WIRE_HOLD_MAX || row.lh != 1 + || row.lo != row.up - 1 || row.span() > row.uhWire * 1000L) + throw unknown("the delivered stitch no longer matches its recorded boundary"); + if (carryRow == null) carryRow = row; + carryHold += row.span(); carryWork += row.span(); previous = row; + continue; + } + long upper = row.uhWire * 1000L, release = row.lh * 1000L; + if (release == 0) { + // With no release the positive command is continuous across device repeats. + out.add(new Span(carryRow == null ? row : carryRow, carryWork + row.span(), carryHold + row.span())); + } else { + long remaining = row.span(); + while (remaining > 0) { + long held = Math.min(remaining, upper); + long work = Math.min(remaining, upper + release); + out.add(new Span(carryRow == null ? row : carryRow, carryWork + work, carryHold + held)); + carryHold = 0; carryWork = 0; carryRow = null; + remaining -= work; + } + } + carryHold = 0; carryWork = 0; carryRow = null; previous = row; + } + // A stopped final chunk retains only its recorded, clipped hold duration. + if (carryHold > 0) out.add(new Span(carryRow, carryWork, carryHold)); + return out; + } + private static int part(AsRun.Row row, List plan) { + if (row.cyclePart >= 0) return row.cyclePart; + if (row.kind != AsRun.PLAN) return -1; + for (Model.Preset p : plan) if (p.stageIdx == row.stageIdx && p.pos == row.pos && p.ordinal == row.ordinal + && p.setId.equals(row.setId) && p.uh == row.uhWire && p.uh == row.uhReq && p.lh == row.lh) + return p.cyclePart ? 1 : 0; + return -1; + } + private static IllegalArgumentException unknown(String reason) { + return new IllegalArgumentException("Automatic technique unavailable: " + reason + ". This recording cannot be sent."); + } +} diff --git a/core/src/main/java/org/openpump/Model.java b/core/src/main/java/org/openpump/Model.java index 970d8c5..c83cef8 100644 --- a/core/src/main/java/org/openpump/Model.java +++ b/core/src/main/java/org/openpump/Model.java @@ -4688,6 +4688,10 @@ private static int b64Val(char c) { */ public static final class AsRunSnapshot { public String routineId = ""; + /** Frozen structured track for connected-app classification. -1 means an old + * snapshot did not record it; 0 is a known unmarked routine. Never inferred + * from the routine/stage names or a later edited routine. */ + public int trainerTrack = -1; public final List stages = new ArrayList(); /** One stage as it stood at run start: its name, the set ids it held, in order, and @@ -4814,6 +4818,7 @@ public static AsRunSnapshot of(Routine r, Model m) { if (r == null) return null; AsRunSnapshot s = new AsRunSnapshot(); s.routineId = r.id == null ? "" : r.id; + s.trainerTrack = r.trainerTrack; for (int i = 0; i < r.stages.size(); i++) { Stage src = r.stages.get(i); StageSnap st = new StageSnap(); @@ -4874,6 +4879,7 @@ public boolean matches(Routine r, Model m) { public JSONObject toJson() throws JSONException { JSONObject o = new JSONObject(); o.put("rid", routineId); + if (trainerTrack >= 0) o.put("trainerTrack", trainerTrack); JSONArray a = new JSONArray(); for (int i = 0; i < stages.size(); i++) { StageSnap st = stages.get(i); @@ -4898,6 +4904,8 @@ public static AsRunSnapshot fromJson(JSONObject o) { if (o == null) return null; AsRunSnapshot s = new AsRunSnapshot(); s.routineId = o.optString("rid", ""); + s.trainerTrack = o.optInt("trainerTrack", -1); + if (s.trainerTrack < -1 || s.trainerTrack > 4) s.trainerTrack = -1; JSONArray a = o.optJSONArray("stages"); if (a != null) for (int i = 0; i < a.length(); i++) { JSONObject so = a.optJSONObject(i); @@ -4942,7 +4950,12 @@ public static AsRunSnapshot fromJson(JSONObject o) { public static final class Sess { public String id; public String routineId, routineName; - public long ts; // epoch millis at filing + public long ts; // legacy day/key stamp; old releases filed end, current releases start + /** Explicit wall-clock boundaries for optional partner export. Zero means unknown; + * never infer the meaning of an old ts or reconstruct gaps between rejoined parts. */ + public long recordedStartMs, recordedEndMs; + /** Non-secret browser-grant identity captured at filing; empty records never enter sync. */ + public String growthTrackConnectionId = ""; public long durSec; // the SAME elapsed the summary displayed public boolean completed; // false = stopped early (an attempt) /** True when this was a MANUAL run (Task 18) — an ephemeral cycle, not a routine. @@ -5419,6 +5432,9 @@ public JSONObject toJson() throws JSONException { // keeps the phone and the self-test on the same code path. o.put("ts", String.valueOf(ts)); o.put("dur", String.valueOf(durSec)); + if (recordedStartMs > 0) o.put("recordedStartMs", String.valueOf(recordedStartMs)); + if (recordedEndMs > 0) o.put("recordedEndMs", String.valueOf(recordedEndMs)); + if (!growthTrackConnectionId.isEmpty()) o.put("growthTrackConnectionId", growthTrackConnectionId); o.put("done", completed); o.put("man", manual); o.put("tag", tag == null ? "" : tag); @@ -5535,6 +5551,9 @@ public static Sess fromJson(JSONObject o) { s.countedTrack = o.optInt("countedTrack", TRAINER_TRACK_NONE); s.routineName = o.optString("rname", ""); s.ts = parseLong(o.optString("ts", "0")); + s.recordedStartMs = parseLong(o.optString("recordedStartMs", "0")); + s.recordedEndMs = parseLong(o.optString("recordedEndMs", "0")); + s.growthTrackConnectionId = o.optString("growthTrackConnectionId", ""); s.durSec = parseLong(o.optString("dur", "0")); s.completed = o.optBoolean("done"); // Absent (false) on every session filed before Task 18 — a routine run, which is @@ -7986,6 +8005,8 @@ public static int clampRunStripWhere(int w) { * restarting it; see {@link FirstRun}. */ public int firstRun = FirstRun.DONE; + /** Old phones have already set up; only seed creates an unshown optional invitation. */ + public boolean growthTrackOfferSeen = true; /** "Whole app" — gates the COLD OPEN only (the very first content this Activity shows * after a fresh launch), never re-checked for the rest of that process's lifetime. * See AppLock#isLocked's own doc for why this scope's re-lock rule differs from the @@ -11645,6 +11666,7 @@ public static double actualRatePerYear(long fromTs, double fromCm, /** What a brand-new install starts with. Nothing here is required to be kept. */ public static Model seed() { Model m = new Model(); + m.growthTrackOfferSeen = false; m.firstRun = FirstRun.NOT_STARTED; // the one place a setup is ever owed // One example per distinct behaviour the pump can run, so a fresh install shows what // each mode is rather than an empty library. All well inside the default 40 kPa @@ -11747,6 +11769,7 @@ public String toJson() { o.put("blur", privacyBlur); o.put("lockOn", appLockOn); o.put("firstRun", firstRun); + o.put("growthTrackOfferSeen", growthTrackOfferSeen); o.put("lockWhole", appLockWholeApp); o.put("lockPhotos", appLockPhotos); o.put("lockMeas", appLockMeasurements); @@ -12136,6 +12159,7 @@ public static Model fromJson(String s) { m.appLockOn = o.optBoolean("lockOn", false); // MIGRATION: absent in every file written before the first-run setup existed, and // those phones are all set up already - so absent means DONE, never "not started". + m.growthTrackOfferSeen = o.optBoolean("growthTrackOfferSeen", true); m.firstRun = Math.max(FirstRun.NOT_STARTED, Math.min(FirstRun.DONE, o.optInt("firstRun", FirstRun.DONE))); m.appLockWholeApp = o.optBoolean("lockWhole", false); diff --git a/core/src/test/java/Migrate.java b/core/src/test/java/Migrate.java index e845ca9..275c3e7 100644 --- a/core/src/test/java/Migrate.java +++ b/core/src/test/java/Migrate.java @@ -4,6 +4,42 @@ public class Migrate { // `throws Exception` only for the org.json constructor below, which is checked. public static void main(String[] a) throws Exception { + // GrowthTrack: old snapshots have no recorded designation, while new snapshots + // freeze the structured track and keep it through later routine edits/deletion. + Model.AsRunSnapshot gtOld = Model.AsRunSnapshot.fromJson( + new org.json.JSONObject("{\"rid\":\"legacy\",\"stages\":[]}")); + if (gtOld.trainerTrack != -1 || gtOld.toJson().has("trainerTrack")) + throw new AssertionError("Old track metadata must remain unknown"); + Model.Routine gtRoutine = new Model.Routine(); gtRoutine.id = "gt-fixture"; + gtRoutine.trainerTrack = org.openpump.Plan.TRACK_LENGTH; + Model.AsRunSnapshot gtFrozen = Model.AsRunSnapshot.of(gtRoutine, new Model()); + gtRoutine.trainerTrack = Model.TRAINER_TRACK_NONE; + if (Model.AsRunSnapshot.fromJson(gtFrozen.toJson()).trainerTrack != org.openpump.Plan.TRACK_LENGTH + || Model.AsRunSnapshot.fromJson(Model.AsRunSnapshot.of(gtRoutine, new Model()).toJson()).trainerTrack != 0) + throw new AssertionError("Frozen Length and known unmarked metadata must round-trip"); + System.out.println("GrowthTrack snapshot: old unknown / frozen Length / known unmarked preserved"); + + org.openpump.AsRun.Row gtOldRow = org.openpump.AsRun.Row.fromJson(new org.json.JSONObject()); + if (gtOldRow.cyclePart != -1 || gtOldRow.toJson().has("cyclePart")) throw new AssertionError("Old stitch boundary must remain unknown"); + gtOldRow.cyclePart = 1; + if (org.openpump.AsRun.Row.fromJson(gtOldRow.toJson()).cyclePart != 1) throw new AssertionError("Frozen stitch boundary must round-trip"); + Model gtFresh = Model.seed(); + if (gtFresh.growthTrackOfferSeen || !Model.fromJson("{\"sets\":[],\"routines\":[]}").growthTrackOfferSeen) throw new AssertionError("Optional invitation migration must never nag upgrades"); + org.openpump.GrowthTrackOnboarding.presented(gtFresh); + if (!Model.fromJson(gtFresh.toJson()).growthTrackOfferSeen) throw new AssertionError("Invitation dismissal must round-trip"); + System.out.println("GrowthTrack timing/onboarding: unknown old boundaries, frozen new boundaries, one-time offer preserved"); + + Model.Sess gtLegacyTime = Model.Sess.fromJson(new org.json.JSONObject("{\"ts\":\"1700000000000\",\"dur\":\"120\"}")); + if (gtLegacyTime.recordedStartMs != 0 || gtLegacyTime.recordedEndMs != 0) throw new AssertionError("Old wall-clock boundaries must not be invented"); + gtLegacyTime.recordedStartMs = 1700000000000L; gtLegacyTime.recordedEndMs = 1700000120000L; + Model.Sess gtTimeRoundTrip = Model.Sess.fromJson(gtLegacyTime.toJson()); + if (gtTimeRoundTrip.recordedStartMs != gtLegacyTime.recordedStartMs || gtTimeRoundTrip.recordedEndMs != gtLegacyTime.recordedEndMs) throw new AssertionError("Explicit wall-clock boundaries must round-trip"); + + if (!gtLegacyTime.growthTrackConnectionId.isEmpty()) throw new AssertionError("Legacy history must not opt into sync"); + gtLegacyTime.growthTrackConnectionId = "fictional-browser-grant"; + if (!Model.Sess.fromJson(gtLegacyTime.toJson()).growthTrackConnectionId.equals(gtLegacyTime.growthTrackConnectionId)) + throw new AssertionError("Completion grant marker must round-trip for crash recovery"); + // EXACTLY what build 30 (pre-stage) wrote to model.json String old = "{\"ceil\":40,\"unit\":\"inHg\",\"selected\":\"r1\"," + "\"sets\":[{\"id\":\"s1\",\"name\":\"Gentle Warm\",\"ramp\":false,\"up\":14," diff --git a/core/src/test/java/org/openpump/GrowthTrackAutoSyncTest.java b/core/src/test/java/org/openpump/GrowthTrackAutoSyncTest.java new file mode 100644 index 0000000..40206e4 --- /dev/null +++ b/core/src/test/java/org/openpump/GrowthTrackAutoSyncTest.java @@ -0,0 +1,116 @@ +package org.openpump; + +import static org.junit.jupiter.api.Assertions.*; +import java.io.IOException; +import org.json.JSONArray; +import org.json.JSONObject; +import org.junit.jupiter.api.Test; + +/** Fictional completion events, encrypted-store seam and mocked HTTP only. */ +class GrowthTrackAutoSyncTest { + private final GrowthTrackIntegrationTest.Clock clock = new GrowthTrackIntegrationTest.Clock(); + private static AsRun run() { + AsRun run = new AsRun(); run.rows.add(GrowthTrackIntegrationTest.row(AsRun.PLAN, 0, 120_000, 0)); return run; + } + private static boolean complete(GrowthTrackIntegrationTest.Fixture f, String id) throws Exception { + return f.client.recordCompleted(GrowthTrackIntegrationTest.session(id), run(), new Model(), f.client.snapshot().connectionId); + } + @Test void connectedCompletionQueuesThenSyncsWithoutReviewOrSelection() throws Exception { + GrowthTrackIntegrationTest.Fixture f = new GrowthTrackIntegrationTest.Fixture(false); + f.http.acceptAll = true; + assertTrue(complete(f, "one")); assertEquals(1, f.client.snapshot().queued); + f.client.syncPending(); assertEquals(0, f.client.snapshot().queued); + assertEquals(1, f.http.requests.size()); assertTrue(f.client.receipt("op_one").startsWith("Received")); + } + @Test void everyDistinctCompletionAppendsEvenWithOtherSessionsPending() throws Exception { + GrowthTrackIntegrationTest.Fixture f = new GrowthTrackIntegrationTest.Fixture(false); + assertTrue(complete(f, "one")); assertTrue(complete(f, "two")); assertEquals(2, f.client.snapshot().queued); + f.http.acceptAll = true; f.client.syncPending(); + assertEquals(2, new JSONObject(f.http.requests.get(0).body).getJSONArray("sessions").length()); + } + @Test void duplicateCompletionBeforeAndAfterReceiptNeverCreatesAnotherTransfer() throws Exception { + GrowthTrackIntegrationTest.Fixture f = new GrowthTrackIntegrationTest.Fixture(false); f.http.acceptAll = true; + assertTrue(complete(f, "one")); assertFalse(complete(f, "one")); assertEquals(1, f.client.snapshot().queued); + f.client.syncPending(); assertFalse(complete(f, "one")); f.client.syncPending(); assertEquals(1, f.http.requests.size()); + } + @Test void disconnectedCompletionAndPreconnectionHistoryNeverQueue() throws Exception { + GrowthTrackIntegrationTest.Storage store = new GrowthTrackIntegrationTest.Storage(); + GrowthTrackIntegrationTest.Http http = new GrowthTrackIntegrationTest.Http(); + GrowthTrackClient client = new GrowthTrackClient(store, http, clock); + assertFalse(client.recordCompleted(GrowthTrackIntegrationTest.session("before"), run(), new Model(), "")); + assertEquals(-1, client.automaticRetryAt()); client.syncPending(); assertTrue(http.requests.isEmpty()); + GrowthTrackIntegrationTest.Fixture f = new GrowthTrackIntegrationTest.Fixture(false); + assertFalse(f.client.recordCompleted(GrowthTrackIntegrationTest.session("before"), run(), new Model(), "")); + assertEquals(0, f.client.snapshot().queued); + } + @Test void offlineQueueRestartsAndRetriesAfterDurableBackoff() throws Exception { + GrowthTrackIntegrationTest.Fixture f = new GrowthTrackIntegrationTest.Fixture(false); complete(f, "one"); + f.http.responses.add(new IOException("fictional offline")); assertThrows(IOException.class, f.client::syncPending); + long retryAt = f.client.automaticRetryAt(); assertTrue(retryAt > f.clock.now()); + GrowthTrackClient restarted = new GrowthTrackClient(f.storage, f.http, f.clock); + assertEquals(retryAt, restarted.automaticRetryAt()); restarted.syncPending(); assertEquals(1, f.http.requests.size()); + f.clock.time = retryAt; f.http.acceptAll = true; restarted.syncPending(); + assertEquals(0, restarted.snapshot().queued); assertEquals(2, f.http.requests.size()); + assertEquals("op_one", new JSONObject(f.http.requests.get(1).body).getJSONArray("sessions").getJSONObject(0).getString("external_session_id")); + } + @Test void filingMarkerRecoversCompletionLostBeforeEncryptedQueueSave() throws Exception { + GrowthTrackIntegrationTest.Fixture f = new GrowthTrackIntegrationTest.Fixture(false); + Model.Sess saved = GrowthTrackIntegrationTest.session("crash"); saved.growthTrackConnectionId = f.client.snapshot().connectionId; + saved = Model.Sess.fromJson(saved.toJson()); + GrowthTrackClient restarted = new GrowthTrackClient(f.storage, f.http, f.clock); + assertTrue(restarted.recordCompleted(saved, run(), new Model(), saved.growthTrackConnectionId)); + f.http.acceptAll = true; restarted.syncPending(); assertTrue(restarted.receipt("op_crash").startsWith("Received")); + } + @Test void relinkDiscardsQueueAndNeverClaimsOldOrDisconnectedCompletions() throws Exception { + GrowthTrackIntegrationTest.Fixture f = new GrowthTrackIntegrationTest.Fixture(false); String old = f.client.snapshot().connectionId; + complete(f, "old"); f.client.begin(false); assertEquals(0, f.client.snapshot().queued); + f.http.responses.add(GrowthTrackIntegrationTest.token(GrowthTrackIntegrationTest.R2, false)); + String state = f.storage.saved.getJSONObject("pending").getString("state"); + f.client.callback(GrowthTrackProtocol.REDIRECT + "?code=" + GrowthTrackIntegrationTest.CODE + "&state=" + state); + assertNotEquals(old, f.client.snapshot().connectionId); + assertFalse(f.client.recordCompleted(GrowthTrackIntegrationTest.session("old"), run(), new Model(), old)); + assertFalse(f.client.recordCompleted(GrowthTrackIntegrationTest.session("disconnected"), run(), new Model(), "")); + assertEquals(0, f.client.snapshot().queued); assertTrue(complete(f, "new")); + } + @Test void disconnectDuringTransferStopsRemainingBatchesAndRecovery() throws Exception { + GrowthTrackIntegrationTest.Fixture f = new GrowthTrackIntegrationTest.Fixture(false); + for (int i = 0; i < 101; i++) complete(f, "record" + i); + String old = f.client.snapshot().connectionId; f.http.acceptAll = true; + f.http.beforeReturn = () -> { try { f.client.disconnect(); } catch (IOException e) { throw new AssertionError(e); } }; + assertThrows(IOException.class, f.client::syncPending); assertEquals(1, f.http.requests.size()); + assertFalse(f.client.snapshot().connected); assertEquals(0, f.client.snapshot().queued); + assertFalse(f.client.recordCompleted(GrowthTrackIntegrationTest.session("late"), run(), new Model(), old)); + } + @Test void simulatedInvalidAndUnsupportedRecordsHaveDurableFailureStatuses() throws Exception { + GrowthTrackIntegrationTest.Fixture f = new GrowthTrackIntegrationTest.Fixture(false); String grant = f.client.snapshot().connectionId; + Model.Sess sim = GrowthTrackIntegrationTest.session("sim"); sim.sim = true; + assertFalse(f.client.recordCompleted(sim, run(), new Model(), grant)); + Model.Sess invalid = GrowthTrackIntegrationTest.session("invalid"); invalid.recordedEndMs = 0; + assertFalse(f.client.recordCompleted(invalid, run(), new Model(), grant)); + assertFalse(f.client.recordCompleted(GrowthTrackIntegrationTest.session("unsupported"), null, new Model(), grant)); + assertEquals(0, f.client.snapshot().queued); assertTrue(f.http.requests.isEmpty()); + GrowthTrackClient restarted = new GrowthTrackClient(f.storage, f.http, f.clock); + for (String id : new String[]{"sim", "invalid", "unsupported"}) assertTrue(restarted.receipt("op_" + id).startsWith("Not synced:")); + } + @Test void partialFailureRetainsOnlyTransientRecordAndRetriesAutomatically() throws Exception { + GrowthTrackIntegrationTest.Fixture f = new GrowthTrackIntegrationTest.Fixture(false); + complete(f, "sent"); complete(f, "later"); complete(f, "bad"); + JSONArray rejected = new JSONArray().put(new JSONObject().put("external_session_id", "op_later").put("reason", "Insert failed")) + .put(new JSONObject().put("external_session_id", "op_bad").put("reason", "Invalid fixture field")); + f.http.responses.add(GrowthTrackIntegrationTest.ok(1, 0, rejected)); f.client.syncPending(); + assertEquals(1, f.client.snapshot().queued); assertTrue(f.client.receipt("op_bad").startsWith("Needs review")); + assertFalse(complete(f, "bad")); f.clock.time = f.client.automaticRetryAt(); f.http.acceptAll = true; f.client.syncPending(); + assertEquals("op_later", new JSONObject(f.http.requests.get(1).body).getJSONArray("sessions").getJSONObject(0).getString("external_session_id")); + } + @Test void permissionFailurePausesRatherThanSpinningBackgroundRequests() throws Exception { + GrowthTrackIntegrationTest.Fixture f = new GrowthTrackIntegrationTest.Fixture(false); complete(f, "one"); + f.http.responses.add(new GrowthTrackClient.Response(403, "{}")); assertThrows(IOException.class, f.client::syncPending); + assertTrue(f.client.snapshot().paused); assertEquals(-1, f.client.automaticRetryAt()); + f.client.syncPending(); assertEquals(1, f.http.requests.size()); assertEquals(1, f.client.snapshot().queued); + } + @Test void manualConsentFromOlderBuildMustReconnectBeforeAutoSync() throws Exception { + GrowthTrackIntegrationTest.Fixture f = new GrowthTrackIntegrationTest.Fixture(false); f.storage.saved.remove("automatic_sync"); + GrowthTrackClient restarted = new GrowthTrackClient(f.storage, f.http, f.clock); + assertFalse(restarted.snapshot().connected); assertEquals(-1, restarted.automaticRetryAt()); + } +} diff --git a/core/src/test/java/org/openpump/GrowthTrackIntegrationTest.java b/core/src/test/java/org/openpump/GrowthTrackIntegrationTest.java new file mode 100644 index 0000000..a48793e --- /dev/null +++ b/core/src/test/java/org/openpump/GrowthTrackIntegrationTest.java @@ -0,0 +1,430 @@ +package org.openpump; + +import static org.junit.jupiter.api.Assertions.*; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Deque; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import org.json.JSONArray; +import org.json.JSONObject; +import org.junit.jupiter.api.Test; + +/** Only fictional records and mocked HTTP/storage. These tests cannot reach GrowthTrack. */ +class GrowthTrackIntegrationTest { + static final String A = "gta_" + "a".repeat(43), R = "gtr_" + "r".repeat(43), R2 = "gtr_" + "s".repeat(43); + static final String CODE = "gtc_" + "c".repeat(43); + static class Clock implements GrowthTrackClient.Clock { long time = 1_700_000_000_000L; public long now() { return time; } } + static class Storage implements GrowthTrackClient.Store { + JSONObject saved = new JSONObject(); boolean fail; + public JSONObject read() throws IOException { try { return new JSONObject(saved.toString()); } catch (Exception e) { throw new IOException(); } } + public void write(JSONObject state) throws IOException { if (fail) throw new IOException("fixture storage failure"); try { saved = new JSONObject(state.toString()); } catch (Exception e) { throw new IOException(); } } + } + static class Request { + final String method, path, body; final Map headers; + Request(String method, String path, Map headers, String body) { this.method = method; this.path = path; this.headers = headers; this.body = body; } + } + static class Http implements GrowthTrackClient.Transport { + final Deque responses = new ArrayDeque<>(); final List requests = new ArrayList<>(); Runnable beforeReturn; boolean acceptAll; + public GrowthTrackClient.Response request(String method, String path, Map headers, String body) throws IOException { + requests.add(new Request(method, path, headers, body)); + if (beforeReturn != null) beforeReturn.run(); + if (acceptAll && path.equals("/partner-ingest")) try { return ok(new JSONObject(body).getJSONArray("sessions").length(), 0, new JSONArray()); } catch (Exception e) { throw new IOException(); } + Object response = responses.removeFirst(); if (response instanceof IOException) throw (IOException) response; + return (GrowthTrackClient.Response) response; + } + } + static class Fixture { + final Storage storage = new Storage(); final Http http = new Http(); final Clock clock = new Clock(); final GrowthTrackClient client; + Fixture(boolean routines) throws Exception { + client = new GrowthTrackClient(storage, http, clock); client.begin(routines); + String state = storage.saved.getJSONObject("pending").getString("state"); + http.responses.add(token(R, routines)); client.callback(GrowthTrackProtocol.REDIRECT + "?code=" + CODE + "&state=" + state); + http.requests.clear(); + } + void approve(String... ids) throws Exception { List list = new ArrayList<>(); for (String id : ids) list.add(payload(id)); client.approve(list); } + } + static GrowthTrackClient.Response token(String refresh, boolean routines) throws Exception { + return new GrowthTrackClient.Response(200, new JSONObject().put("access_token", A).put("refresh_token", refresh) + .put("token_type", "Bearer").put("expires_in", 900).put("scope", "sessions:write" + (routines ? " routines:read" : "")).toString()); + } + static GrowthTrackClient.Response ok(int accepted, int duplicates, JSONArray rejected) throws Exception { return new GrowthTrackClient.Response(200, new JSONObject().put("accepted", accepted).put("duplicates", duplicates).put("rejected", rejected).toString()); } + static Model.Sess session(String id) { + Model.Sess s = new Model.Sess(); s.id = id; s.ts = 1_700_000_000_000L; s.durSec = 120; s.routineName = "Fictional fixture"; s.completed = true; s.recordedStartMs = s.ts; s.recordedEndMs = s.ts + s.durSec * 1000L; + s.peakKpa = 33.8639; s.netTupSec = 60.8; s.grossTupSec = 110.0; s.note = "PRIVATE NOTE"; s.afterLenAbsCm = 999.0; s.afterGirAbsCm = 999.0; s.afterLenCm = 1.0; + return s; + } + static JSONObject payload(String id) throws Exception { AsRun run = new AsRun(); run.rows.add(row(AsRun.PLAN, 0, 120_000, 0)); return GrowthTrackSession.draft(session(id), run).payload(); } + static AsRun.Row row(int kind, long t0, long t1, int pos) { AsRun.Row r = new AsRun.Row(); r.kind = kind; r.t0 = t0; r.t1 = t1; r.pos = pos; r.setId = "set" + pos; r.up = 20; r.uhWire = 20; r.uhReq = 20; r.lh = 5; return r; } + + @Test void pkceMatchesRfc7636() { assertEquals("E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM", GrowthTrackProtocol.challenge("dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk")); } + @Test void authorizationUsesOnlyPublicClientAndExactRedirect() { + GrowthTrackProtocol.Pending p = GrowthTrackProtocol.pending(true, 1000); + String url = p.authorizeUrl(); assertTrue(url.startsWith(GrowthTrackProtocol.API + "/oauth-authorize?")); + assertTrue(url.contains("client_id=" + GrowthTrackProtocol.CLIENT_ID)); assertTrue(url.contains("code_challenge_method=S256")); + assertFalse(url.contains("secret")); assertNotEquals(p.state, p.verifier); assertEquals(64, p.state.length()); + } + @Test void callbackRejectsWrongStateEndpointDuplicateAndMissingParameters() { + GrowthTrackProtocol.Pending p = GrowthTrackProtocol.pending(false, 1000); + String suffix = "?code=" + CODE + "&state=" + p.state; + assertThrows(IllegalArgumentException.class, () -> GrowthTrackProtocol.callback(GrowthTrackProtocol.REDIRECT + "?code=" + CODE + "&state=wrong", p, 1001)); + assertThrows(IllegalArgumentException.class, () -> GrowthTrackProtocol.callback("gt-openpump://evil/callback" + suffix, p, 1001)); + assertThrows(IllegalArgumentException.class, () -> GrowthTrackProtocol.callback(GrowthTrackProtocol.REDIRECT + suffix + "&state=" + p.state, p, 1001)); + assertThrows(IllegalArgumentException.class, () -> GrowthTrackProtocol.callback(GrowthTrackProtocol.REDIRECT + suffix + "&access_token=bad", p, 1001)); + assertThrows(IllegalArgumentException.class, () -> GrowthTrackProtocol.callback(GrowthTrackProtocol.REDIRECT + suffix + "#fragment", p, 1001)); + assertThrows(IllegalArgumentException.class, () -> GrowthTrackProtocol.callback(GrowthTrackProtocol.REDIRECT + suffix, p, 1_000 + GrowthTrackProtocol.AUTH_LIFETIME_MS + 1)); + } + @Test void denialAndCancelNeverExchangeOrSend() throws Exception { + Storage storage = new Storage(); Http http = new Http(); Clock clock = new Clock(); GrowthTrackClient client = new GrowthTrackClient(storage, http, clock); + client.begin(false); String state = storage.saved.getJSONObject("pending").getString("state"); + client.callback(GrowthTrackProtocol.REDIRECT + "?error=access_denied&state=" + state); + assertFalse(client.snapshot().connected); assertFalse(client.snapshot().pending); assertTrue(http.requests.isEmpty()); + client.begin(false); client.cancelAuthorization(); assertFalse(client.snapshot().pending); assertTrue(http.requests.isEmpty()); + } + @Test void forgedCallbackPreservesPendingRequestAndSendsNothing() throws Exception { + Storage store = new Storage(); Http http = new Http(); GrowthTrackClient c = new GrowthTrackClient(store, http, new Clock()); c.begin(false); + assertThrows(IOException.class, () -> c.callback(GrowthTrackProtocol.REDIRECT + "?code=" + CODE + "&state=forged")); + assertTrue(c.snapshot().pending); assertTrue(http.requests.isEmpty()); + } + @Test void lostCodeExchangeRequiresFreshApprovalAndIsNotReplayed() throws Exception { + Storage storage = new Storage(); Http http = new Http(); Clock clock = new Clock(); GrowthTrackClient c = new GrowthTrackClient(storage, http, clock); c.begin(false); + String callback = GrowthTrackProtocol.REDIRECT + "?code=" + CODE + "&state=" + storage.saved.getJSONObject("pending").getString("state"); + http.responses.add(new IOException()); assertThrows(IOException.class, () -> c.callback(callback)); + assertFalse(c.snapshot().pending); assertFalse(c.snapshot().connected); assertThrows(IOException.class, () -> c.callback(callback)); assertEquals(1, http.requests.size()); + } + @Test void mapperAllowlistUsesStartTimestampObservedPeakAndNoPrivateFields() throws Exception { + JSONObject p = payload("stable"); assertEquals("op_stable", p.getString("external_session_id")); assertEquals("2023-11-14T22:13:20.000Z", p.getString("started_at")); + assertEquals("2023-11-14T22:15:20.000Z", p.getString("completed_at")); assertEquals(10, p.getDouble("pressure_peak_inhg"), .000001); assertEquals(60, p.getInt("time_under_tension_seconds")); + for (String key : Arrays.asList("note", "measurements", "afterLenAbsCm", "user_id", "email", "link_id", "heat", "vibration", "pressure_inhg")) assertFalse(p.has(key), key); + assertFalse(p.toString().contains("PRIVATE")); assertFalse(p.toString().contains("999")); + } + @Test void simulatedUnidentifiedAndInvalidTimeRecordsAreNotExportable() { + Model.Sess s = session("x"); s.sim = true; assertThrows(IllegalArgumentException.class, () -> GrowthTrackSession.draft(s, null)); + s.sim = false; s.id = ""; assertThrows(IllegalArgumentException.class, () -> GrowthTrackSession.draft(s, null)); + s.id = "x"; s.durSec = 0; assertThrows(IllegalArgumentException.class, () -> GrowthTrackSession.draft(s, null)); + } + @Test void oneSittingContainsFixedAutomaticMethodsAndExcludesPauseAndUnconfirmedRows() throws Exception { + AsRun run = new AsRun(); run.rows.add(row(AsRun.PLAN, 0, 30_000, 0)); run.rows.add(row(AsRun.REST, 30_000, 40_000, 0)); run.rows.add(row(AsRun.OVERRIDE, 40_000, 60_000, 0)); run.rows.add(row(AsRun.PLAN, 60_000, 90_000, 1)); + AsRun.Row unconfirmed = row(AsRun.OVERRIDE, 90_000, 120_000, 1); unconfirmed.confirmed = false; run.rows.add(unconfirmed); + run.rows.get(0).uhWire = 6; run.rows.get(0).lh = 4; run.rows.get(2).uhWire = 6; run.rows.get(2).lh = 4; run.rows.get(3).uhWire = 31; + GrowthTrackSession.Draft d = GrowthTrackSession.draft(session("mixed"), run); assertEquals(2, d.parts.size()); + JSONObject p = d.payload(); assertEquals("milking", p.getJSONArray("sets").getJSONObject(0).getString("method")); assertEquals(50, p.getJSONArray("sets").getJSONObject(0).getInt("duration_seconds")); + assertEquals(30, p.getJSONArray("sets").getJSONObject(1).getInt("duration_seconds")); assertEquals("rip", p.getJSONArray("sets").getJSONObject(1).getString("method")); assertFalse(p.getJSONArray("sets").getJSONObject(0).has("pressure_inhg")); + } + @Test void inconsistentRecordingIsRejectedRatherThanInventingDurations() { + AsRun run = new AsRun(); run.rows.add(row(AsRun.PLAN, 0, 60_000, 0)); run.rows.add(row(AsRun.PLAN, 50_000, 80_000, 1)); + assertThrows(IllegalArgumentException.class, () -> GrowthTrackSession.draft(session("overlap"), run)); + } + @Test void sendRequiresExplicitApprovalAndNoTrafficOnStartup() throws Exception { + Fixture f = new Fixture(false); f.client.sendApproved(); assertTrue(f.http.requests.isEmpty()); + GrowthTrackClient restarted = new GrowthTrackClient(f.storage, f.http, f.clock); assertTrue(restarted.snapshot().connected); assertTrue(f.http.requests.isEmpty()); + } + @Test void durableApprovedSnapshotsAreDetachedFromMutableReviewObjects() throws Exception { + Fixture f = new Fixture(false); JSONObject p = payload("stable"); f.client.approve(Arrays.asList(p)); p.put("routine_name", "changed later"); + GrowthTrackClient restarted = new GrowthTrackClient(f.storage, f.http, f.clock); f.http.responses.add(ok(1, 0, new JSONArray())); restarted.resumeApproved(); + assertEquals("Fictional fixture", new JSONObject(f.http.requests.get(0).body).getJSONArray("sessions").getJSONObject(0).getString("routine_name")); + } + @Test void duplicateSendUsesExactlyTheSameExternalId() throws Exception { + Fixture f = new Fixture(false); f.approve("stable"); f.http.responses.add(ok(1, 0, new JSONArray())); f.client.sendApproved(); + f.approve("stable"); f.http.responses.add(ok(0, 1, new JSONArray())); f.client.sendApproved(); + assertEquals(f.http.requests.get(0).body, f.http.requests.get(1).body); assertEquals(0, f.client.snapshot().queued); assertTrue(f.client.receipt("op_stable").startsWith("Received")); + } + @Test void offlineRetrySurvivesRestartAndHonorsBackoff() throws Exception { + Fixture f = new Fixture(false); f.approve("offline"); f.http.responses.add(new IOException()); assertThrows(IOException.class, () -> f.client.sendApproved()); + String first = f.http.requests.get(0).body; assertEquals(1, f.client.snapshot().queued); assertEquals(f.clock.time + 5000, f.client.snapshot().retryAt); + GrowthTrackClient restarted = new GrowthTrackClient(f.storage, f.http, f.clock); assertThrows(IOException.class, () -> restarted.resumeApproved()); assertEquals(1, f.http.requests.size()); + f.clock.time += 5001; f.http.responses.add(ok(0, 1, new JSONArray())); restarted.resumeApproved(); assertEquals(first, f.http.requests.get(1).body); assertEquals(0, restarted.snapshot().queued); + } + @Test void repeatedServerFailuresBackOffAndHonorRetryAfter() throws Exception { + Fixture f = new Fixture(false); f.approve("server"); f.http.responses.add(new GrowthTrackClient.Response(503, "", "30")); assertThrows(IOException.class, () -> f.client.sendApproved()); assertEquals(f.clock.time + 30_000, f.client.snapshot().retryAt); + f.clock.time += 30_001; f.http.responses.add(new GrowthTrackClient.Response(503, "")); assertThrows(IOException.class, () -> f.client.resumeApproved()); assertEquals(f.clock.time + 10_000, f.client.snapshot().retryAt); + } + @Test void unauthorizedRefreshesOnlyOnceAndRotatesBeforeResendingTheSameBatch() throws Exception { + Fixture f = new Fixture(false); f.approve("auth"); f.http.responses.add(new GrowthTrackClient.Response(401, "{}")); f.http.responses.add(token(R2, false)); f.http.responses.add(ok(1, 0, new JSONArray())); f.client.sendApproved(); + assertEquals(3, f.http.requests.size()); assertEquals("/oauth-token", f.http.requests.get(1).path); assertEquals(f.http.requests.get(0).body, f.http.requests.get(2).body); assertEquals(R2, f.storage.saved.getJSONObject("tokens").getString("refresh_token")); + assertFalse(f.storage.saved.optBoolean("refreshing")); assertFalse(f.http.requests.get(0).headers.containsKey("x-partner-key")); + } + @Test void aSecondUnauthorizedResponseDisconnectsWithoutAnInfiniteRefreshLoop() throws Exception { + Fixture f = new Fixture(false); f.approve("auth"); f.http.responses.add(new GrowthTrackClient.Response(401, "{}")); f.http.responses.add(token(R2, false)); f.http.responses.add(new GrowthTrackClient.Response(401, "{}")); + assertThrows(IOException.class, () -> f.client.sendApproved()); assertEquals(3, f.http.requests.size()); assertFalse(f.client.snapshot().connected); assertEquals(0, f.client.snapshot().queued); + } + @Test void ambiguousRotatingRefreshRequiresRelinkRatherThanReplaying() throws Exception { + Fixture f = new Fixture(false); f.approve("refresh"); f.clock.time += 900_000; f.http.responses.add(new IOException()); assertThrows(IOException.class, () -> f.client.sendApproved()); + assertFalse(f.client.snapshot().connected); assertEquals(0, f.client.snapshot().queued); assertEquals(1, f.http.requests.size()); assertThrows(IOException.class, () -> f.client.resumeApproved()); assertEquals(1, f.http.requests.size()); + } + @Test void restartDuringRefreshDropsTheOldCredentialAndQueuedApprovals() throws Exception { + Fixture f = new Fixture(false); f.approve("refresh"); f.storage.saved.put("refreshing", true); + GrowthTrackClient restarted = new GrowthTrackClient(f.storage, f.http, f.clock); assertFalse(restarted.snapshot().connected); assertEquals(0, restarted.snapshot().queued); assertTrue(f.http.requests.isEmpty()); + } + @Test void partialResultsRecordOnlyKnownCompleteOutcomesAndRetainTransientFailures() throws Exception { + Fixture f = new Fixture(false); f.approve("accepted", "duplicate", "temporary"); + f.http.responses.add(ok(1, 1, new JSONArray().put(new JSONObject().put("external_session_id", "op_temporary").put("reason", "Insert failed")))); f.client.sendApproved(); + assertEquals(1, f.client.snapshot().queued); assertTrue(f.client.receipt("op_accepted").startsWith("Received")); assertEquals("", f.client.receipt("op_temporary")); + } + @Test void permanentPerSessionRejectionIsVisibleAndNotRetriedAutomatically() throws Exception { + Fixture f = new Fixture(false); f.approve("bad"); f.http.responses.add(ok(0, 0, new JSONArray().put(new JSONObject().put("external_session_id", "op_bad").put("reason", "Unknown method")))); f.client.sendApproved(); + assertEquals(0, f.client.snapshot().queued); assertTrue(f.client.receipt("op_bad").startsWith("Needs review")); + } + @Test void incompleteOrForeignResultDoesNotMarkAnythingSent() throws Exception { + Fixture f = new Fixture(false); f.approve("mine"); f.http.responses.add(ok(0, 0, new JSONArray().put(new JSONObject().put("external_session_id", "op_someone_else").put("reason", "bad")))); + assertThrows(IOException.class, () -> f.client.sendApproved()); assertEquals(1, f.client.snapshot().queued); assertEquals("", f.client.receipt("op_mine")); + } + @Test void oversizeResponseSplitsWithoutChangingStableIds() throws Exception { + Fixture f = new Fixture(false); f.approve("one", "two"); f.http.responses.add(new GrowthTrackClient.Response(413, "{}")); f.http.responses.add(ok(1, 0, new JSONArray())); f.http.responses.add(ok(1, 0, new JSONArray())); f.client.sendApproved(); + assertEquals(2, new JSONObject(f.http.requests.get(0).body).getJSONArray("sessions").length()); assertEquals(1, new JSONObject(f.http.requests.get(1).body).getJSONArray("sessions").length()); assertEquals(0, f.client.snapshot().queued); + } + @Test void boundedBatchingCountsUtf8BytesAndNeverExceedsLimits() throws Exception { + Fixture f = new Fixture(false); List list = new ArrayList<>(); + for (int i = 0; i < 105; i++) { JSONObject s = payload("batch" + i); s.put("routine_name", "å".repeat(150)); s.remove("method"); s.put("total_duration_seconds", 1000); JSONArray sets = new JSONArray(); for (int j = 0; j < 500; j++) sets.put(new JSONObject().put("set_index", j + 1).put("method", "rapid_interval_vacuum_extending").put("duration_seconds", 1)); s.put("sets", sets); list.add(s); } + f.client.approve(list); f.http.acceptAll = true; f.client.sendApproved(); assertTrue(f.http.requests.size() > 2); + for (Request r : f.http.requests) { assertTrue(r.body.getBytes(StandardCharsets.UTF_8).length <= GrowthTrackProtocol.MAX_BYTES); assertTrue(new JSONObject(r.body).getJSONArray("sessions").length() <= GrowthTrackProtocol.BATCH_SIZE); } + assertEquals(0, f.client.snapshot().queued); + } + @Test void approvalRejectsPrivateFieldsBeforeAnyNetworkRequest() throws Exception { + Fixture f = new Fixture(false); JSONObject s = payload("private"); s.put("note", "must never escape"); assertThrows(IOException.class, () -> f.client.approve(Arrays.asList(s))); assertTrue(f.http.requests.isEmpty()); + } + @Test void cancelAndDisconnectStopFutureRequestsAndClearAuthority() throws Exception { + Fixture f = new Fixture(false); f.approve("cancel"); f.http.beforeReturn = () -> f.client.cancelRequests(); f.http.responses.add(ok(1, 0, new JSONArray())); + assertThrows(IOException.class, () -> f.client.sendApproved()); f.client.cancelApproved(); assertEquals(0, f.client.snapshot().queued); assertEquals("", f.client.receipt("op_cancel")); + f.client.disconnect(); assertFalse(f.client.snapshot().connected); assertFalse(f.storage.saved.has("tokens")); assertThrows(IOException.class, () -> f.client.resumeApproved()); assertEquals(1, f.http.requests.size()); + } + @Test void relinkingCannotCarryAnOldAccountsApprovedPayloadsOrReceipts() throws Exception { + Fixture f = new Fixture(false); f.approve("previous"); f.client.begin(true); assertFalse(f.client.snapshot().connected); assertEquals(0, f.client.snapshot().queued); assertTrue(f.client.snapshot().pending); assertEquals("", f.client.receipt("op_previous")); + } + @Test void storageFailureFailsClosedBeforeUpload() throws Exception { + Fixture f = new Fixture(false); f.storage.fail = true; assertThrows(IOException.class, () -> f.approve("storage")); assertThrows(IOException.class, () -> f.client.resumeApproved()); assertFalse(f.client.snapshot().connected); assertTrue(f.http.requests.isEmpty()); + } + @Test void serializedSendsCannotRefreshInParallelOrDuplicateTheQueue() throws Exception { + Fixture f = new Fixture(false); f.approve("parallel"); f.clock.time += 900_000; f.http.responses.add(token(R2, false)); f.http.responses.add(ok(1, 0, new JSONArray())); + var executor = Executors.newFixedThreadPool(2); + try { List> calls = Arrays.asList(() -> { f.client.sendApproved(); return null; }, () -> { f.client.sendApproved(); return null; }); for (var future : executor.invokeAll(calls)) future.get(5, TimeUnit.SECONDS); } + finally { executor.shutdownNow(); } + assertEquals(2, f.http.requests.size()); assertEquals(0, f.client.snapshot().queued); + } + @Test void routinePermissionIsSeparateAndUnsupportedFormatsAreRejected() throws Exception { + Fixture without = new Fixture(false); assertThrows(IOException.class, () -> without.client.routines(null)); assertTrue(without.http.requests.isEmpty()); + Fixture with = new Fixture(true); with.http.responses.add(new GrowthTrackClient.Response(200, "{\"format\":\"pet-routine\",\"schema_version\":1,\"routines\":[]}")); assertThrows(IOException.class, () -> with.client.routines(null)); + with.http.responses.add(new GrowthTrackClient.Response(200, "{\"format\":\"gt-routine\",\"schema_version\":2,\"routines\":[]}")); assertThrows(IOException.class, () -> with.client.routines(null)); + with.http.responses.add(new GrowthTrackClient.Response(200, "{\"format\":\"gt-routine\",\"schema_version\":1,\"routines\":[],\"new_optional_field\":true}")); assertEquals(0, with.client.routines(null).getJSONArray("routines").length()); + } + + @Test void fixedHoldRulesHonorEveryInclusiveBoundaryBeforeRounding() { + assertEquals("milking", GrowthTrackSession.methodForHoldMillis(0.1)); + assertEquals("milking", GrowthTrackSession.methodForHoldMillis(5999.9)); + assertEquals("milking", GrowthTrackSession.methodForHoldMillis(6000)); + assertEquals("rip", GrowthTrackSession.methodForHoldMillis(6000.1)); + assertEquals("rip", GrowthTrackSession.methodForHoldMillis(30000)); + assertEquals("interval_pumping", GrowthTrackSession.methodForHoldMillis(30000.1)); + assertEquals("interval_pumping", GrowthTrackSession.methodForHoldMillis(120000)); + assertEquals("static_pumping", GrowthTrackSession.methodForHoldMillis(120000.1)); + for (double invalid : new double[]{0, -1, Double.NaN, Double.POSITIVE_INFINITY}) + assertThrows(IllegalArgumentException.class, () -> GrowthTrackSession.methodForHoldMillis(invalid)); + } + @Test void actualAcknowledgedHoldDrivesLabelNotRequestedHoldSetLengthOrRelease() throws Exception { + AsRun run = new AsRun(); AsRun.Row r = row(AsRun.PLAN, 0, 120_000, 0); + r.uhWire = 6; r.uhReq = 200; r.lh = 80; run.rows.add(r); + JSONObject p = GrowthTrackSession.draft(session("wire"), run).payload(); + assertEquals("milking", p.getJSONArray("sets").getJSONObject(0).getString("method")); + assertEquals(120, p.getInt("total_duration_seconds")); + assertEquals(120, p.getJSONArray("sets").getJSONObject(0).getInt("duration_seconds")); + assertEquals(60, p.getInt("time_under_tension_seconds")); + } + @Test void liveHoldChangesWithinOneSetRemainMixedWithoutSplittingTheSitting() throws Exception { + Model.Sess s = session("varying"); s.durSec = 1000; s.recordedEndMs = s.recordedStartMs + 1000000L; + AsRun run = new AsRun(); int[] holds = {6, 7, 30, 31, 120, 121}; + for (int i = 0; i < holds.length; i++) { AsRun.Row r = row(AsRun.OVERRIDE, i * 150_000L, i * 150_000L + (holds[i] + 5) * 1000L, 0); r.uhWire = holds[i]; run.rows.add(r); } + JSONObject p = GrowthTrackSession.draft(s, run).payload(); + assertEquals("op_varying", p.getString("external_session_id")); assertEquals(6, p.getJSONArray("sets").length()); + String[] expected = {"milking", "rip", "rip", "interval_pumping", "interval_pumping", "static_pumping"}; + for (int i = 0; i < expected.length; i++) assertEquals(expected[i], p.getJSONArray("sets").getJSONObject(i).getString("method")); + } + @Test void frozenLengthTrackOverridesEveryHoldIncludingUnknownHoldAndSurvivesRoutineDeletion() throws Exception { + Model.Sess s = session("length"); s.durSec = 1000; s.recordedEndMs = s.recordedStartMs + 1000000L; + AsRun run = new AsRun(); run.snap = new Model.AsRunSnapshot(); run.snap.trainerTrack = Plan.TRACK_LENGTH; + int[] holds = {0, 6, 30, 120, 255}; + for (int i = 0; i < holds.length; i++) { AsRun.Row r = row(AsRun.PLAN, i * 150_000L, (i + 1) * 150_000L, i); r.uhWire = holds[i]; run.rows.add(r); } + run.snap = Model.AsRunSnapshot.fromJson(run.snap.toJson()); + JSONObject p = GrowthTrackSession.draft(s, run, new Model()).payload(); + for (int i = 0; i < p.getJSONArray("sets").length(); i++) assertEquals("length_pumping", p.getJSONArray("sets").getJSONObject(i).getString("method")); + s.asRunSnapshot = run.snap; JSONObject withoutRows = GrowthTrackSession.draft(s, null).payload(); + assertEquals("length_pumping", withoutRows.getString("method")); assertFalse(withoutRows.has("sets")); + } + @Test void snapshotTrackMigrationPreservesUnknownVersusKnownUnmarkedAndRecordedLength() throws Exception { + Model.AsRunSnapshot old = Model.AsRunSnapshot.fromJson(new JSONObject("{\"rid\":\"old\",\"stages\":[]}")); + assertEquals(-1, old.trainerTrack); assertFalse(old.toJson().has("trainerTrack")); + Model.Routine routine = new Model.Routine(); routine.id = "new"; routine.trainerTrack = Plan.TRACK_LENGTH; + Model.AsRunSnapshot frozen = Model.AsRunSnapshot.of(routine, new Model()); routine.trainerTrack = 0; + assertEquals(Plan.TRACK_LENGTH, Model.AsRunSnapshot.fromJson(frozen.toJson()).trainerTrack); + assertEquals(0, Model.AsRunSnapshot.fromJson(Model.AsRunSnapshot.of(routine, new Model()).toJson()).trainerTrack); + } + @Test void legacyStructuredLengthMarkerRequiresAnUnchangedMatchingSnapshot() throws Exception { + Model model = new Model(); Model.Routine routine = new Model.Routine(); routine.id = "r"; routine.trainerTrack = Plan.TRACK_LENGTH; model.routines.add(routine); + AsRun run = new AsRun(); run.rows.add(row(AsRun.PLAN, 0, 120_000, 0)); run.snap = Model.AsRunSnapshot.of(routine, model); run.snap.trainerTrack = -1; + Model.Sess s = session("legacy"); s.routineId = "r"; + assertEquals("length_pumping", GrowthTrackSession.draft(s, run, model).parts.get(0).method); + Model.Stage added = new Model.Stage(); added.name = "Later edit"; routine.stages.add(added); + assertEquals("rip", GrowthTrackSession.draft(s, run, model).parts.get(0).method); + } + @Test void freeTextNamesAndCountedTrackDoNotInventAPhysicalLengthDesignation() { + Model.Sess s = session("name"); s.routineName = "Length pumping"; s.countedTrack = Plan.TRACK_LENGTH; + AsRun run = new AsRun(); run.rows.add(row(AsRun.PLAN, 0, 120_000, 0)); + assertEquals("rip", GrowthTrackSession.draft(s, run).parts.get(0).method); + } + @Test void noRecordingZeroHoldAndNoConfirmedWorkCannotInventATechnique() { + assertThrows(IllegalArgumentException.class, () -> GrowthTrackSession.draft(session("old"), null)); + AsRun run = new AsRun(); AsRun.Row r = row(AsRun.PLAN, 0, 120_000, 0); r.uhWire = 0; run.rows.add(r); + assertThrows(IllegalArgumentException.class, () -> GrowthTrackSession.draft(session("zero"), run)); + r.uhWire = 6; r.confirmed = false; + assertThrows(IllegalArgumentException.class, () -> GrowthTrackSession.draft(session("unconfirmed"), run)); + r.confirmed = true; r.kind = AsRun.REST; + assertThrows(IllegalArgumentException.class, () -> GrowthTrackSession.draft(session("rest"), run)); + } + @Test void subsecondWorkKeepsItsMethodWithoutFabricatingASecond() throws Exception { + Model.Sess s = session("fraction"); s.durSec = 1; s.recordedEndMs = s.recordedStartMs + 1000L; s.netTupSec = null; + AsRun run = new AsRun(); AsRun.Row r = row(AsRun.PLAN, 0, 600, 0); r.uhWire = 6; run.rows.add(r); + JSONObject p = GrowthTrackSession.draft(s, run).payload(); + assertTrue(p.getJSONArray("sets").getJSONObject(0).isNull("duration_seconds")); + Fixture f = new Fixture(false); f.client.approve(Arrays.asList(p)); assertEquals(1, f.client.snapshot().queued); + s.durSec = 0; assertThrows(IllegalArgumentException.class, () -> GrowthTrackSession.draft(s, run)); + } + @Test void grossSealedTimeIsNeverSentAsUnderTensionWhenNetWasNotRecorded() throws Exception { + Model.Sess s = session("gross"); s.netTupSec = null; s.grossTupSec = 120.0; + AsRun run = new AsRun(); run.rows.add(row(AsRun.PLAN, 0, 120_000, 0)); + assertFalse(GrowthTrackSession.draft(s, run).payload().has("time_under_tension_seconds")); + } + @Test void transientPerItemFailuresUseIncreasingBackoffAcrossRetries() throws Exception { + Fixture f = new Fixture(false); f.approve("temporary"); + JSONArray rejected = new JSONArray().put(new JSONObject().put("external_session_id", "op_temporary").put("reason", "Insert failed")); + f.http.responses.add(ok(0, 0, rejected)); f.client.sendApproved(); assertEquals(f.clock.time + 5000, f.client.snapshot().retryAt); + f.clock.time += 5001; f.http.responses.add(ok(0, 0, rejected)); f.client.resumeApproved(); assertEquals(f.clock.time + 10000, f.client.snapshot().retryAt); + } + @Test void invalidTypedPayloadFieldsFailBeforeNetworkAndDoNotCreateApproval() throws Exception { + for (String field : Arrays.asList("total_duration_seconds", "time_under_tension_seconds", "pressure_peak_inhg", "started_at", "completed_at", "routine_name")) { + Fixture f = new Fixture(false); JSONObject p = payload("bad"); + if (field.endsWith("seconds")) p.put(field, 1.5); else if (field.equals("pressure_peak_inhg")) p.put(field, 30); else if (field.equals("routine_name")) p.put(field, "x".repeat(201)); else p.put(field, "invalid"); + assertThrows(IOException.class, () -> f.client.approve(Arrays.asList(p)), field); + assertTrue(f.http.requests.isEmpty()); assertEquals(0, f.client.snapshot().queued); + } + Fixture f = new Fixture(false); JSONObject p = payload("index"); p.getJSONArray("sets").getJSONObject(0).put("set_index", 0); + assertThrows(IOException.class, () -> f.client.approve(Arrays.asList(p))); assertTrue(f.http.requests.isEmpty()); + } + @Test void goneGrantClearsAuthorityAndForbiddenPermissionPreservesApprovalForExplicitRelink() throws Exception { + Fixture gone = new Fixture(false); gone.approve("gone"); gone.http.responses.add(new GrowthTrackClient.Response(410, "{}")); + assertThrows(IOException.class, () -> gone.client.sendApproved()); assertFalse(gone.client.snapshot().connected); assertEquals(0, gone.client.snapshot().queued); + Fixture denied = new Fixture(false); denied.approve("denied"); denied.http.responses.add(new GrowthTrackClient.Response(403, "{}")); + assertThrows(IOException.class, () -> denied.client.sendApproved()); assertEquals(1, denied.client.snapshot().queued); assertEquals(1, denied.http.requests.size()); + } + @Test void supportedRoutineDetailUsesReadScopeAndDoesNotChangeSessionQueue() throws Exception { + Fixture f = new Fixture(true); String id = "00000000-0000-4000-8000-000000000001"; + JSONObject detail = new JSONObject().put("format", "gt-routine").put("schema_version", 1) + .put("routine", new JSONObject().put("external_id", id).put("steps", new JSONArray())); + f.http.responses.add(new GrowthTrackClient.Response(200, detail.toString())); assertEquals(id, f.client.routines(id).getJSONObject("routine").getString("external_id")); + assertEquals("GET", f.http.requests.get(0).method); assertNull(f.http.requests.get(0).body); + assertEquals("no-store", f.http.requests.get(0).headers.get("Cache-Control")); assertEquals(0, f.client.snapshot().queued); + assertThrows(IOException.class, () -> f.client.routines("../../tokens")); assertEquals(1, f.http.requests.size()); + } + @Test void stoppedUpperHoldUsesClippedDurationAndNeverItsLongerSetting() { + AsRun run = new AsRun(); AsRun.Row r = row(AsRun.PLAN, 0, 6000, 0); r.uhWire = 121; run.rows.add(r); + GrowthTrackSession.Part p = GrowthTrackSession.draft(session("clipped"), run).parts.get(0); + assertEquals("milking", p.method); assertEquals(6000, p.holdMillis); + r.t1 = 30001; assertEquals("interval_pumping", GrowthTrackSession.draft(session("clipped"), run).parts.get(0).method); + } + @Test void completedCyclesAndInterruptedLastCycleCanHaveDifferentMethods() { + AsRun run = new AsRun(); AsRun.Row r = row(AsRun.PLAN, 0, 26000, 0); r.uhWire = 20; r.lh = 5; run.rows.add(r); + GrowthTrackSession.Draft d = GrowthTrackSession.draft(session("cycles"), run); + assertEquals(2, d.parts.size()); assertEquals("rip", d.parts.get(0).method); assertEquals(25000, d.parts.get(0).recordedMillis); + assertEquals("milking", d.parts.get(1).method); assertEquals(1000, d.parts.get(1).holdMillis); + } + @Test void zeroReleaseIsOneContinuousRecordedHold() { + Model.Sess s = session("continuous"); s.durSec = 150; s.recordedEndMs = s.recordedStartMs + 150000L; + AsRun run = new AsRun(); AsRun.Row r = row(AsRun.PLAN, 0, 121000, 0); r.uhWire = 6; r.lh = 0; run.rows.add(r); + assertEquals("static_pumping", GrowthTrackSession.draft(s, run).parts.get(0).method); + } + static AsRun stitch(boolean frozen) { + AsRun run = new AsRun(); + AsRun.Row first = row(AsRun.PLAN, 0, 255000, 0); first.uhWire = 255; first.uhReq = 255; first.lh = 1; first.lo = first.up - 1; + AsRun.Row last = row(AsRun.PLAN, 255000, 360000, 0); last.ordinal = 1; last.uhWire = 45; last.uhReq = 45; last.lh = 60; + if (frozen) { first.cyclePart = 1; last.cyclePart = 0; } + run.rows.add(first); run.rows.add(last); return run; + } + @Test void verifiedStitchKeepsOneLongHoldWithReleaseExcludedFromLabel() throws Exception { + Model.Sess s = session("stitch"); s.durSec = 360; s.recordedEndMs = s.recordedStartMs + 360000L; + GrowthTrackSession.Draft d = GrowthTrackSession.draft(s, stitch(true)); + assertEquals(1, d.parts.size()); assertEquals(300000, d.parts.get(0).holdMillis); assertEquals("static_pumping", d.parts.get(0).method); + assertEquals(360, d.payload().getJSONArray("sets").getJSONObject(0).getInt("duration_seconds")); + } + @Test void oldSnapshotCanProveStitchButAnUnknownBoundaryCannot() { + Model.Sess s = session("oldStitch"); s.durSec = 360; s.recordedEndMs = s.recordedStartMs + 360000L; AsRun run = stitch(false); + assertThrows(IllegalArgumentException.class, () -> GrowthTrackSession.draft(s, run)); + Model model = new Model(); Model.Set set = Model.Set.fixed("set0", "old long hold", 20, 0, 300, 60, 50, 360); model.sets.add(set); + Model.Routine routine = new Model.Routine(); Model.Stage stage = new Model.Stage(); stage.setIds.add(set.id); routine.stages.add(stage); + run.snap = Model.AsRunSnapshot.of(routine, model); + assertEquals(1, GrowthTrackSession.draft(s, run).parts.size()); assertEquals(300000, GrowthTrackSession.draft(s, run).parts.get(0).holdMillis); + } + @Test void changedOrUnconfirmedStitchFailsClosedAndStoppedChunkIsClipped() { + Model.Sess s = session("badStitch"); s.durSec = 360; s.recordedEndMs = s.recordedStartMs + 360000L; AsRun run = stitch(true); run.rows.get(1).confirmed = false; final AsRun unconfirmed = run; + assertThrows(IllegalArgumentException.class, () -> GrowthTrackSession.draft(s, unconfirmed)); + run = stitch(true); run.rows.get(1).up = 21; final AsRun changed = run; + assertThrows(IllegalArgumentException.class, () -> GrowthTrackSession.draft(s, changed)); + run = stitch(true); run.rows.remove(1); run.rows.get(0).t1 = 6000; + assertEquals("milking", GrowthTrackSession.draft(s, run).parts.get(0).method); + } + @Test void pausedOrLostPhaseCannotBeInventedButStructuredLengthStillOverrides() { + for (int kind : new int[]{AsRun.HOLD, AsRun.LOST}) { + AsRun run = new AsRun(); run.rows.add(row(AsRun.PLAN, 0, 20000, 0)); run.rows.add(row(kind, 20000, 30000, 0)); run.rows.add(row(AsRun.PLAN, 30000, 50000, 0)); + assertThrows(IllegalArgumentException.class, () -> GrowthTrackSession.draft(session("gap"), run)); + run.snap = new Model.AsRunSnapshot(); run.snap.trainerTrack = Plan.TRACK_LENGTH; + for (GrowthTrackSession.Part p : GrowthTrackSession.draft(session("gap"), run).parts) assertEquals("length_pumping", p.method); + } + } + @Test void frozenStitchMarkerRoundTripsAndLegacyRemainsUnknown() throws Exception { + AsRun.Row old = AsRun.Row.fromJson(new JSONObject()); assertEquals(-1, old.cyclePart); assertFalse(old.toJson().has("cyclePart")); + AsRun run = new AsRun(); Model.Preset preset = new Model.Preset(); preset.cyclePart = true; + AsRun.Row now = run.open(AsRun.PLAN, 0, preset, 20, 20, 19, 255, 255, 1, 50); + assertEquals(1, AsRun.Row.fromJson(now.toJson()).cyclePart); + preset.cyclePart = false; assertEquals(0, run.open(AsRun.PLAN, 1000, preset, 20, 20, 0, 6, 6, 4, 50).cyclePart); + } + + @Test void staleReviewCannotBeApprovedAfterAnAccountRelink() throws Exception { + Fixture f = new Fixture(false); String old = f.client.snapshot().connectionId; f.approve("old"); + f.client.begin(false); assertEquals(0, f.client.snapshot().queued); + String state = f.storage.saved.getJSONObject("pending").getString("state"); f.http.responses.add(token(R2, false)); + f.client.callback(GrowthTrackProtocol.REDIRECT + "?code=" + CODE + "&state=" + state); f.http.requests.clear(); + assertNotEquals(old, f.client.snapshot().connectionId); + assertThrows(IOException.class, () -> f.client.approve(Arrays.asList(payload("stale")), old)); + assertEquals(0, f.client.snapshot().queued); assertTrue(f.http.requests.isEmpty()); + f.client.approve(Arrays.asList(payload("fresh")), f.client.snapshot().connectionId); assertEquals(1, f.client.snapshot().queued); + } + @Test void restoredQueueWithDifferentGrantIdentityIsDiscardedWithoutTraffic() throws Exception { + Fixture f = new Fixture(false); f.approve("old"); f.storage.saved.put("approved_connection_id", "another-grant"); + GrowthTrackClient restored = new GrowthTrackClient(f.storage, f.http, f.clock); + assertTrue(restored.snapshot().connected); assertEquals(0, restored.snapshot().queued); assertTrue(f.http.requests.isEmpty()); + } + @Test void refreshRetainsGrantIdentityAndApprovedSnapshotsForTheSameAccount() throws Exception { + Fixture f = new Fixture(false); String id = f.client.snapshot().connectionId; f.approve("refreshIdentity"); + f.clock.time += 901000; f.http.responses.add(token(R2, false)); f.http.responses.add(ok(1, 0, new JSONArray())); f.client.sendApproved(); + assertEquals(id, f.client.snapshot().connectionId); assertEquals(0, f.client.snapshot().queued); assertEquals(2, f.http.requests.size()); + } + + @Test void legacyTimestampMeaningAndRejoinedBoundsAreNeverGuessedEvenForLength() { + Model.Sess s = session("unknownTime"); s.recordedStartMs = 0; s.recordedEndMs = 0; + AsRun run = new AsRun(); run.rows.add(row(AsRun.PLAN, 0, 120000, 0)); run.snap = new Model.AsRunSnapshot(); run.snap.trainerTrack = Plan.TRACK_LENGTH; + assertThrows(IllegalArgumentException.class, () -> GrowthTrackSession.draft(s, run)); + assertThrows(IllegalArgumentException.class, () -> GrowthTrackSession.draft(s, null)); + } + @Test void explicitlyRecordedWallClockBoundsTakePrecedenceOverLegacyKeyAndElapsed() throws Exception { + Model.Sess s = session("wallclock"); s.ts += 999000; s.recordedEndMs += 60000; + AsRun run = new AsRun(); run.rows.add(row(AsRun.PLAN, 0, 120000, 0)); + JSONObject payload = GrowthTrackSession.draft(s, run).payload(); + assertEquals("2023-11-14T22:13:20.000Z", payload.getString("started_at")); + assertEquals("2023-11-14T22:16:20.000Z", payload.getString("completed_at")); assertEquals(120, payload.getInt("total_duration_seconds")); + Model.Sess restored = Model.Sess.fromJson(s.toJson()); assertEquals(s.recordedStartMs, restored.recordedStartMs); assertEquals(s.recordedEndMs, restored.recordedEndMs); + } + +} diff --git a/core/src/test/java/org/openpump/GrowthTrackOnboardingTest.java b/core/src/test/java/org/openpump/GrowthTrackOnboardingTest.java new file mode 100644 index 0000000..9ff2251 --- /dev/null +++ b/core/src/test/java/org/openpump/GrowthTrackOnboardingTest.java @@ -0,0 +1,37 @@ +package org.openpump; + +import static org.junit.jupiter.api.Assertions.*; +import org.junit.jupiter.api.Test; + +class GrowthTrackOnboardingTest { + @Test void freshSetupCanOfferWithoutGrantingUploadingOrChangingFirstRun() { + Model m = Model.seed(); int first = m.firstRun; + assertTrue(GrowthTrackOnboarding.shouldOffer(m, false, false)); + GrowthTrackOnboarding.presented(m); assertEquals(first, m.firstRun); assertTrue(m.sessLog.all.isEmpty()); + assertFalse(GrowthTrackOnboarding.shouldOffer(m, false, false)); + } + @Test void skipBackCancelAndRepeatedLaunchDoNotRepeatOffer() throws Exception { + for (String exit : new String[]{"skip", "back", "cancel", "close", "connect"}) { + Model m = Model.seed(); GrowthTrackOnboarding.presented(m); + Model restored = Model.fromJson(m.toJson()); + assertFalse(GrowthTrackOnboarding.shouldOffer(restored, false, false), exit); + assertEquals(FirstRun.NOT_STARTED, restored.firstRun); + } + } + @Test void existingUsersAndARealSessionAreNeverInterrupted() throws Exception { + assertFalse(GrowthTrackOnboarding.shouldOffer(Model.fromJson("{\"sets\":[],\"routines\":[]}"), false, false)); + Model m = Model.seed(); Model.Sess s = new Model.Sess(); s.sim = true; m.sessLog.all.add(s); + assertTrue(GrowthTrackOnboarding.shouldOffer(m, false, false)); + s.sim = false; assertFalse(GrowthTrackOnboarding.shouldOffer(m, false, false)); + } + @Test void activeRunOrUnreadableStoreDefersOfferWithoutConsumingIt() { + Model m = Model.seed(); assertFalse(GrowthTrackOnboarding.shouldOffer(m, true, false)); + assertFalse(GrowthTrackOnboarding.shouldOffer(m, false, true)); assertFalse(m.growthTrackOfferSeen); + } + @Test void completingSetupAfterSkipStillUsesExistingStarterGateAndMigrationDefault() throws Exception { + Model m = Model.seed(); GrowthTrackOnboarding.presented(m); FirstRun.finish(m); + assertEquals(FirstRun.FINISH_STARTER, FirstRun.finishAction("starter", true, false)); + assertEquals(FirstRun.DONE, m.firstRun); assertTrue(Model.fromJson("{\"sets\":[],\"routines\":[]}").growthTrackOfferSeen); + assertTrue(Model.fromJson(m.toJson()).growthTrackOfferSeen); + } +} diff --git a/docs/growthtrack-developer-handoff.md b/docs/growthtrack-developer-handoff.md new file mode 100644 index 0000000..674b251 --- /dev/null +++ b/docs/growthtrack-developer-handoff.md @@ -0,0 +1,136 @@ +# GrowthTrack integration — developer handoff + +Connecting a user's own GrowthTrack account now authorizes **automatic transfer of every new real finished session** recorded while connected. Disconnected sessions send nothing. There is no per-session picker, review, Send button or normal selective opt-in. Mixed techniques remain **one GrowthTrack session** with granular set/timing detail. Invalid, simulated or unrepresentable recordings have explicit status rather than a silent omission. + +**Android compilation, lint and device/platform QA remain required before release.** JVM tests and source checks have passed. This work has not installed or run the Android app, made a live OAuth grant, transmitted real sessions or operated pump hardware. + +## Checkout and scope + +- Fork: . +- Hemmaservern checkout: `F:\Codex\OpenPump-App`, branch `codex/growthtrack-integration`, baseline commit `1dddec436e552b0669e69427e9d054a71598d859`. The implementation is carried by the feature branch. +- Origin: `https://github.com/kwikmn/OpenPump-App.git`; upstream: `https://github.com/OpenByte-source/OpenPump-App.git`. +- Publication target: [personal-fork feature branch](https://github.com/kwikmn/OpenPump-App/tree/codex/growthtrack-integration). Publishing this branch does not merge main, deploy the site or send the developer message; no upstream PR was requested. +- GrowthTrack source was inspected read-only and remains unchanged. Its advanced routine builder is a future task, not implemented here. +- Pump commands, pressure ceilings, START, stop/vent and the session engine are unchanged. Filing adds data-only timestamp/connection markers and dispatches background work. GrowthTrack UI refuses browser/keyguard/navigation handoffs while the pump owner is unsafe or RunService is running. Neither data screens nor the retry service own a PumpLink. + +## User behavior and consent + +The fresh-install Ready screen offers connection once, with Skip; Settings → Connected apps → GrowthTrack remains available later. Existing models default the offer to already seen. Displaying it consumes the one-time offer, so cancellation, Back, upgrade and relaunch do not nag. Existing setup and START gates remain intact. + +The connection dialog and information screen explain that browser approval enables automatic session sync to the user's own linked account. OAuth approval is still performed by the human in the system browser. Connection never starts hardware. Earlier history and records filed while disconnected are **not silently uploaded on connection**. This branch has no historical-import selection flow; historical policy was unspecified and remains a separate decision. + +Every new filed real recording with representable saved work goes through the same sync path, including stopped sessions with recorded work. Simulations, missing stable identifiers, missing/inconsistent original timestamps, and unsupported timing have visible failure status. There is no technique override. Session summary opens sync status, not a send form. Retry is an optional recovery control; ordinary completed sessions require no action. A permission error asks for reconnect instead of repeatedly attempting unauthorized requests. + +Local disconnect stops future requests, cancels the retry job and erases encrypted connection state and its Keystore key. Erase-all OpenPump data also resets the connection. A request already in flight may have committed; already received sessions remain in GrowthTrack. Reconnect changes grant identity and discards previous-account pending work and receipts. No profile/email scope exists in the inspected contract, so OpenPump does not invent a verified local account name. The browser identifies the approved account. + +## Architecture and files + +| Area | Responsibility | +|---|---| +| `GrowthTrackProtocol` | Fixed public registration, S256 PKCE, random state/verifier, strict callback, stable external IDs. | +| `GrowthTrackClient` | Serial OAuth/refresh, allowlisted completion snapshots, same-grant encrypted queue, receipts, bounded batches, retry/backoff and partial results. | +| `GrowthTrackSession`, `GrowthTrackTiming` | Recorded wall-clock/time/pressure mapping, fixed techniques, confirmed command-phase clipping and verified stitch joins. | +| `GrowthTrackOnboarding`, `SetupScreen` | One-time invitation, Skip and clear automatic-sync consent. | +| `GrowthTrackStore` | Keystore AES-GCM, AtomicFile and no-backup storage; no plaintext fallback. | +| `GrowthTrackHttp` | Fixed-origin HTTPS, no redirects/cookies/logging; bounded request/response size and timeouts; cancellation generations; assigned Android job network. | +| `GrowthTrackManager`, `GrowthTrackSyncService` | One serial worker; startup/filing recovery; persisted network-constrained JobScheduler retries; targeted job cancellation. | +| `GrowthTrackActivity`, callback activity | Private connection/status/information screens, system browser callback and own-account consent; optional existing read-only library previews. | +| `Model.Sess`, `AsRun`, `SessionActivity`, `Migrate` | Preserve new original timestamps, frozen track/stitch facts, completion grant marker and old-file compatibility. | +| Settings/summary/Store | Later connection, sync-status entry point and erase-all cleanup. | +| `docs/site/index.html`, `assets/growthtrack/` | Compact inline section, three supplied screenshots, implemented roadmap substep 2c. Separate GrowthTrack page removed. | + +## Durable automatic completion and retry + +`SessionActivity.fileSession()` captures the manager's non-secret browser-grant identity in `Sess.growthTrackConnectionId` immediately before its normal model save. It then dispatches sync; no HTTP runs on the pump/UI thread. Startup initializes the connection cache and also resumes pending work. + +The serial worker reads saved records and only admits markers matching the **current exact browser grant**. This marker closes the filing/queue-save crash window without importing unmarked history. It is not a token or personal identity. Empty legacy/disconnected markers and markers from another approval cannot acquire a new account's consent. Restored model data alone cannot restore authority; encrypted tokens are kept outside backup. + +Completion produces an allowlisted detached payload and appends it to the same-grant durable encrypted queue. Queued IDs and finished/failure receipts prevent duplicate completion callbacks from adding another item. Later source edits cannot alter a queued retry payload. Stable external IDs additionally protect server retries after a lost response. The existing JSON `approved` field remains the internal queue name; native production UI no longer invokes manual approval/selection APIs. Connections from the earlier per-send implementation require fresh browser approval before automatic behavior is enabled. + +Offline, 408/429/5xx, malformed results and transient per-item failures retain the same queue and use exponential delay from 5 seconds to 1 hour; numeric Retry-After is bounded to 24 hours. Persisted one-shot Android jobs require any available network and preserve minimum retry time; Android can delay execution under Doze, quotas or force-stop. Reopening OpenPump also resumes eligible pending work. Scheduling failure is visible and pending snapshots remain secure. There is no fixed delivery-time promise. + +Job ID `72041` is dedicated to GrowthTrack. The service is protected by `BIND_JOB_SERVICE`. Main manifest includes INTERNET and ACCESS_NETWORK_STATE; the latter is required for network-constrained jobs on Android 14. Existing RECEIVE_BOOT_COMPLETED supports persisted jobs. API28+ jobs use the network supplied by JobParameters; older supported platforms use ordinary HTTPS. Replacement scheduling is posted after job completion to avoid stopping the job just finished. A stopped queued job does not cancel unrelated OAuth/routine work; active same-job work is cancelled. Reset blocks new work and invalidates transport generations. + +See official [JobInfo network requirements](https://developer.android.com/reference/android/app/job/JobInfo.Builder#setRequiredNetworkType(int)), [JobScheduler replacement semantics](https://developer.android.com/reference/android/app/job/JobScheduler#schedule(android.app.job.JobInfo)) and [JobService lifecycle](https://developer.android.com/reference/android/app/job/JobService). + +Requests contain at most 100 sessions and 1,000,000 UTF-8 bytes. Oversized multi-session 413 batches split; a single unrepresentable record has a visible failure. Valid response counts and unique known rejected IDs must cover every submitted item before any receipt is recorded. Permanent rejections leave failure receipts; transient items alone remain queued. Accepted versus duplicate counts are aggregate, so a per-record receipt accurately says “Received (sent or already present).” No false individual distinction is invented. Missing permission or unexpected permanent HTTP failure pauses automatic retry visibly. Revocation/second401/410 or unsafe credential rotation clears authority and stops sync. + +## Public OAuth registration and security + +- Client: `gtc_bfa5809efbd314f246dd5d0f` (public PKCE; no native secret). +- Exact callback: `gt-openpump://oauth/callback`. +- API origin: `https://rxtqbktyeetmbigbpbdd.supabase.co/functions/v1`. +- Project/account setup: . +- Remote revoke/manage: . +- Required `sessions:write`; optional `routines:read` for existing read-only previews. +- OAuth authorization is GET `/oauth-authorize`; token exchange/refresh is form POST `/oauth-token`; ingestion uses `/partner-ingest`; routine preview uses `/partner-routines`. + +Parent verified the public registration screenshot. No older sandbox password or unrelated client was reused. Code/state are single-use locally. Callback rejects wrong endpoint/port/userinfo/fragment, duplicated/extra fields, wrong state, code plus error, stale/backwards-clock requests and missing required values. Pending lifetime is 15 minutes. Wrong callbacks preserve the legitimate pending request. + +Access tokens last up to 900 seconds. Refresh rotates serially; the new refresh credential is durably saved before further authenticated HTTP. Before code/refresh requests, a durable exchange marker is written. Crash, ambiguous response or failed secure save forces fresh authorization instead of replaying a potentially consumed credential. Cancellation generations stop stale queued work opening HTTP after reset. These behaviors are covered by mocks, not live server/device tests. + +Token/verifier/payload/receipt state is AES-256-GCM encrypted with AndroidKeystore key `org.openpump.growthtrack.v1`, random 12-byte IV, AAD `GT1`, AtomicFile and a 16 MB encrypted-file cap in no-backup storage. Missing/tampered/inaccessible storage blocks access; no plaintext fallback. Tokens do not enter preferences, model JSON, backup/export, logs, clipboard or forwarded Activity Intents. Only the non-secret completion grant marker enters Sess JSON. Release forbids cleartext; debug diagnostic upload remains separate. Secure-window and app-lock whole/settings/history preferences apply to connection screens and dialogs. Background automatic sync is the already approved connection behavior and does not navigate to UI or unlock the device. + +GT exposes no native token-revocation endpoint in the inspected contract. Device disconnect cannot claim remote revocation; the UI guides the user to Connected apps. + +## Mapping and material limits + +| Positive confirmed hold, excluding release/rest | Fixed method | +|---|---| +| Through 6 seconds | `milking` | +| Above 6 through 30 | `rip` | +| Above 30 through 120 | `interval_pumping` | +| Above 120 | `static_pumping` | +| Structured recorded Length track | `length_pumping`, overriding duration classification | + +Labels use saved acknowledged command holds, clipped to delivered closed-row spans. They are **not measured pressure-plateau timelines**: OP does not preserve every sampled plateau historically. Zero-release rows are one continuous hold. Proven `cyclePart` markers or matching older frozen plans join stitched chunks; 255+45 seconds is one 300-second Static hold when proven. Missing, changed, gapped or unconfirmed stitches and ambiguous paused/lost-link phase histories fail closed. A stopped final chunk is clipped to its delivered span. Release/rest does not affect technique thresholds. Total/set elapsed duration may include recorded releases, and must not be described as continuous time under pressure. + +Structured frozen `trainerTrack` determines Length; no free-text name, picker or guessed category. Old snapshots retain unknown=-1. A verified Length record without detailed rows can use a session-level method without inventing sets, but it still needs reliable timestamps/duration. All mixed methods remain inside one original stable external_session_id; GT's current SetSchema supports per-set method and groups exercises within that sitting. + +Uploaded fields are stable session ID, original started/completed timestamps, routine name, total elapsed seconds, set index/fixed method/recorded duration, observed overall peak (kPa magnitude divided by 3.38639) and saved measured **net** time under pressure when valid. Gross sealed elapsed includes releases and is never substituted. Whole seconds round down; subsecond set duration is null rather than a fabricated second. Unknown/implausible optional telemetry is omitted honestly. No measurements, photos, notes, user/email identity, invented set-pressure average, heat, vibration or traction data is uploaded. + +Old `Sess.ts` meant filing/end in some releases and start in later ones; it cannot safely reconstruct original boundaries. New nonrejoined records preserve `recordedStartMs`/`recordedEndMs`. Old records and rejoined records without all original bounds are unrepresentable. No guessed backfill was added. New unsupported records receive an explicit status; this is a material coverage limitation, not an optional user selection. Detailed AsRun retention is 90 days, so long-pending records without a completed encrypted snapshot may become unsupported and show that status. The normal saved session remains in OpenPump. + +Verified GT SQL deduplication uses `(user_id, partner_app_id, external_session_id)`, including same-account relinks; approval logic reuses the active app/user link while rotating grants. No SQL/RPC/server changes were performed. + +Existing optional `routines:read` remains a **read-only preview**, not routine transfer/import or an executable pump builder. No device pressures/cycle/escalation settings are invented, no routine is inserted and no hardware can start from a preview. Private preview responses are not saved and are cleared on backgrounding. There is no routine-transfer claim on the website. The future advanced builder in GT is outside this task. + +## Website and supplied screenshots + +Only `docs/site/index.html#growthtrack` is the destination. It uses the same `.wrap` width/padding as adjacent sections. Copy is compact: automatic connection behavior; fixed techniques in one granular session; advanced tracking and voluntary study/data contribution benefits. It explicitly avoids claiming measurement upload or automatic research enrolment. Roadmap 2c says Implemented; no local-branch/QA metadata appears in public copy. All original ten main steps and 21 roadmap entries/metadata are preserved. + +Three user-supplied PNGs were resolved and materialized through Library's official helper, verified as readable bytes, inspected and copied without pixel modification to `docs/site/assets/growthtrack/`. Source dimensions: dashboard327×692, girth313×550, length313×478. Desktop uses a 300px-high row; mobile a horizontal gallery with full-size image links. Aspect ratios are preserved. These screenshots show genital measurements and dated training charts, though no account name/email was visible. The user explicitly approved including these three screenshots unchanged in the public personal fork on 10 October 2026. This approval covers the images and their visible measurements/dates; no website deployment was requested. + +The existing main site's first-use liability agreement was not accepted by the agent. Live read-only DOM checks verify the section/wrap/roadmap. Desktop and390px mobile screenshots use an isolated local visual QA fixture built from the exact current section HTML and styles; all three images load and the document has no horizontal overflow. This is layout evidence, not acceptance of the site's notice. Main preview remains alive on `http://127.0.0.1:8766/index.html#growthtrack`; nothing was deployed. + +## Completed verification + +Final `:core:test`: BUILD SUCCESSFUL in3m49s; **2,140 JUnit tests,0 failures,0 errors**. Includes57 protocol/mapping/integration mocks,12 automatic-sync mocks and5 onboarding tests. Legacy SelfTest35,830/0, Scenarios58/0, MigrationOK, Wiring1,787 self-tests/0 source violations. Earlier wiring violations were fixed in UI code; checks were not weakened. The final source-only wiring follow-up also passed in2m44s with453 pure/46 Android sources and0 violations. + +Automatic mocks cover completion without selection, every distinct record appending, duplicate completion before/after receipt, disconnected/preconnection exclusion, offline durable delay/restart retry, filing-marker crash recovery, relink and disconnect/in-flight races, simulations/invalid/unrepresentable status, partial permanent/transient results, paused permissions and migration from old manual-only consent. Fixtures are fictional; mocked transport cannot reach GT. + +Java17 parsing checks all45 main Android source files, and XML/source/whitespace checks pass. These are **not Android API/type compilation**. Hemmaservern has TemurinJDK21.0.7, Java17 target, Gradle8.11.1/AGP8.7.3 and F: Gradle cache. No Android SDK/adb/emulator was found. Per the user's instruction, no SDK/emulator/BlueStacks installation or license acceptance was attempted. + +## Required developer validation + +Use an already authorized Android environment with JDK17+, SDK/platform34 and build-tools34.0.0. Do not add secrets/signing keys. With no Android runtime currently available, the following remain **not run**: + +```powershell +.\gradlew.bat :core:test --console=plain +.\gradlew.bat :app:compileDebugJavaWithJavac :app:lintDebug :app:assembleDebug --console=plain +``` + +1. **Build/API:** compile/dex/lint on minAPI24/25 and current Android; inspect release manifest, exact callback, private Activity, BIND_JOB_SERVICE, INTERNET/ACCESS_NETWORK_STATE, persisted-job boot permission, HTTPS/backup rules and absence of debug console. Check assigned-network API28 guards and all Keystore/minSDK calls. +2. **Consent/UI:** fresh Ready offer/Skip; existing users no offer; Back/decline/cancel/relaunch no nag; own account shown in browser; dialog explicitly authorizes all future new session transfers. No session picker/Send/technique override. Summary/status remains accurate under locks, rotation and stale Activity callbacks. +3. **Automatic completion:** connect, file multiple fictional real training sessions, including manual/ordinary and stopped-with-work records; all supported records queue/send automatically once. No transfer from simulations, disconnected/preconnection history or another grant. Unsupported records show reasons. Verify connection cache initialized before a first filed record and recovery after process kill between model save and encrypted queue save. Do not operate attached pump hardware for this data QA. +4. **Retry jobs:** airplane mode, intermittent networking, network switch/VPN, metered connection, Android14 permission, Doze/standby/quota, app background, process kill, reboot, force-stop/reopen, JobService stop while queued/active and schedule refusal. Respect persisted delay and bound same IDs; no busy loop or cancelled job interfering with OAuth. Every network/storage operation remains off UI/pump threads. Verify jobFinished/replacement order on device. +5. **OAuth/rotation:** write-only/write+read, exact own-account scopes, decline/no-browser/cancel, strict forged/stale/duplicate callbacks, cold/background/recreated callbacks,900s expiry/401 refresh, rotated credential saved first, second401/410/revoke,403 pause. Crash or lose response during code/refresh exchange: no old-credential replay. Concurrent operations never rotate in parallel. +6. **Secure storage:** AES-GCM integrity/random IV/AAD, AtomicFile failure/recovery, disk full, tampered/truncated/overlarge data, missing/invalidated Keystore key. No plaintext/token logging/preferences/backup/Intent leaks. Reset remains usable and erase-all removes file/key/job. Restored model markers cannot restore link authority. +7. **Labels/timing:** 6/6.001,30/30.001,120/120.001 boundaries;6s hold+80s release remainsMilking; continuous zero release; verified >255s stitches and interrupted last chunk. Compare confirmed command-phase projection to approved device recordings; ambiguous paused/lost/changed stitches report unsupported. Frozen Length survives rename/edit/delete; free text never determines it. Length cannot bypass timestamp facts. +8. **Payload/results:** one mixed-technique sitting remains one ID/session with proper set order. Verify original times, elapsed/release/net distinctions, kPa→inHg, missing optional telemetry, whole-second/null fractions and no measurements/photos/notes. Test100-record/UTF8byte batching,413 split/single failure,422,408,429,5xx, unknown/duplicate result IDs, incomplete totals, permanent/transient mixtures and lost-response server dedup. Source edits after enqueue cannot alter retries. +9. **Account/disconnect races:** reconnect while queued, same/different own test accounts, reset during token/upload and before a queued operation enters HTTP; no old-account payload carryover. A possible in-flight commit is stated accurately. Previously received records remain, local erasure does not claim server revoke; Connected apps removal stops access. +10. **Safety/privacy:** app-lock whole/settings/history and secure-window preferences; failed unlock reveals nothing; optional private preview cache cleared on background. Browser/keyguard/new screen refused during unsafe hold/RunService; no second owner, pump command, lost STOP handle or timing change. Automatic data-only background work must not interfere with existing pump behavior. +11. **Existing routine preview:** extra scope only, own/favorite/community authorization, matching UUID/schema,404/deleted access, own notes and expanded order/rest handling. No executable import, pressure substitution or routine builder was added. +12. **Website:** main inline links, 2c desktop/mobile roadmap, preserved original entries, no separate-page links/dev QA copy, keyboard/focus/full-size images, mobile swipe, readable aspect ratios and approved screenshot publication. + +Do not release until Android/platform checks and command-phase timing assumptions are verified. This local implementation does not claim end-to-end production or device validation. diff --git a/docs/growthtrack-developer-message-draft.md b/docs/growthtrack-developer-message-draft.md new file mode 100644 index 0000000..02b8202 --- /dev/null +++ b/docs/growthtrack-developer-message-draft.md @@ -0,0 +1,19 @@ +# Message draft for the OpenPump developer — NOT SENT + +Hi, + +I've implemented an optional GrowthTrack connection in my OpenPump fork on `codex/growthtrack-integration`. Browser approval now enables automatic sync of every new real finished session to the user's own account. There is no per-session picker, review or Send step. Disconnected and preconnection history is not uploaded; unsupported recordings show their reason. + +The implementation has public PKCE, Keystore-encrypted state, a durable same-grant completion queue, stable IDs, offline/backoff retries through Android jobs, partial-result statuses and local disconnect. A persisted non-secret completion marker recovers the filing/queue crash window without moving old-account records to a new grant. Reconnect clears pending work for the previous approval. + +Technique labels are fixed: positive holds through6s are Milking, through30 RIP, through120 Interval pumping and longer Static; structured Length overrides these. Recorded command phases are clipped and joined only across proven stitch boundaries. Release/rest do not determine the labels, and mixed techniques remain one GT session with granular detail. OP measurements/photos/notes are not uploaded. + +The Ready screen offers connection once with Skip; Settings remains available later. The existing website now has one compact inline GrowthTrack section and completed roadmap substep2c, plus three supplied screenshots. The separate page and public branch/QA commentary were removed. The screenshots contain measurements/training dates, and I approved including all three unchanged in my public fork. No site was deployed. + +Material limits remain: old Sess.ts meanings vary across releases, so ambiguous old/rejoined timestamps are not guessed; unknown pressure-phase histories fail closed. Labels use saved acknowledged command phases, not reconstructed measured plateaus. Existing routine access is read-only preview; no executable import or new GT routine builder was built. + +All2,140 JVM tests passed, including57 integration,12 auto-sync and5 onboarding mocks, plus legacy, migration and unchanged wiring checks. Android compilation/lint, Keystore, browser callbacks, background-job lifecycle/network behavior and device timing still need verification. The handoff contains setup commands and a concrete checklist. + +The checkout is on Hemmaservern at `F:\Codex\OpenPump-App`. Review the implementation on https://github.com/kwikmn/OpenPump-App/tree/codex/growthtrack-integration. Main has not been merged; no PR, deployment, live OAuth/data transfer or hardware test was made. GrowthTrack source is unchanged. Its advanced routine builder remains a separate future task. + +Please review `docs/growthtrack-developer-handoff.md` and `docs/growthtrack-verification.md` before considering release. diff --git a/docs/growthtrack-verification.md b/docs/growthtrack-verification.md new file mode 100644 index 0000000..df6bd87 --- /dev/null +++ b/docs/growthtrack-verification.md @@ -0,0 +1,44 @@ +# GrowthTrack verification record + +Final behavior: connection authorizes automatic sync of every new real finished session; disconnected/preconnection history sends none. No per-session selection/review/Send UI. Unsupported recordings have explicit status. Historical bulk import and a GrowthTrack routine builder are outside scope. + +## Passed locally + +| Check | Evidence | +|---|---| +| Full JVM suite | `:core:test` BUILD SUCCESSFUL3m49s;2,140 JUnit,0 failures,0 errors. | +| Feature mocks |57 integration/protocol/mapping +12 automatic-sync +5 onboarding. All fictional HTTP/storage fixtures; no live GT request. | +| Legacy harness | SelfTest35,830 passed,0 failed; Scenarios58/0; MigrationOK, including unknown historical timestamps and completion-grant marker round-trip. | +| Unchanged wiring |1,787 self-tests; final source scan0 violations. Final Android network-source follow-up BUILD SUCCESSFUL2m44s;453 pure/46 Android sources,0 violations. | +| Android syntax | Java17 parse45 main Java sources,0 grammar errors; **no Android API/type compile**. | +| Manifest/source | Exact callback, private status Activity, protected retry JobService, INTERNET/ACCESS_NETWORK_STATE, backup disabled and release cleartext disabled; no pump calls or credential logging in feature classes. | +| Site source/JS |21 original roadmap entries and metadata preserved;10 main steps;2c in both layouts;6 inline scripts parse. No separate-page destination or public local/QA status. | +| Images |3 official Library-materialized PNGs readable/inspected; unmodified source/site SHA256 matches;327×692,313×550,313×478;full-size links. | +| Layout | Same wrap and padding as adjacent section. Exact-source isolated fixture at default desktop/390px mobile;3 images loaded; no document horizontal overflow; mobile gallery scrolls internally. | +| Whitespace | `git diff --check`, no errors. | + +The initial automatic-sync run stopped on two unchanged wiring rules (onStop service guard and a142-character face note). Both source violations were corrected; no check was altered, disabled or deleted. The completed full rerun then passed. + +Automatic cases cover new completion without selection, every distinct completion appending while work is pending, duplicate completion before/after receipt, disconnected/preconnection exclusion, durable offline delay/restart, filing/queue crash-gap recovery, old/new grant isolation, in-flight disconnect and remaining batches, simulated/invalid/unsupported statuses, partial transient/permanent results, paused missing permission and old manual-only consent requiring a fresh link. + +## Platform checks still required + +Android SDK/adb/emulator absent on Hemmaservern. The user explicitly chose no SDK/emulator/BlueStacks setup or license acceptance. Android compile/dex/lint, actual Keystore/AtomicFile, system browser callback/lifecycle/locks, JobScheduler/network/Doze/reboot/force-stop behavior, consent/end-to-end GT ingest and device timing remain **NOT RUN**. Source parsing and mocked JVM checks cannot confirm these. See the concrete checklist and commands in `growthtrack-developer-handoff.md`. + +Material limits: labels project saved acknowledged command phases, not sampled pressure plateaus; ambiguous paused/lost/stitch histories and missing original timestamps remain unsupported. Old Sess.ts meaning varies across releases; old/rejoined boundaries are not invented. GT durations are whole seconds/null for subsecond work. Invalid new records show reasons, never optional selection. Android controls background delivery timing; an in-flight commit cannot be recalled. Local disconnect differs from remote Connected apps revocation. No executable routine import or new GT builder is present. + +## Website evidence and privacy + +Main preview: `http://127.0.0.1:8766/index.html#growthtrack`, HTTP200; loopback server remains alive. Separate growthtrack.html removed. Compact inline card has automatic sync, one mixed-method session and advanced/voluntary tracking/study benefits; no routine-transfer claim. + +The main site's first-use liability agreement was not accepted. Live read-only DOM verifies the section/roadmap; screenshots were made using an isolated fixture from the exact current HTML and stylesheet, avoiding changes to the agreement or production page. Local screenshot files: `growthtrack-website-desktop.png`, `growthtrack-website-mobile.png` in the task workspace. Temporary QA server is stopped after verification; main8766 remains alive. + +Supplied screenshots contain genital measurements and dated training charts, with no account name/email observed. The user explicitly approved publishing all three unchanged in the public personal fork on 10 October 2026. This publication does not deploy the website. + +## Repository state + +Fork `https://github.com/kwikmn/OpenPump-App`; path `F:\Codex\OpenPump-App`; branch `codex/growthtrack-integration`; baseline `1dddec436e552b0669e69427e9d054a71598d859`; origin personal fork, upstream OpenByte-source/OpenPump-App. Implementation source/docs/assets are carried by the [personal-fork feature branch](https://github.com/kwikmn/OpenPump-App/tree/codex/growthtrack-integration). Publishing it does not merge main or deploy the site. The developer draft is unsent; no PR, live grant, real data transfer or hardware use occurred. Existing CI triggers only on main pushes or pull requests, so this feature-branch push alone does not run CI. + +GT checkout remains tracked-clean on `codex/routine-intensity-defaults`, with the same three preexisting untracked docs. No GT file was changed. + +Logs in task workspace: `growthtrack-auto-tests-final.log`, `growthtrack-auto-wiring-final.log`; JUnit XML/HTML in ignored core build output. Private Library replacements preserve the existing four attachment identities; results are recorded separately after replacement. diff --git a/docs/site/assets/growthtrack/GTdoseresponsegirthwork.png b/docs/site/assets/growthtrack/GTdoseresponsegirthwork.png new file mode 100644 index 0000000000000000000000000000000000000000..f871b23d5b8425441f51ee1915c9b9fbbd8b38ce GIT binary patch literal 60276 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 literal 0 HcmV?d00001 diff --git a/docs/site/assets/growthtrack/GTdoseresponselengthwork.png b/docs/site/assets/growthtrack/GTdoseresponselengthwork.png new file mode 100644 index 0000000000000000000000000000000000000000..f31a9e3b6a5bdf245019d6701aa47c76c60f60cd GIT binary patch literal 53865 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 Y_K&{O7au^u|K54~gFm16)9S7N2cFH6mH+?% literal 0 HcmV?d00001 diff --git a/docs/site/assets/growthtrack/GTscreen.png b/docs/site/assets/growthtrack/GTscreen.png new file mode 100644 index 0000000000000000000000000000000000000000..23d965793e82d28d30a40a0ee338bc1e022a862e GIT binary patch literal 154386 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 z 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 RXb%6G_xgg@j)h6L{x7S@`|1Dy literal 0 HcmV?d00001 diff --git a/docs/site/index.html b/docs/site/index.html index 5689519..00ad0f6 100644 --- a/docs/site/index.html +++ b/docs/site/index.html @@ -45,8 +45,8 @@ - body imagery - that more than one pump works today. Other pumps are a goal: "any Bluetooth pump whose protocol can be reverse engineered" -- privacy wording other than: nothing leaves your phone unless you export, back up or share it - yourself. Never "no internet permission" (debug builds declare it, for the diagnostic +- privacy wording other than: nothing leaves your phone unless you export, back up, share it or + connect your own GrowthTrack account for automatic session sync. Never "no internet permission" (debug builds declare it, for the diagnostic console, which the release APK does not carry) - a fixed pressure limit. The ceiling is user-set, 7 to 57 kPa, 40 by default - "vented" before telemetry proves it @@ -78,6 +78,14 @@ @font-face{font-family:'Familjen Grotesk';font-style:normal;font-weight:600 700;font-display:swap;src:url(data:font/woff2;base64,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) format('woff2');unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;} @font-face{font-family:'Karla';font-style:normal;font-weight:400 700;font-display:swap;src:url(data:font/woff2;base64,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) format('woff2');unicode-range:U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;} @font-face{font-family:'Karla';font-style:normal;font-weight:400 700;font-display:swap;src:url(data:font/woff2;base64,d09GMgABAAAAAF8AABUAAAAAs5QAAF6KAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGoJtG6sGHIZIP0hWQVKDUz9NVkFSRQZgP1NUQVSBACciAIQsL0wRCAqBiwTySAuELgAw/FgBNgIkA4hYBCAFhSgHiUgMBxu9owc4jn/dCULTqmvfhNGB1u1IFJ7TGWYHatg4AGRcDST//x+TG0NE20Cx+g6KgYIqUds9VDV7z2lZ1sZ2ecqBKE5xqmASdMMXXYf4FRVNFMWjTGLo0enGtHAlbMVPcMWLIjb+ys3lG0dRgc90vvlawwwDinHAdJg2lzfNFS5O9oGYjG/REhcajQr1mq/jCXcON0OlH1O+6eJM4krF1JjgBxeBjcsYycrJy0OkU/o3sytZtmXHgaPk6AMQaz9XAFgydMDQIegIN737T0IaRCtORWdUtzFRYy1qEzdleHSzfy8JSYAAYQQIECBggDAFDIqCMhRbummPrnVjDVLbu7/GvLGXven1vNZ2Y4s2WsThYW79g1Y5o2YTWRsbYyMGDJYBbMASVlQMpKWEOUJASkTEIEoF8/yKZ2OAdWCe3yuulOcfLnvPKzP7wcKlA4U/SDTeCvgSRyQU5dIu/H+/993a5dynUpr/RkYngLC40o1mEEIzKFX69fl/3frfdpwby1RFRSeOzBg1KmC0xzSCBM1AxCMGiXZGM2NUZkSHO/J6Xf+t99vKa41buuvxY271LxTNa+iqS5MgY8VKEe0wLVm9MpfDfrHDxexminyVqzX1mSibKBOHMhJQRlZWnrypIlAvQ6rlS3H/fp9BFXG+d86p/4n/iUhgapY0ci1RaBqukNel2QGtB1GZmc8/OrUPyFgse2crfjRrUru9y8buJjlezWvVO+6ctWiPS8la4K41aTMWzlZyaqpgxYFEIZ9pMWRHYIE/mAKSJ3SB6vbfm9uHDDRpQhtIOrMQKs6ankSxL+aH676F/vm1Pu3crn4DG+ob4l0etyDMfOHiZM+bXnhz/oQ69KaXOUAKyCVCRsbYvz9hBSgTH0VGiL0y1UqnCa4ESHrd4uycX5zEr6HeQaUgEfnGmNAH6WCwENYI1JIUdTAyS5DQLW0tAEJaui/hzlN8Z/2AIHRLA9GdM7z3PvQmdD6IPv8o/Sg0Prz0/K+lZS2bvnNKY2cWIvQBlLAJOwNi1v36j3p6ehVHeaQNIescW9Lujm91DikiA3Y5IQPq4ge4CTVghqbIhBu6rDJW2KQid38ZdlgR2es7P9E8O2tcOIwjjFCEEEJ1nTW/+tVjTNN1v5q0tcvnlShBBFTGMZKyydQQLpnb5XzE7GWEo/aHojrlArvEQ3q+ONX+BoGqDhwAvg78Fo/QokHEJpshBk0/HHCDq0p/u+k/NUXSkUJy4aBIEVApBagdN+TVFeplGDTdXGi+RdASS6HllkMrrYLWWAttsBHanyO22Pr2196mD8XRF4f+VzCEa4CGBKWuUmjbLhACwwG2H4/j8Yz+8mcgwAUBOwT3Jvdai6203o5SaZgmQIB3m/1hD/XWFV8A8uKsAjFSraPPmBnWybk9vM/fFQyJ4WgsnkylezK9fdncUEGUZCWohqbC2vSMHjPIfHJpdXPrxl7m5q2D7G0UAxCCa6sKAljJtBq5/rMbJsge/tqtgQwDmL9TgEDQh71v1SB5mkch5otdWtAMI0d50uitV4coHuYm4P5oVwtALL6XqftrhyLBqqpQlAuFZov5dFQhQGQCSrh68qhHAG5IXePDvj1a8h+noe/Tfu4+0OjejqPHt2CBwMPFTCQ8ko7lr6RGFQcf9bW/jZZjw0A4egCCSQedn8+03Rt9qipW9Rf7T+hvItH1RC6fZbZ2L0P2+/w8z+bWTM/oaKd+isrNTzrsiZ6g8RiL2T66zOe+6k+93NH2t6Xlzauw9GKKaEDdatMDNWEI/ZnPeZHrmc25DEaTuqgjT3qowQaVkHjFIWbZHRPy11/9yo993bM+50E3usIFljvZeEc7zH628yHv9EYC+Hd91Cs91m0taFKD6lSzCvz8QMmVLrawy2eeKWzq5SeXhfRnrbRT+p0fQctR1N9yY7lcbQPNTtnscHgCwKaO7ven9xgdvCiOd8nd+XD9Nw0Xv3pD/imxvGaPuNyOvJDHxuXcv352IWnw12ZTLphdMt33EcptXaVcm7Y20dWZ5DgyL6+1ZSO9sQ+Ymt4LtQpgZ2e37LiFbHzcLKaZhLSWrgsaq6UwK/DalPbUZ/Iw3qOjWXNPSo+rokndKZLaUSUj9wht57QRPm5cLvfa7uUJfToWGja7+KPryS3LWMBIUdCog5SLRT/8Ry/cBXbjwXZkM2SmFGhYkAj0G1I6mhTp8tj5EKRyuX/1VlS7zZHzIXUpp5EaB2pilstdiOPmFwKdC9L0QDLbKxMHEKt4TGlQqmyVTXFsbcLdRuTRy6h3eT5w5ZPHiN4ONrS4zCiiau1tHUb4cVIVdwrTeLlc/6JuTuBhfY2/ednbL7+o4aOXfzTi65evW7p/u3x2PV+fIqgLnGV+ocwywteOVR0mFDfeJbV96iwSDoS01UfegRS0yQOXvLZT1/CWFL20B0QUzwZXdNFPlR5dXyWuyE1S3lwWonLEtWzcS0vLRUfxrUooq2F2fCC7tU82gSm4VG/M0l5CL/sU031XyX8dh62tnB4PS5P7Vc/NZ0P2AxiBNxnUDsqepEzW/lMUh9B+TLYEji6/th/ZQA322l56UmrY3bjJj+xSItxhmzDrSliVMCPWXyx7Fn7yDDAy+OQD75pJeQjAEArF+yP96UIawrPHYJX+yy5X2348h64pV8wBgS7pmXYver1T7Q9iOSy1H0iE+Zdrt0WP7efLbmpiKYxD9G8n4PJa9mM1LZu25QS7nqqpgg6AmJnx+fDWrzvYUnhbRLI1d+F+Ea7nEDIk99GVmkNrKlg+IOQvc1ZjpPtcLoqAvaneNyiYJwEuujKYScxmBevZzFbbdpfXDu18a8JuhEi4Vte9YqZd1WqWs8Dc+io882epVQbPl82Hwl50nwKnyrMDbEX6WZiXj6hZSrqRQsAhGD2Ttzy/QJF6BI/02xGQKoGaoPtiy5FT2rLm4fH+ZnXP1XKdfuIvKg+LeWW8T8eMGWe9PKqojk063t8HG9x38sbfUur15fZZMzK64KvZudkcGhn/3vINyDWa/fnLHB7NnntMvnyzu3YBnQ2FcO3tWrTrNWcLxwHExU4gCjlatD12hm0peiavqzeb5yfzG9xZVd7GVrJUY+yl/k2/YbnCdvNWRE99hzU6iN8+PnxxRNWN35773F7FkE9v5sobKLSqRl9G1O7RiGfLzOhjcpO2yaCjeyb4v87JzcMvKCpFktEvb8hG7XVgUMzIxMLBycvHL6iLXnrro5+BpptpljnmmmehRZZYark11gLXvwJiAlhOAOsJSAV0pAgZGYacnIQCzUDJiKBhgYlkh4nlpJLAjebhoZOEh0vhp5chCMoiIhWQoPT6qVWSp1HFEK122iHaa0+kgw5YBgaoSBFOsWKEkZGIiQnLzIxjYYEcnCgvL8LHR8TPjxUQwAkKQl10QfXSC9FbbyJ99MHqqy9OP/2ggQaippuOmGkm1iyzoDnmoOaaB803H2+hRagllhBbahliueUkVlhBbo21WERaisjPhCVHae1MUChpcspaJYvr5EqFl0OFMvQVHGKShPk4TQhGNJVJhBFNYhJhRMNPomhsYVTQ+LBUYSH/vxSarCZw/ZO8XJbZ2jMj0zXlkzXJEz1+4zYH+m8/d6m3Ol5emY3OH3mV65lMeYRhBpsQ/6ZbGlWBDi3y2jNuud5OR55i9u88bmrNhjcT98zq/DagmqRka+om5mRdneVwF+6XeHjxfPy6CAKCwo+r3CubUy5/8oMna8mpstRtBHWaLkW3nQMO4IL7Nm+mHYLk7Hc+dIUO57QQKoAczwPe3J0Gg7LxBnL5MHcemDyiXpasvsIBnHRhIHNYyKIfylBFqHLUWQXOykHtQpeR5By6oCtHDhQooyhQdULWs+uxWkRCvJpDIpTReFYeomYNFfRqN1L7QgcMFGPEhEUOoZyQF/jwE1QXBr3oTR/6MZDpzGSW5oC5zNN8UgsbFmlpYRnLtQKsYe1PE1OFFi/MKWc/0RmpKeaAuG+LT7cwSc4QOH9Im9PSnNkxkJWSiG4adqA8y768Sq7ILGkOgZF2Lx9GfD9RMQ/5G4PJii4wkl9h1rpAWE6i4+2fR9I+M0/t8064buocTActXUprlC5d7+oArhkvK6wcsj+WuBg5A9MdxqSsY1KxZko/lKwmXEi3WN6uo85D9JIh06X160EnmtbsPNgy0v0aHs0zeC1v4I16u+QCcNkrzFyIq74KYslBFy9GTc0LcBqDvhztvh8fWWSRRRYBAIiSaPdE4mmTvk+jOTY3plRh4YcQzeE8dg4MhDpeRF/fFgAmwh/Kif4Mxcz7puGxHgKnRnIaHkyZXqiYg7Aa+66rLrgdS6TvGtNwvDoO2iv8rOdZKyIPMoQ+IV+NeGDPzh+87vs0semAg5W6UYQfABfgde9z4/3zCvPLcsUgoh9PDGgtY94z3WkoQ5EFCoJ+zGIu87Rc5e3gglcA52nsJEZ9DlyJObWV1U6TTs+1qz4UM2s6IOXqsRhUNBVbbb6JvWdkgVCQwpoXIHI2dXEN9MLvmepRZ1NVzO0bDyzeu33zWfPsyBPvXZ99Z9tj8XjfD2ktRAf3OECa40NBgKuV8PJYNlttQKP5w4VcrV6FqULLyoKoOLYFdyr1bAWu1aarLbjQSEs4lQkdSUMIKLyHgZpkUSwiBbsRJNLs3yaNz1rRpHpVowKl63kr7ebx552XwfCwKKYdJyQ4m3OU/+t4OAipUpLA+XtVkdOiDiyw3Q477bLbHnvts98B35wNA2QwDHkUwRcts5TQQYIcELw6WgJ31JrMcCPIjTWW0vdNovJj06id8pnFvDa79bVU1UBDjTTWRFPNNNfCBx+t80ndZ6Pq+u2cpUYY7q8fm2QsgtruAPAXCo2QzhIU6udGlfMGB1I6DRrZlK4ZMM7I5EGnwJjF7a5yT7vCIn5VOeLUo3cd81GNG1lmoIZbXwtr5E6FveGQaoSpxE3fw0cq9ibO5KTb1tmVVuPIHWurrhwfJLVbH7qlbiQE4E8eBCh7huQCq8C6IeIU2TBD21Xcgc0PplI7y/215HxbTa20Og6otmUkiZJk0kar/26sDk+yYRaN/6xE63tZ8Wcln07+TFbWz7uOlZ2Lm5udHYGFraXJz2UwLX/XN7gyn081nUTWKTZRwor+6w52eEZ4/HAJtVb6G1zvzhvra+vR4kxsb22JKS0uf1ZONofZCuarU092xi7cEjRnq6tXR1OyzfolPGNdN2j1TY93V5Vf3F4zhz8pXB7u3j8+nJ76frM5GlUH0aA5XK3NXPCO2SZRp21QAOmvz1B/aP4xMeWZm8GGGGGUcSaaYur7wPy5Flpq7TdO6ub102CHAw456rgzLrjksituuOOuR177GKGCCi2+RFohCxDgz7Bhgga7uYWDq5uHb2DwT1V0cUNFxyUk4olUFjeFnyYQ3Z6aMjv/+HBcNgMq9dVu0QWQ/30FOz7hye9tN8zTT2ti4xPPs8Ph3fo9zV4rwYmwoDvqCOBVbb38JLkdkr8GprCUvnMpeBBwU9v77EYvbl+Gg6TnvSMc/g8Ahi1JOgHwckD5ZNcA50LhOrPVA7zc+SFP+lh0CQD/7m5SYgyooaryhX79UjBBl0QYQ/usWFBt/0BP9j7QkbZuxa+sPJxBcaLioilzn6UwOOKO7gYnYP3aD8/zFOqk1Bw5QOtiGXWtX5MXJoFOGVkF7Rn11t9aIZ3LKMHTvuolP4sOZSivvfU6vG6vzxvw9nuHLw7R7T6Tj1n8hw2tbv2KOjDpY6H15S6shgtoeezGa/e6dsv+VdvfAMfA/DOYhwDMCvC/5y+c6PgvvLZj8u3sm5d/APDz7yvnV06t1K2Eu1984fMiAghcHXBn4L7AQwWNBHFhtr3EsQ7xb7eSGGmUlsbqrppaahutDlYYzhg8keHqGaGVHiYYZ6JJJmtjivGmmoa664PWPjrtpFd6em2Lp/45Vjs54a+P3NbWf4fdr13st9Uz111zwz0GFkGz9DPXPPN1ZNVJfzZ2nQ2wwGwOTl3MMdBvv+Pi1tUgSyy2XjvFvKbrZalllmvPyKe3wYYYapgVZujAxG+mPv74pyJmAX1tsM5Gi6xVQ00PPfbEIwjsIJCGCr/MTh12uaTG66sBWldlf3U6LGo++qUMY8GXh4Pzd/aElVMBA+zizmXOcfAIgJNfQH0uUM4/C9qoCoTE0XaSxJ0xgKGEgocUQrEE1ShOVZO02LfKVnG9up142tjfuHkT7JhwbAdbsB6b4CZSRvKUcxSaBN7IFr0jEa0l3RK3EsoNGVsmpEBZlbAzizjUnWypJ2YimpJ7lISahxV+/nK3QigJNkeaKOukua8ewJuwj2uQHKdAffk0jbqjPD97VdQLtZycKYkua2vOYyGkmzr0++tN0OWoCK4248Bfueasdrth3+chnr+xeCfacdZzZgWzgIkkF17Zbx5jAGPwp4HaZ9ZQFCNkIETYMFa+MFH9eoBn3ytjdQZf8GjJoHjndRN2B5VSZC9EO46k87VmQBzPA5lI6dgaAZDnxkR5CgR31Wb91DPwYzouDbEmDvHkGVvnlgNn+iBjIyYYiDxxL0YP9b/vq+4/XQqovKFhhxaD+McfvZH58/+yr8nVcPG5uv4NTp5KOCvDhPKp+s9S2lMmiQG/586HZBe9xH6KTBmbK7Snylgf9mFFAE+ZOjUCAFQ5fEZRhkm+8WeZ4JPTQbX1cZeA/4JmIIYmawqj5lTvi/pgrVnLWxUrNUn8Crsm+KkUMfRZLTfjxDjyokLvQVliH3mM0fUdNF85O+GwJUOXMGgu8Cbk2/CfPlL6sv7e08FHYyGbFT3ZrROyhGL2Fjk/Z4urb7ZvPzeksyVzFPqqp39H/Cl4EQyTJnuKkF9uvhzDaNY4s8+EjW6zJJ2hUctS59Uj3QjyNkW5bpn8xel3CFZX7wFgnbvmNN4jjx6JzqMvIjEaL/7yKkYwnJoxdLrRfKUFk5JAXqEQi8KQbqMv5Gv8roF1eSiC4Sdrj/NPsEsMUNpHuxik6gJK2afQp5V53EeTOD485wSOUbQLmLcxl6eFHJ2cFd0iXzI5vJRIZuLEANcAfB9L6Yk0m87H2MZ90wHEXD7DjrquhwKDvmpP+ckbc/2tjPfEis36P8Kk/UF1FVbsYozNhdqA8Zu31xnYn20/tWJHcAkyRTKEBRUGjRBGaSgevA1idD2hk4qSsjUq9Iicpp2Jk0tU1IB4THIe8ImKR7seJCeGoSUFhD14X8ya5KlBexpM+kImP0VpIISbbj7HT0ZzhZD8ffngqV+8Dm9hgcVKPWiU0OmokM7A1k/guiFvuq1vf4Aic4E6HaWxkwrDT6QdHJK62lbgd/hNn3FUsVw69luLDn9ffhmLQRpCoqBL5sLhsiooTFDVu+G9QPJPcM3rLjihxOfqanx01OAaBhTvozQhNyWvcjGSMBoKqYBz/KAwwEAPUE0nLI2fulHbVqzcjYXv4QXysYptUUo7/JBJR86WMc0aguVgmdsHsstQ0rSalTR1dnwMI9AA1eJkpsi0LZtD5PVJA+L7E9vezPKGV4vqqf8mLfBOK0foBqiiimuQMICc2sBoYoUfIzKfYcyPaO9sE/EoandVhFBdsMrKLBSH7lITPirR2fWwEIQrDFjZ8iSF3FbzLiPV8RVgRbmsJOqHAouNrqfzxAgfc2iHGOBfKonOWkmAN4tRPXV3+v0FtmaO0Dhlr7JnfOJrWvN7gfr9v5Pxl5IqKIllmpAwDPnkUGL/LwbeUn36n5hKMXkwl6Z4gzTNFsmKNrRkE1QKE3p9LJumEl1MsHvQpDmaxjj+fIaSQnKBpm8WX+hDCj3stBrfOu2MwwY131wCVwyoj2DpBBUlLF1+OiUZZXlKmLBcSsO0eE4cga/IYGL5JBmtHSfv0Gvyx2RTQ6BNrZ8WuyRVmt/PVYcdWwMK4tuD+wzn7g6K02l7dZmgyt8R2kpl8ZT5bz2JyZC8U8tpbU/PvsIX4rXgNv8GZYxnMbDrrxmMIZxCvpHTAe3Uy8efVjSHevZyB2wkNU1TpD0MIzkyfBZPIokLA13R5lF5v8lsA3pYR4LQPsXr4Iu7/iwj6ysffHNrwpOEyjVx6QfdzeM/DD2NMEBqEJAZo/UAVSlC2/64kRF2Inb1GzTTP+JtjJ5igMp6W9K7kz6ZfnrRXWWceP9AFQpa3T4/quL19vFOj1k4aqPCESYxSEOvsQ3kzWjHl+s/33UNX276BCIGFmfvYndsD93BXLlUyhhYng8o0YmdY9ZxcjTNQN+eWPRqhSkqvdzOMxvsf9BK4JVY1okBxQ0FO1pMAutBfk28caFOLGxXzY/Eq/y7LYdZ9eevvjGeuuOPphR6SkUs/c6j61DoXwHHBiSfh9cegZqlrqafEklJSRCjzJj1U5NN0t5SiHwzSdPHJ58LX1QXNhRk3vvzRdmCzXMkLwndbRkbGejBMYU+1o7aaZ1K699KUwT1OGB1MEMVCGUjjZ5MVgKAs1tK0U4OviGMVmZTmIyHMN+9QTudIgzo9iANbc1ikqaPrB8TNOU7VcFwfDKCYa8+YhGE7gF5z7gd1NFV0Y7bQvKKPpLnpsis58FjvEUMqKHOQqXsN5khbujdNj7ralPAJrD+jGv/hhyHlsIuyLzNc3mzkI7iuUfo+MTQSs+DYvg3BgZbX41PzoX6lGMdqiqB8hKqzuxMI5HTbF6+9OrVxvVhXKFpgyrIvBI2r9+QNwUanSM53IGqqG6NjS1ufjrDtbafPyfwTZ/N514pA+JJee1go9XP+PCS5Oa5MeAeWLHUA0OOh6BX8CvoebXeu6TAMMco1MyfXDXiORZxDfeKUjxU8VpvbL39a4HvyJDi5I0+6R7dp0f0mJ6h5+jZZQH/fyLzr8KEj/bMOzp8cA+JIoWgiVUgkOoKg3t4hMNdXcNDPQcGBVE65WblQLB6HqLFrCcMHXhJb70eN0TiCQt1c4WFeXogwlzdEKHAseOuplAgFoZwLQMiA4d5SXmjfhnCATSjs5D3P4i8NSGqNJLFVPX58aOaD0bsZY817w9lFmMYSC5HhMlBoQS+NkxsdysH5EOYGg75YmUQLbpIBYfH5BOiu6XiqM4CfCxCGRZZyAgqJ861JfEl00rJcqsm49WlHHsIUCVqM52XwV5IxtVs1UtNu2r5qiwjvsCNgQtJ/9Dxwx5sJjMxIicuplMpQnZlk2JF0nRWJ5d2bIgETCGZl3IyXmlaJcvTSgm/PYkwVx7EiCxUhIVjCvDRnWJpdHcBAQNTwmOKaEGV5Isazj3I3uVMLTBIBLsgOZfzxC8b2kQvLytzBa0EWpPVtBH3HDPmucN2mSebFoimZtOCW0hQblCbikKjKUgmhK7NdH55Nxp8i2OglFn+0faJm44dzH56cr/Zn/awEMyO4rj0b+lg/4eGR1h5RaKoK7MQ550uI2EwfWldynMIrhDqDIMiMllVAVEM/yB4Qrpj6JNDN0vsXKKCuZllGkkyvXToL+EGS33hCCG5jcdPbhvBC9OG8SltfF5K2zAhVSRUlSXzVUVCQU4RPzmnrPcs+mAiLE+FTNzUSmZEA6nH4aIaCT6XBh9WBfURPvTnqCSdXNxUoUeYtkOYN9hu1e4md3bKcHPLcHKWG71pn9XOgp36Bwhfm3A1thlWhBdaIayMyjrFdkGJlDQpUoOa7T0Bbi40F8Ry9ejSpWBI7hVK2/5VxY80YHYCIaqIig5fP4Mq9LjEKqgMJVUVaRfBtHrbnFNKl0j6+fk1ax73wgboZbddT1eAJaTwuiRjuedExgu/vVFYUG3SFyd6pMtwKS6jFRLDuiUnqxeq6AWZHvxvPVCzALIhfA0jYbw0jIRWyUPgqGJswnGFNPp4IS4WoYCh82hhlZTpNgZbfEmhWNa2K1/OKyxmgEF90d7lUbALIl1QKF+2axXLlxUidjuDOl2JoKHzFDAEphAXc1yqwB4vxqLD5MGRKrJLDnqiNqX7Xpm1qK0u03U5lY7YG/OvFNyc1c66nr4Q/kUtZ5mh+je0jsNM9uFSmC6nFcAcUjmBYVTokpr1yeVkeUiKbXC4k198KAkyEai0LiihZf5Y2TtyqVYvTVMjgCax0vHenAGXmdAsqyIFDUXLPxkLfoFgsgMjFORFza3GCvvobf/fJbLm7j9kyUtnpwNv+Kx2VuvAmdUmKjOzBeQSBqpfEHCc+rpfKU0/HkvqygW68OnR7fM+u7V7FPJ6evAlr2sL/NDOT78Uffj/dr0MZtod3dMtq5f2vL3UArz4z/nLs9v4RvzZtOVM8f90mkEa0J2fHt5R7GW1xdKrc3dJjlNt0zFZqNjyTB2abekTFBflLd8O3c8AqXmec155b/Ic55zygNd+/bXZfdy1Hj7sNwGsaarnrgDcXHtYcZ5MaiSmy9vlB6I3ZSbCspBQBxWHdcI/27/MrzZHQWeUSUv8PIa8ZBVSPlstUIBcSO8vvYV2C3erDn6tb7nx23Xnzj5zm8zBCimnV5y0fKI57l1XaU7TcEfRYFn2xRcpwKRnTTwL0bTunZtp3QvRzoo3LNAf3q+5lS98UV0jfHYrrwZcXzNUp/Yi1cjh1HJwc+1fNbPLR+0TAMYgzb8fO3JwoZv2Z3ff/URFw3MlpqjBMwzbgsFLZnKl77rqaC+G8hSUuoSE/kK3zqgPvYUvIPttfwrckHpf6Dlf6yB00Kxp/IR+WnAz+JCvgf+6kM7V7nA+MVuco805jB1sieXF17iB7MLaYw/LgGcvo1gPbhmI/lK0r7WD3dw1plv5rTVg0rO2sl+Pu7UqS73lQx44MN/PO9DdtR2qBSYla5O2bzZu1vxqbdge4bTHSQtKIdrfa6vNnjxD8vca7bPffOnp75PVx2rP3NL5e+aW8ZnTtzf8MXcbgKtrjXeWFs+eWbp954836X8Uq0ulmerDxX/I+Wl86k2sJgfiCycqHTNOmCCttnmT9tsOXY7f3nnFBcfNtQuPnt3qO6F/Wx/t07VK8R8s8/8k2lxdXyGSVpWW7QObIXl3yflndIpaiDhxtU8ipdQfRfePY7EZh57HHPPtqC2qXxm7c/HRyJZGzWZ8CTMr57AgMRwfRzXVI6mdtMcLuanH7zAAWGCrf1LPWaq1akugebOmtThzRltXO6I9Y6G1aM+8/yD1yJGHqbL77cD2BAQR1ntAf61r/8JaL8QqDOgsrI3s5NJddr5+lrqz+74ThIsf7v+j79lumXho+3xiD2BPiGYU0/zvUfPsWdm85GLa7wm/g7Bam05h2QlYk1de67frg/oqNyvdADczwxpNlUv3q4nfDQjQK43s3DUmBz9YIu2eWEY4oegVeCdRYCUGffd1p6PjbWD8GikKrMKg5hbmHZ1ggR4Ok6ZIJxStHJi/HRbFSDJa1qMNym4OFQwOieIvj14/83b1+mkw8FoSPzg0XHCzLMagdV2SIYm5NHLj9GoryXZkG3Dp/faL/nZ1e/9+OcDp+fLJcjCfUl5ffiRXXa8G8ZDGZ7VZ5ktLWWbPahobntf4UMsxf15bX9vVZ/zrmZro6hwgxf+sYOfCx7yPWq8vPAtYy5rcFTQmBcgjBQyCxZ8shg1DRWGWqBTyZu5G7rBhuvToFUyJao4m7EljRdSWpJCt/ktiWDFSEoKW6kA8hD7sVZEce5TbqYMjyOoDKVWtZcmwUE6Fn6Qmh1nfl5FMLsvOYx3F7ON1bslU1I5BxbnzBHErlyOuSrWWmzcfy0oX996hAyfu3/WIpZIlD41HhbKW9ID0EHhCFPM4WS9QaqWdGbIyTY/qq1+cN2O8JuB3+/Puf6uM3W59On8uS3D5phiQIEdXTmQfOHsv1+ldR5d4MDH56Abu4GYSn580NG+nY63HJBmX8FraQonScxzeSEZm2vBFZl6ttKlUKmsqkUbJoAgxKriBDJBb9yR2NhLrrKyIIAnS+IpW0M1jsMuGUMKDuFUXbhM0gR8cSUrWeLG6zqYkIugE7EFS8WpBAiEXzxI03U48UnqbLNNyMZiSljjxfsPHHvGFPtEsWEgks8SHiFN7xRB94OS4uIOk4sOeDdVZHEXLSxzwheRcTBKOZnTpSivKlfL6MsT6gK1eGMkkjVXTgmSKR7m8Eaks9dQcPT/9AqG0J/Jt7R2e6+OeM02RCli4CJnZUiqSNpVmZjSVgFxI0jmPCg6midNCIok7vHlJ6kA4yy+Knkzcn+9YNR9a7qQ5VkjkH3+UstoGDJFZwBCZ8F+SeY/34JGi+vdjyz8+G4XUazfjm5ILq+vFhPD4eNIBliXYGUomWIlw8ZF2YZaxwRYRXpi8FFF2IXMrvveHI4/P3igYJ7IbySkpFRnBYVbRIaZxsDg0wQMGdXGFwj08kGGuLuEwwCC5xCCIFKui+PFudlbV2WZIo8Ykjh8dwREXVR/IzhWuksormGSsCb7XuOp4z7xsl3BaCCide6JLoNGIWlhESHxTPI5WcipKKe6NphZhMejiDkmhJy5e5KYgO1M9UQIGOV0giPTxZJ+rDC78xcUzOsqA0GhYre7Qpo76lSSh1ZjWr60/b7emDG/rFXJgkWm1UH7WTe6JkYI15xJBoNvROsOLV74KAUKH95iu+GdJbT86UuT44QU0e/bMfv7NmbS/61QRl7MqWyOxWH6kcRjNPFyRxMbV40htAmZEb05eO5KorKytd/OB2flns3ATxcePrG8m0z0bBbOXkr/VX5114gz82dcRy2M64h9i/XEI+GEstYUnFGYA90R5kkIwBRWMJalk/ysH+rFJKv4YlD+VpJDdKQd757U7xjp3jS3sS4EdwQd3Xd/Av26dJObN/dvqb6bXwW112ODWJv4ty6T4wV31QF5x23yvQVcvoOHgz55+F+/Kv8ff9l2Jy3nPdk1bjT17hhTTdqfjDpDUmiYjO017keCF15u1rsQ7uyMsAxqRz6BH5HlOWLgMisyjM5D5shhEWiAKR6eiCIEBEQQqPQJXyBemAzIghPxMwYHz/VF4Gg1F9BeF6BcQgQcGJy5oTXuRneDGr6Bf1Fb6RjcOmVdR8RkYpH6uAIIbFk2W5B1tIOvHZRve4Bhg5Aj+uuPYhAr7U9kFghnS1qLFwtYMSRgv33WbUB6raMga6Jqk+aWvGq6+47TJrlemAWzZ8d3N+Kc/Mnzkb1Qcm6Fzj14+/ibfhbI/c6nAzZnELGG9l9l74Y717mWe2sXqOVdgePhrazn/5Zl1rXWLu+pZ7Rlt4HyTgPHBw3dv/im4eq27h27Uu8rrr5+8/js/L+n+7hXvcb+J656b/mb/hfcxLrbQjr587S9I1JK2C32VdyZq4Ln3FYFv3h/v4m0yiOZTXdmZj+NSmekvNW3ysZjABAaMXOiEGZvb+OfMnO44TuneT+GMX4iphqwtqKN+/fTVVm379bevUeXg6lr77x77+gjmPg9vRbBlP+s+T5CEuewKKRBJ0oJYlv4jyeU94ceQn8AS+gtvj3SmHcXEqRNoDEW3Pzem1TQcwu7T7A+kFsaS0WwuKzE/Qhbnl0zm9FzgiYsWCoVvGhoEK5dy89NGZndjPW2M6CdQsmghXxSBbdkUOjELb0Lu+oHKP3w8ElhAMi/nyF5rWiQvplQiwUAG63ZFILgSX1OIQMQW4KOPS8RRXYVELFHBCTtlIDDqdncQGQw8EsUpGDzs0MtsI/ihugbEh6wTPUSTRpkrFwqpLeC+gvS4GR/GRN8AAdE9Y6be/exMsGqStRmSPScRXDEELzzOzpqVUNCIMiu3jJRFDKWpXiglv9UQUHA8meWKibzUlw1t9QpNKBUp9QRSk8W0EbeB6T7jsE3mxiYFIgkyWkDM/PDwa8ukkIgSkglB8wOdW34MrQYlDGe1/FwEQSVeUX5sNLLG1g4WzAxixsDEiTRSdWMsxb/WLFu3VKGy9YXFeVjFuyBC6NQIalhIrK9PQEwMMiAxBJPJFyozGwaPadsrKfOjKaXKzpi4IsVkgjkXbndamjLB9ZziWDRKeSIBQu9kIpM8K8/d3vWl3jBdY6aggvoEmzygmRSEHylg4PsowMKlKqVsg03cIYN0CcryCKTiFJLVd9GGloQeB9EgjNNeh5PjhC5fxTGAniauy2HXBauzcznNmL28LlFZO35EzmW/wrxJFPXevgIuVn/X8R59VL4/i2/Bnx1Rlk384/3I3+Y8QPdUTpP36ijbvepjWYr2rFasb/YEoey1P35uJMU/kJhU2++5JPivlpz1pw7P+STN9ZtLJFFyaDg6mtpotbZyLlr3JHYCHKThDYXAEQWHwldzGv3E5FY4/0CY+LhDlKPukOVBrXGS/X9c94i7ZVFAEY5gEAjJvRUE644iGZKzm6admqXnCsYIZX6+6xMUBYiTj5ZKxfxqSGnX/JUrXZdLS7sve7fu+RIcm9tZX8/tYLOSOuR6rqRO8PmPqD/p5fNvvlATIw6AhphNT8Ycagxs9kP7BsSiIlCkoHSeHgxBlrvGoaW+YWwYkZ6ONW8Jamr3sBongFpIdKlvgiCWqRMticcmUrHBgYTIZHxUAJEbHuwTHxkdQHbdzPyBV0dJkjRkUALhiX8gd5KpkaymXHDxG+HPkriScep4ObqCv5e/D9wNiZB7hnNBBA3BQiKxVA66fvqmmaALRwjdazR5Fa1/ILsmXYCGyVRwUA+hqDmRg+lzfQ8bkvLFxuTk0u95Wz56bPdef4PYEArdxjvlcDGPX1MgxYWT4hERpNhwOBkHQ5DjATlV00TWJ1DbgBZSOpBNxxcyudu+Wh0gCxP84PIIAjst1MO+ZNudWBrG3TPKi1gvLo7P9kHSg5F8XtLW+zsORXJQgX5sVFx0CsLdTqx7B4M9RMShSZ1ScBgSmRkMSwtn6YQnYFFptPMte/b4bWLJGISk8pKU1Oo8KaGMFtOfMVXztK0GQcGFIilxCAQpDokkYcCWLUOluKzQ8ziw+c3H6Kzx/raCkC53/SMOc8DH+Ic3BjT7o/LqOPRavj742QIc8LHByWlNc5vrJ3/+IWywKyWIqeuFJFr53gvjQ2zTsOYIa7G+8ejoQLLLd+aAjMAJbRFRMi84NSgonabZQaZFwuCcHD+g6xbnIaGWgdcfHojVckHAPTy7cRc3GN6qgGUFmWApwg9ZxSLmlpNN6p6ce5A8XoU0SIF7EwkFgRPzVmRRKflZeHx+JoWqkAElVxaZneuXnfuQWoY8WaeinDj4xn1RRrg2sMTNjMz60h9UF9o6BPa+Xnxye3T0yeJiYYV1FmoZGLzJqnAMVP8sQN5QaIptMNLVGx+M2zMRqLTMV1Nk16sGTl+q00vTGEIzQ/ECPsGX3u82E5plXSQnRdEKTsbKSfdY6WR/+oD7TIjKukhBiiQVjsQW513AsCp1SM06pEpKanoxAeGDD8HtGQ/Iss4rpaluVvaBM5tKVzkjpzmrpaWc1ZGRVU5pyRrn9AjPOH26dSvGZt7At7Tgb2Zk4m+0tAqRmXET39pKPY2N1hb+iVt0LKUxOXZMVUQ43USm5zRnH+Ck7ISgeLiI4JTwkIpkqnsdFxtDTWPH8XP7q111s7y1iOa8Qv/w6FtOvpzJqJc9p8eB34ZNiIOGxcVgegBI+gX+Dr6sA8fAt2WGiR+dMV4ugyAz1nLz23tebxY3mGHAroQptCoiQoWOjFB4jEYrI3AkOkJlQ6XDyFQYEaWGWoIKblzZrd29YX4y/yAGH9x9o35jzC24rr6ub3kV2V42wkvxmEAwLhEJxsbSxeIL6eljIkn6+AWB6EiELBwpR6OQmTJEBIq28EwUOlweu0RNxMbOX4qNuTszMxUTi+YrV7wSv6Nc2UextMkHHZDuT70FWFndL/VNV77W9VcIGT3rO0uVdacGP0355y/2EPvkjc2LgZ/0515jHEpH3N7D7r7Cvzi/5lAgzGGPshHpphoSUXzUm8Eqkah87J5C4lJosUNr2415x39KeQJRuMt3sHbKd2MvLj4e3VmvMcEfSVZUNIiETUjcx/nQ/RzS/aE3z27hbuXB9/W1c++uO3T2mVtmHq9IJnWIuC8HGyJXNSVKanlEZBFhsEx58aeUT3+x69i1N/iY4MhI9Ijq0dthST1s2ACX0WRZQ/2HXk+yWHZJrMmZuBN72SconeCzIE2w/+D+vYmCmiKguPfgQdrIKM28aL6OA3b2g47Og/Z2A06ujn0O9v3fGfrtHfrAgYV9RdFeqV7RbCk7cEPKIXBBNYSTbqAygVn3Qy3iQ/GToVVrAd/5mfoZ+EHUqG40bCxvBLHKYTnvN/IURdoUNG0sZfR4/h2qTCDmYJxZ9PFwPyXuqExu1AkYU/Su5NQ7xijLO2KENsz3cs83a1ov/bUGSKg4KDyPyw3PFQeGQquBzG0NZF7MC03zQ+PoDDTeL4AGg85UlCBEADi8wBCoJDAij8ONyPXtoXz/yOFAJN7fH+WCwgHzhQzH6w4OPzo6/+iW/Xq2c7+d04Cz8w0nu+vgh5Jsd2iIiysU5u4Oh7m6wEMAtzNJiEgnxtLBJkRSTAgvUzoRmCDCaeggn2AfKvqfbz7A9NbGl+ztTfb1fRlkH1KoPi3ysXn7iGwsrB+rbKw/9QH+hr4O4VD5wfuKVpZ/3X1QEg53/uxFCnO1PBS2B2qWPrLLtApsO79dUiQBSZx9MLSNbCPS1sKim2K5ETlJNAob9iPtUPkHRoX6ERHFOyv3xs0DXJDqioAFjrN730fhl3+AfFKEJanJSjiAelKAvCB5FiGBVyAYN5BjwcnzMvgA8liyjNwC30r+05gnd6sUy+6vCKiDk/zAMygEvbVsQoQaJLZLQk/DqZNCln0p3YJNS+kWnL9yYGLgs+AhC/BWGQzO6/Q1L3ouncWABS/I2UVw74vKPEQYXyZuCWWW0i3wamLgPHgo7r34uBCQrQJsBNFkbHCyLvD8DsHFhYxpgL1l4pZQVyndgpErB/S3yB6RPOX5Lmnu7yY6yo14Y7ujsAayAPGL//t/0DcqeCKfOLYqtBlEcc+jZfABPi3rsqcVnBMkrAhulGMkKcSNKtGAxVAm9gr3B2CjHH7OhhiuqDgFA/ysuJ7DUmjhSVGTYHu/YAAK0+eE9yk4RdSJ7EF6Z7G+gIPpzj4cCofDkXA0HAvHw4lwsjj1CBme6d5vA2+rw2v7XnbhaP+5W8eiPRCBFi7Q4dHNZsnljQBHGMZbl9iRVapYoB5RzlHlwqyq4ajhkrTUfCYRDikL11pt/JYPL5MArlKibciq3zde9ef2vr+P3hrJnlbuEg5SpWLihM8FZ+Vvuvz8v4+LzwNc68eBgr5dhTjgsbFRS+QE6Yrq56o7KAn7Cvsm2XLpGYGeWStdYBc6YSU4H6g3Plx52Ve+2v+f3JUvTtbMfxGAlbBICLJTwZ4C5Tnx78OV74K8ecRMo/ZCZiHAf4AXBiZSsOF+OVe9EqaEKWGqdmrNWqUktgHyDrJsChkYL2vpfjY41GVYPUFCkBAkBAlBAs/Pg1cvDBkEHsXg2iALR2GmD9xh/tuvA/ecKZTqZVh4Jeiuyde8TGA4jV0ja4AYxivISkB9kZe9iGXZKe+ijf6nmt5vmHZd8bCXpc6QtKLJfTZUjFcNaf/2fwtaMP/dbxrELlCvNGw4DDulM7LXjLk21GMTj7GJCphYBkqW24aMuwd9an4U0A6BwamjA3cCTaqOAa+Jwa/epjoU2hlZOF/6hfA9zCN382IF9oq5K9/B1zFN1crOEurGRkIOGquicxOD9HTfwFl3O9tuzxz7U8zXyv8X4CzsCWf9/9S8+bOc4Z/lFEkfxgZZKZAxwOj0+7jwsNGfawz71vnRbE+6Z/MWKPdb+La87m8q0fVb7c43FLBxyzmPkUd3g423waewE15lmtAT+5nsDB/1iKf9/+wMNIqcz/Nuq3Pjyq26x+t+57CPsOmKSMUjFdkVtapPVNcqTlTMKdnKlsq2Sk5ldmU/fr6SIBJVm6pKiG+JLiJaueiyuwyuRa4HXKmuavJb8jw+jn+JT6s81S3VG6tPqy+ozqqLp+JPjWhETekutfas5g3NB5rPNYc0JzWKFlJqykyNoSZQM6m/U61UJJVA0agUSkJlU8XUi9Rb1MfUN5SbOkad1b6p3a/9QjdaM6s7r3tLN66b0E3ppnXzVahep7fVNtVOrjXUzqldXEurldb26k/o6WpX3bS6v9U9UEeuS6+T1Z019NfQtIP201E6Qxfqq/Xr6vfSVfQ5erVWMC49vfb01tMv1r5Vp69rMvlM/aZLzzxhOvhXndMzHauJmqX5Wqwb9aT+SXdMv9k3eWbcXA2YYDMQBUXByeB5MI12XEEiavE2foCf4iQexWlUcJEW9chQQi/oE32nbcql83SRJvBhR3jsIXu5cXljvXF748WN1zR+y95mH7JfG+lkL3H/j30ycmFUzSi56dam/znujto/2jA62rzPOeV8OPqz0a0xbS7JVW6ptmxs2dVyScvVLXe2fNfV6epzXR/zEeHkRj3bPJd6bvTs47q5/xHjRIt4aWzZ2FNG1IOABHYvwO9fqPCfuhR3NQl01YDbn7m7p2H80il/pwjgz7MBaxzocWg2/e3vkM/esvxEUkG4YraYosmIpIa/+tb+QUCl9IwEzegt1u65z52jcG81CSECJDI15kYRvpK5tWXKCOV6ANJlADBUhs2BBd2XjFACBPkNB47n2Pgw7H61JvNoZe8egfDqVmnGnwAIhte5UcRVj5Z3Y0hAWS7gOG0EOTg625TlFENxCayVlQ8Ba+UrKkQZo8maLQWMdKOXiaWBvVLg9qGA2YP+XU/cLYpP2AmCxOmODBkyrZ21rS3YDClgHrbQtKNi2DJUmCeRKxOMjE3wdqeAgG/7Z3tTPQdBHmObzbbFy7WRGFCUBQPf4BJFkkqFP7E4y0vOwEXBl/SoUGKjCznA6itbti2CpH18bMrHKxx7EO8b3n7t3NjsteXNvaMcOm5uPLK8XUTOQaLhCJ0zsrG6DIJ0th/LefrANzzJz7yKYvB5y7nMNAUUQNP8Xg9hrWXmzQsilAU/TX67vWJ89nmE56+WIJ4d620qPeu+10K+gJGybR11SMrZIQhJJSlP7V/5Ab/1U/5lycvCbqUvGTGgjEWbbGhc6LcYpFsoKc2fbTbfY/saxb4debFWhgXziT1FhDGrbNO7cljuQgBQ9FlXPZ8BoXc+u6lvQvTgjfLkiCXJxoznEOBpOgKw+Xg7x+EKTke0mkpqjvMj24eJKHkt53SR0nqb4jl+MMVihPqB0gLraE7FNCeIzUFmeH4KsXHyRCzJGoS/aVdm+8V62oxHBHoN/RQqFszu0CprYnSWWLfDzG+KdWlsa42GZUlrgWRkfvPTN0e9f5gDk2cdfFQGPGANieAPn4P0A78wwZYSDNL143pH4WDOfJwo0np02ZGW+AlcImJBshSmiXCJNPurhN9M8ep/4F6y27b5Ki8nxo4OuuQqmN5UqdRfZrfaKfmEVhga6FoYFUzHdApJy7WJySSJGy8l83Xhz3OehdQUMeAXxJAgzpAo+ANiEXKyBtI770F7QSDCzmjC2rjuOA88HYoA5Az8OXYUpPhBuZw253qpZe+lb92yETl/zgnMb40aE++LIH9kP+HwgLfXTmlleFAMP9LgLg0ADFGIDlFVmpuiU1VcZ+a5BX7m1DD5aDCxzXKt6OUGhkyOn6MM5/lwlheW+kSRN9JYPyNoW9c2ljfnnEomw+rLjqOvj8XYpTs5LQyzHCdiKQlAjMG1/tSE4xEojHQU3CI0SRM41BqqmnrgnMW9BQ7WXoUvOrixc7N2cg07DeYf5hJml9o2J5Wic3oyaUjykWXH5J2WzT69RNQFUlg9K0bezMW3mHIg2qcmPDV52ERoLyLEmINabzCO3wGdCOGpg7bKOfwir7KpeCaxxDJiqo8/TXmSyeROzA7beNlch/nNgNDZDuUxDclDlQYVJwBWS71U8k19c54c1S0APAcg1PqqetU0A+t40SCsy3Hs4IUbba3m0hJZ3PHeILGVq47vaAQKmmcwTMo4UNbS1keFvn3U+9IntyW67LyNwtpIecXaeKhdh1B+ztyp0tbQrkqKmozI4/zy/Sp7l5+SesCfTKrwG02THTPKYMHVXPZksKNpEWpjlI0WQIY5We37ku/7NkQlbDHMA9kwhioddCxtZtqEh42UokTaC4yaz0LtjgwRIqOKfdXZDwJHitDwBnnnWkOjp8HThN1jQ/yiXC5mL0ogg7R/2mIJO6bTYMhXNGOdb57yfhhaVNU27q+f/CX5jPISjeFc9j8P3WJKGcEussKV9mkAKRvXUQuwbKHDXRrlkaAPdWRmywRICxx33FMBKpJOu928DdMbJCxGYE6yQLvctImEWBqOKyjGgulm9nbAJiltNoMX37FPThSsgkEDG5/qvudLCXI//x2XxYI+XyTSK2E62BTBhgb1tIzqtek8zJ9sebcgIFkXImVJAcCzSDB1bbgK4f3FJgFkesDkBglPcW9Cvmkftsi0V1FOs/q0AC0tKkIgMKU5hgA1JFBguVlV91pr6v8ORTROOeUn2UBsQcg4GjcChZUKw1UzbgZt0GUywtf2y7U8XQBIl6GcwOZiUOCAVgFJee8IXXoulYrcLjf1TpZ1dfFEpl8QQYF6fyansvNxfiplUhiu3M/tnarT3yttVhUOIYK8J0Lpd9NhAqqjFOeZeJdr3W5tNQS5sIkVcderlEjuLWqQmwPMxYbLSPJy1Xf/AJ/YL44CLi+DTkBzehoA7BMSzcVsBH4kPuyGALI3Ua+tbUzV45jDvWwTQWEVe1qjmqxiKFyqNxnJ9Alhr8F5aNb2YR8F4aUcver5IxR1cLDaMooeTISi+LLbMtZnn9WI0qEMrZ+vuOpgzrnvtZoLKhY3Zh2dKvHDNYvSEAse+1w/VPHsrnNBz+8SmUVz1AvydJqSeemls+xLak5lrUzMuYgFDYka9jSCHDkzKrK4C2RcN6cy+n2nXlzrP5rRqW8daZNwwrpptNIR1vUy/I/SHqAgIceuShWc6jPLaEKz1UuKfU8hFbrUBg1fmxet+0SG5K8ZVjnQUfFapu8WGaifWShLuaDBieEkbqSc0OBLB7VpXznD1hHdl032r7BMOvPc0Qy6LzY8W91NNb/TsZ2XySJSwPEBbLrrq8RXK7dEO/CVWu6V84SWSjoEt5uziD7CUuhzkDydXqq2kpYqw4DDRlSbGekCAtTAh2EAc3Gu1hmEGAyKePqZMUfpAewwvk0s8PmFxCRkc4MAKrfJSVIQkC+yUWCKuCiytJSjgtMvIr2PCDqlSUaOo/4yABPXRnmWRu51k/xOEIpXubIZo4xLGly7RLzz2ZsvLxZlhaEp83lN+9wiVU0CC0LQQj+xU/qpy5X61XwKzY0WSrxAWqEYpweoWerqFVCw4SBYdRRALqgA06559yATtD0jM2MTMMfCmk3t95WIZb3R8SRP1Naw0jdHI1lIrl2iz0V96CecaY2FuV2l5VXUWpvBbi2Pfa1Rh8dT9BzvBQps0KcouRnfrYKVXNqzK90Cd1f+4Dj4VInKXp4WlXsEvod8a3LSdKLIZc3lTXu5ls0fXaMlqFmSmYB3bPLbO8PKHS6fK5aAJCF7oiqlu+qlQqgycBJ4nCdFQtaadSzc00X2SgW35WGVAXpWTA/t+zkLalsrwuPV3D2R1PDslUi8Ty2Wg9lL5Ri42wyC5SB0M0UHnAVVFnSc/ODG2/bBP5fj5xGGAYZudsQyqXOK76YPJY9N72X14YCID+Tc4O6Rc1/eWUf1KooM8k1FkeRH11LO+ilhodgEopXVbTYCjKXKSANbXL/LYBYvh41BUaBNuy5GYnxUC+K8TbFdNr7KE7qScOVJABruCgQKfEG6VtKI7DMMiyoaizOmP8yGnV8KBLks/cE//cwfvNIhF0OA4PrMIOgcsFjgKFmDH1f67I+2D8Qv8bkIhWFYCkFJDO+CVQVWm8HbMpIkPgiBGrFGEc3S3zSkgFRKUswK2x3OdykVZup2eo+5yAGhs/6unIhyD9KHdaPtN4o4RnnWUfeEAZBAPjWWoVQRLnLoURrodMoOPqgD0KYhC8FzxW5zgezSkMtCDXvg4swU7vpRJ5nbxT64cTLig1wsUl11hQ+x6g0249cWB177iTWGESaQ5xegnDOdEwCKTbOJQqD51PrO1rbiZ0uhAkff/qcuZn+JnbyM5lR96/hDEEJ3NoiNAtHVg4Y7oZhR4YIVssxaZa8nR/ti4U16wWVklxK5IGUvAIuac7RVMJnpCoCgpLZVn2ZPJ5rNrljSSdY4JMw6KHS/WxKw3zH8ivErfvDHC7fjOePHZ/9VQGnjZl7Z4EcWfBQxrRXY3Dj2wB3lQvwFmEt2/odAvafy49W1UaqQ6je2FFj9vs06bDA6SNvdZRzZn7ajlMI83gmAEQsBIjAMqM9ih3YGijIxhtgwAN8K+CnWkudvRmv1tApc1uBS6+X4m3875iFn56jhfEPXNOJgEliblLSoH4oqrdCJcXVOWwrCrOYyOjLJke0K5B75WL7PbzRFavNbTnZH9c6zRtwQBGKlVS4+LngMBYHPSWkHl3WJ9gyBqtSvS0M4Yofr3YbU0lYelbPZWAHy1lIuVTKGtZv/2bXQ6EpaAzygRI78qvteLgYX/waGaDhCHcrnS9bvOL7tfJSR4OKfDK3EPDtPd18Rm0ZJYV+qwknhGO8SbAgqdLUApMlAiAJPehHRJBO1RRiUzmw/AJ7eViiqILaBeh6WP3MiAjUr7f1nQs072n50b1eOZHLKPMFOkUWY7uKIYnqhps+Q0fXtNZpHZG7CgknyzSe5iq4bmWEpqBUJfcsGdZ03iFiSrkfakBAtkWtSqELajg4R7AJWFGcNl6liwRNjsw+bSC9lA+JkT4b1uFpjT8RBjYFFRiwliDCNYcdoHnAvHAhgV9tSBnvVgOhUmZG1XMHpIDryPLfzG2vAVgY+5yZkjowA9Cjdj7nvJQlOwQOWvtTc4m10L/RlbCPZDXce2eDA4BQQguY2nhMK//JL8RDwTFOzH/8gpnGVpnWTg32aiJQ8ziyTsZwPpqlwap9riRXAkOo8HGJ0mwkXKfIM0zc6WQs5E1+K5qMSUFJI3rfEND6Lj6XzJePMkFxudZjTRmk5JF8vrtjPmfEZPkpFILjJHZMuzvh8rSAxpyWxzsUCgqdbh/03hOt/0AURDe+vZ+9l30k0rzbuAy7y05nkDE6J1begZd3LqTgXlKm0Ph+BIkaC4pi+VZbLrNQr2FVoWzbp51qUZCROyYoAv+oukKrDC+gjdsUBzFTmulxVa0eAHLP5Kj5BHej0wcfNhEkT4bIWVvya5OGmXiUhzSfQwbZZ1+w7+Xd6i2YCSTWyBLbXxw+et5FLduG0cQHc9y3exgXcPIMg37r0PcvBGryKzwZUipGdbNoI4C5Dr4BYRNrSBFNNkSRHAtiODykF6UY44RnIEMmid9jsLZea7OuaVUDYbxc+gdltknuWIntwY9WM9DOzNKVuntdvKxH1s1ZqJB0dtoYGVoQis3ckW99bdhtdxwXXcxDWbRpDJkcNKTEz36diuiHjTjZ/cVC/zGU81R+DrYCcvflf5nwI3A72/ffWoZC2Bn3Zso4eBX2UYiVFaE9ZTSjuWmLuy49fWj26uHW0HkAdzhjarM30gTi/TAJNqhGC3elPXmvI50wKIJpjLoE4RLGcuBA4CrgYEiOZnILjq1+izVP7AxBnIuwe8KF8rBN3N3fw4IO+r/5ZzExLidDIADo1rkChiumKW3Q0Bq9RMiBner1l0x51sX7BVEx3MFLuxicbP9L44VuW8Zw5a2J07sFwicbSCX8K0gQ70YtOaLcFjlESTloZVnVCcKjzCjhX9NAWFq8jhvzVThBR8ut5n6f25qCqCn1iCYYU4nLt4oSHQtJFWcOxXtxMMCqsWxcbjvfkz3HaVnasRkWxtvV08iqfjMXSS0ybODIYYxYAXR+1INixIqvBdBxpShlTtl7bYU46PaZY5Y7Ww3bnss28lV976ItHD9tme/kpnz8eo+DgEtEL9U69C96WigclLHHcyzIVQsSe+nKWtB1x0w5Lkci5wCKTDOxsmzwJQ82w1XFPSrctAyHwF587ZldV6aQrzjllt9VhIntY8T/chBOTsZkRMLHSjij2Eg8HH7hCg//pduaV3Jxrab/38K+e1MNx+8Cm/3+5cHLQpK4mTfsgOfPg6du/qndKsz9yd5MIpvmHF3euOW4t3pq/v/qlAJvut6+njDR4jf03Xn8vfDbEMe46eeNLjZ2HqSLS2JPuezDe6T3HzpoYvR7spuvUPCjGC/9H+hQ1j5Xpc9nGE+V5K5dQ7IRAY1Wtvnkwc3P11GNgyKMR2bXRl6m/WkfyAXQbhb8GZY+FFVknxtxr0yUR2WubHzXwk5X5REZzSVOJ/iljgslYAn05cb6GfvH3gcXScuqWSLvbEu1pFwswVcOshfHMe1fMuOvnv9OvoiJdNIlKU/9TwR7AJu1uzPMm7emn62Qk+5u2NKKxivc0HGQDBmF+9ATDg9ffm8IBtyQcA1vBk7Le0lS8LGsYWYeRajTDC6ZbSnkI141y0EDUFHBOdoIZ/NwNHjeUUa0SxNVdGSubC4MD6RJkg5rQmrDuMDjsKAgoxd2GfyEnuEmX1XikN1XSIEl+QR3BKkG2dTnJ5+9vxhP8aQMD1ztYApePcwbQrhM/p+ElS8356262qy3PdZrN6YGK7HWZKB/ag07mV2ckiDFPrwp8HhU4wHNHvwiedflJWBhzPa2wUXZ8KlnLydfGN7jpeaFIbdCtdAxGRIMp4ht4r5+A69bRpgajXBRmGby3zHyq6pS5NPDLxKsV9e3f942/GlNl98okNNBVLHSdhofa9oq9lnIz6fqmWNkg0zOFMO8Y6+8tKuyO6qKkUJ8zYTCa4uCD5w/TGOQtdRcgDKVjX16GInFi9T2R2ZVztiLSSLeJAmzN4A9XGOZTCnggKoo9JDrVN+n2Gfha0CCP/R0hp3P3FcB5KTUXaoYbUKb+bCmy5eRWGbEscOosZQUxOilTeCsYlJH4+YOcYWA5nNojT3AGz2C3UwhFgl237k3aTaTG2WDR4CAls6MT2jrwLFkypYThUc7g02eOZDwWdycSaER0bAR7AG88WPIHOjyG/lwkPZWLkb0ibExARqpGodY/v/YCtL4dK8QU/0Vr/rt6IkM2VmKHUJINuza+cnzzZ5GTt6wyQBBsavJH165Nuy7zp9cNRdHLc5tC+M7TJzcjPTNXj5+j6gQxhlTHNCMOS17BRl97GhScNe09zlHRGW3jAz45IqBwlYt0wrQNh6HIrgI7N9jsw/8ilyEByAlE9iXYYXs8jq28UOoS40Ag4TqLjnFQ1EqQpXvWhp4q6tNOGNzH/46QYG50NnAk0SZdE8GUHWX9E1R5VsdG+n3FOcukpaO693RnPr4hteb5++zgQ58RmDwbcq9tP/AUpF1nd6AuRMEP9uzLJydA0T5KhnblvZ600IhtvvUeLagt9ZQVLQBIO6Y6GNKoglg7G71igiDOWEdAjh0164OOPwoZwi0lgRnjrFApOcSeVSlQKqltpox1XlUy1HAtZ5NpRTy2uEKII1yei8vXrn1bR164AfaXMzIs1o9jvWnccvjn4OLIWABMyEM6MuqdjzCwKhqyBRBjA+Ou7fXkvOZ4PGxAJFC9gW4SXhZLpPkR5ACk+AxYcUVa5x+CcoBusKGLGEpAThxO9BXb09PvdQt1/gzpYX5rwzyZ9LOX+iqPfTszL8LOsn4MI6OMmvm64TQ0UiD8pnPcBlopP/fe5+vDdP4G6afwUMjMBn5Uz6lWVd2glb2NC1llZpkA1vCGHLXqoFN6ytXlmDIUBA9m1KqPx6BJh/WvDFeHSE7olOONOjOcW3pl+E3KHnDLt9yXo8MJI5Qez/T/T8aTMzZjK16kuy7C/ueHqzGvJ5nMW68ErNnsz7X+p79dOdU4nPiGINYfPW5+hjhobgPDvPa59DEI+3rEdmq/G9mEv8b9nFWDNjvqpGjp32w+VWdZ55ep9CavuGxOtotykjQ/v7ZipWU+rC8bJsytAdHLmk336JykzDQWkEPlPaHZYwHOSUuWO+17OU7orGG2AiEzOKE0M3PBhu2I9nhsWnl7cM/Wah7i2M8RdATKTaNK76XnEbCPgYp+AeUQGEIedQsX1jX+0FMZHUiv4AGPy+Z/og2TRmqicx75b2HbxOWOELH+rQutdGQ2+s3CAO9qwtYad5DyBspW4/i9RzfCkwSQoiWJDeAPH8c9wKHyROoaQ04h5DQ+d4Z3J1D/DG5mPnz5OEnoivrKRrUjw9+776N93LL44AD6heH/gxHvfHSQe+U7PsSicfO9v+rKAHNukxALo5TYqCZXV185VXrH59JePVpRkE24yiWISd2/65+RJs9gYQ+DmE6sINYvvFYS0RdTpTd//TOOFFNwSqAARLXjWaJ7xiZGo/oKh/nRi9L8JpN38/7uZezJJ5lPrEb9W51NzTpXMx54WiOrayRhef2Pgy2dNFS5Hip0PMltNbsp7qAFx+lf/MGkMRUdW1bSfGpS29iEKDplfBoLLrfiHOJ1s6RXwYJLR86PFvu0yZPOmJ5aWyRbB1lI1iDXYqQBdnCj/aRUXol4gtkNf+9eNfvufexYA5J8UpYtkUjCT6zGtR79Equczz9486erisIMV0Uac4Kn7yXC2DhzxLP9eDjtOXMGEdow7PC5gyAuHgV7cyuJ5nZbpJysh2Lj7E5notSdM0sIzoEJVrqbzF38zkMuDIPC+2c0iDrm/8Xz0+efkYeDChHWLELxtIPHPPiy4lGfNPTYFCYzoddkI5YmsaV7aDeejBhK2BCR55IyFrVzJsp0SeCEGRS1x1imE5BMiWHXJCpwLZR6vOYNx771vjwIy3DOiIyfpK/6m1PpwsWmFK2v8gZsxvjmzpK5+L/3ZU5ILVsUaY51pmruKyvoKTeanGP5ckjqsrZjQiSllrEv+yWl/VYjlNLzXn7jApWwJeKchbAtiTJOVZokma6tWu6d4Vm8kczQe5dz3NgOCf/PLpekD/TL4jpa1Dubc2uAAx5DCv0N81Q+kI4MnDFG8uE4jrEbdfUVFemCHv3CRSLPdmymXijA3VMuV+Gf4P1l/psEomiED6+9Ew/B+0JddiVqQaPe9i3IB3vzeGk4GCo77Aflsnsd4VxP1BcGdwTBhuQHPfykaCSecnOWYUo0cmbci4osxdDFraUXi6bAwGohdWLOz66uY7XXCrG1t5JKqggBa0Fj7ZEgORaLzc9yi45L5ViQe7uTIVF7bJBTJfAcGm5wjXI8+ydeZ6N7gmuY0hqY19ucfjNx4S4z757nGRQL9GOsW1qOObd1mqD8FaB1VJK7O8skSI8pfr8fT2O1O7BC6neOzsR5o2BMHlhHGIUvSNCZkxAqnSNruAMO7FYiq6DG5oQkUsLHurtyIsnR0BV9vvoyZUx3MRRIRHzGpOBpS7Hu7rm5IJ4NkQxHr12Gf9JSsqwLT2IOJrw4XrYnkizoA9s8otVGYiD9RhVKxctYw7RL8YjDDNvdCq3PXUcmpw8qIHdmnkVbOA37NOzZcFDD6Q0bNQxpOKXhWGEgGLpUB5/sqAz70GzDuRkG2o5GTl3Q447J8O9AhHpnL632aksF9WigFwoVNUstr3BQPLewYT5hwCoQuo8KBbrC4jJlM/XEv8ChQGp5yojkVK8rvTb0dOsl+61rMXM9fT0dHfdpQBhYd2ZdqkoiPN4X51mrd2Tt+NgrGy5ySTrpdhLCJVf5LieWRss5a1n1dHijvft5QxjnW7V0DTGgT06Yebti2bKc2D2itBgaFyxoB9YGeBWhcbUkVqS+5AU9GOQLw1IuHfCS1Q/RuA7iwvSyQslKMugSzRwcpydNMN8+PPTJeYi9aDRabFNrzRWDOs6eTJdZwcrgZgO/EUOd6/u3ZYjOW+7VNmGfibQC4E94YRbykOKQY/8Uxyw7xlrziaNL6gPRTS1jzlu94VQxlVURKR/ESTHx7cZeVMptzhzkM/ZyuBf6Ij5iCKK1uh+KkBakCBchVgad4ifvVBDlznlaTVnROQWgSlr9uw8lpBhWv0OpXdRYlcMmNRXg/KmofBoF3m8ePdDDP7MgvYWbizByy4+Ahx9l+xVhdakFauFF1xXHA2EJpOtWFsOFLz4oDs8AOzmqdfVlTfoT7M7biAlsDzonzq3o7X/p+Q9MlPFqi/sJ/SgbBRtIjhS+9JA4IlAsA8W2U1qaso6crqpqIfZzMLZQAFPu+VN8YwFgzfOFpYR8a6qOpRyi3w0Bcrzn09Bi7tmW/t8X/8yiyBN10uTgfY/maS0ruIYRHiWCBhWV3cbF+FyM61h8Svns1Pn26LnJY/Oa2LSRve/92n+U8whRYxliQmlFe88fv/tlNJrViHTg1KlPxPmfmxidxjEUb8cDgRPZA/rqGpdOSxFWJ31qCxtbq/pPvrYGLK2UzniOCaZSWQ8/ckMj7SbCNWRU1x7pKUcJg6LyQERwmPVB5iYhkhwYniKVsWQ04rXx7Xx0VBceXhseze+6W+a9q/fobt5ckC+gXr0NfS/ipvZIunx1TnhX5UDEZ8LLoVF53ooIDpMOukkmpxxqdposXdFMFlLArsmGZxkwdcTX7e9XSKO9ca8zeCvvvvX6nccqti+/9JIvsBpiwLdOPI9Liq4513xmaysIbZ4+4jhvpbweEXeaO0k5QFCNayA8mGF4WeSFF7RxARdyWbIBd7k8XctA7kbpbhE4PGfisGkVVa1Mj2nzMD/ZRCiUtaeB7ePr1YZPb0CWioYSdXpoGEVQGNq871WoamWfy7XyQIKLi61uJSFyLKCoePxrNkR4ecuamN9TAFH+Fd5FPd17ER7LKIQ3W2xzYo0i2p0D5yccSa7nxq86vtGFNs9vBl+rWJJOft1Rc4F5yT9inBGxHNC6vIRLCw5/SidOxMH+EetKBJunPI1mUGxRenpGOa2VTtjOgR+FLEaxQ0A+S2Ezgt0hDCXRbtjoFiUSx30r+fPjDXmgDq+JimcSH4tcWh2jVQTVw5jZiwTgve2bb1piw0fr6YQDKqvyPLkuvaH/bZXgrufsY9XazVuYMiSntHVCEZoeW0uiWXUPBt82i4XV3V375VPoVrAeDb5ZsScnBGFCCqj+6sDEc6jEglsmtlBD3u/CXzW48wzbmQTjxOiWdJ0A+kBkHCswGblXRey7XhUXP7P8tgByGUkM0Zcr8tRbW098HlBzWPPgZuIdd3YIW7BL0Q6zH0pVywAqrzCFYi5Wf4JKZlC9aQ/yBerjj/STM5yTsckIz1/ZSO3Fmh1ueF1m7QlhdPF7Ktud09towejqeo0+57aS1xEsS8gXn8pgCSI/1E4/+ui7H7/1omm2qJHemxEbm/GPdkvTGf/yl4PbTp7+elVV//RaS13HN1YYzmpQBsK0xPI/9Fxd3nCoQ4BQO/4KmS+r5jO7p5YKELulf9u78p7eNaxwcK9c3LDVoHQbRrbBnRcYfA5dH4R+lw/+R6mYWCwF3nw/q9bvSmtRsdeP7b7Qtx6bfrHy+Pywle6YqSYhwhZCrjqz6rFDB5oXbsfqtQFzTmZkm3KsmZq5yUHg24Ao1ml5OJMqf7j6MwSRE5afz0q8/jZVNbr2yYn/4JjpiyDlpkjpyxDvkVU+W0lkzgqlyUw9jqPCA2WELPvRIshZAWC/SgT93qmpz9rRrVfqGWWht6251M1uK7z3D53aStlD+CRa4X8usM2Pft8loY76joqiSHLbXEc7d0JcPKecbo7pXDY/rjv0W185KpysCEBFgnqqcwG3Owf+8NYmDvx/5rgR2Q5AdIykk4PPP45vAvOKRIOTM8eJgTZANHSTxh4sWtvH5gAdP5TTHXZgKEvuUA/bySg1o5PFoZhKtrOGAOMf3LgeWF/g/Yw4w0fEafyKKMWfFfY4kY9oQgXxc6rA+7N1edHf2GSefXSLBAocq5+DVRKxaTvSeOgebNMssRW2fWWjLBsFdriIX4YOxjwF0bMrQdeN98fmnAxG0hkg5tWqJaWRnc8nBbifo7il2tAjUTuxr7AQnMdM31BdYxWXFSarSGmgt74S128vbdMzWfJ2uGtVsAVBhIQD8p7AQoWK2i0BlmHLhBSNSsMzM7Wv7k7n0k1nF00mgm53MMNgWUkwKGsna9FFbaB/0C7mXQKI1aO6xMUHV2haSHkl0sjt0BqGPoXGr1906NTRscIxxA6jVjcGg5JuAjf+GgaT+pD8TWeaxVM9586dDQ8eQda6kVd8jNVq0gx1Mu+0jjZGD07+QZjFn8qB9tnFzvyZ480HnIoSSUQE9FwuKxLw8B8rkMHNf36YtWQEfRv0RBcBfvSN9jnAL+5ofvSt0LEQK4crQI8ABPjf2Moa3vb1jFt0GhGn1gigvncL+43iR9LbqFhJ10lvhM71P4I9TsM5g/8YwN+O/Glj6LnHOb3GYPdi99VbYJ/CvHz0O8bpTmR3Z+zEmtIqbvReYtmoecNn1COqHoF6z183+7m1vzC8U6yfT6QeA6TOd3D56d4v5TzuzSV/nwLd9B12FpeTuwfNh3G01V8/a2yhd45KH71n+xQHkZPHJrBvAscPCNQbs++5CUbPOyRdDP/ZJFLPf3EdelC0aFTbweTaAuVKSN0a2AMJfY3JgXK7Dx/877p/fruxdxPGLWPgtuFeGvnLZ/CqsM9JdPs//FvO0P3E6HlLxb6Y2eUMLDOWE8uAxWJRWOziEaMi4/Z2I1+170ZhYN4Zns9BYpft2Ab/e3hg2C/W3HvBMbw/YC1keydSW9FM7369Acrdx28X1Cz0+xr3+HP1MOh8nKS319rn39xKJvx2NKpfux6JcaZTVUT6DbaRuTNLeTZynzmP4JmrGo13Gs6lmbGZMGnH2U7P9ULs9k7katU3rFRXOG3P5p46AvQjWxW2E50umPc5k0djxkfw0isTH7OhK7SOTe9hMRSVHjkHuLANwDgWNBB8aV8jf4xhcKj3jYSAbgK5ciDs5RJt8f+PBJo4P2O82RSVIcv0BpF7DLXN28eYYlfr1QPqDg8s0b4TVoF9dS+xuGoQ65YcHHS5CLhcRf0ZtK4Op2fi3iQ/v8QI+xUmQnfIhfZaY18ZbB9j66z+jLGHclL9ADc645O+uhnwGr1sk7Em7O1LADywwH03fyC6pDdOsAc21TtB3hZ/hyCIU9mI0xjz3KMXKrnzYtdcw/IxbypEHRJCX0fIomQdpdOuY8T4so6l9zeV6wnfWS9TaEMBAowa4nxUtxkZClXrWFDoruOAl1knAhWrys8IwkLCQABuK8DHxK+err60l4tFLT111Zn7VxfgZUWiBZtucxW1UQRWtdtLbdPmuzg4dd3pWj+oC72E8zoWnbkE1T6hbmnXaa/XV1E7RhpXqa2hBkzRkb1MSqmxceh27HbW2nQWzg/w00rN6+5ypB4DLmLXVcBVpYsOnUpctuq0xrfq7kFr61RGNSF3nVbwYROv5s6tISCoF1QC8NjYLSnLa13IbJYXvbpZHOBmYxFERd1yM4GYH68VypDDuFiLuzErbffkMEBw7WBkY08HCfeWcZndYBbE0XJvs1auP+04FSCCIAVKTsmNQ7vXFoyaaqmtjrrqyWe8sqVWWmujrfBNuqMYzplCRGERUTFxiVdy5JS0HpKMXn3COGYgPLMmGNhuBzEJmQ+08jmGCdP28EoJyfapYRy3Tnpb54wBXK//9+zvFJsLzrriouuuusmjsz4GOuGkcy655pYwKb7Dq4u+BjE77fxnwyf4L6C7HhLxVDhSKqQqtqusoJrqihiZ+PTUz3p2G1hMZPXT3zlixCcfCHUSee9RjYrTGZ8d9tGkiy5p016ENkzLdlzPZ2RiZsGwsrFjOTi5cNw8vHg+fl2ETPvNq7cKCArlpG5d5pVZcMU55w0YNGtOObVKFaoUW1NipsQ0V1hEVExcQne+mGrQU0kpaT0kGb36ZPUbkJM3aEiBSCJTBPPckhdtUfleZLrm7NlT7KdWo952W/VGMR9zdlWcbMW8d31FtC/d2x3YX5IjW1de4G6G67W857ozdRPVQRQ5iNiJDCd0Orvc3XY8aQ+zoFRbRBrWnKOY2Ckhj6Kjb+qBgKGUNDHKBKRdQQlJ6kvSalCCCBEVgHoAQJAkICEAgEwIRAJAQEJPslAa6V7vzwWCNphkItnruPMUmtWIuyc9STwPrMMOO7TdfKvi1Oww7D+zFLOiY/p+xWoXT5vjIiqNeKJR1an1UriBJ3lqMgASa6QBkV1JKjiJy0HdsSvQj5M4xhxmtbOOk01lXxOfsP+xAY7gKXN20W9Q6O7rHrxXbbesTj5br2Zi1gXj0ltOptxfKVa7TiWXXdezb6r3Yf9pozw=) format('woff2');unicode-range:U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;} + +/* GrowthTrack: full screenshots in a compact row; swipe on small screens. */ +.gt-gallery{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:18px;max-width:900px;margin:28px 0 10px} +.gt-gallery figure{margin:0;padding:12px;border:1px solid var(--rule,#C3CBD1);min-width:0} +.gt-gallery a{display:block;text-decoration:none}.gt-gallery img{display:block;width:auto;max-width:100%;height:300px;object-fit:contain;margin-inline:auto} +.gt-gallery figcaption{font-size:13px;line-height:1.5;margin-top:10px;text-align:center} +.gt-gallery-hint{font-size:13px} +@media(max-width:640px){.gt-gallery{display:flex;overflow-x:auto;scroll-snap-type:x mandatory;gap:14px;padding-bottom:12px}.gt-gallery figure{flex:0 0 min(78vw,260px);scroll-snap-align:start}.gt-gallery img{height:300px}}