httpstore

package
v0.6.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 18, 2026 License: Apache-2.0 Imports: 22 Imported by: 0

README

pkg/httpstore

Pure HTTP resource store with render-local initial admission and two-version (pending/accepted) refresh caching.

Overview

HTTPStore fetches arbitrary HTTP content (IP blocklists, JSON config, anything templates need from outside the cluster). Periodic refreshes use a two-version pattern: changed content lands in pending until the controller validates it, then it's promoted to accepted. New authoritative sources use render-local candidates instead. The controller accepts the complete candidate set only after the exact rendered output passes every validator, so an upstream serving garbage can't break the live HAProxy config.

This is the pure half of the design. Periodic refresh timers, validation event handling, eviction, and the template-callable wrapper all live in pkg/controller/httpstore. Reach for the pure store directly only from tests; in production the event adapter is what you want.

Quick Start

import "github.com/haproxy-haptic/HAPTIC/pkg/httpstore"

store := httpstore.New(logger, 2*time.Minute) // maxAge for eviction; 0 disables

opts := httpstore.FetchOptions{
    Timeout:  30 * time.Second,
    Retries:  3,
    Critical: true,
    Delay:    5 * time.Minute,
}
source, err := store.ReconcileSource("https://example.com/blocklist.txt", opts,
    &httpstore.AuthConfig{Type: "bearer", Token: "secret"})

// Fetch without exposing the response through accepted or pending state.
content, candidate, err := store.PrepareInitial(ctx,
    "https://example.com/blocklist.txt", source.State)

// The caller validates the complete rendered output, then commits every
// candidate from that render in one operation.
err = store.CommitInitialCandidates(ctx, []*httpstore.InitialCandidate{candidate})

// Later: refresh into pending (returns true if content changed)
changed, err := store.RefreshURL(ctx, "https://example.com/blocklist.txt")

// Production render reads accepted only
got, ok := store.Get(url)

// Validation render reads pending if available, otherwise accepted
got, ok = store.GetForValidation(url)

// After periodic-refresh validation returns:
store.PromotePending(url) // success → pending becomes accepted
store.RejectPending(url)  // failure → pending discarded, accepted preserved

// Test fixtures bypass HTTP entirely
store.LoadFixture(url, "mock content")

Fetch remains a convenience for isolated stores that may accept their first successful response immediately. The controller uses it only for render-local read-only stores; authoritative live renders use PrepareInitial and CommitInitialCandidates through pkg/controller/httpstore.

Authentication

Three modes via AuthConfig.Type:

  • "basic" — Username + Password
  • "bearer" — Token
  • "header" — Headers map (e.g. X-API-Key: ...)

Conditional Requests

Refreshes use ETag and Last-Modified automatically; a 304 returns changed=false so the existing accepted/pending state is preserved. Repeated refreshes against an unchanged upstream cost one round-trip and zero cache churn.

Source authority

The URL, effective fetch options, and authentication form one source declaration. An authoritative declaration change invalidates the accepted body and advances the source generation. The generation fences refreshes and render-local candidates that started under the previous declaration. The controller returns the candidate response to its render but accepts it only when the exact full pipeline succeeds; one stale candidate rejects the complete multi-URL commit. Admission and source-map renders fetch a different declaration into a render-local store instead of calling this shared-store transition.

Size Limit

Responses are capped at MaxContentSize (10 MiB, declared in types.go and enforced in fetcher.go via io.LimitReader). Larger payloads fail with an explicit response body exceeds maximum size of N bytes error rather than being silently truncated, and the limited reader means the store never buffers more than ~10 MiB even for a runaway upstream. There is no per-call override; if you legitimately need larger blobs, change the constant.

Trust Model

Content fetched here becomes HAProxy configuration and Coraza WAF rules, so it is a supply-chain path into the data plane. What the store does and does not guarantee:

TLS. The client uses http.DefaultTransport, so server certificates are verified against the system roots. There is no configuration, values key, or environment variable that disables verification and no custom-CA hook — InsecureSkipVerify appears nowhere outside test helpers. To trust a private CA, mount it into the container's system trust store.

Redirects. Up to 10 hops are followed. A redirect from an https:// source to a plaintext target is refused: the fetched bytes become HAProxy config, so anyone on a plaintext hop could rewrite them. A source configured as http:// was never confidential, so it may redirect within http://. On a host change every header except User-Agent, Referer, If-None-Match, If-Modified-Since and Accept-Encoding is dropped — net/http strips only Authorization, which would otherwise leak AuthTypeHeader API keys to the redirect target.

Size. Bodies are capped per entry at MaxContentSize (10 MiB); the cap is not aggregate, so worst-case memory scales with the number of cached URLs. Archives expanded by the template-side untar_gz are bounded separately (4096 entries, 8 MiB per entry, 32 MiB total).

Integrity. The SHA-256 in each cache entry is change detection against the previously accepted body — it drives the pending/accepted validation cycle. It is not a pin: there is no way to declare an expected checksum, so an upstream that is silently replaced with content that still renders to valid HAProxy config is validated and adopted. Operators who need stronger guarantees should serve fetched content from infrastructure they control rather than a third-party URL.

Residual risks. A compromised upstream chooses the final host, so a redirect can point the controller at an in-cluster address it would not otherwise reach; the response only becomes config if it passes validation, but the request itself is made. http.DefaultTransport also honours HTTP_PROXY/HTTPS_PROXY/NO_PROXY, so an operator-set proxy in controller.extraEnv sees the whole fetch path.

Eviction

Entries that haven't been read in maxAge are evicted on the next EvictUnused() call. Entries with pending content are never evicted (pending must always have a place to land or be rejected). The event adapter calls EvictUnused periodically; for tests, pass maxAge: 0 to disable eviction entirely.

See Also

  • pkg/controller/httpstore — event adapter that drives refresh timers, handles validation lifecycle, and exposes the wrapper to templates
  • pkg/controller/testrunner — uses LoadFixture to mock HTTP content during validation tests
  • pkg/httpstore/CLAUDE.md — design notes (two-version pattern, eviction tuning, conditional-request mechanics)

License

Apache-2.0 — see root LICENSE.

Documentation

Overview

Package httpstore provides HTTP resource fetching with caching and validation.

This package implements a store that fetches resources from HTTP(S) URLs, caches them, and supports periodic refresh with a two-version cache for safe validation before accepting new content.

Index

Constants

