Repository navigation
Take exactly one verdict from each creation validator - #6430
Merged
Merged
Conversation
RunCreationValidatorsObs now takes each validator's FIRST emission as its verdict (Take(1) per validator), so a validator that answers and stays subscribed no longer holds every validator after it - and the create - until the create's deadline. A validator that completes WITHOUT a verdict is now a refusal (Unavailable, naming the validator), never a pass: no verdict is not consent, the #2742 rule generalised from RLS to every validator. A validator that never answers is deliberately not bounded per validator: the create's deadline already answers and names it, and why its read stalls is the open root cause on #6391. Refs #6391 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
🟢 Approval recommended
The implementation is reactive, fail-closed, localized, documented, and covered by focused integration tests.
0 open findings
What changed in this PR
Enforces one definitive result per creation validator, preventing live validators from stalling creation and empty validators from failing open.
Changes:
- Takes only each validator’s first emission and rejects empty completion as unavailable.
- Adds English/German refusal localization and architecture documentation.
- Adds integration tests covering stalled, empty, refusing, and healthy validators.
| File | Description |
|---|---|
src/MeshWeaver.Mesh.Contract/MeshExtensions.cs |
Enforces one creation-validation verdict. |
src/MeshWeaver.Messaging.Hub/Localization/strings.en.json |
Adds English refusal text. |
src/MeshWeaver.Messaging.Hub/Localization/strings.de.json |
Adds German refusal text. |
test/MeshWeaver.Hosting.Test/CreationValidatorGivesExactlyOneVerdictTest.cs |
Tests validator terminal behavior. |
src/MeshWeaver.Documentation/Data/Architecture/WriteVerdictTotality.md |
Documents the enforced contract. |
src/MeshWeaver.Documentation/Data/Architecture/AccessControl.md |
Updates fail-closed guidance. |
🧠 Review effort: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Contributor
Test Results 20 files - 4 20 suites - 4 51m 28s ⏱️ -48s Results for commit a5d2d72. ± Comparison against base commit 98a6d57. This pull request removes 334 and adds 20 tests. Note that renamed tests count towards both. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What changed
RunCreationValidatorsObs(src/MeshWeaver.Mesh.Contract/MeshExtensions.cs) now holds every creation validator to theINodeValidatorcontract ("emits exactly one result and completes") instead of trusting it:Take(1)per validator.Concatsubscribes to validator i+1 only when validator i completes, so a validator that answeredValidfrom a live read and stayed subscribed held every validator after it. As the last validator it held the create itself, until the create's deadline answered it as a stall.Concat, so the create went ahead as if the validator had passed. It now refuses the createUnavailable(activity.node.create.validatorGaveNoVerdict, en + de), naming the path and the validator, and nothing is written. This is the A starved permission fold parks every [RequiresPermission] delivery until the caller's own timeout — AccessControlPipeline has no terminal, and the bounded twin attributes the same event correctly #2742 rule, generalised fromRlsNodeValidatorto every validator.Supersedes the genuine half of #6423
#6423 bundled these two fixes with a per-validator timeout (
NestedTimeouton each validator). That part is deliberately left out. It turns a stall into a refusal one rung earlier without finding out why the validator does not answer. The create's own deadline already answers that case, and since #6421 it names the stalled validator (validators: <TypeName> (i/n)). The root cause of the production stall (why that validator's read never answers) stays open on #6391. #6423 also resolved a service withGetService ?? new, which this PR does not carry.How tested
New
test/MeshWeaver.Hosting.Test/CreationValidatorGivesExactlyOneVerdictTest.cs. No sleeps; each assertion checks what the verdict contains, inside the operation budget:Validand stays subscribed, then a refusing validatorValidationFailed), nothing storedUnavailable, "reached no verdict within 6s … stage 'validators: MisbehavingCreationValidator'"Validand stays subscribed, nothing refusesUnavailable, validator and path named, nothing storedsuccess=True, the node was CREATEDLifecycleRequestsAlwaysReachAVerdictTest(5 tests) still passes, and so doesLocalizationTest. Builds are clean with-c Release -warnaserrorfor Messaging.Hub, Mesh.Contract, Documentation, Hosting.Test and Messaging.Hub.Test.Doc:
Doc/Architecture/WriteVerdictTotalitygains the section "Each validator gives EXACTLY ONE verdict"; theAccessControl#2742 paragraph now points to it.Nothing to recycle:
Mesh.Contractis framework code and takes effect when a portal rolls.Mirror-sync: none — the new key
activity.node.create.validatorGaveNoVerdictis server-side create-refusal text, which no React client renders from the catalog; the next routinenpm run sync:i18npicks it up.Refs #6391
🤖 Generated with Claude Code