Repository navigation
fix: keep Discord decision questions readable after settlement - #256
Merged
Merged
Conversation
Keep the original last chunk beneath a bold outcome, reserve room for settlement headers, and highlight a unique choice on disabled buttons.
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.
Intent
When a Discord decision post is settled, keep its question readable instead of replacing the message text with a single outcome line. After a decision button is clicked, the message is edited and the question disappears; the captain wants to return later and still see both the question and the decision. The captain approved proposal items 1–4; the decision log (item 5) is out of scope. Every outcome state—chosen, chosen by the captain elsewhere, decided without a unique option, waiting to be recorded, closed because it is no longer needed, and replaced—must edit only the last chunk (the chunk carrying the buttons) to put one bold outcome line above that chunk’s original text unchanged; the example header is “✅ Decided: Fix Pergub 33 first (dnth, )”. Buttons stay visible and disabled: the uniquely chosen option is success-styled with a ✓ in its label, while other options and Discuss are grey; when no option is chosen, all buttons are disabled grey. A replaced post’s header links to the newer post when its message link is known, otherwise it says “Replaced by a newer question”. Respect Discord’s 2,000-character limit by trimming only the tail of the original explanation and adding “(trimmed)”; never trim the header, question, or option list, and leave earlier chunks untouched. Preserve the existing write-once settle and finalize-receipt ledger semantics, retries, and guards (plain-text choice refusal, still-open checks, and occurrence matching) unchanged except for the content shape.
What Changed
Risk Assessment
✅ Low: The changes are bounded and preserve settlement semantics while implementing the accepted question-preservation and legacy button-only behavior.
Testing
Both targeted suites and isolated CLI/poster scenarios passed. Captured actual PATCH payloads against a disposable Discord HTTP substitute. No screenshot was captured because this setup has no Discord renderer; Discord appearance was not exercised. Disposable files were removed.
Evidence: Captured product PATCH requests
Evidence: Outcome headers and button states
Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
🔧 **Rebase** - 1 issue found → auto-fixed ✅
docs/configuration.md- merge conflict rebasing onto origin/main🔧 Fix applied.
✅ Re-checked - no issues remain.
🔧 **Review** - 3 issues found → auto-fixed (5) ✅
bin/fm-ext-decision.sh:657- The legacy overflow fallback contradicts “never trim the header, question, or option list.” An existing 1,900-character post plus a long waiting header exceeds 2,000 characters; this path passes the entire original text as the question and deliberately slices it, then appends reconstructed options. Remove this question-trimming fallback and resolve legacy overflow without silently weakening the accepted constraint. Related sites: bin/fm-ext-decision.sh:624 and :641 perform slicing; :657–658 enable it; docs/configuration.md:1022 documents the exception; tests/fm-ext-decision.test.sh:1440–1457 endorses it.contrib/hermes-gateway-firstmate-comms/outbox_poster.py:399- Legacy resume must retain its stored split budget. Here legacy=True still reduces a stored 1,900-character limit to 1,744. A partially posted 3,600-character decision previously split into two chunks can now produce three, making deliver_one return mid-delivery indefinitely because stored total remains two. When counts match, changed boundaries can repeat content. Bypass the new reservation for legacy resumes and pass the original limit unchanged. Related changed call site: contrib/hermes-gateway-firstmate-comms/outbox_poster.py:1830–1835 routes existing progress through this path.contrib/hermes-gateway-firstmate-comms/outbox_poster.py:415- The new structured splitter silently truncates protected question text at the configured chunk cap. With FM_EXT_DISCORD_THREAD_MAX=1, a 1,800-character question plus two short options and the normal cue previously fits the default 1,900-character budget. The new reservation and suffix subtraction reduce the question budget below 1,744; split_discord truncates its tail to an ellipsis, then this function appends options and successfully delivers the incomplete question. Detect cap overflow before publishing and preserve the question, trimming only explanation or refusing an impossible plan. Related changed sites: :398 reserves space, :412 subtracts the suffix, and :418 appends options after truncation.🔧 Fix applied.
3 errors still open:
bin/fm-ext-decision.sh:657- The legacy overflow fallback contradicts “never trim the header, question, or option list.” An existing 1,900-character post plus a long waiting header exceeds 2,000 characters; this path passes the entire original text as the question and deliberately slices it, then appends reconstructed options. Remove this question-trimming fallback and resolve legacy overflow without silently weakening the accepted constraint. Related sites: bin/fm-ext-decision.sh:624 and :641 perform slicing; :657–658 enable it; docs/configuration.md:1022 documents the exception; tests/fm-ext-decision.test.sh:1440–1457 endorses it.bin/fm-ext-decision.sh:664- Completed legacy posts can still lose their questions. Commit 84ec252 introduced persisted texts; older completed progress records lack that field and already have delivery receipts, so they never resume to acquire it. The new.texts[-1] // ""substitutes an empty original, producing and finalizing a header-only edit. This contradicts “above that chunk’s original text unchanged.” Recover the original chunk before editing; never treat missing text as empty. Round 1 fixed legacy resumes but left completed generations uncovered. Related sites: bin/fm-ext-decision.sh:637 accepts the empty original, :683 prepares the replacement; contrib/hermes-gateway-firstmate-comms/outbox_poster.py:1560 publishes it.bin/fm-ext-decision.sh:646- Round 1 replaced legacy overflow truncation with an indefinitely skipped edit:error("legacy decision text cannot safely fit its outcome header"), followed by|| continueat bin/fm-ext-decision.sh:683. A 1,900-character legacy post with a sufficiently long waiting header therefore keeps enabled buttons and receives no outcome header. This contradicts “Every outcome state … must edit” and “Buttons stay visible and disabled.” The prior decision authorized removing question trimming, not omitting settlement display. Resolve the behavior for impossible-to-fit legacy posts explicitly before accepting this fallback. Related sites: docs/configuration.md:1023 documents refusal; tests/fm-ext-decision.test.sh:1451 endorses no edit.🔧 Fix applied.
5 errors still open:
bin/fm-ext-decision.sh:657- The legacy overflow fallback contradicts “never trim the header, question, or option list.” An existing 1,900-character post plus a long waiting header exceeds 2,000 characters; this path passes the entire original text as the question and deliberately slices it, then appends reconstructed options. Remove this question-trimming fallback and resolve legacy overflow without silently weakening the accepted constraint. Related sites: bin/fm-ext-decision.sh:624 and :641 perform slicing; :657–658 enable it; docs/configuration.md:1022 documents the exception; tests/fm-ext-decision.test.sh:1440–1457 endorses it.bin/fm-ext-decision.sh:664- Completed legacy posts can still lose their questions. Commit 84ec252 introduced persisted texts; older completed progress records lack that field and already have delivery receipts, so they never resume to acquire it. The new.texts[-1] // ""substitutes an empty original, producing and finalizing a header-only edit. This contradicts “above that chunk’s original text unchanged.” Recover the original chunk before editing; never treat missing text as empty. Round 1 fixed legacy resumes but left completed generations uncovered. Related sites: bin/fm-ext-decision.sh:637 accepts the empty original, :683 prepares the replacement; contrib/hermes-gateway-firstmate-comms/outbox_poster.py:1560 publishes it.bin/fm-ext-decision.sh:646- Round 1 replaced legacy overflow truncation with an indefinitely skipped edit:error("legacy decision text cannot safely fit its outcome header"), followed by|| continueat bin/fm-ext-decision.sh:683. A 1,900-character legacy post with a sufficiently long waiting header therefore keeps enabled buttons and receives no outcome header. This contradicts “Every outcome state … must edit” and “Buttons stay visible and disabled.” The prior decision authorized removing question trimming, not omitting settlement display. Resolve the behavior for impossible-to-fit legacy posts explicitly before accepting this fallback. Related sites: docs/configuration.md:1023 documents refusal; tests/fm-ext-decision.test.sh:1451 endorses no edit.bin/fm-ext-decision.sh:646- Legacy overflow remains unresolved after rounds 1 and 2. A 1,900-character original plus a long waiting header reaches this error, and command_posts skips the edit. This contradicts “Every outcome state … must edit” and “Buttons stay visible and disabled”: the outcome is absent and buttons remain enabled. Resolve this constraint conflict explicitly; refusing the edit was not the accepted fix. Related sites: bin/fm-ext-decision.sh:687 skips publication; contrib/hermes-gateway-firstmate-comms/outbox_poster.py:1586–1590 repeats the refusal during recovery; docs/configuration.md:1026 documents it; tests/fm-ext-decision.test.sh:1451 endorses it.contrib/hermes-gateway-firstmate-comms/outbox_poster.py:1577- Round 2 leaves already-edited legacy posts behind. A completed legacy post without texts can have a .finalized receipt for its waiting state before upgrade. When Firstmate subsequently records the answer, this new exists() guard permanently skips it, leaving Discord showing “waiting” after the decision is recorded. This contradicts “Every outcome state … must edit” and preserving existing retries. Recover the original last chunk from the retained payload using its stored legacy split budget rather than treating any earlier edit as unrecoverable. Related sites: bin/fm-ext-decision.sh:683–701 emits missing originals for these posts; contrib/hermes-gateway-firstmate-comms/outbox_poster.py:1588–1590 suppresses every subsequent attempt.🔧 Fix applied.
7 errors still open:
bin/fm-ext-decision.sh:657- The legacy overflow fallback contradicts “never trim the header, question, or option list.” An existing 1,900-character post plus a long waiting header exceeds 2,000 characters; this path passes the entire original text as the question and deliberately slices it, then appends reconstructed options. Remove this question-trimming fallback and resolve legacy overflow without silently weakening the accepted constraint. Related sites: bin/fm-ext-decision.sh:624 and :641 perform slicing; :657–658 enable it; docs/configuration.md:1022 documents the exception; tests/fm-ext-decision.test.sh:1440–1457 endorses it.bin/fm-ext-decision.sh:664- Completed legacy posts can still lose their questions. Commit 84ec252 introduced persisted texts; older completed progress records lack that field and already have delivery receipts, so they never resume to acquire it. The new.texts[-1] // ""substitutes an empty original, producing and finalizing a header-only edit. This contradicts “above that chunk’s original text unchanged.” Recover the original chunk before editing; never treat missing text as empty. Round 1 fixed legacy resumes but left completed generations uncovered. Related sites: bin/fm-ext-decision.sh:637 accepts the empty original, :683 prepares the replacement; contrib/hermes-gateway-firstmate-comms/outbox_poster.py:1560 publishes it.bin/fm-ext-decision.sh:646- Round 1 replaced legacy overflow truncation with an indefinitely skipped edit:error("legacy decision text cannot safely fit its outcome header"), followed by|| continueat bin/fm-ext-decision.sh:683. A 1,900-character legacy post with a sufficiently long waiting header therefore keeps enabled buttons and receives no outcome header. This contradicts “Every outcome state … must edit” and “Buttons stay visible and disabled.” The prior decision authorized removing question trimming, not omitting settlement display. Resolve the behavior for impossible-to-fit legacy posts explicitly before accepting this fallback. Related sites: docs/configuration.md:1023 documents refusal; tests/fm-ext-decision.test.sh:1451 endorses no edit.bin/fm-ext-decision.sh:646- Legacy overflow remains unresolved after rounds 1 and 2. A 1,900-character original plus a long waiting header reaches this error, and command_posts skips the edit. This contradicts “Every outcome state … must edit” and “Buttons stay visible and disabled”: the outcome is absent and buttons remain enabled. Resolve this constraint conflict explicitly; refusing the edit was not the accepted fix. Related sites: bin/fm-ext-decision.sh:687 skips publication; contrib/hermes-gateway-firstmate-comms/outbox_poster.py:1586–1590 repeats the refusal during recovery; docs/configuration.md:1026 documents it; tests/fm-ext-decision.test.sh:1451 endorses it.contrib/hermes-gateway-firstmate-comms/outbox_poster.py:1577- Round 2 leaves already-edited legacy posts behind. A completed legacy post without texts can have a .finalized receipt for its waiting state before upgrade. When Firstmate subsequently records the answer, this new exists() guard permanently skips it, leaving Discord showing “waiting” after the decision is recorded. This contradicts “Every outcome state … must edit” and preserving existing retries. Recover the original last chunk from the retained payload using its stored legacy split budget rather than treating any earlier edit as unrecoverable. Related sites: bin/fm-ext-decision.sh:683–701 emits missing originals for these posts; contrib/hermes-gateway-firstmate-comms/outbox_poster.py:1588–1590 suppresses every subsequent attempt.bin/fm-ext-decision.sh:646- Legacy overflow remains unresolved after rounds 1–3. A 1,900-character original plus an 80-character option’s waiting header exceeds 2,000 characters; this error causes command_posts to skip publication, leaving the outcome absent and buttons enabled. This contradicts “Every outcome state … must edit” and “Buttons stay visible and disabled.” Resolve the incompatible legacy constraints explicitly before merging; silent refusal is not the accepted fix. Siblings: bin/fm-ext-decision.sh:687 skips publication; contrib/hermes-gateway-firstmate-comms/outbox_poster.py:1596–1600 repeats the refusal during recovery; docs/configuration.md:1025 documents it; tests/fm-ext-decision.test.sh:1451 endorses it.contrib/hermes-gateway-firstmate-comms/outbox_poster.py:1590- Round 3 leaves previously edited legacy posts without retained payloads permanently stuck. Before upgrade, successful delivery retired the payload; older progress lacks texts, and a waiting edit already created .finalized. After Firstmate records the answer, this guard raises and every reconciliation skips the post, so Discord continues showing “waiting.” New retention cannot restore payloads already deleted. This still contradicts “Every outcome state … must edit” and preserving retries. Establish an explicit migration exception or an available recovery source rather than treating retention as a complete fix. Related changed sites: bin/fm-ext-lib.sh:1512–1516 only retains surviving payloads; outbox_poster.py:1598–1600 suppresses the update; docs/configuration.md:1022–1024 describes the incomplete recovery.🔧 Fix applied.
6 errors still open:
bin/fm-ext-decision.sh:657- The legacy overflow fallback contradicts “never trim the header, question, or option list.” An existing 1,900-character post plus a long waiting header exceeds 2,000 characters; this path passes the entire original text as the question and deliberately slices it, then appends reconstructed options. Remove this question-trimming fallback and resolve legacy overflow without silently weakening the accepted constraint. Related sites: bin/fm-ext-decision.sh:624 and :641 perform slicing; :657–658 enable it; docs/configuration.md:1022 documents the exception; tests/fm-ext-decision.test.sh:1440–1457 endorses it.bin/fm-ext-decision.sh:664- Completed legacy posts can still lose their questions. Commit 84ec252 introduced persisted texts; older completed progress records lack that field and already have delivery receipts, so they never resume to acquire it. The new.texts[-1] // ""substitutes an empty original, producing and finalizing a header-only edit. This contradicts “above that chunk’s original text unchanged.” Recover the original chunk before editing; never treat missing text as empty. Round 1 fixed legacy resumes but left completed generations uncovered. Related sites: bin/fm-ext-decision.sh:637 accepts the empty original, :683 prepares the replacement; contrib/hermes-gateway-firstmate-comms/outbox_poster.py:1560 publishes it.bin/fm-ext-decision.sh:646- Round 1 replaced legacy overflow truncation with an indefinitely skipped edit:error("legacy decision text cannot safely fit its outcome header"), followed by|| continueat bin/fm-ext-decision.sh:683. A 1,900-character legacy post with a sufficiently long waiting header therefore keeps enabled buttons and receives no outcome header. This contradicts “Every outcome state … must edit” and “Buttons stay visible and disabled.” The prior decision authorized removing question trimming, not omitting settlement display. Resolve the behavior for impossible-to-fit legacy posts explicitly before accepting this fallback. Related sites: docs/configuration.md:1023 documents refusal; tests/fm-ext-decision.test.sh:1451 endorses no edit.bin/fm-ext-decision.sh:646- Legacy overflow remains unresolved after rounds 1 and 2. A 1,900-character original plus a long waiting header reaches this error, and command_posts skips the edit. This contradicts “Every outcome state … must edit” and “Buttons stay visible and disabled”: the outcome is absent and buttons remain enabled. Resolve this constraint conflict explicitly; refusing the edit was not the accepted fix. Related sites: bin/fm-ext-decision.sh:687 skips publication; contrib/hermes-gateway-firstmate-comms/outbox_poster.py:1586–1590 repeats the refusal during recovery; docs/configuration.md:1026 documents it; tests/fm-ext-decision.test.sh:1451 endorses it.bin/fm-ext-decision.sh:646- Legacy overflow remains unresolved after rounds 1–3. A 1,900-character original plus an 80-character option’s waiting header exceeds 2,000 characters; this error causes command_posts to skip publication, leaving the outcome absent and buttons enabled. This contradicts “Every outcome state … must edit” and “Buttons stay visible and disabled.” Resolve the incompatible legacy constraints explicitly before merging; silent refusal is not the accepted fix. Siblings: bin/fm-ext-decision.sh:687 skips publication; contrib/hermes-gateway-firstmate-comms/outbox_poster.py:1596–1600 repeats the refusal during recovery; docs/configuration.md:1025 documents it; tests/fm-ext-decision.test.sh:1451 endorses it.bin/fm-ext-decision.sh:305- Round 4's legacy migration leaves previously settled choices dependent on the current lifecycle. Before upgrade, settle a post as North, then reopen its key or re-hold its task. Its immutable settlement has no selected_option_id. This fallback calls post_outcome, which now returns superseded with no answer, so the components-only migration permanently receipts all-grey buttons despite the retained outcome naming North. This contradicts the accepted requirement that legacy posts retain “the chosen one success-styled with a check mark.” Recover the historical selection from the existing immutable settlement evidence rather than the current lifecycle, preserving write-once semantics. Related changed sites: bin/fm-ext-decision.sh:665 consumes the selection; :696 records the empty selection; contrib/hermes-gateway-firstmate-comms/decisions.py:102 renders every option grey.🔧 Fix applied.
✅ Re-checked - no issues remain.
✅ **Test** - passed
✅ No issues found.
TMPDIR="$PWD/.phase-test-tmp" PYTHONDONTWRITEBYTECODE=1 bash tests/fm-ext-decision.test.shTMPDIR="$PWD/.phase-test-tmp" PYTHONDONTWRITEBYTECODE=1 bash tests/fm-ext-ask.test.shDisposable live driver: cases 2, 6, 7, 17, 19, and 21 using the real CLI/poster against a local HTTP endpoint.Disposable legacy-waiting and HTTP-retry drivers: case 20, including an actual HTTP 429 followed by successful retry.Verified cleanup and an unchanged working tree.✅ **Document** - passed
✅ No issues found.
🔧 **Lint** - 1 issue found → auto-fixed ✅
🔧 Fix applied.
✅ Re-checked - no issues remain.
✅ **Push** - passed
✅ No issues found.