Skip to content

feat(P-035): declared weak-subscribe wrapper = accepted release (B0+B1)#284

Merged
PhysShell merged 5 commits into
mainfrom
claude/p035-b0b1-weak-subscription
Jul 16, 2026
Merged

feat(P-035): declared weak-subscribe wrapper = accepted release (B0+B1)#284
PhysShell merged 5 commits into
mainfrom
claude/p035-b0b1-weak-subscription

Conversation

@PhysShell

@PhysShell PhysShell commented Jul 16, 2026

Copy link
Copy Markdown
Owner

Implements P-035 Increment B behind a minimal P-015 config carrier (B0), per the arbiter decision. One PR = B0+B1; no Increment A / C / general AddHandler inference.

B0 — minimal P-015 carrier

  • own-check --config <own.toml> → the only public surface. Reads only [weak-subscription].subscribe (a list of "SimpleType.Method"). No auto-discovery / severity / per-path / env.
  • ownlang/config.py (tomllib): a malformed declaration is a hard error, never a silent skip. python -m ownlang config prints the names; scripts/own-check.sh forwards them to the extractor via the internal --weak-subscribe transport flag.

B1 — extractor detection + released recognition

  • A call whose exact (containing-type simple name, method name) is declared, with the MVP (source, handler) positional contract, becomes one subscription fact with released: true. Match resolves via the method symbol, or a conservative syntactic receiver-name fallback for an unresolved external package.
  • A declared wrapper named Subscribe is suppressed in the Rx .Subscribe() matcher (no double-count).
  • Empty allowlist ⇒ inert ⇒ facts byte-for-byte unchanged.