View Source
const (
	// AuthTypeBasic selects HTTP basic authentication (Username/Password).
	AuthTypeBasic = "basic"

	// AuthTypeBearer selects bearer-token authentication (Token).
	AuthTypeBearer = "bearer"

	// AuthTypeHeader selects custom-header authentication (Headers).
	AuthTypeHeader = "header"
)

Supported AuthConfig.Type values.

View Source
const DefaultRetries = 2

DefaultRetries is the default number of retry attempts. Combined with DefaultRetryDelay's exponential backoff (0.5s + 1s = 1.5s of waits across 3 total attempts), this caps the worst-case budget for an unreachable URL well under the 5-second envelope a non-critical HTTP fetch should respect on a template render hot path. Raising either value here pushes per-render latency past the conformance test convergence windows that gate CI.

View Source
const DefaultRetryDelay = 500 * time.Millisecond

DefaultRetryDelay is the base delay between retry attempts. The backoff is exponential — attempt N waits `RetryDelay * 2^(N-1)` — so with Retries=2 the per-call wait budget is 0.5s + 1s = 1.5s. Each fetch attempt that fails fast on connection refused adds at most ~1 RTT, so total worst case is ~2s on a healthy network with an unreachable target.

View Source
const DefaultTimeout = 30 * time.Second

DefaultTimeout is the default HTTP request timeout.

View Source
const DefaultValidationStuckAfter = 5 * time.Minute

DefaultValidationStuckAfter bounds a pending validation. Render plus three-phase HAProxy validation takes seconds; an entry still validating minutes later is waiting for a verdict that is never coming.

View Source
const MaxContentSize = 10 * 1024 * 1024

MaxContentSize is the maximum allowed content size (10MB).

Variables

View Source
var ErrActiveLeaseTokenStale = errors.New("HTTP active lease token is stale or substituted")

ErrActiveLeaseTokenStale means a newer lease generation was committed after the token was read: the reader began on a snapshot that a concurrent commit has since replaced, and can begin again on the current one.

View Source
var ErrCriticalFetch = errors.New("critical HTTP fetch failed")

ErrCriticalFetch marks a critical source whose fetch failed, as opposed to a render that must retry because its source authority moved.

View Source
var ErrInputsMoved = errors.New("changed while the render was running")

ErrInputsMoved reports that another render accepted content the failing render had read, so it was composed against inputs that no longer hold. Rendering again reads the accepted content; refusing leaves the render without a trigger, because acceptance raises no watch event.

Functions

func RedactURL

func RedactURL(raw string) string

RedactURL removes user information, queries, and fragments from diagnostics.

func SourceIdentity

func SourceIdentity(opts FetchOptions, auth *AuthConfig) (string, error)

SourceIdentity returns an opaque identity for the effective fetch policy.

Types

type AcceptedReplayProof

type AcceptedReplayProof struct {
	// contains filtered or unexported fields
}

AcceptedReplayProof authenticates one accepted value and its source state.

func (*AcceptedReplayProof) Descriptor

func (p *AcceptedReplayProof) Descriptor() SourceDescriptor

Descriptor returns the exact observed source declaration.

func (*AcceptedReplayProof) URL

func (p *AcceptedReplayProof) URL() string

URL returns the observed source URL.

func (*AcceptedReplayProof) ValidateAuthentication

func (p *AcceptedReplayProof) ValidateAuthentication() error

ValidateAuthentication verifies that the proof retains its minted identity.

type AcceptedReplayState

type AcceptedReplayState struct {
	// contains filtered or unexported fields
}

AcceptedReplayState authenticates the accepted HTTP inputs published by one render.

func (*AcceptedReplayState) Epoch

func (s *AcceptedReplayState) Epoch() *ReplayEpoch

Epoch returns the complete HTTP replay root captured with the publication.

func (*AcceptedReplayState) Proofs

Proofs returns the authenticated proofs corresponding to Snapshots.

func (*AcceptedReplayState) ReplayWatermark

func (s *AcceptedReplayState) ReplayWatermark() Revision

ReplayWatermark returns the render-observable journal position at publication.

func (*AcceptedReplayState) Snapshots

func (s *AcceptedReplayState) Snapshots() []ContentSnapshot

Snapshots returns detached accepted inputs in URL order.

func (*AcceptedReplayState) Source

func (s *AcceptedReplayState) Source() SourceID

Source returns the store identity that minted the state.

func (*AcceptedReplayState) ValidateAuthentication

func (s *AcceptedReplayState) ValidateAuthentication() error

ValidateAuthentication verifies that the state retains its minted identity and immutable root.

func (*AcceptedReplayState) Watermark

func (s *AcceptedReplayState) Watermark() Revision

Watermark returns the accepted-content journal position at publication.

type ActiveLeaseChange

type ActiveLeaseChange struct {
	URL        string
	Descriptor SourceDescriptor
	Revision   ActiveLeaseRevision
}

ActiveLeaseChange identifies one leased declaration whose meaning changed.

type ActiveLeaseCommit

type ActiveLeaseCommit struct {
	Snapshot        *ActiveLeaseSnapshot
	VerifyOnly      bool
	Updates         []ActiveLeaseUpdate
	Replacement     []ActiveLeaseReference
	Replace         bool
	Replay          *AcceptedReplayState
	PublishedReplay []ContentSnapshot
}

ActiveLeaseCommit verifies a snapshot or publishes its dependency transition.

func (*ActiveLeaseCommit) ValidateMode

func (c *ActiveLeaseCommit) ValidateMode() error

ValidateMode rejects transitions combined with verification-only mode.

type ActiveLeaseReference

type ActiveLeaseReference struct {
	URL        string
	Descriptor SourceDescriptor
	References uint64
}

ActiveLeaseReference is one exact declaration and its complete reference count.

type ActiveLeaseRevision

type ActiveLeaseRevision uint64

ActiveLeaseRevision is monotonic within one lease set.

type ActiveLeaseSet

type ActiveLeaseSet struct {
	// contains filtered or unexported fields
}

ActiveLeaseSet owns the HTTP dependencies of one persistent render cache.

func (*ActiveLeaseSet) BeginActiveLeases

func (s *ActiveLeaseSet) BeginActiveLeases(token ActiveLeaseToken) (*ActiveLeaseSnapshot, error)

BeginActiveLeases captures relevant changes and the exact commit fence.

func (*ActiveLeaseSet) RetireActiveLeases

