Skip to content

Commit c16a1d9

Browse files
committed
Join native query cancellation proof and require complete-flow coverage
1 parent 88ce19e commit c16a1d9

9 files changed

Lines changed: 500 additions & 33 deletions

File tree

‎AGENTS.md‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ Rule format:
162162
## Global Skills
163163
The explicit owner instruction to enable Orleans distributed directory and activation repartitioning is consent to those two native experimental APIs. Confine compiler opt-in ORLEANSEXP003/ORLEANSEXP001 to their two configuration calls, with ADR-034 evidence; it does not authorize global NoWarn, suppression of quality diagnostics or changing analyzer severity.
164164

165+
- On 2026-10-05 the owner explicitly authorized installing the Managed Code `quality` bundle from https://skills.managed-code.com/bundles/quality/ and running KeyLoad complexity, CRAP and related code-quality analysis. Use the catalog's current bundle skill names and native installation command; this scoped permission supersedes the historical skill-installation prohibition and older `mcaf-*` naming requirement for this bundle only. Preserve existing quality thresholds and use the Aspire-owned entry point for coverage-producing tests.
165166
- On 2026-10-01 the owner explicitly authorized installing the Orleans skill through the `dotnet skills` command. This rule-specific permission applies to the requested Orleans skill; other skill installation remains prohibited unless separately authorized. Read and apply its installed SKILL.md before continuing Orleans implementation.
166167
- Orleans 3.1.1 is installed globally at `/Users/ksemenenko/.codex/skills/orleans/SKILL.md`, from Managed Code catalog `2026.10.1.0` using `dotnet-skills` 0.1.242. It is applied to Orleans design, lifecycle, transport, routing and failure verification. This is the specifically authorized addition; the historical bootstrap installed no skills.
167168
- No MCAF or .NET skills are installed for this bootstrap. The owner explicitly instructed: do not install skills. Do not create skill directories, install tools or modify global agent configuration for this task.
@@ -182,7 +183,7 @@ The explicit owner instruction to enable Orleans distributed directory and activ
182183
- `format`: `dotnet format KeyLoad.slnx --verify-no-changes --no-restore` is the required formatter command and CI gate; source configuration does not establish a green formatter qualification.
183184
- `analyze`: solution Release build with TreatWarningsAsErrors=true, AnalysisLevel=latest-all, SDK/style analysis and centrally attached KeyLoad.Analyzers; compiler SARIF reports are retained under artifacts/code-quality.
184185
- `complexity`: numeric policy limits below are mandatory and are enforced by source-owned KLD0030/KLD0031/KLD0032/KLD0033 during ordinary consumer builds; the analyzer infrastructure has a real compiler source-inventory fixture in CI. Configuration or a scoped build MUST NOT be reported as a passing full gate without the exact-SHA complete build and fixture qualification.
185-
- `coverage`: no CI coverage collector or numeric baseline is configured yet. Configure an MTP/TUnit-compatible collector before claiming the coverage thresholds below pass; no invented coverage result is allowed.
186+
- `coverage`: owner direction 2026-10-05 requires native MTP/TUnit-compatible code-coverage collection through the Aspire-owned test entry point, retained original reports, and module/file/line/branch gap analysis. Only functional operation tests may contribute; load, stress, performance and database-comparison runs MUST NOT contribute to coverage totals. Until a complete source-bound report exists, report coverage as unmeasured rather than inventing a numeric baseline or claiming the thresholds below pass.
186187
- `governance`: `node scripts/Features/RepositoryGovernance/verify.mjs` validates the real inventory and policy-preservation record; it is static installation validation, not a runtime test result.
187188
- Active runner: Microsoft.Testing.Platform in global.json; mandatory framework: TUnit. Existing xUnit/VSTest references are migration debt, not a permitted second framework.
188189
- .NET target is net10.0. C# 14.0 is explicitly pinned in Directory.Build.props. Root .editorconfig is the source of truth for formatting/style/analyzer severity; nested files require a concrete subtree purpose.
@@ -344,7 +345,7 @@ Local `AGENTS.md` files may tighten these values, but they must not loosen them
344345
- Each `AC-*` criterion in the owning feature specification MUST be covered by one or more automated tests or by an explicit written exception.
345346
- Test names, display names, or comments should reference the relevant `AC-*` ID when that improves traceability.
346347
- Every behaviour change needs new or updated automated tests with meaningful assertions. New tests are mandatory for new behaviour and bug fixes.
347-
- Tests must prove the real user flow or caller-visible system flow, not only internal implementation details.
348+
- Owner correction 2026-10-05 requires every test to contain a complete real operation or caller-visible workflow: arrange actual preconditions, execute the operation through its real contract, and verify its outcome plus the resulting state or fields changed during that operation. Getter/setter checks, property-only assertions and implementation-mirroring tests do not satisfy this rule or acceptance; a negative flow must execute the rejected operation and verify its error and preserved state.
348349
- Tests should be as realistic as possible and exercise the system through real flows, contracts, and dependencies.
349350
- Tests must cover positive flows, negative flows, edge cases, and unexpected paths from multiple relevant angles when the behaviour can fail in different ways.
350351
- Prefer integration/API/UI tests over isolated unit tests when behaviour crosses boundaries.
@@ -356,6 +357,7 @@ Local `AGENTS.md` files may tighten these values, but they must not loosen them
356357
- Critical flows and public contracts MUST reach at least 90% line coverage with explicit success and failure assertions.
357358
- Repository or module coverage must not decrease without an explicit written exception. Coverage after the change must stay at least at the previous baseline or improve.
358359
- Coverage is for finding gaps, not gaming a number. Coverage numbers do not replace scenario coverage or user-flow verification.
360+
- Owner direction 2026-10-05 requires reviewing actual uncovered code when closing each module and adding meaningful complete-flow regressions for the missing success, failure and edge behaviours. Keep load, stress, performance and comparison tests out of the coverage evidence, preserve an explicit contributor inventory, and never add trivial tests solely to raise a percentage.
359361
- The task is not done until the full relevant test suite is green, not only the newly added tests.
360362
- If the stack is `.NET`, document the active framework and runner model explicitly so agents do not mix VSTest and Microsoft.Testing.Platform assumptions.
361363
- If the stack is `.NET`, after changing production code run the repo-defined quality pass: format, build, analyze, focused tests, broader tests, complexity, coverage, and any configured extra gates such as architecture, security, or mutation checks.

