Summary
SMSService._on_delivery_report() deletes the modem/SIM delivery-report slot even when the +CMGR status-report header cannot be parsed. That turns an unparseable or vendor-variant delivery report into permanent data loss: no SMSDeliveryReportEvent is emitted, but AT+CMGD=<index> has already been sent.
Evidence
Affected code:
callstack/sms/service.py:230-247 reads the +CDSI slot and attempts to parse +CMGR status-report lines.
callstack/sms/service.py:249-253 deletes the SMS slot before checking whether status was parsed.
callstack/sms/service.py:255-259 then logs Could not parse delivery report and returns without emitting an event.
- Existing coverage in
tests/test_delivery_reports.py:106-115 asserts that malformed reports do not emit false success, but it does not assert that the SIM slot is preserved.
No-hardware reproduction from main (6926b369cdba):
PYTHONPATH=. uv run --no-project --with pyserial-asyncio python - <<'PY'
import asyncio
from callstack.events.bus import EventBus
from callstack.events.types import SMSDeliveryReportEvent, _RawDeliveryReport
from callstack.protocol.executor import ATCommandExecutor
from callstack.protocol.urc import URCDispatcher
from callstack.sms.service import SMSService
from callstack.transport.mock import MockTransport
async def main():
bus = EventBus()
urc = URCDispatcher(bus)
transport = MockTransport()
service = SMSService(ATCommandExecutor(transport, urc), bus)
emitted = []
bus.subscribe(SMSDeliveryReportEvent, lambda e: emitted.append(e))
transport.feed('+CMGR: "REC READ",not-a-reference,"+120****0999"', 'OK')
transport.feed('OK')
await service._on_delivery_report(_RawDeliveryReport(storage='SM', index=7))
await asyncio.sleep(0.02)
print('written_commands:', [w.strip() for w in transport.all_written])
print('delivery_events:', len(emitted))
asyncio.run(main())
PY
Actual output:
Could not parse delivery report at index 7
written_commands: ['AT+CMGR=7', 'AT+CMGD=7']
delivery_events: 0
Expected behavior
If a delivery report cannot be parsed, Callstack should not silently delete it. It should either leave the slot intact for a later parser fix/manual inspection, or move it through an explicit failure/quarantine path that preserves enough bounded, PII-safe metadata for debugging.
Suggested fix direction
- Parse the
+CMGR status-report header first.
- Send
AT+CMGD=<index> only after a report has been parsed and emitted/stored successfully.
- Add a regression test around malformed
+CMGR status reports that asserts AT+CMGD is not written when no delivery status can be parsed.
- Keep logs redacted; do not dump raw recipient numbers or full status-report lines by default.
Acceptance criteria
- Malformed/unrecognized delivery-report responses do not emit a false success event.
- Malformed/unrecognized delivery-report responses do not delete the SIM/modem slot.
- Valid delivery reports still emit
SMSDeliveryReportEvent and are deleted after successful processing.
- Regression coverage uses
MockTransport and requires no modem hardware.
Verification gates
git diff --check
PYTHONPATH=. uv run --no-project --with pytest --with pytest-asyncio --with pytest-aiohttp --with pyserial-asyncio --with aiosqlite pytest tests/test_delivery_reports.py tests/test_sms_service.py -q
PYTHONPATH=. uv run --no-project --with pytest --with pytest-asyncio --with pytest-aiohttp --with pyserial-asyncio --with aiosqlite pytest tests/ -q
Duplicate check
Searched existing issues/PRs for delivery report malformed delete CMGD, CDSI malformed status report deletes SIM slot, and broader delivery-report parse/delete terms. I did not find an existing issue or open PR covering delete-before-parse data loss.
Summary
SMSService._on_delivery_report()deletes the modem/SIM delivery-report slot even when the+CMGRstatus-report header cannot be parsed. That turns an unparseable or vendor-variant delivery report into permanent data loss: noSMSDeliveryReportEventis emitted, butAT+CMGD=<index>has already been sent.Evidence
Affected code:
callstack/sms/service.py:230-247reads the+CDSIslot and attempts to parse+CMGRstatus-report lines.callstack/sms/service.py:249-253deletes the SMS slot before checking whetherstatuswas parsed.callstack/sms/service.py:255-259then logsCould not parse delivery reportand returns without emitting an event.tests/test_delivery_reports.py:106-115asserts that malformed reports do not emit false success, but it does not assert that the SIM slot is preserved.No-hardware reproduction from
main(6926b369cdba):Actual output:
Expected behavior
If a delivery report cannot be parsed, Callstack should not silently delete it. It should either leave the slot intact for a later parser fix/manual inspection, or move it through an explicit failure/quarantine path that preserves enough bounded, PII-safe metadata for debugging.
Suggested fix direction
+CMGRstatus-report header first.AT+CMGD=<index>only after a report has been parsed and emitted/stored successfully.+CMGRstatus reports that assertsAT+CMGDis not written when no delivery status can be parsed.Acceptance criteria
SMSDeliveryReportEventand are deleted after successful processing.MockTransportand requires no modem hardware.Verification gates
Duplicate check
Searched existing issues/PRs for
delivery report malformed delete CMGD,CDSI malformed status report deletes SIM slot, and broader delivery-report parse/delete terms. I did not find an existing issue or open PR covering delete-before-parse data loss.