Skip to content

Latest commit

 

History

History
279 lines (234 loc) · 28.9 KB

File metadata and controls

279 lines (234 loc) · 28.9 KB

DocumentStorage

Exact caller-owned document text (REQ/AC-DSTORE-008)

REQ-DSTORE-008 retains the exact validated PutDocument.Json text in the native canonical document record: Unicode, escapes, property order, whitespace and decimal spelling remain caller-owned. Validation still enforces the same byte, depth, object-root, duplicate-member and decimal-range rules. Reuse the existing CanonicalJsonWriter validation walk with a discard sink rather than materializing rewritten canonical text. JsonData.Validate and every frozen canonical fingerprint/golden digest remain unchanged. Patch produces its existing validated derived document; redacted reads still use the existing persisted field policy. There is no promise of original text for a redacted or patched result.

AC-DSTORE-008 maps to DocumentExactContentTests: real TestDatabase/ZoneTree Put, authorized raw read, native record roundtrip and exact same-command replay retain literal caller text; replacement retains its new literal text; duplicate members and out-of-range decimals still fail without effects. Existing CRUD, rollback, index, image/outbox and native serialization regressions remain mandatory. The real SDK/official MCP PhysicalShardCatalogRf3Tests Unicode assertion remains exact across every voter and restart. Root owns JsonData's shared validation join and the document command/test slice; ADR-060 already requires exact caller-owned document strings. Stored record aliases/IDs, native format, public DTOs and command fingerprints do not change; existing historical records are not rewritten. Local proof and exact-source Linux RF3 qualification are separate required evidence.

Status: source-present baseline documented; complete product and GitHub qualification remain pending. Current behavior is distinguished below from the accepted target architecture in design sections 7 and 37–41.

Purpose and actors

DocumentStorage owns tenant/database/domain-scoped JSON documents, revisions, mutations, scalar index maintenance, and authorized reads. Actors are database clients, the server command boundary, and internal query operators. Concrete current operations are DatabaseEngine.GetDocument and mutation application for PutDocument, PatchDocument, and DeleteDocument; these flow through the server/replicated command path. This document describes the observed Core contract and its target slice, not an unqualified production guarantee.

Canonical slice map and boundaries

Surface Current source Target owner
Contracts src/KeyLoad.Abstractions/Contracts.cs (EntityRef, document mutations/results, DocumentAuthority, IndexDefinition) src/KeyLoad.Abstractions/Features/DocumentStorage/
Backend src/KeyLoad.Core/Features/DocumentStorage/Execution/Documents.cs, shared mutation dispatch in DatabaseEngine.cs src/KeyLoad.Core/Features/DocumentStorage/
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
Durable specification This file docs/Features/DocumentStorage.md
HTTP src/KeyLoad.Server/Features/DocumentStorage/Transport/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/
.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
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
UI No document-specific frontend interaction is specified N/A: database document CRUD is consumed through API/SDK/MCP, not a separate UI surface
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

The current root-level files are documented migration debt under ADR-032, not the target layout. Search/query owns query planning and projections; Authorization owns identity/policy rules; Messaging/EventStreams own their resources. DocumentStorage does not own physical node placement, replica consensus, or public route naming.

Current source behavior

  • A collection resource is resolved inside the supplied partition. JSON is validated against database limits. Put creates or replaces with an incremented revision; optional expected revision is checked. Patch requires an existing live document, a nonempty bounded patch, and an exact revision. Delete writes a tombstone and increments revision.
  • Direct document writes are denied when catalog authority is EventStream. Row and field permissions are checked on mutation/read. Reads omit deleted or row-invisible records and use the persisted field projector.
  • Index definitions currently support scalar paths with inclusion rules for null/missing values. Updates remove old keys and write new keys in the same transaction. Unique values are enforced within the partition; a conflicting owner rejects the transaction.
  • CommandRequest and mutation records participate in the shared ordered batch path. Existing tests cover document/event/queue batch atomicity and persisted command retry. The literal partition key alone does not merge distinct transaction domains.
  • The observed implementation does not establish multikey, covering, partial, computed, global-unique, online generation rebuild, or cluster-wide split semantics. These remain design/backlog work in section 7 and KL-010..012/039.

Requirements and acceptance

