Skip to content

ETag contract consolidation + PR #1091 review follow-ups (findings 2–5, 8–12)#1099

Merged
stephenfuqua merged 32 commits into
etag-content-versionfrom
etag-contract-consolidation
Jul 8, 2026
Merged

ETag contract consolidation + PR #1091 review follow-ups (findings 2–5, 8–12)#1099
stephenfuqua merged 32 commits into
etag-content-versionfrom
etag-contract-consolidation

Conversation

@stephenfuqua

Copy link
Copy Markdown
Contributor

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)

  • Typed wire-format grammar for the served etag: EtagValue.TryParse + VariantKey.Components/FromComponents/TryParseComponents (finding 5).
  • Two focused services, IServedEtagComposer and IIfMatchEvaluator, replace duplicated etag construction/comparison across all five compose sites and the comparison sites (finding 4). Pure refactor — byte-identical output.
  • Added a precondition-checker test guarding schema-epoch mismatch (finding 9).

Functionality fixes (findings 2, 3)

  • Descriptor served _etag is now profile-sensitive (descriptors are profile-projected); If-Match stays profile-insensitive per the 2026-07-04 ADR amendment (compares only ContentVersion + 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)

  • E2E proves the RFC 7232 quoted If-Match round-trips verbatim on PUT and DELETE (scenarios 12/13) (finding 8).
  • PostgreSQL + SQL Server integration fixtures prove profiled/unprofiled DELETE etag parity: a profiled-GET etag is accepted by an unprofiled DELETE; a stale profiled etag yields a 412; and the profiled etag is asserted to genuinely change on a hidden-field bump (finding 10).

Cleanup (findings 11, 12)

  • Corrected stale "legacy content-hash fallback" XML docs on RelationalReadMaterializerComposeEtag throws when the etag inputs are absent; there is no fallback (finding 11).
  • Debug-level structured logging at the three If-Match decision sites (precondition checker, write-execution resolver, descriptor write handler): single evaluation per path, the client If-Match value sanitized via LoggingSanitizer, all IsEnabled(LogLevel.Debug)-guarded (finding 12).

Test Plan

  • Backend unit suite: 1890/1890 pass
  • PostgreSQL If-Match/profile integration: 17/17 pass
  • SQL Server If-Match/profile integration: 32/32 pass
  • E2E project builds clean (Reqnroll codegen) — scenarios 12/13 to be exercised in CI (require a running API + database stack)
  • Solution builds with 0 warnings / 0 errors (warnings-as-errors)

Notes for reviewers

  • Client-visible API change: a new 412 response body is returned when an If-Match targets a non-existent resource (distinct from the byte-identical-to-legacy concurrency body). Worth a deliberate human review of the wording.
  • Known follow-up: the three finding-12 debug logs do not yet carry a TraceId/correlation id — their request contracts don't provide one; threading one is left as a scoped follow-up.

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

stephenfuqua and others added 30 commits July 7, 2026 13:24
…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>
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>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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-Match evaluation into IServedEtagComposer + IIfMatchEvaluator, and adds parsing helpers (EtagValue.TryParse, VariantKey component 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-Match acceptance, 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.

Comment thread src/dms/core/EdFi.DataManagementService.Core/Utilities/EtagValue.cs
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@stephenfuqua stephenfuqua merged commit 473ade9 into etag-content-version Jul 8, 2026
2 checks passed
@stephenfuqua stephenfuqua deleted the etag-contract-consolidation branch July 8, 2026 01:20
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants