Fix bidirectional RF-001A coordination

This commit is contained in:
LittleSam129
2026-09-21 18:05:28 +03:00
parent bc55d0516d
commit a516b99015
17 changed files with 1745 additions and 244 deletions

View File

@@ -1,7 +1,7 @@
TASK_ID = RF001A_OTA_CAPTURE_002
TASK_ID = RF001A_OTA_CAPTURE_003
TASK_TITLE = Start coordinated RF-001A common-clock OTA capture
SOURCE_BRANCH = video-development
EXPECTED_HEAD = 34f5ace54540f947311ada7e81a463c08abfd2d3
EXPECTED_HEAD = bc55d0516dc1ee36cefa1ff096e7d86f60645303
REQUIRES_APPROVAL = YES
HARDWARE_ACCESS = YES
RF_RX = YES
@@ -11,7 +11,7 @@ ALLOW_PUSH = RX_RESULT_ONLY
RISK_LEVEL = YELLOW
This file is the authoritative current task for the RX laptop.
The RX assistant must read RX_NODE_STATE.md first, then execute this file.
The RX assistant must read RX_NODE_STATE.md and RF_HANDOFF_CHANNEL_TEST.md first, then execute this file.
If chat history conflicts with these files, the shared RF-001A spec or current Git HEAD, stop and report the conflict.
# Current RX task
@@ -19,13 +19,25 @@ If chat history conflicts with these files, the shared RF-001A spec or current G
TASK =
START COORDINATED RF-001A COMMON-CLOCK OTA CAPTURE
This is a new YELLOW task. Do not reuse approval or completion state from RF001A_OTA_CAPTURE_001 or any earlier task.
This is a new YELLOW task. Do not reuse approval or completion state from RF001A_OTA_CAPTURE_001, RF001A_OTA_CAPTURE_002 or any earlier task.
One approval for this task covers its read-only checks, bounded RF RX capture, and publication of the two small allowlisted result files to the existing rx-node branch. It never authorizes RF TX.
One approval for this task covers the model-free channel test, read-only checks, one bounded RF RX capture and publication of the two small allowlisted result files to the existing rx-node branch. It never authorizes RF TX.
## 1. Preconditions
The preceding RF001A_OTA_CAPTURE_002 produced a real 120.1413067 s, 1920000000-byte capture with CLKIN detected before and after, zero reported overruns and SHA256 99a3e7cb51d3ad4e3b914b63da0695ed6658aa169110c32659aba995b542fa0c. Its READY publication failed, its exact child exit code was not recorded and no synchronized truth metadata was available. Treat that as historical evidence only.
After the user chooses [A] Approve for this exact TASK_ID:
## 1. Model-free handoff channel gate
After the user chooses [A] Approve for this exact TASK_ID, run before any hardware access:
python -B tools/rf_handoff_smoke.py --hold-seconds 7
This subtask uses TASK_ID RF_HANDOFF_CHANNEL_TEST_001. It accesses no SDR and keeps RF TX = 0. It publishes fake READY and COMPLETE results through the real origin of the main RX checkout. Require the MAIN watcher to observe both states, require confirmed remote commit hashes, and require COMPLETE to remain as the final origin/rx-node state. Require the MAIN watcher state to contain both channel_test_ready_digest and channel_test_complete_digest for this run.
If any channel check or publication fails, do not access HackRF, do not start capture and STOP with RF TX = 0.
## 2. Preconditions
After the same [A] approval for this exact TASK_ID and after the channel gate passes:
1. Run git pull --ff-only origin video-development.
2. Require branch video-development.
@@ -33,7 +45,7 @@ After the user chooses [A] Approve for this exact TASK_ID:
4. Require local HEAD to equal origin/video-development and EXPECTED_HEAD to be its ancestor.
5. Run hackrf_info and preserve complete stdout.
6. Require RX serial 000000000000000097c063dc2e5327d3.
7. Confirm that configs/rf/rf001a_common_clock.json exists and matches the fixed RF-001A frequency, sample rate, modulation and common-clock plan.
7. Confirm that configs/rf/rf001a_common_clock.json and configs/rf/rf001a_truth_metadata.json exist. Require the tracked truth metadata test ID, seed, 100 packet hashes, sequence numbers, waveform SHA256 and PHY settings to agree with the shared RF-001A spec.
8. With MASTER CLKOUT physically connected to SLAVE CLKIN, run:
hackrf_clock.exe -d 000000000000000097c063dc2e5327d3 -i
@@ -42,13 +54,13 @@ After the user chooses [A] Approve for this exact TASK_ID:
CLKIN status: clock signal detected
10. Require the remote branch origin/rx-node to exist. Do not create it in this task.
10. Require the remote branch origin/rx-node to exist through both git fetch origin rx-node and git ls-remote --heads origin rx-node. Do not create it in this task.
11. Create a unique capture directory outside Git. Refuse an existing directory and never overwrite an earlier capture.
12. Require at least 2500000000 free bytes on the target volume. Record the measured free-byte count.
If any condition fails, do not start capture. Report the failed condition, keep RF TX = 0, and STOP.
## 2. Authorized bounded RX capture
## 3. Authorized bounded RX capture
Only this finite receive operation is authorized:
@@ -80,16 +92,16 @@ The hackrf_transfer.exe arguments are fixed:
Do not add repeat mode. Do not change gain, clock, frequency or sample rate.
## 3. Background runner and durable evidence
## 4. Background runner and durable evidence
Use a local runner outside Git. The runner must:
Use the tracked tools/capture_process_runner.ps1. Write the exact hackrf_transfer argument array to a unique local JSON file outside Git. Start the runner itself as a controlled background PowerShell process and pass unique paths for the capture, complete stdout, complete stderr, capture_started.json and capture_result.json. The runner must:
- start exactly one hackrf_transfer child;
- record its PID and start time immediately;
- record the expected end time as start time plus 120 seconds;
- redirect complete stdout and stderr to unique local files;
- wait for that exact child;
- record its exact exit code, finish time, duration and output size;
- wait for that exact child, refresh its process object and read ExitCode only after WaitForExit;
- record its exact integer exit code, finish time, duration and output size;
- calculate SHA256 after the file is closed;
- write started and final JSON atomically;
- never start a second capture automatically.
@@ -103,13 +115,13 @@ Start the runner as a controlled background process. Within five seconds, requir
If any fact is false, publish no READY state. Preserve local evidence, report RX CAPTURE STARTED = NO, keep RF TX = 0, and STOP.
## 4. Publish READY immediately
## 5. Publish READY immediately
As soon as the child is alive and the file is growing, build a local JSON file outside Git with every field below:
{
"schema_version": 1,
"task_id": "RF001A_OTA_CAPTURE_002",
"task_id": "RF001A_OTA_CAPTURE_003",
"state": "RX_READY_FOR_TX",
"rx_ready_for_tx": true,
"process_alive": true,
@@ -142,14 +154,16 @@ The publisher may change only:
docs/handoff/results/RX_RESULT.json
docs/handoff/results/RX_RESULT.md
It uses a temporary checkout of the existing rx-node branch. It must not switch the source checkout, push video-development, or add raw IQ, video, waveform or large logs.
The publisher checks origin/rx-node through the real origin of the main checkout. It creates a temporary Git worktree from that repository, stages exactly the two allowlisted result paths, pushes HEAD:rx-node, confirms the new remote HEAD with git ls-remote and removes the worktree. It must not switch the source checkout, push video-development, or add raw IQ, video, waveform or large logs.
If publication fails, show the complete error. Do not claim RX READY FOR TX and do not trigger TX automatically. The finite capture may finish normally, but RF TX remains 0.
Use this order without exception: verify CLKIN, start capture, verify the child is alive, verify the file is growing, publish READY, verify RX RESULT REMOTE HEAD equals the published commit, and only then report RX READY FOR TX = YES.
If publication or remote confirmation fails, show the complete error and report RX READY FOR TX = NO. The finite capture may finish normally, but TX coordination does not start and RF TX remains 0.
After successful publication, immediately return control to the user without waiting for capture completion:
TASK_ID =
RF001A_OTA_CAPTURE_002
RF001A_OTA_CAPTURE_003
RX CAPTURE STARTED =
YES
@@ -168,14 +182,16 @@ After successful publication, immediately return control to the user without wai
RX RESULT PUBLISH =
PASS
RX RESULT REMOTE HEAD =
RF TX =
0
The READY state describes a live receiver only. It does not authorize RF TX on either computer.
## 5. Capture completion
## 6. Capture completion
After the child exits, read the durable final status. Preserve all local logs and inspect every line for RX overruns, USB errors and file write errors. Do not infer zero when a tool did not report a counter; use JSON null and report NOT_REPORTED.
After the child exits, read capture_result.json and require an integer exit_code recorded by the runner. Preserve all local logs and inspect every line for RX overruns, USB errors and file write errors. Do not infer zero when a tool did not report a counter; use JSON null and report NOT_REPORTED.
Run the read-only clock check again:
@@ -193,7 +209,7 @@ Require CLKIN to remain detected. The expected complete capture has:
A shorter file is incomplete. Any reported nonzero error remains evidence. Do not retry automatically.
## 6. Publish final result
## 7. Publish final result
Build a second local JSON document with the same identity and timing fields. Set:
@@ -219,17 +235,17 @@ Publish with:
python -B tools/rx_agent_watcher.py --publish-result <final-json-path> --result-branch rx-node
If the push fails, preserve the local JSON and logs, report the error, and do not claim publication success.
Require RX RESULT REMOTE HEAD to equal the final result commit. If the push or confirmation fails, preserve the local JSON and logs, report the error, and do not claim publication success.
## 7. Offline decode
## 8. Offline decode
Do not start automatic decode unless synchronized truth metadata for this exact transmitted waveform is available and consistent. If it is absent, preserve the IQ file and report:
Use the tracked synchronized truth artifact without copying TX files manually:
OFFLINE DECODE = BLOCKED_BY_TRUTH_METADATA
python -B -X utf8 tools/qpsk_rf_rx.py --iq-file <capture-path> --iq-format cs8 --truth-metadata configs/rf/rf001a_truth_metadata.json --report <unique-path-outside-git>\rf001a_ota_decode.json
Do not modify the receiver or invent results.
Report exact packets, missing packets, duplicates, BER and post-FEC errors from the generated report. Preserve a decoder failure as evidence; do not modify the receiver or invent results.
## 8. Prohibited actions
## 9. Prohibited actions
On the RX laptop, do not perform:
@@ -244,10 +260,12 @@ On the RX laptop, do not perform:
- creation of rx-node;
- publication of raw IQ, video, waveform or large logs.
## 9. Final RX report
## 10. Final RX report
TASK_ID =
RF001A_OTA_CAPTURE_002
RF001A_OTA_CAPTURE_003
CHANNEL TEST =
RX CAPTURE COMPLETE =
@@ -271,8 +289,20 @@ On the RX laptop, do not perform:
FINAL RX RESULT PUBLISH =
FINAL RX RESULT REMOTE HEAD =
OFFLINE DECODE =
EXACT PACKETS =
MISSING =
DUPLICATES =
BER =
POST-FEC ERRORS =
RF TX =
0

View File

@@ -1,7 +1,7 @@
TASK_ID = RF001A_OTA_TX_002
TASK_ID = RF001A_OTA_TX_003
TASK_TITLE = Transmit one coordinated RF-001A QPSK burst
SOURCE_BRANCH = video-development
EXPECTED_HEAD = 34f5ace54540f947311ada7e81a463c08abfd2d3
EXPECTED_HEAD = bc55d0516dc1ee36cefa1ff096e7d86f60645303
REQUIRES_APPROVAL = YES
HARDWARE_ACCESS = YES
RF_RX = NO
@@ -18,7 +18,7 @@ Read TX_NODE_STATE.md first. If chat history, this file, the shared RF-001A spec
TASK =
TRANSMIT ONE COORDINATED RF-001A QPSK BURST
This is a new RED task. Publication of this file is not permission to transmit. It does not inherit any approval from RF001A_OTA_TX_001, the RX task, a previous watcher prompt or a previous RF run.
This is a new RED task. Publication of this file is not permission to transmit. It does not inherit any approval from RF001A_OTA_TX_001, RF001A_OTA_TX_002, the RX task, a previous watcher prompt or a previous RF run.
## 1. Coordination gate
@@ -28,7 +28,7 @@ Start the local model-free watcher before the RX task begins:
The watcher polls origin/rx-node every 5 seconds and accepts only a strict result for:
RX TASK_ID = RF001A_OTA_CAPTURE_002
RX TASK_ID = RF001A_OTA_CAPTURE_003
STATE = RX_READY_FOR_TX
RX_READY_FOR_TX = YES
PROCESS_ALIVE = YES
@@ -36,7 +36,7 @@ The watcher polls origin/rx-node every 5 seconds and accepts only a strict resul
COMMON_CLOCK_VERIFIED = YES
RF_TX = 0
The READY publication must be no more than 30 seconds old. Its planned capture window must be 120 seconds. At least 10 seconds must remain.
The watcher records RF_HANDOFF_CHANNEL_TEST_001 READY and COMPLETE states without prompting for RED approval and never treats them as production readiness. A production READY publication must be no more than 30 seconds old. Its planned capture window must be 120 seconds. At least 10 seconds must remain. COMMON_CLOCK_VERIFIED must be YES, source_head must equal the expected video-development HEAD, and the result commit must be the current valid origin/rx-node HEAD.
When a valid new READY result appears, the watcher prints:
@@ -47,7 +47,7 @@ When a valid new READY result appears, the watcher prints:
CAPTURE END EXPECTED =
TX TASK =
RF001A_OTA_TX_002
RF001A_OTA_TX_003
RF TX REQUIRES RED APPROVAL
@@ -56,7 +56,7 @@ When a valid new READY result appears, the watcher prints:
Without the exact phrase AUTHORIZE RF TX, do not transmit.
After the phrase, the watcher fetches rx-node again and repeats all READY, freshness and capture-window checks. If the result changed or less than 10 seconds remain, it refuses authorization and requires a new RX capture.
After the phrase, the watcher fetches rx-node again and repeats the result commit, digest, task ID, source HEAD, READY, freshness, common-clock and capture-window checks. If the result changed or less than 10 seconds remain, it refuses authorization and requires a new RX capture.
The watcher never opens HackRF hardware. On success it writes one local authorization record under:
@@ -103,7 +103,7 @@ Before the watcher can request RED approval:
10. Verify the actual cs8 bytes through the existing offline receiver:
python -B -X utf8 tools/qpsk_rf_rx.py --iq-file data/raw/rf001/rf001a_common_clock_001/tx_waveform.cs8 --iq-format cs8 --truth-metadata data/raw/rf001/rf001a_common_clock_001/tx_metadata.json --report <new-path-outside-git>\rf001a_tx_offline_verify.json
python -B -X utf8 tools/qpsk_rf_rx.py --iq-file data/raw/rf001/rf001a_common_clock_001/tx_waveform.cs8 --iq-format cs8 --truth-metadata configs/rf/rf001a_truth_metadata.json --report <new-path-outside-git>\rf001a_tx_offline_verify.json
Require:
@@ -112,7 +112,7 @@ Require:
offline hardware-format roundtrip = 100/100 exact
TX waveform path = data/raw/rf001/rf001a_common_clock_001/tx_waveform.cs8
TX waveform size = 17667656 bytes
TX waveform SHA256 = 75f3fa39bfa88b623b7e4e5a02e3a80b52185007397ea56ceaacc13353974b2
TX waveform SHA256 = 75f3fa39bfa4ee419f0676c54e4b9c486d7ff64bd0fe2fc06343b20c9d117ecd
sample count = 8833828 complex samples
expected duration = 1.1042285 s
@@ -122,9 +122,9 @@ If any value differs, do not transmit.
The controlled TX executor must read the authorization record immediately before TX and require:
- task_id = RF001A_OTA_TX_002;
- task_id = RF001A_OTA_TX_003;
- authorization = AUTHORIZE RF TX;
- rx_task_id = RF001A_OTA_CAPTURE_002;
- rx_task_id = RF001A_OTA_CAPTURE_003;
- dry_run = false;
- action = MANUAL_EXECUTOR_GATE_OPEN;
- rf_tx_started = false;
@@ -165,7 +165,7 @@ Set RF TX STOPPED = YES only after the finite process exits. Never retry automat
## 6. Final TX report
TASK_ID =
RF001A_OTA_TX_002
RF001A_OTA_TX_003
RED AUTHORIZATION =
AUTHORIZE RF TX / MISSING

View File

@@ -0,0 +1,41 @@
TASK_ID = RF_HANDOFF_CHANNEL_TEST_001
TASK_TITLE = Verify the bidirectional Git handoff channel without SDR
SOURCE_BRANCH = video-development
EXPECTED_HEAD = bc55d0516dc1ee36cefa1ff096e7d86f60645303
REQUIRES_APPROVAL = NO
HARDWARE_ACCESS = NO
RF_RX = NO
RF_TX = NO
ALLOW_COMMIT = NO
ALLOW_PUSH = RX_RESULT_ONLY
RISK_LEVEL = GREEN
# Model-free handoff channel test
Run this gate before RF001A_OTA_CAPTURE_003 and while the MAIN watcher is active:
python -B tools/rf_handoff_smoke.py --hold-seconds 7
The script uses the real origin configured in the main RX checkout. It publishes a synthetic READY result to the existing origin/rx-node branch, waits seven seconds, then publishes a synthetic COMPLETE result. Both publications may change only:
docs/handoff/results/RX_RESULT.json
docs/handoff/results/RX_RESULT.md
The MAIN watcher must observe both states and must not request RED approval. Its state file must record both channel_test_ready_digest and channel_test_complete_digest. The final remote state must be CAPTURE_COMPLETE, with rx_ready_for_tx false. A synthetic READY state must not remain active.
The script and watcher do not access SDR hardware, do not run hackrf_transfer and do not authorize any RF transmission.
Required report:
TASK_ID = RF_HANDOFF_CHANNEL_TEST_001
READY PUBLISH = PASS / FAIL
READY REMOTE HEAD =
MAIN WATCHER RECEIVES READY = PASS / FAIL
COMPLETE PUBLISH = PASS / FAIL
COMPLETE REMOTE HEAD =
MAIN WATCHER RECEIVES COMPLETE = PASS / FAIL
FINAL REMOTE STATE = CAPTURE_COMPLETE / OTHER
REAL RX = NOT_RUN
RF TX = 0
If any field fails, do not start the hardware RX task. STOP.

View File

@@ -10,7 +10,7 @@ The reverse result channel uses the separate origin/rx-node branch. The RX lapto
docs/handoff/NEXT_RX_TASK.md starts with these plain key-value fields:
TASK_ID = RF001A_OTA_CAPTURE_002
TASK_ID = RF001A_OTA_CAPTURE_003
TASK_TITLE = Start coordinated RF-001A common-clock OTA capture
SOURCE_BRANCH = video-development
EXPECTED_HEAD = <full 40-character base commit hash>
@@ -69,7 +69,7 @@ After the task actually finishes, mark the local ID complete:
The coordinated RF-001A result is a strict JSON object. Unknown or missing keys are rejected. READY requires:
- task_id = RF001A_OTA_CAPTURE_002;
- task_id = RF001A_OTA_CAPTURE_003;
- state = RX_READY_FOR_TX;
- rx_ready_for_tx, process_alive, file_growing and common_clock_verified all true;
- a positive capture_pid;
@@ -105,12 +105,12 @@ The publisher checks:
6. origin/rx-node already exists;
7. the JSON schema is valid.
It clones only the existing result branch into a temporary directory, writes exactly:
It checks the branch with git fetch and git ls-remote through the real origin configured in the main checkout. It creates a temporary worktree from that repository and writes exactly:
docs/handoff/results/RX_RESULT.json
docs/handoff/results/RX_RESULT.md
It checks the staged path set, commits, and pushes HEAD to origin/rx-node. It never switches the source checkout. It rejects any additional staged path and enforces a 64 KiB limit per result file.
It checks the staged path set, commits, pushes HEAD:rx-node, and confirms that git ls-remote returns the new commit. It then removes the temporary worktree. It never switches the source checkout. It rejects any additional staged path and enforces a 64 KiB limit per result file.
Raw IQ, video, waveform files, large logs, secrets and machine-local state remain outside Git. A missing result branch or failed push is reported and never authorizes TX.
@@ -118,9 +118,9 @@ Raw IQ, video, waveform files, large logs, secrets and machine-local state remai
tools/tx_agent_watcher.py polls origin/rx-node every 5 seconds by default. It does not call an LLM or open SDR hardware.
For a new valid READY result it checks task ID, state, live-process flags, common clock, publication age, planned duration and remaining capture time. It then requests the exact phrase AUTHORIZE RF TX.
For RF_HANDOFF_CHANNEL_TEST_001 the watcher records synthetic READY and COMPLETE states without requesting RED approval. For a new valid production READY result it checks task ID, source HEAD, current result branch commit, state, live-process flags, common clock, publication age, planned duration and remaining capture time. It then requests the exact phrase AUTHORIZE RF TX.
After the phrase, it fetches rx-node again and repeats the checks. A changed result, stale publication or fewer than 10 seconds remaining blocks authorization.
After the phrase, it fetches rx-node again and repeats every check. A changed result or commit, stale publication or fewer than 10 seconds remaining blocks authorization.
A successful gate writes one local authorization record under .local/tx_authorizations/. The record has rf_tx_started = false and rf_tx = 0. The separately controlled TX executor must re-fetch and compare the result commit and digest immediately before opening hardware.
@@ -135,6 +135,8 @@ Duplicate result digests are not shown repeatedly.
- Neither watcher starts RF TX.
- The TX executor remains a separate RED-controlled action.
## Dry-run contract
## Model-free channel and dry-run contracts
The fake coordination tests use a temporary local bare Git repository and branch named rx-node. They publish a fake READY result, detect it, require the exact RED phrase and invoke a fake TX action that writes RF TX = 0. They do not fetch the real remote, access SDR hardware, run hackrf_transfer, create the production rx-node branch or transmit RF.
The preflight channel command publishes synthetic READY and COMPLETE results with TASK_ID RF_HANDOFF_CHANNEL_TEST_001 through the real origin. The MAIN watcher must observe both without a RED prompt, and COMPLETE must be the final remote state. It does not access SDR hardware or run hackrf_transfer.
Automated coordination tests use a temporary local bare Git repository and branch named rx-node. They cover worktree publication, READY, COMPLETE, push failure and watcher reception without accessing the real remote or SDR hardware and without transmitting RF.

View File

@@ -41,11 +41,11 @@ When the task is actually complete, run:
## Publish READY
For RF001A_OTA_CAPTURE_002, create the strict READY JSON outside Git only after the capture child is alive and the file is growing. Then run:
For RF001A_OTA_CAPTURE_003, first complete RF_HANDOFF_CHANNEL_TEST_001. Then create the strict READY JSON outside Git only after CLKIN is detected, the capture child is alive and the file is growing. Run:
python -B tools/rx_agent_watcher.py --publish-result <ready-json-path> --result-branch rx-node
On success, the watcher prints RX RESULT PUBLISH = PUBLISHED or UNCHANGED, the result commit, and RF TX = 0.
On success, the watcher prints RX RESULT PUBLISH = PUBLISHED or UNCHANGED, the result commit, the independently confirmed RX RESULT REMOTE HEAD, and RF TX = 0. Report RX READY FOR TX = YES only after the remote HEAD equals the result commit.
The command fails if:
@@ -58,6 +58,14 @@ The command fails if:
A failed publication is not READY evidence and cannot authorize TX.
## Model-free channel test
Before hardware access, while the MAIN watcher is running, execute:
python -B tools/rf_handoff_smoke.py --hold-seconds 7
It publishes synthetic READY and COMPLETE results through the real origin. Require the MAIN watcher to observe both and require COMPLETE as the final remote state. Failure blocks the hardware capture. REAL RX remains NOT_RUN and RF TX remains 0.
## Publish COMPLETE
After capture completion, build the strict final JSON with state CAPTURE_COMPLETE and publish it with the same command:

View File

@@ -6,7 +6,7 @@ The publisher creates both docs/handoff/results/RX_RESULT.json and docs/handoff/
{
"schema_version": 1,
"task_id": "RF001A_OTA_CAPTURE_002",
"task_id": "RF001A_OTA_CAPTURE_003",
"state": "RX_READY_FOR_TX",
"rx_ready_for_tx": true,
"process_alive": true,

View File

@@ -14,17 +14,19 @@ The visible console polls every 5 seconds. A missing rx-node branch or invalid r
## READY and RED approval
For a fresh valid RF001A_OTA_CAPTURE_002 READY result, the watcher prints the task ID, capture start and expected end. It then asks for:
RF_HANDOFF_CHANNEL_TEST_001 READY and COMPLETE states are recorded without a RED prompt and never open the production gate.
For a fresh valid RF001A_OTA_CAPTURE_003 READY result, the watcher checks the expected source HEAD, current origin/rx-node commit, common clock and capture window. It then prints the task ID, capture start and expected end and asks for:
AUTHORIZE RF TX
Any other input blocks authorization.
After the exact phrase, the watcher fetches rx-node again. It requires the same result digest, a fresh publication, an active 120-second capture window and at least 10 seconds remaining.
After the exact phrase, the watcher fetches rx-node again. It requires the same result digest and commit, the expected source HEAD, a fresh publication, an active 120-second capture window, verified common clock and at least 10 seconds remaining.
On success it writes:
.local\tx_authorizations\RF001A_OTA_TX_002_authorized.json
.local\tx_authorizations\RF001A_OTA_TX_003_authorized.json
This record opens the gate for the separately controlled TX executor. The watcher itself does not run hackrf_transfer. The record explicitly says rf_tx_started = false and rf_tx = 0.