Requirement Measurable acceptance Existing TUnit evidence or planned test
REQ-DSTORE-001: validate scoped CRUD, revisions, and document authority AC-DSTORE-001 passes when create/read/replace/patch/delete produce monotone revisions, exact-CAS concurrency has one winner, and invalid/stale writes or direct mutation of event-authoritative resources are rejected without partial state. Existing ConcurrentCompareAndSwapHasOneWinner; actual DocumentCrudRevisionTests and DocumentPutValidationAtomicityTests cover create/missing/delete/invalid JSON/authority. Local full-suite evidence below; exact-source CI qualification pending.
REQ-DSTORE-002: maintain declared scalar indexes atomically AC-DSTORE-002 passes when replacing/deleting a document removes its old keys, writes new keys, and a duplicate partition-unique value rejects the full mutation batch. Existing UniqueConflictRollsBackDocumentIndexEventAndEnqueue, AcMp003PointAndIndexDereferenceConsumeTheSameRawReadBudget; actual DocumentScalarIndexMutationTests covers persisted old/new index transitions and rollback. Local full-suite evidence below; exact-source CI qualification pending.
REQ-DSTORE-003: enforce row/field policy at every document boundary AC-DSTORE-003 passes when unauthorized row writes/reads and protected field use fail or project according to policy; tenant or row ownership cannot be supplied to gain access. Existing NestedSensitiveFieldsAreOmittedAndAliasedPredicateAndSortAreDenied, RowScopeAndTenantCannotBeForged; actual field/row/tenant matrix plus isolated replacement-write and Delete-index-use controls. Local full-suite evidence below; exact-source CI qualification pending.
REQ-DSTORE-004: share an atomic transaction domain with eligible events and queues AC-DSTORE-004 passes when document + event + local enqueue commit together or all remain absent, same command retry returns the stored outcome, and identical partition-key text in unrelated domains stays isolated. Existing DocumentEventAndQueueCommitTogetherAndCommandRetryDoesNotRepeatEffects, UniqueConflictRollsBackDocumentIndexEventAndEnqueue, SameLiteralPartitionKeyCannotCrossTransactionDomains. CI qualification pending.
REQ-DSTORE-005: reuse transaction-scoped document images AC-DSTORE-005 passes when one before-record lookup supplies CRUD and outbox; no final staged lookup/decode is required; exact bytes, revisions, tombstones, indexes, authorization, quotas and sequential same-ID mutations remain intact. Native placement admission adds exactly three bounded metadata point reads to the prior six, reused by the Batch receipt and every outbox effect under REQ/AC-MTOKEN-007; paired-size payload work remains unchanged. TASK-MP-007I in ADR-035 and ADR-017; real-store paired-size/counter and before/after/failure cases plus existing transaction/change-feed/recovery/RF3 regressions; GitHub evidence pending.
REQ-DSTORE-006: retain command identity and outcome atomically across real process restart AC-DSTORE-006 passes when two distinct actual CrashHost processes execute one hundred same-ID/same-content retries each around a real first-process kill; every result matches the original complete receipt, while one document revision, one event, one Ready queue message and the exact batch outbox cut remain. Same-ID/changed-content returns Conflict without changing effects or the original outcome; a fresh authorized command succeeds afterward. TASK-DSTORE-COMMAND-100-RESTART in ADR-002; new CommandIdempotencyProcessRecoveryTests and actual CrashHost scenario under Features/DocumentStorage/. This remains planned until original Aspire/native reports exist.
REQ-DSTORE-007: prove persisted precondition-failure replay and authenticated-principal isolation AC-DSTORE-007 passes when a failed expected-revision command replays its exact persisted error after a fresh command makes that precondition satisfiable, without a new document/outbox effect, and a fresh command ID then succeeds. Two distinct persisted authorized principals independently execute the same literal command ID and retain their own exact outcomes and documents; changing either principal's existing command content conflicts without changing either effect. TASK-DSTORE-OUTCOME-MATRIX under ADR-002; new DocumentCommandOutcomeReplayTests and DocumentCommandPrincipalScopeTests, with real TestDatabase/ZoneTree helpers under UnitTests/Features/DocumentStorage. Native Aspire normal/scalar and delivered-source Linux proof remain required.
REQ-DSTORE-009: persist command identity in its full resolved scope AC-DSTORE-009 passes when the scoped-key, retained-error, corruption, prior-frame, restart and public RF3 flows below all pass without outcome rewrites, guessed partition identity or ambiguous principal/ID lookup. TASK-DSTORE-SCOPED-OUTCOMES-001..004; ADR-002, ADR-011 and ADR-017; real ZoneTree unit/scalar, existing CrashHost recovery and SDK/official MCP Aspire RF3 cases. Contract accepted before implementation; no complete gate is claimed.

