ETag contract consolidation + PR #1091 review follow-ups (findings 2–5, 8–12)#1099
Merged
Merged
Conversation
…vices (finding 4)
…oser Fallout from routing RelationalReadMaterializer through IServedEtagComposer; these integration-test wrapper constructions use target-typed new(...) and were not caught by the unit-project build. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds response-body assertions to etag.feature scenarios 10 and 11 (wildcard If-Match on a non-existent PUT/DELETE target) asserting the new target-does-not-exist 412 body. Concurrency scenarios 04/05 are unchanged (that body stayed byte-identical). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…DELETE (finding 10) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…izer (finding 11) The EtagVariant XML docs on RelationalReadMaterializationRequest and RelationalReadPageMaterializationRequest claimed a null value falls back to a legacy content hash, but ComposeEtag throws on a null EtagVariant/MappingSet for an external response. Reframe both as: required on external-response materialization; absence is a caller wiring bug; no legacy content-hash path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ug log (finding 12)
The currentState-null debug log said "precondition fails" unconditionally, but the non-wildcard PUT arm returns UpdateFailureNotExists (404 not-exists), not a 412 precondition failure. Reword to "resolving missing-target outcome" so the message is accurate across all three arms (Post conflict, wildcard-PUT 412, non-wildcard-PUT 404). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
This PR builds on the ContentVersion-based ETag work by consolidating ETag composition/parsing/comparison into shared contracts and services, refining 412 precondition-failure responses, and expanding coverage (unit, integration, and E2E) around wildcard and quoted If-Match behaviors—plus making descriptor served _etag correctly vary by readable profile.
Changes:
- Consolidates served ETag composition and
If-Matchevaluation intoIServedEtagComposer+IIfMatchEvaluator, and adds parsing helpers (EtagValue.TryParse,VariantKeycomponent parsing) to standardize grammar and comparisons. - Threads a machine-readable precondition-failure reason (
ETagPreconditionFailureReason) through write results so the API can return distinct 412 bodies for concurrency vs. missing-target cases. - Adds/updates E2E + integration + unit tests to verify quoted
If-Matchacceptance, wildcard behaviors, and profiled/unprofiled parity (including descriptor profile-sensitive served ETags).
Reviewed changes
Copilot reviewed 50 out of 50 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| src/dms/tests/EdFi.DataManagementService.Tests.E2E/StepDefinitions/StepDefinitions.cs | Captures and reuses raw quoted ETag for quoted If-Match E2E scenarios. |
| src/dms/tests/EdFi.DataManagementService.Tests.E2E/Features/Resources/etag.feature | Adds E2E scenarios for missing-target 412 body and quoted If-Match on PUT/DELETE. |
| src/dms/core/EdFi.DataManagementService.Core/Utilities/EtagValue.cs | Adds TryParse to split {ContentVersion}-{variantKey} into parts. |
| src/dms/core/EdFi.DataManagementService.Core/Response/FailureResponse.cs | Adds 412 body selection based on ETagPreconditionFailureReason. |
| src/dms/core/EdFi.DataManagementService.Core/Handler/UpsertHandler.cs | Uses reason-aware 412 response generation. |
| src/dms/core/EdFi.DataManagementService.Core/Handler/UpdateByIdHandler.cs | Uses reason-aware 412 response generation. |
| src/dms/core/EdFi.DataManagementService.Core/Handler/DeleteByIdHandler.cs | Uses reason-aware 412 response generation. |
| src/dms/core/EdFi.DataManagementService.Core.Tests.Unit/Utilities/EtagValueTests.cs | Adds tests for EtagValue.TryParse round-trip and malformed inputs. |
| src/dms/core/EdFi.DataManagementService.Core.Tests.Unit/Handler/UpsertHandlerTests.cs | Adds unit coverage for missing-target 412 body on upsert mismatch. |
| src/dms/core/EdFi.DataManagementService.Core.Tests.Unit/Handler/UpdateByIdHandlerTests.cs | Adds unit coverage for missing-target vs concurrency mismatch response bodies. |
| src/dms/core/EdFi.DataManagementService.Core.Tests.Unit/Handler/DeleteByIdHandlerTests.cs | Adds unit coverage for missing-target vs concurrency mismatch response bodies. |
| src/dms/core/EdFi.DataManagementService.Core.External/Backend/UpsertResult.cs | Adds Reason to ETag mismatch failure result (default Concurrency). |
| src/dms/core/EdFi.DataManagementService.Core.External/Backend/UpdateResult.cs | Adds Reason to ETag mismatch failure result (default Concurrency). |
| src/dms/core/EdFi.DataManagementService.Core.External/Backend/DeleteResult.cs | Adds Reason to ETag mismatch failure result (default Concurrency). |
| src/dms/core/EdFi.DataManagementService.Core.External/Backend/ETagPreconditionFailureReason.cs | Introduces enum to distinguish concurrency vs missing-target precondition failures. |
| src/dms/backend/EdFi.DataManagementService.Backend/RelationalWriteExecutorResults.cs | Threads ETagPreconditionFailureReason through precondition failure result builder. |
| src/dms/backend/EdFi.DataManagementService.Backend/RelationalWriteExecutionStateResolver.cs | Uses shared served-etag composer + evaluator; adds debug logs and missing-target reasoning. |
| src/dms/backend/EdFi.DataManagementService.Backend/RelationalReadMaterializer.cs | Uses IServedEtagComposer to compose served _etag; clarifies “no fallback” contract. |
| src/dms/backend/EdFi.DataManagementService.Backend/RelationalDocumentStoreRepository.cs | Returns mismatch results with explicit missing-target reasons for wildcard/missing cases. |
| src/dms/backend/EdFi.DataManagementService.Backend/RelationalCurrentEtagPreconditionChecker.cs | Uses shared served-etag composer + evaluator; adds debug logs. |
| src/dms/backend/EdFi.DataManagementService.Backend/RelationalCommittedRepresentationReader.cs | Uses IServedEtagComposer for committed _etag materialization. |
| src/dms/backend/EdFi.DataManagementService.Backend/ReferenceResolverServiceCollectionExtensions.cs | Registers IServedEtagComposer and IIfMatchEvaluator in DI. |
| src/dms/backend/EdFi.DataManagementService.Backend/Etag/VariantKey.cs | Adds typed component grammar (parse/format) and central If-Match significant projection. |
| src/dms/backend/EdFi.DataManagementService.Backend/Etag/ServedEtagComposer.cs | New service to compose served etags from a single context object. |
| src/dms/backend/EdFi.DataManagementService.Backend/Etag/IfMatchEvaluator.cs | New service to evaluate If-Match via the state-significant projection. |
| src/dms/backend/EdFi.DataManagementService.Backend/Etag/EtagMatchProjection.cs | Delegates parsing to EtagValue + VariantKey for the significant projection. |
| src/dms/backend/EdFi.DataManagementService.Backend/DescriptorWriteHandler.cs | Threads missing-target reasons; centralizes If-Match evaluation and adds debug logs. |
| src/dms/backend/EdFi.DataManagementService.Backend/DescriptorReadHandler.cs | Makes served descriptor _etag profile-sensitive when readable-profile projection applies. |
| src/dms/backend/EdFi.DataManagementService.Backend/DescriptorDocumentMaterializer.cs | Accepts a caller-precomposed _etag string (and guards against missing wiring). |
| src/dms/backend/EdFi.DataManagementService.Backend/DefaultRelationalWriteExecutor.cs | Wires new services into execution-state resolution and precondition failure reasons. |
| src/dms/backend/EdFi.DataManagementService.Backend.Tests.Unit/RelationalReadMaterializerTests.cs | Updates unit tests for IServedEtagComposer wiring. |
| src/dms/backend/EdFi.DataManagementService.Backend.Tests.Unit/RelationalDocumentStoreRepositoryTests.cs | Asserts mismatch reasons across wildcard/missing/concurrency paths (incl. new case). |
| src/dms/backend/EdFi.DataManagementService.Backend.Tests.Unit/RelationalCurrentEtagPreconditionCheckerTests.cs | Adds test guarding schema-epoch significance; updates wiring for evaluator/logging. |
| src/dms/backend/EdFi.DataManagementService.Backend.Tests.Unit/RelationalCommittedRepresentationReaderTests.cs | Updates tests for IServedEtagComposer usage. |
| src/dms/backend/EdFi.DataManagementService.Backend.Tests.Unit/Etag/VariantKeyTests.cs | Adds tests for component parsing/formatting and significance projection. |
| src/dms/backend/EdFi.DataManagementService.Backend.Tests.Unit/Etag/ServedEtagComposerTests.cs | New tests validating served-etag composition equivalence and profile sensitivity. |
| src/dms/backend/EdFi.DataManagementService.Backend.Tests.Unit/Etag/IfMatchEvaluatorTests.cs | New tests validating wildcard and projection-based matching behavior. |
| src/dms/backend/EdFi.DataManagementService.Backend.Tests.Unit/DescriptorWriteHandlerResponseEtagTests.cs | Updates descriptor write response etag tests for served composer/evaluator wiring. |
| src/dms/backend/EdFi.DataManagementService.Backend.Tests.Unit/DescriptorWriteHandlerPreconditionTests.cs | Adds descriptor PUT/DELETE profiled-etag acceptance tests and reason assertions. |
| src/dms/backend/EdFi.DataManagementService.Backend.Tests.Unit/DescriptorWriteHandlerNamespaceAuthorizationTests.cs | Updates wiring for served composer/evaluator. |
| src/dms/backend/EdFi.DataManagementService.Backend.Tests.Unit/DescriptorWriteHandlerDeleteTests.cs | Updates wiring for served composer/evaluator. |
| src/dms/backend/EdFi.DataManagementService.Backend.Tests.Unit/DescriptorReadHandlerTests.cs | Flips assertions to require profile-sensitive descriptor _etag on profiled reads. |
| src/dms/backend/EdFi.DataManagementService.Backend.Tests.Unit/DescriptorReadHandlerNamespaceAuthorizationTests.cs | Updates handler wiring for served composer. |
| src/dms/backend/EdFi.DataManagementService.Backend.Tests.Unit/DescriptorDocumentMaterializerTests.cs | Updates materializer signature and adds guard test for missing composed etag. |
| src/dms/backend/EdFi.DataManagementService.Backend.Tests.Unit/DefaultRelationalWriteExecutorTests.cs | Updates wiring and adds/updates assertions for mismatch reasons. |
| src/dms/backend/EdFi.DataManagementService.Backend.Postgresql.Tests.Integration/PostgresqlRelationalQueryExecutionTests.cs | Updates integration test wiring for served composer. |
| src/dms/backend/EdFi.DataManagementService.Backend.Postgresql.Tests.Integration/PostgresqlProfileIfMatchEtagTests.cs | Adds PG integration coverage for unprofiled DELETE using profiled GET etags and stale etag behavior. |
| src/dms/backend/EdFi.DataManagementService.Backend.Mssql.Tests.Integration/MssqlRelationalQueryExecutionTests.cs | Updates integration test wiring for served composer. |
| src/dms/backend/EdFi.DataManagementService.Backend.Mssql.Tests.Integration/MssqlProfileIfMatchEtagTests.cs | Adds MSSQL integration coverage for unprofiled DELETE using profiled GET etags and stale etag behavior. |
| reference/adr-etag-from-content-version.md | Documents the 2026-07-07 amendment for descriptor profile-sensitive served _etag. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
stephenfuqua
added a commit
that referenced
this pull request
Jul 8, 2026
…5, 8–12) (#1099) * refactor: add EtagValue.TryParse and Separator constant * refactor: give VariantKey a typed component parser/formatter (finding 5) * refactor: rename VariantKey builder to FromComponents to avoid member shadow * test: guard schema-epoch mismatch in the precondition checker (finding 9) * refactor: EtagMatchProjection delegates to the typed wire parsers (finding 5) * feat: add IServedEtagComposer service (finding 4) * feat: add IIfMatchEvaluator service (finding 4) * chore: register IServedEtagComposer and IIfMatchEvaluator * refactor: route the precondition checker through the etag services (finding 4) * refactor: route the write execution resolver through the etag services (finding 4) * refactor: compose read/write-response etags via IServedEtagComposer (finding 4) * refactor: route descriptor write compose/compare through the etag services (finding 4) * fix: update integration read-materializer wrappers to IServedEtagComposer Fallout from routing RelationalReadMaterializer through IServedEtagComposer; these integration-test wrapper constructions use target-typed new(...) and were not caught by the unit-project build. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix: descriptor served etag varies by readable profile (finding 2) * feat: add ETagPreconditionFailureReason to precondition-failure results (finding 3) * feat: tag precondition failures with concurrency vs target-missing reason (finding 3) * feat: reason-specific 412 body factory (finding 3) * feat: return reason-specific 412 detail from write handlers (finding 3) * test: assert distinct 412 body for wildcard-missing target (finding 3) Adds response-body assertions to etag.feature scenarios 10 and 11 (wildcard If-Match on a non-existent PUT/DELETE target) asserting the new target-does-not-exist 412 body. Concurrency scenarios 04/05 are unchanged (that body stayed byte-identical). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test: capture raw quoted ETag and add {IfMatchQuoted} token (finding 8) * test: quoted If-Match round-trip on PUT and DELETE (finding 8) * test: profiled/unprofiled DELETE etag parity on PostgreSQL (finding 10) * test: assert profiled etag changes on hidden-field bump before stale DELETE (finding 10) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test: profiled/unprofiled DELETE etag parity on SQL Server (finding 10) * docs: correct stale legacy-content-hash comments on the read materializer (finding 11) The EtagVariant XML docs on RelationalReadMaterializationRequest and RelationalReadPageMaterializationRequest claimed a null value falls back to a legacy content hash, but ComposeEtag throws on a null EtagVariant/MappingSet for an external response. Reframe both as: required on external-response materialization; absence is a caller wiring bug; no legacy content-hash path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat: debug-log the If-Match decision in the precondition checker (finding 12) * fix: sanitize the client If-Match tag in the precondition-checker debug log (finding 12) * feat: debug-log the deferred If-Match decision in the resolver (finding 12) * feat: debug-log the descriptor If-Match decision (finding 12) * refactor: guard finding-12 If-Match debug logs with IsEnabled(Debug) (finding 12) * docs: clarify the resolver missing-target debug message (finding 12) The currentState-null debug log said "precondition fails" unconditionally, but the non-wildcard PUT arm returns UpdateFailureNotExists (404 not-exists), not a 412 precondition failure. Reword to "resolving missing-target outcome" so the message is accurate across all three arms (Post conflict, wildcard-PUT 412, non-wildcard-PUT 404). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix: Improve XML comment; look for last `-` for future resilience Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
stephenfuqua
added a commit
that referenced
this pull request
Jul 8, 2026
…5, 8–12) (#1099) * refactor: add EtagValue.TryParse and Separator constant * refactor: give VariantKey a typed component parser/formatter (finding 5) * refactor: rename VariantKey builder to FromComponents to avoid member shadow * test: guard schema-epoch mismatch in the precondition checker (finding 9) * refactor: EtagMatchProjection delegates to the typed wire parsers (finding 5) * feat: add IServedEtagComposer service (finding 4) * feat: add IIfMatchEvaluator service (finding 4) * chore: register IServedEtagComposer and IIfMatchEvaluator * refactor: route the precondition checker through the etag services (finding 4) * refactor: route the write execution resolver through the etag services (finding 4) * refactor: compose read/write-response etags via IServedEtagComposer (finding 4) * refactor: route descriptor write compose/compare through the etag services (finding 4) * fix: update integration read-materializer wrappers to IServedEtagComposer Fallout from routing RelationalReadMaterializer through IServedEtagComposer; these integration-test wrapper constructions use target-typed new(...) and were not caught by the unit-project build. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix: descriptor served etag varies by readable profile (finding 2) * feat: add ETagPreconditionFailureReason to precondition-failure results (finding 3) * feat: tag precondition failures with concurrency vs target-missing reason (finding 3) * feat: reason-specific 412 body factory (finding 3) * feat: return reason-specific 412 detail from write handlers (finding 3) * test: assert distinct 412 body for wildcard-missing target (finding 3) Adds response-body assertions to etag.feature scenarios 10 and 11 (wildcard If-Match on a non-existent PUT/DELETE target) asserting the new target-does-not-exist 412 body. Concurrency scenarios 04/05 are unchanged (that body stayed byte-identical). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test: capture raw quoted ETag and add {IfMatchQuoted} token (finding 8) * test: quoted If-Match round-trip on PUT and DELETE (finding 8) * test: profiled/unprofiled DELETE etag parity on PostgreSQL (finding 10) * test: assert profiled etag changes on hidden-field bump before stale DELETE (finding 10) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test: profiled/unprofiled DELETE etag parity on SQL Server (finding 10) * docs: correct stale legacy-content-hash comments on the read materializer (finding 11) The EtagVariant XML docs on RelationalReadMaterializationRequest and RelationalReadPageMaterializationRequest claimed a null value falls back to a legacy content hash, but ComposeEtag throws on a null EtagVariant/MappingSet for an external response. Reframe both as: required on external-response materialization; absence is a caller wiring bug; no legacy content-hash path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat: debug-log the If-Match decision in the precondition checker (finding 12) * fix: sanitize the client If-Match tag in the precondition-checker debug log (finding 12) * feat: debug-log the deferred If-Match decision in the resolver (finding 12) * feat: debug-log the descriptor If-Match decision (finding 12) * refactor: guard finding-12 If-Match debug logs with IsEnabled(Debug) (finding 12) * docs: clarify the resolver missing-target debug message (finding 12) The currentState-null debug log said "precondition fails" unconditionally, but the non-wildcard PUT arm returns UpdateFailureNotExists (404 not-exists), not a 412 precondition failure. Reword to "resolving missing-target outcome" so the message is accurate across all three arms (Post conflict, wildcard-PUT 412, non-wildcard-PUT 404). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix: Improve XML comment; look for last `-` for future resilience Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
stephenfuqua
added a commit
that referenced
this pull request
Jul 8, 2026
…5, 8–12) (#1099) * refactor: add EtagValue.TryParse and Separator constant * refactor: give VariantKey a typed component parser/formatter (finding 5) * refactor: rename VariantKey builder to FromComponents to avoid member shadow * test: guard schema-epoch mismatch in the precondition checker (finding 9) * refactor: EtagMatchProjection delegates to the typed wire parsers (finding 5) * feat: add IServedEtagComposer service (finding 4) * feat: add IIfMatchEvaluator service (finding 4) * chore: register IServedEtagComposer and IIfMatchEvaluator * refactor: route the precondition checker through the etag services (finding 4) * refactor: route the write execution resolver through the etag services (finding 4) * refactor: compose read/write-response etags via IServedEtagComposer (finding 4) * refactor: route descriptor write compose/compare through the etag services (finding 4) * fix: update integration read-materializer wrappers to IServedEtagComposer Fallout from routing RelationalReadMaterializer through IServedEtagComposer; these integration-test wrapper constructions use target-typed new(...) and were not caught by the unit-project build. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix: descriptor served etag varies by readable profile (finding 2) * feat: add ETagPreconditionFailureReason to precondition-failure results (finding 3) * feat: tag precondition failures with concurrency vs target-missing reason (finding 3) * feat: reason-specific 412 body factory (finding 3) * feat: return reason-specific 412 detail from write handlers (finding 3) * test: assert distinct 412 body for wildcard-missing target (finding 3) Adds response-body assertions to etag.feature scenarios 10 and 11 (wildcard If-Match on a non-existent PUT/DELETE target) asserting the new target-does-not-exist 412 body. Concurrency scenarios 04/05 are unchanged (that body stayed byte-identical). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test: capture raw quoted ETag and add {IfMatchQuoted} token (finding 8) * test: quoted If-Match round-trip on PUT and DELETE (finding 8) * test: profiled/unprofiled DELETE etag parity on PostgreSQL (finding 10) * test: assert profiled etag changes on hidden-field bump before stale DELETE (finding 10) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test: profiled/unprofiled DELETE etag parity on SQL Server (finding 10) * docs: correct stale legacy-content-hash comments on the read materializer (finding 11) The EtagVariant XML docs on RelationalReadMaterializationRequest and RelationalReadPageMaterializationRequest claimed a null value falls back to a legacy content hash, but ComposeEtag throws on a null EtagVariant/MappingSet for an external response. Reframe both as: required on external-response materialization; absence is a caller wiring bug; no legacy content-hash path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat: debug-log the If-Match decision in the precondition checker (finding 12) * fix: sanitize the client If-Match tag in the precondition-checker debug log (finding 12) * feat: debug-log the deferred If-Match decision in the resolver (finding 12) * feat: debug-log the descriptor If-Match decision (finding 12) * refactor: guard finding-12 If-Match debug logs with IsEnabled(Debug) (finding 12) * docs: clarify the resolver missing-target debug message (finding 12) The currentState-null debug log said "precondition fails" unconditionally, but the non-wildcard PUT arm returns UpdateFailureNotExists (404 not-exists), not a 412 precondition failure. Reword to "resolving missing-target outcome" so the message is accurate across all three arms (Post conflict, wildcard-PUT 412, non-wildcard-PUT 404). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix: Improve XML comment; look for last `-` for future resilience Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
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.
Summary
Addresses nine findings from the PR #1091 review, layered as four sequenced, single-concern efforts on top of
etag-content-version. (The GET-by-id-missing-etag finding is tracked separately as DMS-1266.)Refactor — contract consolidation (findings 5, 4, 9)
EtagValue.TryParse+VariantKey.Components/FromComponents/TryParseComponents(finding 5).IServedEtagComposerandIIfMatchEvaluator, replace duplicated etag construction/comparison across all five compose sites and the comparison sites (finding 4). Pure refactor — byte-identical output.Functionality fixes (findings 2, 3)
_etagis now profile-sensitive (descriptors are profile-projected);If-Matchstays profile-insensitive per the 2026-07-04 ADR amendment (compares onlyContentVersion+schemaEpoch) (finding 2).ETagPreconditionFailureReason { Concurrency, TargetDoesNotExist }threaded to distinct 412 bodies. The concurrency body is kept byte-identical to legacy; a new, clearer body is returned when the target does not exist (finding 3).Test coverage (findings 8, 10)
If-Matchround-trips verbatim on PUT and DELETE (scenarios 12/13) (finding 8).Cleanup (findings 11, 12)
RelationalReadMaterializer—ComposeEtagthrows when the etag inputs are absent; there is no fallback (finding 11).If-Matchdecision sites (precondition checker, write-execution resolver, descriptor write handler): single evaluation per path, the clientIf-Matchvalue sanitized viaLoggingSanitizer, allIsEnabled(LogLevel.Debug)-guarded (finding 12).Test Plan
Notes for reviewers
If-Matchtargets a non-existent resource (distinct from the byte-identical-to-legacy concurrency body). Worth a deliberate human review of the wording.This work was developed with AI assistance (Claude Code); all changes were reviewed and a human remains accountable for the merge decision.
🤖 Generated with Claude Code