77912c54
Correct two errors in the merge-recording design
a73x 2026-08-10 07:01
Commit message
docs/superpowers/specs/2026-08-09-merge-recording-design.md
| Old | New | ||
|---|---|---|---|
| @@ -120,27 +120,43 @@ must never break sync: | |||
| 120 | | Commit unreadable | Warn, continue | | 120 | | Commit unreadable | Warn, continue | |
| 121 | | Revwalk cannot start | Return `Err`; caller skips the scan for this sync | | 121 | | Revwalk cannot start | Return `Err`; caller skips the scan for this sync | |
| 122 | 122 | ||
| 123 | The archived-patch row is **conditional on a walk happening at all**, and the | ||
| 124 | bounding rule above can prevent one. If an archived patch is the only patch on | ||
| 125 | its base branch, that base is not walked, so no trailer is read and no warning is | ||
| 126 | emitted. The optimisation is right and the warning is a courtesy, so the | ||
| 127 | optimisation wins — but the table overstates the guarantee and the tests should | ||
| 128 | encode the conditional behaviour rather than the absolute. | ||
| 129 | |||
| 123 | ## Auto-closing the referenced issue | 130 | ## Auto-closing the referenced issue |
| 124 | 131 | ||
| 125 | `--fixes` is documented as "auto-closes on merge" (`cli.rs:309`) and nothing | 132 | `--fixes` is documented as "auto-closes on merge" (`cli.rs:309`) and nothing |
| 126 | implements it. It could not have worked reliably before this design, because | 133 | implements it. It could not have worked reliably before this design, because |
| 127 | merge itself was not reliably detectable. | 134 | merge itself was not reliably detectable. |
| 128 | 135 | ||
| 129 | When a `PatchMerge` is emitted, an `IssueClose` for the patch's `fixes` issue is | 136 | The close is driven by an **invariant**, not by an emission: *a merged patch's |
| 130 | emitted **at the same moment, in the same operation**. Not during derivation — | 137 | `fixes` issue is closed*. Anything that establishes or observes the merge works |
| 131 | that is the constraint above, and it applies with equal force here. | 138 | to restore it. |
| 139 | |||
| 140 | An earlier draft specified this the other way — "emit the close only alongside a | ||
| 141 | `PatchMerge` that was actually emitted, never alongside one that was a no-op" — | ||
| 142 | and that is self-defeating. Closing writes to a different ref than the merge, so | ||
| 143 | the two are not atomic; if the close fails, the merge still stands, and on the | ||
| 144 | next pass the merge is *necessarily* a no-op. The retry the same section called | ||
| 145 | for was therefore unreachable by its own rule. | ||
| 146 | |||
| 147 | Stated as an invariant, both cases fall out: | ||
| 148 | |||
| 149 | - A freshly recorded merge closes its issue inline, in the same operation. | ||
| 150 | - A patch already merged with its `fixes` issue still open has the close retried. | ||
| 151 | |||
| 152 | Either way it is a write performed by an operation the user invoked — never | ||
| 153 | during derivation. That constraint is unchanged and applies with equal force. | ||
| 132 | 154 | ||
| 133 | Guards: | 155 | Guards: |
| 134 | 156 | ||
| 135 | - Skip if the issue is already closed, so re-running the scan appends nothing. | 157 | - Skip if the issue is already closed, so repeated scans append nothing. |
| 136 | - Skip and warn if the issue does not resolve or is ambiguous. | 158 | - Skip and warn if the issue does not resolve or is ambiguous. |
| 137 | - Emit the close only alongside a `PatchMerge` that was *actually* emitted, never | 159 | - Both paths must be idempotent, since the retry pass runs on every scan. |
| 138 | alongside one that was a no-op because the patch was already merged. | ||
| 139 | |||
| 140 | Closing writes to a different ref than the merge, so the two are not atomic. If | ||
| 141 | the close fails the merge still stands; the next scan sees a merged patch with an | ||
| 142 | open `fixes` issue and retries the close. Make that path explicit rather than | ||
| 143 | letting it fall out of the ordering. | ||
| 144 | 160 | ||
| 145 | ## Stamping the trailer | 161 | ## Stamping the trailer |
| 146 | 162 | ||