Decisions as a Service
Sign Up Now →Sign Up Now

Errors and failure modes

What can go wrong, what the runtime raises when it does, and which failures stop a payload (fail closed) versus let it through (fail open). Read the fail-open section before you go to production.

Runtime 0.3.1

Catching runtime exceptions#

Every error the runtime raises derives from HelixorRuntimeError, and the classes on this page are exported from helixor_runtime, except the two subclasses LicenseRevokedAuthorityError and PlaybookParseError: catch their parents, LicenseSignatureInvalidError and CompilerError. Catch the base class around every evaluation, and never forward a payload that was not evaluated.

from helixor_runtime import DeveloperQuotaExceededError, HelixorRuntimeError

try:
    result = engine.evaluate(text)
except DeveloperQuotaExceededError:
    raise PayloadRejected("rate limited")      # back off and retry later
except HelixorRuntimeError as exc:
    log.error("helixor evaluation failed: %s", type(exc).__name__)
    raise PayloadRejected(type(exc).__name__) from exc   # never forward the payload

The exceptions that do not derive from it are FileNotFoundError and ValueError from load_pack() for a bad path or argument, and NativeRuntimeUnavailableError from the generated SDK client, which is not exported either. Some classes also derive from a built-in type for compatibility: StreamBlockedError from RuntimeError, UnsupportedPackDeclarationError from ValueError, RemovedCapabilityError also from AttributeError, DecisionBudgetExceededError from RuntimeError, BeliefSnapshotError from ValueError.

Evaluation#

ExceptionRaised whenWhat to do
RemovedCapabilityErrorcompile_custom_rule() on any engine: the method is removed from HelixorEngine (0.3.0). An EnclaveCapabilityError that is also an AttributeError, so hasattr(engine, "compile_custom_rule") is False. The message says to declare the rule in the playbook.Add the rule to the playbook's rules and compile it.
SalesEngineUnavailableError, code SALES_ENGINE_NOT_IN_RUNTIMECode uses helixor_runtime.HelixorSalesEngine, including from helixor_runtime import HelixorSalesEngine. The sales engine is not part of the runtime (removed in 0.3.1; in 0.3.0 it always failed with FileNotFoundError). Also an ImportError.Write the decision as a playbook, compile it with helixor-pack compile and load it with HelixorEngine.load_pack().
CodonRuntimeErrorevaluate() got a dict with no text, message or payload key, a value under that key that is not a string, or a payload that is neither a string nor a dict.Pass a string.
AmbiguousPayloadErrorevaluate() got a dict with more than one key. A CodonRuntimeError; its keys attribute lists the dict's keys, sorted.Pass a string, or a dict with exactly one text key.
DecisionBudgetExceededErrorA compiled pack's decision took longer than its budget.max_latency_ms. The result is withheld. Attributes pack_id, budget_key, limit, measured.Treat the payload as not evaluated.
LicenseExpiredErrorA compiled pack's license passed its grace period while the engine was running.Deploy a renewed license with packs compiled for it.
DeveloperQuotaExceededErrorA Developer license's per-minute rate (a sliding 60-second window) or lifetime evaluation quota is exceeded, and the key's enforcement mode is fail-closed. The built-in example pack and compiled packs both enforce the license's max_decisions_per_minute and max_decisions_lifetime: past a limit a compiled pack raises this error unless the license's overage policy is TRUE_UP, which records the overage and keeps deciding. (In 0.2.1 only the built-in pack enforced the limits.)Back off, or move to a license without limits. Treat the payload as not evaluated.
KeyInvalidErrorA license key string is malformed or its signature does not match.Check the key you were issued.
KeyExpiredErrorEvery key for the pack is past its expiry plus grace period.Install the renewal.
KeyNotFoundErrorNo key covers the pack.Check which pack the key was issued for.
StreamBlockedErrorstream_filter or astream_filter hit a fatal match with block_on_fatal=True. Message: Stream blocked by statutory invariant: <reason> (<action>); attributes action and reason.Stop sending the response; see Streaming.

evaluate() never raises because of what a string says. Empty text evaluates as clean.

Licenses#

All license file errors share the base class LicenseError. See License file for the checks.

ExceptionRaised whenExample message
LicenseSignatureInvalidErrorThe signature is missing, or does not verify because any signed field was changed.License digital signature verification failed: …
LicenseRevokedAuthorityError (a LicenseSignatureInvalidError)The license was signed by a revoked authority key. The authority key used for 0.2.0 and early 0.2.1 builds is revoked, and since 0.3.0 the runtime rejects licenses it signed.License was signed by revoked authority key … Request a reissued license signed by the current authority.
LicenseNotYetValidErrorThe current time is before valid_from.License is not yet valid (starts at …).
LicenseExpiredErrorThe current time is after valid_until plus the grace period.License expired on YYYY-MM-DD and grace period has elapsed.
LicenseEntitlementErrorThe license does not allow the pack (at compile or load) or a solver the pack declares.License '…' (DEVELOPER) is not entitled to execute pack '…'.
LicenseNotFoundError (also a FileNotFoundError), code LICENSE_NOT_FOUNDNo license in the search order, or an explicit path or HELIXOR_LICENSE_FILE names a missing file. New in 0.3.1; the message lists the locations checked and where to request a license.LICENSE_NOT_FOUND: no Helixor license file was found.
LicenseLocationAmbiguousError, code LICENSE_LOCATION_AMBIGUOUSOne search directory holds both helixor.hxlic and the older helixor.lic. New in 0.3.1.LICENSE_LOCATION_AMBIGUOUS: both … and … exist; the runtime will not guess which license to use.

