You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Retrospective for PR #1213 ("fix(hooks): fail closed on missing/malformed jq in four PreToolUse gates", which addressed #1208), merged 2026-08-19T22:18:37Z by the repository owner. Twelve repairs occurred between PR open (2026-08-18T15:08:11Z) and merge, across ten commits: one Codecov patch-coverage fix, one CodeRabbit review round, two independent adversarial-review rounds (bundling four distinct bug fixes across a single commit plus a fifth in a following commit), one code-reuse finding from this skill's own two-layer review, a self-directed registry-linkage audit (prompted by a direct question, not CI or review) that found and fixed a missing .gitapex/ssot.json entry plus stale rule text on three others, a merge conflict from a concurrent independent session fixing the identical registry gap, a follow-up completeness audit disclosing a gap in the drift gate's own rule text, and a final merge inheriting two pre-existing main-branch CI fixes unrelated to this PR's own diff.
Repairs
[Codecov patch-coverage: unreachable skip branch] The template-overwrite test suite's own pytest.skip fallback (for when no real PR template exists in the checkout) was never exercised in this repository's own CI, since a template always exists here -- Codecov's patch-coverage gate flagged the line as uncovered and failed the check. Fixed in commit ad67a28 by replacing the checkout scan with a self-contained tmp_path fixture that creates its own template file, removing the unreachable branch entirely rather than covering it artificially.
Classification: missing deterministic gate.
Status: missing-deterministic-gate
Proposed gate: a pre-push (or pre-commit) stage running pytest --cov scoped to new/changed test files only, mirroring Codecov's own patch-coverage gate locally and fast, so an unreachable branch in a new test is caught before it ever reaches CI.
[CodeRabbit: tool_input false-falsy gap] The tool_input-shape guard these hooks had just gained, (.tool_input // {}) | type == "object", accepted the JSON literal false the same as null or an absent key (jq's // treats both as falsy) -- a tool_input: false payload slipped past the guard and crashed the next field-extraction line instead of denying. Live-confirmed, then fixed in commit de7e6bd by tightening the predicate to (.tool_input == null) or (.tool_input | type == "object"), verified against the full value matrix.
Classification: missing deterministic gate.
Status: missing-deterministic-gate
Proposed gate: see repair 5's proposed gate below -- the same general type-confusion matrix test would have caught this instance too.
[Adversarial round: warn()'s ARG_MAX-vulnerable jq construction] check-bash-safety.sh's warn() (the non-blocking counterpart to the already-hardened deny()) still built its JSON via jq -n --arg, the exact construction deny() had already been hardened away from. Its only call site interpolates the provenance scanner's report over the entire outgoing push, large enough to realistically hit the limit -- live-confirmed a crash (exit 126) on a 3MB message. Fixed in commit 0b0a3af (bundled with repairs 4-6 below in the same push) with the same jq -Rs piped-stdin construction already proven in deny().
Classification: missing deterministic gate.
Status: missing-deterministic-gate
Proposed gate: a consistency check (e.g. a shape-checker rule) flagging any hooks/*.sh file where deny() uses the safe jq -Rs piped-stdin form but a sibling function (warn() or equivalent) in the same file still uses jq -n --arg, so a hardening applied to one does not silently stop short of the other.
[Adversarial round: test suite narrower than its own docstring claims] The same dispatch found the tool_input tests never actually included the "string" case their own docstrings claimed to cover, and no hook had a regression test proving tool_input: null/absent still correctly allows the call through. Both added in commit 0b0a3af.
Classification: missing deterministic gate.
Status: missing-deterministic-gate
Proposed gate: same as repair 5 below -- a single parametrized fixture enumerating the full value matrix (including the allow-through baseline) per guard, rather than individual cases added reactively per review round.
[Adversarial round, most severe: non-string tool_name bypass] jq -r never errors on a non-string .tool_name (e.g. an array) -- it pretty-prints the JSON value across multiple lines, which then never matches the plain-string re-check every one of these hooks' own defense-in-depth relies on, silently falling through as "not our tool" instead of failing closed. Most severe on check-merge-pull-request-block.sh: an array-wrapped tool_name let a real merge_pull_request call straight through this repository's own categorical "no override" deny -- the exact bypass class issue hooks/: four more hooks fail open on missing/malformed jq (same class as gitapex#436) #1208 exists to close, via a different field than the one it named. Live-confirmed across all four hooks, fixed in commit 0b0a3af with (.tool_name == null) or (.tool_name | type == "string").
Classification: missing deterministic gate.
Status: missing-deterministic-gate
Proposed gate: a parametrized property test enumerating jq's full falsy/type-confusion matrix (absent, null, false, true, 0, "", array, object) against every (.field == null) or (.field | type == "X") guard in hooks/*.sh, run in CI. This exact bug class needed four separate adversarial rounds (repairs 2, 5, 7, plus this one) to fully stamp out across four files -- a systematic matrix test, ideally written once against issue hooks/: extract the guard prologue into a shared sourced helper (duplication caused #1216 and #1217) #1218's own already-proposed shared sourced guard-prologue helper rather than duplicated per file, would have caught all four in one pass instead of four.
[Adversarial round: unguarded mktemp crash] Two unguarded var=$(mktemp) calls in check-pr-skill-audit-disclosure.sh's tier-1/tier-2 logic crashed the whole script under set -e on an unwritable/full TMPDIR, instead of the intended degrade-to-tier-2-then-CI fallback every other tier-1-incomplete path in that hook already takes. Live-confirmed, fixed in commit 0b0a3af to fall through with a warning instead.
Classification: missing deterministic gate.
Status: missing-deterministic-gate
Proposed gate: a static check flagging any $(mktemp ...) call in hooks/*.sh not immediately guarded by a fallback, since set -e alone converts an unguarded mktemp failure into a hard crash rather than the graceful degrade these hooks otherwise implement consistently.
[Adversarial round: leaf-field type confusion one level deeper] The same jq-pretty-print gap as repair 5, found one level deeper: check-bash-safety.sh's .tool_input.command and check-template-overwrite.sh's .tool_input.file_path, both feeding a blacklist-style danger-pattern match rather than a positive-match-required-to-pass check, so an array/object value slipped through as "no match" instead of failing closed. Live-confirmed with an array-wrapped ["gh","pr","merge","1"] command and an array-wrapped file_path targeting the real, on-disk PR template. Fixed in commit 3236ee7.
Classification: missing deterministic gate.
Status: missing-deterministic-gate
Proposed gate: same as repair 5 above.
[Two-layer review, inner layer: hand-built test instead of reusing existing helper] Three test_denied_when_tool_name_is_not_a_string tests hand-built their own ~15-line raw subprocess.run block instead of reusing each file's own pre-existing run() helper, which already parameterized tool_name. Fixed in commit 2a83666 by widening each run()'s tool_name parameter from str to object and calling it directly.
Classification: unclear agent instruction -- no standing instruction says to check for and reuse an existing parametrizable test helper already present in the same file before hand-writing a new one; a code-reuse judgment call review caught, not something a duplication-detection gate could flag reliably without false positives.
Status: unclear-agent-instruction
[Self-directed audit: missing ssot.json registry entry] Prompted directly (not by CI or review) after the two-layer review, a registry-linkage audit found check-merge-pull-request-block.sh -- the hook carrying this PR's most severe fix (repair 5) -- had no .gitapex/ssot.jsongates[] entry at all, and the three already-registered gates this PR touches carried rule text predating every fail-closed guard added across repairs 2-7. .github/scripts/gitapex_scan_ssot_schema.py's own eight drift checks only validate that a registered entry points to a real file, never the reverse -- which is why CI never caught the missing entry; it was still covered by skill-audit-disclosure's own separate naming-convention backstop. Fixed in commit a3cd021: added the missing entry and refreshed the three stale rule texts.
Classification: missing deterministic gate.
Status: missing-deterministic-gate
Proposed gate: implement issue .gitapex/ssot.json: drift gate has no reverse-direction check (unregistered gate-shaped file passes silently) #1227's own already-filed proposal -- extend gitapex_scan_ssot_schema.py with a ninth, reverse-direction check that walks the same naming-convention regex gitapex_detect_changed_gate_scripts.py already applies for a different purpose, and fails when a matching gate-shaped file has no gates[] entry.
[Merge conflict: concurrent independent session fixed the identical gap] Merging origin/main to pick up unrelated upstream work produced a genuine conflict in .gitapex/ssot.json: a separate, concurrent PR (refactor(hooks): consolidate hooks.json description into ssot.json registry #1223, issue refactor(hooks): consolidate hooks.json description into ssot.json gate registry #1222) had independently added an equivalent merge-pull-request-block entry at a different array position with different rule/local_exclusion text. Resolved in commit ce558d5 by dropping this PR's own duplicate entry (which would otherwise have violated find_duplicate_ids) and merging the fail-closed-mechanics sentence from this PR's own draft into main's existing entry's rule field, preserving both sessions' information rather than picking one side. Documented via a PR comment per this skill's own mandatory conflict-disclosure rule.
Classification: external/human decision -- ordinary concurrent-development timing (two independent sessions closing the identical registry gap in parallel) is not a pattern any deterministic gate or clearer instruction could have prevented.
Status: external-human-decision
[Completeness audit: drift gate's own rule text didn't disclose its own gap] A follow-up completeness audit (prompted directly, after the merge above) found the ssot-schema-drift gate's own rule text accurately described what it currently checks but never disclosed the reverse-direction gap that let repair 9 happen -- the same gap issue .gitapex/ssot.json: drift gate has no reverse-direction check (unregistered gate-shaped file passes silently) #1227 now tracks. Fixed in commit 20c5bfa by adding a one-sentence disclosure citing .gitapex/ssot.json: drift gate has no reverse-direction check (unregistered gate-shaped file passes silently) #1227.
Classification: unclear agent instruction -- no standing instruction says that filing an issue describing a gate's own known limitation must also update that gate's own ssot.json rule text in the same breath to disclose it inline; a self-disclosure-completeness judgment call, not something a script could enforce without a speculative, over-fit gate.
Status: unclear-agent-instruction
[Final merge: inherited two pre-existing main-branch CI fixes] Responding to a direct report that CI was red, both flagged checks (pytest's prose-count-vs-registry drift, ruleset-scan's live-vs-declared ruleset drift) were root-caused via isolated git worktree testing against origin/main's own tip, independent of this PR's diff -- confirming both were pre-existing main-branch bugs, already fixed upstream by unrelated work that had landed on main in the meantime. Recovered in commit 2e266b6 by merging origin/main in (a clean merge, no conflict markers); both checks confirmed success via a live get_check_runs call against the merge commit afterward, not assumed from static reasoning.
Classification: external/human decision -- ordinary concurrent-development timing (unrelated work fixing pre-existing bugs on main while this PR was in flight), the same pattern issue chore(retrospective): merge retrospective for PR #1234 #1236's own repair 5 already modeled for the immediately preceding PR.
Status: external-human-decision
Carried-forward gate
Issue #1226 (retrospective for PR #1223) first surfaced this backlog and proposed a periodic (not per-PR) batch-triage pass over retrospective-labelled issues that fail the two-signal resolution check, since doing this per-PR does not scale once the backlog reaches this size. Issue #1235 (retrospective for PR #1233) re-ran the check and found 291 of 294 candidates unresolved, still only #187/#285/#536 resolved. Issue #1236 (retrospective for PR #1234, the immediately preceding cycle) re-ran it again with the same result.
Re-running the identical two-signal check this cycle (295 prior candidates: the full retrospective-labelled backlog minus this cycle's own stub #1237, now including #1236 itself as a prior candidate for the first time) against the full, unshallowed history of origin/main post-merge (2e266b6): still only 3 resolved (#187, #285, #536, unchanged across at least 4 consecutive cycles now), 292 unresolved -- exactly one more than #1236's own 291, fully explained by #1236 itself now counting as a prior candidate instead of the current cycle's own stub. This PR's own commits cite #1208, #1216, #1217, #1218, and #1227 -- none among the backlog numbers. No implementing commit for #1226's own proposed periodic-triage mechanism exists on origin/main as of this cycle either. Following #1226/#1235/#1236's own established precedent, the complete number list is not repeated a fourth consecutive time here -- see #1226's own body for the original full enumeration.
Status: carried-forward
Proposed gate: (restated from #1226, unimplemented across at least 4 consecutive cycles now including this one) a periodic (not per-PR) batch-triage pass over retrospective-labelled issues that fail the two-signal resolution check -- reading each one's actual proposed gate, and for each: confirm it is still relevant, close it explicitly if superseded/no-longer-applicable, or convert it into real follow-on implementation work if still worth building.
Summary
Retrospective for PR #1213 ("fix(hooks): fail closed on missing/malformed jq in four PreToolUse gates", which addressed #1208), merged 2026-08-19T22:18:37Z by the repository owner. Twelve repairs occurred between PR open (2026-08-18T15:08:11Z) and merge, across ten commits: one Codecov patch-coverage fix, one CodeRabbit review round, two independent adversarial-review rounds (bundling four distinct bug fixes across a single commit plus a fifth in a following commit), one code-reuse finding from this skill's own two-layer review, a self-directed registry-linkage audit (prompted by a direct question, not CI or review) that found and fixed a missing
.gitapex/ssot.jsonentry plus stale rule text on three others, a merge conflict from a concurrent independent session fixing the identical registry gap, a follow-up completeness audit disclosing a gap in the drift gate's own rule text, and a final merge inheriting two pre-existingmain-branch CI fixes unrelated to this PR's own diff.Repairs
[Codecov patch-coverage: unreachable skip branch] The template-overwrite test suite's own
pytest.skipfallback (for when no real PR template exists in the checkout) was never exercised in this repository's own CI, since a template always exists here -- Codecov's patch-coverage gate flagged the line as uncovered and failed the check. Fixed in commitad67a28by replacing the checkout scan with a self-containedtmp_pathfixture that creates its own template file, removing the unreachable branch entirely rather than covering it artificially.Classification: missing deterministic gate.
Status:
missing-deterministic-gateProposed gate: a pre-push (or pre-commit) stage running
pytest --covscoped to new/changed test files only, mirroring Codecov's own patch-coverage gate locally and fast, so an unreachable branch in a new test is caught before it ever reaches CI.[CodeRabbit: tool_input
false-falsy gap] The tool_input-shape guard these hooks had just gained,(.tool_input // {}) | type == "object", accepted the JSON literalfalsethe same asnullor an absent key (jq's//treats both as falsy) -- atool_input: falsepayload slipped past the guard and crashed the next field-extraction line instead of denying. Live-confirmed, then fixed in commitde7e6bdby tightening the predicate to(.tool_input == null) or (.tool_input | type == "object"), verified against the full value matrix.Classification: missing deterministic gate.
Status:
missing-deterministic-gateProposed gate: see repair 5's proposed gate below -- the same general type-confusion matrix test would have caught this instance too.
[Adversarial round: warn()'s ARG_MAX-vulnerable jq construction]
check-bash-safety.sh'swarn()(the non-blocking counterpart to the already-hardeneddeny()) still built its JSON viajq -n --arg, the exact constructiondeny()had already been hardened away from. Its only call site interpolates the provenance scanner's report over the entire outgoing push, large enough to realistically hit the limit -- live-confirmed a crash (exit 126) on a 3MB message. Fixed in commit0b0a3af(bundled with repairs 4-6 below in the same push) with the samejq -Rspiped-stdin construction already proven indeny().Classification: missing deterministic gate.
Status:
missing-deterministic-gateProposed gate: a consistency check (e.g. a shape-checker rule) flagging any
hooks/*.shfile wheredeny()uses the safejq -Rspiped-stdin form but a sibling function (warn()or equivalent) in the same file still usesjq -n --arg, so a hardening applied to one does not silently stop short of the other.[Adversarial round: test suite narrower than its own docstring claims] The same dispatch found the tool_input tests never actually included the "string" case their own docstrings claimed to cover, and no hook had a regression test proving
tool_input: null/absent still correctly allows the call through. Both added in commit0b0a3af.Classification: missing deterministic gate.
Status:
missing-deterministic-gateProposed gate: same as repair 5 below -- a single parametrized fixture enumerating the full value matrix (including the allow-through baseline) per guard, rather than individual cases added reactively per review round.
[Adversarial round, most severe: non-string tool_name bypass]
jq -rnever errors on a non-string.tool_name(e.g. an array) -- it pretty-prints the JSON value across multiple lines, which then never matches the plain-string re-check every one of these hooks' own defense-in-depth relies on, silently falling through as "not our tool" instead of failing closed. Most severe oncheck-merge-pull-request-block.sh: an array-wrappedtool_namelet a realmerge_pull_requestcall straight through this repository's own categorical "no override" deny -- the exact bypass class issue hooks/: four more hooks fail open on missing/malformed jq (same class as gitapex#436) #1208 exists to close, via a different field than the one it named. Live-confirmed across all four hooks, fixed in commit0b0a3afwith(.tool_name == null) or (.tool_name | type == "string").Classification: missing deterministic gate.
Status:
missing-deterministic-gateProposed gate: a parametrized property test enumerating jq's full falsy/type-confusion matrix (absent, null, false, true, 0, "", array, object) against every
(.field == null) or (.field | type == "X")guard inhooks/*.sh, run in CI. This exact bug class needed four separate adversarial rounds (repairs 2, 5, 7, plus this one) to fully stamp out across four files -- a systematic matrix test, ideally written once against issue hooks/: extract the guard prologue into a shared sourced helper (duplication caused #1216 and #1217) #1218's own already-proposed shared sourced guard-prologue helper rather than duplicated per file, would have caught all four in one pass instead of four.[Adversarial round: unguarded mktemp crash] Two unguarded
var=$(mktemp)calls incheck-pr-skill-audit-disclosure.sh's tier-1/tier-2 logic crashed the whole script underset -eon an unwritable/fullTMPDIR, instead of the intended degrade-to-tier-2-then-CI fallback every other tier-1-incomplete path in that hook already takes. Live-confirmed, fixed in commit0b0a3afto fall through with a warning instead.Classification: missing deterministic gate.
Status:
missing-deterministic-gateProposed gate: a static check flagging any
$(mktemp ...)call inhooks/*.shnot immediately guarded by a fallback, sinceset -ealone converts an unguardedmktempfailure into a hard crash rather than the graceful degrade these hooks otherwise implement consistently.[Adversarial round: leaf-field type confusion one level deeper] The same jq-pretty-print gap as repair 5, found one level deeper:
check-bash-safety.sh's.tool_input.commandandcheck-template-overwrite.sh's.tool_input.file_path, both feeding a blacklist-style danger-pattern match rather than a positive-match-required-to-pass check, so an array/object value slipped through as "no match" instead of failing closed. Live-confirmed with an array-wrapped["gh","pr","merge","1"]command and an array-wrappedfile_pathtargeting the real, on-disk PR template. Fixed in commit3236ee7.Classification: missing deterministic gate.
Status:
missing-deterministic-gateProposed gate: same as repair 5 above.
[Two-layer review, inner layer: hand-built test instead of reusing existing helper] Three
test_denied_when_tool_name_is_not_a_stringtests hand-built their own ~15-line rawsubprocess.runblock instead of reusing each file's own pre-existingrun()helper, which already parameterizedtool_name. Fixed in commit2a83666by widening eachrun()'stool_nameparameter fromstrtoobjectand calling it directly.Classification: unclear agent instruction -- no standing instruction says to check for and reuse an existing parametrizable test helper already present in the same file before hand-writing a new one; a code-reuse judgment call review caught, not something a duplication-detection gate could flag reliably without false positives.
Status:
unclear-agent-instruction[Self-directed audit: missing ssot.json registry entry] Prompted directly (not by CI or review) after the two-layer review, a registry-linkage audit found
check-merge-pull-request-block.sh-- the hook carrying this PR's most severe fix (repair 5) -- had no.gitapex/ssot.jsongates[]entry at all, and the three already-registered gates this PR touches carriedruletext predating every fail-closed guard added across repairs 2-7..github/scripts/gitapex_scan_ssot_schema.py's own eight drift checks only validate that a registered entry points to a real file, never the reverse -- which is why CI never caught the missing entry; it was still covered byskill-audit-disclosure's own separate naming-convention backstop. Fixed in commita3cd021: added the missing entry and refreshed the three staleruletexts.Classification: missing deterministic gate.
Status:
missing-deterministic-gateProposed gate: implement issue .gitapex/ssot.json: drift gate has no reverse-direction check (unregistered gate-shaped file passes silently) #1227's own already-filed proposal -- extend
gitapex_scan_ssot_schema.pywith a ninth, reverse-direction check that walks the same naming-convention regexgitapex_detect_changed_gate_scripts.pyalready applies for a different purpose, and fails when a matching gate-shaped file has nogates[]entry.[Merge conflict: concurrent independent session fixed the identical gap] Merging
origin/mainto pick up unrelated upstream work produced a genuine conflict in.gitapex/ssot.json: a separate, concurrent PR (refactor(hooks): consolidate hooks.json description into ssot.json registry #1223, issue refactor(hooks): consolidate hooks.json description into ssot.json gate registry #1222) had independently added an equivalentmerge-pull-request-blockentry at a different array position with differentrule/local_exclusiontext. Resolved in commitce558d5by dropping this PR's own duplicate entry (which would otherwise have violatedfind_duplicate_ids) and merging the fail-closed-mechanics sentence from this PR's own draft into main's existing entry'srulefield, preserving both sessions' information rather than picking one side. Documented via a PR comment per this skill's own mandatory conflict-disclosure rule.Classification: external/human decision -- ordinary concurrent-development timing (two independent sessions closing the identical registry gap in parallel) is not a pattern any deterministic gate or clearer instruction could have prevented.
Status:
external-human-decision[Completeness audit: drift gate's own rule text didn't disclose its own gap] A follow-up completeness audit (prompted directly, after the merge above) found the
ssot-schema-driftgate's ownruletext accurately described what it currently checks but never disclosed the reverse-direction gap that let repair 9 happen -- the same gap issue .gitapex/ssot.json: drift gate has no reverse-direction check (unregistered gate-shaped file passes silently) #1227 now tracks. Fixed in commit20c5bfaby adding a one-sentence disclosure citing .gitapex/ssot.json: drift gate has no reverse-direction check (unregistered gate-shaped file passes silently) #1227.Classification: unclear agent instruction -- no standing instruction says that filing an issue describing a gate's own known limitation must also update that gate's own
ssot.jsonrule text in the same breath to disclose it inline; a self-disclosure-completeness judgment call, not something a script could enforce without a speculative, over-fit gate.Status:
unclear-agent-instruction[Final merge: inherited two pre-existing main-branch CI fixes] Responding to a direct report that CI was red, both flagged checks (
pytest's prose-count-vs-registry drift,ruleset-scan's live-vs-declared ruleset drift) were root-caused via isolatedgit worktreetesting againstorigin/main's own tip, independent of this PR's diff -- confirming both were pre-existingmain-branch bugs, already fixed upstream by unrelated work that had landed onmainin the meantime. Recovered in commit2e266b6by mergingorigin/mainin (a clean merge, no conflict markers); both checks confirmedsuccessvia a liveget_check_runscall against the merge commit afterward, not assumed from static reasoning.Classification: external/human decision -- ordinary concurrent-development timing (unrelated work fixing pre-existing bugs on
mainwhile this PR was in flight), the same pattern issue chore(retrospective): merge retrospective for PR #1234 #1236's own repair 5 already modeled for the immediately preceding PR.Status:
external-human-decisionCarried-forward gate
Issue #1226 (retrospective for PR #1223) first surfaced this backlog and proposed a periodic (not per-PR) batch-triage pass over
retrospective-labelled issues that fail the two-signal resolution check, since doing this per-PR does not scale once the backlog reaches this size. Issue #1235 (retrospective for PR #1233) re-ran the check and found 291 of 294 candidates unresolved, still only #187/#285/#536 resolved. Issue #1236 (retrospective for PR #1234, the immediately preceding cycle) re-ran it again with the same result.Re-running the identical two-signal check this cycle (295 prior candidates: the full
retrospective-labelled backlog minus this cycle's own stub #1237, now including #1236 itself as a prior candidate for the first time) against the full, unshallowed history oforigin/mainpost-merge (2e266b6): still only 3 resolved (#187, #285, #536, unchanged across at least 4 consecutive cycles now), 292 unresolved -- exactly one more than #1236's own 291, fully explained by #1236 itself now counting as a prior candidate instead of the current cycle's own stub. This PR's own commits cite #1208, #1216, #1217, #1218, and #1227 -- none among the backlog numbers. No implementing commit for #1226's own proposed periodic-triage mechanism exists onorigin/mainas of this cycle either. Following #1226/#1235/#1236's own established precedent, the complete number list is not repeated a fourth consecutive time here -- see #1226's own body for the original full enumeration.Status:
carried-forwardProposed gate: (restated from #1226, unimplemented across at least 4 consecutive cycles now including this one) a periodic (not per-PR) batch-triage pass over
retrospective-labelled issues that fail the two-signal resolution check -- reading each one's actual proposed gate, and for each: confirm it is still relevant, close it explicitly if superseded/no-longer-applicable, or convert it into real follow-on implementation work if still worth building.Refs
Refs #1213, #1208.