Skip to content

Commit df79b64

Browse files
committed
Cover SDK and MCP document CRUD parity in RF3
1 parent 6e09f03 commit df79b64

6 files changed

Lines changed: 298 additions & 3 deletions

File tree

‎docs/Features/DocumentStorage.md‎

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ DocumentStorage owns tenant/database/domain-scoped JSON documents, revisions, mu
1414
| Backend | `src/KeyLoad.Core/Documents.cs`, shared mutation dispatch in `DatabaseEngine.cs` | `src/KeyLoad.Core/Features/DocumentStorage/` |
1515
| Tests | `tests/KeyLoad.UnitTests/Features/DocumentStorage/`, shared atomic batch cases in `Features/ResourceExecution/TransactionTests.cs`, `SecurityAndQueryTests.cs`, `Features/QueryExecution/` | DocumentStorage behavior and its documented cross-slice atomic/query callers |
1616
| Durable specification | This file | `docs/Features/DocumentStorage.md` |
17-
| HTTP | `src/KeyLoad.Server/ApiEndpoints.cs`: `POST /v1/documents/get` and shared mutation command `POST /v1/commands` | Shared HTTP transport belongs to `src/KeyLoad.Server/Features/ClientApi/`; document validation and behavior belong to `src/KeyLoad.Core/Features/DocumentStorage/` |
17+
| HTTP | `src/KeyLoad.Server/Features/DocumentStorage/DocumentApi.cs` plus shared `Features/ClientApi/ApiEndpoints.cs`: `POST /v1/documents/get` and mutation command `POST /v1/commands` | Shared HTTP transport belongs to `src/KeyLoad.Server/Features/ClientApi/`; document validation and behavior belong to `src/KeyLoad.Core/Features/DocumentStorage/` |
1818
| .NET SDK | `src/KeyLoad.Client/KeyLoadClient.cs`: `GetAsync` and `CommitAsync` | Shared client transport belongs to `src/KeyLoad.Client/Features/ClientApi/`; typed document behavior maps to this DocumentStorage slice |
19-
| Official MCP | Not present in current source; required integration is planned under shared ClientApi, with document operations mapped to this slice | Tool/protocol mapping awaits ADR-039; no tool names or routes are asserted |
19+
| Official MCP | Actual `Features/ClientApi/McpCommandCatalog.cs` and `McpReadCatalog.cs`, with `McpDocumentParityTests` exercising `keyload_documents_commit` and `keyload_documents_get` through the official C# SDK | Shared ClientApi owns transport/dispatch; DocumentStorage owns the CRUD contract and matching RF3 parity cases |
2020
| UI | No document-specific frontend interaction is specified | N/A: database document CRUD is consumed through API/SDK/MCP, not a separate UI surface |
2121
| Backup/export | Cross-resource archive behavior is owned by BackupRestore | N/A here: this slice supplies canonical records but does not define an independent backup format |
2222