func (s *ActiveLeaseSet) RetireActiveLeases(token ActiveLeaseToken) ([]string, error)

RetireActiveLeases removes a complete cache lease set.

type ActiveLeaseSnapshot

type ActiveLeaseSnapshot struct {
	// contains filtered or unexported fields
}

ActiveLeaseSnapshot is an authenticated begin-of-render lease fence.

func (*ActiveLeaseSnapshot) Changes

func (s *ActiveLeaseSnapshot) Changes() []ActiveLeaseChange

Changes returns detached relevant changes pending at the begin fence.

func (*ActiveLeaseSnapshot) Contains

func (s *ActiveLeaseSnapshot) Contains(url string, descriptor SourceDescriptor) bool

Contains reports whether the exact declaration was active at the begin fence.

func (*ActiveLeaseSnapshot) ContainsURL

func (s *ActiveLeaseSnapshot) ContainsURL(url string) bool

ContainsURL reports whether any declaration for the URL was active.

func (*ActiveLeaseSnapshot) HasChanges

func (s *ActiveLeaseSnapshot) HasChanges() bool

HasChanges reports whether a leased declaration changed since its last publication.

func (*ActiveLeaseSnapshot) ReplayContains

func (s *ActiveLeaseSnapshot) ReplayContains(url string, descriptor SourceDescriptor) bool

ReplayContains reports whether the selective replay lease owns the declaration.

func (*ActiveLeaseSnapshot) Token

Token returns the exact committed token from which this snapshot began.

type ActiveLeaseToken

type ActiveLeaseToken struct {
	// contains filtered or unexported fields
}

ActiveLeaseToken authenticates one committed lease root and acknowledgment.

func (ActiveLeaseToken) Source

func (t ActiveLeaseToken) Source() SourceID

Source returns the store that minted the token.

func (ActiveLeaseToken) Valid

func (t ActiveLeaseToken) Valid() bool

Valid reports whether the token was minted for a lease set.

type ActiveLeaseTransition

type ActiveLeaseTransition struct {
	Activated []string
	Retired   []string
}

ActiveLeaseTransition reports URL-level zero-to-one and one-to-zero changes.

type ActiveLeaseUpdate

type ActiveLeaseUpdate struct {
	URL        string
	Descriptor SourceDescriptor
	Added      uint64
	Removed    uint64
}

ActiveLeaseUpdate changes the reference count for one exact declaration.

type AuthConfig

type AuthConfig struct {
	// Type is the authentication type: AuthTypeBasic, AuthTypeBearer, or AuthTypeHeader.
	Type string

	// Username for basic auth.
	Username string

	// Password for basic auth.
	Password string

	// Token for bearer auth.
	Token string

	// Headers for custom header auth (e.g., API keys).
	// These headers are added to every request.
	Headers map[string]string
}

AuthConfig configures HTTP authentication.

type CacheEntry

type CacheEntry struct {

	// URL is the source URL for this entry.
	URL string

	// Accepted version (validated, in production use)
	AcceptedContent  string
	AcceptedChecksum string
	AcceptedTime     time.Time

	// LastAccessTime tracks when this entry was last accessed via Get/Fetch.
	// Used for cache eviction of unused entries.
	LastAccessTime time.Time

	// Pending version (fetched, awaiting validation)
	PendingContent  string
	PendingChecksum string
	PendingRevision uint64
	HasPending      bool

	// ValidationState tracks the current state of this entry.
	ValidationState ValidationState

	// ValidationStartedAt is when ValidationState last became StateValidating.
	// Only StateValidating leaves it meaningful; see HTTPStore.validationStuckAfter.
	ValidationStartedAt time.Time

	// HTTP caching headers for conditional requests
	ETag         string
	LastModified string

	// Configuration for this URL
	Options FetchOptions
	Auth    *AuthConfig
	// contains filtered or unexported fields
}

CacheEntry holds cached content with two-version support for safe validation.

The two-version design ensures that new content is only accepted after successful validation. This is critical for resources like IP blocklists where we must not discard the old blocklist before knowing the new one is valid.

type CandidateCommit

type CandidateCommit struct {
	Candidate SnapshotToken
	Accepted  SnapshotToken
}

CandidateCommit maps render-local candidate bytes to the accepted version created by the same atomic commit.

type ContentSnapshot

type ContentSnapshot struct {
	URL         string
	Descriptor  SourceDescriptor
	Content     string
	Found       bool
	Cacheable   bool
	Token       SnapshotToken
	StoreSource SourceID
	Observation Revision
	Watermark   Revision
}

ContentSnapshot binds template-observable bytes to their exact store version.

func (ContentSnapshot) MarshalJSON

func (ContentSnapshot) MarshalJSON() ([]byte, error)

MarshalJSON rejects lossy serialization of opaque descriptors and tokens.

func (*ContentSnapshot) ObservationToken

func (s *ContentSnapshot) ObservationToken() ObservationToken

ObservationToken returns the exact verification token for this snapshot.

type FetchOptions

type FetchOptions struct {
	// Delay is the refresh interval (how often to re-fetch).
	// Zero means no automatic refresh (fetch once).
	Delay time.Duration

	// Timeout is the HTTP request timeout.
	// Default: 30s
	Timeout time.Duration

	// Retries is the number of retry attempts on failure.
	// Default: 3
	Retries int

	// RetryDelay is the wait time between retries.
	// Default: 1s
	RetryDelay time.Duration

	// Critical indicates whether fetch failure should fail the template render.
	// If true, a failed fetch returns an error.
	// If false, a failed fetch returns empty string and logs a warning.
	Critical bool

	// AcceptStatus lists statuses besides 200 whose body is the content,
	// for servers that answer with the status they describe (an error-page
	// service serving its 404 page as a 404).
	AcceptStatus StatusSet
}

FetchOptions configures HTTP fetching behavior.

func (FetchOptions) WithDefaults

func (o FetchOptions) WithDefaults() FetchOptions

WithDefaults returns a copy of the options with default values applied.

type HTTPOverlay

type HTTPOverlay struct {
	// contains filtered or unexported fields
}

HTTPOverlay represents pending HTTP content changes awaiting validation.

Unlike K8s overlays which are constructed explicitly with additions/modifications/deletions, HTTP overlays are derived from the HTTPStore's pending content state. When content changes during refresh, it's stored as pending in the HTTPStore. The overlay provides access to this pending content during validation rendering.

