Skip to content

Make target.loadedFromSource the sole cache-disposition signal; drop the Context mirror (#1576)#1590

Closed
kriszyp wants to merge 1 commit into
v5.1from
kris/loadedfromsource-target-only
Closed

Make target.loadedFromSource the sole cache-disposition signal; drop the Context mirror (#1576)#1590
kriszyp wants to merge 1 commit into
v5.1from
kris/loadedfromsource-target-only

Conversation

@kriszyp

@kriszyp kriszyp commented Jul 3, 2026

Copy link
Copy Markdown
Member

Follow-up to #1575 (already merged to v5.1). Fixes #1576.

Why

#1575 mirrored cache disposition onto the request Context so plain-id gets could observe it (#1571). But loadedFromSource is fundamentally a per-get() result: each get() has its own RequestTarget, whereas the Context is shared for the whole request lifecycle. Mirroring onto the shared Context makes it last-write-wins — an internal caching get() inside a resource's own get() overwrites the entry get's disposition before the caller reads it.

The REST handler already reads the precise per-get value from target.loadedFromSource (server/REST.ts:231). And #1571's "unobservable on string-id gets" is really a usage question best answered by docs: observe disposition on the RequestTarget you pass to the get. So the Context mirror is code papering over a documentation gap, and it introduces a semantic that doesn't hold up.

What

Remove the Context mirror and standardize on target.loadedFromSource:

Retained from #1575 (the parts that were genuinely right)

  • Per-get false-marking on cache HITS in the loadAsInstance=false value path and ensureLoaded (Table.ts ~1227/~1240). Before Fix loadedFromSource being unobservable on string-id gets on caching tables #1575 those paths set the flag on source fetch but left it undefined on cache hits, so REST.ts (if (loadedFromSource !== undefined)) skipped the Age header on hits. This is a real target-side fix, independent of the mirror.
  • The primitive-target guard in the helper.

Testing

Caching unit suite converted to observe target.loadedFromSource; 15/15 passing against dist (incl. the loadAsInstance=false hit/miss case and stale-while-revalidate). The plain-id "observable on context" test was removed (its premise — the mirror — is gone; target hit/miss is covered by the existing Can load cached data test).

Docs

HarperFast/documentation#563 reframed to make target.loadedFromSource canonical and drop context.loadedFromSource.

Note for downstream

The OCSP/CRL cert-verification code (security/certificateVerification/*) does plain-id gets and previously read the (always-undefined) wasLoadedFromSource(). With the mirror gone, disposition there must be observed by passing a RequestTarget; tracked separately.

Authored by KrAIs (Claude Opus 4.8), at Kris's direction.

…osition signal (#1576)

#1575 mirrored cache disposition onto the request Context so plain-id gets
could observe it (#1571). But loadedFromSource is fundamentally a per-get
result: each get() has its own RequestTarget, whereas the Context is shared
for the whole request lifecycle. Mirroring onto the shared Context makes it
last-write-wins — an internal caching get() inside a resource's own get()
overwrites the entry get's disposition before the caller reads it. The REST
handler already reads the precise per-get value from target.loadedFromSource
(server/REST.ts), and #1571's gap is really a usage question answered by docs:
observe disposition on the RequestTarget you pass to the get.

This removes the Context mirror and standardizes on target.loadedFromSource:
- setLoadedFromSource() now writes only the target (keeping the primitive-id
  guard #1575 added); drop the Context param and all context writes.
- Remove the context-only marking in ensureLoaded() (undefined target).
- Remove the now-unused Context.loadedFromSource declaration (it predated
  #1575 as declared-but-never-assigned).

Retained from #1575 (the parts that were genuinely right): the per-get
false-marking on cache HITS in the loadAsInstance=false value path and
ensureLoaded (Table.ts ~1227/~1240) — before #1575 those paths set the flag
on source fetch but left it undefined on cache hits, so REST skipped the Age
header on hits. And the primitive-target guard.

Tests updated to observe target.loadedFromSource; full caching suite passes
(15/15) against dist. Docs: HarperFast/documentation#563.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

@gemini-code-assist gemini-code-assist Bot 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.

Code Review

This pull request refactors how the cache disposition flag loadedFromSource is tracked and exposed. It removes the mirroring of loadedFromSource onto the request Context (removing it from the Context interface and the setLoadedFromSource function), and instead records it strictly on the RequestTarget object passed to the get operation. The corresponding unit tests have been updated to reflect this change. I have no feedback to provide as there are no review comments.

@claude

claude Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Reviewed; no blockers found.

kriszyp added a commit that referenced this pull request Jul 3, 2026
…mSource

The OCSP and CRL verification paths reported the `cached` field (and the trace
log) by calling `wasLoadedFromSource?.()` on the record returned by the caching
table's `get()`. That plain frozen record has no resource methods, so the call
was always `undefined` and `cached` was always `true` (always "cache hit").
`wasLoadedFromSource()` was removed from the Resource base class in #1576, and
the Context mirror of the flag is removed in #1590.

Observe cache disposition on the RequestTarget of the get instead (#1590 makes
`target.loadedFromSource` the sole signal, marked on both hit and miss by the
`_loadRecord` path that serves this instance-mode table).

- ocsp / crl: pass a RequestTarget as the get id (empty ctor + `target.id =
  cacheKey` to avoid URL-parsing keys that may contain '/' or '?'); the cert
  data still travels in the context arg, which the source reads as
  requestContext. Read `target.loadedFromSource` after the get. CRL is aligned
  to the same 2-arg `get(target, context)` convention as OCSP.
- certificateVerificationSource: read performCRLCheck/performOCSPCheck off the
  live module object (`.default`) with memoized, per-method dynamic imports. A
  CJS module's ESM-import namespace snapshots its named exports at first import,
  so a cached function reference would honor a stale implementation forever —
  e.g. a test double pinned past its restore, which leaked across test files.
- crl: import the PKI.js Ed25519/Ed448 patch directly. CRL signature
  verification uses PKI.js but previously got the patch only as a side effect of
  loading the OCSP module; with the per-method import above, a CRL-only path no
  longer loads OCSP, so it must own its patch dependency (else Ed25519/Ed448
  issuers degrade to unknown/failure).

Adds unit tests covering fresh-fetch cached:false and cached-read cached:true
for both OCSP and CRL. Verified the cert cache table marks target.loadedFromSource
true on miss / false on hit despite being instance mode (loadAsInstance unset),
and that loading only the CRL module applies the Ed25519 patch.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@kriszyp

kriszyp commented Jul 6, 2026

Copy link
Copy Markdown
Member Author

Closing — v5.1's #1575 (the base this PR reworks) was reverted after we caught it riding into an unintended v5.1.16 patch (it should not have shipped as a patch). The Context-mirror bug and the fix this PR describes both only ever existed on v5.1, which no longer carries any of it.

The net end-state this PR was going for (target-only loadedFromSource, Age-header-on-hits fix, primitive guard, no Context mirror) has been ported to main/5.2 as one clean PR instead: #1626.

— KrAIs

@kriszyp kriszyp closed this Jul 6, 2026
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.

1 participant