@windyroad/itil 2.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -46,7 +46,7 @@ A space-separated list of `<NNN> <status>` pairs. Repeating the singular skill's
46
46
  - `<status>` — destination status. One of:
47
47
  - `known-error` — Open → Known Error (root cause + workaround documented).
48
48
  - `verifying` — Known Error → Verification Pending (fix released; awaiting user verification per the "Problem lifecycle — add a Verification Pending status between Known Error and Closed" architecture rule).
49
- - `close` — Verification Pending → Closed (user has confirmed the fix works in production).
49
+ - `close` — Verification Pending → Closed (cited evidence meets the ticket's own close criterion, OR the user has confirmed the fix works in production — see Step 2c).
50
50
 
51
51
  **Examples:**
52
52
 
@@ -111,13 +111,19 @@ If no file is found OR multiple files are found (suffix-exclusive lifecycle viol
111
111
  | `.open.md` | `known-error` | yes |
112
112
  | `.known-error.md` | `verifying` | yes |
113
113
  | `.verifying.md` | `close` | yes |
114
+ | `.verifying.md` | `known-error` | yes — **flip-back**: the fix recurred or proved incomplete. `review-problems` Bucket 3 already instructs this and had no batch path. |
115
+ | `.closed.md` | `known-error` | yes — **reopen**: a close was wrong. The recovery path every evidence-authorised close reports (the "The Verification Pending → Closed transition is reserved for the maintainer, so evidence-based closure never fires and the verification queue grows without bound" problem). |
114
116
  | any other pairing | — | no — record as `invalid-transition` and continue |
115
117
 
118
+ Lockstep with `/wr-itil:transition-problem` Step 3 per the drift clause below — the singular and plural tables move together or neither moves. The two backward pairings are what make an agent-authorised close safe: the argument for closing on evidence without asking is that the close is cheap and reversible, and a reopen route either skill refuses would make that argument false. Both are ordinary renames; neither re-runs the Open → Known Error pre-flight (root cause and workaround were established the first time round) and neither fires the "Problem 063: manage-problem does not trigger /wr-itil:report-upstream when root cause is external" problem detection at 2d.
119
+
116
120
  **2c. Run pre-flight checks** for the destination (same gating as the singular):
117
121
 
118
122
  - Open → Known Error (`known-error`): root cause documented; ≥ 1 investigation task ticked; reproduction test or reference; workaround documented; effort bucket re-rated if scope shifted (the "Problem 047: WSJF effort buckets are coarse and not re-rated at lifecycle transitions" problem); status multiplier re-rated from Open 1.0 to Known Error 2.0 and WSJF recomputed with the post-transition status and current Effort (the "WSJF uses the pre-transition status multiplier" problem).
119
123
  - Known Error → Verification Pending (`verifying`): fix implemented; release marker available (version, commit SHA, or date) for the `## Fix Released` section.
120
- - Verification Pending → Closed (`close`): user has explicitly confirmed the fix works in production. AFK callers (work-problems orchestrator) MUST supply the close pair via prior user authorisation (e.g. an `request_user_input`-batched closure prompt at the orchestrator layer); this skill never auto-closes on inference.
124
+ - Verification Pending → Closed (`close`): cited observed evidence or explicit user confirmation authorizes closure. Evidence-backed closure is mechanical and does not require another question. Record the citation and recovery path (`/wr-itil:transition-problem <NNN> known-error`) in each ticket's Status line. A provenance-proven GitHub issue on this project's tracker receives the gated comment and closes; unresolved or non-issue inbound provenance performs no issue mutation. A foreign issue receives the comment but stays open on local evidence alone; that upstream party's confirmation may authorize closure. Pull requests remain comment-only.
125
+
126
+ **Never close on inference** — absence of evidence is not evidence. A pair whose ticket carries no citation records as `pre-flight-failed` with reason `no-evidence` and stays Verification Pending; age, plausibility, and "the fix is on disk" are not observations. **Do-not-close marker check** — run `<itil-plugin-root>/bin/wr-itil-is-close-blocked <NNN> docs/problems` per pair before closing. Exit 0 → record the pair as `pre-flight-failed` with reason `close-blocked` plus the matched marker line, and continue to the next pair; do NOT close it whatever the evidence says. Exit 2 (unresolvable ref) is a pre-flight failure, not permission.
121
127
 
122
128
  If any pre-flight fails, record the pair as `pre-flight-failed` with the failed-check list and continue to the next pair.
123
129
 
@@ -162,10 +168,18 @@ git add docs/problems/verifying/<NNN>-<title>.md
162
168
  ```bash
163
169
  # Verification Pending → Closed
164
170
  git mv docs/problems/verifying/<NNN>-<title>.md docs/problems/closed/<NNN>-<title>.md
165
- # Edit Status field to "Closed"
171
+ # Edit Status field per the shape below
166
172
  git add docs/problems/closed/<NNN>-<title>.md
167
173
  ```
168
174
 
175
+ **Write the close's authority basis into the Status field.** The Status line is the only place the basis survives — the `Likely verified?` cell lives in the README's Verification Queue table, and that row is deleted by this very transition, so a downstream reader (Step 7b's upstream leg) would find nothing there. Shape, matching what the 2026-07-15 closes already wrote (`docs/problems/closed/186-*.md:3`):
176
+
177
+ - Evidence-authorised: `**Status**: Closed (closed-on-evidence <YYYY-MM-DD> — <citation>. Recovery: rerun /wr-itil:transition-problem <NNN> known-error to reopen)`
178
+ - User-confirmed: `**Status**: Closed (user-confirmed <YYYY-MM-DD>)`
179
+
180
+ `closed-on-evidence` is what the outbound leg reads to keep a foreign issue open on our evidence alone. The inbound leg uses committed issue-channel provenance instead: ambiguity fails closed before any issue operation.
181
+
182
+
169
183
  If `git mv` or `git add` fails for a pair (e.g. the file has been moved by a parallel process), record the pair as `git-failed` with the error and continue to the next pair. Do NOT attempt to roll back prior pairs' staged renames — those are now part of the in-progress batch.
170
184
 
171
185
  Record each pair's outcome (succeeded / failed-with-reason) for the summary in Step 5.
@@ -178,7 +178,7 @@ Update from <downstream-repo-url>/<local-ticket-relative-path>:
178
178
 
179
179
  <one-sentence summary from the local ticket's ## Fix Released section>
180
180
 
181
- Please upgrade and verify when convenient. We'll close this issue after your confirmation OR after a 14-day quiet period (per the "Problem 048: manage-problem does not surface Fix Released problems as verification candidates when the fix path has been exercised" problem default). Local tracking: P<NNN>.
181
+ Please upgrade and verify when convenient. We'll close this issue after your confirmation. If you'd rather we closed it without one, say so here. Local tracking: P<NNN>.
182
182
  ```
183
183
 
184
184
  #### Verification Pending → Closed template
@@ -186,12 +186,38 @@ Please upgrade and verify when convenient. We'll close this issue after your con
186
186
  ```markdown
187
187
  Update from <downstream-repo-url>/<local-ticket-relative-path>:
188
188
 
189
- **Status**: Closed locally after user-side verification.
189
+ **Status**: Closed locally after verification.
190
190
 
191
- Closing this issue to match. Thanks for the report — your filing is what got this on the queue. Local tracking: P<NNN>.
191
+ Thanks for the report — your filing is what got this on the queue. Local tracking: P<NNN>.
192
192
  ```
193
193
 
194
- After posting the Verifying → Closed comment, the skill also runs `gh issue close <n>` (per Step 5b below) so the upstream tracker matches local state.
194
+ After posting the Verifying → Closed comment, leave the foreign issue open by default. Closing it requires the target-bound upstream-party confirmation below.
195
+
196
+ #### Foreign issues close only on target-bound upstream-party confirmation
197
+
198
+ Local evidence or local user confirmation can close our ticket, but neither decides another maintainer's tracker state. Post the lifecycle comment and **stop there** unless the local ticket's `## Reported Upstream` section carries this durable, target-bound signal:
199
+
200
+ ```markdown
201
+ - **Closure authority**: upstream-confirmed <exact GitHub issue-comment URL>
202
+ ```
203
+
204
+ Before running `gh issue close`, validate that URL read-only: it MUST be an `#issuecomment-<id>` URL on the exact `UPSTREAM_OWNER_REPO/issues/UPSTREAM_ISSUE_NUMBER` target; `gh api repos/<owner>/<repo>/issues/comments/<id>` MUST return that exact issue URL; `author_association` MUST be `OWNER`, `MEMBER`, or `COLLABORATOR`; and the comment body MUST unambiguously authorize closing this issue. A missing marker, URL mismatch, API failure, non-maintainer association, or ambiguous body leaves the issue open. Never infer this authority from a local `user-confirmed` Status marker.
205
+
206
+ Read the Status line, not the README's `Likely verified?` cell: the close deletes that row from the Verification Queue before this step runs, so the cell is gone by the time anything could read it.
207
+
208
+ Confirmation from the upstream party may authorize closing their issue; their word, not our evidence, supplies the missing authority. Do not promise a quiet-period close that no mechanism delivers.
209
+
210
+ When target-bound confirmation is absent, use the evidence-authorised local-close-only variant below and record `posted-comment-local-close-only` in the back-write disclosure path. When validation succeeds, use the ordinary Closed template and record `posted-comment-and-closed`.
211
+
212
+ ```markdown
213
+ Update from <downstream-repo-url>/<local-ticket-relative-path>:
214
+
215
+ **Status**: Closed locally. We verified the fix on our side — <one-line evidence citation>.
216
+
217
+ Leaving this issue open for you. Close it whenever you have confirmed the fix works in your setup. Local tracking: P<NNN>.
218
+ ```
219
+
220
+ On this leg, without validated target-bound confirmation from them: comment, do not close. Pull requests are always comment-only, even if they carry a confirmation marker.
195
221
 
196
222
  #### Template-filling rules
197
223
 
@@ -267,7 +293,7 @@ gh issue comment "${UPSTREAM_ISSUE_NUMBER}" \
267
293
 
268
294
  Capture the returned comment URL (gh prints `https://github.com/<owner>/<repo>/issues/<n>#issuecomment-<id>`).
269
295
 
270
- On the **Verifying → Closed** transition, after posting the comment, also close the upstream issue:
296
+ On the **Verifying → Closed** transition, after posting the comment, close the upstream issue only when the target-bound `Closure authority` validation above passed:
271
297
 
272
298
  ```bash
273
299
  gh issue close "${UPSTREAM_ISSUE_NUMBER}" \
@@ -298,7 +324,7 @@ Append a log entry to the local ticket's `## Upstream Lifecycle Updates` section
298
324
  - **<YYYY-MM-DD>** — Open → Known Error
299
325
  - **Target URL**: <upstream-issue-url>
300
326
  - **Comment URL**: <posted-comment-url> (or "queued — see ## Queued Upstream Update" when above-appetite)
301
- - **Disclosure path**: posted-comment | posted-pr-comment (pull-request target, the "Prefer an upstream pull request over an issue when the upstream accepts pull requests" architecture rule — never closed) | posted-comment-and-closed (Verifying → Closed, issue targets only) | queued-above-appetite | closed-already-upstream | skipped-out-of-band
327
+ - **Disclosure path**: posted-comment | posted-pr-comment (pull-request target — never closed) | posted-comment-and-closed (confirmed Verifying → Closed, issue targets only) | posted-comment-local-close-only (outbound evidence-authorised close) | queued-above-appetite | closed-already-upstream | skipped-out-of-band
302
328
  - **Gate verdict**: external-comms <band/score> + voice-tone <pass|fail>
303
329
 
304
330
  - **<YYYY-MM-DD>** — Known Error → Verification Pending
@@ -317,7 +343,51 @@ This leg runs **in addition to** Steps 2–6 (the outbound `## Reported Upstream
317
343
 
318
344
  #### I1. Parse the inbound origin
319
345
 
320
- From the `**Origin**: inbound-reported (#NN)` field, extract the originating issue number `NN`. Resolve the repo it lives on as **our own repo** — the `gh` default for the current working tree (`gh repo view --json nameWithOwner -q .nameWithOwner`), NOT an external upstream owner/repo. If the `**Origin**` field is `internal` or absent, this leg does not run (the no-op is already handled at Step 1 when both surfaces are absent).
346
+ From the `**Origin**: inbound-reported (#NN)` field, extract the candidate number `NN`. That untyped number is not proof of an issue: issues, discussions, and advisories have distinct mutation paths, and numeric identifiers can collide.
347
+
348
+ Before any inbound `gh issue view`, `gh issue comment`, or `gh issue close`, prove the channel from the committed discovery cache:
349
+
350
+ ```bash
351
+ OWN_OWNER_REPO=$(gh repo view --json nameWithOwner -q .nameWithOwner)
352
+ LOCAL_TICKET_ID="P${LOCAL_ID}"
353
+ ISSUE_CHANNEL="github-issues:${OWN_OWNER_REPO}"
354
+ CACHE_REF="HEAD:docs/problems/.upstream-cache.json"
355
+ CACHE_JSON=$(git show "${CACHE_REF}" 2>/dev/null) || CACHE_JSON='{}'
356
+
357
+ ISSUE_NUMBER_MATCHES=$(printf '%s' "${CACHE_JSON}" | jq \
358
+ --arg channel "${ISSUE_CHANNEL}" \
359
+ --argjson number "${NN}" \
360
+ '[.channels[$channel].reports[]? | select(.number == $number)] | length' \
361
+ 2>/dev/null) || ISSUE_NUMBER_MATCHES=0
362
+
363
+ ISSUE_TICKET_MATCHES=$(printf '%s' "${CACHE_JSON}" | jq \
364
+ --arg channel "${ISSUE_CHANNEL}" \
365
+ --argjson number "${NN}" \
366
+ --arg ticket "${LOCAL_TICKET_ID}" \
367
+ '[.channels[$channel].reports[]? |
368
+ select(.number == $number and .matched_local_ticket == $ticket)] | length' \
369
+ 2>/dev/null) || ISSUE_TICKET_MATCHES=0
370
+
371
+ NON_ISSUE_MATCHES=$(printf '%s' "${CACHE_JSON}" | jq \
372
+ --arg issue_channel "${ISSUE_CHANNEL}" \
373
+ --arg repo_suffix ":${OWN_OWNER_REPO}" \
374
+ --argjson number "${NN}" \
375
+ '[.channels | to_entries[] |
376
+ select(.key != $issue_channel and (.key | endswith($repo_suffix))) |
377
+ .value.reports[]? | select(.number == $number)] | length' \
378
+ 2>/dev/null) || NON_ISSUE_MATCHES=0
379
+
380
+ if [ "${ISSUE_NUMBER_MATCHES}" -ne 1 ] || \
381
+ [ "${ISSUE_TICKET_MATCHES}" -ne 1 ] || \
382
+ [ "${NON_ISSUE_MATCHES}" -ne 0 ]; then
383
+ echo "inbound-channel-unresolved: ${LOCAL_TICKET_ID} #${NN}"
384
+ INBOUND_CHANNEL_RESOLVED=0
385
+ else
386
+ INBOUND_CHANNEL_RESOLVED=1
387
+ fi
388
+ ```
389
+
390
+ When `INBOUND_CHANNEL_RESOLVED=0`, skip I2–I6 and perform no inbound issue operation; an independent outbound leg may still continue. Missing committed cache data, zero or multiple issue matches, a repository mismatch, a wrong `matched_local_ticket`, or a discussion/advisory collision all take this path. If the Origin field is `internal` or absent, this leg does not run.
321
391
 
322
392
  #### I2. Determine the transition (same suffix logic as Step 3)
323
393
 
@@ -441,12 +511,14 @@ Above-appetite handling is identical to Step 5c (silent risk-reduce + re-score;
441
511
  gh issue comment "${NN}" --repo "${OWN_OWNER_REPO}" --body "${INBOUND_BODY}"
442
512
  ```
443
513
 
444
- On the **Verification Pending → Closed** transition, after the comment, also close the originating issue so our own tracker matches the local `.closed.md` state (this addresses the "work-problems orchestrator carries prior-ticket Fix Strategy text into iter dispatch without re-grounding in design intent" problem #97 *silent-and-unclosed* witness):
514
+ On the **Verification Pending → Closed** transition, after the comment, close the provenance-proven issue on this project's tracker whether the local close was confirmed or evidence-authorised:
445
515
 
446
516
  ```bash
447
517
  gh issue close "${NN}" --repo "${OWN_OWNER_REPO}" --comment "" --reason completed
448
518
  ```
449
519
 
520
+ This outbound guard does not apply to a provenance-proven GitHub issue on this project's own repository. After the gated comment, close it whether the local close was evidence-authorised or confirmed; the reporter can reopen it. Unknown or non-issue inbound channels already stopped at the provenance guard.
521
+
450
522
  If the issue is already closed (someone closed it manually), `gh issue close` returns a benign error — record `closed-already` in the disclosure path and continue.
451
523
 
452
524
  #### I6. Back-write to the lifecycle log
@@ -457,7 +529,7 @@ Append to the same `## Upstream Lifecycle Updates` log (Step 6 shape), tagged fo
457
529
  - **<YYYY-MM-DD>** — Known Error → Verification Pending (inbound)
458
530
  - **Target**: inbound #<NN> (own repo <OWN_OWNER_REPO>)
459
531
  - **Comment URL**: <posted-comment-url> (or "skipped — already-posted-inbound" when the idempotency guard matched, or "queued — see ## Queued Upstream Update" when above-appetite)
460
- - **Disclosure path**: posted-inbound-comment | posted-inbound-comment-and-closed (Verifying Closed) | already-posted-inbound | queued-above-appetite | closed-already
532
+ - **Disclosure path**: posted-inbound-comment | posted-inbound-comment-and-closed (proven owned issue, confirmed or evidence-authorised close) | already-posted-inbound | queued-above-appetite | closed-already | inbound-channel-unresolved
461
533
  - **Gate verdict**: external-comms <band/score> + voice-tone <pass|fail>
462
534
  ```
463
535