The current command-outcome retention contract has no automatic TTL/purge path. Retries are supported while the original outcome remains in the canonical store with the same incarnation and current persisted authorization. No finite minimum time window or retry guarantee after explicit outcome/store removal or changed incarnation is advertised. This is documented current behavior, not an implemented expiry policy. A new policy needs an accepted ADR and its own qualification.

TASK-DSTORE-OUTCOME-MATRIX is a bounded completion of two existing ADR-002 test rows. Root owns the contract and review; query_wave Luna/high owns only the two new named cases and cohesive helpers under UnitTests/Features/DocumentStorage. Use the real persisted policies and original native outcome bytes, with literal document/revision and outbox expectations. Physical apply position may advance on error/replay and must not be mistaken for a new domain effect. No production key, fingerprint, serializer, public outcome API, expiry or permission changes are authorized by this test stage.

The2026-10-05 source audit separately found that current outcome keys bind only PrincipalId and CommandId, while the canonical fingerprint binds the batch's partition. The original unscoped-key baseline conflicted when an ID was reused for another partition. The accepted scoped-key source repair below replaces that path; current-source build, recovery and SDK/MCP RF3 qualification remain pending. The historical partial matrix does not close independent partition isolation.

Accepted scoped outcome repair, 2026-10-05

REQ/AC-DSTORE-009 repairs that gap under the exact accepted matrix in ADR-011. Durable identity is verified principal, explicit Global/Partition scope, the complete resolved PartitionRef for Partition, and CommandId. The canonical fingerprint, native StoredOutcome alias and IDs 0..7, persisted authorization, incarnation checks and ordered atomic apply boundary remain unchanged. A partition digest cannot reconstruct its full identity or physical owner.

TASK-DSTORE-FULL-IDENTITY-001 supplements the scoped repair with four complete real-ZoneTree cases, one for each individual PartitionRef component. Hold the other three components, persisted principal and command ID fixed while changing only tenant, database, transaction domain or partition key. Configure two real collections in their actual scopes so a domain change never overwrites the first collection's catalog authority. Commit both commands, compare exact independent receipts on replay, reject changed content separately in each scope, verify both literal document values and unchanged outbox tails after retries/conflicts, then reopen the store and resolve/read both original commands. Expected keys use the existing independent test oracle, not the production scope/key resolver. A dedicated worker owns only new ClusterRouting test cases/helpers; root owns live integration and the ordinary/scalar/recovery/RF3 gates. Acceptance maps to AC-DSTORE-009 and ADR-002/011; these authored cases do not replace genuine prior-format migration evidence or caller-visible RF3 proof.

New partition keys are KeySpace.Partition("outcome-v2", partition, principal, id); global keys are KeyCodec.Encode("outcome-v2", "global", principal, id). New Unknown-scope persisted errors use the distinct nonmovable key KeyCodec.Encode("outcome-v2", "unknown", principal, id); Unknown is explicit missing scope, never an inferred partition or trusted global role. outcome-locator-v2 contains the complete partition/principal/id and the exact matching v2 outcome key. Success and persisted domain failure write outcome, locator where applicable, domain effects, apply watermark and clock in the same existing transaction. New Unknown writes have no locator. Retained prior Unknown rows remain untouched at the internal exact legacy key and act as ambiguity barriers. No new write uses the legacy key and no guessed partition is created.

Operation-aware ResolveOutcome(originalOperation) is the sole retained-result lookup. Remove ambiguous public DatabaseEngine.Outcome(principal,id) and KeySpace.Outcome; update every current caller to its original operation or an internal raw-format oracle. SDK/HTTP/MCP lookup-schema changes are N/A because none exposes the removed Core accessor. UI is N/A for this database contract.

Within one existing view, validate the selected v2 row and the retained legacy row, including each row's native metadata and applicable locator, before deciding replay or new write. Known legacy scope matching the requested scope retains its original replay/conflict behavior; a valid different known scope does not collide. Unknown legacy scope is an ambiguity barrier: matching content retains its old behavior; different content conflicts and no v2 shadow is written. Duplicate same-scope legacy/v2 rows, contradictory scope, malformed frames or missing, wrong or extra locators fail Corruption without repair or winner selection. New Unknown identities are distinct from new Global/Partition identities; a malformed request cannot install a later legacy barrier over a committed scoped result. Point lookup remains bounded, without a cross-partition reservation, presence scan or new global first-writer gate. Retained v1 bytes are a supported persisted format, not an alternate dispatcher, unscoped public alias or temporary dual writer. No existing outcome is rekeyed.

