Software engineer validating HTTP cache revalidation headers and 304 responses across multiple monitors

HTTP Cache Revalidation: Prove Conditional Requests Do Not Serve Stale or Unauthorized Data

Tue, Sep 22, 2026

HTTP cache revalidation is a correctness mechanism before it is an optimization. A cache can return a previously stored representation without contacting the origin while that response remains fresh, then use conditional requests such as If-None-Match or If-Modified-Since when validation is required. The acceptance problem is therefore broader than “did the server return 304?” It must prove that the cache selected the right representation, obeyed freshness limits, validated the right validator, and never crossed an authorization or personalization boundary.

The primary authorities for this playbook are RFC 9110: HTTP Semantics (IETF, June 2022; accessed September 22, 2026) and RFC 9111: HTTP Caching (IETF, June 2022; accessed September 22, 2026). Supporting protocol sources are RFC 9211: The Cache-Status HTTP Response Header Field (IETF, June 2022; accessed September 22, 2026) and ,c 39(May 2010; accessed September 22, 2026).

For browser-facing interpretation, this playbook also uses the WHATWG Fetch Living Standard (Living Standard; snapshot accessed September 22, 2026). It does not assume that a browser, CDN, reverse proxy, or managed edge cache behaves identically to the controlled cache used in this lab. Product-specific behavior needs product-specific documentation and evidence.

The laboratory run used only disposable loopback HTTP services, synthetic resources, deterministic validators, and fake identities. No real credential, production token, customer record, or private production response entered the shared cache. Every result below is labelled either as a protocol requirement, actual lab observation, implementation behavior, or engineering inference. No unexecuted test is presented as evidence, and no latency or throughput result is invented.

Why cache revalidation needs acceptance testing

A successful conditional request does not, by itself, prove cache correctness. The origin can correctly produce 304 Not Modified while a cache has selected the wrong stored variant. A cache can also obey an ETag correctly yet violate private, mishandle Vary, or accidentally reuse a personalized representation for another user. RFC 9111 therefore conditions reuse on more than freshness: the URI, applicable method, Vary dimensions, cache directives, and validation state all matter.

There is another important distinction: “not stale” must mean not unintentionally stale. HTTP permits serving stale content in defined circumstances, including explicit stale extensions or other authorization to do so. RFC 5861's stale-while-revalidate, for example, deliberately permits a stale response for a bounded interval while asynchronous validation occurs. A system requiring strict blocking revalidation must not silently enable that behavior on the protected path.

Acceptance therefore needs two independent safety invariants. A freshness invariant says the cache may reuse an entry only while fresh, after successful validation, or under an explicitly approved stale policy. An isolation invariant says the representation selected for one request must be eligible for that request's cache key, variant dimensions, authentication context, and shared/private caching rules. A correct validator cannot repair a violation of the second invariant.

Acceptance risk

Failure signature

Required evidence

Evidence class

Fresh object unnecessarily revalidated

Origin sees conditional request during valid freshness window

Origin request log plus cache state

Implementation behavior

Stale object reused without permission

Old bytes returned with no successful validation or approved stale directive

Age/freshness calculation, request trace

Protocol + observation

Changed object hidden behind old validator

Origin changed representation but returned matching validation result

Body identity plus validator history

Origin correctness

Wrong Vary variant reused

A request receives bytes generated for another variant

Request headers, Vary, representation ID

Protocol + observation

Personalized response crosses users

Bob receives Alice's representation

Synthetic identities and cache storage trace

Security acceptance

private or no-store ignored

Shared cache stores prohibited response

Cache inventory and repeat requests

Protocol + observation

304 treated as new body

Empty 304 replaces stored representation

Client body and cache metadata trace

Protocol + observation

Race returns old generation

Concurrent clients see inconsistent validator/body pair

Coordinated revalidation trace

Implementation behavior

The acceptance target is not “the cache makes fewer origin requests.” It is that every cache-produced representation has a defensible protocol path from the current request to the stored response. Performance can be measured separately after correctness is established.

Define freshness, validation, and representation identity

RFC 9111 defines freshness mathematically: a stored response is fresh when its freshness lifetime is greater than its current age. Explicit freshness can come from s-maxage for a shared cache, then max-age, then Expires according to the specification's precedence rules. Once a response is not fresh, reuse generally requires validation or an explicit allowance for stale service.

