Branch: feat/ecr17-transport-and-commands (off PR #3 branch fix/ecr17-spec-compliance).
Plan: ~/.claude/plans/vivid-honking-bengio.md.
Target PR: to be opened against main once Phase 0 lands (or reuse a draft).
- Local tests green (
ctest+ TS typecheck where applicable). - Local Copilot review:
copilot --autopilot --yolo -p "/review <full branch diff vs origin/main>"(save diff to temp file if large). Record learnings indocs/LESSON.md. - Zero actionable review comments → continue; else fix and go to 1.
- Push branch.
- CI all green; else fix and go to 1.
- Phase done → continue to next phase in automode.
- Phase 0 — TS spec async + types + events + transport spec + nitrogen ✅ DONE (PR #4 stacked on #3, CI green)
- Phase 1 — all command builders (Ecr17Protocol) + builder tests ✅ DONE (CI 55/55)
- Phase 2 — response parsers (Ecr17Response) + parser tests ✅ DONE (CI 66/66)
- Phase 3 — Ecr17Session orchestration + FakeTransport + session tests ✅ DONE (CI 74/74)
- Phase 8a — ts-checks CI (typecheck + nitrogen), checkout@v5 ✅ GREEN
- Phase 4 — HybridEcr17Client wiring + NativeTransportAdapter + sendAckOnly ✅ written (cpp-tests 77/77; client/adapter verified only by Android build)
- Phase 5 — native Kotlin (HybridEcr17Transport.kt) + Swift (best-effort, no iOS CI) ✅ written
- Phase 8b — native Android build CI ✅ GREEN on first real attempt (run 26562706306); client/adapter/Kotlin/Nitro all compiled+linked into APK
- Phase 7 — README (Roadmap/Feature status ✅, async API, events, hook) ✅ DONE
- Phase 6 — tests done inline per phase (cpp-tests 77/77) ✅
- Phase 9 — root AGENTS.md distilled from LESSON.md (workflow + nitro/build know-how) ✅
- Final confirming Android build dispatched after the Kotlin reconnect/disconnect fix (run 26564115692).
-
Auto-connect/keepAlive (ensureConnected via transport await; socket kept open)
-
Tokenization (U) flow wired (exchangeWithAdditionalData; pay/preAuth/verify)
-
Receipt streaming after result (SessionConfig.receiptDrainMs + Ecr17Config field)
-
Opt-in real-terminal test (PosixTcpTransport + test_integration_terminal, env-gated)
-
PR #4 retargeted to main (#3 merged)
-
cpp-tests 80/80 green; client/native verified by Android build.
-
Auto-reconnect on mid-session drop — SAFE policy (financial never replayed; RetryPolicy.hpp unit-tested; recover via sendLastResult 'G'). cpp 82/82.
-
Money-safety reviewed by Copilot (no double-charge path) + README enterprise section.
-
PROACTIVE reconnect (PR #11, fix/proactive-reconnect): ECR17/Nexi terminals close TCP between transactions → detect the peer-closed/half-open socket BEFORE sending (Kotlin isConnected() = write-free 1-byte peek-with-pushback on a PushbackInputStream; NOT sendUrgentData — OOB would corrupt a financial frame under SO_OOBINLINE). Removes the FALSE "transport disconnected during exchange" on financial commands; money-safety (RetryPolicy / 'G') untouched. Also reverted the LRC ETX-fold regression in Lcr.cpp (NOEXT, not STD) introduced by 44f178e. LESSON.md updated.
- iOS Swift transport verification: NO macOS CI runner. Swift written best-effort.
- (everything else is implemented + verified: Android build green, cpp 82/82, ts-checks green)
Trigger: gh workflow run "Android build" --ref feat/ecr17-transport-and-commands. ~15-20 min.
Likely first-run errors to fix (unverifiable locally): nitro include paths
(<NitroModules/ArrayBuffer.hpp>, <NitroModules/HybridObjectRegistry.hpp>),
HybridObjectRegistry::createHybridObject usage, Kotlin ArrayBuffer/Promise API,
Swift ArrayBuffer bridging (iOS has NO CI — best-effort). Batch fixes, re-dispatch.
CI bun install 403 on private @padosoft/config is fixed by STRIPPING that
tooling-only devDep before install in CI (jq del + rm bun.lock + bun install).
No token / package-visibility change needed. Use the SAME trick in the Android
build job. Org disallows making the package public anyway.
cpp-tests.ymluses actions/checkout@v4 (Node20 deprecation warning) → bump to @v5 in Phase 8.
Transport C++ spec (generated): connect(string host,double port,double timeoutMs)->Promise<void>,
disconnect(), isConnected()->bool, send(shared_ptr<ArrayBuffer>),
setOnData((shared_ptr<ArrayBuffer>)->void), setOnDisconnect(()->void).
- ArrayBuffer:
ArrayBuffer::copy(const std::vector<uint8_t>&)to send; read viabuf->data()(uint8_t*) +buf->size()(owning native buffers are thread-safe). - Registry:
#include <NitroModules/HybridObjectRegistry.hpp>;auto obj = HybridObjectRegistry::createHybridObject("Ecr17Transport");auto t = std::static_pointer_cast<HybridEcr17TransportSpec>(obj); - NativeTransportAdapter : Transport — store host/port/timeout (from config); connect() -> t->connect(host,port,timeout); send(vector)-> t->send(ArrayBuffer::copy(v)); setDataCallback -> t->setOnData(cb{ cb(vector(buf->data(),buf->data()+buf->size())); }).
- Native impls (auto-globbed iOS / android src): HybridEcr17Transport.kt extends HybridEcr17TransportSpec() (java.net.Socket + reader thread -> onData(ArrayBuffer)); HybridEcr17Transport.swift extends HybridEcr17TransportSpec (Network.framework). Nitro generates the JNI bridge for the Kotlin HybridObject (no manual JNI).
- Client (Phase 4): own NativeTransportAdapter + Ecr17Session(adapter, SessionConfig from config_). Commands: Promise::async([this,req]{ ensureConnected(); auto pkt = session.exchange(Ecr17Protocol::buildX(...)); auto p = Ecr17Response::parseX(pkt.payload); return mapToNitro(p); }). Map: outcome->TransactionOutcome (OK/KO/CARDNOTPRESENT/ UNKNOWNTAG/UNKNOWN), cardType "1"/"2"/"3"->DEBIT/CREDIT/OTHER else UNKNOWN, entryMode ICC/MAG/MAN/CLM/CLI->ICC/MAG/MANUAL/CLESSMAG/CLESSICC. Optional strings -> std::optional (empty => nullopt). amountCents is double in requests (cast int).
- Verify ONLY via Android build job (workflow_dispatch). Batch native edits; one build per batch.
Phase 4: wire HybridEcr17Client to real logic:
- Hold a Transport (NativeTransportAdapter wrapping a generated HybridEcr17TransportSpec obtained via HybridObjectRegistry::createHybridObject ("Ecr17Transport")) + an Ecr17Session built from SessionConfig(config_).
- configure(): store config + (re)build SessionConfig (lrcMode/timeouts/retry).
- connect()/disconnect()/isConnected(): delegate to transport (host/port/ connectionTimeoutMs); fire onConnectionStateChange_.
- Each command (Promise::async): ensureConnected -> Ecr17Protocol::buildX(...) -> session.exchange(payload) -> Ecr17Response::parseX(pkt.payload) -> map to the generated Nitro result struct (field-copy; CHECK exact generated field names in nitrogen/generated/shared/c++/.hpp). Map outcome enum->TS union string, cardType '1'/'2'/'3'->debit/credit/other, entryMode ICC/MAG/...->union.
- Wire session.setOnProgress/onReceiptLine -> onProgress_/onReceiptLine_ (ProgressEvent{message}/ReceiptLine{text}). NOTE: Phase 4/5 are NOT compilable by the C++ unit CI (nitro-coupled). Verified only by the Phase-8 Android/iOS build jobs. Keep mapping mechanical; the risky logic (build/parse/session) is already CI-tested. Match generated struct field names EXACTLY. Promise::async({ return T; }) — see Ecr17Client.cpp stubs.
- Cannot compile natively locally (no toolchain). C++ unit target (GoogleTest) is the local/CI gate; native verified only via CI build jobs (Phase 8).
copilotCLI present (v1.0.54).- Generated
nitrogen/generated/**is gitignored (regenerated via./node_modules/.bin/nitrogen).