FKITDEV-8787 / SLARAFIPI-60 — Raiffeisen Myra “Already authorized” / “Already has some kind of room”
RESOLVED — merged 2026-08-10 via vuer_css PR #3064 into the 1.9.11.100 release branch
Current status lives in [[#Resolution — merged 2026-08-10 (PR #3064)]] and the release hub raiffeisen-1.9.11.100. Merge-back to customization/raiffeisen / devel is still owed, and the ticket is still Pending / Review Needed in YouTrack.
One-line summary
Three Raiffeisen Myra mobile self-service rooms (186120, 186126, 186135) failed in production. Two of the three customer logs show the strings "Already authorized" and "Already has some kind of room". Both literals are defined server-side in vuer_css (server/socket/events/selfservice-v2.js:30-32, EXCEPTIONS.ALREADY_AUTHORIZED / EXCEPTIONS.ALREADY_HAS_ROOM) — an early triage claim that they came from the FaceKom mobile SDK was wrong; see Where the error strings come from — CORRECTED 2026-08-11. On the OSS side, selfService:v2:start silently resumes any non-closed room. Class is the same phantom-room family as FKITDEV-4736 (instrumentation, 2024-09) and same flow surface as the SLARAFIPI-53 / FKITDEV-7667 fix (2025-10).
Fix implemented (2026-06-02)
] for final state
Server-side self-heal + fail-closed hardening landed on vuer_css branch fix/FKITDEV-8787-selfservice-v2-abort-clear-state. Two commits pushed to origin, no PR opened yet. The reported phantom-room / “Already has some kind of room” symptom is addressed server-side. ⚠️ The clause once here — “the mobile-SDK angle from triage remains the upstream cause” — is retracted: there is no upstream SDK cause, both throws are vuer_css’s own (see the retraction below). The server now recovers from stale room state instead of refusing re-init, and that is the whole fix.
The branch was originally named bugfix/FKITDEV-8787-... but TechTeamer/vuer_css enforces a branch-name regex ^(feature|feat|chore|fix|release)/FKITDEV-\d+... — bugfix/ is not in the allowed prefix set and is rejected on push. Must use fix/ (or feature/feat/chore/release). This matches the InstaCash-update note’s mention of the parallel bugfix/FKITDEV-8787 WIP that pre-dated the rename.
The fix (server/socket/events/selfservice-v2.js)
Self-heal in selfService:v2:start — before the ALREADY_HAS_ROOM throw: if the socket carries stale selfServiceRoomData, call OSS rpcClient.selfServiceV2.getRemainingSeconds(roomId). If remainingSeconds < 1 (room positively expired), delete the stale selfServiceRoomData so re-init proceeds. This is the actual fix for the phantom-room / “Already has some kind of room” bug — the server now lets a customer re-start after their room has timed out, instead of staying wedged.
Cleanup in selfService:v2:abort — delete selfServiceRoomData after the OSS abort RPC succeeds, so a subsequent start on the same socket doesn’t throw ALREADY_HAS_ROOM.
Key design learning — fail closed on ambiguity (4-perspective review)
Fail CLOSED for a duplicate-prevention bug
For a bug whose whole purpose is preventing duplicate / phantom rooms, the safe default on ambiguity is fail closed (preserve state, keep the ALREADY_HAS_ROOM guard) unless the room is positively confirmed gone. Do not treat “the RPC threw” as “the resource is dead.”
The first version of the fix used a catch-all: on any error from getRemainingSeconds it deleted the room state (“fail open”).
A multi-agent review (silent-failure + test-quality perspectives) caught that this re-introduces the duplicate-room problem from the opposite direction: a transient RPC/OSS error (network blip, OSS overload) while the room is actually alive would clear the state and let start() create a second live room, orphaning the original. (The de-registration loop at start only cleans sockets matching the new room id, so the old live room is left dangling.)
Verified against OSS: SelfServiceV2Service.getRemainingSeconds returns Math.max(0, floor((expireAt - now) / 1000)), and the RPC wrapper does resolveModels(...) first. So a genuinely timed-out room returns 0 — handled by the < 1 branch, not the catch. Therefore the catch is only reached for truly-absent rooms, and we can fail closed there (preserve selfServiceRoomData, keep the ALREADY_HAS_ROOM guard, log at error) while still fixing the reported bug.
Net: commit 923e4c70 replaced the catch-all “delete on any error” with “delete only when remainingSeconds < 1; on RPC error, preserve state + log error.”
Also verified (NOT bugs)
The abort handler is guarded by createEndpoint’s default selfServiceRoomRequired = true, so there is no unguarded .id dereference on a missing room.
The self-heal only clears selfServiceRoomData, not roomData — which is fine: self-service sockets never carry roomData.
Tests now use the realserver/auth.js predicates (via jest.requireActual) instead of hand-rolled fakes — the fakes had diverged from production: they dropped the isAuthorized / customerId conjunct and collapsed the hasAnyRoomroomData branch.
Next steps — as recorded 2026-06-02 (all now historical)
Open PR targeting customization/raiffeisen — done twice (#3050, #3051) and both closed unmerged; delivery moved to the release branch via #3064.
Move FKITDEV-8787 from Triaged/blocked → In Review — the ticket is still Pending / Review Needed in YouTrack as of 2026-08-11, even though the code merged 2026-08-10.
Coordinate with m3szi — m3szi reviewed and approved the sibling FKITDEV-8827 PR in the same release.
Current open items are in [[#Resolution — merged 2026-08-10 (PR #3064)]].
Ticket chain
FKITDEV-8787 (FaceKom Dev) — triaged 2026-05-13.
SLARAFIPI-60 (Raiffeisen SLA) — partner-tagged Minor; upgraded to P2-Medium here (customer-blocking, no in-app recovery path).
Three SelfServiceRooms on Raiffeisen Myra mobile self-service: 186120, 186126, 186135.
Customer of room 186120 has a vestigial duplicate (csökevény szoba) in 186126 — same customerId.
Customer of room 186135 has no sibling room.
186126 customer never received tasks on the phone, retried 5x; after restart couldn’t pass the first photo.
Both 186126 and 186135 logs contain the strings "Already authorized" and "Already has some kind of room".
Partner (Raiffeisen) suspects the in-app “Restart” button leaves stale SDK memory.
Triage outcome
Severity: P2-Medium. Customer-blocking on the bank’s primary self-service flow with no in-app recovery — upgrades partner-tagged Minor.
Class: phantom-room family. Same surface as FKITDEV-4736 (instrumentation) and SLARAFIPI-53 / FKITDEV-7667 (2025-10 fix).
State: triaged; blocked on Raiffeisen for mobile-SDK confirmation.
Where the error strings come from — CORRECTED 2026-08-11
RETRACTION — the strings are server-side in vuer_css, NOT in the mobile SDK
The original triage claim (“they originate in the FaceKom mobile SDK (Android/iOS) local guard layer”) was wrong. Both literals are defined in vuer_css at server/socket/events/selfservice-v2.js:30-32:
Constant
Literal
EXCEPTIONS.ALREADY_AUTHORIZED
Already authorized
EXCEPTIONS.ALREADY_HAS_ROOM
Already has some kind of room
The original git grep was not wrong about what it searched — the strings genuinely are not in vuer_oss — but “absent from OSS” was incorrectly generalised to “absent from the server”, skipping the socket layer in vuer_css. Lesson: on this platform, “the server” is two repos; an OSS-only grep does not clear the server side.
The {"data": {"error": …}} shape the partner quoted is our own wire log
The partner pasted the errors in a "data": {"error": …} envelope, which read like a mobile-SDK log format. It is not — that is createEndpoint’s own wire log, emitted server-side via reportWireClientResponse. So the “mobile log” evidence was in fact our own vuer_css log all along, which is why the SDK-origin story looked so well-supported.
Consequences for everything downstream of this correction:
The recommendation C (“mobile Restart must call clearSession()”) is not the primary fix — a server-side fix was always sufficient and is what shipped.
The mermaid “working hypothesis sequence” below still describes a plausible client behaviour, but its SDK->>SDK: register guard hits steps are wrong: those guards are in vuer_css, so OSS/CSS did receive the request and did answer with the throw. Read that diagram as historical.
Attribution to the Raiffeisen mobile team was misdirected. Nothing was ever blocked on them for these two strings.
On the OSS side the original observation stands: there is no equivalent throw on V2. SelfServiceV2Service.start() silently resumes:
V1 does throw at SelfServiceRoomService.js:217 (Customer already has an open room), but SelfServiceActions.js:27-34 swallows it into a generic { success: false } — so even the V1 error never surfaces to the SDK as a rich code.
Csökevény szoba mechanism (by design)
By-design, but a debugging trap
customization/raiffeisen/server/service/RaiffeisenService.js::resolveExternalToken() returns the same customer.id for the same offerId. When the bank reissues the link or the customer gets a new attempt, SelfServiceV2Service.start() sees the previous SSR is closed and creates a new SSR for the same customerId. The previous closed SSR is the “vestigial room” (csökevény). Intentional, but it makes the duplicate-room arrangement look like a bug to anyone reading the DB cold.
Suspected race that produces the rarer non-closed duplicate:
Two start() calls in quick succession on a flapping SDK both pass the _findOpenRoomForCustomer null check.
Result: two SSRs for the same customerId in non-closed states.
Nothing at the DB layer guards against this — no partial unique index on (customerId) for ('waiting','incall','left').
Working hypothesis (root-cause path)
SUPERSEDED 2026-08-11 — rests on the retracted SDK-origin premise
This hypothesis was built on “the two error strings are SDK-local”, which is false — they are thrown by vuer_cssserver/socket/events/selfservice-v2.js:30-32 (see Where the error strings come from — CORRECTED 2026-08-11). The stale-socket-state half of the story survives and is exactly what the shipped fix addresses (the socket keeps client.sessionData.selfServiceRoomData across an in-app Restart); the “SDK short-circuits without contacting OSS” half is wrong — the request reached the server, and the server threw. Kept as a historical record of the reasoning. Nothing here is blocked on the Raiffeisen mobile team.
Working hypothesis sequence
sequenceDiagram
participant C as Customer (Myra app)
participant SDK as FaceKom SDK
participant OSS as vuer_oss server
participant DB as SelfServiceRoom
C->>SDK: Start KYC
SDK->>OSS: register / start
OSS->>DB: create SSR (waiting)
OSS-->>SDK: {created:true, ssrId, currentStep}
Note over C,SDK: Flow stalls (network or missing task)
C->>SDK: Tap in-app Restart
Note over SDK: clearSession() NOT called<br/>JWT + ssrData still in memory
SDK->>SDK: register guard hits → 'Already authorized'
SDK->>SDK: start guard hits → 'Already has some kind of room'
Note over OSS: never receives a fresh request
Note over DB: SSR still waiting/incall/left → no new tasks dispatched
Note over C: 'no tasks on phone'
OSS->>DB: timeout closes original SSR
Note over C: bank reissues link → new SSR for same customerId
C->>SDK: New attempt
SDK-->>C: stale state still refuses re-init → couldn't pass first photo
Mobile flow stalls (intermittent connectivity or missing task).
Customer taps in-app Restart instead of OS-killing the app.
SDK retains in-memory selfServiceRoomData + customer JWT — no clearSession().
SDK calls register / start again → SDK local guard short-circuits with the two error strings without contacting OSS.
OSS still has the previous SSR in waiting / incall / left → no new tasks dispatched → “no tasks” symptom.
Eventually OSS-side timeout closes the SSR → bank reissues → new SSR (e.g. 186126) created for same customerId → previous SSR (186120) becomes csökevény.
After the in-app restart the SDK’s stale state still refuses to re-init → “couldn’t pass first photo”.
Code surfaces (file:line)
Layer
Location
Note
OSS V2 service
server/service/SelfServiceV2Service.js:357-394
start() silently resumes any non-closed room
OSS V2 service
server/service/SelfServiceV2Service.js:224-232
_findOpenRoomForCustomer (used by V2)
OSS V1 service
server/service/SelfServiceRoomService.js:213-228
V1 throw at line 217: Customer already has an open room
resolveExternalToken() returns existing customer.id for same offerId — mechanism behind the csökevény arrangement
Open questions / data needed from Raiffeisen
MOSTLY MOOT 2026-08-11 — do NOT send these asks to the partner as written
This section was written on the retracted premise that the error strings were SDK-local. The ticket was never actually blocked on Raiffeisen, and the fix shipped without any of these answers. Struck items below are wrong or unnecessary; only the non-struck ones retain any value.
Mobile SDK version + which file/symbol emits each error string. — answered internally: vuer_cssserver/socket/events/selfservice-v2.js:30-32. Asking the partner to locate our own server strings would have been embarrassing.
Does the in-app “Restart” call SDK clearSession() or just re-run the mobile flow? — still genuinely useful, but reframed: the question is whether Restart reuses the same socket (which is what arms the bug), not what the SDK’s local guards do.
JWT decoded offerId for 186120 vs 186126 vs 186135 — does the shared customerId come from the same offerId?
Mobile-side log timestamps for the two error strings, correlated with server-side selfService:v2:register / :start calls.
Confirm raiffeisen.debug.phantomRoomLog is true in PROD config.
Is OneDrive_1_5-5-2026.zip (attachment on the ticket) the mobile log bundle? — almost certainly yes.
Recommended fixes (ordered)
Order
Type
Action
Ordering CORRECTED 2026-08-11 — A shipped, C was never the primary fix
A (server-side, then labelled “defense in depth”) is what actually shipped, scoped to the socket start/abort handlers in vuer_css. C was labelled “Mobile, primary” purely because of the retracted SDK-origin premise — it is neither primary nor required. B, E, F remain open and still valid.
Order
Type
Action
D
Operational, immediate
Enable raiffeisen.debug.phantomRoomLog: true in PROD; ask Raiffeisen to repro
C
Mobile, primary → not required
In-app Restart must call selfService:v2:abort then clearSession() before re-starting — would be defence-in-depth at best; the server fix stands alone
A ✅ SHIPPED
Server — this is the fix
In SelfServiceV2Service.start(), when _findOpenRoomForCustomer returns a room whose Flow is failed/aborted/cancelledorserviceProgress === 'preparation' and createdAt older than N minutes — abort+close it and create fresh. As shipped: implemented at the vuer_css socket layer instead (self-heal via OSS getRemainingSeconds, fail-closed) — PR #3064
B
Server, long-term
Partial unique index on (customerId) WHERE status IN ('waiting','incall','left') on selfServiceRoom, with cleanup migration
E
Runbook
SQL to manually close a vestigial blocking room: UPDATE "selfServiceRoom" SET status='closed', "closedAt"=now() WHERE id=<X> — for support
F
Server, error UX
Stop swallowing the V1 throw in SelfServiceActions.js:31-34; return the actual error code
Escalation path
Historical — the fix shipped without following this path (see the retraction above)
Steps 1 and 4 were premised on the SDK-origin claim and were never needed. Step 5’s routing is also not what happened: the customization/raiffeisen-targeted PRs were closed unmerged and delivery went via the release branch — see Delivery-route lesson.
Ask reporter for mobile app version + the data items in Open questions.
Enable phantomRoomLog in prod.
Coordinate with m3szi (author of f830fd8e5a / FKITDEV-7667) — owns the same flow handler. ✅ m3szi approved the sibling FKITDEV-8827 PR in this release.
If mobile fix needed, ticket the mobile team (Raiffeisen Myra app). — not needed.
Server fixes target raiffeisen first, forward-port to devel. → actual route: release branch chore/FKITDEV-9156-raiffeisen-release-1.9.11.100, with merge-back to customization/raiffeisen and develstill owed.
Cross-references
SLARAFIPI-53 / FKITDEV-8581 / FKITDEV-7667 — same flow, prior fix (no Obsidian note yet).
SUPERSEDED — PR #3051 was CLOSED UNMERGED. The fix shipped via PR #3064.
Verified 2026-08-11: bothcustomization/raiffeisen-targeted PRs (#3050 and #3051) were closed without merging. The change actually landed through the release-branch PR #3064 — see [[#Resolution — merged 2026-08-10 (PR #3064)]], which is now the source of truth. The snapshot below is the 2026-06-11 state of #3051 and is retained as history; treat its “OPEN, MERGEABLE” row as stale.
c0712c3a99, 428cbe3cca, 15ac89da1c (3 commits on the rebased branch; supersede the earlier 8586df65 / 923e4c70)
Final fix approach (PR #3051)
The fix lives entirely in vuer_cssserver/socket/events/selfservice-v2.js plus its unit test. Three behaviours:
Self-heal stale room state on selfService:v2:start. Before the ALREADY_HAS_ROOM guard, if the client still carries client.sessionData.selfServiceRoomData, the handler RPC-calls serviceContainer.rpcClient.selfServiceV2.getRemainingSeconds(roomId). If remainingSeconds is falsy or < 1 (room dead / expired), it runs delete client.sessionData.selfServiceRoomData and logs at warn (message self-heal: clearing stale selfServiceRoomData for dead room) — so a fresh room spawns instead of throwing ALREADY_HAS_ROOM. This is the actual fix for the phantom-room / “Already has some kind of room” symptom.
Fail-closed on lookup error. If getRemainingSecondsthrows, the handler does NOT delete the state — it logs at error and preservesselfServiceRoomData. Rationale embedded in the PR: an unexpected lookup failure is not proof the room is dead, and clearing on error could spawn a duplicate live room.
Abort-path cleanup. Also runs delete client.sessionData.selfServiceRoomData on the abort path (~line 569), so a subsequent start on the same socket does not re-trip ALREADY_HAS_ROOM.
Tests
test/tests/unit/socket/events/selfservice-v2.test.js adds 149 lines of unit tests covering the self-heal branches: remainingSeconds === 0, null / undefined (falsy, clears), and error-preserves-state (the fail-closed branch). Checks are green (2/2) on the PR.
Open items on the PR
Awaiting an approval decision — m3szi (owner of the prior FKITDEV-7667 / SLARAFIPI-53 fix on the same flow surface) has only left an empty-body COMMENTED review so far.
The mobile-SDK angle from triage remains the upstream cause — this PR makes the server recover from stale room state rather than refusing re-init; it does not change the SDK guards that emit the two error strings (recommendation C above stays open). PR #3051 corresponds to server-side recommendation A, scoped to the socket start / abort handlers.
FKITDEV-8787 (this ticket — phantom-room / “Already has some kind of room”) is distinct fromFKITDEV-8581 (Raiffeisen GIRinfO / logical data-match verification gate, under SLARAFIPI-53). Same partner (Raiffeisen) and same customization/raiffeisen branch documented in customization-branches, but different tickets and different fixes — they are frequently conflated. See also FKITDEV-4736 (phantom-room instrumentation).
Manual verification (partner hand-off script)
Purpose
Reusable end-to-end test procedure to verify the server-side self-heal against the real Raiffeisen Myra self-service (önkiszolgáló) flow, with a tester who has live access to it. This complements the existing automated coverage — the 76 Jest unit tests on server/socket/events/selfservice-v2.js exercise the self-heal branches in isolation, but cannot prove the fix against the actual mobile SDK that emits the “Already authorized” string.
Precondition — must be on the build with the fix (UPDATED 2026-08-11)
The fix is merged and lives on chore/FKITDEV-9156-raiffeisen-release-1.9.11.100 (both repos) — not on customization/raiffeisen, not on devel, and there is no raiffeisen-1.9.11.100 tag yet. The tester MUST be on a build from that release branch. On any earlier build they will simply reproduce the original bug and the test proves nothing.
The earlier rollout target recorded here (release/FKITDEV-8902-raiffeisen-1.9.11.95, “the 1.9.11.95 release”) is stale — the delivery moved to 1.9.11.100 (raiffeisen-1.9.11.100). Verify the code is actually in your build at server/socket/events/selfservice-v2.js:303-322 (self-heal) and :575 (abort clear).
Critical gotcha — must reuse the SAME socket (in-app Restart)
The bug only arms when the mobile SDK reuses the same socket — i.e. the in-app Restart. The tester must stay in the same app session and use in-app Restart/Cancel.
Fully closing & reopening the app (or re-scanning a fresh token) creates a NEW socket with no stale selfServiceRoomData — so the bug never triggers and the test is meaningless. If the procedure ever has the tester kill the app, the run is invalid.
Scenario A — reported failure (expired / dead room recovery)
Start a self-service flow with a valid offer / token.
Drive it into the broken state: room times out / fails to deliver tasks (original report: no tasks after ~5 retries). Do NOT close the app.
Tap the in-app Restart button.
Evaluate:
PASS — a fresh flow loads, tasks appear, the first photo works, and the flow continues.
FAIL — "Already authorized" / "Already has some kind of room", no tasks, stuck at the first photo.
Scenario B — controllable variant (abort → restart)
A more reliably-triggerable variant when the timeout in Scenario A is hard to force on demand:
Start a flow.
Cancel / abort in-app.
Start again in the same session.
Evaluate:
PASS — starts cleanly, no"Already has some kind of room".
Evidence to capture
Screen recording of: Restart → tasks appear → first-photo OK.
Note the room IDs if visible on screen.
Server-side confirmation (engineer side)
Tail the css server logs on the test environment:
docker logs -f vuer_css | grep -i "self-heal"
Log line
Meaning
self-heal: clearing stale selfServiceRoomData for dead room … (warn)
Positive proof — the self-heal fired: room confirmed expired (remainingSeconds < 1), stale state cleared, fresh room will spawn.
getRemainingSeconds failed … preserving state (error)
Fail-closed branch — the lookup threw, so the room is not confirmed-expired and state is deliberately preserved (see the “fail CLOSED on ambiguity” callout above). Recovery from here is abort, then start (Scenario B), not Restart.
Caveat — why this is handed off, not mocked locally (premise CORRECTED 2026-08-11)
The original reason given here — “the Already authorized string originates in the Raiffeisen mobile SDK” — is wrong (it is thrown by vuer_css; see the retraction above). The hand-off is still worth doing, but for a different reason: what cannot be mocked is the real client’s socket-reuse behaviour on in-app Restart, i.e. whether the app actually reuses the same socket and thus arms the bug. The server self-heal is fully exercised by Scenarios A/B and by the 76 unit tests — that part needs no partner.
Deploy to dev box (2026-06-18)
Fix branch deployed to lederera-447-fk-hardver — server-side verification PASS
Deployed the 8787 fix to the dev box and confirmed clean server-side boot. Functional verification (Scenarios A/B) still pending — needs the real Raiffeisen Myra mobile client pointed at https://css-lederera.facekomdev.net. Satisfies the “must be on the build with the fix” precondition of the hand-off script above: the box now runs the PR #3064 tip.
What was deployed
Box:lederera-447-fk-hardver, reached via command ssh Facekom (ProxyJump FKJumpBox). The box had been offline ~4h on Tailscale and had to be brought back online before the deploy. ⚠️ That box is now DECOMMISSIONED (offline since ~2026-06-27). Any re-deploy / the still-pending functional verification must be redone on the fk-dev Tailscale VM (command ssh ops@fk-dev.taild4189d.ts.net) — see dev-build-host; the Myra client would point at the fk-dev MagicDNS css URL, not css-lederera.facekomdev.net.
vuer_css: switched from customization/instacash @ e3f7a1d6e→fix/FKITDEV-8787-selfservice-v2-abort-clear-state-95 @ d69f7272e (PR #3064 tip). Left on the fix branch.
vuer_oss: left untouched — customization/raiffeisen, up. Provides the getRemainingSeconds RPC the self-heal calls.
Stash: a whitespace-only config/dev.json reformat was stashed as stash@{0} ("fk-8787-deploy: instacash dev.json whitespace reformat") before the branch switch.
New process booted clean: RabbitMQ connection established · Web server listening on 10083 · Socket server listening on 10082.
Zero error lines since boot.
Self-heal code present at selfservice-v2.js:313.
Operational corrections / observations
vuer_css has NO docker healthcheck — verify via supervisorctl, not docker health
docker inspect reports health = nohealthcheck, and docker ps shows “Up 5 weeks” because only the supervisord-managed inner processes restart, not the container itself. So the “~60s unhealthy anti-flap” note that applies to esign containers does NOT apply to vuer_css. Verify a restart took via docker exec vuer_css supervisorctl status (expect RUNNING + climbing uptime), not via docker health.
Pre-existing error on customization/instacash (not 8787-related) — flag for branch owner
The OLDcustomization/instacash process logged a TypeError: Cannot read properties of undefined (reading 'stack') at server/web/WebServer.js:481 (findRoute) just before the restart. It is unrelated to the 8787 fix and does not recur on the fix branch, but it is a real error-handling fault on customization/instacash — flag for whoever owns that branch.
Restore command — return the box to customization/instacash later
cd /workspace/vuer_css && git checkout customization/instacash && git stash pop && docker exec vuer_css sh -c 'cd /workspace/vuer_css && yarn && yarn build && supervisorctl restart all'
Still pending
Functional verification (Scenarios A/B) against the real Raiffeisen Myra mobile client pointed at https://css-lederera.facekomdev.net — not yet run. See Manual verification (partner hand-off script). ⚠️ That URL is dead (box decommissioned) — redo on fk-dev against a 1.9.11.100 release-branch build.
Resolution — merged 2026-08-10 (PR #3064)
MERGED — this is the current source of truth
All facts below verified 2026-08-11 via gh + git ls-remote + an explicit-refspec fetch.
raiffeisen-1.9.11.100 does NOT exist yet in either repo
Merge-back is owed — a branch cut off customization/raiffeisen today LOSES this fix
The fix is only on the release branch. It is not on customization/raiffeisen and not on devel. Anyone cutting a new Raiffeisen branch from customization/raiffeisen silently gets the pre-fix code, and the next devel → customization/raiffeisen sync will not carry it either. Same for the sibling FKITDEV-8827.
Delivery-route lesson
The two customization/raiffeisen-targeted PRs were abandoned in favour of a release-branch PR. For this partner, payload lands on chore/FKITDEV-<n>-raiffeisen-release-<ver> and the customization branch is reconciled afterwards — so “is it merged?” and “is it on the partner branch?” are two different questions, and answering only the first is how a fix goes missing. Always check both plus the tag.
YouTrack is stale
FKITDEV-8787 is still State Pending, Blocker Review Needed; parent SLARAFIPI-60 is still Blocked / Release needed — nobody transitioned them after the 2026-08-10 merge.
Unanswered partner question:Bence Varga, 2026-07-30 on SLARAFIPI-60: “melyik release-be tud ez belekerülni?” — never replied. The answer is now known: 1.9.11.100 / ASSRAFIPI-135.
Still-open gap — only the ALREADY_HAS_ROOM half is fixed
Already authorized can still be thrown on the same socket
The fix deliberately preserves customerId / authorization state — asserted by the test preserves customerId after abort (only roomData is cleared). So a re-register / re-auth on the same socket can still throw ALREADY_AUTHORIZED. What got fixed is the ALREADY_HAS_ROOM half: stale selfServiceRoomData is now self-healed (when the room is positively dead) and cleared on abort.
This is a deliberate scope choice, not an oversight — clearing authorization on abort would be a much larger behavioural change. But it means the Already authorized string in a future partner report is not evidence the fix regressed; it is a different, still-unaddressed path.
For a duplicate-prevention guard: "RPC threw" ≠ "resource dead" → fail CLOSED
The v1 of this fix was fail-open: it deleted the room state on any error from getRemainingSeconds. A transient RPC/OSS blip while the room was actually alive would then clear state and let start() create a second live room — re-creating the very duplicate-room bug the guard exists to prevent, from the opposite direction.
The shipped version deletes only on a positive death signal (remainingSeconds < 1, and OSS genuinely returns 0 for a timed-out room), and on an RPC error it preserves state + logs at error. Generalises to every duplicate/uniqueness guard: an ambiguous failure to observe a resource must never be read as proof the resource is gone.
Evidence provenance — local re-verification 2026-08-11
Re-ran the suite against the merged code:
vuer_csstest/tests/unit/socket/events/selfservice-v2.test.js — 76 / 76 pass, 12 of them the FKITDEV-8787 cases.
Sibling FKITDEV-8827 evidence re-run in the same session: RaiffeisenFaceComparisonExportService.test.js23 / 23, plus a real end-to-end CSV from the untracked .dev-e2e/run-e2e.sh harness. Detail in Local verification evidence (2026-08-11).
Two environment gotchas hit while re-running
The vuer_css worktree’s node_modules was out of sync with the branch lockfile — missing @babel/plugin-proposal-class-properties. Fix: yarn install --frozen-lockfile in the worktree. Switching a worktree onto a release branch does not re-install; you can silently be running another branch’s dependency tree.
rtk mangles git show <rev>:<path> arguments, and swallows jest --verbose per-test lines. Use python3subprocess.run([...]) with an args list (no shell) for the git show, and jest --json --outputFile=<f> instead of --verbose to get per-test results. Same family as rtk-mangles-curl-and-pipes / rtk-git-log-hides-merge-commits.