@@ -95,3 +95,11 @@ flowchart LR
9595
```
9696

9797
The [2026-10-04 development receipt](../implementation/keycodec-crud-development-2026-10-04.json) records8 new real-ZoneTree CRUD cases in full Aspire normal/scalar suites at2889/2889 each and recovery228/228, with unchanged source/runtime and1000 unique atomic process cuts. Local development verification is authorized through unified Aspire; delivered-source qualification still requires complete Linux GitHub original build/TUnit/recovery/Docker RF3 artifacts. Required official MCP parity through Aspire RF3 remains pending. Document-specific UI is N/A; cross-partition unique constraints and production readiness remain unqualified.
98+
99+
## RF3 CRUD public-client completion (2026-10-04 accepted test scope)
100+
101+
TASK-DSTORE-RF3-PARITY adds only new `McpDocumentCrudParityTests`, `McpDocumentCrudParityAssertions` and, if needed, `McpDocumentCrudParityScenario` under IntegrationTests/Features/DocumentStorage. REQ-DSTORE-001 / AC-DSTORE-001 and existing ADR-002 define the behavior; an additional ADR is N/A because there is no boundary, format, transport or mutation-contract change. cluster_wave Luna/high owns these disjoint test files; root owns review, feature/task mapping, builds and exact-source Aspire/Docker RF3 qualification.
102+
103+
Two mirrored success cases use the actual existing keyed Aspire ClusterFixture, separate real scoped persisted principal/API-key grants, the .NET SDK and official MCP C# SDK on different RF3 endpoints. One executes SDK create -> MCP explicit replacement -> SDK Patch -> MCP Delete; the other reverses each caller. Both callers therefore successfully execute Patch and Delete, and opposite-client reads assert exact canonical JSON and revisions 1/2/3, revision4 tombstone mutation receipt, and null from both after deletion. Matching accepted command retries through the other client preserve the original command receipt/token. A third case performs an explicit replacement at revision1, then repeats a stale expected revision1 through MCP and the same stable command through SDK: exact RevisionConflict and unchanged revision2/JSON are required. Error assertions do not infer durable storage of a failed outcome merely from repeated identical errors.
104+
105+
Use the existing bounded McpCallerDeadline, actual persisted authorization helpers, native official tool serializers and existing fixture cleanup. No mock, hand-written MCP transport, trusted client role, new listener, broadened retries or weakened test is allowed. Existing document and policy tests stay intact. The earlier e97 Linux RF3 report remains 83/84 overall; this test scope counts only after its own complete exact-source Linux Aspire RF3 result.

‎docs/implementation/documentation-coverage.json‎

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11162,6 +11162,85 @@
1116211162
"state": "root_reviewed_full_Aspire_local_normal2908_scalar2908_recovery257_passed_exact_Linux_cold_RF3_pending_20261004",
1116311163
"joinCondition": "all_migration_inputs_guarded_actual_FIFO_no_hang_lock_interop_and_exact_original_preservation",
1116411164
"developmentEvidence": "docs/implementation/node-epoch-development-2026-10-04.json"
11165+
},
11166+
{
11167+
"id": "TASK-DSTORE-RF3-PARITY",
11168+
"owner": "cluster_wave",
11169+
"model": "gpt-6-luna",
11170+
"effort": "high",
11171+
"contracts": [
11172+
"docs/Features/DocumentStorage.md",
11173+
"docs/ADR/ADR-002-command-idempotency.md"
11174+
],
11175+
"requirementIds": [
11176+
"REQ-DSTORE-001"
11177+
],
11178+
"acceptanceIds": [
11179+
"AC-DSTORE-001"
11180+
],
11181+
"backlog": [
11182+
"KL-010"
11183+
],
11184+
"permissions": "test_only_existing_wire_and_persisted_authority_contract",
11185+
"fileOwnership": [
11186+
"tests/KeyLoad.IntegrationTests/Features/DocumentStorage/McpDocumentCrudParityTests.cs",
11187+
"tests/KeyLoad.IntegrationTests/Features/DocumentStorage/McpDocumentCrudParityAssertions.cs",
11188+
"tests/KeyLoad.IntegrationTests/Features/DocumentStorage/McpDocumentCrudParityScenario.cs"
11189+
],
11190+
"dependencies": [],
11191+
"startCondition": "root_approved_exact_mirrored_success_and_stale_replacement_oracles_after_6e09f03_checkpoint",
11192+
"artifacts": [
11193+
"both_real_clients_successfully_Patch_and_Delete",
11194+
"exact_revision_and_canonical_JSON_oracles",
11195+
"explicit_stale_replacement_and_same_ID_error_no_state_change",
11196+
"root_diff_review"
11197+
],
11198+
"verification": [
11199+
"strict Release solution build",
11200+
"formatter",
11201+
"governance",
11202+
"Aspire actual Docker RF3 SDK and official MCP",
11203+
"exact delivered-source Linux original reports"
11204+
],
11205+
"state": "source_root_reviewed_full_Release_and_formatter_passed_actual_RF3_unqualified",
11206+
"joinCondition": "both_write_directions_real_persisted_principal_no_public_semantic_change_no_skipped_gate",
11207+
"sourceReview": {
11208+
"files": [
11209+
{
11210+
"path": "tests/KeyLoad.IntegrationTests/Features/DocumentStorage/McpDocumentCrudParityAssertions.cs",
11211+
"bytes": 3429,
11212+
"sha256": "3765e29f6aa9f78ceeca98d3fc69a947301a54f8de8d2cdd4dcbee7cbd6a58d2"
11213+
},
11214+
{
11215+
"path": "tests/KeyLoad.IntegrationTests/Features/DocumentStorage/McpDocumentCrudParityScenario.cs",
11216+
"bytes": 2107,
11217+
"sha256": "79ce374e7d5efe4e7db19302d0645466cbdc5183a722b3a1fb5038f46bac9890"
11218+
},
11219+
{
11220+
"path": "tests/KeyLoad.IntegrationTests/Features/DocumentStorage/McpDocumentCrudParityTests.cs",
11221+
"bytes": 6376,
11222+
"sha256": "793be85426d1611ba2ffedf4532620f69fd46633be1786a2b2b039f0539d2714"
11223+
}
11224+
],
11225+
"checks": {
11226+
"build": {
11227+
"exitCode": 0,
11228+
"path": "artifacts/qualification/document-crud-rf3-source-20261004-0342/build.log",
11229+
"bytes": 3544,
11230+
"sha256": "4fa3218fdabe416a159749258955aef7fa18be59c157ca9d5e6331a3fd9f1f84"
11231+
},
11232+
"formatter": {
11233+
"exitCode": 0,
11234+
"path": "artifacts/qualification/document-crud-rf3-source-20261004-0342/formatter.log",
11235+
"bytes": 0,
11236+
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
11237+
}
11238+
},
11239+
"actualRF3Executed": false,
11240+
"publicContractsChanged": false,
11241+
"caseCount": 3,
11242+
"bothClientsEachPatchAndDelete": true
11243+
}
1116511244
}
1116611245
],
1116711246
"qualificationRule": "Neither presence of source nor a local development result closes a full task. Preserve exact-source Linux fault/endurance and publication gates.",

‎docs/implementation/status.json‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -682,7 +682,8 @@
682682
"src/KeyLoad.Core/DatabaseEngine.cs",
683683
"tests/KeyLoad.UnitTests/TransactionTests.cs",
684684
"tests/KeyLoad.UnitTests/Features/DocumentStorage",
685-
"docs/implementation/keycodec-crud-development-2026-10-04.json"
685+
"docs/implementation/keycodec-crud-development-2026-10-04.json",
686+
"tests/KeyLoad.IntegrationTests/Features/DocumentStorage/McpDocumentCrudParityTests.cs"
686687
],
687688
"qualification": {
688689
"sourceStage": "real_ZoneTree_CRUD_revision_validation_index_and_persisted_policy_matrix",
@@ -709,6 +710,13 @@
709710
"overallConclusion": "failure",
710711
"newNodeStageQualified": false,
711712
"originalReportsReceipt": "docs/implementation/node-epoch-development-2026-10-04.json"
713+
},
714+
"RF3ParitySource": {
715+
"cases": 3,
716+
"rootReviewed": true,
717+
"ReleaseBuildAndFormatterPassed": true,
718+
"actualRF3Executed": false,
719+
"traceability": "TASK-DSTORE-RF3-PARITY"
712720
}
713721
}
714722
},
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
using KeyLoad.Client;
2+
using KeyLoad.IntegrationTests.Features.ClientApi;
3+
4+
namespace KeyLoad.IntegrationTests.Features.DocumentStorage;
5+
6+
/// <summary>Exact comparisons shared by the real SDK/MCP CRUD parity cases.</summary>
7+
internal static class McpDocumentCrudParityAssertions
8+
{
9+
internal static async Task<CommitReceipt> CommitAndRetryAsync(KeyLoadClient sdk, McpOfficialClient mcp,
10+
CommandRequest command, bool sdkPrimary, CancellationToken cancellationToken)
11+
{
12+
var first = await CommitAsync(sdk, mcp, command, sdkPrimary, cancellationToken);
13+
var retry = await CommitAsync(sdk, mcp, command, !sdkPrimary, cancellationToken);
14+
await Assert.That(retry.Token).IsEqualTo(first.Token);
15+
await Assert.That(JsonDefaults.Serialize(first).AsSpan().SequenceEqual(JsonDefaults.Serialize(retry))).IsTrue();
16+
return first;
17+
}
18+
19+
internal static async Task AssertMutationAsync(CommitReceipt receipt, Guid commandId, string kind, long revision)
20+
{
21+
await Assert.That(receipt.CommandId).IsEqualTo(commandId);
22+
await Assert.That(receipt.Mutations).HasSingleItem();
23+
await Assert.That(receipt.Mutations[0]).IsEqualTo(
24+
new MutationReceipt(kind, McpDocumentProtocol.Collection, McpDocumentProtocol.Entity, revision));
25+
await Assert.That(receipt.Durability).IsEqualTo(DurabilityProfile.QuorumProcessDurable);
26+
}
27+
28+
internal static async Task AssertDocumentAsync(KeyLoadClient sdk, McpOfficialClient mcp,
29+
McpDocumentCrudParityScenario scenario, long? expectedRevision, string? expectedJson,
30+
CancellationToken cancellationToken)
31+
{
32+
var sdkDocument = await McpCallerAssertions.SdkSuccessAsync(await sdk.GetAsync(scenario.Reference, cancellationToken));
33+
var mcpDocument = await McpCallerAssertions.SuccessAsync<DocumentResult?>(await mcp.CallAsync(
34+
McpCallerTools.DocumentsGet, new GetDocumentRequest(scenario.Reference), cancellationToken));
35+
if (expectedRevision is null)
36+
{
37+
await Assert.That(sdkDocument).IsNull();
38+
await Assert.That(mcpDocument.Value).IsNull();
39+
return;
40+
}
41+
42+
await Assert.That(sdkDocument).IsNotNull();
43+
await Assert.That(mcpDocument.Value).IsNotNull();
44+
var sdkValue = sdkDocument!;
45+
var mcpValue = mcpDocument.Value!;
46+
await Assert.That(sdkValue.Revision).IsEqualTo(expectedRevision.Value);
47+
await Assert.That(mcpValue.Revision).IsEqualTo(expectedRevision.Value);
48+
await Assert.That(sdkValue.Json).IsEqualTo(expectedJson);
49+
await Assert.That(mcpValue.Json).IsEqualTo(expectedJson);
50+
await Assert.That(sdkValue.Reference).IsEqualTo(scenario.Reference);
51+
await Assert.That(mcpValue.Reference).IsEqualTo(scenario.Reference);
52+
await Assert.That(JsonDefaults.Serialize(sdkValue).AsSpan()
53+
.SequenceEqual(JsonDefaults.Serialize(mcpValue))).IsTrue();
54+
}
55+
56+
private static async Task<CommitReceipt> CommitAsync(KeyLoadClient sdk, McpOfficialClient mcp,
57+
CommandRequest command, bool useSdk, CancellationToken cancellationToken)
58+
=> useSdk
59+
? await McpCallerAssertions.SdkSuccessAsync(await sdk.CommitAsync(command, cancellationToken))
60+
: (await McpCallerAssertions.SuccessAsync<CommitReceipt>(await mcp.CallAsync(
61+
McpCallerTools.DocumentsCommit, command, cancellationToken))).Value;
62+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
namespace KeyLoad.IntegrationTests.Features.DocumentStorage;
2+
3+
/// <summary>Frozen caller-visible document values and commands for the real RF3 CRUD parity cases.</summary>
4+
internal sealed record McpDocumentCrudParityScenario(PartitionRef Partition)
5+
{
6+
internal const long CreatedRevision = 1;
7+
internal const long ReplacedRevision = 2;
8+
internal const long PatchedRevision = 3;
9+
internal const long TombstoneRevision = 4;
10+
internal const string CreatedJson = "{\"state\":\"created\"}";
11+
internal const string ReplacedJson = "{\"state\":\"replaced\"}";
12+
internal const string PatchedJson = "{\"state\":\"patched\"}";
13+
internal const string StaleJson = "{\"state\":\"stale\"}";
14+
15+
internal EntityRef Reference => new(Partition, McpDocumentProtocol.Collection, McpDocumentProtocol.Entity);
16+
17+
internal CommandRequest CreateCommand(Guid commandId)
18+
=> Command(commandId, new PutDocument(McpDocumentProtocol.Collection, McpDocumentProtocol.Entity,
19+
CreatedJson, ExpectedRevision: 0));
20+
21+
internal CommandRequest ReplaceCommand(Guid commandId)
22+
=> Command(commandId, new PutDocument(McpDocumentProtocol.Collection, McpDocumentProtocol.Entity,
23+
ReplacedJson, ExpectedRevision: CreatedRevision, ExplicitReplacement: true));
24+
25+
internal CommandRequest StaleReplaceCommand(Guid commandId)
26+
=> Command(commandId, new PutDocument(McpDocumentProtocol.Collection, McpDocumentProtocol.Entity,
27+
StaleJson, ExpectedRevision: CreatedRevision, ExplicitReplacement: true));
28+
29+
internal CommandRequest PatchCommand(Guid commandId)
30+
=> Command(commandId, new PatchDocument(McpDocumentProtocol.Collection, McpDocumentProtocol.Entity,
31+
[new("/state", PatchKind.Set, "\"patched\"")], ReplacedRevision));
32+
33+
internal CommandRequest DeleteCommand(Guid commandId)
34+
=> Command(commandId, new DeleteDocument(McpDocumentProtocol.Collection, McpDocumentProtocol.Entity,
35+
PatchedRevision));
36+
37+
private CommandRequest Command(Guid commandId, Mutation mutation)
38+
=> new(commandId, Partition, [mutation]);
39+
}

0 commit comments

Comments
 (0)