AC-DSTORE-009 requires complete actual-operation scenarios:

  1. The same principal and literal ID commits independently in two configured full partitions, with exact documents, receipts and outbox effects. Both exact retries retain their own results after fresh engine/store reopen; changed content in either scope conflicts and preserves both scopes. Include tenant/database/domain/key distinctions and explicit Global versus Partition reuse.
  2. A real precondition failure in A remains the same retained error after a fresh command makes that precondition satisfiable; reuse in B remains independent. Retry produces no new effects. Actual authorized resolution and revoked/denied controls preserve existing reauthorization. A real newly persisted Unknown failure before and after valid A/B commands with the same ID cannot shadow either scoped result; its own exact retry/error and changed-content conflict remain independent. Existing prior Unknown remains an ambiguity barrier.
  3. Real native transactions prove outcome/locator/effects/watermark/clock atomicity. Missing/wrong/extra locators, contradictory metadata, malformed frames and duplicate same-scope legacy/v2 records reject without fallback, rewriting or partial domain effects; a healthy unrelated command remains usable where its authority is intact.
  4. The existing immutable native6 producer supplies the genuine prior ConfigureResource outcome. Preserve its exact old key/value bytes, Unknown defaults and source/driver/binary receipts; current operation-aware resolution preserves matching behavior and mismatching content cannot create a v2 shadow. No fabricated prior partition frame or backfill is accepted.
  5. Existing Aspire-owned real process cuts and two-process retry scenarios prove new-key recovery and old-byte preservation. Aspire RF3 tests use both actual SDK and official MCP clients for same-ID/two-partition commits, opposite-endpoint retries and an owned restart/leader path. Local proof remains distinct from delivered-source Linux qualification.

Ordered ownership: TASK-DSTORE-SCOPED-OUTCOMES-001 freezes this feature and the ADR-002/011/017 and TokenMigrationLineage joins (root); 002 owns scoped keys, locator codecs/inventory and existing Core commit/resolution paths (Luna private packet); 003 updates all actual accessor callers and adds UnitTests, RecoveryTests/CrashHost and IntegrationTests operation flows in their canonical slices (same worker); 004 joins/reviews, runs build/format/governance and complete Aspire normal/scalar/recovery/RF3 plus exact-SHA Linux gates (root). Cold rollout stops every writer, verifies a complete immutable backup and installs homogeneous compatible RF3 binaries before admission. After the first v2 write, old-reader rollback requires the verified pre-upgrade backup and explicit accepted data-loss scope; otherwise recover forward. Outcome expiry and physical movement remain separate unimplemented contracts. No old binary is claimed to honor a new marker it does not read.

Authorization and retained-error ordering

TASK-DSTORE-SCOPED-OUTCOMES-002 preserves the original command trust boundary. Normal execution and operation-aware resolution authenticate the persisted principal and authorize the operation before reading or decoding retained outcome metadata. A revoked caller receives the original authorization error, including when the retained bytes are corrupt; after valid authorization the same corrupt row fails Corruption. The already-applied replica path retains its existing authority and replay order rather than introducing a new caller authorization step.

If normal admission fails before outcome selection, retain that original error, reset staged domain effects, and use only a bounded legacy-key presence check. An occupied legacy key suppresses a new v2 outcome write; it is not decoded, repaired, shadowed or used to replace the original error. Preserve the existing apply-watermark and monotonic-clock transaction behavior. An existing v2 row also cannot be overwritten by this failure. Actual rejected-operation tests verify both original bytes and the absence of a second outcome/locator or domain effect. When neither legacy nor selected v2 identity exists, preserve the original retention of a newly persisted denied/domain-error outcome. Suppression for an occupied identity must not become a blanket prohibition on retained errors.

Unknown-scope retry/conflict tests must use an actual operation whose persisted authorization succeeds before its malformed payload fails execution. An error rejected by authorization cannot reveal a prior fingerprint. Exact authorized Unknown retries and changed-content conflicts still use their independent v2 identity; no caller-supplied role or metadata-before-authorization shortcut is permitted. These flows extend AC-DSTORE-009 and its existing native unit/scalar, recovery and RF3 evidence, without qualifying an unexecuted gate.

