The runner's three FETCH/TR sites (the proactive-drain action, the
expectation, the cleanup drain) built every TERMINAL RESPONSE from the
script, so a card-announced OPEN CHANNEL during a run was answered
"performed successfully" without opening a channel - SEND DATA was
dropped and RECEIVE DATA returned nothing (silent data loss, broken
applet state).
_test_step_tr now routes 0x40-0x44 through _handle_bip_command: the
terminal answers for real, so the card's channels work inside a run.
The script keeps an escape hatch - a respond with `raw` TLVs or a
non-ok result wins (the applet error-path tests).
The BIP events flow during a run again for the channels the card
itself opened: _bip_data_available and _bip_flush_channel_events no
longer stop at the run guard (a channel only exists because the card
opened it) and the runner flushes the link events after each TR, so an
applet's own HTTP/BIP conversation completes inside a scripted run.
BIP control stays blocked (409).
Tests: the drain/expect/cleanup routing, the raw and non-ok-result
overrides, the run-time data-available and link events. 883 frontend
+ 873 Python; sw cache simple-v427.
A card that re-queues a command immediately after each accepted TR
(live 2026-10-11: the EP BAP card re-issued the same PROVIDE LOCAL
INFORMATION 1100 times) turned the background poll's FETCH/TR chain
into an unbounded loop that held _CARD_LOCK forever - every card
operation, a test run's start included, blocked behind it.
The chain now yields:
- _handle_proactive_chain aborts before the next FETCH while a test run
owns the card (_TEST_RUNNING) - the pending command stays for the
script - and takes max_rounds; the background poll passes
_POLL_CHAIN_MAX = 10, so a chatty card cannot hold the lock.
- _do_status_poll re-arms on its run skip (a heartbeat that never
fetches); _timer_expired and the BIP Data-available event wait during
a run; _bip_flush_channel_events stays quiet too.
- _sim_resume_poll() resumes polling at every run end - the two worker
bodies, their exception paths and the spawn failure.
Also the TR command-details qualifier echo (TS 102 223 6.8.1: the
object shall be identical to the command's): _build_tr,
testscript.build_tr and the interactive _menu_send_response echoed a
hardcoded 00 - harmless for the loop's qual-00 PLI, wrong for any
other qualifier. The menu pendings now carry cmd_qual (parsed in the
on_fetch handler).
Tests: the chain bound/abort (a fake card re-queueing a command), the
poll/timer guards, the resume wiring, the qualifier echo in both
builders. 883 frontend + 865 Python; sw cache simple-v425.
- Background STATUS polling is enabled by default, per the spec's idle
polling rule (TS 102 221 14.6.2): `_POLL_ENABLED = True`, __main__ runs
`_poll_enable()` regardless of card presence (--poll-interval 0 still
disables) and `_do_status_poll` keeps ticking while enabled even without
a session, so a cardless start resumes as soon as a card appears.
- A new card session clears a POLLING OFF from the previous card in
`_apply_equipped_card` before re-enabling polling.
- The Phone-tab toggle now shows the *effective* state: OFF in amber
("card disabled polling (POLLING OFF)") while the card suspended
proactive polling, back to ON on the next POLL INTERVAL. The 5 s
background poll now passes `card_disabled` through (it was dropped, so
an autonomously received POLLING OFF never reached the UI).
- Tests: poll_ui updated (button OFF while suspended, warning path),
test_poll +2 (cardless ticking, module default via a subprocess check).
- Help EN/RU, docs/api.md, AGENTS; version 3.5.10; sw cache simple-v263.
573 frontend / 441 Python green.
- EVENT_NAMES (server + PWA) corrected against TS 102 223 v18.3.0 8.25:
0x14 is "Access technology change (multiple)" (not Change of UICC
Access), 0x19 Profile container, 0x1A Void, 0x1B Secured profile
container, 0x1C Poll interval negotiation, 0x20-0x22 reserved. Values
the CAT spec leaves "Reserved for 3GPP" now carry the concrete TS 31.111
event name + clause (0x11 (I-)WLAN access status, 0x12 Network
rejection, 0x15 CSG cell selection, 0x17 IMS registration, 0x18 Incoming
IMS data, 0x1D Data connection status change, 0x1E CAG cell selection,
0x1F Slices status change).
- Poll Interval Negotiation (0x1C) in the Phone tab: the form proposes a
Duration (unit + interval) and the UICC's answer (TS 102 223 8.97
accepted / rejected / modified + optional Duration) is decoded and
shown; a "modified" duration becomes the background poll interval. The
form appears when the card subscribed to the event, like every other.
- Polling emulation is card-driven: a POLL INTERVAL adopts its Duration
(minutes/seconds/tenths -> 1..255 s, logged) for the background poll,
is echoed in the TERMINAL RESPONSE (6.8.4) and clears a POLLING OFF
suspension; POLLING OFF (6.4.14) suspends proactive polling until a new
POLL INTERVAL - the manual Send STATUS button and presence detection are
unaffected. /api/poll-status and /api/poll-toggle report card_disabled
(+ a warning when enabling while suspended); the PWA shows it in amber.
- Tests: tests/test_poll.py +7, test_proactive_names.py +3, frontend
event_forms +2 and poll_ui (4). Help EN/RU, docs/api.md, AGENTS.
573 frontend / 439 Python green; version 3.5.9; sw cache simple-v262.
--poll-interval 0 now really disables background STATUS polling (it was
clamped to 1s, and the equip branch force-enabled it anyway). Request
handlers and the poll thread now share _CARD_LOCK so a poll can never
interleave a FETCH/TERMINAL RESPONSE pair — the baseline log showed
AUTO-STATUS chains and duplicate FETCHes inside the equip TP chain.
--timing adds elapsed timestamps, per-reset logging (RESET #n) and
phase durations for startup (init_reader, card_init, pysim_app,
terminal_profile_drain) and equip (onecmd, terminal profile).
AGENTS.md documents the proactive-TR invariant: never fetch without
answering, never fetch twice, one APDU conversation at a time.