Skip to content

feat(android): add registerEvent to the Kotlin SDK (#127) - #146

Merged
V3RON merged 13 commits into
issue-126-add-registerevent-to-the-swift-sdkfrom
issue-127-add-registerevent-to-the-kotlin-sdk
Oct 2, 2026
Merged

V3RON merged 13 commits into
issue-126-add-registerevent-to-the-swift-sdkfrom
issue-127-add-registerevent-to-the-kotlin-sdk

Conversation

@V3RON

@V3RON V3RON commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

Closes #127

Stacked on #143 (issue #124); base is that branch.

What changed

The Kotlin core gets Appduct.registerEvent(name, description, payloadSchema) returning an EventRegistration. After every ack carrying event_registry: true, resume included, the client sends an event_registry_snapshot; later declarations and disposals send upsert and remove deltas. With no flag nothing is sent. The core-noop facade mirrors the API. Adds fixtures/event-registry-frames.json (snapshot, empty snapshot, upsert and remove deltas), which the Swift slice (#126) also adds in the same shape.

Acceptance criteria

# Criterion Test Tier
1 Kotlin suite passes event-descriptors.json FixturesConformanceTest event-descriptors fixture unit
2 Every flagged ack, resume included, is followed by a snapshot AppductEventRegistryTest snapshot, empty snapshot, resume ack tests unit
3 The registration's disposer sends a remove delta Appduct registerEvent ... its registration removes it unit
4 No flag, no event frames an ack without the flag sends no event frames, a resume ack without the flag ... unit
5 Snapshot and delta frames fixture passes the frames sent match every vector in event-registry-frames fixture unit
6 Docs: Android README and install page; changelog n/a docs

E2E evidence

Target: Android emulator (Pixel API 35, Android 15, arm64), playground-native/android, commit 09ede49, this branch's daemon (isolated state dir, port 9611). The playground was temporarily patched, not committed, to call Appduct.registerEvent for cart.item_added (with a payload schema) and checkout.done (without).
Smoke: SMOKE_OK (throwing_tool returned tool_execution_error, exit 72)
Feature:

$ appduct events ls
  cart.item_added { sku: string, qty?: number }
    Posted when an item is added to the cart.
  checkout.done
    Posted when checkout completes.

$ appduct events ls --name cart.item_added
  Signature       cart.item_added { sku: string, qty?: number }
  Description     Posted when an item is added to the cart.
  Payload schema  {"type":"object","properties":{"sku":{"type":"string"},"qty":{"type":"number"}},"required":["sku"]}

$ appduct events ls --name checkout.done --json
{"ok":true,"data":{"events":[{"name":"checkout.done","description":"Posted when checkout completes."}],"total":1,"name":"checkout.done"}}

After backgrounding (HOME) and foregrounding the app, events ls and events ls --name cart.item_added --json returned the same declarations. The session stayed active throughout, so this shows persistence across background/foreground, not a new socket resume.

Checklist

  • CHANGELOG.md has an entry under Unreleased
  • User-facing docs updated (packages/native/android/README.md, website/.../install/android.mdx)
  • No new import past a module's index.ts; no new direct node:* I/O outside an adapter
  • Simplification checklist applied
  • docs/ARCHITECTURE.md unaffected

Out of scope

fixtures/README.md still says the event descriptors fixture is asserted by TypeScript only and does not describe event-registry-frames.json; it is #124's file, so left for the stack to reconcile with #126.

Status

Implement: done (5/5 green) Review: round 2, approve E2E: pass (Android, playground-native) Ready: yes

@V3RON
V3RON changed the base branch from issue-124-declare-events-over-the-wire-and-list-th to issue-126-add-registerevent-to-the-swift-sdk October 2, 2026 07:46
V3RON added 3 commits October 2, 2026 10:03
2 failing -> 2 failing (red tests for the ordering contract)
…127)

Declarations mutate and send from the client dispatcher, the ack captures its
snapshot synchronously, and a lock keeps deltas out of a snapshot in flight.

2 failing -> 0 failing

@V3RON V3RON left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Request changes (posted as a comment: GitHub refuses request-changes on your own PR): 1 blocker, 0 should-fix.
Spec: issue #127 plus the ordering contract in packages/native/fixtures/README.md.
Fix first: an event declared from a connection listener during the ack goes out as a delta before the snapshot that then wipes it.

scope.launch { sendSnapshotSafely() }
// The event snapshot is the declarations as they stand at ack time; declarations made
// after it go out as deltas behind it.
val eventSnapshot = if (heldSession?.acceptsEventRegistry == true) eventRegistry.snapshotWireJson() else null

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocker: a declaration made while the ack is handled is sent as a delta before the snapshot, and the snapshot then erases it on the daemon.

setClientState(active) and emitSessionChange run listeners synchronously on the dispatcher before this line. A listener that declares an event, e.g. Appduct.addListener { if (it is AppductEvent.SessionChange) Appduct.registerEvent("x", "d") }, enqueues the upsert coroutine ahead of the sendSnapshotSafely launch below, and this snapshot is taken before that coroutine has upserted. The dispatcher then runs the upsert first: state is active and the session accepts, so the delta is sent; the snapshot follows without x. The daemon's registry.snapshot replaces the whole map, so x is gone from appduct events ls until the next resume. A registerEvent call from another thread that lands between the continuation starting and the launch below hits the same order.

Reproduced on this head with a scratch test: client.addSessionChangeListener { _, _, _, _ -> client.registerEvent(event("from_listener")) } then connectAndAck(eventRegistry = true) sends [event_registry_delta:from_listener, event_registry_snapshot:[]]. This breaks the fixtures README contract (snapshot before any later delta).

Fix direction: gate deltas on a per-ack "snapshot sent" flag that the snapshot task sets, and read the event registry inside that task (under sendLock) instead of here.

V3RON added 2 commits October 2, 2026 10:49
…#127)

2 failing -> 0 failing; core-noop gains registerEvent, unregisterEvent and AppductEventDescriptor

@V3RON V3RON left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approve (posted as a comment: GitHub blocks approving your own PR): 0 blocker, 0 should-fix. Spec: issue #127 via the PR's criteria table.
Round 1's ordering blocker is fixed: a declaration or removal made while an ack is handled lands in that ack's snapshot and sends no delta; the event registry suite passed 25 of 25 reruns and the two new tests fail on 6dca660 without the fix.

…he-swift-sdk' into issue-127-add-registerevent-to-the-kotlin-sdk
@V3RON
V3RON added this pull request to stack #148 October 2, 2026 09:41
@V3RON
V3RON marked this pull request as ready for review October 2, 2026 09:41
V3RON added 3 commits October 2, 2026 12:27
…he-swift-sdk' into issue-127-add-registerevent-to-the-kotlin-sdk
…he-swift-sdk' into issue-127-add-registerevent-to-the-kotlin-sdk
…he-swift-sdk' into issue-127-add-registerevent-to-the-kotlin-sdk
@V3RON
V3RON merged commit f58c5c0 into main Oct 2, 2026
9 checks passed
@V3RON
V3RON deleted the issue-127-add-registerevent-to-the-kotlin-sdk branch October 2, 2026 12:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add registerEvent to the Kotlin SDK

1 participant