This type implements the stores.ContentOverlay interface, enabling unified handling of both K8s and HTTP overlays in the validation pipeline.

func NewAcceptedHTTPOverlay

func NewAcceptedHTTPOverlay(store *HTTPStore) *HTTPOverlay

NewAcceptedHTTPOverlay freezes accepted content without selecting pending versions.

func NewHTTPOverlay

func NewHTTPOverlay(store *HTTPStore) *HTTPOverlay

NewHTTPOverlay creates an overlay from the store's current pending state.

The overlay captures a snapshot of which URLs have pending content at creation time. This ensures consistent behavior even if the store's state changes during validation.

Parameters:

  • store: The HTTPStore to derive pending state from

Returns:

  • An HTTPOverlay with the current pending URLs snapshot

func (*HTTPOverlay) GetContent

func (o *HTTPOverlay) GetContent(url string) (string, bool)

GetContent returns content for the given URL.

If the URL has pending content, returns the pending content. Otherwise, returns the accepted content if available. This behavior matches what templates should see during validation rendering.

Parameters:

  • url: The URL to get content for

Returns:

  • content: The content string (pending preferred, otherwise accepted)
  • ok: True if content was found

func (*HTTPOverlay) GetContentForDescriptor

func (o *HTTPOverlay) GetContentForDescriptor(url string, descriptor SourceDescriptor) (string, bool)

GetContentForDescriptor returns content only for the exact fetch declaration.

func (*HTTPOverlay) GetContentForSource

func (o *HTTPOverlay) GetContentForSource(url, sourceIdentity string) (string, bool)

GetContentForSource returns overlay content only for its fetch authority.

func (*HTTPOverlay) HasPendingURL

func (o *HTTPOverlay) HasPendingURL(url string) bool

HasPendingURL returns true if the given URL has pending content.

func (*HTTPOverlay) IsEmpty

func (o *HTTPOverlay) IsEmpty() bool

IsEmpty returns true if the overlay contains no pending content. Implements the stores.ContentOverlay interface.

func (*HTTPOverlay) PendingURLs

func (o *HTTPOverlay) PendingURLs() []string

PendingURLs returns the list of URLs with pending content. This is the snapshot captured at overlay creation time.

func (*HTTPOverlay) PendingVersion

func (o *HTTPOverlay) PendingVersion(url string) (checksum string, revision uint64, found bool)

PendingVersion returns the checksum and revision captured for url.

func (*HTTPOverlay) RevisionSource

func (o *HTTPOverlay) RevisionSource() SourceID

RevisionSource identifies the store that supplied this immutable overlay.

func (*HTTPOverlay) Snapshot

func (o *HTTPOverlay) Snapshot(url string, descriptor SourceDescriptor) ContentSnapshot

Snapshot returns frozen pending or accepted bytes for one exact source.

type HTTPStore

type HTTPStore struct {
	// contains filtered or unexported fields
}

HTTPStore provides HTTP resource fetching with caching and two-version validation.

The store supports:

  • Synchronous initial fetch (blocks until complete)
  • Cached access to previously fetched content
  • Two-version cache for safe validation (pending vs accepted)
  • Conditional requests using ETag/If-Modified-Since
  • Automatic eviction of unused entries based on last access time

Thread-safe for concurrent access.

func New

func New(logger *slog.Logger, maxAge time.Duration) *HTTPStore

New creates a new HTTPStore with the given logger and maximum cache age.

maxAge is the maximum time an entry can remain unused before becoming eligible for eviction. If maxAge is 0, entries are never evicted based on access time.

func (*HTTPStore) AcceptanceSequence

func (s *HTTPStore) AcceptanceSequence() uint64

AcceptanceSequence counts acceptances so far. A render that reads the store after an acceptance sees that acceptance's content.

func (*HTTPStore) AcceptedSnapshot

func (s *HTTPStore) AcceptedSnapshot(url string, source SourceDescriptor) ContentSnapshot

AcceptedSnapshot returns accepted bytes and their exact source version atomically.

func (*HTTPStore) AcceptedStagedSnapshot

func (s *HTTPStore) AcceptedStagedSnapshot(source *StagedSource) (ContentSnapshot, bool, error)

AcceptedStagedSnapshot returns the source's accepted content without fetching, and whether an unaccepted source would be critical to the render.

func (*HTTPStore) AdvanceAcceptedReplayState

func (s *HTTPStore) AdvanceAcceptedReplayState(
	state *AcceptedReplayState,
) (*AcceptedReplayState, bool)

AdvanceAcceptedReplayState rebases a state across unrelated URL changes.

func (*HTTPStore) CaptureAcceptedReplayProof

func (s *HTTPStore) CaptureAcceptedReplayProof(snapshot *ContentSnapshot) (*AcceptedReplayProof, bool)

CaptureAcceptedReplayProof binds an accepted snapshot to current refresh state.

func (*HTTPStore) CaptureAcceptedReplayState

func (s *HTTPStore) CaptureAcceptedReplayState(
	snapshots []ContentSnapshot,
) (*AcceptedReplayState, bool)

CaptureAcceptedReplayState binds accepted snapshots to one selective replay cursor.

func (*HTTPStore) CaptureReplayEpoch

func (s *HTTPStore) CaptureReplayEpoch() *ReplayEpoch

CaptureReplayEpoch snapshots the global render-relevant HTTP epoch.

func (*HTTPStore) ChangesSince

func (s *HTTPStore) ChangesSince(revision Revision) (
	current Revision,
	changes []SemanticChange,
	complete bool,
)

ChangesSince returns the bounded accepted-content change journal.

func (*HTTPStore) CommitInitialCandidates

func (s *HTTPStore) CommitInitialCandidates(ctx context.Context, candidates []*InitialCandidate) error

CommitInitialCandidates atomically accepts a complete validated candidate set.

func (*HTTPStore) CommitInitialCandidatesAndVerify

func (s *HTTPStore) CommitInitialCandidatesAndVerify(
	ctx context.Context,
	candidates []*InitialCandidate,
	accepted []SnapshotToken,
) ([]CandidateCommit, Revision, error)

CommitInitialCandidatesAndVerify verifies all pre-existing reads and accepts every validated initial candidate in one store transaction.

func (*HTTPStore) CommitInitialCandidatesAndVerifyObservations

