Repository navigation
feat(validator): add the unpaired, pairing_psk and post-pairing suites - #164
Merged
chrisuthe merged 3 commits intoOct 14, 2026
Conversation
Walk a client through U1-U4, the pairing_psk walkthrough (P1) and R1-R5 after the opening checks, selected with --suite. U4 sends an activation no client may accept and runs only with --fault-injection. Which PSK keyed each handshake is not in any frame, so the session records it from aiosendspin's ClientConnectedEvent and ClientCredentialMismatchEvent as connection events beside the frames. Pairing runs through the bundled SDK's initiate_pairing, and forgetting and unpairing through its pairing store and unpair, so no new aiosendspin patch is needed. The self-test now drives the bundled client through every suite in both directions, and shows that a client ignoring server/unpair, or answering U4 with the wrong goodbye reason, fails.
… before redialing POSTPAIR-006 now passes only when the delay the validator commanded and the client reported is reported again after the restart; a client whose delay could not be set is N/A rather than passing on matching defaults. A step waiting for the user to change a setting reconnects once and then asks before each further attempt, instead of redialing every second. U1 puts back a pairing an earlier run left once the unpaired steps are done, and the restart prompt carries its own [R5 restart] tag.
chrisuthe
marked this pull request as ready for review
October 14, 2026 23:56
This was referenced Oct 15, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Phase 2a of
sendspin-validate: the unpaired,pairing_pskand post-pairing suites (validator spec §5.3, §5.4 P1, §5.5). Part of Sendspin/backlog#84.What it does
After the opening checks,
--suite unpaired,pairing,post-pairing(default: all three, in that order) walks the user through:UNPAIRED-001UNPAIRED-002['playback']activation getsclient/goodbye pairing_required(M:251-253)UNPAIRED-004--fault-injection; N/A without it. An inadmissible activation (['playback','pairing'], methodpairing_psk, Sentinel session) is rejected per the rule orderUNPAIRED-003client/statefollowsPAIR-PSK-001…007client_id; re-handshake ontopr;client/pair-init(index, nocommit_B);client/pair-finalize{long_term_psk}; re-handshake tolt; the paired activation is acceptedPOSTPAIR-001server_id, C:121) is covered by the same checkPOSTPAIR-002POSTPAIR-005,-006set_output_delayand echoes it; otherwise N/A)POSTPAIR-003,-004server/unpairgetsclient/goodbye unpaired. The validator then references the old record again: a client that really dropped it falls back to the Sentinel, and one that kept it matchesltand FAILsU4 runs before U3 because it needs unpaired access off. R5 runs before R4 because it needs the pairing. Every step prompts through the
Prompter(skip, retry, not sure). U2, U3 and R5 rest on something the user did, so their findings carryevidence: "human". The opening checks still judge only the first connection, but a strict-mode rejection on any of the client's connections is now reported.aiosendspin hooks: none needed
I checked whether the spec's planned "externally driven pairing" and "forget client" patches are needed. The bundled aiosendspin 10.0.2 already covers both through public API, so this PR adds no patch:
SendspinServer.initiate_pairing(client_id, PairingAttempt(method=PAIRING_PSK, pairing_psk=…, client_id=…)). It re-handshakes onto the pairing PSK, runs pair-init/finalize, stores the record, re-handshakes to the long-term PSK and re-activates.pairing_store.remove_record/store_record.SendspinServer.unpair.ClientConnectedEvent→connection.psk_categoryand fromClientCredentialMismatchEvent, and records it as connection events next to the frames, so checks stay pure functions. Afterinitiate_pairingit readspsk_categoryagain.SendspinConnection.send_priority_message. That does not cover U4'spair/abort method_not_supportedbranch (unpaired access on). The strict server rejects apair/abortit never activated pairing for, so that branch cannot be reached without the raw-send patch planned for phase 5.The bundled-aiosendspin patch list therefore stays at 0001–0002. The spec's §7/§8 list still names the forget-client and pairing-driver patches; they are not needed for rc1.
Interpretation call: U4's rule order
For the U4 activation with unpaired access off, I read rc1 literally. The pairing method is part of "admissible" (M:247, M:251), and no unpaired access makes
pairing_pskvalid on a Sentinel session. So rule 1 (pairing_required) does not apply, and rule 2'sunauthorizeddoes. Both the bundled aiosendspin client and sendspin-cpp-cli answerpairing_required, deciding rule 1 on the activity set and roles alone. U4 FAILs that, andtest_inadmissible_activationpins the verdict. Either the clients or the spec's wording of rule 1 should change.Tests
tests/test_validator_suite_checks.py: every new check against hand-built frame sequences, pass and fail, including the pairing.md:97-101 token vector.tests/test_validator_suites.py:--suiteparsing. Also checks that the pasted token, and its PSK, are registered for redaction before anything is recorded, typos included.tests/test_validator_selftest.py(runs inci.yml): the real console script with--auto-answer --verbose, against the bundled client in both connection directions, through every suite. A scripted operator reacts to the step tags ([U2],[U3],[R5 restart]). Each run asserts zero FAIL/TOOL_ERROR, and that neither the token, the pairing PSK nor any long-term PSK appears inreport.json,report.html,protocol.log.jsonlor the verbose output. Broken cases: a client that ignoresserver/unpairFAILs POSTPAIR-003 and -004, and the U4 case above FAILs too.python -m unittest discover -s tests: 482 tests pass.scripts/vendor_aiosendspin.py --checkpasses. Nothing outside the validator, tests and README changed, sopublish.ymldoes not run.Manual run against sendspin-cpp-cli
The CLI was built from
main(7501898) on macOS arm64 with-o null, server-initiated over localhost, with--fault-injection. A scripted operator answered the real terminal prompts:--allow-unpairedfor U2/U3;sendspin-cli pair-tokenfor P1;Result: 34 PASS, 1 FAIL, 1 N/A.
Pairing, all post-pairing steps (including output-delay persistence across a real restart) and the unpair round trip pass. The run's report and log contain neither the token nor the pairing PSK.
Not run against the CLI: client-initiated mode (
-s) and mDNS discovery. The self-test covers client-initiated mode against aiosendspin.