[GH-ISSUE #6875] [Bug] Signal decoding returns successful Evidence after an input-format option is ignored #4213

Closed
opened 2026-10-31 00:47:47 +00:00 by bsakaguchi · 0 comments
Owner

Originally created by @coygeek on GitHub (Oct 23, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/6875

Before submitting

  • I searched the open and closed issues for a related report.
  • I removed credentials, tokens, private paths, and sensitive target data.

Area

Evidence, artifacts, or reconstruction

Observed behavior

On a valid synthesized UART VCD, the public CLI accepts --input_format vcd:down_sample=2 and returns successful observed Evidence. sigrok reports Unknown keyword: down_sample. and ignores the option. REA retains that warning in diagnostics, but records the rejected option in selection.input_format and returns the same sample ranges as plain vcd.

The correctly spelled vcd:downsample=2 control changes the returned ranges. This is a related selector refusal, rather than an unrelated producer warning. Raw sigrok and REA outputs were compared for all three cases; no subprocess or provider was simulated.

Reproduction

Use the built checkout at the revision below, Node.js 24.22.1 on PATH, Python 3, and an absolute REA_SIGROK_COMMAND for sigrok-cli 0.7.2 with libsigrokdecode. Save the following executed script as /tmp/signal-option-repro.py and run it from the checkout root:

NODE_EXE=node python3 /tmp/signal-option-repro.py

The tested engine was the official stable 0.7.2 x86-64 AppImage, extracted using --appimage-extract, with REA_SIGROK_COMMAND pointing to the extracted AppRun. On the tested newer GLib host it also required LD_PRELOAD=/usr/lib/libgmodule-2.0.so.0; this loads the system module library while leaving the shipped sigrok engine and decoder modules unchanged. Another sigrok distribution was not tested.

from pathlib import Path
import hashlib,json,os,subprocess,tempfile
repo=Path.cwd()
node=os.environ.get('NODE_EXE','node')
root=Path(tempfile.mkdtemp(prefix='signal-option-',dir=os.environ.get('PROBE_PARENT','/tmp')))
(root/'home').mkdir()
env={**os.environ,'HOME':str(root/'home'),'XDG_CONFIG_HOME':str(root/'home/config'),'XDG_CACHE_HOME':str(root/'home/cache')}
lines=['$timescale 1 us $end','$scope module logic $end','$var wire 1 ! RX $end','$upscope $end','$enddefinitions $end','#0','1!'];tick=1000
for byte in b'REA':
 for bit in [0,*[(byte>>i)&1 for i in range(8)],1]:
  lines.extend([f'#{tick}',f'{bit}!']);tick+=104
 tick+=208
lines.extend([f'#{tick+1000}','1!'])
recording=root/'uart.vcd';recording.write_text('\n'.join(lines)+'\n')
digest=hashlib.sha256(recording.read_bytes()).hexdigest()
summary=[]
# Compare raw producer output against REA for exactly the same benign recording/selectors.
for name,fmt in [('plain','vcd'),('ignored-option','vcd:down_sample=2'),('working-option','vcd:downsample=2')]:
 raw=subprocess.run([env['REA_SIGROK_COMMAND'],'--dont-scan',f'--input-file={recording}','--protocol-decoders=uart:rx=RX:baudrate=9600','--protocol-decoder-samplenum',f'--input-format={fmt}','--protocol-decoder-annotations=uart=rx-data'],env=env,capture_output=True,text=True,timeout=40)
 (root/(name+'.raw.stdout')).write_text(raw.stdout);(root/(name+'.raw.stderr')).write_text(raw.stderr)
 p=subprocess.run([node,str(repo/'scripts/rea.mjs'),'decode-signal-recording',str(recording),'--decoders','uart:rx=RX:baudrate=9600','--input_format',fmt,'--annotations','uart=rx-data','--all_lines','--json'],env=env,capture_output=True,text=True,timeout=50)
 (root/(name+'.rea.json')).write_text(p.stdout);(root/(name+'.rea.stderr')).write_text(p.stderr)
 x=json.loads(p.stdout)
 assert raw.returncode==0 and p.returncode==0
 result=x['normalized_result']
 assert result['selection']['input_format']==fmt
 assert result['annotations_text']==raw.stdout and result['diagnostics']==raw.stderr
 assert result['artifact']['sha256']==digest
 assert result['output_window']=={'total_lines':3,'returned_lines':3,'has_more':False}
 item={'case':name,'raw_exit':raw.returncode,'rea_exit':p.returncode,'input_format':fmt,'annotations_text':result['annotations_text'],'diagnostics':result['diagnostics']}
 summary.append(item)
plain,ignored,working=summary
assert ignored['annotations_text']==plain['annotations_text']
assert ignored['diagnostics']=='Unknown keyword: down_sample.\n'
assert plain['diagnostics']==working['diagnostics']==''
assert working['annotations_text']!=plain['annotations_text']
assert hashlib.sha256(recording.read_bytes()).hexdigest()==digest
metadata={'commit':subprocess.check_output(['git','rev-parse','HEAD'],text=True).strip(),'node':subprocess.check_output([node,'--version'],text=True).strip(),'platform':subprocess.check_output(['uname','-srmo'],text=True).strip(),'sha256':digest}
(root/'summary.json').write_text(json.dumps({'metadata':metadata,'results':summary},indent=2))
print(json.dumps({'metadata':metadata,'results':summary},indent=2))
print('evidence:',root)

The final three-case probe passed all assertions. An earlier probe also observed the same successful ignored-option behavior with vcd:sample_width=8.

Expected behavior

Recognize the producer's explicit rejection of a caller-supplied input-format option as invalid_request, identify input_format, and retain the captured output. Do not return successful Evidence from default settings after this selected option was ignored. This does not request rejecting unrelated warnings or guessing the corrected spelling.

The input contract explicitly selects a format and options. The existing selector contract already refuses selected-channel fallbacks despite an exit-zero producer and distinguishes them from unrelated warnings. Direct sigrok --input-format=vcd --show lists downsample as the supported option.

Impact and scope

P2: a caller checking success and the recorded selection can use sample ranges produced without the requested preprocessing. The warning remains visible; no diagnostic loss, source modification, data loss, or security consequence is claimed. The verified workaround is to correct the option to vcd:downsample=2; the control preserves the three UART bytes and applies downsampling.

Observed on the public CLI with one 388-byte VCD and the real supported engine. CLI and MCP share this provider, but MCP, other input formats, other sigrok distributions, and the published npm package were not exercised for this finding.

REA version

Package 6.7.0, source main at d78892eb4ba41cf8746e5410b3ddf6e7f0ba524f.

Runtime and platform

Node.js v24.22.1; Linux 7.2.8-9-omarchy x86_64; glibc 2.44; repository-locked dependencies. Official sigrok-cli 0.7.2 AppImage, libsigrok 0.5.2, libsigrokdecode 0.5.3, bundled Python 3.5.2; real /usr/bin/prlimit.

Target and artifact context

Self-contained local VCD, one RX channel, 1 microsecond timescale, synthesized 9600-baud 8N1 UART bytes 52 45 41. Artifact SHA-256: a29e843888f33b66a2f3d195fb16bc1bdc3481c6a899dfef60dc327fa956fe78. The script checks that the input digest stays unchanged.

Interface

CLI

Earliest failure stage

Capture or observation

Relevant output or evidence

All three raw sigrok commands and all three REA CLI commands exited zero. REA returned the following values, matching the raw producer output exactly:

input_format=vcd
  diagnostics=""
  1104-1938 uart-1: 52
  2352-3186 uart-1: 45
  3600-4434 uart-1: 41

input_format=vcd:down_sample=2
  diagnostics="Unknown keyword: down_sample.\n"
  1104-1938 uart-1: 52
  2352-3186 uart-1: 45
  3600-4434 uart-1: 41

input_format=vcd:downsample=2
  diagnostics=""
  552-970 uart-1: 52
  1176-1594 uart-1: 45
  1800-2218 uart-1: 41

The script emits summary.json plus each complete CLI JSON response and each raw producer stdout/stderr in its temporary evidence directory. Every response reports three returned lines with no continuation.

Typed outcome or recovery expected

invalid_request, with an issue at ["input_format"] identifying the rejected option and preserving sigrok's warning and fallback output in captured_output. Correcting the spelling is a demonstrated recovery.

Analysis or suspected cause

Source-derived: the selector classifier handles an unknown input-module ID but does not recognize input-format option refusals. The executor accepts unmatched exit-zero output, and report construction retains the caller's rejected option in a successful result.

Additional context

Related closed reports cover different refusals: #6631 covered selected-channel fallback, #6766 covered a nonzero unknown input-module refusal, and #5515 covered generic nonzero decoder/annotation errors. No matching unknown input-format option success report was found among the supplied 1,082 open issue bodies or the relevant open/closed GitHub search results.

The invalid option is deliberate; the recording itself is valid. No hardware acquisition or target execution occurred. This report is specific to refusal handling, not a request to validate protocol semantics independently.

Triage assessment

  • Impact level: P2
  • Assessment status: supported
  • Impact basis: Real public CLI calls return successful observed Evidence containing an explicitly rejected preprocessing option and ranges produced with default preprocessing; the working option control produces different ranges.
  • Workaround status: available
  • Workaround basis: The executed vcd:downsample=2 control applies downsampling and retains the same three UART bytes, with empty diagnostics and an unchanged source digest.
Originally created by @coygeek on GitHub (Oct 23, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/6875 ## Before submitting - [x] I searched the open and closed issues for a related report. - [x] I removed credentials, tokens, private paths, and sensitive target data. ## Area Evidence, artifacts, or reconstruction ## Observed behavior On a valid synthesized UART VCD, the public CLI accepts `--input_format vcd:down_sample=2` and returns successful observed Evidence. sigrok reports `Unknown keyword: down_sample.` and ignores the option. REA retains that warning in `diagnostics`, but records the rejected option in `selection.input_format` and returns the same sample ranges as plain `vcd`. The correctly spelled `vcd:downsample=2` control changes the returned ranges. This is a related selector refusal, rather than an unrelated producer warning. Raw sigrok and REA outputs were compared for all three cases; no subprocess or provider was simulated. ## Reproduction Use the built checkout at the revision below, Node.js 24.22.1 on PATH, Python 3, and an absolute `REA_SIGROK_COMMAND` for sigrok-cli 0.7.2 with libsigrokdecode. Save the following executed script as `/tmp/signal-option-repro.py` and run it from the checkout root: ```sh NODE_EXE=node python3 /tmp/signal-option-repro.py ``` The tested engine was the [official stable 0.7.2 x86-64 AppImage](https://sigrok.org/download/binary/sigrok-cli/sigrok-cli-0.7.2-x86_64.AppImage), extracted using `--appimage-extract`, with `REA_SIGROK_COMMAND` pointing to the extracted `AppRun`. On the tested newer GLib host it also required `LD_PRELOAD=/usr/lib/libgmodule-2.0.so.0`; this loads the system module library while leaving the shipped sigrok engine and decoder modules unchanged. Another sigrok distribution was not tested. ```python from pathlib import Path import hashlib,json,os,subprocess,tempfile repo=Path.cwd() node=os.environ.get('NODE_EXE','node') root=Path(tempfile.mkdtemp(prefix='signal-option-',dir=os.environ.get('PROBE_PARENT','/tmp'))) (root/'home').mkdir() env={**os.environ,'HOME':str(root/'home'),'XDG_CONFIG_HOME':str(root/'home/config'),'XDG_CACHE_HOME':str(root/'home/cache')} lines=['$timescale 1 us $end','$scope module logic $end','$var wire 1 ! RX $end','$upscope $end','$enddefinitions $end','#0','1!'];tick=1000 for byte in b'REA': for bit in [0,*[(byte>>i)&1 for i in range(8)],1]: lines.extend([f'#{tick}',f'{bit}!']);tick+=104 tick+=208 lines.extend([f'#{tick+1000}','1!']) recording=root/'uart.vcd';recording.write_text('\n'.join(lines)+'\n') digest=hashlib.sha256(recording.read_bytes()).hexdigest() summary=[] # Compare raw producer output against REA for exactly the same benign recording/selectors. for name,fmt in [('plain','vcd'),('ignored-option','vcd:down_sample=2'),('working-option','vcd:downsample=2')]: raw=subprocess.run([env['REA_SIGROK_COMMAND'],'--dont-scan',f'--input-file={recording}','--protocol-decoders=uart:rx=RX:baudrate=9600','--protocol-decoder-samplenum',f'--input-format={fmt}','--protocol-decoder-annotations=uart=rx-data'],env=env,capture_output=True,text=True,timeout=40) (root/(name+'.raw.stdout')).write_text(raw.stdout);(root/(name+'.raw.stderr')).write_text(raw.stderr) p=subprocess.run([node,str(repo/'scripts/rea.mjs'),'decode-signal-recording',str(recording),'--decoders','uart:rx=RX:baudrate=9600','--input_format',fmt,'--annotations','uart=rx-data','--all_lines','--json'],env=env,capture_output=True,text=True,timeout=50) (root/(name+'.rea.json')).write_text(p.stdout);(root/(name+'.rea.stderr')).write_text(p.stderr) x=json.loads(p.stdout) assert raw.returncode==0 and p.returncode==0 result=x['normalized_result'] assert result['selection']['input_format']==fmt assert result['annotations_text']==raw.stdout and result['diagnostics']==raw.stderr assert result['artifact']['sha256']==digest assert result['output_window']=={'total_lines':3,'returned_lines':3,'has_more':False} item={'case':name,'raw_exit':raw.returncode,'rea_exit':p.returncode,'input_format':fmt,'annotations_text':result['annotations_text'],'diagnostics':result['diagnostics']} summary.append(item) plain,ignored,working=summary assert ignored['annotations_text']==plain['annotations_text'] assert ignored['diagnostics']=='Unknown keyword: down_sample.\n' assert plain['diagnostics']==working['diagnostics']=='' assert working['annotations_text']!=plain['annotations_text'] assert hashlib.sha256(recording.read_bytes()).hexdigest()==digest metadata={'commit':subprocess.check_output(['git','rev-parse','HEAD'],text=True).strip(),'node':subprocess.check_output([node,'--version'],text=True).strip(),'platform':subprocess.check_output(['uname','-srmo'],text=True).strip(),'sha256':digest} (root/'summary.json').write_text(json.dumps({'metadata':metadata,'results':summary},indent=2)) print(json.dumps({'metadata':metadata,'results':summary},indent=2)) print('evidence:',root) ``` The final three-case probe passed all assertions. An earlier probe also observed the same successful ignored-option behavior with `vcd:sample_width=8`. ## Expected behavior Recognize the producer's explicit rejection of a caller-supplied input-format option as `invalid_request`, identify `input_format`, and retain the captured output. Do not return successful Evidence from default settings after this selected option was ignored. This does not request rejecting unrelated warnings or guessing the corrected spelling. The [input contract](https://github.com/morluto/rea/blob/d78892eb4ba41cf8746e5410b3ddf6e7f0ba524f/src/domain/signals/signalRecording.ts#L19-L24) explicitly selects a format **and options**. The [existing selector contract](https://github.com/morluto/rea/blob/d78892eb4ba41cf8746e5410b3ddf6e7f0ba524f/docs/signal-recordings.md#L32-L48) already refuses selected-channel fallbacks despite an exit-zero producer and distinguishes them from unrelated warnings. Direct sigrok `--input-format=vcd --show` lists `downsample` as the supported option. ## Impact and scope P2: a caller checking success and the recorded selection can use sample ranges produced without the requested preprocessing. The warning remains visible; no diagnostic loss, source modification, data loss, or security consequence is claimed. The verified workaround is to correct the option to `vcd:downsample=2`; the control preserves the three UART bytes and applies downsampling. Observed on the public CLI with one 388-byte VCD and the real supported engine. CLI and MCP share this provider, but MCP, other input formats, other sigrok distributions, and the published npm package were not exercised for this finding. ## REA version Package 6.7.0, source main at `d78892eb4ba41cf8746e5410b3ddf6e7f0ba524f`. ## Runtime and platform Node.js v24.22.1; Linux 7.2.8-9-omarchy x86_64; glibc 2.44; repository-locked dependencies. Official sigrok-cli 0.7.2 AppImage, libsigrok 0.5.2, libsigrokdecode 0.5.3, bundled Python 3.5.2; real `/usr/bin/prlimit`. ## Target and artifact context Self-contained local VCD, one `RX` channel, 1 microsecond timescale, synthesized 9600-baud 8N1 UART bytes `52 45 41`. Artifact SHA-256: `a29e843888f33b66a2f3d195fb16bc1bdc3481c6a899dfef60dc327fa956fe78`. The script checks that the input digest stays unchanged. ## Interface CLI ## Earliest failure stage Capture or observation ## Relevant output or evidence All three raw sigrok commands and all three REA CLI commands exited zero. REA returned the following values, matching the raw producer output exactly: ```text input_format=vcd diagnostics="" 1104-1938 uart-1: 52 2352-3186 uart-1: 45 3600-4434 uart-1: 41 input_format=vcd:down_sample=2 diagnostics="Unknown keyword: down_sample.\n" 1104-1938 uart-1: 52 2352-3186 uart-1: 45 3600-4434 uart-1: 41 input_format=vcd:downsample=2 diagnostics="" 552-970 uart-1: 52 1176-1594 uart-1: 45 1800-2218 uart-1: 41 ``` The script emits `summary.json` plus each complete CLI JSON response and each raw producer stdout/stderr in its temporary evidence directory. Every response reports three returned lines with no continuation. ## Typed outcome or recovery expected `invalid_request`, with an issue at `["input_format"]` identifying the rejected option and preserving sigrok's warning and fallback output in `captured_output`. Correcting the spelling is a demonstrated recovery. ## Analysis or suspected cause Source-derived: [the selector classifier](https://github.com/morluto/rea/blob/d78892eb4ba41cf8746e5410b3ddf6e7f0ba524f/src/signals/sigrokSelectorRejection.ts#L102-L109) handles an unknown input-module ID but does not recognize input-format option refusals. The [executor](https://github.com/morluto/rea/blob/d78892eb4ba41cf8746e5410b3ddf6e7f0ba524f/src/signals/SigrokRecordingProvider.ts#L260-L284) accepts unmatched exit-zero output, and [report construction](https://github.com/morluto/rea/blob/d78892eb4ba41cf8746e5410b3ddf6e7f0ba524f/src/signals/SigrokRecordingProvider.ts#L374-L391) retains the caller's rejected option in a successful result. ## Additional context Related closed reports cover different refusals: [#6631](https://github.com/morluto/rea/issues/6631) covered selected-channel fallback, [#6766](https://github.com/morluto/rea/issues/6766) covered a nonzero unknown input-module refusal, and [#5515](https://github.com/morluto/rea/issues/5515) covered generic nonzero decoder/annotation errors. No matching unknown input-format **option** success report was found among the supplied 1,082 open issue bodies or the relevant open/closed GitHub search results. The invalid option is deliberate; the recording itself is valid. No hardware acquisition or target execution occurred. This report is specific to refusal handling, not a request to validate protocol semantics independently. ## Triage assessment - Impact level: P2 - Assessment status: supported - Impact basis: Real public CLI calls return successful observed Evidence containing an explicitly rejected preprocessing option and ranges produced with default preprocessing; the working option control produces different ranges. - Workaround status: available - Workaround basis: The executed `vcd:downsample=2` control applies downsampling and retains the same three UART bytes, with empty diagnostics and an unchanged source digest.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#4213
No description provided.