‎docs/ADR/ADR-103-scaled-fair-comparisons.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,3 +110,8 @@ cleanup failures, and run them through the canonical Aspire comparison entry.
110110
ComparisonTests owns this code, partition_pages owns its private guarded packet,
111111
and root owns integration/gates/evidence/commit. There is no data or wire migration;
112112
rollback cannot convert an unfinished original task into a passing qualification.
113+
The stage also replaces the collector's premature completed boolean with one
114+
memoized original completion task, preserves cancellation-callback failures
115+
while joining its original observation, and retains failed write settlement on
116+
repeated teardown. Its schema, byte/time bounds and unqualified categories stay
117+
unchanged. This lifecycle amendment precedes the private repair.

‎docs/Features/BenchmarkComparisons/ScalingQualification.md‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,12 @@ request cancellation or escalation through the original owner's supported API
113113
where available, retain the threshold failure, and ultimately await that same
114114
original task. Do not start a replacement operation, use an uncancellable shadow
115115
task, or dispose an owner while its observation/writer still uses it.
116+
The resource collector memoizes one original completion task. Concurrent or
117+
repeated completion calls await that same task, including its failure; a boolean
118+
set before cancellation/join is not completed settlement. Retain any native stop
119+
cancellation-callback failure and still join the original observation before
120+
disposing the CTS or writing/copying evidence. A failed evidence write remains
121+
the same failed completion on a later teardown call.
116122

