diff --git a/docs/reviews/binary-payload-broadcast-summary.md b/docs/reviews/binary-payload-broadcast-summary.md new file mode 100644 index 0000000..aabbbc3 --- /dev/null +++ b/docs/reviews/binary-payload-broadcast-summary.md @@ -0,0 +1,84 @@ +# Architect Review — binary-payload-broadcast + +**Reviewed commits** +- `62e0479` Specify binary hash160 broadcast payload for follow/mute (specifier) +- `5e1f473` Broadcast follow/mute/unfollow/unmute hash160 payloads as Uint8Array (coder) +- `53f6b90` / `8d40f2b` Deduplicate Memo follow/mute onto shared MemoStateAction base (refactorer) + +**Task**: Follow/unfollow and mute/unmute must broadcast the target's raw +20-byte hash160 (not its display-form cash address text) as the OP_RETURN +payload, and must not depend on the Node-only `Buffer` global. The refactorer +also consolidated the previously near-identical `MemoFollow`/`MemoMute` action +classes onto a shared `MemoStateAction` base. + +## Architectural findings + +**UI/Core separation — good.** The follow/mute services remain pure logic over +injected `wallet`/`profiles` adapters. No UI, framework, or IO leaked into the +core; everything network/UI-specific stays behind the small wallet/profile +adapter boundaries. The modules remain fully testable without launching a UI or +network. + +**Dependency rule — good.** `MemoFollow`/`MemoMute` → `MemoStateAction` → +`MemoAction` → `hex`/`utf8`. High-level action semantics depend on low-level +byte/hex helpers through a stable base; the direction is inward. The base +subclasses (`MemoFollow`, `MemoMute`) are thin facades exposing only +`follow`/`unfollow` and `mute`/`unmute`. + +**Information hiding / encapsulation — good.** `MemoStateAction` encapsulates +the shared validate → toHash160 → hexToBytes → sendOpReturn → reflect +transition. Subclasses expose only config (`followConfig`/`muteConfig`) and +their two public methods; static exports and the public API are preserved. The +20-byte length lives on the base as `PK_HASH_LENGTH`, surfaced on both +subclasses. + +**Local code quality.** The refactor is a clean DRY extraction (~150 duplicate +lines removed). Two minor, pre-existing notes (not requiring change): +- Each config carries a `prefix` key that is unused (state actions pass the + prefix explicitly to `_setState`); harmless dead config carried over from the + prior `MemoAction` contract. +- `MemoStateAction.validate` throws a typed error whereas the base + `MemoAction.validate` returns `{ok:false}`; contract difference is intended + and documented in the module header. + +No architectural changes were required. The merged work confirms and preserves +the earlier binary `Uint8Array` broadcast behavior (`hexToBytes`) and the +Buffer-free production source. + +## Verification + +All per-component verification for `psf-memo-client` (the only touched +component) passed. + +- **Unit tests**: 280/280 pass +- **Property tests**: 40/40 pass (run separately, as required) +- **Lint**: clean (`standard`) +- **Acceptance**: all 23 generated feature suites pass, including the new + `binary-payload-broadcast.feature` +- **Language mutation** (`mutate4javascript --mutate-all --max-workers 8`): + - `memo-follow.js`: 2 killed, 0 survived, 0 uncovered + - `memo-mute.js`: 2 killed, 0 survived, 0 uncovered + - `memo-state-action.js` (new base): 5 killed, 0 survived, 0 uncovered + - `hex.js`/`memo-action.js` unchanged and unaffected. +- **DRY** (`dry4javascript`) on `memo-state-action.js`/`memo-follow.js`/ + `memo-mute.js`: no duplicate candidates. + +## Soft Gherkin acceptance mutation (survivors) + +`gherkin-mutator --level soft` over `binary-payload-broadcast.feature`: +4 mutations run, 4 survived, 0 killed, 0 errors. All four are single-character +**case** mutations of the shared example cash address (`q`→`Q`, `x`→`X`, +`c`→`C`, `k`→`K`). Each mutated example is applied consistently on both the +broadcast setup side and the assertion side of its scenario, so the scenario +still passes under the mutation (both sides derive from the same mutated +value). These are **genuine intrinsic equivalents**, not implementation gaps, +and match the documented read-only-payload survivor pattern. Documented here; +no chase performed. + +## Handoffs + +No functional handoffs sent. The work was a review of an already-functional +refactorer merge; my branch adds only the durable review summary plus the +tool-refreshed manifests (mutation `tested_at` timestamps and the new +acceptance-mutation manifest) from the verification runs. Function and module +hashes in the mutation manifests are unchanged, confirming no code drift. diff --git a/psf-memo-client/specs/binary-payload-broadcast.feature b/psf-memo-client/specs/binary-payload-broadcast.feature index c21cea9..49209b3 100644 --- a/psf-memo-client/specs/binary-payload-broadcast.feature +++ b/psf-memo-client/specs/binary-payload-broadcast.feature @@ -1,3 +1,7 @@ +# acceptance-mutation-manifest-begin +# {"version":1,"tested_at":"2026-09-04T18:29:23.460506768Z","feature_name":"Binary Payload Broadcast","feature_path":"../../psf-memo-client/specs/binary-payload-broadcast.feature","background_hash":"e1d5f81f1ed083ac6934c429ca3cb4a0f8d4dac44c2eaa45c0960920bde2c017","implementation_hash":"unknown","scenarios":[]} +# acceptance-mutation-manifest-end + # Scenarios: Binary Payload Broadcast - 1, Binary Payload Broadcast - 2, Binary Payload Broadcast - 3, Binary Payload Broadcast - 4 # # Follow/unfollow and mute/unmute broadcast an OP_RETURN whose payload is the diff --git a/psf-memo-client/src/services/memo-follow.js b/psf-memo-client/src/services/memo-follow.js index 0f5e18a..5f7f069 100644 --- a/psf-memo-client/src/services/memo-follow.js +++ b/psf-memo-client/src/services/memo-follow.js @@ -55,5 +55,5 @@ MemoFollow.PK_HASH_LENGTH = MemoStateAction.PK_HASH_LENGTH module.exports = MemoFollow // mutate4javascript-manifest-begin -// {"version":1,"tested_at":"2026-09-04T18:22:54.857Z","module_hash":"cc36dc8387a54f50e91393bcb10382515e66a0cfc74fb4f51412fac4df580136","functions":[{"id":"func/followConfig","name":"followConfig","line":25,"end_line":35,"hash":"6843e108f72a95c18267d1ecea29fdfbdb0814ce4b5e3672f22df612e6ecaccd"},{"id":"func/MemoFollow.follow","name":"MemoFollow.follow","line":41,"end_line":43,"hash":"82635824a8e221e587b74b22fea454d2f661e599d8c9b9d0d7ec7c14e214cbd7"},{"id":"func/MemoFollow.unfollow","name":"MemoFollow.unfollow","line":46,"end_line":48,"hash":"fa99fa77d68a875092f5aad8323dca42de06dff49af469c6d7db94f7033399b5"}]} +// {"version":1,"tested_at":"2026-09-04T18:27:18.336Z","module_hash":"cc36dc8387a54f50e91393bcb10382515e66a0cfc74fb4f51412fac4df580136","functions":[{"id":"func/followConfig","name":"followConfig","line":25,"end_line":35,"hash":"6843e108f72a95c18267d1ecea29fdfbdb0814ce4b5e3672f22df612e6ecaccd"},{"id":"func/MemoFollow.follow","name":"MemoFollow.follow","line":41,"end_line":43,"hash":"82635824a8e221e587b74b22fea454d2f661e599d8c9b9d0d7ec7c14e214cbd7"},{"id":"func/MemoFollow.unfollow","name":"MemoFollow.unfollow","line":46,"end_line":48,"hash":"fa99fa77d68a875092f5aad8323dca42de06dff49af469c6d7db94f7033399b5"}]} // mutate4javascript-manifest-end diff --git a/psf-memo-client/src/services/memo-mute.js b/psf-memo-client/src/services/memo-mute.js index 88ada11..d8f7f20 100644 --- a/psf-memo-client/src/services/memo-mute.js +++ b/psf-memo-client/src/services/memo-mute.js @@ -55,5 +55,5 @@ MemoMute.PK_HASH_LENGTH = MemoStateAction.PK_HASH_LENGTH module.exports = MemoMute // mutate4javascript-manifest-begin -// {"version":1,"tested_at":"2026-09-04T18:22:55.701Z","module_hash":"05cb4f14173db9584533e58f931d775a2505d844f53bd29724935c3de083c40b","functions":[{"id":"func/muteConfig","name":"muteConfig","line":25,"end_line":35,"hash":"f05b5b03c441735322cdb433417ae5351626ed4c7d1db0a94cbf350f6818a605"},{"id":"func/MemoMute.mute","name":"MemoMute.mute","line":41,"end_line":43,"hash":"ad4c0ae3933e732017a59814bc6b9f6e7d263669f59440f1010209403ce0066f"},{"id":"func/MemoMute.unmute","name":"MemoMute.unmute","line":46,"end_line":48,"hash":"758b2676537ce5861f94ca9efff5b98a946bd82c023964e4d0c40f90d0a1261a"}]} +// {"version":1,"tested_at":"2026-09-04T18:27:53.801Z","module_hash":"05cb4f14173db9584533e58f931d775a2505d844f53bd29724935c3de083c40b","functions":[{"id":"func/muteConfig","name":"muteConfig","line":25,"end_line":35,"hash":"f05b5b03c441735322cdb433417ae5351626ed4c7d1db0a94cbf350f6818a605"},{"id":"func/MemoMute.mute","name":"MemoMute.mute","line":41,"end_line":43,"hash":"ad4c0ae3933e732017a59814bc6b9f6e7d263669f59440f1010209403ce0066f"},{"id":"func/MemoMute.unmute","name":"MemoMute.unmute","line":46,"end_line":48,"hash":"758b2676537ce5861f94ca9efff5b98a946bd82c023964e4d0c40f90d0a1261a"}]} // mutate4javascript-manifest-end diff --git a/psf-memo-client/src/services/memo-state-action.js b/psf-memo-client/src/services/memo-state-action.js index e80b02e..7c8cdc2 100644 --- a/psf-memo-client/src/services/memo-state-action.js +++ b/psf-memo-client/src/services/memo-state-action.js @@ -96,5 +96,5 @@ MemoStateAction.PK_HASH_LENGTH = PK_HASH_LENGTH module.exports = MemoStateAction // mutate4javascript-manifest-begin -// {"version":1,"tested_at":"2026-09-04T18:22:56.557Z","module_hash":"938e453562e0258b383cbc31f449fccc2e6105c805c97c52fb13bef3ee0fe6b4","functions":[{"id":"func/MemoStateAction.constructor","name":"MemoStateAction.constructor","line":25,"end_line":28,"hash":"9407b43605444074011b1da595c9d53356352c72b0d847e00365d79ad705663a"},{"id":"func/MemoStateAction.validate","name":"MemoStateAction.validate","line":32,"end_line":47,"hash":"4154af442c36324ba147dff352c55610397fb430c9b62fbbe44728ab0e379179"},{"id":"func/MemoStateAction._setState","name":"MemoStateAction._setState","line":51,"end_line":69,"hash":"86b976bcbe8532f7bfb9bdc989976f2266bce3d0e8c3a77a1f3e0b8413951684"},{"id":"func/MemoStateAction._toHash160","name":"MemoStateAction._toHash160","line":73,"end_line":75,"hash":"3ed2ba4853b718a8316345c9f988ed3bed3ef647de3fd9761ee3c9e0bee86070"},{"id":"func/MemoStateAction._ensureBchjs","name":"MemoStateAction._ensureBchjs","line":77,"end_line":81,"hash":"92828d9038c9a4d367e80edd7f129291e434d57aae80b332081a66af9896a5d6"},{"id":"func/MemoStateAction.reflect","name":"MemoStateAction.reflect","line":85,"end_line":91,"hash":"4ef0ab37e0ab08a681475805c5e53a010b35aa56560bc84f6d2189154ed7fa7d"}]} +// {"version":1,"tested_at":"2026-09-04T18:28:28.750Z","module_hash":"938e453562e0258b383cbc31f449fccc2e6105c805c97c52fb13bef3ee0fe6b4","functions":[{"id":"func/MemoStateAction.constructor","name":"MemoStateAction.constructor","line":25,"end_line":28,"hash":"9407b43605444074011b1da595c9d53356352c72b0d847e00365d79ad705663a"},{"id":"func/MemoStateAction.validate","name":"MemoStateAction.validate","line":32,"end_line":47,"hash":"4154af442c36324ba147dff352c55610397fb430c9b62fbbe44728ab0e379179"},{"id":"func/MemoStateAction._setState","name":"MemoStateAction._setState","line":51,"end_line":69,"hash":"86b976bcbe8532f7bfb9bdc989976f2266bce3d0e8c3a77a1f3e0b8413951684"},{"id":"func/MemoStateAction._toHash160","name":"MemoStateAction._toHash160","line":73,"end_line":75,"hash":"3ed2ba4853b718a8316345c9f988ed3bed3ef647de3fd9761ee3c9e0bee86070"},{"id":"func/MemoStateAction._ensureBchjs","name":"MemoStateAction._ensureBchjs","line":77,"end_line":81,"hash":"92828d9038c9a4d367e80edd7f129291e434d57aae80b332081a66af9896a5d6"},{"id":"func/MemoStateAction.reflect","name":"MemoStateAction.reflect","line":85,"end_line":91,"hash":"4ef0ab37e0ab08a681475805c5e53a010b35aa56560bc84f6d2189154ed7fa7d"}]} // mutate4javascript-manifest-end