The scoped implementation, caller migration and authored operation flows were joined in the 2026-10-06 source checkpoint. It captures the current source and root corrections during the shared options migration. Current build, formatter, unit/scalar, process recovery, RF3 and functional coverage are pending; AC-DSTORE-006/009 remain open.

The accepted ADR-035 document image contract assigns CRUD handlers and new matching tests to one worker, and shared AtomicMutationApplication caller integration to the lead. Internal context/result carriers stay under Features/DocumentStorage and within one atomic transaction. UI/SDK/MCP schema migration is N/A: public contracts and persisted bytes stay exact.

The2026-10-04 regression-completion stage maps AC-DSTORE-001 to DocumentCrudRevisionTests and DocumentPutValidationAtomicityTests, AC-DSTORE-002 to DocumentScalarIndexMutationTests, and AC-DSTORE-003 to DocumentFieldMutationAuthorizationTests and DocumentRowTenantMutationAuthorizationTests. These use actual TestDatabase/ZoneTree and the existing contracts: no new product semantics or mutation DTO. Root owns this mapping and join; cluster_wave Luna/high owns only these new UnitTests/Features/DocumentStorage files and their cohesive fixture. Include duplicate JSON members, document-byte/depth bounds after an earlier staged indexed mutation, exact rollback, tombstone/recreate revisions, unique-index isolation, persisted field grants and healthy owner follow-up.

Field-write grants gate create/replacement/patch of protected fields. Whole-row Delete follows the existing DocumentsWrite capability and row-write policy, plus any index-field-use grant required for strict maintenance. It does not introduce a field-write requirement for whole-row deletion. This distinguishes the current lifecycle and field-mutation contracts in REQ-AUTH-006; neither client-supplied row ownership nor an administrator label establishes authority.

TASK-DSTORE-FIELD-MUTATION-ORACLES closes the remaining AC-DSTORE-003 boundaries with separate DocumentReplacementFieldAuthorizationTests and DocumentDeleteIndexAuthorizationTests. Replacement denial uses a principal that already has DocumentsWrite, field-read and indexed field-use grants, isolating the missing field-write grant; exact original revision/JSON/index entries remain unchanged, and a principal with both grants replaces successfully. Delete denial isolates the missing indexed field-use grant and preserves the live record/index; its allowed control has field-use but no field-write grant and produces the exact next tombstone revision while removing the index entry. These are existing persisted-policy semantics, not a public contract or schema change. cluster_wave Luna/high owns only the two new files; root reviews and integrates actual Aspire normal/scalar/recovery and exact delivered-source Linux/RF3 qualification.

Flows and failure behavior

Positive: authorized valid JSON mutation passes catalog and CAS checks, updates document and affected indexes in one atomic command, and returns its committed revision/receipt. Negative: malformed JSON, stale CAS, denied row/field access, event-authoritative direct write, or unique collision rejects the operation. Edge: replacing indexed values removes old entries; delete creates a tombstone; patch of missing/deleted document fails; duplicate command identity is resolved by persisted outcome. Error responses must not disclose protected payload values. Query pages and index scans remain subject to the shared read budgets.

Decisions and verification

Related decisions: ADR-001, ADR-002, ADR-004, ADR-005, ADR-006, ADR-010, ADR-014, and ADR-016. Cross-resource batches follow ADR-024.

flowchart LR
    Client[Authorized command] --> Bind[Catalog resource and atomic partition]
    Bind --> Check[Validate JSON authority policy and CAS]
    Check --> Mutate[Document plus strict scalar index mutations]
    Mutate --> Commit[Ordered atomic commit and persisted outcome]
    Commit --> Read[Authorized projected read or bounded query]
Loading

The 2026-10-04 development receipt 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.

RF3 CRUD public-client completion (2026-10-04 accepted test scope)

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.

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.

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.

TASK-DSTORE-EXACT-TEXT also updates the existing native ownership, malformed record restore, canonical retry and embedded-fixture oracles to require the original submitted literal JSON, rather than JsonData.Validate output. Their authority/corruption/revision/receipt/no-second-effect assertions stay intact. Canonical validation/fingerprint golden bytes remain unchanged; canonical retry equivalence must not rewrite the first acknowledged document text. These cases map to REQ/AC-DSTORE-008 and ADR-060 with the dedicated exact-content tests.