From 4db896761ce61d64b3f29f83b7731a5fb9d99684 Mon Sep 17 00:00:00 2001 From: "Jamey M. Yates" Date: Mon, 28 Sep 2026 19:47:22 -0500 Subject: [PATCH 1/3] fix: verify declared GitHub issue closure at publication --- handoffs/review-to-publication.md | 8 +++++ roles/publication.md | 56 +++++++++++++++++++++++++++++++ workflows/reviewed-change.md | 5 +++ 3 files changed, 69 insertions(+) diff --git a/handoffs/review-to-publication.md b/handoffs/review-to-publication.md index 0076739..731de18 100644 --- a/handoffs/review-to-publication.md +++ b/handoffs/review-to-publication.md @@ -66,11 +66,19 @@ return control without performing that action. | Publication identities and surfaces | `` | | Publication correspondence | `` | | Completion and verification | `` | +| Issue closure on merge (when GitHub issue-closing semantics apply) | `none` or ``; `` | | Prior exposure and recovery | `` | | Required checks and conditions | `` | | Required shared targets | `` | | Return destination | `` | +For applicable GitHub publication, declare every issue by full +`owner/repository#number` identity, including issues in other repositories; +`none` means no closing relationship in any repository. Follow the +[GitHub issue-closure safety rule and verification](../roles/publication.md#github-issue-closure) +when preparing PR prose and performing publication. This field does not require +other consumers to use GitHub or issues. + Check that the proposed publication state is the state reviewed. If it differs, return it for proportionate re-review under the workflow-local reviewed-change rule before relying on earlier review. Preserve the finding dispositions through diff --git a/roles/publication.md b/roles/publication.md index c5b8de7..e89d1d7 100644 --- a/roles/publication.md +++ b/roles/publication.md @@ -63,3 +63,59 @@ Perform only the authorized action. Return resulting object identities, verification evidence, recovery performed, remaining conditions, and next owner through [return of control](../handoffs/return-of-control.md). Publication is an optional authorized boundary, not a prerequisite for reporting completed work. + +### GitHub issue closure + +When GitHub is the relevant host, its closing keywords before issue references +are commands, not ordinary prose. Use `close` / `closes` / `closed`, +`fix` / `fixes` / `fixed`, or `resolve` / `resolves` / `resolved` before an issue +reference only when closure on merge is intentionally authorized. Never use +those forms inside negative or prohibitive prose: `do not close #N` is unsafe; +use `#N remains open` or `leave issue #N open`. Ordinary issue references remain +permitted. Apply this rule to generated handoffs and PR prose. + +Where the publication action uses GitHub issue-closing semantics, follow this +manual procedure. It applies whether repository auto-close is enabled or disabled +and requires no repository-setting change. + +1. Read the handoff's **Issue closure on merge** declaration: `none` or the exact + authorized issue set, each identified as `owner/repository#number`, with the + authority/source where material. Do not assume issues belong to the PR's + repository. Identify issues covered by the publication action, including + those intended to remain open, and record their pre-merge state for comparison. +2. After ordinary candidate correspondence and required validation checks, inspect + the PR's actual closing issue relationships as the **last host-state + verification immediately before merge**: + + ```sh + gh pr view --json closingIssuesReferences + ``` + + Compare the actual and declared sets exactly, using both repository identity + and issue number (resolve full identities from the returned issue URLs). + Include cross-repository relationships. For `none`, the actual set must be + empty across all repositories. An unexpected or missing relationship, or an + inability to verify the complete set, blocks merge. Correct the PR description + or relationship source within authority and recheck. A passing result is + evidence of host state, not authorization. If the PR description or linked-issue + relationships change afterward, invalidate the result and rerun this final + check before the authorized merge; an earlier review/handoff snapshot is + insufficient. +3. After the authorized merge, verify actual issue state against the declaration + using full identities, including cross-repository issues. For `none`, compare + the covered issues' states and host history with the pre-merge record to verify + the merge did not unexpectedly close an issue. For an authorized set, verify + every declared issue reached the intended closed state, and check for + unexpected closures among the covered issues. Retain resulting-state evidence + and report any verification limits. +4. If an authorized issue remains open because GitHub did not close it + automatically, that alone is not publication failure. Route explicit closure + to the role/owner holding issue-closure authority, or perform it only if that + authority is already held. If an issue closes unexpectedly, preserve evidence + of the observed state, report it, and route correction/reopening through proper + project authority; do not silently restore state. Carry unresolved conditions, + follow-up, and the next owner through return of control. + +Publication authority does not imply issue-closure authority. Publication is not +general project closeout: merge need not complete the work item, and issues may +legitimately remain open afterward. diff --git a/workflows/reviewed-change.md b/workflows/reviewed-change.md index 2ec7128..e90e682 100644 --- a/workflows/reviewed-change.md +++ b/workflows/reviewed-change.md @@ -101,6 +101,11 @@ role. Preparing that handoff does not itself authorize the sender to execute it. Publication does not redesign the candidate. See the applicable PRI guidance under [Source-derived guidance](#source-derived-guidance) above. Return corrections through the authorized role handoff. + Where GitHub issue-closing semantics apply, host closing state must match the + handoff's declared project completion intent. Follow the + [issue-closure safety rule](../roles/publication.md#github-issue-closure) in PR + prose; Publication verifies actual closing relationships immediately before + merge and resulting issue state afterward. 6. **Return from every boundary.** Use [return of control](../handoffs/return-of-control.md) on completion, interruption, missing authority, or a new material decision. Publication can be absent; all From 303d9fe40792556a33bf248112c56e3f1b38bbdc Mon Sep 17 00:00:00 2001 From: "Jamey M. Yates" Date: Mon, 28 Sep 2026 23:14:45 -0500 Subject: [PATCH 2/3] fix: verify commit and landing issue-closure semantics --- handoffs/review-to-publication.md | 7 +- roles/publication.md | 126 ++++++++++++++++++------------ workflows/reviewed-change.md | 7 +- 3 files changed, 87 insertions(+), 53 deletions(-) diff --git a/handoffs/review-to-publication.md b/handoffs/review-to-publication.md index 731de18..95431ce 100644 --- a/handoffs/review-to-publication.md +++ b/handoffs/review-to-publication.md @@ -76,8 +76,11 @@ For applicable GitHub publication, declare every issue by full `owner/repository#number` identity, including issues in other repositories; `none` means no closing relationship in any repository. Follow the [GitHub issue-closure safety rule and verification](../roles/publication.md#github-issue-closure) -when preparing PR prose and performing publication. This field does not require -other consumers to use GitHub or issues. +for all applicable closure-bearing surfaces: PR relationships, controlling commit +messages, and final landing messages, plus resulting issue state after merge. +Source references may use supported local or URL forms; resolve them to full +identities for comparison. This field does not require other consumers to use +GitHub or issues. Check that the proposed publication state is the state reviewed. If it differs, return it for proportionate re-review under the workflow-local reviewed-change diff --git a/roles/publication.md b/roles/publication.md index e89d1d7..a85adf5 100644 --- a/roles/publication.md +++ b/roles/publication.md @@ -66,56 +66,86 @@ optional authorized boundary, not a prerequisite for reporting completed work. ### GitHub issue closure -When GitHub is the relevant host, its closing keywords before issue references -are commands, not ordinary prose. Use `close` / `closes` / `closed`, -`fix` / `fixes` / `fixed`, or `resolve` / `resolves` / `resolved` before an issue -reference only when closure on merge is intentionally authorized. Never use -those forms inside negative or prohibitive prose: `do not close #N` is unsafe; -use `#N remains open` or `leave issue #N open`. Ordinary issue references remain -permitted. Apply this rule to generated handoffs and PR prose. - -Where the publication action uses GitHub issue-closing semantics, follow this -manual procedure. It applies whether repository auto-close is enabled or disabled -and requires no repository-setting change. - -1. Read the handoff's **Issue closure on merge** declaration: `none` or the exact - authorized issue set, each identified as `owner/repository#number`, with the - authority/source where material. Do not assume issues belong to the PR's - repository. Identify issues covered by the publication action, including - those intended to remain open, and record their pre-merge state for comparison. -2. After ordinary candidate correspondence and required validation checks, inspect - the PR's actual closing issue relationships as the **last host-state - verification immediately before merge**: +When GitHub is the relevant host, closing keywords before issue references are +commands, not ordinary prose. Use `close` / `closes` / `closed`, `fix` / `fixes` / +`fixed`, or `resolve` / `resolves` / `resolved` before an issue reference only when +closure is authorized. Never use those forms in negative or prohibitive prose: +`do not close #N` is unsafe; use `#N remains open` or `leave issue #N open`. +Ordinary references remain permitted. This rule covers generated handoffs, PR +descriptions, commit messages, and merge/squash subjects and bodies. + +Where GitHub issue-closing semantics apply, use this manual procedure regardless +of repository auto-close configuration; no setting change is required. + +1. **Prepare the final gate.** After candidate correspondence and required + validation, establish the exact PR head SHA (``) and the + permitted landing method. The handoff's **Issue closure on merge** remains + the single declaration: `none` or authorized `owner/repository#number` + identities, with authority/source where material. Resolve every discovered + closing reference to that canonical identity before comparison. Source text + may use supported local, qualified, or GitHub issue URL forms without rewriting; + in commit messages, local `#N` resolves against the repository whose default + branch the commit will land in. +2. **Verify all controlling surfaces for that head.** Inspect actual PR closing + relationships and applicable commit messages using: ```sh gh pr view --json closingIssuesReferences + gh pr view --json commits ``` - Compare the actual and declared sets exactly, using both repository identity - and issue number (resolve full identities from the returned issue URLs). - Include cross-repository relationships. For `none`, the actual set must be - empty across all repositories. An unexpected or missing relationship, or an - inability to verify the complete set, blocks merge. Correct the PR description - or relationship source within authority and recheck. A passing result is - evidence of host state, not authorization. If the PR description or linked-issue - relationships change afterward, invalidate the result and rerun this final - check before the authorized merge; an earlier review/handoff snapshot is - insufficient. -3. After the authorized merge, verify actual issue state against the declaration - using full identities, including cross-repository issues. For `none`, compare - the covered issues' states and host history with the pre-merge record to verify - the merge did not unexpectedly close an issue. For an authorized set, verify - every declared issue reached the intended closed state, and check for - unexpected closures among the covered issues. Retain resulting-state evidence - and report any verification limits. -4. If an authorized issue remains open because GitHub did not close it - automatically, that alone is not publication failure. Route explicit closure - to the role/owner holding issue-closure authority, or perform it only if that - authority is already held. If an issue closes unexpectedly, preserve evidence - of the observed state, report it, and route correction/reopening through proper - project authority; do not silently restore state. Carry unresolved conditions, - follow-up, and the next owner through return of control. - -Publication authority does not imply issue-closure authority. Publication is not -general project closeout: merge need not complete the work item, and issues may -legitimately remain open afterward. + Require the complete PR relationship set to equal the declaration exactly, + including repository identity. Inspect commit messages only when they can + contribute closing semantics to the default-branch landing; every such target + must be declared. Intermediate messages are non-controlling for squash when an + explicitly supplied final subject/body prevents their inclusion; merge/rebase + methods must account for messages they land. Establish and check the exact + final merge/squash subject and body, requiring their closing targets to match + the declaration. For methods without a synthesized message, verify the messages + that actually land instead. Do not rely on unchecked future host defaults. + + Aggregate all controlling relationships and commands, resolve full identities, + and require that set to equal the declaration. For `none`, relationships must + be empty and controlling messages must contain no closing command. Unexpected + or missing targets, unresolved identities, or incomplete/unverifiable state + block merge. Correct within authority and recheck. The relevant issue scope is + the union of declared identities and targets reachable through PR relationships, + controlling commit commands, and final landing commands; record their pre-merge + states for comparison, without inspecting unrelated issues. + + This combined gate is the **last host-state verification immediately before + merge**, not an earlier review/handoff snapshot or authorization. Any change to + the head (including a required branch update), PR description, linked issues, + controlling messages, final subject/body, or declaration invalidates it; rerun + affected checks and all comparisons against the current head before merging. +3. **Bind the authorized merge to verified inputs.** For squash, use: + + ```sh + gh pr merge --squash --match-head-commit \ + --subject "" --body "" + ``` + + Adapt to the permitted method while retaining a fail-closed head-match guard + and binding actual landing messages to checked text. Explicitly supply verified + merge/squash text; never fall back silently to a generated message. Confirm the + CLI/API preserves an intended empty body before relying on `--body ""`; + otherwise use a non-empty verified body. If the method cannot bind these inputs, + stop and return the unresolved condition rather than claim the gate passed. +4. **Verify the result.** Identify the actual landed commit(s), establish their + correspondence to the expected publication action, and inspect their subjects + and bodies against the verified messages. Resolve landed closing references to + full identities and extend the relevant issue set with any newly observed + targets. Verify every declared issue reached the intended closed state and + check issue states/history for unexpected closure through all three surfaces. + For `none`, verify no relevant issue was unexpectedly closed. Retain landed + identities, message correspondence, resulting-state evidence, and verification + limits; report and route any mismatch for disposition. + +An authorized issue remaining open because host auto-close differs is not alone +publication failure. Explicit closure requires the actual issue-closure owner or +that authority already held by the executor. Preserve and report unexpected +observed closure; route correction/reopening under project authority without +silently restoring state. Return unresolved conditions, follow-up, and next owner +through return of control. Publication authority does not imply issue-closure +authority or general project closeout: merge need not complete the work item, +and issues may legitimately remain open afterward. diff --git a/workflows/reviewed-change.md b/workflows/reviewed-change.md index e90e682..d26e45a 100644 --- a/workflows/reviewed-change.md +++ b/workflows/reviewed-change.md @@ -103,9 +103,10 @@ role. Preparing that handoff does not itself authorize the sender to execute it. Return corrections through the authorized role handoff. Where GitHub issue-closing semantics apply, host closing state must match the handoff's declared project completion intent. Follow the - [issue-closure safety rule](../roles/publication.md#github-issue-closure) in PR - prose; Publication verifies actual closing relationships immediately before - merge and resulting issue state afterward. + [issue-closure safety rule](../roles/publication.md#github-issue-closure) across + PR, commit, and landing-message surfaces; Publication verifies applicable + GitHub issue-closing state immediately before merge and resulting issue state + afterward. 6. **Return from every boundary.** Use [return of control](../handoffs/return-of-control.md) on completion, interruption, missing authority, or a new material decision. Publication can be absent; all From a9745bd1a0a4a612d185c506ceabc1d192961b85 Mon Sep 17 00:00:00 2001 From: "Jamey M. Yates" Date: Mon, 28 Sep 2026 23:33:07 -0500 Subject: [PATCH 3/3] fix: compare closure surfaces as subsets of aggregate intent --- roles/publication.md | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/roles/publication.md b/roles/publication.md index a85adf5..400a5a7 100644 --- a/roles/publication.md +++ b/roles/publication.md @@ -94,21 +94,26 @@ of repository auto-close configuration; no setting change is required. gh pr view --json commits ``` - Require the complete PR relationship set to equal the declaration exactly, - including repository identity. Inspect commit messages only when they can + Resolve each surface's targets to full identities and require each set to be + a subset of the declaration; any undeclared target blocks merge. Inspect + commit messages only when they can contribute closing semantics to the default-branch landing; every such target must be declared. Intermediate messages are non-controlling for squash when an explicitly supplied final subject/body prevents their inclusion; merge/rebase methods must account for messages they land. Establish and check the exact - final merge/squash subject and body, requiring their closing targets to match - the declaration. For methods without a synthesized message, verify the messages + final merge/squash subject and body under the same subset rule. + For methods without a synthesized message, verify the messages that actually land instead. Do not rely on unchecked future host defaults. - Aggregate all controlling relationships and commands, resolve full identities, - and require that set to equal the declaration. For `none`, relationships must - be empty and controlling messages must contain no closing command. Unexpected + Take the union of all applicable controlling surfaces' resolved target sets: + this aggregate closure-command set must equal the declared closures expected + from the merge action. Targets need not appear on every surface. For `none`, + every surface and the aggregate must be empty. Unexpected or missing targets, unresolved identities, or incomplete/unverifiable state - block merge. Correct within authority and recheck. The relevant issue scope is + block merge. For a missing declared target, correct an authorized surface and + rerun the gate, or correct the handoff within authority if explicit post-merge + closure is intended instead of closure on merge. Do not silently reinterpret + the declaration or add a second one. The relevant issue scope is the union of declared identities and targets reachable through PR relationships, controlling commit commands, and final landing commands; record their pre-merge states for comparison, without inspecting unrelated issues.