Validation answers a different question. A conditional request asks the recipient whether a stored representation remains suitable relative to a validator. If-None-Match compares entity tags and is the preferred cache-validation mechanism when an ETag is available. If-Modified-Since is typically the fallback when a stored response has Last-Modified but no ETag. When both are supplied, If-None-Match takes precedence and If-Modified-Since is ignored.

For acceptance testing, “representation identity” needs a test-visible meaning. The lab added X-Representation-Id solely as instrumentation. It is not an HTTP standard header and is not part of normal cache semantics. It made it possible to record that body, validator, variant, and synthetic principal belonged to the expected representation without inferring identity from status code alone.

Concept

Acceptance meaning

What does not prove it

Freshness

Stored response remains inside its permitted freshness lifetime

A recent-looking ETag

Validation

Origin/cache successfully establishes that a stored response can be reused

Merely sending a conditional header

ETag

Opaque validator for the selected representation

User identity or authorization

Last-Modified

Modification-time validator usable for conditional retrieval

Byte-level uniqueness

Representation identity

Exact synthetic body/variant/user generation expected by the fixture

URI alone

Cache key

Request dimensions used to select eligible stored responses

ETag alone

Shared cache

Cache whose stored response can potentially serve multiple users

Browser history

Private cache

Cache dedicated to one user context

Cache-Control: private as an authorization system

Strong versus weak validators and what they actually prove

RFC 9110 defines a strong validator as one that changes whenever representation data changes in a way observable in a successful GET representation. It also requires strong validators to remain unique across versions over time. Weak validators deliberately provide a looser equivalence relation: two byte-different representations can legitimately share a weak validator when the origin considers them equivalent for the validation purpose.

That distinction is particularly important for If-None-Match, because HTTP explicitly requires weak comparison for that precondition. A weak ETag can therefore validate a cached representation even when the current representation's bytes are not identical. Cache validation should consequently be tested for semantic validator behavior, not incorrectly treated as cryptographic proof that every byte is unchanged.

Build a synthetic HTTP server with deterministic validators

The executed fixture used two ThreadingHTTPServer instances bound only to loopback on ephemeral ports: one origin and one controlled shared-cache proxy. That boundary mirrors the kind of request/response work practiced in backend development, but the acceptance evidence here comes from the HTTP specifications and this isolated lab rather than from any production platform.

The origin generated deterministic strong ETags from a truncated SHA-256 digest of the synthetic response body. The test harness could mutate resource state between requests, advance a logical cache clock without sleeping, and record all incoming conditional headers. Fixed Last-Modified timestamps prevented wall-clock timing from becoming hidden evidence.

The proxy stored entries by URI plus the dimensions named by Vary. It recorded cache disposition using a local Cache-Status-style header. RFC 9211 standardizes such concepts as hit and forwarded cache states, but LocalTestCache was simply the fixture's own cache identifier.

Endpoint

Synthetic behavior

Cache policy

Identity evidence

/etag

Strong ETag changes with body

max-age=2

etag:<digest>

/lm

No ETag; fixed Last-Modified

max-age=0

lm:v1, lm:v2

/vary

Body selected by X-Rep

max-age=60, Vary: X-Rep

vary:A:*, vary:B:*

/personal

Fake Alice/Bob/anonymous bodies

Authenticated: private; anonymous: public

personal:<principal>

/nostore

Synthetic nonsecret body

no-store

request generation

/nocache

Stable ETag

no-cache

validator + body

/must

Can simulate origin failure

max-age=0, must-revalidate

fixed body ID

/weak

Byte formatting can change under same weak tag

max-age=0

weak semantic version

A representative deterministic validator helper was:

def strong_etag(body: bytes) -> str:
    digest = hashlib.sha256(body).hexdigest()[:12]
    return f'"{digest}"'

For /etag, alpha-v1 produced "1045d9a162b5" in the executed fixture, and changing the body to alpha-v2 produced "52d7699527d7". Those values are test artifacts, not recommended validator formats. HTTP treats entity tags as opaque; an application is free to choose an implementation that satisfies the validator requirements.

The fake authentication strings such as Bearer alice and Bearer bob were deliberately nonsecret markers understood only by the local fixture. They must never be replaced with real API tokens in a shared-cache test.

Validate ETag and If-None-Match behavior