Acceptance contract (#1#10)

  • tests/test_weak_subscribe.py — the config carrier incl. every malformed-config hard-error case (auto-discovered by run_tests.py, SDK-free). 15/15 locally.
  • frontend/roslyn/samples/WeakSubscribeAllowlistSample.cs + a new step in the C# leak extractor CI job: runs the extractor with/without --weak-subscribe, asserts the facts via tests/check_weak_subscribe_facts.py (positive released:true; += still leaks; same-name-different-type / too-few-args / no-flag all produce no fact), plus own-check --config plumbing and the malformed-config hard error end-to-end.

Verified locally via WSL: run_tests.py green (incl. weak-subscribe config 15/15, no parity drift), extractor facts match the contract for the resolved and unresolved-external paths.

Out of scope (arbiter)

Increment A (unsubscribe wrapper — needs event identity first), C (project-aware fix text / 007 wiring), general AddHandler heuristic, rest of P-015.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added weak-subscription wrapper declarations via own.toml with strict TOML validation.
    • Added --weak-subscribe to recognize declared wrapper APIs as already released.
    • Added ownlang config (and --config support in own-check / the marketplace action) to surface configured weak-subscribe entries.
  • Bug Fixes
    • Prevented matching declared weak wrappers from being reported as leaked, while preserving ordinary +=.
    • Suppressed duplicate Rx “dropped subscribe” findings and added an Rx-collision regression guarantee.
  • Tests
    • Expanded weak-subscribe fact validation and CI end-to-end checks for success and hard-fail malformed config cases.

Implements P-035 Increment B behind a minimal P-015 config carrier (B0), per the
arbiter decision. A project declares its own weak-subscribe wrapper API and the
extractor treats a matching call as a first-class, already-released subscription
instead of assuming the BCL WeakEventManager.

B0 — minimal P-015 carrier (config only; no auto-discovery / severity / env):
  * ownlang/config.py — read [weak-subscription].subscribe from an explicit own.toml
    (tomllib). Only that one table/key; each entry must be exact "SimpleType.Method".
    A malformed declaration is a HARD error, never a silent skip.
  * `python -m ownlang config <own.toml>` prints the declared names for own-check.
  * scripts/own-check.sh: `--config own.toml` forwards them to the extractor via the
    internal `--weak-subscribe` transport flag; a bad config exits non-zero.

B1 — extractor detection + released recognition (only declared exact pairs; zero
behaviour without a declaration):
  * A call whose (containing-type simple name, method name) is declared, with the MVP
    (source, handler) positional-arg contract, becomes one subscription fact with
    released:true. Match resolves via the method symbol, or a conservative syntactic
    receiver-name fallback for an unresolved external package.
  * A declared wrapper named `Subscribe` is suppressed in the Rx `.Subscribe()` matcher
    so it is not double-counted.
  * Empty allowlist => the detector is inert and the Rx guard is a no-op => facts are
    byte-for-byte unchanged.

Acceptance contract (#1-#10) covered by:
  * tests/test_weak_subscribe.py — the config carrier, incl. every malformed-config
    hard-error case (auto-discovered by run_tests.py; SDK-free).
  * frontend/roslyn/samples/WeakSubscribeAllowlistSample.cs + a new CI step in the
    "C# leak extractor" job that runs the extractor with/without --weak-subscribe and
    asserts the facts via tests/check_weak_subscribe_facts.py, plus the own-check
    --config plumbing and the malformed-config hard error end-to-end.

Not in scope (arbiter): Increment A (unsubscribe wrapper), C (project-aware fix
text / 007 wiring), a general AddHandler heuristic, and the rest of P-015.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JxKjqdGEFzq4UzZupw379G
@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@PhysShell, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 44 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 5e55e48b-0a1d-4d64-9aa5-3acb6c38b179

📥 Commits

Reviewing files that changed from the base of the PR and between 9b50252 and a1b182d.

📒 Files selected for processing (1)
  • frontend/roslyn/OwnSharp.Extractor/Program.cs
📝 Walkthrough

Walkthrough

Adds strict [weak-subscription] configuration, CLI and own-check.sh plumbing, Roslyn matching for declared weak-subscribe wrappers, released subscription facts, sample cases, Action forwarding, and CI validation.

Changes

Weak-subscription allowlist

Layer / File(s) Summary
Configuration contract and CLI loading
ownlang/config.py, ownlang/__main__.py, tests/test_weak_subscribe.py
Loads and strictly validates SimpleType.Method declarations, exposes them through ownlang config, and tests malformed input as hard errors.
Declared wrapper matching and released facts
frontend/roslyn/OwnSharp.Extractor/Program.cs, frontend/roslyn/samples/*
Matches configured wrapper calls with valid source and handler arguments, emits released subscription facts, supports unresolved receivers, and avoids duplicate dropped-token findings.
Check-script and Action configuration wiring
scripts/own-check.sh, action.yml, fixtures/marketplace-consumer-demo/*
Adds explicit config forwarding through both Action execution paths and propagates malformed configuration failures.
Acceptance validation and documented behavior
tests/check_weak_subscribe_facts.py, .github/workflows/*, docs/proposals/*, spec/CLI.md
Validates positive, negative, suppression, unresolved, Rx-collision, plumbing, and malformed-config scenarios while documenting the shipped configuration scope.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Action
  participant OwnCheck
  participant OwnLangConfig
  participant OwnSharpExtractor
  participant FactChecker
  Action->>OwnCheck: pass --config own.toml
  OwnCheck->>OwnLangConfig: load weak-subscription declarations
  OwnLangConfig-->>OwnCheck: return wrapper pairs
  OwnCheck->>OwnSharpExtractor: pass --weak-subscribe pairs
  OwnSharpExtractor-->>FactChecker: write subscription facts
  FactChecker-->>Action: validate released and ordinary findings
Loading

Possibly related PRs

  • PhysShell/Own.NET#10: Changes the same scripts/own-check.sh and action.yml orchestration boundary for configuration input forwarding.
  • PhysShell/Own.NET#283: Shares the P-035 declared weak-subscription work and updates the same proposal documentation.
🚥 Pre-merge checks | ✅ 3 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description covers the change, but it does not follow the required template sections like 'Что и зачем', 'Как проверено', issues, and checklist. Rewrite it using the repository template and fill in all required sections, especially 'Что и зачем', 'Тип изменения', 'Как проверено', issues, and checklist.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: declared weak-subscribe wrappers are treated as accepted releases.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/p035-b0b1-weak-subscription

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

{
public OrdinaryPlusEquals(ISettings settings)
{
settings.PropertyChanged += OnChanged;

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4e6a911925

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread ownlang/config.py Outdated
return _weak_subscribe_from(data, path)


def _weak_subscribe_from(data: dict, path: str) -> list[str]:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Parameterize the TOML dict type

The lint job in .github/workflows/ci.yml runs mypy over ownlang with strict = true from pyproject.toml; this new bare dict annotation makes the gate fail with Missing type arguments for generic type "dict" before tests run. Use a parameterized shape such as dict[str, object]/Mapping[str, object] for the TOML payload so the documented mypy gate stays green.

Useful? React with 👍 / 👎.

Comment thread scripts/own-check.sh
Comment on lines +67 to +69
--config)
[[ $# -ge 2 ]] || { echo "own-check: --config requires a value" >&2; exit 2; }
config="$2"; shift 2 ;;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Mirror config plumbing in the PowerShell wrapper

This makes --config available only through the bash wrapper, but the documented Windows/Visual Studio entrypoint is scripts/own-check.ps1, and I checked that it still has no -Config parameter or forwarding to python -m ownlang config / --weak-subscribe. In Windows workflows the same own.toml declaration is therefore unusable, so declared weak wrappers continue to report OWN001 there; please add equivalent PowerShell plumbing when making this surface public.

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/ci.yml:
- Around line 1583-1586: Update the malformed-config assertion around
scripts/own-check.sh to capture its output and verify the command fails before
leak detection runs. Do not rely solely on the exit status, since the
intentional OrdinaryPlusEquals finding in $sample also produces a non-zero
result; assert that the captured output does not contain that positive-control
finding while preserving the expected failure for malformed bad.toml.

In `@ownlang/config.py`:
- Line 43: Fix the mypy generic-type error in _weak_subscribe_from by importing
Any from typing and changing the data parameter annotation from an
unparameterized dict to a dict keyed and valued with Any.

In `@scripts/own-check.sh`:
- Around line 16-21: Update the -h|--help branch in scripts/own-check.sh to have
sed print through line 38, ensuring the complete usage, option, and requirements
documentation is displayed.
- Around line 67-69: Add a -Config parameter to the scripts/own-check.ps1
entrypoint and wire it through the same configuration-selection flow used by the
existing --config handling in own-check.sh. Ensure the supplied value is
validated and passed to the underlying check command so PowerShell users can
select a config file.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: e520a0cd-e580-46d8-bd1a-f53cd3d53104

📥 Commits

Reviewing files that changed from the base of the PR and between ca2f9c3 and 4e6a911.

📒 Files selected for processing (8)
  • .github/workflows/ci.yml
  • frontend/roslyn/OwnSharp.Extractor/Program.cs
  • frontend/roslyn/samples/WeakSubscribeAllowlistSample.cs
  • ownlang/__main__.py
  • ownlang/config.py
  • scripts/own-check.sh
  • tests/check_weak_subscribe_facts.py
  • tests/test_weak_subscribe.py

Comment thread .github/workflows/ci.yml
Comment on lines +1583 to +1586
printf '[weak-subscription]\nsubscribe = ["bad_no_dot"]\n' > "$RUNNER_TEMP/bad.toml"
if scripts/own-check.sh --config "$RUNNER_TEMP/bad.toml" "$sample" >/dev/null 2>&1; then
echo "FAIL: malformed --config was silently accepted"; exit 1
fi

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Fix the false positive in the malformed config test.

The current test logic for the hard error on malformed config relies on a non-zero exit code. Because $sample contains an intentional leak (OrdinaryPlusEquals), scripts/own-check.sh will exit non-zero when it successfully runs the leak check.

If a bug were introduced where bad.toml is silently ignored, the script would run the leak detector, find OrdinaryPlusEquals, and exit non-zero. The if condition would then incorrectly pass the test, masking the configuration bug. To make this assertion robust, capture the output and ensure the leak detector never ran by checking for the absence of the positive control finding.

🐛 Proposed fix to assert early abort
-          if scripts/own-check.sh --config "$RUNNER_TEMP/bad.toml" "$sample" >/dev/null 2>&1; then
-            echo "FAIL: malformed --config was silently accepted"; exit 1
-          fi
+          bad_out=$(scripts/own-check.sh --config "$RUNNER_TEMP/bad.toml" "$sample" 2>&1 || true)
+          if echo "$bad_out" | grep -q "OrdinaryPlusEquals"; then
+            echo "FAIL: malformed --config was silently accepted (leak check ran)"; exit 1
+          fi
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
printf '[weak-subscription]\nsubscribe = ["bad_no_dot"]\n' > "$RUNNER_TEMP/bad.toml"
if scripts/own-check.sh --config "$RUNNER_TEMP/bad.toml" "$sample" >/dev/null 2>&1; then
echo "FAIL: malformed --config was silently accepted"; exit 1
fi
printf '[weak-subscription]\nsubscribe = ["bad_no_dot"]\n' > "$RUNNER_TEMP/bad.toml"
bad_out=$(scripts/own-check.sh --config "$RUNNER_TEMP/bad.toml" "$sample" 2>&1 || true)
if echo "$bad_out" | grep -q "OrdinaryPlusEquals"; then
echo "FAIL: malformed --config was silently accepted (leak check ran)"; exit 1
fi
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci.yml around lines 1583 - 1586, Update the
malformed-config assertion around scripts/own-check.sh to capture its output and
verify the command fails before leak detection runs. Do not rely solely on the
exit status, since the intentional OrdinaryPlusEquals finding in $sample also
produces a non-zero result; assert that the captured output does not contain
that positive-control finding while preserving the expected failure for
malformed bad.toml.

Comment thread ownlang/config.py Outdated
Comment thread scripts/own-check.sh
Comment on lines +16 to +21
# [--emit-facts <path>] [--config <own.toml>] [--root <own.net checkout>]
# [--] <path|file> [more ...]
#
# --config <own.toml> reads the project's [weak-subscription].subscribe wrapper
# names (P-035) and teaches the extractor to treat those calls as already-released
# weak subscriptions. A malformed config is a hard error.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Fix the truncated --help output.

The addition of these documentation lines pushes the bottom of the comment block down. As a result, the sed -n '2,30p' "$0" command at line 75 now truncates the help text, cutting off the existing --flow-locals, --legacy, and requirements documentation.

Please update the help command in line 75 to print the entire block.

🛠️ Suggested fix for line 75
# Update line 75 to print the full block (up to line 38):
-    -h|--help)         sed -n '2,30p' "$0"; exit 0 ;;
+    -h|--help)         sed -n '2,38p' "$0"; exit 0 ;;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/own-check.sh` around lines 16 - 21, Update the -h|--help branch in
scripts/own-check.sh to have sed print through line 38, ensuring the complete
usage, option, and requirements documentation is displayed.

Comment thread scripts/own-check.sh
Comment on lines +67 to +69
--config)
[[ $# -ge 2 ]] || { echo "own-check: --config requires a value" >&2; exit 2; }
config="$2"; shift 2 ;;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check if scripts/own-check.ps1 exists and if it implements the new --config flag.

if [ -f scripts/own-check.ps1 ]; then
  if rg -q '\-\-config' scripts/own-check.ps1; then
    echo "Parity maintained: --config is present in own-check.ps1"
  else
    echo "Action required: own-check.ps1 exists but is missing --config support"
  fi
else
  echo "No action required: own-check.ps1 does not exist in this repository"
fi

Repository: PhysShell/Own.NET

Length of output: 225


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'own-check.sh:\n'
ast-grep outline scripts/own-check.sh --view expanded || true
printf '\nown-check.ps1 exists?\n'
if [ -f scripts/own-check.ps1 ]; then
  wc -l scripts/own-check.ps1
  printf '\nRelevant lines from scripts/own-check.ps1:\n'
  rg -n --context 3 -- '--config|config|own-check' scripts/own-check.ps1
else
  echo 'missing'
fi

Repository: PhysShell/Own.NET

Length of output: 958


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '47,95p' scripts/own-check.ps1

Repository: PhysShell/Own.NET

Length of output: 1995


Add -Config to scripts/own-check.ps1 The Windows entrypoint still has no equivalent for --config, so PowerShell users can’t select a config file.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/own-check.sh` around lines 67 - 69, Add a -Config parameter to the
scripts/own-check.ps1 entrypoint and wire it through the same
configuration-selection flow used by the existing --config handling in
own-check.sh. Ensure the supplied value is validated and passed to the
underlying check command so PowerShell users can select a config file.

Source: Coding guidelines

mypy --strict flagged 'Missing type arguments for generic type dict' on the
_weak_subscribe_from param. dict[str, object] (tomllib's shape) -- ruff + mypy
both clean now.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JxKjqdGEFzq4UzZupw379G
@github-actions

Copy link
Copy Markdown

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

…ent-leaks, fallback, Action, docs)

Addresses the REQUEST CHANGES review on PR #284. Scope stays B0+B1.

1. Handler-shape guard. MatchesDeclaredWeakSubscribe now runs the second argument
   through NormalizeHandler + IsHandler, so a declared overload whose 2nd parameter is
   not a delegate -- AddPropertyChanged(source, 42) -- is NOT minted as a subscription.
   New negative control NonHandlerSecondArgument (zero facts).

2. --no-event-leaks respected. The new detector is gated on `emitEvents &&
   weakSubscribe.Count > 0` (was just Count > 0), and the Rx-Subscribe suppression only
   fires when emitEvents is on. New facts case: sample + --no-event-leaks + declared ->
   neither the weak wrapper nor the ordinary += is emitted.

3. Real unresolved-external fixture. WeakSubscribeUnresolvedSample.cs calls
   External.WeakEvents.AddPropertyChanged (type not in the compilation) so the syntactic
   receiver-name fallback is exercised (acceptance #8), plus an unresolved negative
   control with a different final receiver type name.

4. Composite Action exposes the feature. action.yml gains an optional `config:` input,
   forwarded as OWN_CONFIG and passed to own-check (--config) on both the SARIF and the
   normal path. A real Action-level test (action-marketplace-readiness.yml, uses: ./):
   a weak wrapper is a finding WITHOUT config and silent WITH it, and a malformed config
   hard-errors through the Action.

5. Docs synced to reality. P-035 status + "What exists today" (method-call wrapper is
   detected, config is consumed; method detector folded into P-035; A and C deferred);
   P-015 (format resolved: explicit --config own.toml TOML carrier, rest deferred);
   spec/CLI.md (the `config` command + own-check --config).

Verified locally via WSL: extractor facts for all four configs (on/off/no-events/
unresolved) pass tests/check_weak_subscribe_facts.py; the WeakBus fixture is OWN001
without --config and silent with it; ruff + mypy --strict + run_tests.py all green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JxKjqdGEFzq4UzZupw379G
{
// Token ignored on purpose: a leak WITHOUT the declaration, an accepted
// weak subscription WITH it.
WeakBus.Subscribe(source, OnNext);
… truth

Arbiter round-3 blockers on B0+B1:

1. Rx suppression was gated on `emitEvents` -- so under `--no-event-leaks` a
   declared IDisposable-returning wrapper named `Subscribe` (e.g.
   `WeakBus.Subscribe`) fell back to the Rx dropped-token matcher and the OFF
   switch INVENTED a finding. Suppression is now UNCONDITIONAL
   (`!MatchesDeclaredWeakSubscribe(...)`); only the weak-fact emission stays
   `emitEvents`-gated. A declaration classifies the API regardless of
   event-fact emission.

2. Regression: WeakSubscribeRxNoEventsSample.cs (WeakBus.Subscribe returning
   IDisposable) scanned with `--weak-subscribe WeakBus.Subscribe
   --no-event-leaks` must yield ZERO subscriptions (no weak fact: pass off; no
   Rx fact: declared -> suppressed). A contrast run WITHOUT the declaration
   proves the Rx dropped-token finding still fires, so it is the declaration --
   not the flag -- doing the suppression.

3. Docs: P-035 no longer claims a `+=`-only detector (B1 mints the wrapper call
   as a released:true subscription), and its fix-text section now quotes
   OWN001's real text (which names no WeakEventManager) with the manager named
   only as the deferred Increment-C alternative.

Scope unchanged: B0+B1, no Increment A/C, no general AddHandler inference.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JxKjqdGEFzq4UzZupw379G
{
public RxCollisionSubscriber()
{
WeakBus.Subscribe(this, _ => { });
The round-3 Rx-collision regression (WeakBus.Subscribe(this, _ => {}) under
--no-event-leaks) failed in CI: MatchesDeclaredWeakSubscribe returned false, so
the Rx dropped-token matcher still flagged it. Cause: the handler-shape guard
gated arg[1] on IsHandler alone, which accepts only IdentifierName /
MemberAccess -- a lambda is neither. Its own comment already promised "method
group / delegate / new H(...) / lambda", and the `+=` detector's main path does
count a lambda RHS (IsHandler gates only its unresolved fallback), so the guard
was inconsistent and too strict.

Fix: accept `IsHandler(...) || IsLambdaHandler(...)`. The int-literal negative
control (AddPropertyChanged(source, 42)) is still rejected (neither shape), so
NonHandlerSecondArgument stays empty. Now the declared lambda-handler wrapper is
recognised -> minted released:true when events are on, and unconditionally
suppressed from the Rx matcher when they are off.

Verified locally (Windows extractor + WSL python), 7/7 incl. the regression and
its no-declaration contrast (which still fires the Rx fact).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JxKjqdGEFzq4UzZupw379G
@PhysShell
PhysShell merged commit ff21d4a into main Jul 16, 2026
46 checks passed
@PhysShell
PhysShell deleted the claude/p035-b0b1-weak-subscription branch July 16, 2026 05:45
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.

2 participants