func (s *HTTPStore) CommitInitialCandidatesAndVerifyObservations(
	ctx context.Context,
	candidates []*InitialCandidate,
	observations []ObservationToken,
) ([]CandidateCommit, Revision, error)

CommitInitialCandidatesAndVerifyObservations verifies exact present and negative reads before accepting validated initial candidates.

func (*HTTPStore) ConfirmAcceptances

func (s *HTTPStore) ConfirmAcceptances(observations []ObservationToken)

ConfirmAcceptances makes only the exact versions read by a passing render permanent.

func (*HTTPStore) DiscardPendingVersion

func (s *HTTPStore) DiscardPendingVersion(url, expectedChecksum string, expectedRevision uint64) bool

DiscardPendingVersion discards one exact version without recording a validation failure.

func (*HTTPStore) EvictUnused

func (s *HTTPStore) EvictUnused() []string

EvictUnused removes cache entries that haven't been accessed within maxAge. Entries with pending validation are never evicted to protect the two-version cache. Returns the list of evicted URLs (empty if none evicted or eviction disabled).

This method is called periodically by the event adapter to prevent unbounded memory growth when templates change and old URLs are no longer used.

func (*HTTPStore) Fetch

func (s *HTTPStore) Fetch(ctx context.Context, url string, opts FetchOptions, auth *AuthConfig) (string, error)

Fetch retrieves content from a URL, using cache if available.

On first call for a source declaration, this performs a synchronous HTTP fetch and caches the result. Subsequent calls with the same effective options and authentication return cached content immediately.

If the URL has a Delay > 0 in options, the caller is responsible for scheduling refreshes (typically done by the event adapter component).

Parameters:

  • ctx: Context for cancellation and timeout
  • url: The HTTP(S) URL to fetch
  • opts: Fetch options (timeout, retries, critical flag, delay for refresh)
  • auth: Optional authentication configuration

Returns:

  • Content string (empty if fetch failed and not critical)
  • Error if critical fetch fails

func (*HTTPStore) Get

func (s *HTTPStore) Get(url string) (string, bool)

Get returns the accepted content for a URL if it exists in cache. Returns empty string and false if not cached. Updates LastAccessTime to track usage for cache eviction.

func (*HTTPStore) GetDelay

func (s *HTTPStore) GetDelay(url string) time.Duration

GetDelay returns the configured delay for a URL. Returns 0 if URL not in cache or no delay configured.

func (*HTTPStore) GetEntry

func (s *HTTPStore) GetEntry(url string) *CacheEntry

GetEntry returns a copy of the cache entry for a URL. Returns nil if not cached.

func (*HTTPStore) GetForValidation

func (s *HTTPStore) GetForValidation(url string) (string, bool)

GetForValidation returns content for validation rendering. If pending content exists, returns pending; otherwise returns accepted. Updates LastAccessTime to track usage for cache eviction.

func (*HTTPStore) GetPendingURLs

func (s *HTTPStore) GetPendingURLs() []string

GetPendingURLs returns all URLs with pending content awaiting validation.

func (*HTTPStore) GetSource

func (s *HTTPStore) GetSource(url string, source SourceDescriptor) (string, bool)

GetSource returns accepted content only when it belongs to source.

func (*HTTPStore) GetSourceState

func (s *HTTPStore) GetSourceState(url string) (SourceState, bool)

GetSourceState returns the current authority and timer policy for a URL.

func (*HTTPStore) HasActiveLease

func (s *HTTPStore) HasActiveLease(url string) bool

HasActiveLease reports whether any cache currently leases the URL.

func (*HTTPStore) LoadFixture

func (s *HTTPStore) LoadFixture(url, content string)

LoadFixture loads a single HTTP fixture directly into the store as accepted content. This is used by validation tests to provide mock HTTP responses without making actual HTTP requests.

The fixture is stored directly as accepted content, bypassing the normal fetch and validation workflow.

func (*HTTPStore) NewActiveLeaseSet

func (s *HTTPStore) NewActiveLeaseSet() (*ActiveLeaseSet, ActiveLeaseToken, error)

NewActiveLeaseSet allocates an empty lease owner without publishing leases.

func (*HTTPStore) PinAcceptedSnapshot

func (s *HTTPStore) PinAcceptedSnapshot(token SnapshotToken) (ContentSnapshot, SourceState, bool)

PinAcceptedSnapshot returns and touches the exact accepted version identified by token.

func (*HTTPStore) PrepareInitial

func (s *HTTPStore) PrepareInitial(
	ctx context.Context,
	url string,
	state SourceState,
) (string, *InitialCandidate, error)

PrepareInitial fetches candidate bytes without exposing them through the cache.

func (*HTTPStore) PrepareInitialCandidatesAndVerifyObservations

func (s *HTTPStore) PrepareInitialCandidatesAndVerifyObservations(
	ctx context.Context,
	candidates []*InitialCandidate,
	observations []ObservationToken,
) (*PreparedInitialCandidateCommit, error)

PrepareInitialCandidatesAndVerifyObservations validates a commit without publishing it.

func (*HTTPStore) PrepareInitialSnapshot

func (s *HTTPStore) PrepareInitialSnapshot(
	ctx context.Context,
	url string,
	state SourceState,
) (ContentSnapshot, *InitialCandidate, error)

PrepareInitialSnapshot fetches candidate bytes without exposing them through shared accepted state and returns their exact lifecycle version.

func (*HTTPStore) PrepareStagedSnapshot

func (s *HTTPStore) PrepareStagedSnapshot(
	ctx context.Context,
	source *StagedSource,
) (ContentSnapshot, *InitialCandidate, error)

PrepareStagedSnapshot fetches against render-local source authority.

func (*HTTPStore) PrepareStagedSourcesAndVerifyObservationSets

func (s *HTTPStore) PrepareStagedSourcesAndVerifyObservationSets(
	ctx context.Context,
	sources []*StagedSource,
	candidates []*InitialCandidate,
	verificationOnly []ObservationToken,
	retained []ObservationToken,
) (*PreparedInitialCandidateCommit, error)

PrepareStagedSourcesAndVerifyObservationSets permits verification-only reads to be rebased while retained reads must survive the planned publication.

func (*HTTPStore) PrepareStagedSourcesAndVerifyObservationSetsWithActiveLeases

