Skip to content

fix(OMN-18233): closer ignores a closed cascade bump only on a verified-supersession predicate - #3465

Merged
jonahgabriel merged 5 commits into
devfrom
jonah/omn-18233-verified-supersession-predicate
Sep 12, 2026
Merged

jonahgabriel merged 5 commits into
devfrom
jonah/omn-18233-verified-supersession-predicate

Conversation

@jonahgabriel

@jonahgabriel jonahgabriel commented Sep 12, 2026 •

Copy link
Copy Markdown
Collaborator

Closes OMN-18233. Phase 1 item 1 of epic OMN-18232 (process-friction remediation plan, revision 2, approved on the knowledge-base-internal#380 merge at 2026-09-12T15:53:07Z).

Problem

The closer's cited-PR merge conjunct reads merged_at and refuses any citation without one. That is correct for an OPEN pull request, which can still merge and is re-offered next tick. It is permanently wrong for a CLOSED one, which never becomes merged. Cascade bump pull requests carry the RELEASING ticket's id, so every ticket whose release opens downstream bumps inherits the block forever. OMN-18201 is the measured case: all four criteria evidenced, flip refused because omnibase_infra#3446 closed unmerged in favour of #3448.

Ignoring closed bumps outright is what the plan review refused, correctly. A closed bump with no replacement is abandoned work and must keep blocking.

Mechanism

A closed-unmerged citation is ignorable only when supersession is PROVEN by four clauses, all of which must resolve:

  1. the closed pull request carries a machine-readable cascade provenance block declaring a source package and a required version;
  2. a MERGED pull request in the same repository moved that package's pin or lockfile entry, proven by reading the pinned version at that pull request's base commit and at its merge commit and requiring them to differ;
  3. the delivered version is at or above the required version, compared as a PEP 440 version rather than as a string;
  4. the delivered version is readable from the repository's own default branch, not only from a pull request body.

Two properties are load-bearing and each is pinned by its own test. Nothing reads a title -- one test retitles every pull request in the fixture and asserts the verdict does not move. Ordering is not a clause -- on the real pair the replacement merged roughly three hours BEFORE the superseded bump was closed, so a merge-after-close requirement would refuse the single case this predicate was built for.

Every clause fails CLOSED. An unreadable default branch, an unparseable version, an unfound replacement and a bounded walk that ran out all resolve to "this bump still blocks", never to "so I will ignore it".

  • tests/unit/nodes/node_evidence_autoclose_sweep_effect/test_omn_18233_verified_supersession.py, 23 tests, all green. RED first: the file was written before the module existed and failed to import.
    • Green case: the real #3446 / #3448 pair replayed from live readbacks, including the ordering trap, resolving every clause and flipping the OMN-18201-shaped candidate end to end through the handler.
    • Red cases, one per clause: no provenance block; no merged replacement at all; a merged pull request that touched the pin file without moving the pin; an unmerged candidate; a delivered version below the required one; an unreadable default-branch pin.
    • A string-versus-version control: required 0.47.9, delivered 0.47.12. Lexically the required version is the larger string, so a string comparison would refuse the most common real shape.
    • A non-regression control: an OPEN cited pull request still blocks, with state=OPEN in the refusal, so this narrowing is not a way past the original conjunct.
  • Live readbacks the fixture is built from, 2026-09-12: #3446 closed_at 2026-09-12T05:56:58Z with merged_at null and provenance naming OmniNode-ai/omnibase_core at 0.47.12; #3448 merged_at 2026-09-12T03:14:52Z touching pyproject.toml and uv.lock; omnibase_infra@dev pins omnibase-core==0.47.12.
  • Full closer suite: 476 tests across all 27 modules of tests/unit/nodes/node_evidence_autoclose_sweep_effect/, all green.
  • mypy --strict clean on the node. pre-commit run --all-files exit 0.

Gate findings fixed in this change, not worked around

Three repository gates fired and all three were fixed at the cause rather than routed around.

The architecture validator refused two models in one file, so the models are split one per file.

The handler contract compliance scanner audits every module under a node's handlers/ directory as a handler and requires each one to appear in the contract's handler_routing. The predicate module is routed by nothing and is called as a function by the sweep handler, so it could never satisfy that rule and exempting it would have been a false statement about what it is. It is therefore not in handlers/ at all: it sits at the node root, which is where non-handler node modules already live in this repository, and its docstring says why so a later edit does not move it back.

That relocation also removed the third finding. An earlier revision of this branch registered the module in the infra node-handler ownership allowlist, which the move made unnecessary; the entry is deleted rather than left stale, and the ownership ratchet passes with the allowlist byte-identical to dev.

Enforcement wiring

No new detection surface is added, so there is no new gate to wire. The predicate is a narrowing inside an existing node whose tests are already part of this repository's CI suite, and the scheduled closer (.github/workflows/evidence-autoclose-sweep.yml) is its production caller.

Runtime surface

The module ships in the package and therefore in the runtime image, but the changed code path executes only in the scheduled closer job. It adds no dispatcher, no subscription, no migration and no startup wiring, so image boot is unchanged. The automatic lab pass on the merged sha covers the boot surface; no lab lane exercises this code path, and this PR does not claim one.

Evidence-Ticket: OMN-18233
Evidence-Source: OCC#9275

…ed-supersession predicate

The cited-PR merge conjunct reads merged_at and refuses anything without
one. That is right for an OPEN pull request, which can still merge, and
permanently wrong for a CLOSED one, which cannot. Cascade bump pull
requests carry the releasing ticket's id, so every ticket whose release
opens downstream bumps inherits a permanent block: OMN-18201 has all four
criteria evidenced and is refused because omnibase_infra#3446 closed
unmerged in favour of #3448.

Ignoring closed bumps outright is what the plan review refused. A closed
bump with no replacement is abandoned work and must keep blocking. So the
bump is ignorable only when supersession is PROVEN by four clauses: the
closed pull request declares cascade provenance naming a source package
and a required version; a merged pull request in the same repository moved
that package's pin or lockfile entry; the delivered version is at or above
the required one, compared as a version rather than as a string; and the
delivered version is readable from the repository's own default branch.

Nothing reads a title, and ordering is not a clause -- on the real pair
the replacement merged about three hours before the superseded bump was
closed, so a merge-after-close requirement would refuse the one case this
was built for. Every clause fails closed.
@github-actions

github-actions Bot commented Sep 12, 2026 •

Copy link
Copy Markdown
Contributor

⚠️ Hostile Reviewer — DEGRADED (informational)

Critical findings: 0
Major findings: 0
Total findings: 0
Models succeeded: glm-review

Note: Fewer than 2 reviewer models succeeded. Degraded results are informational (OMN-8468/OMN-8524) and do not block merge. Error: cli_review exit 2 (fewer than 2 models succeeded — partial/total outage)


Semantics (OMN-17492 — the model finds, thread resolution gates)

Surface Meaning Blocks merge?
Review threads Per-finding, posted by the reviewer No (informational)
Hostile Review Thread Gate Deterministic: unresolved hostile-reviewer threads exist Fails until resolved (not yet a required context)
degraded verdict Fewer than 2 models succeeded (infra) No

Powered by omniintelligence.review_pairing.cli_review — multi-model adversarial review: qwen3-review, qwen3-review-b, glm-review (OMN-8468/OMN-8524/OMN-17492)

jonahgabriel pushed a commit to OmniNode-ai/onex_change_control that referenced this pull request Sep 12, 2026
#9271)

* evidence: OCC companion pass 1 for OmniNode-ai/omnibase_infra#3465

* evidence: OCC companion self-bind for #9271

---------

Co-authored-by: node-occ-companion-effect <occ-companion-effect@omninode.ai>
…is not a handler

The handler contract compliance scanner audits every module under a node's
handlers/ directory as a handler and requires each one to appear in the
contract's handler_routing. This module is routed by nothing and is called as a
function by the sweep handler, so satisfying that rule was impossible and
exempting it from the rule would have been a lie about what it is.

Moved to the node root, which is where non-handler node modules already live
(node_chain_canary_effect carries its lane transport the same way), and the
infra node-handler ownership allowlist entry it needed under handlers/ is
removed rather than left stale. The docstring now says why it sits there so a
later edit does not move it back.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hostile Reviewer — adversarial findings (OMN-17492)

Models succeeded: glm-review
Models failed: codex
New finding threads: 0
Deduped (already posted on this PR): 0
Nit-level findings suppressed: 1

The model is the FINDER, never the gate: merge is gated only by the
deterministic Hostile Review Thread Gate, which blocks while
hostile-reviewer threads are unresolved. Resolve each thread after
addressing (or rejecting, with a reply) its finding.

Findings not anchored to a changed file

  • [MAJOR] hostile-reviewer (glm-review)

    Clause 2 compares versions across different pin sources | _pull_request_moved_the_pin reads before/after via _read_pinned_version, which prefers pyproject.toml and falls back to uv.lock. When a repo constrains rather than pins (the docstring itself anticipates this: 'a repo that constrains rather than pins only has the latter'), a replacement PR that bumps only the lockfile leaves pyproject.toml unchanged. The before/after reads both hit pyproject.toml, return the same version, and clause 2 refutes a genuin

    Resolve this thread when addressed — the Hostile Review Thread Gate blocks while hostile-reviewer threads are unresolved (OMN-17492).

  • [MAJOR] hostile-reviewer (glm-review)

    Unbounded sequential API fan-out per closed PR per sweep tick | The clause 2 walk issues up to 2 (paths) x 20 (commits) commit-lookup calls plus up to 10 candidate evaluations at 3 calls each, all sequentially, inside the sweep handler for every ticket whose citation is a closed unmerged PR. A closed PR is immutable; the verdict is deterministic modulo default-branch drift, yet the full walk reruns every sweep tick forever. This multiplies gh API quota consumption and sweep latency linearly with the number

    Resolve this thread when addressed — the Hostile Review Thread Gate blocks while hostile-reviewer threads are unresolved (OMN-17492).

  • [MINOR] hostile-reviewer (glm-review)

    Candidate exhaustion message overstates what was examined | The exhaustion branch claims it examined 'the 10 most recent merged pull requests touching pyproject.toml/uv.lock'. The examined counter only increments for PRs that are merged, not the closed PR, and deduplicated; moreover exhaustion can fire on the pyproject path alone, after which uv.lock commits are never walked. The claim that all recent PRs across both paths were examined is not what happened. | Evidence: return 0, (f"clause 2 unresolved: exa

    Resolve this thread when addressed — the Hostile Review Thread Gate blocks while hostile-reviewer threads are unresolved (OMN-17492).

  • [MINOR] hostile-reviewer (glm-review)

    pyproject pin regex misses extras-decorated requirements | The pattern requires == immediately after the distribution name. A dependency spelled "omnibase-core[server]==0.47.12" produces no match (name regex stops at '[' and the version group then sees '[server]=='), so the predicate reports no pin and defers to the lockfile. Functional via fallback, but the docstring claims an exact pin in pyproject is 'the declaration'; that claim silently fails for any extras-qualified requirement. | Evidence: r"""["']\s

    Resolve this thread when addressed — the Hostile Review Thread Gate blocks while hostile-reviewer threads are unresolved (OMN-17492).

  • [MINOR] hostile-reviewer (glm-review)

    Lockfile parser assumes name precedes version within a package block | pinned_version_from_lockfile tracks current_name and returns only on a version line seen after the matching name line. TOML key order within a table is not semantically significant; a [[package]] block spelling version before name resolves to nothing, and the caller treats that as an unresolvable clause 4 rather than a parser limitation. | Evidence: name_match = re.match(...); ... version_match = re.match(...); if version_match and curre

    Resolve this thread when addressed — the Hostile Review Thread Gate blocks while hostile-reviewer threads are unresolved (OMN-17492).

  • [MINOR] hostile-reviewer (glm-review)

    PR body is untrusted input that gates an auto-close action | parse_cascade_provenance treats any PR body containing a plausible provenance block as a cascade bump. The remaining clauses require real repository state, which limits forgery, but an adversary who can open a PR in the product repo can author provenance naming a package whose default branch already satisfies a higher version plus an unrelated merged PR that moved that pin, flipping a ticket the original merged_at conjunct protected. There is no c

    Resolve this thread when addressed — the Hostile Review Thread Gate blocks while hostile-reviewer threads are unresolved (OMN-17492).

  • [MINOR] hostile-reviewer (glm-review)

    No tests for partial gh failures mid-walk | Every call site discards the error string. The FakeGh returns errors only for unmapped paths; no test exercises a transient gh failure at, say, the clause 2 commit-lookup step on a PR that would otherwise have qualified. The design intent (fail closed) is stated, but the observation that a transient 500 permanently holds the ticket with a misleading 'clause 2 unresolved: no merged pull request was found' detail is untested and the detail message would be a lie abo

    Resolve this thread when addressed — the Hostile Review Thread Gate blocks while hostile-reviewer threads are unresolved (OMN-17492).

  • [MINOR] hostile-reviewer (glm-review)

    Test double re-implements gh routing, coupling tests to URL shapes | FakeGh dispatches on substring matches ('/files' in path, 'state=closed' in path, path.count('/')). Any incidental change to a gh path elsewhere in the handler, or a route that happens to contain 'state=closed', silently routes to the wrong stub branch or an unmapped error, producing false positives or false negatives unrelated to the predicate under test. | Evidence: if "/files" in path and "/pulls/" in path: ... if "state=closed" in path

    Resolve this thread when addressed — the Hostile Review Thread Gate blocks while hostile-reviewer threads are unresolved (OMN-17492).

  • [MINOR] hostile-reviewer (glm-review)

    Race between clause 4 read and clause 2 walk on a moving default branch | The default-branch pin is read at the branch head for clause 3/4, then clause 2 evaluates candidate PRs against their historical merge commits. If a commit lands between these reads (or between the two reads inside a candidate evaluation), the verdict can mix evidence from two different branch states: delivered_version from the new head, replacement detail from the old head. The predicate is deterministic in neither direction and the

    Resolve this thread when addressed — the Hostile Review Thread Gate blocks while hostile-reviewer threads are unresolved (OMN-17492).

@jonahgabriel

Copy link
Copy Markdown
Collaborator Author

Manual merge sweep update: pushed ccabad2 to address the hostile-review version-source finding. Clause 2 now reads before/after versions by pin source and compares each source path independently, so an unchanged pyproject constraint cannot hide a moved uv.lock resolution. Also hardened pyproject extras pins and lockfile key order.\n\nLocal verification before push:\n- uv run ruff format src/omnibase_infra/nodes/node_evidence_autoclose_sweep_effect/cascade_supersession.py tests/unit/nodes/node_evidence_autoclose_sweep_effect/test_omn_18233_verified_supersession.py\n- uv run ruff check src/omnibase_infra/nodes/node_evidence_autoclose_sweep_effect/cascade_supersession.py tests/unit/nodes/node_evidence_autoclose_sweep_effect/test_omn_18233_verified_supersession.py\n- uv run pytest tests/unit/nodes/node_evidence_autoclose_sweep_effect/test_omn_18233_verified_supersession.py -q (26 passed)\n- commit/pre-push hooks passed; deploy-scope hook reported NOTICE_COMPANION_UNMERGED, expected until the OCC companion refresh lands.

jonahgabriel added a commit to OmniNode-ai/onex_change_control that referenced this pull request Sep 12, 2026
…ibase_infra#3465 (#9275)

* evidence(OMN-18233): author OCC companion for OmniNode-ai/omnibase_infra#3465

OCC companion by node_pr_lifecycle_fix_effect (OMN-13317 F1 / OMN-13990 / OMN-14285). Product PR head dffab4778f2b426f921ad9992cbe83f80ee84016.

* evidence(OMN-18233): self-bind OCC#9275 + rebind contract_sha256

* fix(OMN-18233): repair OCC companion evidence checks

---------

Co-authored-by: omnimarket-bot <bot@omninode.ai>
Co-authored-by: jonahgabriel <jonah@omninode.ai>

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hostile Reviewer — adversarial findings (OMN-17492)

Models succeeded: glm-review
Models failed: codex
New finding threads: 1
Deduped (already posted on this PR): 0
Nit-level findings suppressed: 1

The model is the FINDER, never the gate: merge is gated only by the
deterministic Hostile Review Thread Gate, which blocks while
hostile-reviewer threads are unresolved. Resolve each thread after
addressing (or rejecting, with a reply) its finding.

Findings not anchored to a changed file

  • [CRITICAL] hostile-reviewer (glm-review)

    No linkage between closed PR and the replacement that discharges it | The predicate never establishes that the closed PR was itself a dependency bump delivering the provenance-declared version, nor that the replacement PR delivered that version. Clauses are satisfiable by an arbitrary closed PR whose body merely contains a 'cascade provenance' heading: the body is fully author-controlled, and clauses 2 through 4 read only public repository state (a pin move somewhere in history, default-branch version >= de

    Resolve this thread when addressed — the Hostile Review Thread Gate blocks while hostile-reviewer threads are unresolved (OMN-17492).

  • [MAJOR] hostile-reviewer (glm-review)

    Clause 2 walk issues unbounded N+1 GitHub API calls per citation | For each closed citation the walk requests up to 20 commits per pin path, one pulls-per-commit call per commit (up to 40), and for each unique merged candidate two full contents fetches per pin path (4 calls), before any caching. A ticket citing several closed bumps multiplies this. Under GitHub's 5000 req/h REST limit a sweep over a few dozen tickets can exhaust the token and degrade every other consumer sharing it. | Evidence: _merged_pr_t

    Resolve this thread when addressed — the Hostile Review Thread Gate blocks while hostile-reviewer threads are unresolved (OMN-17492).

  • [MAJOR] hostile-reviewer (glm-review)

    Early return at candidate cap discards later qualifying PRs and misreports | When the 11th unique merged candidate appears, the function returns 0 immediately without examining it. If that candidate moved the pin, the verdict says 'examined the 10 most recent... none moved the pin', which is false as stated and, more importantly, the cap is applied per unique-candidate encounter across both paths in walk order rather than by recency of the pin move, so the '10 most recent' claim is not guaranteed. | Evidenc

    Resolve this thread when addressed — the Hostile Review Thread Gate blocks while hostile-reviewer threads are unresolved (OMN-17492).

  • [MINOR] hostile-reviewer (glm-review)

    Pin reader matches commented-out pyproject entries | pinned_version_from_pyproject scans raw text with no comment stripping. A line like '# "omnibase-core==0.99.0"' (leftover from a rollback or upgrade script) yields version 0.99.0, which passes clause 3's comparison even though no such pin is effective. The same defect applies to the base/merge comparison, where a commented pin can fabricate a 'pin move'. | Evidence: pattern = re.compile(r"""['"]\s*([\w.-]+)(?:[[^\]]+])?\s*==\s*([^'\",\s\[\]]+)""",); fo

    Resolve this thread when addressed — the Hostile Review Thread Gate blocks while hostile-reviewer threads are unresolved (OMN-17492).

  • [MINOR] hostile-reviewer (glm-review)

    GitHub errors indistinguishable from absence, producing misleading hold reasons | Every _error return value is discarded. A 403 rate-limit or 5xx on the contents endpoint produces the same 'clause 4 unresolved: no version readable' detail as a genuine 404, telling the on-call reader the repo has no pin when the truth is an infrastructure failure. Fail-closed is correct; the receipt is not. | Evidence: payload, _error = await run_gh_command([...], gh_timeout_seconds); if not isinstance(payload, dict): contin

    Resolve this thread when addressed — the Hostile Review Thread Gate blocks while hostile-reviewer threads are unresolved (OMN-17492).

  • [MINOR] hostile-reviewer (glm-review)

    Provenance heading match accepts duplicate or nested sections loosely | The parser takes the first 'cascade provenance' heading and stops at the next heading of any level, but a body containing a second provenance block (e.g., quoting another bump's provenance inside a subsection before the real fields) yields whichever block appears first. source_repo/required_version bind to the first matching heading regardless of position relative to 'Triggered by' or other structural anchors. | Evidence: if _PROVENANCE

    Resolve this thread when addressed — the Hostile Review Thread Gate blocks while hostile-reviewer threads are unresolved (OMN-17492).

Findings demoted from threads (anchor rejected)

  • [MINOR] hostile-reviewer (glm-review)

    Untested paths: candidate cap, transient gh errors, lockfile parsing edge | No test exercises the _MAX_REPLACEMENT_CANDIDATES exhaustion branch, non-empty gh error returns with None payloads anywhere in the walk, or a uv.lock whose package block contains a nested table with name/version keys. Given the module's stated invariant that every clause fails closed, the failure modes that most plausibly break that invariant are exactly the untested ones. | Evidence: tests cover green path, absent pin commits, unmo

    Resolve this thread when addressed — the Hostile Review Thread Gate blocks while hostile-reviewer threads are unresolved (OMN-17492).

…w declares

The node contract moved to 1.12.2 for this change while the fingerprint
constant still read 1.12.1, and the test that exists to stop exactly that
drift went red. The constant follows the contract: the version is part of the
gap-comment fingerprint, so a stale one would keep de-duplicating comments
against a rule the closer no longer applies.
@jonahgabriel
jonahgabriel merged commit 877610e into dev Sep 12, 2026
128 checks passed
@jonahgabriel
jonahgabriel deleted the jonah/omn-18233-verified-supersession-predicate branch September 12, 2026 22:48
Patel230 pushed a commit to OmniNode-ai/onex_change_control that referenced this pull request Sep 16, 2026
#9271)

* evidence: OCC companion pass 1 for OmniNode-ai/omnibase_infra#3465

* evidence: OCC companion self-bind for #9271

---------

Co-authored-by: node-occ-companion-effect <occ-companion-effect@omninode.ai>
Patel230 pushed a commit to OmniNode-ai/onex_change_control that referenced this pull request Sep 16, 2026
…ibase_infra#3465 (#9275)

* evidence(OMN-18233): author OCC companion for OmniNode-ai/omnibase_infra#3465

OCC companion by node_pr_lifecycle_fix_effect (OMN-13317 F1 / OMN-13990 / OMN-14285). Product PR head dffab4778f2b426f921ad9992cbe83f80ee84016.

* evidence(OMN-18233): self-bind OCC#9275 + rebind contract_sha256

* fix(OMN-18233): repair OCC companion evidence checks

---------

Co-authored-by: omnimarket-bot <bot@omninode.ai>
Co-authored-by: jonahgabriel <jonah@omninode.ai>
Patel230 pushed a commit that referenced this pull request Sep 16, 2026
…ed-supersession predicate (#3465)

* fix(OMN-18233): closer ignores a closed cascade bump only on a verified-supersession predicate

The cited-PR merge conjunct reads merged_at and refuses anything without
one. That is right for an OPEN pull request, which can still merge, and
permanently wrong for a CLOSED one, which cannot. Cascade bump pull
requests carry the releasing ticket's id, so every ticket whose release
opens downstream bumps inherits a permanent block: OMN-18201 has all four
criteria evidenced and is refused because omnibase_infra#3446 closed
unmerged in favour of #3448.

Ignoring closed bumps outright is what the plan review refused. A closed
bump with no replacement is abandoned work and must keep blocking. So the
bump is ignorable only when supersession is PROVEN by four clauses: the
closed pull request declares cascade provenance naming a source package
and a required version; a merged pull request in the same repository moved
that package's pin or lockfile entry; the delivered version is at or above
the required one, compared as a version rather than as a string; and the
delivered version is readable from the repository's own default branch.

Nothing reads a title, and ordering is not a clause -- on the real pair
the replacement merged about three hours before the superseded bump was
closed, so a merge-after-close requirement would refuse the one case this
was built for. Every clause fails closed.

* fix(OMN-18233): move the supersession predicate out of handlers/, it is not a handler

The handler contract compliance scanner audits every module under a node's
handlers/ directory as a handler and requires each one to appear in the
contract's handler_routing. This module is routed by nothing and is called as a
function by the sweep handler, so satisfying that rule was impossible and
exempting it from the rule would have been a lie about what it is.

Moved to the node root, which is where non-handler node modules already live
(node_chain_canary_effect carries its lane transport the same way), and the
infra node-handler ownership allowlist entry it needed under handlers/ is
removed rather than left stale. The docstring now says why it sits there so a
later edit does not move it back.

* fix(OMN-18233): sync autoclose contract for supersession predicate

* fix(OMN-18233): compare cascade pins by source

* fix(OMN-18233): pin the gap fingerprint to the contract version it now declares

The node contract moved to 1.12.2 for this change while the fingerprint
constant still read 1.12.1, and the test that exists to stop exactly that
drift went red. The constant follows the contract: the version is part of the
gap-comment fingerprint, so a stale one would keep de-duplicating comments
against a rule the closer no longer applies.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant