Documentation
¶
Overview ¶
Package pipeline is cadish's request-evaluation engine: it compiles a parsed Cadishfile site (the semantics-free AST from internal/cadishfile) into an executable Pipeline and evaluates each request through the cache lifecycle.
This package is PURE: it performs no network I/O, starts no goroutines, and owns no listeners. It turns a Request plus a few scalars (response status, cache-lookup outcome) into DECISIONS — structured values that the server layer (a later milestone) applies to net/http and the cache/origin packages. The split mirrors the request lifecycle:
EvalRequest (RECV + KEY) -> respond / purge / route / pass / cache_key / req headers EvalResponse (ORIGIN/store)-> cache_ttl / storage (needs the response status) EvalDeliver (DELIVER) -> resp headers / strip_cookies / cors / cache-status
Compile validates the directives once (returning errors that carry the offending source Pos) and compiles every matcher a single time; the per-request Eval methods are allocation-light and safe for concurrent use (a *Pipeline is immutable after Compile).
Index ¶
- Constants
- func CacheKeyHash(rawKey string) string
- func CacheKeyHeaderValue(rawKey string, raw bool) string
- func FileImportResolver(baseDir string) func(string) ([]cadishfile.Node, error)
- func IsRedirectCodeToken(raw string) bool
- func LenientCookieValue(h http.Header, name string) string
- func NormalizeHost(host string) string
- func NormalizePath(p string) string
- func ParseDuration(s string) (time.Duration, error)
- func ServerOnlyMatcherKeywords() map[string]bool
- func SpliceImports(site *cadishfile.Site, resolve func(path string) ([]cadishfile.Node, error)) (*cadishfile.Site, error)
- type CORSDecision
- type CORSView
- type CacheStatus
- type ClassifierView
- type ClassifyRowView
- type CompileError
- type DeliverDecision
- type DeployView
- type DeviceClassifierView
- type DeviceFoldView
- type DeviceRuleView
- type DirectivePhase
- type DirectiveSpec
- type EdgeClass
- type EncodeDecision
- type HeaderOp
- type HeaderOpKind
- type HeaderOpView
- type HeaderRuleView
- type KeyRecipeView
- type KeyTokenView
- type MatcherSpec
- type MatcherView
- type NormalizerView
- type OnError
- type OnErrorView
- type Pipeline
- func (p *Pipeline) Addresses() []string
- func (p *Pipeline) BypassForCredentials(req *Request) bool
- func (p *Pipeline) CacheCredentialedMatches(req *Request) bool
- func (p *Pipeline) DeviceClassifier() *classify.Classifier
- func (p *Pipeline) DeviceClassifierView() (DeviceClassifierView, bool)
- func (p *Pipeline) EvalDeliver(req *Request, respHeader http.Header, cacheStatus CacheStatus) DeliverDecision
- func (p *Pipeline) EvalDeliverUpstream(req *Request, respHeader http.Header, cacheStatus CacheStatus, upstream string) DeliverDecision
- func (p *Pipeline) EvalOnError(req *Request, status int) *OnError
- func (p *Pipeline) EvalRequest(req *Request) RequestDecision
- func (p *Pipeline) EvalResponse(req *Request, status int, respHeader http.Header) ResponseDecision
- func (p *Pipeline) EvalSecurity(req *Request) SecurityDecision
- func (p *Pipeline) FilterRequestCookies(raw string) (string, bool)
- func (p *Pipeline) HasCacheCredentialed() bool
- func (p *Pipeline) HasDerivesFrom() bool
- func (p *Pipeline) HasOnError() bool
- func (p *Pipeline) HasScopedCacheKey() bool
- func (p *Pipeline) IgnoreClientRevalidation() bool
- func (p *Pipeline) NeedsPoolHealth() bool
- func (p *Pipeline) SelectedDerivedForwardCookies(req *Request) []string
- func (p *Pipeline) SelectedDerivedStripCookies(req *Request) []string
- func (p *Pipeline) StripDerivedCookies(req *Request) bool
- func (p *Pipeline) Tenant() string
- func (p *Pipeline) UsesDeviceClassification() bool
- func (p *Pipeline) UsesDeviceToken() bool
- func (p *Pipeline) UsesGeoToken() bool
- func (p *Pipeline) UsesIPMatcher() bool
- func (p *Pipeline) UsesRateLimit() bool
- func (p *Pipeline) UsesSecurityGate() bool
- func (p *Pipeline) ViewBypassPasses() bool
- func (p *Pipeline) ViewBypassPatterns() []string
- func (p *Pipeline) ViewCORSRule() (CORSView, bool)
- func (p *Pipeline) ViewCacheUnsafe() bool
- func (p *Pipeline) ViewCanonicalHost() string
- func (p *Pipeline) ViewClassifiers() map[string]ClassifierView
- func (p *Pipeline) ViewCookieAllow() ([]string, bool)
- func (p *Pipeline) ViewCredentialedRules() []ScopeView
- func (p *Pipeline) ViewDefaultTier() string
- func (p *Pipeline) ViewDefaultUpstream() string
- func (p *Pipeline) ViewDeployConfig() DeployView
- func (p *Pipeline) ViewHasEncode() bool
- func (p *Pipeline) ViewHosts() []string
- func (p *Pipeline) ViewKVMaxBytes() int64
- func (p *Pipeline) ViewKVTTL() (time.Duration, bool)
- func (p *Pipeline) ViewKeyHeaderNames() []string
- func (p *Pipeline) ViewKeyRecipes() []KeyRecipeView
- func (p *Pipeline) ViewKeyTokens() []KeyTokenView
- func (p *Pipeline) ViewMatchers() map[string]MatcherView
- func (p *Pipeline) ViewNormalizers() map[string]NormalizerView
- func (p *Pipeline) ViewOnErrorRules() []OnErrorView
- func (p *Pipeline) ViewOnErrorScopes() []ScopeView
- func (p *Pipeline) ViewPassRules() []ScopeView
- func (p *Pipeline) ViewPurgeRules() []PurgeView
- func (p *Pipeline) ViewRedirectHosts() []string
- func (p *Pipeline) ViewRedirectRules() []RedirectView
- func (p *Pipeline) ViewReqHeaderRules() []HeaderRuleView
- func (p *Pipeline) ViewRespHeaderRules() []HeaderRuleView
- func (p *Pipeline) ViewRespondRules() []RespondView
- func (p *Pipeline) ViewRewriteScopes() []ScopeView
- func (p *Pipeline) ViewRouteRules() []RouteView
- func (p *Pipeline) ViewScopedRespondCount() int
- func (p *Pipeline) ViewStickyCookie() string
- func (p *Pipeline) ViewStorageRules() []StorageView
- func (p *Pipeline) ViewStripRules() []ScopeView
- func (p *Pipeline) ViewTTLRules() []TTLView
- func (p *Pipeline) ViewTenantConstant() string
- func (p *Pipeline) ViewTenantResolver() (TenantView, bool)
- func (p *Pipeline) ViewTierPolicies() []TierPolicyView
- func (p *Pipeline) ViewTransformRules() []TransformView
- func (p *Pipeline) ViewUpgradeScopes() []ScopeView
- func (p *Pipeline) ViewUsesKV() bool
- type PurgeDecision
- type PurgeView
- type RateLimitHit
- type Redirect
- type RedirectView
- type Replacement
- type Request
- type RequestDecision
- type RespondView
- type ResponseDecision
- type RewriteDecision
- type RouteView
- type ScopeView
- type SecurityDecision
- type SetupLowering
- type StorageView
- type SubMatcherView
- type Synthetic
- type TTLView
- type TemplateEnv
- type TenantMapView
- type TenantView
- type TierPolicyView
- type TransformView
Constants ¶
const TransformMaxBytesView int64 = 1 << 20
TransformMaxBytesView is the body-size ceiling the edge worker buffers to apply a `replace` body transform (D75). It mirrors the server's maxTransformBody (1 MiB, internal/server/transform.go): a body at or below it is transformed; a larger one passes through untransformed (the over-cap path stays a permanent server-only non-goal). Kept here (not imported from internal/server) to avoid an import cycle; both constants are 1<<20 and a comment in each points at the other.
Variables ¶
This section is empty.
Functions ¶
func CacheKeyHash ¶
CacheKeyHash returns the first 12 hex chars of sha256(rawKey) — the value `header +cache_key NAME` (hash form, the default) emits. It is a pure function of the already-built cache key, so the Go server and the JS edge runtime produce an IDENTICAL hash for the same key (a conformance fixture asserts this). An empty key yields an empty string (a request with no key omits the header entirely).
func CacheKeyHeaderValue ¶
CacheKeyHeaderValue resolves the value `header +cache_key NAME[ raw]` emits for a request whose computed cache key is rawKey: the raw key string when raw is set, else its 12-hex hash. An empty rawKey (pass/synthetic/redirect — no key) yields "" so the caller omits the header.
func FileImportResolver ¶
func FileImportResolver(baseDir string) func(string) ([]cadishfile.Node, error)
FileImportResolver returns an import resolver that reads fragments from baseDir (relative imports) or absolute paths, parses them, and returns their body nodes. It is a convenience for the server and tests; it performs filesystem I/O and is therefore deliberately separate from Compile.
The resolver supports two behaviors beyond a single literal path:
- Globs: a path containing filepath.Match metacharacters (`* ? [`) splices every matching file in sorted (lexical) order. A glob that matches NO files is a clear error, never a silent empty splice.
- Transitive imports: an imported fragment may itself contain `import` directives; those are resolved recursively. A cycle (a file that imports itself, directly or indirectly) is reported as "import cycle: …" rather than looping forever or leaving a leftover `import` for Compile to choke on.
func IsRedirectCodeToken ¶ added in v0.2.3
IsRedirectCodeToken is the exported view of isRedirectCodeToken so the check cost catalog can consume the parser's own scoped-redirect disambiguation instead of maintaining a hand-kept mirror of the 3xx code set (CAD-58: reduce dual-parser drift).
func LenientCookieValue ¶
LenientCookieValue returns the named cookie's value parsed with cadish's lenient cookie rules (see lenientCookies) — the value the origin and the gate see, NOT net/http's strict parser (which drops a JSON/quoted value). Exported so the server's sticky-LB affinity reader reads cookies the same way as the rest of cadish.
func NormalizeHost ¶
NormalizeHost is the exported canonical host normalization (lower-case, strip :port, strip an FQDN trailing dot) — the SAME function the `host` cache-key token and the host matchers use. The server uses it to forward the canonical host to the origin (host_header preserve), so the origin sees exactly the host the cache key captured: keying on the normalized host while forwarding the raw `Host: example.com:1337` let an attacker poison the `example.com` entry with a response generated for a port/case variant a Host-reflecting origin treats differently (cache poisoning). One reader = no divergence.
func NormalizePath ¶ added in v0.2.3
NormalizePath canonicalizes a client request path so the path the pipeline MATCHES (deny/ACL, path matchers, the `path` cache_key token) is the SAME path the origin is dialed with, AND so the Cadish Edge worker — which runs the IDENTICAL normalization in its request build (edge/runtime/interpreter.js newRequest) — computes a byte-identical cache key and path-ACL decision (F10). It strips ASCII control bytes (0x00-0x1f, 0x7f) first (Fix #8: no control byte may appear in a cache key, which joins tokens with 0x1f), then applies path.Clean (collapse "//"→"/", resolve "/../" and "/./"), re-appending a meaningful trailing slash (directory vs. file) and mapping the empty path to "/".
The Go server (handler.go) and the conformance harness both call this, so the normalization has ONE authoritative home shared with the edge. Percent-encoding is unaffected: the caller passes an already-decoded path (net/http on the server, decodeURIComponent at the edge), and the query string is untouched, so the by-design "percent-encoded paths key to distinct entries" behavior is preserved.
func ParseDuration ¶
ParseDuration parses a cadish duration. It is the single source of truth for duration syntax across the whole project: the pipeline compiler uses it for `cache_ttl … ttl/grace/hit_for_miss`, and `config`/`check` reuse it (via the thin `config.ParseDuration` wrapper) for every other duration-valued directive — so a value that loads at runtime also lints clean, and vice versa.
It extends Go's time.ParseDuration with day ("d") and week ("w") units, which the canonical configs use (e.g. "grace 365d", "grace 24h", "ttl 60s"). Compound forms ("1d12h", "1h30m") are accepted. Bare zero ("0") is accepted as a zero duration.
Supported units: ns, us (µs), ms, s, m, h, d (=24h), w (=7d).
func ServerOnlyMatcherKeywords ¶ added in v0.2.3
ServerOnlyMatcherKeywords returns the set of matcher-kind names (as they appear in the edge IR) that have NO edge runtime analogue: the projector marks them ServerOnly and delegates every referencing directive, and the worker fails CLOSED. edgeir derives its serverOnlyEdgeKinds from this, keeping the two in lockstep (a guard asserts equality).
func SpliceImports ¶
func SpliceImports(site *cadishfile.Site, resolve func(path string) ([]cadishfile.Node, error)) (*cadishfile.Site, error)
SpliceImports returns a shallow copy of site with every `import PATH` directive replaced, in source order, by the body nodes of the resolved fragment. resolve reads and parses PATH (resolving any NESTED imports transitively) and returns its statement nodes. This is the only place import I/O happens; Compile itself stays pure (a leftover import is an error). A self/cyclic import surfaces here as a positioned "import cycle: …" error, never a leaked internal compile message.
Types ¶
type CORSDecision ¶
type CORSDecision struct {
// AllowAllOrigins is true for `cors *` (Access-Control-Allow-Origin: *).
AllowAllOrigins bool
// Origins is the explicit allowed-origin list (when not AllowAllOrigins).
Origins []string
// Methods is the Access-Control-Allow-Methods list, if specified.
Methods []string
// Headers is the Access-Control-Allow-Headers list, if specified.
Headers []string
}
CORSDecision carries the values for the CORS response headers requested by a `cors` directive.
type CORSView ¶ added in v0.2.3
type CORSView struct {
Scope ScopeView
AllowAllOrigins bool
Origins []string
Methods []string
Headers []string
}
CORSView is the neutral view of a cors rule.
type CacheStatus ¶
type CacheStatus int
CacheStatus is the cache-lookup outcome the server feeds into EvalDeliver so the `header +cache_status` special can emit the right token.
const ( // CacheStatusUnknown is the zero value (renders as "MISS"). CacheStatusUnknown CacheStatus = iota // CacheStatusHit: a fresh object served from cache. CacheStatusHit // CacheStatusMiss: not in cache (or pass); fetched from origin. CacheStatusMiss // CacheStatusHitStale: a stale object served from grace while revalidating. CacheStatusHitStale // CacheStatusHitStaleError (D60): a past-grace object served as a last resort // because the origin fetch FAILED and the object is still within its max_stale // window. Distinct from CacheStatusHitStale (live grace, origin health // irrelevant) so operators can tell "served stale to hide latency" from "served // stale because the origin is down." CacheStatusHitStaleError )
func (CacheStatus) String ¶
func (c CacheStatus) String() string
String renders the cache status as the header token emitted by `header +cache_status`.
type ClassifierView ¶ added in v0.2.3
type ClassifierView struct {
Rows []ClassifyRowView
Default string
Synthetic map[string]MatcherView
// DerivesFrom names the request cookies this axis consumes (`derives_from cookie
// NAME…`). The edge worker keeps them through cookie_allow so the classifier reads
// the original value and the key is built from it, then strips them post-key before
// the credential bypass + origin fetch — the SAME derive→strip the server performs,
// so the two runtimes collapse cardinality identically. nil when none declared.
DerivesFrom []string
// DerivesForward is the SUBSET of DerivesFrom declared `forward` (alias `keep`): the
// worker FORWARDS these to origin unchanged (does NOT strip) and treats them as covered
// by {TOKEN} in the credential bypass — the same forward-mode the server applies, so the
// edge and origin agree. nil when no row uses the modifier.
DerivesForward []string
}
ClassifierView is the neutral view of a classify table: ordered rows (each an AND-conjunction of matcher names) plus the default value.
Synthetic carries the matchers SYNTHESIZED for inline (unnamed) matchers in `when` rows, keyed by the synthetic name that appears in the rows' Conj. The pipeline's own classifier resolves an inline matcher by object identity; the edge IR has only the matcher map, so an inline matcher must be given a name and emitted (BUG-2) — otherwise its Conj entry would be the empty string the runtime can never satisfy. The projector merges Synthetic into EdgeIR.Matchers.
type ClassifyRowView ¶ added in v0.2.3
ClassifyRowView is one `when @a @b -> VALUE` row: the matcher names (AND) and the value. An inline matcher in the row contributes a SYNTHETIC name here (see ClassifierView.Synthetic) rather than an empty string.
type CompileError ¶
type CompileError struct {
Pos cadishfile.Pos
Msg string
}
CompileError is a configuration error discovered while compiling a site into a Pipeline. It carries the offending source position.
func (*CompileError) Error ¶
func (e *CompileError) Error() string
type DeliverDecision ¶
type DeliverDecision struct {
// RespHeaderOps are response-header edits to apply, in order. A matched
// `header +cache_status NAME` is materialized here as a concrete set op writing
// the CacheStatus token into NAME, so the server can apply every op uniformly.
RespHeaderOps []HeaderOp
// StripCookies is true when a matching `strip_cookies` rule fired: the server
// drops Set-Cookie (and request Cookie) for this response.
StripCookies bool
// CORS, when non-nil, carries the CORS headers a `cors` directive requests.
CORS *CORSDecision
// CacheStatusHeader is the header name a `header +cache_status` directive
// targets (e.g. "X-Cache"), or "" if none. Provided in addition to the
// materialized RespHeaderOps entry for servers that handle it specially.
CacheStatusHeader string
// CacheKeyHeader is the header name a `header +cache_key` directive targets
// (e.g. "X-Cache-Key"), or "" if none. Unlike +cache_status the value is NOT in
// this decision — the cache key is the server-held RECV key — so the server's
// deliver path sets the header from the key it already holds, hashing it
// (CacheKeyHash) unless CacheKeyRaw. The header is omitted when the request has
// no key (pass/synthetic/redirect). See CacheKeyHeaderValue.
CacheKeyHeader string
// CacheKeyRaw selects the raw key string (the `raw` modifier) instead of the
// default 12-hex hash for CacheKeyHeader. Meaningful only when CacheKeyHeader is
// set.
CacheKeyRaw bool
// CacheAgeHeader is the header name a `header +cache_age` directive targets
// (e.g. "X-Cache-Age"), or "" if none. Like CacheKeyHeader the value (object age
// in whole seconds) is NOT in this decision — it is derived from the freshness
// index storedAt timestamp — so the server and edge worker materialize it
// separately on HIT/HIT-STALE, and OMIT it on MISS/bypass.
CacheAgeHeader string
// CacheTTLRemainingHeader is the header name a `header +cache_ttl_remaining`
// directive targets (e.g. "X-TTL"), or "" if none. Like CacheAgeHeader the value
// (remaining fresh TTL in whole seconds — freshUntil − now, floored at 0) is NOT in
// this decision — it is derived from the freshness index (storedAt + assigned TTL) —
// so the server and edge worker materialize it separately on HIT/HIT-STALE (a
// stale-in-grace HIT reports 0), and OMIT it on MISS/bypass. The Varnish X-TTL
// complement to CacheAgeHeader's X-Age.
CacheTTLRemainingHeader string
// Transforms are ordered literal body substitutions (`replace OLD NEW`) whose
// scope matched this response. The server applies them POST-cache, on delivery,
// to a size-bounded body — the cache always stores the canonical origin body,
// so transforms run per-delivery on both HIT and MISS. Empty when none apply.
Transforms []Replacement
// Encode, when non-nil, requests on-the-fly response-body compression
// (`encode`) at deliver. It carries the configured codec preference order,
// the Content-Type include list, and the minimum body size. The server
// negotiates against the client's Accept-Encoding and compresses POST-cache,
// AFTER any Transforms. The cache stores the uncompressed origin body, so
// compression runs per-delivery. nil when the site declares no `encode`.
Encode *EncodeDecision
}
DeliverDecision is the outcome of the DELIVER phase (EvalDeliver), applied to the response just before it is written to the client.
type DeployView ¶ added in v0.2.3
type DeployView struct {
Account string
Zone string
Worker string
Routes []string
KVNamespace string
Configured bool // true iff the site declared an `edge {}` block
}
DeployView is the neutral view of the `edge {}` block's Cloudflare deploy identity. HasKV reports whether an L2 KV namespace is configured (or implied by a distribute policy / default).
type DeviceClassifierView ¶ added in v0.2.3
type DeviceClassifierView struct {
Rules []DeviceRuleView
Default string
Folds []DeviceFoldView
}
DeviceClassifierView is the neutral view of the {device} UA classifier: an ordered ruleset (first match wins) + a default class + optional folds. The worker ports the identical scan (substring contains, OR over substrings, NONE of excludes, then fold) so the same User-Agent yields the same {device} bucket Go↔JS (D70).
type DeviceFoldView ¶ added in v0.2.3
DeviceFoldView is one class remap applied after rule matching (FROM -> INTO).
type DeviceRuleView ¶ added in v0.2.3
DeviceRuleView is the neutral view of one UA→class rule (substrings/excludes already lower-cased), evaluated first-match-wins by the worker.
type DirectivePhase ¶ added in v0.2.3
type DirectivePhase string
DirectivePhase is the lifecycle phase a directive runs in. It is the canonical, pipeline-owned phase (pipeline is what EXECUTES the directive), from which check.directivePhase derives its per-directive cost phase.
const ( // DirectiveSetup: parse-once configuration (tls, cache, upstream, the global blocks). // No per-request cost. DirectiveSetup DirectivePhase = "setup" // DirectiveRECV: the request phase, before the cache key / lookup / origin (respond, // route, pass, the security gate, cookie_allow, …). DirectiveRECV DirectivePhase = "recv" // DirectiveKEY: builds the cache key (cache_key). DirectiveKEY DirectivePhase = "key" // DirectiveORIGIN: runs on a miss against the origin response (cache_ttl, storage). DirectiveORIGIN DirectivePhase = "origin" // DirectiveDELIVER: the response phase (header, strip_cookies, cors, replace, encode). DirectiveDELIVER DirectivePhase = "deliver" )
type DirectiveSpec ¶ added in v0.2.3
type DirectiveSpec struct {
// Name is the directive keyword. The full set MUST equal cadishfile.DefaultDirectives
// (asserted by TestDirectiveTableMatchesCatalog).
Name string
// Phase is the lifecycle phase the directive runs in (check derives its cost phase).
Phase DirectivePhase
// ArgForm is a one-line human hint of the argument shape (for docs / diagnostics).
ArgForm string
// DocsAnchor is the slug of the directive's section in docs/cadishfile-reference.md.
DocsAnchor string
}
DirectiveSpec is one row of the directive catalog. It carries the metadata that used to be duplicated across compile.go, check/catalog.go and the config layer. Whether the directive is GLOBAL-ONLY is NOT stored here — it is DERIVED from the authoritative cadishfile.GlobalBlockDirectives allowlist (see Global), so the two can never drift.
func DirectiveSpecs ¶ added in v0.2.3
func DirectiveSpecs() []DirectiveSpec
DirectiveSpecs returns a copy of the directive catalog for external consumers (check derives its directivePhase map from it; docs tooling reads it).
func LookupDirectiveSpec ¶ added in v0.2.3
func LookupDirectiveSpec(name string) (DirectiveSpec, bool)
LookupDirectiveSpec returns the spec for a directive name.
func (DirectiveSpec) Global ¶ added in v0.2.3
func (s DirectiveSpec) Global() bool
Global reports whether the directive is a GLOBAL-ONLY block (read from the leading options block; rejected inside a site body). It is derived from the single-sourced cadishfile.GlobalBlockDirectives allowlist so this table never re-enumerates the set.
type EdgeClass ¶ added in v0.2.3
type EdgeClass string
EdgeClass classifies whether a matcher kind can be faithfully evaluated by the Cadish Edge worker's JavaScript runtime, or has no edge analogue and must be delegated to the Cadish server behind (fail-closed at the edge).
const ( // EdgeNative: the kind is a pure function of request/response data the worker already // parses; the projector emits it and the JS matchOne evaluates it 1:1 with the server. EdgeNative EdgeClass = "native" // EdgeServerOnly: the kind resolves state that only exists on the Cadish server (live // lb-pool health, the trusted-proxy real client IP). The projector marks it ServerOnly // and DELEGATES every directive that references it; the worker fails CLOSED. This is the // set edgeir.serverOnlyEdgeKinds derives from (kept in lockstep by a guard test). EdgeServerOnly EdgeClass = "server-only" )
type EncodeDecision ¶
type EncodeDecision struct {
// Codecs is the configured codec preference order (e.g. ["zstd","br","gzip"]).
// The server picks the first one the client accepts. Each token is a wire
// Content-Encoding name ("gzip", "br", "zstd").
Codecs []string
// Types is the Content-Type include list (lower-cased media types; a trailing
// "/*" matches a whole top-level type, e.g. "text/*"). A response whose
// Content-Type is not in this list is served uncompressed.
Types []string
// MinLength is the body-size floor (bytes): bodies shorter than this are not
// worth compressing and are served uncompressed.
MinLength int
}
EncodeDecision carries the compiled `encode` policy surfaced on the deliver decision. It is the immutable plan the server negotiates and applies at delivery; it does not itself decide whether to compress a given response (the server gates on Accept-Encoding, Range/HEAD, existing Content-Encoding, Content-Type, and MinLength — see the server's encodeApplies).
type HeaderOp ¶
type HeaderOp struct {
Op HeaderOpKind
Name string
Value string
// ValueTpl is set at compile time when Value contains a template placeholder
// ({http.NAME} / {client_ip} / {host} / …) and so must be expanded against the
// request before the op is emitted (dynamic header values, #17). It is false for
// a plain literal value, which is emitted verbatim with zero per-request work.
// The pipeline resolves the template (in applyHeaderRules); a HeaderOp surfaced
// in a decision always carries a fully-resolved Value, so the server applies it
// uniformly and never sees the template.
ValueTpl bool
}
HeaderOp is a single header edit to apply to a request or response.
type HeaderOpKind ¶
type HeaderOpKind int
HeaderOpKind is the kind of a header edit.
const ( // OpSet replaces (sets) the header to Value: `header NAME VALUE`. OpSet HeaderOpKind = iota // OpAppend adds a value without removing existing ones: `header +NAME VALUE`. OpAppend // OpRemove deletes the header: `header -NAME`. OpRemove // OpCacheStatus is the `header +cache_status NAME` special: at delivery the // engine materializes it into an OpSet writing the cache-status token (HIT / // MISS / HIT-STALE) into the header named by Name. It never appears in a // DeliverDecision (it is resolved away); it exists only in the compiled rules. OpCacheStatus // OpCacheKey is the `header +cache_key NAME [raw]` special: a delivery-only // debug header that emits the request's computed cache key into the header named // by Name — the 12-hex sha256 prefix by default, or the raw key string when // Value == "raw". Like OpCacheStatus the cache key is NOT in the deliver-phase // matchContext (it is the server-held key), so this op is NOT materialized into // an OpSet by applyHeaderRules; instead EvalDeliver surfaces the target name + // raw flag on the DeliverDecision (CacheKeyHeader/CacheKeyRaw) and the server // sets the header from the key it already holds. Value carries "raw" for the raw // form and is empty for the hash form. OpCacheKey // OpCacheAge is the `header +cache_age NAME` special: a delivery-only debug // header that emits the object's age in whole seconds (e.g. "45") under header // NAME on a cache HIT (fresh or stale). It is OMITTED on MISS and bypass (no // stored age). Like OpCacheKey the age is NOT in the deliver-phase matchContext // — it is derived from the freshness index / storedAt timestamp — so this op is // not materialized by applyHeaderRules; instead EvalDeliver surfaces the target // name on the DeliverDecision (CacheAgeHeader) and the server + edge worker // materialize the integer age from the object's storedAt time. OpCacheAge // OpCacheTTLRemaining is the `header +cache_ttl_remaining NAME` special: a // delivery-only debug header that emits the object's REMAINING fresh TTL in whole // seconds (freshUntil − now, floored at 0) under header NAME on a cache HIT (fresh // or stale) — the Varnish `X-TTL` complement to `+cache_age`'s `X-Age`. Like // OpCacheAge the value is NOT in the deliver-phase matchContext — it is derived from // the freshness index (storedAt + assigned TTL) — so this op is not materialized by // applyHeaderRules; EvalDeliver surfaces the target name on the DeliverDecision // (CacheTTLRemainingHeader) and the server + edge worker materialize the integer // remaining seconds from the object's stored TTL window. It is OMITTED on MISS and // bypass (no stored object); on a stale-in-grace HIT the remaining fresh TTL is 0. OpCacheTTLRemaining )
func (HeaderOpKind) String ¶
func (k HeaderOpKind) String() string
String renders the op kind (for debugging/tests).
type HeaderOpView ¶ added in v0.2.3
type HeaderOpView struct {
Op string // set|append|remove|cache_status
Name string
Value string
ValueIsTmpl bool // the Value carries a template placeholder (dynamic header value)
}
HeaderOpView is the neutral view of one header edit.
type HeaderRuleView ¶ added in v0.2.3
type HeaderRuleView struct {
Scope ScopeView
Ops []HeaderOpView
}
HeaderRuleView is a scoped group of header ops.
type KeyRecipeView ¶ added in v0.2.3
type KeyRecipeView struct {
Selector ScopeView
Tokens []KeyTokenView
}
KeyRecipeView is the neutral view of one scoped cache_key recipe: a request-phase selector scope plus the ordered token list it produces. The catch-all (`cache_key default …` or an unscoped line) has Always=true. The edge worker evaluates the recipes first-match-wins, exactly like the Go pipeline's selectKeyTokens loop, so a scoped site keys byte-identically on the edge (D70).
type KeyTokenView ¶ added in v0.2.3
type KeyTokenView struct {
// Kind is a stable string token name the JS interpreter switches on:
// method|host|path|url|query|query_allow|query_strip|header|sticky|device|geo|
// geo.continent|geo.region|normalize|classify|tenant|literal
Kind string
Arg string // header name (header), literal text (literal), constant tenant
Ref string // normalize/classify: the referenced definition name
Allow []string // query_allow: the param-name allowlist (exact + globs)
Deny []string // query_strip: the param-name denylist (exact + globs)
}
KeyTokenView is the neutral view of one cache_key token.
type MatcherSpec ¶ added in v0.2.3
type MatcherSpec struct {
// Keyword is the Cadishfile type spelling (`path`, `header_present`, …). It is the
// key matcherType() resolves; the full set MUST equal cadishfile.DefaultMatcherTypes
// (asserted by TestMatcherTableMatchesCatalog).
Keyword string
// EdgeKind is the matcher-kind name the projector emits into the edge IR and that
// matcherKindKeyword returns. It equals Keyword for every matcher EXCEPT
// `header_present`, which projects as a values-less `header` (the edge `header` case
// treats empty values as presence, so no new edge kind is needed).
EdgeKind string
// Inline reports whether the keyword is a PLAIN/INLINE matcher type — one matcherType()
// resolves and compileMatcher builds. It is false ONLY for the two definition-only forms
// (`classify`, `all`) whose `@name classify {…}` / `@name all @a @b` defs are compiled
// through the deferred resolver passes in Compile, never through compileMatcher. A false
// here is why `header classify …` is not accepted as an inline scope (isMatcherType is
// false for it) — matching the pre-table behavior exactly.
Inline bool
// Edge is the edge-projection class (native vs server-only/delegate).
Edge EdgeClass
// Response reports whether the kind evaluates against the ORIGIN RESPONSE rather than
// the request (content_type / resp_header / set_cookie). Such a matcher may scope only
// response/DELIVER directives; isResponsePhaseKind derives from this.
Response bool
// ArgForm is a one-line human hint of the argument shape (for docs / diagnostics).
ArgForm string
// contains filtered or unexported fields
}
MatcherSpec is one row of the matcher catalog: everything a consumer needs to know about a matcher keyword without a switch of its own.
func MatcherSpecs ¶ added in v0.2.3
func MatcherSpecs() []MatcherSpec
MatcherSpecs returns a copy of the matcher catalog for external consumers (check, docs tooling). The internal kind field is unexported, so callers see only the stable metadata.
type MatcherView ¶ added in v0.2.3
type MatcherView struct {
Kind string // matcher type keyword: path|path_regex|host|host_regex|header|method|upstream|content_type|cookie|cookie_json|header_json|set_cookie|classify|geo|query_present
// Pattern-style kinds.
Patterns []string // path/host: reconstructed glob/suffix patterns (OR set)
Regex string // path_regex/host_regex/header_regex: the RE2 source
// header / cookie.
Name string // header name (header/header_regex); cookie name (or prefix when Glob)
Values []string // header/cookie accepted values (OR); empty => presence-only
Glob bool // cookie: Name is a name prefix (`cookie NAME*`)
// method / upstream / content_type / set_cookie.
Methods []string // method: upper-cased OR set
Upstreams []string // upstream: OR set
ContentTypes []string // content_type: lower-cased media-type substrings (OR)
CookieNames []string // set_cookie: OR set of cookie names (empty => any Set-Cookie)
// classify matcher (`@x classify {tok}==val`).
ClassifyToken string // the classifier token name
ClassifyValue string // the compared value
ClassifyNegate bool // true for `!=`
// geo.
GeoGranularity string // country|continent|region
GeoValues []string // upper-cased OR set
// query_present.
QueryNames []string // param-name patterns (exact + `*` globs)
// QueryNamesNonEmpty is the subset of QueryNames that carry the `+` modifier
// (e.g. `query_present adult_content+ ff-*+`): a matched param must have at
// least one non-empty value to fire. Absent => all names are presence-only.
// v7 (additive).
QueryNamesNonEmpty []string
// cookie_json / header_json (D54): a bounded dotted field test inside a JSON
// cookie/header value. Name carries the cookie/header name; JSONPath is the
// dotted PATH (e.g. "user.verified", "flags.0.kind") the JS interpreter splits
// the same way; Values is the OR set of accepted scalar string forms (empty =>
// presence/non-null only — mirrors the cookie matcher).
JSONPath string
// ResponsePhase is true for content_type/set_cookie (needs the origin response).
ResponsePhase bool
// Subs is the AND-conjunction of an `all` matcher: each term references a named
// sub-matcher (Ref) with an optional per-term Negate (`!@x`). Request-phase, depth-1
// (compile forbids all-of-all and response-phase subs). Populated only for kindAll.
Subs []SubMatcherView
}
MatcherView is the neutral, serialization-ready view of one compiled matcher. Kind mirrors the matcher type keyword (path/host/header/…); exactly the fields relevant to that kind are populated. It is the projection the IR's `{kind, fields}` shape is built from — the JS matcher switch mirrors these 1:1.
type NormalizerView ¶ added in v0.2.3
type NormalizerView struct {
Source string // header|cookie|query
SourceName string // the header/cookie/param name
Map map[string]string // raw value -> bucket
Default string
}
NormalizerView is the neutral view of a normalize bucket map.
type OnError ¶
OnError is the resolved origin-error-phase synthetic produced by a matching `respond on_error [@scope] STATUS BODY` directive (D57). It is consulted ONLY on the handler's origin-error path, after serve-stale-within-grace and the cacheable negative-cache (D15) checks have been exhausted — i.e. for an UNCACHEABLE hard failure with no servable object. The body is decided at compile (held as []byte so the error path does one Write, no streaming, no tee); ContentType defaults to "text/html; charset=utf-8". The synthetic is NOT cached by default — it is an availability stopgap, not an origin answer; caching it would mask recovery.
type OnErrorView ¶ added in v0.2.3
OnErrorView is the neutral view of one `respond on_error [@scope] STATUS BODY` origin-error synthetic (D57/D76). Body is the resolved body bytes as a string.
type Pipeline ¶
type Pipeline struct {
// contains filtered or unexported fields
}
Pipeline is the compiled, immutable evaluation plan for one site. It is safe for concurrent use by the per-request Eval* methods.
func Compile ¶
func Compile(site *cadishfile.Site) (*Pipeline, error)
Compile turns a parsed site into an executable Pipeline. Matchers are compiled once; directives are validated and bucketed into per-phase rule lists. Imports must be spliced first (see SpliceImports); a leftover `import` is an error.
func (*Pipeline) BypassForCredentials ¶
BypassForCredentials reports whether a request must bypass the SHARED cache because it carries a per-user credential (`Authorization` or `Cookie`) that the cache key does NOT capture. It is the LEAK-PROOF safe-default for cross-user confidentiality (AUTH-LEAK / COOKIE-LEAK): a private response returned for one user's session cookie or bearer token must never be stored under a credential-agnostic key and served to another user (including anonymous). This mirrors Varnish's builtin VCL (`vcl_recv` passes `Authorization || Cookie`) — the tool cadish replaces — and RFC 9111 §3.5.
The ONLY way to cache a credentialed request is to KEY by that credential, so each distinct value gets its own entry (`cache_key … cookie:session` for a Cookie, `cache_key … header:Authorization` for a bearer token). There is deliberately no flag escape hatch: `cache_unsafe` (which governs response Set-Cookie shareability) does NOT lift this — you cannot accidentally cache credentialed traffic under a shared key. A request carrying a credential the selected key recipe does not cover (the default `method host path`, or a key that omits this credential) bypasses.
Applied by the SERVER (and the edge worker enforces the same rule in JS) rather than baked into EvalRequest's `Pass`, so the portable pipeline decision — matchers, cache key, the conformance snapshot — is unchanged: the credential bypass is a serving-tier cache-safety policy, not part of rule evaluation.
func (*Pipeline) CacheCredentialedMatches ¶ added in v0.2.1
CacheCredentialedMatches reports whether req matches a `cache_credentialed @scope` directive — i.e. caching is ORIGIN-AUTHORITATIVE for it (D101). The SERVER calls this at the credential-bypass decision: when true it does NOT call/short-circuit BypassForCredentials (so a Cookie/Authorization request is NOT bypassed) and restores the original cookies onto the OUTBOUND origin request (origin auth, not the cache key — the entry stays under the SHARED key). Mirrors BypassForCredentials' server+edge split: a serving-tier policy toggled by a matcher, NOT baked into EvalRequest's snapshot, so portable rule eval / cache key / matchers are unchanged. Returns false immediately when no directive is declared (zero cost). The matchers are request-phase (response-phase rejected at compile), so a path/host scope evaluates identically here and in EvalResponse.
func (*Pipeline) DeviceClassifier ¶
func (p *Pipeline) DeviceClassifier() *classify.Classifier
DeviceClassifier returns the site's compiled User-Agent → device-class classifier (from `device_detect { … }`, or the built-in default). Always non-nil. The config layer reads it for the server's {device} pre-pass; the edge projector emits its ruleset so the worker classifies identically (D70).
func (*Pipeline) DeviceClassifierView ¶ added in v0.2.3
func (p *Pipeline) DeviceClassifierView() (DeviceClassifierView, bool)
DeviceClassifierView returns the projected device classifier and ok=false when the site does NOT use {device} at all (zero-cost-when-unused: a site that never keys on OR reflects device ships no UA ruleset to the worker). The gate is the broad UsesDeviceClassification — matching the server pre-pass gate — so a {device} token reflected in a header/redirect (but absent from the cache key) still ships the classifier and the worker resolves the token instead of emitting "". When present the FULL ruleset is projected (even the built-in default) so the worker classifies from an explicit IR and never relies on a JS-side default that could drift from Go.
func (*Pipeline) EvalDeliver ¶
func (p *Pipeline) EvalDeliver(req *Request, respHeader http.Header, cacheStatus CacheStatus) DeliverDecision
EvalDeliver evaluates the DELIVER phase. respHeader is the response header set being assembled (so `content_type` matchers resolve against the real response Content-Type); it may be nil. cacheStatus feeds the `header +cache_status` special.
It resolves the routed upstream itself. Callers that ALREADY resolved it (the server, from EvalRequest's RequestDecision.Upstream) should call EvalDeliverUpstream to skip the redundant route-matcher re-evaluation (F2).
func (*Pipeline) EvalDeliverUpstream ¶ added in v0.2.3
func (p *Pipeline) EvalDeliverUpstream(req *Request, respHeader http.Header, cacheStatus CacheStatus, upstream string) DeliverDecision
EvalDeliverUpstream is EvalDeliver with the routed upstream supplied by the caller. resolveUpstream is a first-match walk over the route rules with its own memo; EvalRequest already ran it (RequestDecision.Upstream), so on the hot serve path re-running it here was pure duplicated work (F2). The supplied upstream is byte-identical to what EvalDeliver would resolve (both call resolveUpstream on the same request), so passing it changes nothing observable.
func (*Pipeline) EvalOnError ¶
EvalOnError resolves the origin-error-phase synthetic for a request whose origin HARD-failed with no servable object (D57). It runs the on_error rules in source order (first-match-wins) evaluating only their request-phase `@scope` matchers — the origin-error path carries a status but no upstream response headers, so a response-phase matcher can never appear here (rejected at compile). It returns nil when no rule is configured or none matches, so the caller falls through to the bare-status fallback. status is the mapped origin failure code; it is accepted for future status-aware scoping but request-phase matchers do not consult it today.
func (*Pipeline) EvalRequest ¶
func (p *Pipeline) EvalRequest(req *Request) RequestDecision
EvalRequest evaluates the RECV + KEY phases and returns the request decision. It is safe for concurrent use.
func (*Pipeline) EvalResponse ¶
EvalResponse evaluates the ORIGIN/store phase using the response status (needed by cache_ttl status selectors) and the origin response headers (needed by response-phase matchers like `set_cookie`/`content_type`). respHeader may be nil (e.g. a bodyless negative entry), in which case response-phase matchers do not fire. First-match-wins for both cache_ttl and storage.
func (*Pipeline) EvalSecurity ¶
func (p *Pipeline) EvalSecurity(req *Request) SecurityDecision
EvalSecurity evaluates the security gate (the first step of RECV) and returns the decision. It is a pure function, safe for concurrent use, and is called by the server BEFORE the cache key / cache lookup / origin — so an enforced deny touches neither cache nor origin.
Order (design §3/§6): allow first (an allowlist bypasses everything — office / monitoring IPs are never blocked), then deny (403). The `monitor` toggle (global or per-rule) turns an enforced deny into a recorded "would-block" that passes.
func (*Pipeline) FilterRequestCookies ¶
FilterRequestCookies applies the `cookie_allow` allowlist to a raw Cookie header value: it returns the header with ONLY the allowed cookies kept (the rest stripped) and active=true. When no `cookie_allow` directive is configured it returns (raw, false) unchanged. The server calls this at RECV — after the security gate, so `deny`/`allow` cookie rules still see the original cookies, but before the cache key, the credential-coverage check, and the origin fetch — so all three see only the operator-controlled cookies. An empty allowlist strips every cookie.
func (*Pipeline) HasCacheCredentialed ¶ added in v0.2.1
HasCacheCredentialed reports whether the site declares any `cache_credentialed @scope` directive. The server gates the whole origin-authoritative path on this so a site without it pays exactly one len check and the credential-bypass hot path is byte-for-byte unchanged (the directive is opt-in and zero-cost when unused).
func (*Pipeline) HasDerivesFrom ¶ added in v0.2.1
HasDerivesFrom reports whether ANY classify token that feeds a cache_key recipe declares `derives_from cookie …`. The server gates the post-key cookie strip on this so a site without the feature pays exactly one nil check and the request path is byte-for-byte unchanged (COOKIE-NORM is opt-in, zero-cost when unused).
func (*Pipeline) HasOnError ¶
HasOnError reports whether the site configures any `respond on_error` rule. The server gates the origin-error synthetic path on this so a site without the directive pays exactly one nil/len check on the error path (zero cost; D57).
func (*Pipeline) HasScopedCacheKey ¶
HasScopedCacheKey reports whether the site uses a scoped cache_key (more than one recipe, or a single non-default selector). As of D70 the edge projects the FULL ordered recipe list + selectors (ViewKeyRecipes) and evaluates the same first-match-wins selection in the worker, so a scoped site is edge-native — this accessor is retained for callers that still want to know whether the site uses scoping (e.g. `cadish check` / build-report wording) but no longer gates edge projection.
func (*Pipeline) IgnoreClientRevalidation ¶ added in v0.2.1
IgnoreClientRevalidation reports whether the site set `client_cache_control ignore`: when true, the server does NOT honor a request `Cache-Control: no-cache` / `max-age=0` (or `Pragma: no-cache`) and serves the fresh/stale entry as normal, instead of forcing a MISS (RFC 9111 §5.2.1.4). False (the default) preserves the standard honor behavior, and lets the server skip the client-revalidation header scan only when set. Computed once at Compile from a SETUP-phase parse-once toggle.
func (*Pipeline) NeedsPoolHealth ¶ added in v0.2.1
NeedsPoolHealth reports whether the site references an `upstream_healthy` matcher, so the server must inject a per-request pool-health view (Request.PoolHealthy) from config.Pools() before EvalRequest. It is false on every site that does not use the matcher, keeping the request fast path zero-cost (no pool snapshot). Computed once at Compile (computeNeedsPoolHealth).
func (*Pipeline) SelectedDerivedForwardCookies ¶ added in v0.2.1
SelectedDerivedForwardCookies returns the sorted set of request cookies the ACTIVE `derives_from … forward` axes FORWARD to origin for THIS request — i.e. the forward-mode cookies declared by every classify {TOKEN} present in the recipe SELECTED for the request. These are read to derive + key but, unlike strip-mode cookies, are KEPT in the request (forwarded to origin) and treated as COVERED by {TOKEN} in the credential bypass — the loud opt-in for cookie-reading backends. Empty when no forward axis is in the selected recipe. Pure (no mutation) so the edge/conformance compute the identical set. The gate is the SAME as strip-mode (token in the selected recipe): a forward cookie whose axis is NOT selected is NOT returned here, so it is never covered — it bypasses like any kept cookie.
func (*Pipeline) SelectedDerivedStripCookies ¶ added in v0.2.1
SelectedDerivedStripCookies returns the sorted set of request cookies the ACTIVE `derives_from` axes consume for THIS request — i.e. the cookies declared by every classify {TOKEN} present in the recipe SELECTED for the request (the per-request gate). These are the cookies Varnish would `unset` after deriving its VARY-* axes: they were read to build the key and must now leave the request before the origin fetch. Empty when no derives_from token is in the selected recipe. Pure (no mutation) so the edge/conformance can compute the identical set.
func (*Pipeline) StripDerivedCookies ¶ added in v0.2.1
StripDerivedCookies removes, from req's Cookie header, the cookies consumed by the ACTIVE `derives_from` axes (those whose token is in the SELECTED cache_key recipe) — the derive→strip half of the Varnish cardinality collapse. The server calls it AFTER the cache key is captured but BEFORE the credential check and the origin fetch, so:
- the key (incl. {TOKEN}) was already built from the ORIGINAL cookie value, and
- the credential bypass (BypassForCredentials) and the origin both see the request with the per-user cookie GONE — so the origin reply is anonymous w.r.t. the axis and is safely stored under the collapsed (shared) key. This is the SINGLE fail-closed mechanism: nothing teaches the coverage check to treat the token as covering the cookie; the cookie is simply absent, so no bypass fires for it.
It also RECONCILES the survive-set: a `derives_from` cookie that was force-kept through cookie_allow (so the classifier could read it) but whose token is NOT in the selected recipe is stripped iff cookie_allow would have stripped it — restoring exactly the behavior a site without the feature would have for that request (the gate). It returns whether it modified the Cookie header (for trace/fast-path).
func (*Pipeline) Tenant ¶
Tenant returns the site's tenant name (from a `tenant NAME` directive or a site-group expansion), or "" when the site is not tenant-scoped.
func (*Pipeline) UsesDeviceClassification ¶ added in v0.2.2
UsesDeviceClassification reports whether the site needs the UA device classifier run at all — either the cache key varies on {device} (UsesDeviceToken) OR a {device} token is reflected in a DELIVER-phase surface: a RESPONSE header value (applied per-request at delivery, so safe to vary an unkeyed object) or a redirect target (a short-circuit). The server gates its UA pre-pass (handler.go) and the edge projector gates shipping the device classifier (view.go) on THIS predicate, so a `header X-Device {device}` placed AFTER cache_key resolves instead of silently expanding to "".
REQUEST-phase headers (before cache_key, forwarded to origin) are DELIBERATELY excluded (F-A1): resolving an unkeyed {device}/{geo} forwarded to origin would make the origin vary content the cache key does not segment on → a cross-device/region cache leak. Such an unkeyed-forwarded token renders empty (fail-safe, the pre-D1 behaviour) and `cadish check` warns loudly (detectForwardedClassTokenUnkeyed) so it is not silent. A request-phase {device} that IS keyed resolves anyway via UsesDeviceToken.
func (*Pipeline) UsesDeviceToken ¶
UsesDeviceToken reports whether the compiled cache key includes the {device} normalizer. It is the cache-key-only predicate; the server/edge gate on the broader UsesDeviceClassification (which also covers a {device} header/redirect reflection) so the classifier still runs when {device} is reflected but unkeyed.
func (*Pipeline) UsesGeoToken ¶
UsesGeoToken reports whether the site needs the server's geo pre-pass — i.e. the compiled cache key includes a geo token ({geo}/{geo.continent}/{geo.region}) OR a `geo` matcher is referenced anywhere. The server skips the geo pre-pass entirely when this is false (zero hot-path cost on sites that do not vary on geo).
func (*Pipeline) UsesIPMatcher ¶ added in v0.2.3
UsesIPMatcher reports whether the site references an `ip` matcher anywhere — crucially INCLUDING scopes OUTSIDE the security gate (a `respond @banned 403`, a redirect/header/handle/route/cache_key scope, an `all` composite, or a classify `when` row). The `ip` matcher tests Request.RealClientIP, which the server otherwise resolves ONLY when a security gate is present (UsesSecurityGate). A site with an `ip` matcher but no allow/deny/rate_limit rule must therefore still resolve the real client IP, or the matcher reads a zero Addr and silently evaluates false (fail-open — the banned IP is served). The server widens its IP-resolution gate to UsesSecurityGate() || UsesIPMatcher(). Computed once at Compile (computeUsesIPMatcher), so the common no-ip path pays nothing per request.
func (*Pipeline) UsesRateLimit ¶
UsesRateLimit reports whether the site configured any `rate_limit` rule, so the server can construct the in-memory token-bucket limiter ONLY when needed (zero cost — and no goroutine — on a site with no rate limiting).
func (*Pipeline) UsesSecurityGate ¶
UsesSecurityGate reports whether the server must run the security gate for this site (and therefore resolve the real client IP for the `ip` matcher). It is false for any site with no allow/deny rules, so a non-security site pays nothing.
func (*Pipeline) ViewBypassPasses ¶ added in v0.2.3
ViewBypassPasses reports whether the site opted into route-exclusion projection (`edge { bypass_passes }`, D105). When true the projector populates EdgeIR.RouteExclusions with the path-only-pass patterns so the deploy plane carves them out of the worker route (run no worker → origin direct). Default false.
func (*Pipeline) ViewBypassPatterns ¶ added in v0.2.3
ViewBypassPatterns returns the operator-declared explicit route-exclusion patterns (`edge { bypass PATTERN… }`, D105 explicit companion). Each is a leading-`/` literal prefix, a trailing-`*` prefix glob, or an exact path. The projector crosses them with the worker route host(s) and adds them to EdgeIR.RouteExclusions IN ADDITION to any auto-derived set — declaring `bypass …` is itself the opt-in. Nil when none declared.
func (*Pipeline) ViewCORSRule ¶ added in v0.2.3
ViewCORSRule returns the cors rule and ok=false when the site has none.
func (*Pipeline) ViewCacheUnsafe ¶ added in v0.2.3
ViewCacheUnsafe reports whether the site set `cache_unsafe` (opts out of the safe-by-default refusal of Set-Cookie / private Cache-Control / uncovered Vary). The edge interpreter reads this to decide whether to apply the same downgrade.
func (*Pipeline) ViewCanonicalHost ¶ added in v0.2.3
ViewCanonicalHost returns the site's canonical host (first non-wildcard configured address, scheme/port stripped, lower-cased) — the safe {host} the edge substitutes into a redirect Location when the inbound Host is not trusted. Mirrors Pipeline.canonicalHost; "" when the site declares no address.
func (*Pipeline) ViewClassifiers ¶ added in v0.2.3
func (p *Pipeline) ViewClassifiers() map[string]ClassifierView
ViewClassifiers returns every classify table, keyed by token name.
func (*Pipeline) ViewCookieAllow ¶ added in v0.2.3
ViewCookieAllow returns the `cookie_allow` allowlist patterns and whether the directive is present. The edge worker strips every request cookie not matching a pattern (an empty list with active=true strips ALL cookies), so the edge can cache the controlled cookie-bearing traffic the server does — mirroring FilterRequestCookies.
func (*Pipeline) ViewCredentialedRules ¶ added in v0.2.3
ViewCredentialedRules returns the `cache_credentialed @scope` scopes, in order (D101). The projector runs each through the fail-closed chokepoint: a scope referencing a ServerOnly/untranslatable matcher fails CLOSED at the edge (fail-open site-wide pass + ForcedPass++), and a translatable scope is projected into EdgeIR.CacheCredentialed so the worker applies the SAME origin-authoritative precedence the server does.
func (*Pipeline) ViewDefaultTier ¶ added in v0.2.3
ViewDefaultTier returns the default edge cache tier ("local" when no edge block).
func (*Pipeline) ViewDefaultUpstream ¶ added in v0.2.3
ViewDefaultUpstream returns the site's default upstream `to` name ("" if none).
func (*Pipeline) ViewDeployConfig ¶ added in v0.2.3
func (p *Pipeline) ViewDeployConfig() DeployView
ViewDeployConfig returns the site's Cloudflare deploy identity. Configured is false when there is no `edge {}` block (deploy then needs flags/env).
func (*Pipeline) ViewHasEncode ¶ added in v0.2.3
ViewHasEncode reports whether the site declares `encode` (on-the-fly response-body compression). Server-only in edge v1; the projector delegates it.
func (*Pipeline) ViewHosts ¶ added in v0.2.3
ViewHosts returns the site's address tokens (e.g. "example.com", "*.example.com").
func (*Pipeline) ViewKVMaxBytes ¶ added in v0.2.3
ViewKVMaxBytes returns the `kv_max_bytes` size bound for the KV (L2) tier. A response body larger than this is written to L1 only, never KV. Defaults to defaultKVMaxBytes (1 MiB) when no edge block sets it.
func (*Pipeline) ViewKVTTL ¶ added in v0.2.3
ViewKVTTL returns the configured `kv_ttl` cap on KV retention and whether it was set. Zero/false => no cap (KV retention defaults to the object's ttl+grace).
func (*Pipeline) ViewKeyHeaderNames ¶ added in v0.2.3
ViewKeyHeaderNames returns the lower-cased set of request header names the cache key varies on (every `header:NAME` token across all key recipes). The edge IR is static, so it uses the union over recipes; the edge interpreter reads it to decide whether a `Vary` field is already covered by the cache key.
func (*Pipeline) ViewKeyRecipes ¶ added in v0.2.3
func (p *Pipeline) ViewKeyRecipes() []KeyRecipeView
ViewKeyRecipes returns the FULL ordered cache_key recipe list for edge projection: one KeyRecipeView per `cache_key [SELECTOR] TOKEN…` line, in source order, each carrying its request-phase selector scope (Always=true for `default`/unscoped) and its compiled tokens. The worker iterates these first-match-wins, mirroring pipeline.selectKeyTokens, so the edge keys byte-identically to the server even for a scoped site (D70). Returns nil when the site declares no cache_key (the worker then falls back to the built-in method/host/path key, exactly like buildKey).
Selectors are request-phase only (compile rejects a response-phase selector), so every matcher referenced here is already projected for the worker.
func (*Pipeline) ViewKeyTokens ¶ added in v0.2.3
func (p *Pipeline) ViewKeyTokens() []KeyTokenView
ViewKeyTokens returns the compiled cache_key tokens for edge projection: the catch-all (`default`/unscoped) recipe, or the built-in method/host/path when the site declares no cache_key. v1 projects a single recipe — a site with one unscoped cache_key (the 100%-backward-compatible case) is unaffected. Projecting the FULL scoped recipe list + selectors to the edge worker is v2 (see the spec's phasing); HasScopedCacheKey lets the edge build plane detect and refuse a scoped site until then, so the edge never silently computes a divergent key.
func (*Pipeline) ViewMatchers ¶ added in v0.2.3
func (p *Pipeline) ViewMatchers() map[string]MatcherView
ViewMatchers returns every NAMED matcher, keyed by name, as a neutral view. Anonymous inline matchers (name == "") are not included here — they surface inside the scope that owns them.
The `ip` ACL matcher resolves the trusted-proxy real client IP (decision #16), which the edge has no concept of, so it is projected as a SERVER-ONLY matcher (Kind "ip" → projectMatcher marks ServerOnly) rather than SKIPPED (R02). It was previously dropped on the assumption it is "only referenced by the security gate" — but `ip` is a GENERAL request-phase matcher that can scope ANY directive (`pass @internal_ips`, `cache_key @internal …`, `route @office …`). Skipping it left a DANGLING name in the projected scope (scopeView still emits it) that the runtime silently treats as a non-match → e.g. `pass @internal_ips` became an edge cache STORE while the server passes it. Projecting it ServerOnly routes every ip-scoped directive through the same fail-closed delegation/fail-open machinery as `upstream_healthy`, so it can never silently mis-project; a security-gate-only `ip` is simply delegated (the gate is already server-only).
func (*Pipeline) ViewNormalizers ¶ added in v0.2.3
func (p *Pipeline) ViewNormalizers() map[string]NormalizerView
ViewNormalizers returns every normalize bucket map, keyed by name.
func (*Pipeline) ViewOnErrorRules ¶ added in v0.2.3
func (p *Pipeline) ViewOnErrorRules() []OnErrorView
ViewOnErrorRules returns the `respond on_error` synthetics, in source order, with their resolved status/body/content_type. The edge worker serves the FIRST whose request-phase scope matches on the outage path (no servable cached object), mirroring Pipeline.EvalOnError. Edge-native as of D76 (was delegated).
func (*Pipeline) ViewOnErrorScopes ¶ added in v0.2.3
ViewOnErrorScopes returns one scope per `respond on_error` rule. The origin-error synthetic is server-only in edge v1; the projector delegates each.
func (*Pipeline) ViewPassRules ¶ added in v0.2.3
ViewPassRules returns the `pass` scopes, in order.
func (*Pipeline) ViewPurgeRules ¶ added in v0.2.3
ViewPurgeRules returns the `purge` guards, in order.
func (*Pipeline) ViewRedirectHosts ¶ added in v0.2.3
ViewRedirectHosts returns the NORMALIZED trusted-host allowlist a `redirect` TARGET's {host} placeholder is checked against — the edge projection of the server's trustedHosts (see Pipeline.redirectHost / normalizeRedirectHost). Each entry is a bare lower-cased host or a preserved `*.suffix` wildcard (scheme/port/ path already stripped). The worker uses it with the SAME semantics as hostSet.Match (exact OR HasSuffix for a wildcard — a `*.suffix` does NOT trust the apex). Returns nil when the site declares no address (trustedHosts == nil): the worker then reflects the request Host verbatim, exactly as the server does. The list is deterministically ordered (exacts then wildcards, each sorted).
func (*Pipeline) ViewRedirectRules ¶ added in v0.2.3
func (p *Pipeline) ViewRedirectRules() []RedirectView
ViewRedirectRules returns the `redirect` rules, in order.
func (*Pipeline) ViewReqHeaderRules ¶ added in v0.2.3
func (p *Pipeline) ViewReqHeaderRules() []HeaderRuleView
ViewReqHeaderRules returns the request-phase header rules, in order.
func (*Pipeline) ViewRespHeaderRules ¶ added in v0.2.3
func (p *Pipeline) ViewRespHeaderRules() []HeaderRuleView
ViewRespHeaderRules returns the response/deliver-phase header rules, in order.
func (*Pipeline) ViewRespondRules ¶ added in v0.2.3
func (p *Pipeline) ViewRespondRules() []RespondView
ViewRespondRules returns the exact-path `respond PATH STATUS BODY` rules, in order. The scoped form (`respond @scope STATUS BODY`, e.g. the ingress terminal no-match 404) is NOT projected: the edge IR's RespondView models only an exact path, and the scoped form expresses a matcher conjunction that has no exact-path representation. Projecting it with the empty path would make the edge worker 404 the path "" (or behave inconsistently with the server), so it is skipped — the server enforces the terminal no-match behavior; the edge tier defers to origin for an uncovered path.
The skipped scoped rules are NOT silently dropped: ViewScopedRespondCount reports them so the caller records a Delegate, keeping them in the coverage report and tripping `-strict` (E2 — "never silently dropped").
func (*Pipeline) ViewRewriteScopes ¶ added in v0.2.3
ViewRewriteScopes returns one scope per `rewrite` rule. `rewrite` is server-only in edge v1 (origin-request URL rewrite); the projector delegates each so it surfaces in the coverage report instead of being silently dropped.
func (*Pipeline) ViewRouteRules ¶ added in v0.2.3
ViewRouteRules returns the `route` rules, in order.
func (*Pipeline) ViewScopedRespondCount ¶ added in v0.2.3
ViewScopedRespondCount returns the number of scoped `respond @scope STATUS BODY` rules that ViewRespondRules deliberately skips (they have no exact-path edge representation). The edge projector records one Delegate per scoped respond so it is counted in the coverage report and fails `cadish edge build -strict` instead of vanishing from the IR and worker bundle (E2).
func (*Pipeline) ViewStickyCookie ¶ added in v0.2.3
ViewStickyCookie returns the configured sticky cookie name ("" if none).
func (*Pipeline) ViewStorageRules ¶ added in v0.2.3
func (p *Pipeline) ViewStorageRules() []StorageView
ViewStorageRules returns the storage tier rules, in order.
func (*Pipeline) ViewStripRules ¶ added in v0.2.3
ViewStripRules returns the `strip_cookies` scopes, in order.
func (*Pipeline) ViewTTLRules ¶ added in v0.2.3
ViewTTLRules returns the cache_ttl rules, in order.
func (*Pipeline) ViewTenantConstant ¶ added in v0.2.3
ViewTenantConstant returns the per-site constant {tenant} value ("" if the site is not a bare-tenant site).
func (*Pipeline) ViewTenantResolver ¶ added in v0.2.3
func (p *Pipeline) ViewTenantResolver() (TenantView, bool)
ViewTenantResolver returns the request-derived tenant resolver view, and ok=false when the site uses no such block (a bare `tenant NAME` constant or no tenant).
func (*Pipeline) ViewTierPolicies ¶ added in v0.2.3
func (p *Pipeline) ViewTierPolicies() []TierPolicyView
ViewTierPolicies returns the per-scope edge cache-tier policies, in order.
func (*Pipeline) ViewTransformRules ¶ added in v0.2.3
func (p *Pipeline) ViewTransformRules() []TransformView
ViewTransformRules returns the `replace` body-substitution rules, in order.
func (*Pipeline) ViewUpgradeScopes ¶ added in v0.2.3
ViewUpgradeScopes returns one scope per `upgrade @scope` rule. `upgrade` is inherently server-only: it opens a live, hijacked connection-upgrade (WebSocket) tunnel to the origin, which a stateless edge worker cannot host. The projector delegates each so it surfaces in the coverage report (and trips `-strict`) instead of being silently dropped.
func (*Pipeline) ViewUsesKV ¶ added in v0.2.3
ViewUsesKV reports whether the edge cache needs an L2 KV namespace: an explicit `kv NAME`, a `distribute` policy, or a default tier of "distribute".
type PurgeDecision ¶
type PurgeDecision struct {
// Authorized is true when the purge guard matched (always true when the
// PurgeDecision is non-nil — a non-matching guard yields a nil PurgeDecision).
Authorized bool
// Regex, when non-empty, is a key-pattern the purge should ban (from the
// directive's `regex EXPR`, with {http.HEADER} placeholders resolved against
// the request). For the `regex-path EXPR` form the operator-written path
// pattern has already been rewritten to anchor against the PATH component of
// the key (so a Varnish-style `^/foo` matches the path token, not the whole
// `host path …` key). Empty means "purge this request's own cache key".
Regex string
}
PurgeDecision describes a matched `purge` request. The guard condition (e.g. a secret token header) already matched, so Authorized is true; the server still owns the actual invalidation.
type PurgeView ¶ added in v0.2.3
type PurgeView struct {
Guard ScopeView
RegexToken string // literal regex or "{http.NAME}" placeholder; "" if none
}
PurgeView is a compiled purge guard.
type RateLimitHit ¶
type RateLimitHit struct {
Key string // the bucket key (rule id + the resolved IP / header / global tag)
RatePerSec float64 // steady refill rate (tokens/second)
Burst int // extra capacity above one token
Monitor bool // would-429 only: record + pass, do not throttle
Rule string // representative matcher name for metrics/audit
}
RateLimitHit is what the pure gate returns when a rate_limit rule applies to a request: the identified rule's parameters plus the computed bucket KEY. The server feeds {Key, Limit/RatePerSec, Burst} to its token-bucket limiter; it does the stateful counting, not the pipeline. Monitor mode (per-rule or global) means the server records a would-429 and PASSES instead of throttling (decision #19).
type Redirect ¶
Redirect is a computed 3xx produced by a `redirect` directive: a status code (301/302/307/308) and a fully-resolved Location built from the request (host + path) via template substitution (regex capture groups and {host}/{path}/{query}/ {uri} placeholders). The server writes Status with a Location header.
NoStore, when true, means the directive carried a trailing `no_store` modifier: the server attaches Cache-Control: no-store, no-cache, must-revalidate, private so no intermediary or browser caches the (typically personalized) redirect.
Headers carries the directive's optional `{ header … }` block (CAD-3): literal operator ops applied AFTER Location and the no_store Cache-Control (so an explicit op in the operator's own block may refine Cache-Control). Ops can never touch Location — compile-rejected AND runtime-skipped — so the open-redirect authority guard on the TARGET stays authoritative. nil when absent.
type RedirectView ¶ added in v0.2.3
type RedirectView struct {
Regex string // path-regex selector: the RE2 source ("" when there is no path regex)
HasScope bool // true when Scope is a real selector (scope-only or combined form)
Scope ScopeView // the selecting scope (valid only when HasScope)
Status int
Target string // template: {host}/{path}/{query}/{uri} (+ $0..$9 when Regex is set)
NoStore bool // true when the directive carried a trailing `no_store` modifier
Headers []HeaderOpView // the optional `{ header … }` block ops (CAD-3); Location/Content-Length never appear (compile-rejected)
}
RedirectView is a compiled redirect rule. Regex and/or Scope select: the regex-only and scope-only forms set exactly one; the combined scope+regex form sets BOTH (HasScope true AND a non-empty Regex), and the rule fires only when the scope matches AND the regex matches the path.
type Replacement ¶
Replacement is one literal body substitution from a `replace OLD NEW` directive: every occurrence of Old becomes New. Applied in order.
type Request ¶
type Request struct {
// Method is the HTTP method (e.g. "GET"). Compared case-insensitively by the
// `method` matcher; an empty Method is treated as "GET".
Method string
// Host is the request authority (e.g. "static.example.com" or
// "example.com:8443"). Any :port is stripped and the value lower-cased for
// host matching and the `host` cache_key token.
Host string
// Path is the URL path (e.g. "/panel/settings"), already percent-decoded by
// the server. It is what `path` / `path_regex` matchers and the `path`
// cache_key token see.
Path string
// Query holds the parsed query parameters. The `query` and `url` cache_key
// tokens render it canonically (keys+values sorted) so semantically equal
// queries share a cache key.
Query url.Values
// Header carries the request headers. May be nil (treated as empty).
Header http.Header
// ClientIP is the resolved client address (no port). It is the fallback for
// the {sticky} normalizer when no sticky cookie is present.
ClientIP string
// RealClientIP is the trusted-proxy-resolved REAL client IP for the `ip` ACL
// matcher (the security gate). The server resolves it via geo.ClientIP — the
// SAME trust_proxy/XFF logic as {geo} (decision #16), so behind a CDN/LB the
// gate ACLs the real client, not the proxy — and sets it before EvalSecurity,
// gated by UsesSecurityGate (zero cost on non-security sites). It is the
// zero netip.Addr (invalid) when the site runs no security gate; an `ip`
// matcher against an invalid address matches nothing.
RealClientIP netip.Addr
// Device is the resolved device class for the {device} cache-key normalizer
// ("desktop"/"mobile"/"tablet"/"bot"/…). The server computes it from the
// User-Agent via the site's classifier (a cheap pre-pass) before EvalRequest;
// it is "" when {device} is unused or no classifier ran.
Device string
// Geo is the resolved geo class for the {geo} cache-key normalizer (a country
// code like "US"/"ES", or "unknown"). The server computes it from the real
// client IP / a CDN country header via the site's geo source before
// EvalRequest; it is "" when {geo} is unused or no geo source is configured.
Geo string
// GeoContinent is the resolved continent class for the {geo.continent} token
// (a 2-letter code like "EU"/"NA", or "unknown"). The server derives it from
// the resolved country via an in-tree static table (no GeoIP dependency) before
// EvalRequest; "" when no geo granularity token/matcher is used.
GeoContinent string
// TLS reports whether the inbound connection terminated TLS AT cadish (the
// server sets it from r.TLS != nil when it builds the Request). It backs the
// {proto}/{scheme} dynamic-header token (FWDHDR part b): {proto} resolves to
// "https" when TLS is true, else "http" — the HAProxy `X-Forwarded-Proto https
// if { ssl_fc }` equivalent. It is purely request-scoped (no cache-key effect).
TLS bool
// GeoRegion is the resolved region / subdivision class for the {geo.region}
// token (e.g. "US-UT"/"US-TX", or "unknown"). The server reads it from a
// configurable upstream CDN region header (no bundled GeoIP DB — region
// granularity REQUIRES an upstream geo header) before EvalRequest; "" when no
// geo granularity token/matcher is used or no region source is configured.
GeoRegion string
// PoolHealthy reports whether the named upstream POOL currently has at least one
// live backend (lb nbsrv()>0). It backs the `upstream_healthy NAME…` matcher (the
// AWS /aws-health-check probe). The server injects it from the site's live pools
// (config.Pools) ONLY when the compiled pipeline references the matcher — gated by
// Pipeline.NeedsPoolHealth — so the fast path is untouched on every other site. It
// is nil when not injected, in which case the matcher fails CLOSED (treats every
// pool as down). The pure pipeline never imports internal/lb: it sees only this
// func, keeping liveness resolution decoupled exactly like geo/device are.
PoolHealthy func(name string) bool
// contains filtered or unexported fields
}
Request is the engine's backend-agnostic view of an HTTP request. It is deliberately decoupled from net/http's *http.Request so the evaluation core can be unit-tested without a server and reused by tooling. The server constructs one of these from the live request before each phase.
Using net/http and net/url *types* (http.Header, url.Values) is intentional — it is the convenient shape for header/query access. No network I/O is implied.
type RequestDecision ¶
type RequestDecision struct {
// Pass is true when the request must bypass the cache entirely: fetch from the
// upstream and stream to the client, never storing. Decided in RECV from `pass`
// rules. When Pass is true the server skips LOOKUP and storage.
Pass bool
// Upgrade is true when the request matched an `upgrade @scope` rule: the route
// opted into a connection-upgrade (WebSocket / `Connection: Upgrade`) passthrough
// tunnel. It always implies Pass (a tunnel is entirely off the caching path).
// Whether the request is ACTUALLY tunnelled is decided by the server, which also
// requires a GENUINE upgrade (a `Connection: upgrade` token plus an `Upgrade`
// header); a non-upgrade request on an upgrade route just Passes as normal.
Upgrade bool
// Synthetic, when non-nil, is a canned response to return immediately without
// touching cache or origin (from a matching `respond` rule). When set, all
// other fields except CacheStatus-style delivery are moot.
Synthetic *Synthetic
// Redirect, when non-nil, is a computed 3xx to return immediately without
// touching cache or origin (from a matching `redirect` rule). Like Synthetic it
// short-circuits the lifecycle in RECV; the server writes the status + Location.
// At most one of Synthetic/Redirect is set per request (first matching RECV
// terminal wins).
Redirect *Redirect
// Upstream is the name of the upstream/cluster the request was routed to (from
// `route`), or "" for the site default. The `upstream` matcher in later phases
// matches against this value.
Upstream string
// CacheKey is the composed cache key (from `cache_key`, or the default
// "method host path"). Empty only if the request is Synthetic.
CacheKey string
// Purge, when non-nil, indicates the request matched a `purge` guard and is a
// cache-invalidation request the server should honor.
Purge *PurgeDecision
// ReqHeaderOps are request-header edits to apply before forwarding to origin
// (from RECV-phase `header` directives). Applied in order.
ReqHeaderOps []HeaderOp
// Rewrite, when non-nil, carries the path/query rewrite to apply to the ORIGIN
// request line (from RECV-phase `rewrite` directives), composed first-to-last.
// It affects ONLY the bytes cadish dials upstream — the cache key (CacheKey
// above) is always computed from the CLIENT-facing request, never the rewrite,
// so two clients hitting the same client URL share one cache entry regardless of
// a deterministic rewrite. nil when no `rewrite` rule matched (the common case;
// the server then forwards the client path/query unchanged).
Rewrite *RewriteDecision
}
RequestDecision is the outcome of the RECV + KEY phases (EvalRequest). The server applies it before consulting the cache.
type RespondView ¶ added in v0.2.3
type RespondView struct {
Path string
Status int
Body string
Headers []HeaderOpView
}
RespondView is a `respond PATH STATUS BODY` rule. Headers is the optional `{ header … }` block (CAD-3): literal set/append/remove ops the worker applies to the synthetic Response (never a template or a delivery-phase special — compile-guaranteed). nil when the directive had no block.
type ResponseDecision ¶
type ResponseDecision struct {
// TTL is how long a fresh object stays servable without revalidation. Zero
// means "not cacheable from a positive TTL rule" (see HitForMiss).
TTL time.Duration
// Grace is the stale-while-revalidate window after TTL during which a stale
// object may be served while an async revalidation runs. Zero means no grace.
Grace time.Duration
// MaxStale (D60) is the additional window after grace during which a past-grace
// object may still be served, but ONLY as a fallback when the origin fetch fails
// (cache-status HIT-STALE-ERROR). Zero means no error-fallback window — the entry
// behaves exactly as today once grace elapses. Server-only in v1 (not projected
// to the edge IR). Measured from storedAt like TTL/Grace; the servable ceiling is
// storedAt + TTL + Grace + MaxStale.
MaxStale time.Duration
// HitForMiss, when > 0, records a "do not cache this key" decision for the
// given duration (Varnish hit-for-miss): the body is not stored but the
// negative decision is, to avoid stampeding the origin on a transient error.
// When HitForMiss > 0, TTL/Grace are zero and the object is not cached.
HitForMiss time.Duration
// StoreTier is the cache tier the object should be stored in: "ram" or "disk"
// (from `storage`). Empty means the server's default tier routing applies.
StoreTier string
// Cacheable reports whether a positive cache_ttl rule matched (TTL set). It is
// false for hit-for-miss and for the (unusual) case of no matching rule.
Cacheable bool
// ForcedPrivate reports that this response is Cacheable ONLY because `cache_unsafe`
// overrode an origin Cache-Control that marked it unshareable (private/no-store/
// no-cache/s-maxage=0). cadish stores it at its OWN tier (the operator's opt-in), but
// the server must NOT advertise `public` downstream (R13/D96) — it emits
// `private, max-age=N` so downstream SHARED caches (CDN/edge/other users) refuse the
// confidential response while cadish's own freshness index (not the emitted header)
// still drives its caching. False for a normal public store (public, max-age=N).
ForcedPrivate bool
// CredentialedStore reports that this response is being stored under a SHARED key in a
// `cache_credentialed @scope` (D101) ON A POSITIVE in-scope cache_ttl signal (the sole
// storage gate). The signal FORCE-OVERRIDES + STRIPS the per-user `Set-Cookie` AND the weak
// Cache-Control/Pragma/Expires refusals (no-store/private/no-cache) — the custom-VCL `if
// (X-Cache-Ttl) { unset set-cookie; unset Cache-Control; set ttl }`. The server strips them
// from the stored+delivered response (Set-Cookie + Pragma explicitly; Cache-Control +
// Expires via setSharedFreshness), so the SHARED entry NEVER carries a per-user Set-Cookie
// (the absolute confidentiality invariant) and cadish never replays a no-store it just
// cached. Mutually exclusive with ForcedPrivate (cache_credentialed is a deliberate opt-in
// to SHARE, never marked private). False outside such a scope.
CredentialedStore bool
// StripHeaders names the from_header-family control headers a matching cache_ttl
// rule CONSUMED from the origin response (the TTL / grace / max_stale header names,
// whichever were configured). The server deletes them before storing+delivering so
// the origin↔cache control contract (X-Cache-Ttl/X-Cache-Grace/…) never leaks to the
// client (Varnish's `unset beresp.http.*`). nil unless a from_header-family rule
// actually applied, so a site without the feature pays nothing and the streaming
// fast path is untouched.
StripHeaders []string
}
ResponseDecision is the outcome of the ORIGIN/store phase (EvalResponse). It is split from EvalRequest because cache_ttl selectors can branch on the response status, which is only known after the origin replies.
type RewriteDecision ¶
RewriteDecision is the resolved origin-request rewrite: the final path and the final raw query string cadish should send upstream, already computed from the matching `rewrite` rules. The server applies it to the origin request only; it never feeds the cache key. Path is the rewritten path; RawQuery is the rewritten, already-encoded query (without a leading '?'), or "" for no query.
type ScopeView ¶ added in v0.2.3
type ScopeView struct {
Always bool // true => unconditional (nil scope)
Names []string // referenced @matcher names
Inline []MatcherView // anonymous inline matchers in the scope (name == "")
}
ScopeView is the neutral view of a directive scope: an OR set of matcher NAMES. A nil ScopeView (Always=true) means the directive is unscoped (always matches). Inline (anonymous) matchers are surfaced as Inline so the projector can still render them; named refs are in Names.
type SecurityDecision ¶
type SecurityDecision struct {
Block bool // a deny fired and was enforced -> server returns Status now
Monitor bool // a deny fired but monitor mode suppressed it -> would-block, passes
Allow bool // an allow short-circuited the gate
Status int // the block status (403) when Block is true
Rule string // representative matcher name of the rule that fired ("" if none/inline)
// RateLimit is set (non-nil) when a `rate_limit` rule applies to this request
// (WAF v1b): the PURE gate identifies the rule and computes the bucket KEY here,
// WITHOUT counting. The server consults its in-memory token bucket with this and
// returns 429 + Retry-After (or, in monitor mode, logs would-429 and passes). It
// is nil when no rate_limit rule applies, or when an allow/deny short-circuited
// the gate first (gate order: allow -> deny -> rate_limit).
RateLimit *RateLimitHit
}
SecurityDecision is the outcome of the security gate (EvalSecurity). The server applies it FIRST, before computing the cache key or consulting cache/origin.
Block is true when a `deny`/`block` rule fired and the rule was NOT in monitor mode: the server must return Status (403) immediately, touching neither cache nor origin. When Block is false the request proceeds normally through the rest of the lifecycle.
Monitor records that a `deny` rule WOULD have blocked but did not (global or per-rule monitor mode, decision #19): the server logs/counts a "would-block" and lets the request pass. Allow records that an `allow` rule short-circuited the gate (an office/monitoring IP that is never blocked). Rule is a representative name for metrics/audit.
type SetupLowering ¶ added in v0.2.3
type SetupLowering string
SetupLowering names WHERE a setup-phase directive's meaning is lowered — the single typed consumer that turns its AST into behavior. Arch review §5 R1 named the recurring check≠run root cause: setup semantics were re-derived in independent walks, one of them a hand-mirror (config.validateOriginStructure) kept in sync by hand. CAD-60 deleted that mirror by giving check and run ONE origin lowering; this table makes the omission class a RED BUILD: TestEverySetupDirectiveHasLowering asserts every DirectiveSetup row points at a lowering owner (or an explicit not-standalone-lowered marker with a reason), so a NEW setup directive can't merge with its meaning silently re-derived in a third place. See developer-docs/specs/single-lowering.md.
const ( // LowerConfigOrigins: config.lowerOne / walkUpstreamDirectives — the CAD-60 // unification. Build (buildOrigins) and check (ValidateStructure → lowerOrigins) // consume the SAME lowering; the former validateOriginStructure hand-mirror is gone. LowerConfigOrigins SetupLowering = "config:origins" // LowerConfigCache: config.parseCacheBlock — the pure, side-effect-free half of // buildStore, already shared by ValidateStructure. LowerConfigCache SetupLowering = "config:cache" // LowerConfigTLS: tlsacme.SiteConfigFromSite → the typed tlsacme.SiteConfig, consumed // by both buildSite and every check TLS pass (the reference model). LowerConfigTLS SetupLowering = "config:tls" // LowerConfigGeo: config.buildGeo (openFiles-gated source build), shared by // ValidateStructure at openFiles=false. LowerConfigGeo SetupLowering = "config:geo" // LowerConfigTrustProxy: config.buildSiteTrustProxies. LowerConfigTrustProxy SetupLowering = "config:trust_proxy" // LowerConfigGlobal: a leading-options-block *FromFile constructor // (admin/security/proxy_protocol/server/access_log/strict_host), run identically by // loadFromSource and validateStructure. LowerConfigGlobal SetupLowering = "config:global" // LowerConfigPoolInner: an INNER knob of an upstream/cluster pool block (lb, sticky, // host_header, sni, http_reuse, tls_insecure, ca_file, alpn, resolve). It is lowered // by config.lowerOne AS PART OF the enclosing pool's spec — it has no standalone // top-level lowering, and this tag records that (not-standalone, with the pool as // owner) so the guard doesn't mistake it for an un-lowered directive. LowerConfigPoolInner SetupLowering = "config:pool-inner" // LowerPipeline: pipeline.Compile lowers it into the *Pipeline. These are "setup" // only in the COST sense (parse-once, no per-request cost); their MEANING is the // request/key half the pipeline owns — cache_unsafe, client_cache_control, // device_detect, normalize, tenant, classify, edge, monitor. LowerPipeline SetupLowering = "pipeline" // LowerSplice: pipeline.SpliceImports expands it before any lowering runs (import). LowerSplice SetupLowering = "splice" )
func LookupSetupLowering ¶ added in v0.2.3
func LookupSetupLowering(name string) (SetupLowering, bool)
LookupSetupLowering returns the lowering owner of a setup directive. ok is false for a non-setup directive (or an unknown name).
type StorageView ¶ added in v0.2.3
type StorageView struct {
SelKind string // status_in|status_not_in|scope|default
Codes []int
Scope ScopeView
Tier string // ram|disk
RespHeader *MatcherView // optional response-phase resp_header AND-term (see TTLView.RespHeader)
}
StorageView is a compiled storage tier rule.
type SubMatcherView ¶ added in v0.2.3
SubMatcherView is the neutral view of one `all` sub-term: a named-matcher reference XOR'd with an optional negate (the secTerm{m, negate} pair).
type Synthetic ¶
Synthetic is a canned response produced by a `respond` directive.
Headers carries the directive's optional `{ header … }` block (CAD-3): literal operator ops (set/append/remove) the server applies to the synthetic response AFTER its base Content-Type/Content-Length — a respond short-circuits before the deliver phase, so this is the only way operator headers reach it. An op may override the default Content-Type; Content-Length is protected (compile-rejected and runtime-skipped). nil when the directive had no block.
type TTLView ¶ added in v0.2.3
type TTLView struct {
SelKind string // status_in|status_not_in|scope|default
Codes []int // status_in/status_not_in
Scope ScopeView // scope selector
TTL time.Duration
Grace time.Duration
MaxStale time.Duration // max_stale (D60): stale-on-error window beyond ttl+grace
HitForMiss time.Duration
IsHFM bool
FromHeader string // origin response header to read the TTL from ("" => static TTL)
// GraceFromHeader / MaxStaleFromHeader name origin response headers the grace /
// max_stale window is read from per-response ("" => the static Grace / MaxStale
// above is used). The literal stays as the fallback when the header is
// absent/unparseable; the worker mirrors the server's resolveGraceMaxStale.
GraceFromHeader string
MaxStaleFromHeader string
// RespHeader is an optional RESPONSE-phase `resp_header NAME VALUE` term ANDed in
// front of the selector (SelKind/Codes/Scope): the rule applies only when the origin
// response carries the named header value. nil for every rule that does not name
// resp_header. The worker evaluates it before the kind selector, mirroring the server.
RespHeader *MatcherView
// StripHeaders names the from_header-family control headers this rule CONSUMES from
// the origin response (X-Cache-Ttl/X-Cache-Grace/X-Cache-Max-Stale, whichever are
// configured). The worker removes them before store + deliver, exactly as the server
// does (handler.go), so the internal origin↔cache contract never leaks to the client.
// nil for a plain rule that sources nothing from a header (the common case).
StripHeaders []string
}
TTLView is a compiled cache_ttl rule. Selector is a status set OR a scope OR default.
type TemplateEnv ¶
type TemplateEnv struct {
Host string
Path string
Query string // raw, canonically-ordered query string without the leading '?'
Capture []string
Header http.Header // request headers, for {http.NAME}; may be nil
ClientIP string // resolved client IP (no port), for {client_ip}
// Geo / GeoContinent / GeoRegion are the server-resolved geo classes, for the
// {geo} / {geo.continent} / {geo.region} placeholders. Left zero on the redirect
// path (no geo in scope there).
Geo string
GeoContinent string
GeoRegion string
// Device is the resolved device class for the {device} template token (desktop /
// mobile / tablet / bot). Mirrors the {device} cache-key token but makes the
// resolved bucket available in header values and redirect targets. Filled from
// req.Device on both the header path and the redirect path (redirect.go sets
// Device: req.Device). Left "" only when no device pre-pass ran — i.e. the cache
// key does not use {device}; see Pipeline.UsesDeviceToken.
Device string
// QueryParams holds the parsed query parameters (url.Values) for the {query.NAME}
// placeholder, which resolves to NAME's first decoded value from the query string
// (empty string when the parameter is absent). Filled from req.Query on the header
// path and the redirect path; nil means the placeholder is left verbatim.
QueryParams url.Values
// Scheme is the request scheme for the {proto}/{scheme} placeholder: "https"
// when cadish terminated TLS for the inbound connection, else "http". It is filled
// from Request.TLS on BOTH the dynamic-header path (fillHeaderTemplateEnv) and the
// redirect path (redirectRule.eval sets it inline). If left "" (a caller that does not
// set it), named() defaults it to "http".
Scheme string
}
TemplateEnv carries the request-derived values a target/header template can interpolate. Capture is the regex submatch slice (Capture[0] is the whole match, Capture[1] is $1, …) from the matcher that selected the directive, or nil when no regex capture is in scope.
This is the shared substitution primitive behind the computed `redirect` directive (target templates) and dynamic header values (#17). It is a pure, allocation-light string expander — no regex compilation, no I/O.
Header and ClientIP are the request-scoped sources for the `{http.NAME}` and `{client_ip}` placeholders (dynamic header values, #17). They are left zero by the redirect path, which uses only the scalar/capture placeholders.
type TenantMapView ¶ added in v0.2.3
TenantMapView is one tenant rule (an exact host or `*.suffix` wildcard).
type TenantView ¶ added in v0.2.3
type TenantView struct {
FromHeader string // "" => derive from Host; else the header name
Rules []TenantMapView // ordered pattern -> name
Default string
}
TenantView is the neutral view of a request-derived `tenant { … }` resolver.
type TierPolicyView ¶ added in v0.2.3
TierPolicyView is the neutral view of one per-scope edge cache-tier policy.
type TransformView ¶ added in v0.2.3
TransformView is one `replace OLD NEW` deliver-phase body substitution.