Skip to content

docs: Timeout DOES manufacture a termination — fix the over-correction from #3919 - #3921

Merged
rbuergi merged 2 commits into
mainfrom
docs/3674-timeout-manufactures-a-termination
Sep 10, 2026
Merged

rbuergi merged 2 commits into
mainfrom
docs/3674-timeout-manufactures-a-termination

Conversation

@rbuergi

@rbuergi rbuergi commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

Follow-up to #3919 (merged). Documentation only.

🚨 Opened as a DRAFT on purpose. auto-arm.yml arms every non-draft PR, and both previous PRs in this chain were enqueued before the Copilot review landed — a queued branch cannot be updated, so each round of review had to become another PR. Draft is the documented opt-out. It will be marked ready once review has landed and been addressed, which is where this loop stops.

The over-correction

#3919 replaced one overbroad claim with another. It said:

no composition over the source can invent a termination it never produces

That is false, and self-contradicting two sentences later — the very same paragraph names Timeout as what supplies liveness, and Timeout is a composition over a source that manufactures an OnError when the source is silent. Copilot flagged it at all three sites.

The fix, stated positively

The limitation is specific to DetachedReplyOutcome.Of, and now the reason is given rather than the claim merely narrowed:

every operator Of applies — Take(1), Select, Catch, DefaultIfEmpty — reacts to a termination; none manufactures one.

Timeout manufactures one, which is exactly what makes it the right tool for the fourth outcome. So the guidance is now actionable instead of merely cautionary:

Compose Of around a Timeout and both properties hold — the timeout supplies liveness, Of turns whichever termination arrives into an answer.

which is, not by accident, what DispatchInnerCreate (.Take(1).Timeout(InnerCreateVerdictBound)) and the no-op probe (.Timeout(NodeOpForwardTimeout)) already do.

Applied at the three sites named: the type's remarks, the Of summary, and the doc page's caveat.

Verification

Mesh.Contract, Documentation, Documentation.Test each 0 Error(s) in Release with -warnaserror; DocumentationLinkIntegrityTest green.

Chain: #3915 (the fix, merged) → #3919 (narrowed two overclaims, merged) → this. Related: #3917 (the pre-existing i18n gap in the upsert failure surface), #3674.

🤖 Generated with Claude Code

https://claude.ai/code/session_01S25PJZbVeCbkWhCfMub9P7

…review, #3919)

The correction over-corrected. "No composition over a source can invent a
termination it never produces" is false, and self-contradicting two sentences
later: `Timeout` is exactly such a composition, and the same paragraph then
points at the two bounds that use it.

The limitation is specific to `DetachedReplyOutcome.Of` and to WHY: every
operator it applies — Take(1), Select, Catch, DefaultIfEmpty — REACTS to a
termination; none MANUFACTURES one. `Timeout` manufactures one, which is what
makes it the right tool for the fourth outcome. Compose `Of` AROUND a Timeout
and both properties hold: the timeout supplies liveness, `Of` turns whichever
termination arrives into an answer.

Fixed at all three sites Copilot named — the type's remarks, the `Of` summary,
and the doc page's caveat.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S25PJZbVeCbkWhCfMub9P7
Copilot AI lite review requested due to automatic review settings September 10, 2026 13:19

Copilot AI 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.

🟡 Changes recommended

Narrow the silent-source wording and address the uncovered NodeType resolution path.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Documentation-only follow-up clarifying termination versus liveness for detached Rx replies.

Changes:

  • Refines DetachedReplyOutcome documentation.
  • Updates write-verdict architecture guidance and examples.
File summaries
File Summary
src/MeshWeaver.Mesh.Contract/DetachedReplyOutcome.cs Clarifies Of and Timeout termination behavior.
src/MeshWeaver.Documentation/Data/Architecture/WriteVerdictTotality.md Aligns architecture guidance with the revised explanation.
Review details

Suppressed comments (2)

src/MeshWeaver.Documentation/Data/Architecture/WriteVerdictTotality.md:472

  • The list omits the NodeType gate, which is another detached-reply leg: ApplyUpdateViaStream wraps NodeTypeResolution.Resolves(...) in Of without a Timeout, and Resolves delegates to IStorageAdapter.Exists(...).Take(1). The normal persistence implementation uses Merge().Any(...), so a lost/stalled adapter can leave an absent type non-terminating and the caller still receives no reply. Add a bound to that leg or explicitly document it as an uncovered case rather than implying the starvation-prone legs are covered by the two examples here.
manufactures one is `Timeout`, and it is separately present where the source can starve:
`InnerCreateVerdictBound` on the inner create, `NodeOpForwardTimeout` inside the no-op probe.

src/MeshWeaver.Mesh.Contract/DetachedReplyOutcome.cs:74

  • The summary repeats an overly broad statement: Take(1) manufactures an output completion after a value, and Catch replaces an error with a new Observable.Return sequence. What Of cannot do is manufacture a termination from a source that stays silent; narrowing this sentence avoids documenting Rx behavior incorrectly.
    /// answer a source that TERMINATES. <b>It adds no deadline</b> — none of its operators
    /// manufactures a termination, they only react to one — so a source that never terminates still
    /// reaches nobody. Bounding that is a <c>Timeout</c> the caller composes UNDER this (see the
  • Files reviewed: 2/2 changed files
  • Comments generated: 2
  • Review effort level: Lite

💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +469 to +471
"The FOURTH outcome" above — reaches no arm at all, and `Of` cannot rescue it, because every
operator it applies **reacts** to a termination and none **manufactures** one. The operator that
manufactures one is `Timeout`, and it is separately present where the source can starve:

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Same fix, applied here too — see the thread on DetachedReplyOutcome.cs. The doc caveat now spells out which notification each operator waits for (Take(1) after a value, Catch after a fault, DefaultIfEmpty on a completion), says that none can fire from a source that has produced nothing, and names Timeout as the one clock-driven operator and therefore the only one that can terminate a silent source.

Comment on lines +32 to +35
/// — reaches no arm at all, and <c>Of</c> cannot rescue it: every operator it applies
/// (<c>Take(1)</c>, <c>Select</c>, <c>Catch</c>, <c>DefaultIfEmpty</c>) reacts to a termination and
/// none MANUFACTURES one. Read the guarantee strictly: <i>if the source terminates, exactly one
/// outcome is emitted.</i> The operator that DOES manufacture a termination is <c>Timeout</c> — it

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Right on both counts, and the narrowed version is more useful than the one it replaces — thank you for pushing on it three times.

The distinction that actually holds is notification-driven vs clock-driven:

  • Every operator Of applies needs an upstream notification before it can do anything. Take(1) synthesises a completion — only after a value. Catch substitutes a sequence — only after a fault. DefaultIfEmpty fires only on a completion. From a source that has produced nothing at all, none of them can fire.
  • Timeout is the exception because it is driven by a clock, not by the source — which is precisely why it is the one composition that can terminate a silent source, and why a leg whose source can starve needs one of its own.

Applied at all three sites. Pushed to this branch directly, which is the point of opening it as a draft: the previous two rounds were auto-armed and enqueued before the review arrived, and a queued branch cannot be updated.

@github-actions

github-actions Bot commented Sep 10, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 0)

254 tests  ±0   254 ✅ ±0   2m 2s ⏱️ -17s
  1 suites ±0     0 💤 ±0 
  1 files   ±0     0 ❌ ±0 

Results for commit 1d71e80. ± Comparison against base commit f97135a.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Sep 10, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 3)

389 tests  ±0   389 ✅ ±0   55s ⏱️ -2s
  2 suites ±0     0 💤 ±0 
  2 files   ±0     0 ❌ ±0 

Results for commit 1d71e80. ± Comparison against base commit f97135a.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Sep 10, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 1)

496 tests  ±0   496 ✅ ±0   52s ⏱️ +11s
  1 suites ±0     0 💤 ±0 
  1 files   ±0     0 ❌ ±0 

Results for commit 1d71e80. ± Comparison against base commit f97135a.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Sep 10, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 2)

1 229 tests  ±0   1 227 ✅ ±0   3m 44s ⏱️ -8s
    3 suites ±0       2 💤 ±0 
    3 files   ±0       0 ❌ ±0 

Results for commit 1d71e80. ± Comparison against base commit f97135a.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Sep 10, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 5)

    6 files  ±0      6 suites  ±0   4m 57s ⏱️ +21s
2 605 tests ±0  2 413 ✅ ±0  192 💤 ±0  0 ❌ ±0 
2 609 runs  ±0  2 417 ✅ ±0  192 💤 ±0  0 ❌ ±0 

Results for commit 1d71e80. ± Comparison against base commit f97135a.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Sep 10, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 4)

1 889 tests  ±0   1 889 ✅ ±0   3m 16s ⏱️ +9s
    4 suites ±0       0 💤 ±0 
    4 files   ±0       0 ❌ ±0 

Results for commit 1d71e80. ± Comparison against base commit f97135a.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Sep 10, 2026 •

Copy link
Copy Markdown
Contributor

Test Results

   17 files  ±0     17 suites  ±0   15m 49s ⏱️ +15s
6 862 tests ±0  6 668 ✅ ±0  194 💤 ±0  0 ❌ ±0 
6 866 runs  ±0  6 672 ✅ ±0  194 💤 ±0  0 ❌ ±0 

Results for commit 1d71e80. ± Comparison against base commit f97135a.

♻️ This comment has been updated with latest results.

…ion (Copilot review, #3921)

"None of Of's operators manufactures a termination" is not literally true, as
the review points out: Take(1) DOES synthesise an OnCompleted, and Catch DOES
switch to a Return sequence that completes.

The accurate limitation is narrower and more useful. Every operator Of applies
is NOTIFICATION-DRIVEN — each needs an upstream notification before it can do
anything: Take(1) synthesises a completion only AFTER a value, Catch substitutes
only AFTER a fault, DefaultIfEmpty fires only ON a completion. From a source
that has produced nothing at all, none of them can fire.

Timeout is the exception, and naming WHY is the part worth writing down: it is
CLOCK-driven, so it is the one composition that can terminate a silent source.
That is the whole reason a leg whose source can starve needs one of its own.

Third and final narrowing of this claim; each round moved it from "any source"
→ "if the source terminates" → this. Applied at all three sites.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S25PJZbVeCbkWhCfMub9P7
@rbuergi
rbuergi marked this pull request as ready for review September 10, 2026 13:46
@rbuergi
rbuergi added this pull request to the merge queue Sep 10, 2026
Merged via the queue into main with commit 80b3b63 Sep 10, 2026
35 checks passed
@rbuergi
rbuergi deleted the docs/3674-timeout-manufactures-a-termination branch September 13, 2026 10:16
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