If-None-Match is the central conditional request for ETag-based HTTP cache revalidation. RFC 9110 says it is primarily used by conditional GET to update cached information efficiently. When the selected representation matches one of the supplied tags under weak comparison, a GET or HEAD condition that evaluates false results in 304; otherwise the normal representation can be returned.

The executed sequence established four distinct states rather than treating “ETag works” as a single test. First, an empty cache requested /etag. Second, an immediate repeat stayed inside the two-second logical freshness window. Third, the clock advanced past freshness without changing the origin. Fourth, the origin representation changed before another stale revalidation.

Step

Cache state before request

Proxy → origin condition

Origin result

Client-visible result

Observed representation

Initial GET

Empty

None

200

200, stored

alpha-v1, ETag "1045d9a162b5"

Fresh repeat

Fresh

No origin request

Not applicable

200, cache hit

Same body/tag

Stale, unchanged

Stale

If-None-Match: "1045d9a162b5"

304

200 from stored body

alpha-v1, same tag

Stale, changed

Stale

Old If-None-Match

200

200, cache replaced

alpha-v2, ETag "52d7699527d7"

Actual observation: the four client requests caused three origin requests. The fresh second request did not contact the origin. The third request produced LocalTestCache; fwd=stale; fwd-status=304, while the changed fourth request produced a full 200 and replaced the stored entry.

That result demonstrates three separate invariants in the local implementation. Fresh content was reusable without validation. A stale unchanged representation was reused only after the origin validated its ETag. A stale changed representation was not masked by the old validator; the full new response replaced it.

It is important not to confuse this with mutation concurrency. Refonte Learning's If-Match lost-update article addresses the complementary problem of protecting writes against stale versions. That article itself separates If-Match write concurrency from If-None-Match read caching; this playbook stays on the cache-validation side.

Validate Last-Modified and If-Modified-Since behavior

If-Modified-Since makes GET or HEAD conditional on whether the selected representation has been modified after the supplied date. RFC 9110 describes cache updating as one of its normal uses, particularly when an ETag is unavailable. When If-None-Match is present, however, the recipient must ignore If-Modified-Since because the ETag condition is considered the more accurate replacement.

The /lm endpoint deliberately omitted ETag so that the test could not accidentally succeed through If-None-Match. Its first version returned Last-Modified: Tue, 14 Nov 2023 22:13:20 GMT and Cache-Control: max-age=0, making validation necessary on subsequent reuse.

Step

Stored validator

Conditional request observed at origin

Origin response

Client result

Representation ID

Initial

None

None

200 + Last-Modified

200

lm:v1

Unchanged revalidation

22:13:20 GMT

If-Modified-Since: Tue, 14 Nov 2023 22:13:20 GMT

304

Stored body reused

lm:v1

Changed origin

Old timestamp

Same old IMS value

200, new timestamp 22:13:30

New body

lm:v2

Actual observation: no If-None-Match appeared in the /lm origin trace. On the unchanged request, the proxy reused the previously stored body only after receiving 304. After the origin advanced Last-Modified by ten seconds and changed the body, the same old conditional date no longer validated; the origin returned the complete new representation.

The acceptance implication is deliberately narrow. This proves that the local cache correctly used Last-Modified fallback for this deterministic fixture. It does not prove that modification times offer the same identity precision as strong ETags. Timestamp resolution, server clock behavior, and application update patterns can make time-based validators less discriminating than a purpose-built entity tag. RFC 9110 consequently gives If-None-Match precedence when both are present.

Test 304 responses and representation reuse

A 304 is not a lightweight substitute representation. RFC 9110 defines it as the result of a conditional GET or HEAD that would otherwise have produced 200 had the condition not evaluated false. A 304 terminates after its headers and cannot contain response content or trailers.

This matters when testing through an intermediary. The origin may produce 304, while the cache produces 200 to the downstream client by combining the validation result with its already stored representation. RFC 9111 explicitly permits the stored response to be updated and reused following successful 304 validation. A test that records only the browser/client status can therefore miss the validation transaction entirely.

Observation point

Correct unchanged-resource behavior

What would fail acceptance

Cache → origin

Conditional GET with stored validator

Unconditional stale reuse where validation is required

Origin → cache

304 with applicable metadata

304 carrying a new response body

Cache store

Existing body retained; metadata updated as applicable

Stored body replaced with empty 304 payload

Cache → client