func (s *HTTPStore) PrepareStagedSourcesAndVerifyObservationSetsWithActiveLeases(
	ctx context.Context,
	sources []*StagedSource,
	candidates []*InitialCandidate,
	verificationOnly []ObservationToken,
	retained []ObservationToken,
	active *ActiveLeaseCommit,
) (*PreparedInitialCandidateCommit, error)

PrepareStagedSourcesAndVerifyObservationSetsWithActiveLeases also prepares one render cache's exact lease transition and relevant-change acknowledgment.

func (*HTTPStore) PrepareStagedSourcesAndVerifyObservationSetsWithActiveLeasesAndReplayEpoch

func (s *HTTPStore) PrepareStagedSourcesAndVerifyObservationSetsWithActiveLeasesAndReplayEpoch(
	ctx context.Context,
	sources []*StagedSource,
	candidates []*InitialCandidate,
	verificationOnly []ObservationToken,
	retained []ObservationToken,
	active *ActiveLeaseCommit,
	replay *ReplayEpoch,
) (*PreparedInitialCandidateCommit, error)

PrepareStagedSourcesAndVerifyObservationSetsWithActiveLeasesAndReplayEpoch also retains one complete HTTP render-root fence.

func (*HTTPStore) PrepareStagedSourcesAndVerifyObservationSetsWithActiveLeasesAndReplayState

func (s *HTTPStore) PrepareStagedSourcesAndVerifyObservationSetsWithActiveLeasesAndReplayState(
	ctx context.Context,
	sources []*StagedSource,
	candidates []*InitialCandidate,
	verificationOnly []ObservationToken,
	retained []ObservationToken,
	active *ActiveLeaseCommit,
	replayEpoch *ReplayEpoch,
	replayState *AcceptedReplayState,
) (*PreparedInitialCandidateCommit, error)

PrepareStagedSourcesAndVerifyObservationSetsWithActiveLeasesAndReplayState retains either a complete root fence or a selective accepted-input fence.

func (*HTTPStore) PrepareStagedSourcesAndVerifyObservations

func (s *HTTPStore) PrepareStagedSourcesAndVerifyObservations(
	ctx context.Context,
	sources []*StagedSource,
	candidates []*InitialCandidate,
	observations []ObservationToken,
) (*PreparedInitialCandidateCommit, error)

PrepareStagedSourcesAndVerifyObservations validates source and content publication.

func (*HTTPStore) PromotePending

func (s *HTTPStore) PromotePending(url string) bool

PromotePending promotes pending content to accepted for a URL. This should be called after successful validation.

func (*HTTPStore) PromotePendingVersion

func (s *HTTPStore) PromotePendingVersion(url, expectedChecksum string, expectedRevision uint64) bool

PromotePendingVersion promotes only the pending version that was validated.

func (*HTTPStore) ReconcileSource

func (s *HTTPStore) ReconcileSource(url string, opts FetchOptions, auth *AuthConfig) (SourceReconcileResult, error)

ReconcileSource makes one URL's effective fetch authority current.

func (*HTTPStore) RefreshURL

func (s *HTTPStore) RefreshURL(ctx context.Context, url string) (changed bool, err error)

RefreshURL fetches fresh content for a URL and stores it as pending.

This does NOT replace accepted content immediately. The caller must: 1. Trigger re-render with pending content (using GetForValidation) 2. On successful validation, call PromotePending 3. On failed validation, call RejectPending

Returns:

  • changed: true if content changed from accepted version
  • err: fetch error (nil if successful or 304 Not Modified)

func (*HTTPStore) RefreshURLVersion

func (s *HTTPStore) RefreshURLVersion(ctx context.Context, url string) (*PendingVersion, error)

RefreshURLVersion refreshes a URL and returns the exact pending version it created.

func (*HTTPStore) RefreshURLVersionForGeneration

func (s *HTTPStore) RefreshURLVersionForGeneration(
	ctx context.Context,
	url string,
	sourceGeneration uint64,
) (*PendingVersion, error)

RefreshURLVersionForGeneration refreshes only the matching source generation.

func (*HTTPStore) RejectPending

func (s *HTTPStore) RejectPending(url string) bool

RejectPending discards pending content for a URL. This should be called when validation fails.

func (*HTTPStore) RejectPendingVersion

func (s *HTTPStore) RejectPendingVersion(url, expectedChecksum string, expectedRevision uint64) bool

RejectPendingVersion rejects only the pending version that was validated.

func (*HTTPStore) ReplayWatermark

func (s *HTTPStore) ReplayWatermark() Revision

ReplayWatermark returns the render-relevant source, content, and pending epoch.

func (*HTTPStore) RestageSource

func (s *HTTPStore) RestageSource(source *StagedSource) (*StagedSource, error)

RestageSource captures the current authority for an earlier declaration.

func (*HTTPStore) RevisionSource

func (s *HTTPStore) RevisionSource() SourceID

RevisionSource returns the stable identity of this HTTPStore instance.

func (*HTTPStore) RevokeAcceptances

func (s *HTTPStore) RevokeAcceptances(sequence uint64) []RevokedContent

RevokeAcceptances restores the confirmed predecessor of unconfirmed versions up to sequence.

func (*HTTPStore) StageAcceptedReplayProof

func (s *HTTPStore) StageAcceptedReplayProof(
	proof *AcceptedReplayProof,
) (ContentSnapshot, *StagedSource, bool)

StageAcceptedReplayProof pins a proof only while every source field remains exact.

func (*HTTPStore) StageAcceptedSnapshot

func (s *HTTPStore) StageAcceptedSnapshot(
	token SnapshotToken,
) (ContentSnapshot, *StagedSource, bool)

StageAcceptedSnapshot pins accepted bytes without changing source or timer state.

func (*HTTPStore) StageCurrentAcceptedSnapshot

func (s *HTTPStore) StageCurrentAcceptedSnapshot(
	url string,
	descriptor SourceDescriptor,
) (ContentSnapshot, *StagedSource, bool)

StageCurrentAcceptedSnapshot pins the current accepted bytes and source authority.

func (*HTTPStore) StageSource

func (s *HTTPStore) StageSource(
	url string,
	opts FetchOptions,
	auth *AuthConfig,
) (*StagedSource, error)

StageSource captures a declaration without changing shared authority.

func (*HTTPStore) VerifyObservations

func (s *HTTPStore) VerifyObservations(tokens []ObservationToken) bool

VerifyObservations atomically verifies exact present and negative reads.

func (*HTTPStore) VerifyReplayEpoch