117123
AC-SCALE-017 requires genuine native lifecycle regressions to prove that cleanup
118124
does not pass an unfinished original operation, and that cancellation and failure
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
{
2+
"schemaVersion": 1,
3+
"date": "2026-10-05",
4+
"kind": "local-development",
5+
"baseRevision": "88ce19ebd5e4405e2ca102a4ea5f2e1c61e46ed3",
6+
"requirement": "REQ-DQUERY-PREREQ-005",
7+
"acceptance": "AC-DQUERY-PREREQ-005",
8+
"task": "TASK-DQUERY-CANCEL-OBSERVATION",
9+
"scope": "Native synchronous partition-query cancellation observation and related functional regressions",
10+
"source": [
11+
{
12+
"path": "tests/KeyLoad.UnitTests/Features/QueryExecution/Cases/PartitionQueryCancellationTests.cs",
13+
"sha256": "0cfcbd1c35211eb19d923611dc1fa4e53b9e0928309d2ebf9fd88385b83e81ee"
14+
},
15+
{
16+
"path": "tests/KeyLoad.UnitTests/Features/QueryExecution/Helpers/PartitionQueryCancellationFailures.cs",
17+
"sha256": "36c057734c08ae373d7b58c86339cda16edcf5b21433ffb9b2076481cdf9649a"
18+
},
19+
{
20+
"path": "tests/KeyLoad.UnitTests/Features/QueryExecution/Helpers/PartitionQueryCancellationObserver.cs",
21+
"sha256": "cd1a9729069e70431ff743bf1d4246578989c9f93d2ce0a3ce98e2a9308d0193"
22+
},
23+
{
24+
"path": "tests/KeyLoad.UnitTests/Features/QueryExecution/Helpers/PartitionQueryCancellationRun.cs",
25+
"sha256": "8577045df2ba053e105f5a2894ae48cd10ba640e9c0301273fa734d9a5de527f"
26+
}
27+
],
28+
"preserved": {
29+
"nativeZoneTreeRecords": 5000,
30+
"operationDeadlineSeconds": 30,
31+
"originalRequestAndCallerCancellationToken": true,
32+
"exactCancellationTokenAsserted": true,
33+
"noPartialResultAsserted": true,
34+
"healthyFollowUpAsserted": true,
35+
"originalObserverJoinedBeforeTokenEventAndStorageDisposal": true
36+
},
37+
"build": {
38+
"command": "dotnet build KeyLoad.slnx --no-restore --configuration Release",
39+
"exitCode": 0,
40+
"warnings": 0,
41+
"errors": 0,
42+
"log": "/private/tmp/keyload-stage88b-query-cancellation-build-20261005.log",
43+
"sha256": "9ba350ada494338827611cc0444cc68d718b4f25f2613f7d2a79e04d8030a350"
44+
},
45+
"initialBuildFailure": {
46+
"errors": 8,
47+
"warnings": 0,
48+
"cause": "Six CA1031 catch diagnostics and two IDE0005 imports were repaired without suppression",
49+
"log": "/private/tmp/keyload-stage88a-query-cancellation-build-20261005.log",
50+
"logPresent": false
51+
},
52+
"formatter": {
53+
"command": "dotnet format KeyLoad.slnx --verify-no-changes --no-restore",
54+
"exitCode": 0,
55+
"log": "/private/tmp/keyload-stage88-query-cancellation-format-20261005.log"
56+
},
57+
"governance": {
58+
"command": "node scripts/Features/RepositoryGovernance/verify.mjs",
59+
"exitCode": 0,
60+
"actualInventory": "27 projects and 4 modules"
61+
},
62+
"nativeRuns": [
63+
{
64+
"suite": "unit",
65+
"entryPoint": "Aspire-owned KeyLoad.AppHost",
66+
"filter": "/*/*/PartitionQuery*/*",
67+
"summary": {
68+
"total": 25,
69+
"passed": 25,
70+
"failed": 0,
71+
"skipped": 0,
72+
"cancelled": 0,
73+
"timedOut": 0,
74+
"flaky": 0
75+
},
76+
"artifacts": [
77+
{
78+
"path": "TestResults/stage88c-partition-query/KeyLoad.UnitTests-macos-net10.0-report.html",
79+
"sha256": "8586c40ac8679e708b4ae7e0d3b907b22198a9d342628a3b4a82abf2c8ff603e"
80+
},
81+
{
82+
"path": "TestResults/stage88c-partition-query/KeyLoad.UnitTests-macos-net10.0.tunit-report.json",
83+
"sha256": "4fd32978b0d9a18cdf2c15a8948c2bd22a34cf206f53baa3e41e165028eef651"
84+
},
85+
{
86+
"path": "TestResults/stage88c-partition-query/KeyLoad.UnitTests_net10.0_arm64.trx",
87+
"sha256": "baf6b0ce1e54d4e9c8f9341aa68082396d1a4d752783bb45322acd319cbba2c8"
88+
},
89+
{
90+
"path": "TestResults/stage88c-partition-query/_konstantins-macbook-pro_2026-10-05_20_30_56.9942060/In/konstantins-macbook-pro/KeyLoad.UnitTests-macos-net10.0-report.html",
91+
"sha256": "8586c40ac8679e708b4ae7e0d3b907b22198a9d342628a3b4a82abf2c8ff603e"
92+
}
93+
],
94+
"qualification": "local-development-only"
95+
},
96+
{
97+
"suite": "unit-scalar",
98+
"entryPoint": "Aspire-owned KeyLoad.AppHost",
99+
"filter": "/*/*/PartitionQuery*/*",
100+
"summary": {
101+
"total": 25,
102+
"passed": 25,
103+
"failed": 0,
104+
"skipped": 0,
105+
"cancelled": 0,
106+
"timedOut": 0,
107+
"flaky": 0
108+
},
109+
"artifacts": [
110+
{
111+
"path": "TestResults/stage88d-partition-query-scalar/KeyLoad.UnitTests-macos-net10.0-report.html",
112+
"sha256": "0c007716c69268da77faf5ff723128e5027e019d2d8ad16538853bcb2404175b"
113+
},
114+
{
115+
"path": "TestResults/stage88d-partition-query-scalar/KeyLoad.UnitTests-macos-net10.0.tunit-report.json",
116+
"sha256": "f26f1a9938fbebaae4f1f2ecb0cefbcdc142aecabab1b3591faafa81a1f2244a"
117+
},
118+
{
119+
"path": "TestResults/stage88d-partition-query-scalar/KeyLoad.UnitTests_net10.0_arm64.trx",
120+
"sha256": "6740fca30a0e89ceb19f199cb8b79eb95ca0eb031b35ef4f1506dd77bfff1b2d"
121+
},
122+
{
123+
"path": "TestResults/stage88d-partition-query-scalar/_konstantins-macbook-pro_2026-10-05_20_36_00.8989700/In/konstantins-macbook-pro/KeyLoad.UnitTests-macos-net10.0-report.html",
124+
"sha256": "0c007716c69268da77faf5ff723128e5027e019d2d8ad16538853bcb2404175b"
125+
}
126+
],
127+
"qualification": "local-development-only"
128+
}
129+
],
130+
"fullModuleAcceptanceClosed": false,
131+
"deliveredLinuxQualified": false,
132+
"remaining": [
133+
"Full required unit/scalar/recovery/RF3 and exact-source Linux qualification remain required",
134+
"Remote physical-owner fanout for KL-037 remains unimplemented",
135+
"Functional coverage collection and module/file/line/branch gap review are separately required; these runs did not collect coverage"
136+
],
137+
"priorFailures": "Original full-suite failures remain retained in partition-runtime-development-2026-10-05.json; focused passes do not replace them"
138+
}