A license file that is not valid JSON, or is missing a section such as identity, fails with a JSON or key error rather than a LicenseError. Treat any error while loading a license as fatal.

Packs and compilation#

ExceptionRaised when
FileNotFoundErrorThe .hxpack passed to load_pack() does not exist (Policy pack not found: …). A missing license raises LicenseNotFoundError, which is also a FileNotFoundError.
UnsupportedPackDeclarationErrorAt compile or load time: the playbook declares codons or hard rules other than the built-in ones, a rule type other than regex or luhn_checksum, a rule action other than block or redact, a rule without id or pattern, or an invalid regular expression. The message lists every problem. At load time it surfaces as a PackUnsealError.
PackUnsealError Invalid .hxpack: file too small to contain valid header.
Invalid pack container magic bytes: …
Unsupported pack format version: …
Decryption failed: content key mismatch or corrupted pack envelope (the wrong license, a renewed license, or a modified file)
Integrity check failed: AST digest mismatch.
Changed in 0.3.1: a missing license raises LicenseNotFoundError, not PackUnsealError.
CompilerErrorThe playbook is not valid YAML (often an unquoted ": "), its root is not a mapping, pack_id is missing, or its solver declarations cannot be read (PlaybookParseError). With --remote: the authority returned an HTTP error, or could not be reached.

The CLI turns each of these into one line on standard error and exit code 1; see CLI.

HTTP status codes#

StatusCause
200Evaluated. Check action and trigger severities; a 200 can be a block.
400Unknown domain in POST /v1/decision, or a request with license_key to a service that serves a compiled pack.
401The service has a bearer token and the request did not carry it. GET /v1/health never needs it.
403license_data failed verification, or the license does not allow the pack or solver.
422The request body failed validation.
500An unhandled error, for example an invalid license_key sent to a service running the built-in pack (a key error).

WebSocket errors arrive as {"type": "error", "message": "…"}. An error inside a handler also closes the connection. Without the bearer token, the connection is closed with code 1008. See HTTP API.

Licensing service: sign-up and issuance#

Planned Sign-up, registration requests and license issuance check your acceptance of the Helixor Software License Agreement. These errors come from the Helixor licensing service, not from the runtime.

StatusCodeCauseWhat to do
422LICENSE_AGREEMENT_NOT_ACCEPTEDThe request has no agreement_id or agreement_sha256, or the account has no recorded acceptance of the current agreement version when a license is issued or renewed.Review the agreement and accept it: tick the checkbox, type the acceptance phrase, or pass --accept-license-agreement <sha256>.
422LICENSE_AGREEMENT_MISMATCHThe agreement_id or agreement_sha256 is not the current agreement version, usually because a new version was published after you reviewed it.Fetch GET /api/v1/legal/license-agreement, review the current text and accept it.

Fails closed#

These checks stop processing with an error. No partial result is returned.

  • A license signature that is missing or invalid, or a license that is not yet valid or is past its grace period.
  • A pack the license does not allow, or a declared solver the license does not allow.
  • A pack sealed for a different license, a modified pack, or a failed integrity check.
  • A fatal match in stream_filter / astream_filter (raises) or in a streaming session (blocked chunk, nothing further emitted).
  • Developer quota or rate exceeded, when the key's enforcement mode is fail-closed.
  • load_pack() with a missing pack or license file.
  • A playbook or pack that declares something the engine does not execute, and a pack rule whose condition cannot be evaluated.
  • A dict payload the engine cannot read.
  • A service started on a non-loopback address without a token, or with a wildcard CORS origin.

Known fail-open paths in 0.3.1#

In these cases the runtime does not raise, and a payload or text can get through. Guard against each one in your integration. 0.3.0 closed these paths that were open in 0.2.1: a WebSocket stream continuing after a blocked token, a blocked value left in a compiled pack's clean_text, a dict with several text fields evaluated on one field, a capturing group reported and redacted instead of the whole match, and compiled packs ignoring actions, default_action and budget.

PathWhat happensGuard
invariants and remedies in a compiled packAccepted and stored, but not executed. (actions, default_action and budget are enforced; see Playbook schema.)Express checks as rules; accept only the actions you declared.
A service with no tokenAny process that can reach the port can call it.Keep it on loopback, or set HELIXOR_SERVICE_TOKEN.

For how to design around these, see Reliability and Troubleshooting.