func (s *HTTPStore) VerifyReplayEpoch(epoch *ReplayEpoch) bool

VerifyReplayEpoch reports whether the complete HTTP render root is current.

func (*HTTPStore) VerifySnapshots

func (s *HTTPStore) VerifySnapshots(tokens []SnapshotToken) bool

VerifySnapshots atomically verifies that every accepted token is still current.

func (*HTTPStore) VerifyStagedSource

func (s *HTTPStore) VerifyStagedSource(source *StagedSource) bool

VerifyStagedSource reports whether the captured source authority is current.

func (*HTTPStore) Watermark

func (s *HTTPStore) Watermark() Revision

Watermark returns the current accepted-content semantic revision.

type InitialCandidate

type InitialCandidate struct {
	// contains filtered or unexported fields
}

InitialCandidate is content fetched for one unpopulated authoritative source.

func (*InitialCandidate) Content

func (c *InitialCandidate) Content() string

Content returns the candidate bytes used by its render.

func (*InitialCandidate) SnapshotToken

func (c *InitialCandidate) SnapshotToken() SnapshotToken

SnapshotToken returns the render-local version of this candidate.

func (*InitialCandidate) URL

func (c *InitialCandidate) URL() string

URL returns the candidate's source URL.

type ObservationToken

type ObservationToken struct {
	// contains filtered or unexported fields
}

ObservationToken identifies one exact present or negative content read.

func (*ObservationToken) Found

func (t *ObservationToken) Found() bool

Found reports whether accepted content existed for the declaration.

func (*ObservationToken) Revision

func (t *ObservationToken) Revision() Revision

Revision returns the exact observable content revision.

func (*ObservationToken) Valid

func (t *ObservationToken) Valid() bool

Valid reports whether the token can be verified by its source store.

func (*ObservationToken) Watermark

func (t *ObservationToken) Watermark() Revision

Watermark returns the journal position captured with the read.

type PendingVersion

type PendingVersion struct {
	Checksum string
	Revision uint64
}

PendingVersion identifies one pending content revision.

type PreparedInitialCandidateCommit

type PreparedInitialCandidateCommit struct {
	// contains filtered or unexported fields
}

PreparedInitialCandidateCommit retains exact store authority until Release.

func (*PreparedInitialCandidateCommit) Abort

func (c *PreparedInitialCandidateCommit) Abort()

Abort discards an unpublished commit and releases commit authority.

func (*PreparedInitialCandidateCommit) AbortPublication

func (c *PreparedInitialCandidateCommit) AbortPublication() error

AbortPublication rolls back a tentative publication and reports authentication failure.

func (*PreparedInitialCandidateCommit) CommitPublishedPublication

func (c *PreparedInitialCandidateCommit) CommitPublishedPublication() error

CommitPublishedPublication records a rollback-capable publication decision.

func (*PreparedInitialCandidateCommit) Planned

Planned returns the exact tokens Publish will create.

func (*PreparedInitialCandidateCommit) PlannedActiveLeases

PlannedActiveLeases returns the exact token and URL transitions Publish creates.

func (*PreparedInitialCandidateCommit) PlannedActiveReplayState

func (c *PreparedInitialCandidateCommit) PlannedActiveReplayState() (
	*AcceptedReplayState,
	bool,
)

PlannedActiveReplayState returns the authenticated selective replay state before publication.

func (*PreparedInitialCandidateCommit) PlannedHasActiveLease

func (c *PreparedInitialCandidateCommit) PlannedHasActiveLease(url string) (bool, error)

PlannedHasActiveLease reports whether the sealed replacement leases one URL.

func (*PreparedInitialCandidateCommit) PlannedPendingOverlay

func (c *PreparedInitialCandidateCommit) PlannedPendingOverlay() (*HTTPOverlay, error)

PlannedPendingOverlay freezes pending inputs from the authenticated sealed replacement.

func (*PreparedInitialCandidateCommit) PlannedSourceState

func (c *PreparedInitialCandidateCommit) PlannedSourceState(url string) (SourceState, bool, error)

PlannedSourceState returns one source from the authenticated sealed replacement.

func (*PreparedInitialCandidateCommit) PrepareAcceptedReplayState

func (c *PreparedInitialCandidateCommit) PrepareAcceptedReplayState(
	snapshots []ContentSnapshot,
) (*AcceptedReplayState, error)

PrepareAcceptedReplayState authenticates the exact post-publication accepted inputs.

func (*PreparedInitialCandidateCommit) PreparePublishedReplayActiveLeases

func (c *PreparedInitialCandidateCommit) PreparePublishedReplayActiveLeases(
	active *ActiveLeaseCommit,
	snapshots []ContentSnapshot,
) error

PreparePublishedReplayActiveLeases binds the post-publication accepted inputs to a lease root.

func (*PreparedInitialCandidateCommit) Publish

func (c *PreparedInitialCandidateCommit) Publish()

Publish accepts the prepared candidates without a fallible step.

func (*PreparedInitialCandidateCommit) PublishSealed

func (c *PreparedInitialCandidateCommit) PublishSealed()

PublishSealed completes a publication returned successfully from SealPublication.

func (*PreparedInitialCandidateCommit) Release

func (c *PreparedInitialCandidateCommit) Release()

Release makes the published store state visible and releases commit authority.

func (*PreparedInitialCandidateCommit) ReleaseCommittedPublication

func (c *PreparedInitialCandidateCommit) ReleaseCommittedPublication()

ReleaseCommittedPublication exposes committed state and releases retained authority.

func (*PreparedInitialCandidateCommit) ReleasePublication

func (c *PreparedInitialCandidateCommit) ReleasePublication() error

ReleasePublication makes the published store state visible and reports authentication failure.

func (*PreparedInitialCandidateCommit) SealPublication

func (c *PreparedInitialCandidateCommit) SealPublication() error

SealPublication authenticates every terminal value while the store lock is retained.

func (*PreparedInitialCandidateCommit) ValidatePublication

func (c *PreparedInitialCandidateCommit) ValidatePublication() error

ValidatePublication verifies the retained terminal publication without changing store state.

func (*PreparedInitialCandidateCommit) ValidatePublishedPublication

func (c *PreparedInitialCandidateCommit) ValidatePublishedPublication() error

ValidatePublishedPublication authenticates tentative live state without releasing authority.

type ReplayChange

type ReplayChange struct {
	Revision Revision
	URL      string
	// contains filtered or unexported fields
}