May be reconstructed 200 with cached representation

Wrong representation or variant

Identity check

Body ID remains same validated generation

Validator points to one generation while body is another

The 304 response is a validation result, not a new representation

The /etag and /lm runs both observed this distinction. The origin's 304 response did not contain the resource body. The proxy retained its existing stored bytes, processed the validation result, and returned a complete 200 representation to the test client.

RFC 9110 also requires specified metadata to be generated on a 304 when that metadata would have appeared on the corresponding 200, including fields relevant to cache updating such as ETag and Vary where applicable. The acceptance harness should therefore preserve origin-response headers separately from downstream-response headers rather than collapsing them into one log record.

A robust evidence record is thus a chain: request identity → selected stored entry → outgoing conditional header → origin validation status → metadata update → reused body identity. “Received 304” proves only the middle of that chain.

Validate Cache-Control directives and freshness boundaries

Cache-Control determines whether a stored response can be reused and under what conditions. max-age establishes a freshness lifetime; s-maxage takes precedence for shared caches when present. RFC 9111's freshness test is strict: a response is fresh when freshness_lifetime > current_age, not when the values are equal.

Directive vocabulary also needs precise acceptance semantics. no-cache does not mean “do not store.” It requires successful validation before reuse. no-store prohibits storing any part of the applicable request/response for later use. private prevents storage by a shared cache while still allowing a private cache under the specification's rules. must-revalidate prevents stale reuse after expiry until validation succeeds.

Directive tested

Protocol expectation

Actual local observation

Acceptance

max-age=2

Reuse while fresh; validate when stale unless other permission applies

Immediate repeat hit cache; post-expiry request revalidated

Pass

no-cache

May be stored, but cannot satisfy later request without validation

First response stored; second use sent conditional request and received 304

Pass

no-store

Must not be stored or reused

Two calls both reached origin; no cache entry created

Pass

private

Shared cache must not store unqualified private response

Alice/Bob responses never entered shared store

Pass

must-revalidate

Once stale, do not reuse until successful validation

Simulated origin 503 was forwarded instead of old body

Pass

s-maxage

Shared-cache freshness overrides max-age/Expires; stale reuse requires validation

Protocol gate documented; not claimed as an executed fixture case

Not used as lab evidence

Cache-Control versus application authorization

Cache directives are not an authorization system. private can stop a conforming shared cache from storing a response, but it does not determine whether Alice is allowed to access /account/42. Likewise, ETag possession does not grant permission. Authentication and object-level authorization remain application decisions before the application exposes personalized content.

RFC 9111 adds an important shared-cache rule for requests containing Authorization: a shared cache must not reuse a cached response to such a request unless the response has a directive that explicitly permits shared-cache storage under the applicable requirements. The specification identifies directives including must-revalidate, public, and s-maxage as having that effect.

That means “an Authorization header automatically makes every response private forever” is also too simplistic. An origin can intentionally authorize shared caching through the protocol. Acceptance must test the application's actual policy, not rely on folklore. For personalized account data in this playbook, the policy is deliberately conservative: authenticated responses are private and never enter the shared store.

Test Vary and representation-specific cache keys

The cache key cannot safely be reduced to URL alone. RFC 9110 says Vary identifies request fields, beyond method and target URI, that may have influenced representation selection. RFC 9111 then prohibits reuse of the stored response without revalidation unless the request fields nominated by Vary match the request that produced the stored response.

The lab used X-Rep instead of a production content-negotiation field because the goal was deterministic evidence. /vary returned Vary: X-Rep; requests carrying X-Rep: A generated one synthetic body and validator, while X-Rep: B generated another.

Request

Expected variant

Cache state

Origin contacted?

Observed body/ID

First X-Rep: A

A

Miss

Yes

representation-A, vary:A:"5f7395e05e67"

First X-Rep: B

B

Miss

Yes

representation-B, vary:B:"cbee62ee585c"

Repeat A

A

Matching variant hit

No

A again

Repeat B

B

Matching variant hit

No

B again

Vary as a cache-key dimension

Actual observation: four client requests generated only two origin retrievals because each variant was stored independently. The repeat for A never received B's body, ETag, or representation identifier; the repeat for B likewise remained isolated.

Internally the fixture recorded variant keys equivalent to (('x-rep', 'A'),) and (('x-rep', 'B'),). That internal structure is implementation-specific. What HTTP requires is the matching behavior, not that exact data structure. RFC 9111 even permits normalization of nominated request fields in appropriate cases before comparison.

The production acceptance lesson is broader than X-Rep. If representation selection depends on Accept-Encoding, Accept-Language, or another request field, that dimension must be reflected in the response's Vary semantics when required. A perfectly valid ETag attached to the wrong selected variant is still the wrong cache answer.

Vary should also not be misused as a substitute for authorization isolation. RFC 9110 specifically treats authorization-based reuse through separate cache rules rather than requiring Authorization to become an ordinary Vary dimension. Authorization safety belongs in authenticated-response cacheability policy.

Test personalized responses and shared-cache isolation

The highest-severity acceptance failure is not a slightly old public asset. It is a personalized representation crossing users. The fixture therefore exercised fake authenticated identities through the same local shared proxy.

/personal returned private, max-age=60 for synthetic authenticated principals and a publicly cacheable representation for the anonymous case. RFC 9111 requires a shared cache not to store a response carrying an unqualified private directive.

Request

Origin body

Cache directive

Stored by shared cache?

Repeat behavior

Fake Alice

private-profile:alice

private, max-age=60

No

Alice repeat reached origin

Fake Bob

private-profile:bob

private, max-age=60

No

Independent origin response

Fake Alice again

private-profile:alice

private, max-age=60

No

No Bob/Alice shared hit

Anonymous first

anonymous representation

public cacheable policy

Yes

Entry stored

Anonymous repeat

anonymous representation

same

Yes

Shared-cache hit

/nostore twice

synthetic nonsecret content

no-store

No

Both calls reached origin

Personalized responses in shared-cache scenarios

Actual observation: Alice's first response was not stored, Bob's response was not stored, and Alice's second request went back to the origin rather than reusing either authenticated response. The anonymous representation was stored and subsequently hit. Two /nostore requests also caused two origin requests with no persistent entry.

This supports a narrowly scoped conclusion: the controlled shared-cache implementation honored the tested isolation directives for these synthetic identities. It does not establish the same behavior for an untested CDN, reverse proxy, service worker, browser cache, or application gateway.

The security acceptance rule should therefore inspect both positive and negative evidence. It is not enough to show Bob did not receive Alice's body once. The cache inventory should show that Alice's representation was never eligible for shared storage in the first place. That reduces dependence on accidental key differences or test ordering.

Authorization checks also remain necessary when a request misses cache. Correct cache isolation cannot rescue an origin that improperly authorizes Bob to fetch Alice's object. Cache correctness and authorization correctness are separate controls whose combined result prevents unauthorized data exposure.

Exercise validator changes, concurrent requests, and revalidation races

Revalidation creates a concurrency boundary. When many requests arrive after one popular entry becomes stale, an implementation can forward every request independently or collapse requests so one revalidation updates the entry and waiting requests reuse the result. RFC 9111 explicitly permits request collapsing, but it does not require every cache to implement the same synchronization strategy.

The local proxy used a per-key lock. The fixture first stored alpha-v1, changed the origin to alpha-v2-race, advanced the logical clock beyond the freshness lifetime, and then released eight client threads against the same stale key.

Race evidence

Actual observation

Concurrent client requests

8

Cached generation at race start

alpha-v1

Origin generation before release

alpha-v2-race

New strong ETag

"a1ecc8e6e0b2"

Additional origin requests during race

1

First cache disposition

stale forward, origin 200, stored

Remaining dispositions

7 cache hits after update

Client bodies

8 × alpha-v2-race

Client ETags

8 × "a1ecc8e6e0b2"

Revalidation race conditions

Actual observation: one thread acquired the revalidation path, received the new 200 representation, and updated the cache. The seven other requests then used that updated entry. All eight responses paired the new body with the new ETag. No client received the old alpha-v1 generation.

The fact that only one additional origin request occurred is implementation behavior, not a general HTTP guarantee. Another conforming cache could send multiple concurrent conditional requests and still be correct if every response is handled safely. This test therefore establishes local synchronization correctness, not an industry-wide request-collapsing property.

The acceptance invariant is about representation generation, not optimization: a losing revalidation must not overwrite a newer accepted representation, and concurrent requests must not combine the body from one generation with the validator or metadata of another. Implementations need enough synchronization or version checking to uphold that invariant.