‎docs/implementation/status.json‎

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1433,14 +1433,25 @@
14331433
"src/KeyLoad.Query/Features/QueryExecution/Queries/QueryEngine.PartitionQuery.cs",
14341434
"src/KeyLoad.Client/Features/QueryExecution/Transport/PartitionQueryClient.cs",
14351435
"tests/KeyLoad.UnitTests/Features/QueryExecution/Cases/PartitionQueryPublicContractTests.cs",
1436-
"tests/KeyLoad.IntegrationTests/Features/QueryExecution/Cases/PartitionQueryPublicRf3Tests.cs"
1436+
"tests/KeyLoad.IntegrationTests/Features/QueryExecution/Cases/PartitionQueryPublicRf3Tests.cs",
1437+
"docs/implementation/query-cancellation-development-2026-10-05.json"
14371438
],
14381439
"sourceStage": "bounded_same_physical_owner_partition_query_public_sdk_http_mcp_and_native_orleans_read_path",
14391440
"qualification": {
14401441
"implementation": "source joined; local Release solution build has zero warnings/errors; focused Aspire unit validation passed 25/25",
14411442
"acceptance": "open",
14421443
"remotePhysicalOwnerFanout": "not implemented",
14431444
"fullTaskComplete": false
1445+
},
1446+
"queryCancellationDevelopment": {
1447+
"requirement": "REQ-DQUERY-PREREQ-005",
1448+
"acceptance": "AC-DQUERY-PREREQ-005",
1449+
"receipt": "docs/implementation/query-cancellation-development-2026-10-05.json",
1450+
"nativeNormal": "25_of25",
1451+
"nativeScalar": "25_of25",
1452+
"releaseBuild": "0_warnings_0_errors",
1453+
"fullModuleAcceptanceClosed": false,
1454+
"deliveredLinuxQualified": false
14441455
}
14451456
},
14461457
"KL-038": {
Lines changed: 5 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,11 @@
1-
using System.Diagnostics;
21
using KeyLoad.Core;
32
using KeyLoad.Query;
4-
using KeyLoad.Query.Features.QueryExecution;
53

64
namespace KeyLoad.UnitTests.Features.QueryExecution;
75

86
internal sealed class PartitionQueryCancellationTests
97
{
108
private const int SeedCount = 5_000;
11-
private static readonly TimeSpan ObservationBound = TimeSpan.FromSeconds(10);
129

1310
[Test]
1411
public async Task ObservedNativeReadProgressCancelsTheOriginalBudgetAndLeavesNoPartialResult()
@@ -18,22 +15,12 @@ public async Task ObservedNativeReadProgressCancelsTheOriginalBudgetAndLeavesNoP
1815
using var cancellation = new CancellationTokenSource();
1916
var budget = new ReadExecutionBudget(database.Database.Limits, cancellationToken: cancellation.Token);
2017
var engine = new QueryEngine(database.Database);
21-
var operation = Task.Run(() => engine.ExecutePartitionQuery(PartitionQueryTestSupport.Principal,
22-
PartitionQueryTestSupport.Request(database, 1), [database.Partition], budget));
23-
var observedBytes = await WaitForReadProgressAsync(budget, operation);
24-
await cancellation.CancelAsync();
25-
OperationCanceledException? failure = null;
26-
try
27-
{
28-
_ = await operation;
29-
}
30-
catch (OperationCanceledException error)
31-
{
32-
failure = error;
33-
}
18+
var outcome = PartitionQueryCancellationRun.Execute(database, engine, cancellation, budget);
3419

35-
await Assert.That(observedBytes).IsGreaterThan(0L);
36-
await Assert.That(failure?.CancellationToken).IsEqualTo(cancellation.Token);
20+
await Assert.That(outcome.QueryReturned).IsFalse();
21+
await Assert.That(outcome.Cancellation?.CancellationToken).IsEqualTo(cancellation.Token);
22+
await Assert.That(outcome.CancellationRequested).IsTrue();
23+
await Assert.That(outcome.ObservedReadBytes).IsGreaterThan(0L);
3724
var healthy = engine.ExecutePartitionQuery(PartitionQueryTestSupport.Principal,
3825
PartitionQueryTestSupport.Request(database, 1), [database.Partition]);
3926
await Assert.That(healthy.Complete).IsTrue();
@@ -52,16 +39,4 @@ private static void SeedRows(TestDatabase database)
5239
PartitionQueryTestSupport.AddRows(database, database.Partition, rows);
5340
}
5441
}
55-
56-
private static async Task<long> WaitForReadProgressAsync(ReadExecutionBudget budget,
57-
Task<PartitionQueryResultV1> operation)
58-
{
59-
var started = Stopwatch.GetTimestamp();
60-
while (budget.ReadBytes == 0 && !operation.IsCompleted
61-
&& Stopwatch.GetElapsedTime(started) < ObservationBound)
62-
{
63-
await Task.Yield();
64-
}
65-
return budget.ReadBytes;
66-
}
6742
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
using System.Runtime.ExceptionServices;
2+
using ManagedCode.Communication.CQRS;
3+
4+
namespace KeyLoad.UnitTests.Features.QueryExecution;
5+
6+
internal static class PartitionQueryCancellationFailures
7+
{
8+
private const string FailureMessage = "The partition query and native cancellation observer did not settle cleanly.";
9+
10+
internal static void ThrowIfAny(Exception? primary, List<Exception> settlement)
11+
{
12+
var failures = new List<Exception>();
13+
if (primary is not null)
14+
{
15+
failures.Add(primary);
16+
}
17+
foreach (var failure in settlement)
18+
{
19+
if (!failures.Contains(failure, ReferenceEqualityComparer.Instance))
20+
{
21+
failures.Add(failure);
22+
}
23+
}
24+
ThrowWithFatalPriority(failures);
25+
}
26+
27+
private static void ThrowWithFatalPriority(List<Exception> failures)
28+
{
29+
if (failures.Count == 0)
30+
{
31+
return;
32+
}
33+
var fatalIndex = failures.FindIndex(failure => CqrsRuntimeFailures.FindFatal(failure) is not null);
34+
if (failures.Count == 1)
35+
{
36+
ExceptionDispatchInfo.Capture(failures[0]).Throw();
37+
}
38+
if (fatalIndex > 0)
39+
{
40+
(failures[0], failures[fatalIndex]) = (failures[fatalIndex], failures[0]);
41+
}
42+
throw new AggregateException(FailureMessage, failures);
43+
}
44+
}

0 commit comments

Comments
 (0)