Transaction API (txn)
Rhai for rules (Rule Rhai) scripts receive a sandboxed txn object — the per-query policy surface on the current transaction. Methods set pools, tags, drop/retry intent, egress overrides, and read question/response metadata. They do not edit DNS wire bytes.
This page covers txn only. Lookups, metrics, logging, and runtime reads are separate host surfaces — see Host API overview.
For which hook each API allows, see Hooks and phases and Host API overview — How to read method reference pages.
Client and listener
Read-only facts about how the query arrived — client socket, transport, and listener bind label. Use for per-client or per-listener policy without high-cardinality user metrics labels.
Methods: txn.client_addr() · txn.client_ip() · txn.client_port() · txn.client_protocol() · txn.listener()
txn.client_addr()
Request + response hook · no args · returns string
Full client socket address (ip:port).
Hooks
Request hook and response hook
Behavior
- Returns the client
SocketAddras a string (for example192.0.2.1:53000). - Read-only — does not affect routing or metrics.
- Do not use client IP or address strings as user metric label values (high cardinality).
txn.client_ip()
Request + response hook · no args · returns string
Client IP address only (no port).
txn.client_port()
Request + response hook · no args · returns i64
Client UDP/TCP source port.
txn.client_protocol()
Request + response hook · no args · returns string
Transport the client used: udp or tcp.
txn.listener()
Request + response hook · no args · returns string
Configured listener bind address label (empty when unset).
Egress
When Conduit sends a query to an upstream backend, it binds a local address on your host — that is egress. You declare allowed addresses in forward.sources_v4 / forward.sources_v6 and, optionally, per-pool sources_v4 / sources_v6.
Use Rhai when egress should depend on the query or on what happened upstream:
| Goal | Request hook | Response hook |
|---|---|---|
| Same local IP for every forward on this query | txn.set_source_v4(addr) or txn.set_source_v6(addr) |
Not allowed — egress for the current attempt is already fixed |
| A different local IP only on the next retry | txn.set_retry_source_v4(addr) (stash for later) |
txn.set_retry_source_v4(addr) + txn.request_retry() when upstream failed or timed out |
If the script does not set a source, Conduit round-robins among the sources configured for the selected pool.
Allowed addresses: the IP you pass must be listed in forward.sources_* or the pool’s sources_* for that address family. If it is not, Conduit still answers the client — it ignores the override and picks another configured source (same as built-in set_source_v4 in YAML). See Dual-stack forwarding — Choosing an egress source.
Pool and egress are separate: txn.set_pool("premium") does not change egress by itself. Set both when you need a specific pool and a specific bind address. On one rule, list built-in set_pool before set_source_* so Forward checks the address against the pool you intended.
Example — egress from a lookup table (request hook):
let egress = lookup("egress_map", txn.question().qname);
if egress != "" {
txn.set_source_v4(egress);
}
Example — different egress only on retry (response hook after a slow upstream):
if txn.last_forward_ms() > 800 {
txn.set_retry_source_v4("10.0.0.9");
txn.request_retry();
}
Retry-specific sources apply to one retry forward, then Conduit returns to the standing source from the request hook (if any). Full lifecycle: Source selection.
Methods: txn.set_source_v4 · txn.set_source_v6 · txn.set_retry_source_v4 · txn.set_retry_source_v6 · txn.clear_retry_source_v4 · txn.clear_retry_source_v6
txn.set_source_v4
Request hook only · addr: string (IPv4) · no return; script error on response hook or invalid address
Sets the standing local IPv4 bind for every forward attempt on this transaction.
Hooks
Request hook only
Arguments / return
| Parameter | Type | Notes |
|---|---|---|
addr |
string | IPv4 address (for example "10.0.0.5") |
| return | — | No return value on success |
Returns a script error (phase guard or parse failure) when called on the wrong hook or with an invalid address — see Behavior.
Summary: Sets the standing local IPv4 bind for every forward attempt on this transaction (request hook only). Conduit checks the address against pool/global allowed sources at Forward — disallowed values fail open to round-robin.
Behavior
- Sets
source_override_v4on the transaction — the local IPv4 address Conduit uses when binding for upstream Forward to an IPv4 backend. - Runs before Route on the request hook; the override applies to every forward attempt on this transaction unless a one-shot
retry_source_override_*wins on a retry forward (see resolution order above). - At Forward, Conduit uses the override only when it is in the allowed set for the selected pool:
forward.sources_v4∪ that pool’ssources_v4(when the pool list is non-empty, pool sources apply for round-robin; the allowed union is still checked for overrides). If the address is not allowed, Conduit does not fail the query — it falls back to ordinary round-robin among configured sources. Same rule as built-inset_source_v4. See Dual-stack forwarding — Choosing an egress source. - YAML
set_source_v4values are checked against the global union at validate/reload. Rhai accepts any parseable IPv4 string at runtime — allowed-set enforcement happens at Forward, not at script compile time. Use fixed literals you have pre-validated in config, or accept fail-open fallback when an address is wrong for the pool. - Response hook: calling this method is a phase error — Conduit logs
rhai script error, skips further script effects for that hook invocation, and continues the pipeline (Sandbox limits — fail-open). Egress is already chosen before Forward on that attempt. - Pair with
txn.set_poolwhen both matter: built-in actions on the same rule should listset_poolfirst so Forward checks the override against the pool you selected; arhaistep at position N sees prior built-in effects. See Action order on one rule. - Independent of
txn.set_source_v6— Forward picks v4 or v6 override to match the backend address family.
YAML equivalent
- type: set_source_v4
value: "10.0.0.5"
Example
Pin egress from a lookup table on the request hook:
let egress = lookup("egress_map", txn.question().qname);
if egress != "" {
txn.set_source_v4(egress);
}
Fixed literal (repository fixture pattern — address must be in forward.sources_v4 or pool sources_v4 for the override to take effect):
txn.set_source_v4("127.0.2.1");
txn.set_source_v6
Request hook only · addr: string (IPv6) · no return; script error on response hook or invalid address
Sets the standing local IPv6 bind for every forward attempt on this transaction.
Hooks
Request hook only
Arguments / return
| Parameter | Type | Notes |
|---|---|---|
addr |
string | IPv6 address (for example "2001:db8::1" or "::1") |
| return | — | No return value on success |
Returns a script error (phase guard or parse failure) when called on the wrong hook or with an invalid address — see Behavior.
Summary: Sets the standing local IPv6 bind for every forward attempt on this transaction (request hook only). Same allowed-source and fail-open rules as set_source_v4, for IPv6 backends.
Behavior
- Sets
source_override_v6on the transaction — the local IPv6 address Conduit uses when binding for upstream Forward to an IPv6 backend. - Runs before Route on the request hook; the override applies to every forward attempt on this transaction unless a one-shot
retry_source_override_*wins on a retry forward (see resolution order above). - At Forward, Conduit uses the override only when it is in the allowed set for the selected pool:
forward.sources_v6∪ that pool’ssources_v6. If the address is not allowed, Conduit falls back to round-robin among configured sources — same as built-inset_source_v6. See Dual-stack forwarding — Choosing an egress source. - YAML
set_source_v6values are checked at validate/reload. Rhai enforces parse + request-hook phase only; allowed-set enforcement is at Forward. - Response hook: phase error — same fail-open behavior as
txn.set_source_v4. - Pair with
txn.set_poolwhen both matter — listset_poolbefore source actions on the same rule when using built-ins; scripts see built-in effects that ran earlier in the action list. See Action order on one rule. - Independent of
txn.set_source_v4— only the override matching the backend family is used.
YAML equivalent
- type: set_source_v6
value: "::1"
Example
Request hook — pin IPv6 egress for queries routed to an IPv6 backend pool:
if txn.question().qname.ends_with(".v6.example.") {
txn.set_pool("v6-upstream");
txn.set_source_v6("::1");
}
txn.set_retry_source_v4
Request + response hook · addr: string (IPv4) · one-shot retry egress; pair with request_retry
Stashes a one-shot IPv4 egress override consumed on the next retry forward only.
Hooks
Request hook and response hook
Arguments / return
| Parameter | Type | Notes |
|---|---|---|
addr |
string | IPv4 address (for example "10.0.0.5") |
| return | — | No return value on success |
Returns a script error on invalid address parse failure.
Summary: Stashes a one-shot IPv4 egress used only on the next retry forward (attempt_count > 1). Does not trigger retry — pair with request_retry on the response hook when needed.
Behavior
- Sets
retry_source_override_v4— a one-shot local IPv4 bind for the next retry forward only (attempt_count > 1at Forward). - Does not trigger retry. Pair with
txn.request_retry()/txn.request_retry_now()on the response hook (or built-inretry/retry_now) when you want failover to use this egress. - On the first forward (
attempt_count == 1at Forward), the stash is ignored — standingsource_override_v4or pool/global round-robin applies instead. - On a retry forward, this override wins over standing
source_override_v4for that attempt only (then the stash is cleared). Same allowed-set check at Forward asset_source_v4; disallowed addresses fail open to round-robin. - YAML
set_retry_source_v4values are checked against the globalsources_v4union at validate/reload (both hooks). Rhai enforces parse only at runtime; allowed-set enforcement is at Forward. - Typical pattern: request rule sets standing egress with
txn.set_source_v4; response rule setstxn.set_retry_source_v4when upstream outcome warrants a different bind on retry.
YAML equivalent
- type: set_retry_source_v4
value: "10.0.0.5"
Example
Response hook — alternate egress on SERVFAIL retry:
if txn.response_rcode() == Rcode::SERVFAIL {
txn.set_retry_source_v4("10.0.0.5");
txn.request_retry();
}
txn.set_retry_source_v6
Request + response hook · addr: string (IPv6) · one-shot retry egress; pair with request_retry
Stashes a one-shot IPv6 egress override consumed on the next retry forward only.
Hooks
Request hook and response hook
Arguments / return
| Parameter | Type | Notes |
|---|---|---|
addr |
string | IPv6 address (for example "2001:db8::1" or "::1") |
| return | — | No return value on success |
Returns a script error on invalid address parse failure.
Summary: Stashes a one-shot IPv6 egress for the next retry forward only. Independent of the v4 retry stash; only the family matching the backend is used.
Behavior
- Sets
retry_source_override_v6— one-shot local IPv6 bind for the next retry forward only (attempt_count > 1at Forward). - Does not trigger retry. Pair with
txn.request_retry()/txn.request_retry_now()when failover should use this egress. - First forward ignores the stash; standing
source_override_v6or pool/global round-robin applies. - On retry forward, wins over standing
source_override_v6for one attempt, then clears. Same allowed-set and fail-open rules asset_source_v6. - Independent of
txn.set_retry_source_v4— only the override matching the backend family is used.
YAML equivalent
- type: set_retry_source_v6
value: "::1"
Example
Request hook — pre-stash alternate v6 egress before first forward (used only if a later retry occurs):
txn.set_retry_source_v6("::1");
txn.clear_retry_source_v4
Request + response hook · no args · clears stashed retry IPv4 override
Clears a stashed retry IPv4 egress override without affecting standing overrides.
Hooks
Request hook and response hook
Arguments / return
No arguments. No return value.
Summary: Clears retry_source_override_v4 without changing standing source_override_v4 from set_source_v4.
Behavior
- Clears
retry_source_override_v4on the transaction. - Does not clear standing
source_override_v4fromset_source_v4. - Use when a stashed retry egress should not apply (for example after deciding same-pool
retrywithout changing bind IP).
YAML equivalent
- type: clear_retry_source_v4
value: ""
Example
txn.clear_retry_source_v4();
txn.clear_retry_source_v6
Request + response hook · no args · clears stashed retry IPv6 override
Clears a stashed retry IPv6 egress override without affecting standing overrides.
Hooks
Request hook and response hook
Arguments / return
No arguments. No return value.
Summary: Clears retry_source_override_v6 without changing standing source_override_v6 from set_source_v6.
Behavior
- Clears
retry_source_override_v6on the transaction. - Does not clear standing
source_override_v6fromset_source_v6.
YAML equivalent
- type: clear_retry_source_v6
value: ""
Example
txn.clear_retry_source_v6();
Timing and clocks
Wall-clock time and forward attempt count on the current transaction. Custom policy counters live on the separate metrics scope object — see User metrics.
Methods: txn.elapsed_ms() · txn.get_attempt_count() · txn.last_forward_ms() · txn.now_unix() · txn.utc_hour() · txn.utc_weekday()
txn.elapsed_ms
Request + response hook · no args · returns i64 (ms since transaction start)
Returns milliseconds since the transaction started.
Hooks
Request hook and response hook
Arguments / return
No arguments. Returns i64 — milliseconds elapsed since the transaction started.
Summary: Wall-clock milliseconds since the transaction started — includes request rules, Lookup (and forward-provider work), and any prior response-rule passes. Not upstream RTT alone.
Behavior
- Measures wall-clock time from transaction creation (when Conduit accepted the client query) through the current hook invocation.
- Includes time spent in earlier pipeline phases on this transaction: request rules, Lookup (including forward-provider route / forward / wait when upstream runs), and any prior response rules passes on retries.
- Not upstream RTT alone — use
txn.last_forward_ms()for the most recent forward attempt’s upstream wait. - On the request hook, elapsed time is usually small (rules only, no upstream wait yet).
- On the response hook, elapsed time includes the wait for the current forward attempt’s answer or timeout.
- The same clock backs transaction duration limits in Retries and transactions (
max_txn_duration_mswhen configured). - Read-only — does not change the transaction.
YAML equivalent
None.
Example
Response script — increment a user metric when a tagged query had a slow upstream forward (repository fixture slow-login-alert.rhai / with-rhai-slow-login.yaml):
if txn.has_tag("suspicious") && txn.last_forward_ms() > 500 {
metrics.inc("slow_login", 1);
}
txn.last_forward_ms
Request + response hook · no args · returns i64 (latest upstream RTT; 0 on request hook)
Returns upstream RTT in ms for the latest forward attempt (0 when no forward ran, including cache hits).
Hooks
Request hook and response hook
Arguments / return
No arguments. Returns i64 — milliseconds for the most recent upstream forward attempt on this transaction.
Summary: Upstream send→answer-or-timeout time for the most recent forward attempt. Always 0 on the request hook and when no forward attempt ran (for example a cache hit). Use txn.answer_source() or built-in metrics to distinguish cache from forward.
Behavior
- Measures upstream send → answer or timeout time for the latest forward attempt inside the forward lookup provider — the same interval recorded in
conduit_forward_duration_secondswhen metrics are enabled. - Not end-to-end transaction time — use
txn.elapsed_ms()for wall-clock time since the client query arrived (includes request rules, prior retries, and response-rule passes). - Request hook: always
0— no forward attempt has completed yet. - Cache hit:
0— forward did not run; do not use RTT alone to detect cache hits. - Response hook after forward: set after the current attempt’s forward completes (success, timeout, or forward error that still runs response rules). On retry, each new attempt overwrites the value with that attempt’s RTT.
- Includes TCP fallback time when UDP returns TC and Conduit retries over TCP on the same attempt.
- Timeout attempts record approximately
forward.timeout_ms(see Forward). - Hard forward failures that skip Response rules (for example immediate SERVFAIL to Send) still set the value on the transaction, but response-hook scripts do not run for that pass.
- Read-only — does not change the transaction.
- Available regardless of
metrics.enabled— this is per-transaction state for policy, not Prometheus export.
YAML equivalent
None.
Example
Retry only when the latest upstream attempt was slow:
if txn.last_forward_ms() > 800 && txn.get_attempt_count() == 1 {
txn.request_retry();
}
txn.get_attempt_count
Request + response hook · no args · returns i64 (forward attempt count at hook entry)
Returns how many forward attempts have started when the hook runs.
Hooks
Request hook and response hook
Arguments / return
No arguments. Returns i64 — the transaction’s forward attempt count at hook entry.
Summary: Forward attempt count at hook entry: 0 on request hook, 1 after the first Route/Forward round trip, and so on for retries.
Behavior
- Reflects how many times Route has selected a pool and recorded a forward attempt on this transaction. Increment happens at Route, before Forward for that attempt.
- Request hook: always
0— no Route has run yet. - First response hook (after one upstream round trip):
1. - Second response hook (after one retry):
2, and so on. - Use on the response hook to branch on first vs subsequent upstream outcomes (for example only retry once, or different metrics per attempt). Pair with Retries and transactions and
txn.request_retry. - Read-only — does not change the transaction.
- The value matches
attempt_counton event export extra fields when that field is enabled.
YAML equivalent
None. Declarative rules do not expose attempt count as a selector today.
Example
Response script — act only on the first upstream failure:
if txn.get_attempt_count() == 1 && txn.response_rcode() == Rcode::SERVFAIL {
txn.request_retry();
}
txn.now_unix()
Request + response hook · no args · returns i64
UTC Unix timestamp (seconds) when the transaction started.
Behavior
- Wall-clock UTC seconds since epoch, captured when Conduit accepted the client query.
- Stable for the lifetime of the transaction (does not advance on retries).
- Use with
txn.utc_hour()/txn.utc_weekday()for maintenance-window style policy.
txn.utc_hour()
Request + response hook · no args · returns i64 (0–23 UTC)
Hour-of-day in UTC when the transaction started.
txn.utc_weekday()
Request + response hook · no args · returns i64 (1–7 UTC)
ISO weekday in UTC when the transaction started (1 = Monday, 7 = Sunday).
Introspection
Read-only identifiers for correlating script behavior with tracing, logs, and config reloads.
Methods: txn.txn_id() · txn.config_generation() · txn.rule_name()
txn.txn_id()
Request + response hook · no args · returns i64
Internal transaction id (same value as txn_id in debug logs and conduitctl trace).
Behavior
- Per-worker sequence — not globally unique across the cluster.
- Read-only. Do not use as a user metric label (disallowed key
txn_id). - Pair with
log.info/log.warnwhen debugging policy on a single query — see Script logging.
txn.config_generation()
Request + response hook · no args · returns i64
Config snapshot generation active when this transaction started.
Behavior
- Matches
conduit_config_generationfor the snapshot this query runs under. - Useful for canary rules after reload — branch policy when generation crosses a threshold.
Answer provenance
Inspect where Lookup got this attempt's wire answer — cache vs forward — and which named cache, pool, or backend produced it. On the request hook, optionally bypass the cache for this query. Distinct from Rhai lookup(table, key) — see Glossary — Lookup vs lookup(table, key).
On the response hook, branch on source then read the matching identity:
if txn.answer_source() == "cache" {
log.info(`served from cache=${txn.cache_instance()}`);
} else if txn.answer_source() == "forward" {
log.info(`served from pool=${txn.selected_pool()} backend=${txn.selected_backend_name()}`);
}
txn.selected_pool() is also routing intent on the request hook (see Routing). On a pure cache hit, backend fields are usually empty because forward did not run.
Methods: txn.answer_source() · txn.cache_instance() · txn.selected_backend() · txn.selected_backend_name() · txn.selected_pool() · txn.set_cache_lookup_eligible(bool)
txn.answer_source()
Response hook · no args · returns string
Which Lookup provider produced this attempt's wire answer — cache, forward, or empty before an answer exists.
Hooks
Response hook — always empty on the request hook.
Behavior
- Answers: did Lookup serve this attempt's answer from the cache provider or from the forward provider?
cache— a cache provider served the wire answer; usetxn.cache_instance()for whichcaches[].name.forward— the forward provider produced the answer; usetxn.selected_pool()/txn.selected_backend()/txn.selected_backend_name()for which pool and backend.- Empty string — no answer yet or unknown source (for example some synthesized errors).
txn.cache_instance()
Response hook · no args · returns string
Named cache instance on cache hits; empty otherwise.
Behavior
- Set when
txn.answer_source()iscache— matches thecaches[].namethat served the hit. - Empty on forward-produced answers and on the request hook.
txn.selected_backend()
Request + response hook · no args · returns string
Upstream backend socket address for the current forward attempt (empty when unset).
Behavior
- Identity for the forward half of answer provenance — pair with
txn.answer_source()forward. - Usually empty on a pure cache hit (forward did not run). Also available as
backendontxn.response().
txn.selected_backend_name()
Request + response hook · no args · returns string
Upstream backend logical label for the current forward attempt: the configured backend name when set, otherwise the socket address. This is the same name-when-set identity used in metrics, logs, traces, and event-sink backend filters. Empty when no backend is selected.
Behavior
- Identity for the forward half of answer provenance — pair with
txn.answer_source()forward. - Usually empty on a pure cache hit (forward did not run). Also available as
backend_nameontxn.response().
txn.selected_pool()
Request + response hook · no args · returns string
Pool name selected for the current forward attempt (empty when unset).
Behavior
- On the response hook after a forward answer, the pool that served this attempt — pair with
txn.answer_source()forward. - On the request hook (and before Route), reflects routing intent from
txn.set_pool/ rules; may be set even when a later cache hit never forwards. Also available aspoolontxn.response().
txn.set_cache_lookup_eligible
Request hook only · eligible: bool · no return
When false, the cache provider bypasses this query for the current Lookup attempt.
Hooks
Request hook only — ignored on the response hook.
Behavior
- Default eligibility is
truewhen the script does not call this method. - Before upstream I/O, the forward provider sets eligibility
false. A Response-rules retry re-enters Lookup with that flag stillfalse, so the cache is skipped. Request rules do not run again on retry, and this method is ignored on the response hook — nothing restores eligibility on the same transaction. - See DNS answer cache — Cache eligibility.
Outcomes
Drop, retry, and response RCODE metadata on the transaction. Soft calls (drop_query, request_retry) set intent that Conduit resolves after the rest of the script (and any later built-in actions on the same rule) finish. Hard calls (drop_query_now, request_retry_now) stop the script immediately — no further Rhai runs on that rule pass. See Outcome at end of rule for precedence (drop beats retry; soft drop blocks hard retry unless clear_drop ran earlier on the rule).
Methods: txn.clear_drop · txn.clear_retry · txn.drop_query · txn.drop_query_now · txn.request_retry · txn.request_retry_now · txn.set_rcode
txn.drop_query
Request + response hook · no args · no return
Sets soft-drop intent — resolved at end of rule; later script lines still run.
Hooks
Request hook and response hook
Arguments / return
| Parameter | Type | Notes |
|---|---|---|
| none | — | |
| return | — | No return value |
Summary: Soft drop — the query stops at the end of this rule pass with no DNS reply if drop intent is still set. Does not stop the script immediately.
Behavior
- Sets soft-drop intent on the transaction (same as built-in
drop). - Later lines in the same script still run. Built-in actions after a
rhaistep on the same rule also still run when the script does not hard-stop. - Conduit resolves outcome once after all actions on the rule: if soft drop is still set, the query drops; otherwise policy continues. See Outcome at end of rule.
- If both soft drop and soft retry are set at the end of a response rule, drop wins.
- On the request hook, drop prevents upstream Forward for this transaction. On the response hook, drop prevents Send to the client.
- Use
txn.clear_drop()later on the same rule to cancel soft-drop intent from an earlierdrop/txn.drop_query()call. - Pair with
txn.drop_query_now()when policy should stop the script immediately instead — seetxn.drop_query_now.
YAML equivalent
- type: drop
Example
Request hook — custom metric and soft-drop a blocked name (walkthrough: Rhai policy — Blocklist drop; repository fixture blocklist.rhai / with-rhai-blocklist.yaml):
if lookup("blocklist", txn.question().qname) == "block" {
metrics.inc("block_hits", 1);
txn.drop_query();
}
On a silent request drop, metrics.inc is the usual choice for block counters; set_tag can still gate event export query frames when sinks use tag_required. See User metrics.
txn.drop_query_now
Request + response hook · no args · no return
Hard drop — stops the script immediately; query drops with no DNS reply.
Hooks
Request hook and response hook
Arguments / return
| Parameter | Type | Notes |
|---|---|---|
| none | — | |
| return | — | No return value |
Summary: Hard drop — same outcome as soft drop (no reply) but stops the script immediately; no further Rhai or later built-in actions on this rule run.
Behavior
- Same client-visible outcome as
txn.drop_query()— no DNS answer — but returnsDropNowfrom the script runner so no further lines in this script execute. - When a rule lists built-in actions before
rhai, those run first; adrop_query_now()inside the script still prevents any actions after therhaistep on that rule. - Does not implicitly clear soft retry or pool stashes — it ends the rule pass. Use on both hooks when policy is final on this rule.
- Prefer
txn.drop_query()when later script logic or a later built-in action on the same rule should still run (for example metrics or tag updates before drop).
YAML equivalent
- type: drop_now
Example
Request hook — immediate drop when lookup marks the qname as blocked:
if lookup("blocklist", txn.question().qname) == "block" {
txn.drop_query_now();
}
txn.clear_drop
Request + response hook · no args · no return
Clears soft-drop intent set earlier on this rule pass.
Hooks
Request hook and response hook
Arguments / return
| Parameter | Type | Notes |
|---|---|---|
| none | — | |
| return | — | No return value |
Summary: Cancels soft-drop intent from an earlier drop / txn.drop_query() on the same rule — use before request_retry_now when retry should win over an earlier soft drop.
Behavior
- Clears soft-drop intent on the transaction for this rule evaluation (same as built-in
clear_drop). - Only affects intent set on this rule pass — not drops already committed by an earlier matching rule.
- Typical use: an earlier action (built-in or Rhai) set soft drop, but later script logic decides to retry instead. Call
txn.clear_drop()beforetxn.request_retry()/txn.request_retry_now()— hard retry does not clear soft drop automatically. See Outcome at end of rule.
YAML equivalent
- type: clear_drop
Example
Response script — cancel an earlier soft drop and retry instead:
if txn.response_rcode() == Rcode::SERVFAIL {
txn.clear_drop();
txn.request_retry();
}
txn.request_retry
Response hook only · no args · no return; no effect on request hook
Soft retry — resolved at end of rule; re-enters Lookup when still set.
Hooks
Response hook only
On the request hook, calls are ignored (no effect, no error).
Arguments / return
| Parameter | Type | Notes |
|---|---|---|
| none | — | |
| return | — | No return value |
Summary: Soft retry — re-enter Lookup after this rule if retry intent is still set and the query is not dropped. Does not stop the script immediately.
Behavior
- Sets soft-retry intent (same as built-in
retry). Conduit resolves it after the rest of the script and any later built-in actions on the rule. - Re-enters Lookup (full provider chain; forward provider may Route / Forward again) when retry wins at end of rule — subject to orchestrator caps (Retries and transactions).
- Does not pick a pool by itself — uses
selected_poolunlessretry_poolis set (Pool selection lifecycle). Pair withtxn.set_retry_poolwhen failover should use a different pool. - Blocked when soft drop is still set at end of rule — drop wins. Does not clear soft drop.
txn.request_retry_now()stops the script immediately instead; use when no further script lines should run.- Use
txn.clear_retry()to cancel soft-retry intent from an earlier call on the same rule.
YAML equivalent
- type: retry
Response hook only — invalid on hook: request (config validation fails).
Prefer declarative retry when retry_pool is already set on the request hook — see Retries and transactions — Declarative examples. Use Rhai when the response rule needs logic beyond an rcode selector.
Example
Response hook — retry after slow SERVFAIL (Rhai adds a latency gate declarative selectors cannot express):
if txn.response_rcode() == Rcode::SERVFAIL && txn.last_forward_ms() > 2000 {
txn.set_retry_pool("secondary");
txn.request_retry();
}
When retry_pool is already stashed on the request hook, the response script can call txn.request_retry() alone. Repository fixture servfail-retry.rhai / with-rhai-servfail-retry.yaml exercises API parity with built-in set_retry_pool + retry.
txn.request_retry_now
Response hook only · no args · no return; no effect on request hook
Hard retry — stops the script immediately and re-enters Lookup (unless soft drop blocks).
Hooks
Response hook only
On the request hook, calls are ignored (no effect, no error).
Arguments / return
| Parameter | Type | Notes |
|---|---|---|
| none | — | |
| return | — | No return value |
Summary: Hard retry — same retry outcome as request_retry when allowed, but stops the script immediately after setting intent.
Behavior
- Returns
RetryNowfrom the script runner — no further lines in this script run, and no built-in actions after therhaistep on this rule. - If soft drop is still set on the transaction at retry time, outcome is drop, not retry — even after
request_retry_now(). Calltxn.clear_drop()first when retry should override an earlier soft drop on the same rule. - Does not clear soft drop by itself. See Outcome at end of rule.
- Pool selection follows the same rules as
txn.request_retry()— pair withtxn.set_retry_pool/txn.set_poolas needed.
YAML equivalent
- type: retry_now
Response hook only — invalid on hook: request.
Example
Response script — fail over immediately on timeout with no further bookkeeping:
if txn.last_forward_ms() > 800 {
txn.set_retry_pool("secondary");
txn.request_retry_now();
}
txn.clear_retry
Response hook only · no args · no return; no effect on request hook
Clears soft-retry intent set earlier on this rule pass.
Hooks
Response hook only
On the request hook, calls are ignored (no effect, no error).
Arguments / return
| Parameter | Type | Notes |
|---|---|---|
| none | — | |
| return | — | No return value |
Summary: Cancels soft-retry intent from an earlier retry / txn.request_retry() on the same rule — the query continues toward Send unless drop intent wins.
Behavior
- Clears soft-retry intent for this rule evaluation (same as built-in
clear_retry). - Does not clear
retry_poolor standing pool choice — usetxn.clear_retry_pool()for the pool stash (Routing). - Does not undo a retry already committed by
request_retry_now()on an earlier line in the same script (hard retry already stopped the script). - Typical use: conditional retry — an earlier branch called
txn.request_retry(), but later logic decides to accept the answer instead.
YAML equivalent
- type: clear_retry
Response hook only — invalid on hook: request.
Example
if txn.get_attempt_count() >= 2 {
txn.clear_retry();
}
txn.set_rcode
Request + response hook · rcode: Rcode or string · no return
Sets response RCODE metadata on the transaction before Send.
Hooks
Request hook and response hook
(YAML set_rcode is response hook only — config validation rejects it on hook: request.)
Arguments / return
| Parameter | Type | Notes |
|---|---|---|
rcode |
Rcode or string |
Prefer Rcode::SERVFAIL; strings such as "SERVFAIL" still accepted (case-insensitive) |
| return | — | No return value |
Summary: Sets the RCODE Conduit attaches to the response metadata for this transaction — for example before Send when policy accepts an upstream answer.
Behavior
- Writes
rcodeon the transaction. Downstream phases use this when building the client response. - Accepts a
Rcodevalue or a case-insensitive string name /RCODE{n}alias. Unrecognized strings map toSERVFAIL(fail-safe default). - Does not by itself trigger drop or retry — pair with outcome methods when policy should fail over instead of accepting.
- On the response hook, commonly used after inspecting
txn.response_rcode()when rewriting metadata for the client. On the request hook, rare — prefer response-hook scripts unless you need to pre-stage metadata before upstream. - Within one rule, later
set_rcode(built-in or Rhai) overrides an earlier value on the same hook pass.
YAML equivalent
- type: set_rcode
value: SERVFAIL
Response hook only in config.
Example
Response script — normalize a policy outcome before Send:
if txn.response_rcode() == Rcode::SERVFAIL && txn.get_attempt_count() >= 3 {
txn.set_rcode(Rcode::REFUSED);
}
Query and response
Read-only access to the client question and, on the response hook, the upstream outcome metadata Conduit recorded after Wait for response. These APIs expose qname, typed DNS wire enums (RecordType, QueryClass, DnsOpcode, EdnsOptionCode), message ID, and Rcode — not full answer records or wire bytes.
On the request hook, only the question is meaningful — upstream has not answered yet. On the response hook, the question is unchanged and txn.response() / txn.response_rcode() reflect the outcome of the current forward attempt (including timeout or pool exhaustion, which Conduit typically records as SERVFAIL). See Hooks and phases — phase guards.
Methods: txn.question() · txn.response() · txn.response_rcode() · txn.response_truncated() · txn.response_answer_count() · Types: RecordType · Rcode · QueryClass · DnsOpcode · EdnsOptionCode
RecordType
Rule Rhai global · static module · DNS QTYPE enum
Named constants and TYPE{n} aliases for every IANA-assigned RR type (plus Conduit-specific ANAME); use for qtype comparisons and RecordType::from_number(n) for arbitrary wire numbers.
Availability
Registered on the Rule Rhai engine — available in every type: rhai script without import.
Constants
Each known type appears twice in the RecordType module:
| Form | Example | Wire number |
|---|---|---|
| Name | RecordType::A, RecordType::HTTPS |
1, 65, … |
TYPE{n} alias |
RecordType::TYPE1, RecordType::TYPE65 |
same |
Known names follow the IANA DNS RR type registry (for example A, AAAA, CNAME, MX, NS, PTR, SOA, SRV, TXT, HTTPS, SVCB, DNSKEY, DS, TLSA, CAA, ANY, ANAME). Unknown numbers are still valid via RecordType::from_number(n); name() on such values returns TYPE{n}.
Methods on values
| Method | Returns | Notes |
|---|---|---|
number() |
i64 |
IANA type number (0–65535) |
name() |
string | A, HTTPS, or TYPE{n} for unknown types |
==, != |
bool | Compare wire numbers — RecordType::A == RecordType::TYPE1 is true |
Constructor
| Call | Notes |
|---|---|
RecordType::from_number(n) |
Build from wire number; error if n ∉ 0…65535 |
Example
let q = txn.question();
if q.qtype == RecordType::AAAA {
txn.set_pool("v6-only");
} else if q.qtype == RecordType::from_number(99) {
// custom TYPE99 policy
}
Rcode
Rule Rhai global · static module · DNS RCODE enum
Response codes for txn.response().rcode, txn.response_rcode(), and txn.set_rcode(...) — every IANA-assigned RCODE with a name (SERVFAIL, NXDOMAIN, DSOTYPENI, …) and matching RCODE{n} alias.
Constants
| Form | Example |
|---|---|
| Name | Rcode::NOERROR, Rcode::SERVFAIL, Rcode::NXDOMAIN |
RCODE{n} |
Rcode::RCODE0, Rcode::RCODE2 |
Also Rcode::BADSIG as an alias for wire 16 (same as Rcode::BADVERS / Rcode::RCODE16). Unknown codes use Rcode::from_number(n); name() returns RCODE{n}.
Example
if txn.response_rcode() == Rcode::SERVFAIL {
txn.request_retry();
}
txn.set_rcode(Rcode::REFUSED);
Same number(), name(), ==, and from_number(n) pattern as RecordType.
QueryClass
Rule Rhai global · static module · DNS QCLASS enum
Query class on txn.question().qclass — all IANA scalar class assignments (IN, CH, HS, NONE, ANY, …), plus CLASS{n} aliases.
Example
if txn.question().qclass == QueryClass::IN {
// typical Internet class
}
Same number(), name(), ==, and from_number(n) pattern as RecordType.
DnsOpcode
Rule Rhai global · static module · DNS opcode enum
Message opcode from the query header — txn.question().opcode. IANA-assigned opcodes include QUERY, STATUS, NOTIFY, UPDATE, obsolete IQUERY, and DNS_STATEFUL_OPERATIONS (alias DSO), each with an OPCODE{n} alias.
Example
if txn.question().opcode != DnsOpcode::QUERY {
txn.drop_query();
}
Same number(), name(), ==, and from_number(n) pattern as RecordType.
EdnsOptionCode
Rule Rhai global · static module · EDNS(0) option code enum
Option codes present on the client OPT record — txn.question().edns_options is an array of EdnsOptionCode values (empty when the query has no EDNS). Named constants cover every IANA-assigned EDNS option code, including COOKIE, EDNS_CLIENT_SUBNET (alias CLIENT_SUBNET), PADDING, EXTENDED_DNS_ERROR (alias EDE), REPORT_CHANNEL, and Cisco UMBRELLA_IDENT (alias UMBRELLA), each with a CODE{n} alias.
Example
let q = txn.question();
for opt in q.edns_options {
if opt == EdnsOptionCode::COOKIE {
// client sent DNS cookies
} else if opt == EdnsOptionCode::UMBRELLA {
// Cisco Umbrella network-device identification (wire 20292)
} else if opt == EdnsOptionCode::REPORT_CHANNEL {
// RFC 9567 report channel (wire 18)
}
}
Same number(), name(), ==, and from_number(n) pattern as RecordType.
txn.question()
Request + response hook · no args · returns map
Returns a map of client question fields: qname, qtype, qclass, opcode, edns_options, and DNS message id (typed enums where noted below).
Hooks
Request hook and response hook
Arguments / return
| Parameter | Type | Notes |
|---|---|---|
| none | — | |
| return | map | Question metadata (see Behavior) |
There is no YAML equivalent.
Summary: Structured read of the client question — qname, typed wire enums, and DNS message ID — as a Rhai map.
Behavior
- Always available on both hooks. On the response hook, values describe the original client question, not upstream answer data.
- Map keys (each present only when Conduit has a value):
qname— string, same astxn.question().qnameqtype—RecordTypeqclass—QueryClassopcode—DnsOpcodeedns_options— array ofEdnsOptionCode(omitted when empty — no EDNS on the query)id— integer DNS message ID from the client query (16-bit; exposed as Rhaii64)- Each wire enum uses its static module for constants (name + numeric alias, e.g.
RecordType::A/RecordType::TYPE1,QueryClass::IN/QueryClass::CLASS1). Compare with==, or usefrom_number(n)for arbitrary wire values;.name()returns the selector-friendly string. - Missing fields are omitted from the map rather than set to empty values — use
txn.question().qnameor check map membership when you need a default. - Does not include client address, EDNS options, or answer records — only parsed question metadata from Parse.
- Use
txn.question().qnamewhen you only need the name; usetxn.question()whenqtypeoridmatter (for example metrics labels or TYPE-specific policy).
Example
Request hook — route HTTPS queries differently:
let q = txn.question();
if q.qtype == RecordType::HTTPS || q.qtype == RecordType::TYPE65 {
txn.set_pool("doh-helper");
} else if q.qname.ends_with(".slow.example.") {
txn.set_pool("bulk");
}
txn.response()
Response hook only · no args · returns map; script error on request hook
Returns upstream outcome metadata for the current forward attempt (rcode, routing path, optional wire-derived counts, question fields).
Hooks
Response hook only
Arguments / return
| Parameter | Type | Notes |
|---|---|---|
| none | — | |
| return | map | Response metadata for this attempt (see Behavior) |
There is no YAML equivalent.
Summary: Map view of upstream outcome metadata after the latest forward — rcode, pool / backend for this attempt, optional wire-derived counts when scripts need them, plus question fields for context. Not available on the request hook.
Behavior
- Response hook only. On the request hook, calling
txn.response()returns a script error (response() is not available in request phase). Conduit logs the error, skips further script effects for that hook invocation, and continues the pipeline (Sandbox limits — fail-open). Repository fixture:bad-phase.rhai/with-rhai-bad-phase.yaml. - Reflects the outcome Conduit recorded for the current forward attempt — after timeout, connection failure, or an upstream DNS response. Conduit often sets
SERVFAILfor timeout and pool exhaustion before your script runs; see Retries and transactions. - Map keys (each present only when available):
rcode—Rcodefor this forward attemptpool,backend— selected pool name and upstream backend socket address for this forward attempt (also available viatxn.selected_pool()/txn.selected_backend())backend_name— backend logical label (configurednamewhen set, else address) for this attempt; matches the name-when-set identity in metrics/logs/traces/event filters (also viatxn.selected_backend_name())answer_count,authority_count,additional_count,truncated,authoritative— present only when a response-hook script references wire-derived fields at compile time (see below)qname,qtype,qclass,opcode,edns_options— same astxn.question()- Compile-time gating: Conduit scans response-hook Rhai sources at snapshot compile. If no script references wire-derived fields (
truncated,answer_count, etc.), the forward stage skips parsing upstream response sections —rcodeis still extracted. When any script needs wire metadata, parsing is enabled for all queries on that snapshot. - Dedicated accessors —
txn.response_truncated(),txn.response_answer_count(), etc. — return defaults when wire metadata was not parsed (-1for counts,falsefor booleans). - Does not expose answer RRs, TTLs, or wire bytes — only transaction-level metadata scripts use for policy.
- On a retried query, the map reflects this attempt only — compare with
txn.get_attempt_count()when policy depends on retry generation. - For simple
rcodebranching,txn.response_rcode()is usually clearer and is safe to call on both hooks (returns()on the request hook when no rcode is available).
Example
Response hook — branch on rcode and attempt count:
let resp = txn.response();
if resp.rcode == Rcode::SERVFAIL && txn.get_attempt_count() == 1 {
txn.set_retry_pool("secondary");
txn.request_retry();
}
txn.response_truncated()
Response hook · no args · returns bool
Whether upstream response had TC=1 (requires compile-time wire-meta gating).
txn.response_answer_count()
Response hook · no args · returns i64
Answer section count from the upstream wire answer, or -1 when wire metadata was not parsed.
txn.response_rcode()
Request + response hook · no args · returns Rcode or ()
Returns upstream RCODE on the response hook; () on the request hook when no rcode is available.
Hooks
Request hook and response hook
Arguments / return
| Parameter | Type | Notes |
|---|---|---|
| none | — | |
| return | Rcode or () |
Upstream RCODE on response hook; () on request hook |
There is no YAML equivalent.
Summary: Convenience accessor for upstream rcode — the most common response-hook branch condition. Returns () on the request hook (no script error).
Behavior
- On the response hook, returns the RCODE Conduit recorded for the current forward attempt — same value as
txn.response().rcodewhen that field is present. - Compare with
==againstRcode::SERVFAIL,Rcode::RCODE2, etc. - Returns
()when: - Called on the request hook (upstream outcome not available yet) — does not raise a phase error (unlike
txn.response()) - No RCODE is set on the transaction yet for this attempt
- Typical uses: retry/failover on
SERVFAIL, accept or rewrite onNOERROR, client-facingtxn.set_rcodeafter inspection. Often paired withtxn.request_retry(),txn.set_retry_pool, ortxn.set_rcode. See Outcomes and Routing. - Runs once per response-hook invocation — on retries, each forward attempt gets a fresh evaluation with the rcode for that attempt.
Example
Response hook — conditional retry when upstream was slow (see Hooks and phases — Pairing):
if txn.response_rcode() == Rcode::SERVFAIL && txn.last_forward_ms() > 2000 {
txn.set_retry_pool("secondary");
txn.request_retry();
}
Accept only after upstream success:
if txn.response_rcode() == Rcode::NOERROR {
txn.set_tag("upstream_ok", true);
}
Routing
set_pool and set_retry_pool choose which pool Route uses. clear_pool removes a standing pool choice so Route picks the configured default pool (the pool named default, or the first pool in your config). See Pool selection lifecycle.
Methods: txn.clear_pool · txn.clear_retry_pool · txn.set_pool · txn.set_retry_pool
txn.clear_pool
Request + response hook · no args · no return
Clears standing pool choice — next Route uses the configured default pool.
Hooks
Request hook and response hook
Arguments / return
No arguments. No return value.
Summary: Clears selected_pool so Route falls back to the configured default pool (default name, or first pool in config).
Behavior
- Sets
selected_poolto unset — same as built-inclear_pool. - On the first Route (
attempt_count == 0), Route uses the default pool whenselected_poolis unset. - On a retry Route, Route uses the default pool when
selected_poolis unset andretry_poolis not set. - Does not clear
retry_pool— usetxn.clear_retry_pool()for that. - Typical uses: undo an earlier
set_poolon the same rule; CSV lookup miss → default pool without hardcoding the default name; response hook → retry on the default pool instead of the pool that just failed.
YAML equivalent
- type: clear_pool
Example
Lookup miss leaves pool at default (request hook):
let pool = lookup("routing", txn.question().qname);
if pool != "" {
txn.set_pool(pool);
} else {
txn.clear_pool();
}
Undo built-in set_pool when Rhai runs later on the same rule:
txn.clear_pool();
txn.clear_retry_pool
Request + response hook · no args · clears retry_pool stash only
Clears the retry_pool stash from set_retry_pool without clearing soft-retry intent.
Hooks
Request hook and response hook
Arguments / return
| Parameter | Type | Notes |
|---|---|---|
| none | — | |
| return | — | No return value |
Summary: Clears the retry_pool stash from set_retry_pool without clearing soft-retry intent or selected_pool.
Behavior
- Clears the
retry_poolfield on the transaction — the pool name stashed bytxn.set_retry_poolor built-inset_retry_pool. - Does not clear soft-retry intent from
txn.request_retry()or built-inretry. Usetxn.clear_retry()on the response hook for that. - Does not change
selected_pool(the pool fromtxn.set_pool, request rules, or the default pool). A retry that still occurs usesselected_poolwhenretry_poolis absent at Route. See Pool selection lifecycle. - Within one rule, built-in actions run in list order; a
rhaistep sees prior effects and can clear a stash set earlier on the same rule. - Typical use: undo an earlier
set_retry_poolwhen this hook should retry in the current pool instead — for example request policy stashed a backup pool, but response policy matches SERVFAIL and you want same-pool failover only. See Retry actions.
YAML equivalent
- type: clear_retry_pool
Example
Response script — retry in the current pool even though request policy stashed a backup pool:
if txn.response_rcode() == Rcode::SERVFAIL {
txn.clear_retry_pool();
txn.request_retry();
}
txn.set_pool
Request + response hook · name: string (pool) · no return
Sets selected_pool for Route — first forward and later attempts when retry_pool is absent.
Hooks
Request hook and response hook
Arguments / return
| Parameter | Type | Notes |
|---|---|---|
name |
string | Pool name from config |
| return | — | No return value |
Summary: Sets selected_pool for Route — used on the first forward and on later attempts when retry_pool is absent.
Behavior
- Writes
selected_poolon the transaction — the primary pool for Route. - On the request hook, the first Route uses this value before Forward.
- On later Routes, Conduit uses
selected_poolwhenretry_poolis unset (after a one-shot stash is consumed,selected_poolreflects the pool from the last Route — not necessarily the original request value). See Pool selection lifecycle. - On the response hook, a pool change affects Route only if policy triggers a retry — otherwise the query is past routing for this attempt.
- If multiple rules or actions set a pool, the last writer on the winning rule wins. Within one rule, built-in actions run in list order; a
rhaistep at position N sees effects from actions 1…N−1 and can override them (for exampletxn.set_pool("vip")after YAMLset_pool: default). - This is
set_pool, notset_retry_pool. Usetxn.set_retry_poolwhen you intend the pool for a retry Route while leaving the first Route on the current pool.
YAML equivalent
- type: set_pool
value: vip
See Pools and backends for pool definitions, default pool, and backend selection.
Example
let qname = txn.question().qname;
if qname.ends_with(".vip.example.") {
txn.set_pool("vip");
} else if qname.ends_with(".slow.example.") {
txn.set_pool("bulk");
}
txn.set_retry_pool
Request + response hook · name: string (pool) · stashes pool for next retry Route
Stashes a pool name consumed once on the next retry Route; does not trigger retry alone.
Hooks
Request hook and response hook
Arguments / return
| Parameter | Type | Notes |
|---|---|---|
name |
string | Pool name from config |
| return | — | No return value |
Summary: Stashes a pool name consumed once on the next retry Route. Does not trigger retry by itself.
Behavior
- Stashes a pool name in
retry_poolon the transaction. It does not trigger a retry by itself — pair withtxn.request_retry()ortxn.request_retry_now()on the response hook, or with built-inretry/retry_nowon a response rule. - On the first Route (
attempt_count == 0), Conduit usesselected_poolfromtxn.set_pool, request rules, or the default pool —retry_poolis ignored. The stash remains for a later retry. - On a retry Route (
attempt_count > 0), Conduit consumesretry_poolonce and routes in that pool; ifretry_poolwas cleared or never set, Route usesselected_pool. After that Route,selected_poolupdates to the pool used — you usually do not need to stash again to stay on a failover pool. See Pool selection lifecycle. - Available on both hooks — common on the request hook to pre-stage a backup pool before Forward, and on the response hook when upstream outcome decides failover.
- Within one rule, later
set_retry_poolorclear_retry_pool(built-in or Rhai) overrides an earlier stash on the same hook pass.txn.request_retry()has no effect on the request hook. - This is
set_retry_pool, notset_pool. Usetxn.set_poolto change the pool for the first forward.
YAML equivalent
- type: set_retry_pool
value: secondary
Pair with retry or retry_now on a response rule to fail over — see Retry actions. Built-in set_retry_pool on the request or response hook is equivalent when you do not need script logic.
Example
Response script — fail over when SERVFAIL followed a slow forward:
if txn.response_rcode() == Rcode::SERVFAIL && txn.last_forward_ms() > 2000 {
txn.set_retry_pool("secondary");
txn.request_retry();
}
Request hook — route to primary now, stash secondary if a later retry occurs (built-in actions on the request rule are equivalent; see tests/fixtures/config/with-rhai-servfail-retry.yaml):
txn.set_pool("primary");
txn.set_retry_pool("secondary");
Sampling
Deterministic sampling and cadence gates for scripts — mirror YAML selectors on Sampling and cadence. Percentage methods (sample_percent*) use a 0..100 scale with optional key salt; cadence methods (every_nth_*) match every Nth query on this worker or process-wide. Use to gate expensive logic, set audit tags, or combine with declarative rules.
YAML selector parity
| YAML selector / field | Rule Rhai equivalent |
|---|---|
sample_percent (no salt) |
txn.sample_percent(percent) |
sample_percent + key |
txn.sample_percent(percent, key) |
sample_percent + key_from: qname |
txn.sample_percent_for_qname(percent) or txn.sample_percent(percent, txn.question().qname) |
sample_percent + key_from: rule_name |
txn.sample_percent_for_rule(percent) or txn.sample_percent(percent, txn.rule_name()) |
every_nth_worker |
txn.every_nth_worker(n) |
every_nth_global |
txn.every_nth_global(n) |
key_from: sink_name applies to event sink filters only — not exposed on Rule Rhai. Prefer a YAML selector on the rule when you only need coarse gating without running script logic on every match.
Methods: txn.sample_percent(percent) · txn.sample_percent(percent, key) · txn.sample_percent_for_qname(percent) · txn.sample_percent_for_rule(percent) · txn.every_nth_worker(n) · txn.every_nth_global(n) · txn.rule_name()
txn.sample_percent
Request + response hook · percent: float · optional key: string · returns bool
Returns whether this transaction falls in the ~percent% sample; optional key selects an independent bucket.
Hooks
Request hook and Response hook
Arguments / return
Two overloads share the same name:
| Overload | Parameters | Notes |
|---|---|---|
| Global bucket | percent: float |
Same hash as YAML sample_percent with no key / key_from — transaction id only |
| Keyed bucket | percent: float, key: string |
Same hash as YAML sample_percent with static key: |
| Parameter | Type | Notes |
|---|---|---|
percent |
float | Target pass rate on 0..100 — clamped (0 never passes; 100 always passes) |
key |
string (optional) | Salt string; empty string is treated like no key |
| return | bool |
true when this transaction is in the sample |
Summary: Deterministic ~percent% gate. When the call returns true, Conduit also sets boolean tag sampled.
Behavior
- Same hash as rule
sample_percentselectors, tracingactivation.sample_percent, and event-export filters. - Repeated calls with the same
percentandkeyreturn the samebool(cached for this hook invocation). - For
key_from: qnameorkey_from: rule_name, prefer the dedicated helperssample_percent_for_qnameandsample_percent_for_rule. - On internal error acquiring script effects, returns
false.
YAML equivalent
selectors:
- type: sample_percent
value: "10"
See also keyed examples under sample_percent_for_qname and sample_percent_for_rule.
Example
if txn.sample_percent(5.0) {
txn.set_tag("audit", true);
}
txn.sample_percent_for_qname
Request + response hook · percent: float · returns bool
~percent% sample with per-qname salt — matches YAML key_from: qname.
Hooks
Request hook and Response hook
Arguments / return
| Parameter | Type | Notes |
|---|---|---|
percent |
float | 0..100 (clamped) |
| return | bool |
false when the question has no qname; otherwise same as txn.sample_percent(percent, txn.question().qname) |
Summary: Keyed sample_percent using the canonical wire qname as salt. Sets tag sampled when true.
YAML equivalent
selectors:
- type: sample_percent
value: "10"
key_from: qname
Example
Repository fixture sample-audit.rhai:
if txn.sample_percent_for_qname(10.0) {
txn.set_tag("rhai_sampled", true);
}
txn.sample_percent_for_rule
Request + response hook · percent: float · returns bool
~percent% sample salted with this rule's configured name — matches YAML key_from: rule_name.
Hooks
Request hook and Response hook
Arguments / return
| Parameter | Type | Notes |
|---|---|---|
percent |
float | 0..100 (clamped) |
| return | bool |
Same bucket as txn.sample_percent(percent, txn.rule_name()) |
Summary: Independent ~percent% slice per rule name — two rules at the same percentage do not share the same bucket. Sets tag sampled when true.
YAML equivalent
selectors:
- type: sample_percent
value: "10"
key_from: rule_name
Example
if txn.sample_percent_for_rule(25.0) {
txn.set_tag("canary", true);
}
txn.every_nth_worker
Request + response hook · n: integer · returns bool
true when this worker's transaction id is divisible by n — matches YAML every_nth_worker.
Hooks
Request hook and Response hook
Arguments / return
| Parameter | Type | Notes |
|---|---|---|
n |
integer | Must be >= 1 — script error otherwise |
| return | bool |
true when txn_id % n == 0 (for example n: 4 matches ids 4, 8, 12, … on each worker) |
Summary: Worker-local cadence gate — same semantics as the every_nth_worker selector. Read-only; does not set tags.
Behavior
- Uses the worker-local transaction id assigned when Conduit creates the transaction.
- Stable for the lifetime of the transaction — same result on request and response hooks (and across retry response passes for the same id).
- Does not set tag
sampled— unlikesample_percent*.
YAML equivalent
selectors:
- type: every_nth_worker
value: "4"
Example
if txn.every_nth_worker(4) {
txn.set_pool("canary");
}
txn.every_nth_global
Request + response hook · n: integer · returns bool
true when the process-wide query index is divisible by n — matches YAML every_nth_global.
Hooks
Request hook and Response hook
Arguments / return
| Parameter | Type | Notes |
|---|---|---|
n |
integer | Must be >= 1 — script error otherwise |
| return | bool |
true when global_query_index % n == 0 |
Summary: Process-wide cadence gate — same semantics as the every_nth_global selector. Read-only; does not set tags.
Behavior
- Uses the process-wide query index incremented once when each transaction is created (before selector evaluation on rules).
- Coordinates cadence across worker threads — unlike
every_nth_worker, which is scoped per worker. - Does not set tag
sampled.
YAML equivalent
selectors:
- type: every_nth_global
value: "100"
Example
if txn.every_nth_global(100) {
txn.set_tag("global_canary", true);
}
txn.rule_name
Request + response hook · no args · returns string
Returns the configured name of the rule whose rhai action is running this script.
Hooks
Request hook and Response hook
Arguments / return
No arguments. Returns the rule name string from config (for example "audit-canary").
Summary: Read-only rule identity for logging, metrics labels, or custom sample_percent(percent, key) salts. Prefer sample_percent_for_rule when you want YAML key_from: rule_name semantics.
YAML equivalent
None — use rule name: in config. Matches the value baked into key_from: rule_name selectors at compile time.
Example
if txn.sample_percent(5.0, txn.rule_name()) {
metrics.inc("rule_sample_hits", 1);
}
Tags
Tags are small key/value labels you attach to a transaction for the rest of its life — including across retries and on the response hook. They do not change routing by themselves; they let later policy, event export, and other scripts branch on how the query was classified.
| Goal | Typical hook | API |
|---|---|---|
| Classify the query before upstream | Request | txn.set_tag("tier", "vip") or txn.set_tag("audit", true) |
| Act on classification + upstream outcome | Response | txn.has_tag("suspicious") then metrics, retry, or drop |
| Gate dnstap / event sinks | Request (set tag) | Sink tag_required in config — see Event export — Filters |
| Remove a label | Either | txn.clear_tag("temporary") |
Tags set on the request hook stay on the transaction when the response hook runs (the request hook does not run again on retry). Pair request set_tag with response has_tag — see Hooks and phases — Pairing scripts.
Example — request classify, response act:
// request hook
if txn.question().qname == "login.suspicious.example." {
txn.set_tag("suspicious", true);
}
// response hook
if txn.has_tag("suspicious") && txn.last_forward_ms() > 500 {
metrics.inc("slow_login", 1);
}
Boolean tags use true / false. String tags store text (txn.set_tag("tier", "vip")). YAML built-in set_tag on the same rule runs before Rhai when listed above the script — the script can read those tags with has_tag and add more.
Methods: txn.clear_tag(key) · txn.has_tag(key) · txn.set_tag(key, value)
txn.clear_tag
Request + response hook · key: string · no return
Removes a tag key from the transaction tag map.
Hooks
Request hook and response hook
Arguments / return
| Parameter | Type | Notes |
|---|---|---|
key |
string | Tag name to remove |
| return | — | No return value |
Summary: Removes a tag key (boolean or string) from the transaction. Use explicitly instead of set_tag(key, false) when you mean removal.
Behavior
- Removes a tag key from the transaction — both boolean flags and string values for that key.
- Later calls with the same key in one script run follow last write wins semantics with
txn.set_tag(for exampleset_tagthenclear_tagleaves the key absent). - Does not treat
txn.set_tag(key, false)as a clear — usetxn.clear_tag(key)explicitly when you mean removal. txn.has_tag(key)checks script effects first, then tags present at hook entry.
YAML equivalent
- type: clear_tag
value: suspicious
Example
if txn.has_tag("temporary") {
txn.clear_tag("temporary");
}
txn.has_tag
Request + response hook · key: string · returns bool
Returns whether a tag key is present on the transaction.
Hooks
Request hook and response hook
Arguments / return
| Parameter | Type | Notes |
|---|---|---|
key |
string | Tag name |
| return | bool |
true when the tag is present under the rules below |
Summary: Read-only check for a tag: script effects from earlier in the run win, then tags present at hook entry.
Behavior
- Read-only — does not change the transaction.
- Returns
truewhen: - A
txn.set_tagortxn.clear_tagearlier in this script run left the key present (last write wins within the script), or - The key was already on the transaction at hook entry (from built-in
set_tag/clear_tagon this or an earlier matching rule, or from a prior hook on the same transaction). - For boolean tags:
trueonly when the bool flag istrue.txn.set_tag(key, false)yieldsfalsefor that key until a laterset_tagorclear_tagin the same script. - For string tags:
truewhen any string value is stored for the key (including values set by YAMLset_tag: key=value). - Tags from the request hook are still visible on the response hook (the request hook does not re-run on retry).
- Declarative rules test tags with the
tagselector, nothas_tag. See Rules and actions — Selectors.
YAML equivalent
None — use a tag selector on a rule to match a tag set elsewhere:
selectors:
- type: tag
value: suspicious
Example
Response script — act only when the request hook tagged the query (see Hooks and phases — pairing scripts):
if txn.has_tag("suspicious") {
if txn.last_forward_ms() > 500 {
metrics.inc("slow_login", 1);
}
}
txn.set_tag
Request + response hook · key: string, value: bool or string · no return
Sets a bool or string tag on the transaction for rules, metrics, and export.
Hooks
Request hook and response hook
Arguments / return
| Parameter | Type | Notes |
|---|---|---|
key |
string | Tag name |
value |
bool or string | true / false for boolean tags; any other value is stored as a string |
| return | — | No return value |
Summary: Sets a boolean or string tag that persists for the rest of the transaction, including across retries and into the response hook.
Behavior
- Sets a tag on the transaction. Tags persist for the rest of the transaction — including when the response hook runs and across retries (the request hook does not re-run on retry).
- Boolean tags use
true/false. String tags store arbitrary text.txn.has_tag(key)is true when the bool flag istrueor a string value is set for that key — pick one style per key in practice. - Later calls with the same key overwrite the value from this script run. Built-in
set_tagactions on the same rule that ran before the script are visible totxn.has_tag; the script can add or override tags after that. - Tags are visible to downstream rules only if those rules’ selectors match (first-match still applies per hook). They also gate event export sinks that use
tag_requiredor similar filters.
YAML equivalent
- type: set_tag
value: suspicious # key only → true
- type: set_tag
value: tier=vip # string value
Request- and response-hook rules both support set_tag. See Request-hook actions and Response-hook actions.
Example
if txn.question().qname.ends_with(".corp.example.") {
txn.set_tag("corp", true);
txn.set_tag("tier", "internal");
}
Related topics
- Host API overview — five scope objects in every hook
- Runtime API — read-only
runtime.routinghealth and routing views - Lookups —
lookup()/lookup_ip()in rule scripts - User metrics —
metrics.inc/metrics.inc_labels - Script logging —
log.info/log.warn - Hooks and phases — request vs response hook
- Rules and actions — YAML equivalents for many
txnmethods