A separate decision is whether stale content may be served while revalidation proceeds. stale-while-revalidate explicitly permits that for its configured interval. A requirement that “no stale representation leaves this boundary during revalidation” is incompatible with enabling such a stale window on that path unless the acceptance definition explicitly allows it.

Test unsafe assumptions around weak versus strong validators

Weak and strong ETags are not interchangeable evidence. If-None-Match intentionally uses weak comparison, so W/"semantic-1" can validate a stored response even when the current representation's bytes differ. That is protocol-defined behavior, not necessarily a stale-cache bug.

The /weak fixture made this visible. Its first body was weak semantic value with ETag W/"semantic-1". The origin then changed only formatting by adding additional spaces while deliberately retaining the same weak tag. On revalidation, the origin judged the representations equivalent under its weak-validator policy and returned 304.

Origin transition

Validator

Conditional result

Cache-visible bytes

Interpretation

Initial semantic version

W/"semantic-1"

Initial 200

Original spacing

Stored

Byte-only formatting change

Still W/"semantic-1"

304

Old cached bytes reused

Valid weak equivalence

Semantic version changes

W/"semantic-2"

Full 200

New body stored

Previous weak equivalence ended

Actual observation: after the formatting-only origin change, the cache continued to return its previously stored bytes because the unchanged weak tag validated them. When the origin advanced the tag to W/"semantic-2" for a semantic change, the cache received a full 200 and replaced the representation.

The unsafe assertion would be: “304 proves that the origin currently stores exactly the same bytes as the cache.” That statement is false when weak validators are involved. A strong validator is appropriate when byte-observable representation changes must invalidate the prior version; even then, the validator is opaque to the client, so acceptance should compare behavior rather than reverse-engineer tag contents.

This is another reason not to drift into If-Match semantics. Strong comparison has important uses for state-changing preconditions, but the cache-validation rule tested here is the weak comparison mandated for If-None-Match. Treating cache revalidation as if it were optimistic write concurrency would test the wrong protocol contract.

Validate browser/proxy observations against protocol semantics

A local proxy gives excellent observability because every lookup, forwarded request, conditional header, and storage decision can be recorded. It is not evidence that every browser uses the same key structure or exposes the same state.

RFC 9111 defines the HTTP cache requirements, while the WHATWG Fetch Standard specifies browser-oriented fetch processing and HTTP-cache integration. Browser implementations can also partition cache state in ways that are not represented by this fixture's simple URI-plus-Vary key. Product behavior should therefore be validated at the layer being accepted rather than inferred from a custom proxy.

That distinction is especially useful for engineers crossing frontend development and cloud development: the request may travel through browser cache logic, service infrastructure, gateways, and shared intermediaries, but each layer needs its own evidence.

Question

Protocol-defined?

Local lab observed?

Safe conclusion

Can a fresh stored response be reused?

Yes, subject to reuse conditions

Yes

Local implementation conforms for tested case

Does If-None-Match use weak comparison?

Yes

Yes through weak fixture

Protocol rule confirmed by fixture

Must Vary dimensions match for ordinary reuse?

Yes

Yes

Local variant isolation passed

Does private bar shared storage?

Yes

Yes

Local shared cache passed

Will every browser use identical internal key structure?

No

Not tested

Do not claim

Will every CDN collapse concurrent revalidations?

No

Not tested

Do not claim

Does a browser necessarily expose local Cache-Status detail?

No general guarantee from this fixture

Not tested

Do not claim

Does one local pass establish production edge correctness?

No

No

Production implementation requires its own run

Cache-Status is particularly useful as an observability vocabulary, but even that must be interpreted correctly. RFC 9211 provides standardized fields for reporting cache handling; its existence does not mean every intermediary emits the header, nor that an intermediary's absence of the header proves there was no cache.

A production acceptance run should therefore collect evidence at every controllable boundary: client request, shared-cache decision, forwarded origin request, origin response, and downstream representation. Where a proprietary cache supplies documented debug headers or logs, those can replace the fixture's synthetic LocalTestCache marker. Their semantics should be tied to that implementation's documentation.

Build an evidence matrix and acceptance gates

Acceptance should finish with evidence that another reviewer can audit without reconstructing intent from console output. For every case, preserve the request headers relevant to cache selection, the origin response status and validators, cache directives, Vary, cache disposition, and final representation identity.

