Skip to content

feat(#146): returned-fresh publisher provenance — prove the Create→Apply shape bounded#208

Merged
PhysShell merged 3 commits into
mainfrom
claude/plans-tasks-review-u77dfs
Jul 10, 2026
Merged

feat(#146): returned-fresh publisher provenance — prove the Create→Apply shape bounded#208
PhysShell merged 3 commits into
mainfrom
claude/plans-tasks-review-u77dfs

Conversation

@PhysShell

@PhysShell PhysShell commented Jul 10, 2026

Copy link
Copy Markdown
Owner

Что и зачем

Issue #146: подписка += на publisher-ПАРАМЕТР локально неотличима от DI-singleton шины, поэтому честно даёт OWN001-warning (injected tier). Экстрактор теперь запускает ленивый мемоизированный общекомпиляционный provenance-проход — дуал ownership transfer (D5): если подписывающий метод private/internal и каждый видимый вызывающий передаёт свежесконструированный локал, который не покидает область иначе как в этот вызов и собственный return (а callee не выпускает параметр дальше), подписка штампуется source_provenance: "returned_fresh" и бридж её скипает — она ограничена временем жизни возвращённого publisher'а, та же bounded-семантика, что у локально сконструированного источника. Инстанс-факт сознательно бьёт type-level DI-хоп (даже если тип publisher'а зарегистрирован singleton'ом, ЭТОТ экземпляр создан вызывающим, а не разрешён из контейнера).

Precision-first отказы — все сохраняют честный warning, запинены сэмплом frontend/roslyn/samples/ReturnedPublisherSample.cs (CI wpf-extractor) и проверками в tests/test_ownir.py: public/protected кандидат, method-group ссылка, named/ref/omitted аргумент, не-свежий/не-локальный аргумент, любое иное использование локала (запись в поле, другой callee, лямбда-захват, переприсваивание), param→param форвардинг, ноль видимых вызовов. Конструкторы исключены самим MethodKind-гейтом, так что DI-форму через ctor заглушить невозможно по построению.

Закрывает майненный ньютоновский over-report (JsonSerializer.CreateApplySerializerSettings, field-notes #8). Его запись в corpus/oracle-fp-baseline.txt удалена сознательно: при регрессии provenance-прохода находка должна всплыть в триаж-очереди оракула, а не быть молча проглоченной allowlist'ом. Поле специфицировано в spec/OwnIR.md §4 и spec/ownir.schema.json (аддитивное опциональное — без бампа OWNIR_VERSION по IR-правилам эволюции).

Тип изменения

  • feat — новая возможность
  • fix — исправление бага
  • docs — документация
  • refactor / chore / test / ci — без изменения поведения

Как проверено

  • python tests/run_tests.py — зелено после ребейза на main c README front door: 20s landing, 3 case studies, FP policy page, C# gallery pairs, CI SHA-pinning #204/feat(P-007): pin POOL005 full-length view stored into a field; defer object-level escape #206 (ownir 255/255, включая 7 новых provenance-проверок: bounded→silent, unknown-значение→warning, premise-guard DI-эскалации, instance-beats-DI, lowered sketch парсится, load() отвергает не-строку)
  • ruff check . и mypy — чисто (mypy --strict по ownlang)
  • селфтесты затронутых скриптов — не затронуты
  • Плюс CI на ветке: полный прогон зелёный (run #1246 до ребейза), включая новые ассерты в wpf-extractor: bounded-кейс молчит со штампом returned_fresh в фактах, все четыре denial-кейса сохраняют OWN001-warning.

Связанные issue

Closes #146. Refs field-notes-patterns.md #8, docs/notes/oracle-known-fps.md (запись обновлена: FP → fixed).

Чеклист

🤖 Generated with Claude Code

https://claude.ai/code/session_01LsWw4Ay8KLTHFom1HvRu3U


Generated by Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Improved event-subscription analysis to recognize publishers that are freshly created and safely returned, suppressing false-positive warnings.
    • Preserved warnings when publisher provenance cannot be proven, including mixed, stored, deferred, or externally visible scenarios.
    • Removed the known false-positive baseline for the Newtonsoft.Json serializer event.
  • Documentation

    • Updated specifications, field notes, and known-issue records to document publisher provenance behavior and the resolved false positive.

…pply shape bounded

The dual of ownership transfer, interprocedural: a += on a PARAMETER publisher
is locally indistinguishable from a DI-singleton bus, so it honestly warns
(OWN001, injected tier). The extractor now runs a lazy, memoized,
compilation-wide provenance pass: when the subscribing method is
private/internal and EVERY visible caller passes a freshly-constructed local
that escapes only into the call / its own return (and the callee never lets
the param escape), the subscription is stamped
source_provenance: "returned_fresh" and the bridge drops it — bounded by the
returned publisher's lifetime, same boundedness as a locally-constructed
source. Instance-level provenance deliberately beats the type-level DI hop.

Precision-first denials (all keep the warning, pinned by
ReturnedPublisherSample.cs in CI + test_ownir.py): public candidate,
method-group reference, named/ref/omitted argument, non-fresh/non-local
argument, any other use of the caller's local (field store, other callee,
lambda capture, reassignment), param->param forwarding, zero visible callers.
Constructors are excluded by construction (MethodKind gate), so the
ctor-injected DI shape can never be silenced.

Clears the mined Newtonsoft JsonSerializer.Create over-report (field-notes #8);
its oracle-fp-baseline entry is REMOVED so a regression resurfaces in triage
instead of being swallowed by the allowlist. Spec'd in OwnIR.md §4 + the JSON
schema (additive optional field, no version bump per IR rules).

Closes #146

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LsWw4Ay8KLTHFom1HvRu3U
@coderabbitai

coderabbitai Bot commented Jul 10, 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: 52 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: 0ab4722d-98cf-4020-9e45-5e36ccd4708b

📥 Commits

Reviewing files that changed from the base of the PR and between c3cb656 and d711ec1.

📒 Files selected for processing (2)
  • docs/notes/field-notes-patterns.md
  • docs/notes/oracle-known-fps.md
📝 Walkthrough

Walkthrough

Adds interprocedural detection for freshly constructed publishers returned by callers. Matching subscriptions receive returned_fresh provenance and are suppressed, while uncertain cases retain warnings. OwnIR validation, Roslyn scenarios, tests, CI checks, specifications, and false-positive records are updated.

Changes

Returned Publisher Provenance

Layer / File(s) Summary
Provenance contract and lowering
spec/OwnIR.md, spec/ownir.schema.json, ownlang/ownir.py, tests/test_ownir.py
Adds optional provenance validation and suppresses only injected subscriptions marked returned_fresh; unknown or invalid values retain existing behavior.
Interprocedural extractor analysis
frontend/roslyn/OwnSharp.Extractor/Program.cs
Audits callee parameters and visible callers, caches proofs, and emits source_provenance: "returned_fresh" for eligible subscriptions.
Provenance scenarios and denial cases
frontend/roslyn/samples/ReturnedPublisherSample.cs
Adds bounded, public-entry, mixed-caller, storage, closure, and parameter-forwarding publisher scenarios.
Regression verification and records
.github/workflows/ci.yml, corpus/oracle-fp-baseline.txt, docs/notes/*
Verifies silent and warning outcomes in CI and records the Newtonsoft false positive as fixed rather than baselined.

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

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant ProvFactory
  participant ApplyBounded
  participant OwnSharpExtractor
  participant OwnIR
  Caller->>ProvFactory: construct publisher and call factory
  ProvFactory->>ApplyBounded: pass publisher parameter
  ApplyBounded->>OwnSharpExtractor: emit subscription candidate
  OwnSharpExtractor->>Caller: verify fresh construction and return
  OwnSharpExtractor->>OwnIR: emit returned_fresh provenance
  OwnIR-->>Caller: suppress bounded subscription finding
Loading

Possibly related PRs

  • PhysShell/Own.NET#28: Updates overlapping OwnIR handling for injected event subscription findings.
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: interprocedural returned-fresh publisher provenance for issue #146.
Linked Issues check ✅ Passed The PR implements returned-fresh publisher provenance, suppresses only proven bounded subscriptions, and preserves warnings for uncertain or leak-prone cases.
Out of Scope Changes check ✅ Passed The changes stay focused on issue #146, with matching code, tests, spec updates, sample, and oracle baseline cleanup.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/plans-tasks-review-u77dfs

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 static void ApplyPublic(ProvPublisher pub, ProvSettings settings)
{
pub.Faulted += settings.Error; // stays the OWN001 warning

private static void ApplyMixed(ProvPublisher target, ProvSettings settings)
{
target.Mixed += settings.Error; // stays the OWN001 warning
Comment thread frontend/roslyn/samples/ReturnedPublisherSample.cs Fixed

private void Wire(ProvBus bus)
{
bus.Changed += OnChanged; // stays the OWN001 warning

@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: 0ae0b5868b

ℹ️ 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 on lines +846 to +847
if (id.Ancestors().OfType<AnonymousFunctionExpressionSyntax>().Any())
return false;

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 Reject local-function captures of publisher parameters

When the subscribing helper captures the publisher parameter in a local function, e.g. void Later() { publisher.Error += h; } and then stores Later, this check still accepts the parameter because it only rejects anonymous-function ancestors. The new provenance stamp can then be applied even though the publisher escaped through the local-function closure and may be invoked from a longer-lived root, causing the bridge to drop an OWN001 warning that should remain.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Confirmed and fixed in c3cb656 — a stored local function is a closure exactly like a lambda, and the callee gate only rejected AnonymousFunctionExpressionSyntax. The ancestor walk now also matches LocalFunctionStatementSyntax, bounded at the method's own declaration node (TakeWhile(a => a != mnode)) so a candidate never self-denies. Regression pinned: ProvLocalFuncFactory in ReturnedPublisherSample.cs (deferred.Deferred must keep the OWN001 warning), asserted in the wpf-extractor CI job.


Generated by Claude Code

Comment on lines +919 to +920
if (use.Ancestors().TakeWhile(a => a != scope)
.OfType<AnonymousFunctionExpressionSyntax>().Any())

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 Reject local-function captures of fresh caller locals

If the fresh local is passed to the audited helper from a nested local function, e.g. void Later() => Apply(p, s);, and that local function is stored/delegated, this loop treats the use of p as an allowed target-argument use because it only rejects anonymous functions. That lets a local which escaped via a local-function closure satisfy returned_fresh, so later calls from the stored delegate are silenced as bounded even though p may now be process-lived.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Confirmed and fixed in c3cb656 — same hole on the caller side: the use-walk's TakeWhile(a => a != scope) window now denies on LocalFunctionStatementSyntax as well as anonymous functions, so a target-argument use inside a stored void Wire() => Apply(p, s); no longer counts as bounded. Regression pinned: ProvCallerLocalFuncFactory in ReturnedPublisherSample.cs (later.Later must keep the OWN001 warning), asserted in the wpf-extractor CI job.


Generated by Claude Code

…tes (Codex P2 x2)

A stored local function is a closure exactly like a lambda: it can be invoked
from a longer-lived root after the audited call returns. Both gates rejected
only AnonymousFunctionExpressionSyntax, so a publisher parameter subscribed
inside a stored 'void Later() { p.Event += h; }' (callee side), or a fresh
local passed to the helper from a stored 'void Wire() => Apply(p, s);'
(caller side), could still earn the returned_fresh stamp and silence a real
warning. Both ancestor walks now also match LocalFunctionStatementSyntax,
bounded at the audited node so a method never self-denies.

Regression-pinned by two new denial cases in ReturnedPublisherSample.cs
(deferred.Deferred / later.Later) asserted in the wpf-extractor CI job.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LsWw4Ay8KLTHFom1HvRu3U

private static void ApplyStored(ProvPublisher stored, ProvSettings settings)
{
stored.Stored += settings.Error; // stays the OWN001 warning

private static void ApplyDeferred(ProvPublisher deferred, ProvSettings settings)
{
void Later() { deferred.Deferred += settings.Error; } // captures the param

private static void ApplyLater(ProvPublisher later, ProvSettings settings)
{
later.Later += settings.Error; // stays the OWN001 warning

Copy link
Copy Markdown
Owner Author

@coderabbitai review


Generated by Claude Code

@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown

@PhysShell I'll review the changes now.

✅ 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.

Copy link
Copy Markdown
Owner Author

@coderabbitai review


Generated by Claude Code

@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown

@PhysShell I'll review the changes now.

✅ 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.

@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.

🧹 Nitpick comments (2)
docs/notes/field-notes-patterns.md (1)

260-271: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Denial case list omits local-function closure captures.

The denial case enumeration at lines 265-266 lists "public candidate, method-group reference, mixed callers, field-stored local, param→param forwarding" but omits the two local-function closure capture cases (callee-side ProvLocalFuncFactory and caller-side ProvCallerLocalFuncFactory) that are present in ReturnedPublisherSample.cs (lines 116-158) and explicitly checked in CI (lines 253-254: "deferred.Deferred", "later.Later"). The commit messages confirm these were added as Codex P2 regression safeguards.

🤖 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 `@docs/notes/field-notes-patterns.md` around lines 260 - 271, Update the
denial-case enumeration in the documented returned-fresh provenance description
to include both local-function closure capture cases: callee-side
ProvLocalFuncFactory and caller-side ProvCallerLocalFuncFactory. Mention that
these cases deny the proof alongside the existing safeguards, matching
ReturnedPublisherSample.cs and the corresponding CI checks.
docs/notes/oracle-known-fps.md (1)

49-57: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Denial case list and summary counts need updating for #146.

Two issues:

  1. The denial case list at lines 55-57 omits the two local-function closure capture cases (callee-side and caller-side) that are present in ReturnedPublisherSample.cs (lines 116-158) and checked in CI (lines 253-254: "deferred.Deferred", "later.Later").

  2. The disposition summary table (lines 26-27) is now stale. Moving serializer.Error from "Baselined FP" to "Fixed in the extractor" means "Fixed" should increase from 6 to 7 and "Baselined FP" should decrease from 5 to 4. The text at lines 36-39 ("5 baselined FPs...8 findings...7 rules") should correspondingly become "4 baselined FPs...7 findings...6 rules" to match the updated corpus/oracle-fp-baseline.txt.

🤖 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 `@docs/notes/oracle-known-fps.md` around lines 49 - 57, The denial-case list in
docs/notes/oracle-known-fps.md must include the callee-side and caller-side
local-function closure captures, identified by “deferred.Deferred” and
“later.Later” in ReturnedPublisherSample.cs. Update the disposition counts to
Fixed 7 and Baselined FP 4, and revise the summary text from 5 baselined FPs, 8
findings, and 7 rules to 4 baselined FPs, 7 findings, and 6 rules.
🤖 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.

Nitpick comments:
In `@docs/notes/field-notes-patterns.md`:
- Around line 260-271: Update the denial-case enumeration in the documented
returned-fresh provenance description to include both local-function closure
capture cases: callee-side ProvLocalFuncFactory and caller-side
ProvCallerLocalFuncFactory. Mention that these cases deny the proof alongside
the existing safeguards, matching ReturnedPublisherSample.cs and the
corresponding CI checks.

In `@docs/notes/oracle-known-fps.md`:
- Around line 49-57: The denial-case list in docs/notes/oracle-known-fps.md must
include the callee-side and caller-side local-function closure captures,
identified by “deferred.Deferred” and “later.Later” in
ReturnedPublisherSample.cs. Update the disposition counts to Fixed 7 and
Baselined FP 4, and revise the summary text from 5 baselined FPs, 8 findings,
and 7 rules to 4 baselined FPs, 7 findings, and 6 rules.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: e39336d5-2263-435c-9f01-98a8d5544fd2

📥 Commits

Reviewing files that changed from the base of the PR and between 9fc8c27 and c3cb656.

📒 Files selected for processing (10)
  • .github/workflows/ci.yml
  • corpus/oracle-fp-baseline.txt
  • docs/notes/field-notes-patterns.md
  • docs/notes/oracle-known-fps.md
  • frontend/roslyn/OwnSharp.Extractor/Program.cs
  • frontend/roslyn/samples/ReturnedPublisherSample.cs
  • ownlang/ownir.py
  • spec/OwnIR.md
  • spec/ownir.schema.json
  • tests/test_ownir.py

…nction denial cases

CodeRabbit nitpicks on #208: the returned-fresh denial-case enumerations in
field-notes #8 and oracle-known-fps predate c3cb656, so they omitted the two
local-function closure captures; and the disposition summary still counted
serializer.Error as baselined (Fixed 6->7, Baselined 5->4, 8 findings/7 rules
-> 7 findings/6 rules).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LsWw4Ay8KLTHFom1HvRu3U
@PhysShell PhysShell merged commit 78e8107 into main Jul 10, 2026
33 checks passed
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.

Subscription precision: suppress += on a publisher that escapes by return (interprocedural — the dual of ownership transfer)

3 participants