* refactor(sdk-android): publish under io.github.priyanshujain.sanderling
Move the published Maven coordinates to a sanderling sub-namespace so the
brand is visible in the dependency line (was io.github.priyanshujain:sdk-android).
Sub-namespace is auto-allowed by Sonatype under the verified parent groupId.
* chore: update sdk-android coordinates in folio + docs
Follow the groupId change to io.github.priyanshujain.sanderling:sdk-android.
* ci(docs): use --version flag for d2 install script
The d2 installer accepts --version vX.Y.Z, not --tag. The --tag form
was rejected as "unrecognized flag" on the docs workflow run after
PR #26 merged.
* feat(ltl): add Now/Next/Eventually/Implies/Or/And/Not formulas
Replace the fold-with-latch evaluator with a residual-formula reducer.
Each Observe() instantiates a fresh obligation from the root (stripping
an outer Always), reduces each pending obligation against current state,
latches Violated on first failure, and surfaces Pending verdicts for
deferred obligations. Existing Always/Pure/Thunk tests continue to pass.
* feat(ltl): support relative duration for eventually().within()
* feat(proto): add Swipe, PressKey, RecentLogs RPCs
* feat(verifier,runner): formula handles, new action kinds, rich state
- verifier: add formula-spec registry; bindNow/bindNext/bindEventually with
chainable .implies/.or/.and/.not and .within(n,unit) on eventually; bindFrom
for uniform sampling. bindAlways keeps accepting plain predicates.
- verifier: store lastTree, lastAction, step time, logs, exceptions on the
Verifier; SnapshotInput replaces the (snapshots, tree) pair. stateObject now
produces state.lastAction/time/logs/exceptions matching the TS State type.
- verifier: make taps/swipes/waitOnce/pressKey built-in generators actually
fire; taps picks a clickable, enabled element from the last hierarchy.
- agent: add exceptions field to Message wire format.
- driver: add Swipe/PressKey/RecentLogs to Driver interface; wire maestro
client and mock driver. LogEntry exposed for runner consumption.
- runner: apply Swipe/PressKey/Wait actions; collect logcat and exceptions;
pass lastAction and step time into PushSnapshot.
* feat(spec-api): LTL operators, new actions, richer State
- ltl.ts exports now/next/eventually; always overload accepts a Formula
- types.ts: Formula gains implies/or/and/not; EventuallyFormula adds .within;
State gains lastAction/time/logs/exceptions; Swipe/PressKey/Wait action types
- actions.ts: Swipe/PressKey/Wait/from constructors; waitOnce + pressKey
default generators
- tests exercise the chaining, sampling, and new actions through a recorded
fake runtime
* feat(sidecar): add swipe, pressKey, recentLogs RPC handlers
* feat(sdk-android): capture uncaught exceptions
Install a default uncaught handler on Uatu.start, chained with any
existing handler so Android's crash reporter still runs. Expose
Uatu.reportError for callers to forward caught throwables. A bounded
circular buffer (default 50) drains into each STATE message's new
exceptions field. Protocol.kt serializes/deserializes the field,
matching the Go wire format added to internal/agent/protocol.go.
* feat(spec-api): add @uatu/spec/defaults/properties bundle
* feat(sample-app): exercise new LTL operators + defaults
spec.ts now imports eventually/next/now/from from @uatu/spec and
noUncaughtExceptions from @uatu/spec/defaults/properties. It declares
three properties that exercise the new surface:
- accountCountNonNegative: plain always() safety
- addAccountAdvances: always(now(x).implies(next(y)))
- eventuallyLoggedIn: eventually(p).within(30, "seconds")
- noUncaughtExceptions: imported default
The weighted actions root uses from() for random phone/name sampling
and entries for taps/swipes/waitOnce/pressKey built-ins.
SampleApplication gains a debug hook gated on the system property
uatu.inject_error so the e2e run can synthesize an Uatu.reportError and
verify noUncaughtExceptions violates.
cmd/uatu/test_run.go adds a subpath alias so specs importing
"@uatu/spec/defaults/properties" resolve against the in-tree source
when running from the uatu checkout. The spec-integration tests swap
the old click-counter fixtures for the new login hierarchy.
* feat(trace): record swipe/key/wait details + exceptions
trace.Step gains an Exceptions array so the trace captures the
class/message/stackTrace for each SDK-reported throwable in a step.
trace.Action gains FromX/FromY/ToX/ToY/Key/DurationMillis so the full
payload of Swipe/PressKey/Wait actions is visible in trace.jsonl.
sample-app's debug error hook now gates on ApplicationInfo.DEBUGGABLE
instead of a system property (adb setprop fails on non-rooted
emulators).
* fix(sdk-android): add @JvmOverloads to Uatu.start
Java callers can now invoke start(application) without supplying a
Configuration, matching the Kotlin default-arg ergonomics.
* feat(agent): add protocol_version to HELLO handshake
ProtocolVersion=1 lives on Message and is set by Hello(). Server.Accept
rejects mismatches with a clear error. SDK upgrades that don't change
the wire format keep the same protocol_version; bump on breaking changes.
* test(agent): assert protocol_version in Hello round-trip
* feat(sdk-android): send protocol_version=1 in HELLO
Mirrors agent.ProtocolVersion on the Go side. Bump in lockstep with
the Go constant when the wire format breaks.
* chore(sample-app): pull @uatu/spec from npm next tag
Replaces the file: dep. Copy-paste users can now npm install against
the registry. The release workflow publishes pre-release tags to
npm dist-tag 'next', so the sample tracks the latest rc without
manual version bumps. Lockfile currently resolves to 0.0.1-rc3.
* build(sdk-android): bump AGP 8.11.0 → 8.13.0
Required by com.vanniktech.maven.publish 0.36.0, bumped in the
following commit to pull a Dokka 2.x that handles JDK 21+ version
strings.
* build(sdk-android): migrate to vanniktech 0.36 + Dokka v2 javadoc
Fixes the `javaDocReleaseGeneration` crash on JDK 21+ hosts
(IllegalArgumentException: 25.0.2 in the bundled IntelliJ
JavaVersion.parse). Root cause: AGP's JavaDocGenerationTask pulled
an old Dokka whose vendored util-lang predated JDK 9+ version
strings. Fixed upstream in Dokka 2.1.0 (Kotlin/dokka#4202).
Changes in this commit:
- Bump vanniktech-maven-publish 0.30.0 → 0.36.0, which drops Dokka
v1 support and the SonatypeHost parameter (Central Portal is
now the default).
- Apply org.jetbrains.dokka and org.jetbrains.dokka-javadoc 2.2.0
so the javadoc jar is generated by Dokka 2.x directly, not AGP's
bundled-Dokka path.
- Switch AndroidSingleVariantLibrary to the explicit
JavadocJar.Dokka("dokkaGeneratePublicationJavadoc") form; the
old `publishJavadocJar = true` boolean now maps to plain
javadoc, which still goes through AGP's broken path on Android
source sets.
- Enable V2Enabled in gradle.properties (required by 0.36+) and
silence the transitional opt-in warning.
Verified: `make release-android-local` on openjdk 25.0.2 produces
aar, sources jar, javadoc jar, pom, and module; `./gradlew
:sdk-android:testDebugUnitTest :sidecar:test` still pass.
* fix(cli): fall back to node_modules for @uatu/spec resolution
Drop the hard failure when the uatu source tree is not reachable from
the spec file. Users integrating uatu in their own app have @uatu/spec
installed via npm; esbuild now resolves it from node_modules.
* build(gradle): drop :sample-app include from root settings
The sample now has its own Gradle project in examples/sample-app/android.
* build(sample-app): vendor gradle wrapper
Users running the sample build the APK via ./gradlew from inside the
sample-app's own android/ directory, no repo-root wrapper required.
* build(sample-app): make gradle project standalone
Drop the project(':sdk-android') dependency in favor of the Maven
Central coordinate io.github.priyanshujain:sdk-android. The sample now
owns its settings.gradle.kts and gradle.properties, so it builds
without any pieces of the uatu source tree.
* chore(sample-app): declare @uatu/spec npm dependency
Mirrors what a downstream user would put in their own package.json.
Uses file: for pre-release development; becomes a normal semver pin
once @uatu/spec ships to npm.
* docs(sample-app): rewrite justfile and add README
Justfile drops repo_root; all recipes run against the local gradle
wrapper and uatu from PATH. README is scoped to what a user needs to
run the sample against their own device.
* docs(manual): update sample install steps to standalone layout
./gradlew :sample-app:installDebug no longer exists; the sample owns
its own wrapper under android/.
* feat(sdk): log when Uatu.start succeeds
Silent SDK start makes the "SDK didn't connect" failure mode
impossible to debug. One INFO line at start time is enough.
* fix(sidecar): launch via am start -W instead of monkey
monkey -p <pkg> -c LAUNCHER 1 is unreliable on API 36+: it reports no
error but silently fails to start the activity, so the SDK never runs
and the CLI times out on the SDK-accept handshake.
Resolve the launcher activity via `cmd package resolve-activity
--brief` and launch it with `am start -W -n`. -W makes the call block
until the activity is up, which also makes the subsequent SDK
accept timing deterministic.
* feat(cli): auto-resolve Android device; boot AVD if none connected
--avd becomes optional. Resolution order:
- use any already-connected adb device;
- else if --avd names an existing AVD, boot it and wait for boot;
- else if --avd is missing or names no AVD, error with a clear message.
Falls back to $ANDROID_HOME/emulator/emulator when the binary is not on
PATH, so a standard Android SDK install works without extra shell setup.
* docs(sample-app): AVD is optional; document both paths
just test runs against any connected device. If none, pass AVD=<name>
to have uatu boot the emulator for you.
* feat(cli): auto-discover Android SDK; auto-pick the lone AVD
adb and emulator are looked up via PATH, then $ANDROID_HOME,
$ANDROID_SDK_ROOT, ~/Library/Android/sdk, ~/Android/Sdk, and the
Homebrew cask path. The discovered platform-tools directory is
prepended to the sidecar's PATH so its adb subprocess calls work too.
When --avd isn't passed and no device is connected, the CLI picks the
sole local AVD and boots it. Multiple AVDs → error listing them.
* build(make): add `make install` that go-installs uatu onto PATH
Puts `uatu` into $GOBIN (or $GOPATH/bin) so the sample and any local
dev flow can call it without PATH= prefixes.
* docs(sample-app): zero-config just test; dotenv-load for persistence
Drop the expectation that users prefix commands with PATH=, ANDROID_HOME=,
or AVD=. `just test` now works as-is; optional knobs can be pinned in a
.env file alongside the justfile.
* fix(sample-app): auto-detect ANDROID_HOME for Gradle tasks
The Go CLI finds the SDK itself, but AGP still needs ANDROID_HOME to
resolve `sdk.dir`. The justfile now resolves it from env or canonical
install paths before invoking ./gradlew, so `just install` works out
of the box on a standard Android SDK setup.
* gitignore runs directory for sample app
* feat(cli): add Version var and version subcommand
* build(gradle): introduce uatu.version property for lockstep releases
* build(sdk-android): swap GitHub Packages for vanniktech Maven Central plugin
* build(spec-api): make package publish-ready for npm
* ci(release): add goreleaser config for cross-platform uatu CLI builds
* ci: add ci and release GitHub Actions workflows
* ci: restrict ci.yml to PR + workflow_dispatch (no direct push to master)
* docs(release): add local release targets, env example, and install docs
* build(sdk-android): make signAllPublications conditional on signing key
* ci(release): stage sidecar JAR at embed path before go build
* chore(spec-api): regenerate package-lock for updated package.json
* ci: install protoc-gen-go plugins before buf generate
* ci: bump Node to 22 (required for --experimental-strip-types)
Coordinates: dev.uatu:sdk-android:0.0.1. Credentials read from
GH_TOKEN/GH_USERNAME (or GITHUB_TOKEN/GITHUB_ACTOR for CI).
.env is gitignored so local tokens stay out of git.
Consumers add the maven repo + debugImplementation in their
build.gradle and we're done.
Choreographer.getInstance() is per-thread and throws on threads
without a Looper. The previous impl called it from the SocketClient
reader thread, which surfaced as snapshot failures with empty
state during the sample-app round trip. Now the reader thread
posts a Runnable to the main Handler that, on the main thread,
gets the Choreographer and posts the frame callback.
Single-activity Android app that calls Uatu.start() in
SampleApplication.onCreate and registers three extractors
(app_state, click_count, uptime_millis). MainActivity has a
button so click_count exercises non-trivial state.
Uatu singleton owns an internal UatuRuntime that wires the socket
client, pauser, and extractor registry. start() sends HELLO on
connect, responds to PAUSE by running extractors inside the pause
window and replying with STATE, and releases the gate on RESUME.
LinkedHashMap preserves extractor registration order so trace
output is deterministic. Failed extractors log a warning and
record null rather than aborting the whole snapshot.
Worker thread calls pauseAndSnapshot with an extractor closure;
the Pauser posts a frame callback that runs extractors on the
main thread and then blocks that thread on a semaphore until
release() fires. FrameCallbackPoster abstracts Choreographer so
JVM unit tests drive it with a single-thread executor, while the
Android runtime uses ChoreographerPoster.
SocketClient owns a background reader thread, dispatches incoming
messages to a handler, and auto-reconnects with exponential backoff
when the transport drops. AgentTransport abstracts the socket so
JVM unit tests drive the client through piped streams while the
Android runtime uses LocalSocket over the abstract namespace.
Send is exposed to consumers via a MessageSender passed to
onConnected. Writes are serialized under the output stream lock.
Uses org.json (already on Android; org.json:json test dep for JVM
unit tests). Field names and framing mirror internal/agent/protocol.go
so Go-encoded frames decode on the SDK without a shared schema file.