The protocol evidence also needs to be distinguished from the lab evidence. RFC 9111 says a cache must not reuse a response unless its target URI, method, Vary selection, validation requirements, and freshness/stale rules allow that reuse. The lab trace then answers whether this particular implementation actually met those conditions.

Acceptance case

Required protocol outcome

Executed evidence

Result

First ETag request

Miss may retrieve/store eligible 200

Origin 200, "1045d9a162b5", stored

Pass

Fresh ETag request

Fresh eligible response reusable

Cache hit; origin not called

Pass

Stale unchanged ETag

Conditional validation before reuse

If-None-Match, origin 304, stored body reused

Pass

Changed ETag

Old tag must not validate new representation

Origin 200, "52d7699527d7", new body

Pass

Last-Modified fallback

IMS can validate when no ETag

IMS observed; unchanged 304

Pass

Changed Last-Modified

Changed representation returned fully

200 with new time and lm:v2

Pass

304 processing

Reuse existing representation, not 304 body

Client received stored complete body

Pass

no-cache

Validate before every reuse

Second request conditionally validated

Pass

no-store

Do not store/reuse

Two origin calls; no entry

Pass

private personalization

Shared cache must not store

Alice/Bob never stored

Pass

Vary: X-Rep

A and B selected separately

Two variants, independent hits

Pass

Concurrent stale entry

No old/new generation corruption

Eight clients received new generation

Pass

must-revalidate + failure

Stale copy not reused

Origin 503 forwarded

Pass

Weak validator

Byte difference may remain equivalent

304 reused old bytes under same weak tag

Pass

The release gate should be stricter than “all expected statuses appeared.” A passing trace must preserve the tuple request selection → eligible cache entry → validator → response metadata → representation identity. Any unexplained mismatch in that chain is a failure even when the final HTTP status looks conventional.

Gate

Pass condition

Block release when

Validator integrity

Each strong tag maps consistently to the tested representation generation

Same strong tag appears on observably changed tested representation

Freshness

No stale reuse outside explicitly allowed policy

Stale body served without successful validation or documented permission

304 handling

Stored body retained and appropriate metadata refreshed

304 treated as independent representation

Variant isolation

Every Vary dimension participates in matching

Variant A can answer variant B

Shared/private boundary

Private and no-store content absent from shared inventory

Personalized entry becomes shared-cache candidate

Auth boundary

No fake principal receives another principal's body

Any cross-principal representation observed

Failure behavior

must-revalidate blocks stale fallback

Origin failure causes prohibited stale reuse

Concurrency

All returned body/validator pairs belong to one valid generation

Mixed generations or stale overwrite

Scope discipline

Claims limited to implementation actually tested

Local run generalized to browsers/CDNs without evidence

There is one subtle gate around permitted stale behavior. RFC 9111 prohibits generating stale responses when an applicable directive forbids them, and otherwise requires a basis such as disconnection, an explicit request/origin permission, an extension such as RFC 5861, or an appropriate out-of-band contract. Acceptance documentation should therefore list every intentional stale pathway rather than treating “stale” as universally forbidden.

For this fixture, no stale-while-revalidate window was used in the strict paths. The concurrent stale test blocked behind a revalidation/update instead of intentionally returning the prior generation. That is a local design decision consistent with the acceptance objective, not a requirement that every HTTP cache adopt the same concurrency strategy.

Final acceptance decision and Refonte Learning CTA

Decision item

Final status

Local ETag revalidation

Accepted

Local Last-Modified fallback

Accepted

304 stored-representation reuse

Accepted

Tested freshness/directive handling

Accepted

Vary variant isolation

Accepted

Synthetic authenticated-user isolation

Accepted

Concurrent local revalidation

Accepted

Weak-validator semantics

Accepted with documented byte-equivalence limitation

Browser/CDN equivalence

Not claimed; requires implementation-specific evidence

Production-data safety

Lab only; no real credentials or customer data permitted

Refonte APIs Developer program facts

3 months; 10–12 hours/week; REST; GraphQL; authentication and authorization; database integration; API documentation and testing; error handling and logging; versioning and deprecation; microservices architecture; performance optimization; API security best practices

Build broader API engineering skills through Refonte Learning’s APIs Developer Fundamentals.

Use its three-month, 10–12-hours/week program without assuming HTTP caching internals are specifically taught.