Skip to content

Give every creation validator exactly one bounded verdict (#6391) - #6423

Closed
systemorph[bot] wants to merge 1 commit into
mainfrom
bugfix/systemorph-meshweaver-6391
Closed

systemorph[bot] wants to merge 1 commit into
mainfrom
bugfix/systemorph-meshweaver-6391

Conversation

@systemorph

@systemorph systemorph Bot commented Oct 10, 2026

Copy link
Copy Markdown
Contributor

What changed

  • RunCreationValidatorsObs (src/MeshWeaver.Mesh.Contract/MeshExtensions.cs) runs each creation validator through the new BoundedCreationVerdict: Defer(Validate).Take(1) under its own bound, MeshOperationOptions.NestedTimeout (rung 2 of the budget ladder, strictly inside the create's own deadline, which stays the backstop).
  • A validator that gives no verdict within that bound, or completes without emitting, is now an Unavailable refusal that names the validator type and the validators stage. Nothing is written. A first emission is the verdict, so a validator that answers and stays subscribed no longer holds the stage.
  • HandleCopyNodeRequest: a create leg refused Unavailable is answered with CopyUnavailablePrefix, so a move whose copy leg is refused that way still reports Unavailable.

Why

Issue #6391: the platform-startup boot recording (Admin/PlatformVersion/_Activity/startup-380439f5) on memex got no verdict for 30 s, stalled at stage validators. The runner chained the validators with Concat and gave none of them a terminal. A validator not armed yet at boot (never answers), or one that emits from a live fold and never completes, held the stage until the create's deadline, which names the stage but not the validator. One that completed empty was read as a pass (fail-open, against the #2742 rule). The stall came before the write stage, so that request did not write the boot's audit row. Which validator stalled on memex cannot be proven from the single log line; after this change the refusal names it.

How tested

New test/MeshWeaver.Hosting.Test/CreationValidatorAlwaysReachesAVerdictTest.cs (rung 1 = 10 s, so each validator's bound is 5 s):

  • never answers: Unavailable, names the validator, answered before the create deadline, nothing stored. On main it is answered only by the deadline and the validator is not named.
  • completes without emitting: Unavailable, nothing stored. On main the node is created.
  • answers, then stays subscribed: commits. On main it stalls.
  • boot-time validator that arms late (1 s), then stays live: commits. On main it stalls.
  • controls: a slow validator that completes inside its bound commits; a refusing validator keeps its own reason.

The existing LifecycleRequestsAlwaysReachAVerdictTest should still hold: the create refusal still names the path and the validators stage, and the move case still reports Unavailable through the copy change. There is no local build here; CI is the build.

Scope: src/MeshWeaver.Mesh.Contract and test only.


Refs #6391
Bug-Thread: Hosting/Triage/_Thread/bug-systemorph-meshweaver-6391

Opened by Dispatch's control plane on behalf of Essentials/Agent/bug-triage (auto · Provider/OpenRouterEU/anthropic/claude-opus-5.5), from the bug's own branch bugfix/systemorph-meshweaver-6391. It is never merged by Dispatch: the merge is the governed activity dev.merge, signed by a person once the checks and the internal review are green.

RunCreationValidatorsObs chained validators with Concat and no per-validator terminal: a silent or never-completing validator held stage 'validators' until the 30 s create deadline, and an empty completion passed. Each validator now runs Defer(Validate).Take(1) under MeshOperationOptions.NestedTimeout; silence and empty completion are Unavailable refusals naming the validator. A copy whose create leg is Unavailable answers with CopyUnavailablePrefix.

Refs #6391
@github-actions

Copy link
Copy Markdown
Contributor

Test Results

0 tests   0 ✅  0s ⏱️
0 suites  0 💤
0 files    0 ❌

Results for commit 311b928.

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

Relative per-validator deadlines can still lose to the create deadline, and new user-facing failures bypass localization.

2 open findings
What changed in this PR

Adds bounded, single-verdict handling for creation validators and propagates unavailable create outcomes through copy/move operations.

Changes:

  • Bounds validator silence and empty completion as Unavailable.
  • Stops live validators after their first verdict.
  • Adds integration coverage for validator terminal behaviors.
File Description
src/​MeshWeaver.Mesh.Contract/​MeshExtensions.cs Adds bounded validator verdicts and copy availability propagation.
test/​MeshWeaver.Hosting.Test/​CreationValidatorAlwaysReachesAVerdictTest.cs Tests silent, empty, live, slow, and refusing validators.

🧠 Review effort: Balanced


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

Comment on lines +6391 to +6395
var noVerdict = NodeValidationResult.Unavailable(
$"CreateNode of '{path}' could not be validated: the creation validator '{name}' gave no "
+ $"verdict within {seconds}s at stage 'validators'. Nothing was written; the create may be retried.");
var completedEmpty = NodeValidationResult.Unavailable(
$"CreateNode of '{path}' could not be validated: the creation validator '{name}' completed "
Comment on lines +6434 to 6438
var validatorBudget = (hub.ServiceProvider.GetService<MeshOperationOptions>() ?? new MeshOperationOptions()).NestedTimeout;

return validators
.Select(v => v.Validate(context))
.Select(v => BoundedCreationVerdict(v, context, node.Path, validatorBudget))
.Concat()
@rbuergi

rbuergi commented Oct 10, 2026

Copy link
Copy Markdown
Contributor

Closed by maintainer decision: the per-validator timeout in this PR is a band-aid. It turns a stalled validator into a refusal one rung earlier without finding why the validator does not answer, and since #6421 the create deadline already names the stalled validator. The two genuine fixes here (Take(1) per validator, so one that answers and stays subscribed no longer blocks the rest; and an empty completion counted as a refusal, not a pass) moved to #6430 with deterministic tests and negative controls. The root cause of the stall stays open on #6391.

@rbuergi rbuergi closed this Oct 10, 2026
@rbuergi
rbuergi deleted the bugfix/systemorph-meshweaver-6391 branch October 24, 2026 16:18
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