ReplayChange identifies one URL whose render-observable state changed.

type ReplayEpoch

type ReplayEpoch struct {
	// contains filtered or unexported fields
}

ReplayEpoch binds the complete render-relevant state of one HTTP store.

func (*ReplayEpoch) Revision

func (e *ReplayEpoch) Revision() Revision

Revision returns the complete render-relevant revision.

func (*ReplayEpoch) Source

func (e *ReplayEpoch) Source() SourceID

Source returns the store instance identity.

func (*ReplayEpoch) ValidateAuthentication

func (e *ReplayEpoch) ValidateAuthentication() error

ValidateAuthentication verifies that the epoch retains its minted identity.

type Revision

type Revision uint64

Revision is a monotonic semantic revision within one HTTPStore.

type RevokedContent

type RevokedContent struct {
	URL      string
	Checksum string
	// Restored is true when the URL went back to the version it had before;
	// false when it has no accepted content again.
	Restored bool
	// Critical is the source's failure mode: a render without it fails.
	Critical bool
}

RevokedContent describes one revoked acceptance.

type SemanticChange

type SemanticChange struct {
	Revision               Revision
	URL                    string
	PreviousSourceIdentity string
	SourceIdentity         string
	PreviousDescriptor     SourceDescriptor
	Descriptor             SourceDescriptor
	Removed                bool
	// contains filtered or unexported fields
}

SemanticChange identifies an accepted-content or source-authority transition.

type SnapshotKind

type SnapshotKind uint8

SnapshotKind distinguishes shared accepted content from render-local versions.

const (
	SnapshotAccepted SnapshotKind = iota + 1
	SnapshotPending
	SnapshotInitialCandidate
)

type SnapshotToken

type SnapshotToken struct {
	// contains filtered or unexported fields
}

SnapshotToken identifies the exact bytes and source returned by one store read.

func (SnapshotToken) Kind

func (t SnapshotToken) Kind() SnapshotKind

Kind returns which content lifecycle supplied the bytes.

func (SnapshotToken) Revision

func (t SnapshotToken) Revision() Revision

Revision returns the content version within the store.

func (SnapshotToken) Source

func (t SnapshotToken) Source() SourceID

Source returns the store instance that minted this token.

func (SnapshotToken) SourceDescriptor

func (t SnapshotToken) SourceDescriptor() SourceDescriptor

SourceDescriptor returns the exact opaque fetch declaration.

func (SnapshotToken) SourceIdentity

func (t SnapshotToken) SourceIdentity() string

SourceIdentity returns the opaque effective options and authentication identity.

func (SnapshotToken) URL

func (t SnapshotToken) URL() string

URL returns the exact requested URL.

func (SnapshotToken) Valid

func (t SnapshotToken) Valid() bool

Valid reports whether the token identifies a cacheable snapshot.

type SourceDescriptor

type SourceDescriptor struct {
	// contains filtered or unexported fields
}

SourceDescriptor is an opaque, exactly comparable fetch declaration.

func DescribeSource

func DescribeSource(opts FetchOptions, auth *AuthConfig) (SourceDescriptor, error)

DescribeSource returns an opaque declaration with exact structural equality.

func (SourceDescriptor) Compare

func (d SourceDescriptor) Compare(other SourceDescriptor) int

Compare orders descriptors by diagnostic identity and exact canonical declaration.

func (SourceDescriptor) Format

func (d SourceDescriptor) Format(state fmt.State, _ rune)

Format prevents diagnostic formatting from exposing the canonical declaration.

func (SourceDescriptor) Identity

func (d SourceDescriptor) Identity() string

Identity returns the non-secret diagnostic identity of this declaration.

func (SourceDescriptor) String

func (d SourceDescriptor) String() string

String returns the non-secret identity.

type SourceID

type SourceID uint64

SourceID identifies one HTTPStore for its lifetime. Zero is unsupported.

type SourceReconcileResult

type SourceReconcileResult struct {
	State   SourceState
	Changed bool
}

SourceReconcileResult describes an authoritative source transition.

type SourceState

type SourceState struct {
	Identity    string
	Descriptor  SourceDescriptor
	Generation  uint64
	Delay       time.Duration
	HasAccepted bool
}

SourceState identifies the current authority and refresh policy for a URL.

type StagedSource

type StagedSource struct {
	// contains filtered or unexported fields
}

StagedSource is one render-local authoritative source declaration.

func (*StagedSource) Changed

func (s *StagedSource) Changed() bool

Changed reports whether publication replaces the current source authority.

func (*StagedSource) Critical

func (s *StagedSource) Critical() bool

Critical reports whether the declaration fails the render when it has no content.

func (*StagedSource) Descriptor

func (s *StagedSource) Descriptor() SourceDescriptor

Descriptor returns the exact effective fetch declaration.

func (*StagedSource) RefreshInterval

func (s *StagedSource) RefreshInterval() time.Duration

RefreshInterval returns the declared refresh interval; zero means none.

func (*StagedSource) State

func (s *StagedSource) State() SourceState

State returns the render-local source state.

func (*StagedSource) URL

func (s *StagedSource) URL() string

URL returns the declared URL.

type StatusSet

type StatusSet struct {
	// contains filtered or unexported fields
}

StatusSet is a set of extra HTTP statuses whose response body is content. It holds its sorted codes as one string so FetchOptions stays small and comparable, and equal sets compare equal however they were built.

func NewStatusSet

func NewStatusSet(codes ...int) (StatusSet, error)

NewStatusSet validates codes and returns their set. 200 is always content, so listing it is a no-op.

func (StatusSet) Codes

func (s StatusSet) Codes() []int

Codes returns the statuses in ascending order.

func (StatusSet) Contains

func (s StatusSet) Contains(code int) bool

Contains reports whether code is in the set.

func (StatusSet) MarshalJSON

func (s StatusSet) MarshalJSON() ([]byte, error)

MarshalJSON encodes the set as its sorted status list.

type ValidationState

type ValidationState int

ValidationState represents the current validation state of a cached entry.

const (
	// StateAccepted means the accepted content is in use, no pending content.
	StateAccepted ValidationState = iota

	// StateValidating means pending content exists and is being validated.
	StateValidating

	// StateRejected means the last pending content was rejected, using accepted.
	StateRejected
)

func (ValidationState) String

func (s ValidationState) String() string

String returns a string representation of the validation state.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL