Decisions as a Service
Sign Up Now →Sign Up Now

Add custom rules

You add your own rules to a decision as regex rules in a playbook that you compile into your own pack. The starting point is the built-in example pack (data protection), which knows regulated data such as SSNs and card numbers but not your internal identifiers.

Tier 1 · Developer30 minutesIntermediate

About the example

This tutorial uses the built-in data-protection pack so you can run everything without writing a pack first. The same calls work for any decision pack: eligibility, limits, routing and so on. See Core concepts.

What you'll build#

  • A check that shows compile_custom_rule() is removed, so custom rules go in a playbook.
  • A playbook, internal_ids.yaml, with two regex rules: block internal project codes such as PROJ-ZEUS-9X, and flag ticket numbers such as TCK-004211.
  • A compiled pack, internal_ids.hxpack, that you run and serve with helixor-pack.

Prerequisites#

  • The runtime installed, which includes the helixor-pack command. See Installation.
  • Your Developer license at ~/.helixor/helixor.hxlic. See Licensing. Check it with helixor-pack inspect-license: it must list custom_playbook_compilation under features.
  • Read Playbooks for what a compiled pack executes in 0.3.1.

Steps#

  1. See that compile_custom_rule() is gone

    Changed in 0.3.0: compile_custom_rule() is removed from HelixorEngine. No public engine could run it (the Community engine is static and a loaded pack is sealed), and it did not redact its match. A call raises RemovedCapabilityError, which is exported from helixor_runtime and is an EnclaveCapabilityError:

    from helixor_runtime import HelixorEngine, RemovedCapabilityError
    
    engine = HelixorEngine()
    print("tier:", engine.tier)
    print("has compile_custom_rule:", hasattr(engine, "compile_custom_rule"))
    
    try:
        engine.compile_custom_rule("Block any text containing 'PROJ-ZEUS'")
    except RemovedCapabilityError as exc:
        print(f"{type(exc).__name__}: {exc}")
    
    tier: COMMUNITY
    has compile_custom_rule: False
    RemovedCapabilityError: HelixorEngine.compile_custom_rule was removed: no public engine could run it (the Community enclave is static and a sealed .hxpack is immutable). Declare the rule in the playbook's `rules:` section (type regex or luhn_checksum, action block or redact) and compile it with `helixor-pack compile`; the pack engine reports and redacts its matches.
    

    Do what the message says: put the rules in a playbook.

  2. Write a playbook with regex rules

    A rules entry with type: regex is compiled into your pack and runs on every payload, next to the built-in checks. The returned action is block_<id> for action: block and redact_<id> for action: redact, so choose IDs that read well as actions and list those actions for reviewers. Any other action or type, or a pattern that is not a valid regular expression, fails compilation with UnsupportedPackDeclarationError.

    pack_id: custom.internal_ids.v1
    version: 1.0.0
    name: Internal identifier guard
    kind: internal_policy
    description: "Blocks internal project codes and flags ticket numbers."
    actions:
      - permit_clean_payload
      - block_internal_project_code
      - redact_ticket_id
      - block_glba_ssn_leakage
      - block_pci_dss_pan_leakage
      - block_hipaa_phi_leakage
      - redact_and_permit_contact_pii
    rules:
      - id: internal_project_code
        type: regex
        pattern: '\bPROJ-[A-Z]+-\d+[A-Z]?\b'
        action: block
      - id: ticket_id
        type: regex
        pattern: '\bTCK-\d{6}\b'
        action: redact
    default_action: permit_clean_payload
    

    actions lists every outcome the pack can return, including the five of the built-in checks that run in every compiled pack. The compiler rejects a list that leaves one out. Quote regex patterns with single quotes so YAML does not interpret the backslashes, and quote any description that contains a colon followed by a space; otherwise the file does not parse.

  3. Compile it with your Developer license
    helixor-pack compile --playbook internal_ids.yaml \
      --license ~/.helixor/helixor.hxlic \
      --out internal_ids.hxpack
    
    Compiling playbook 'internal_ids.yaml' locally...
    Successfully compiled sealed binary pack (943 bytes): internal_ids.hxpack
    

    The size depends on your license and the runtime version.

    The pack is encrypted to your license: it only unseals with the license it was compiled with. The size varies with the playbook.

  4. Run it

    helixor-pack run unseals the pack in memory, evaluates one text and prints the full decision as JSON.

    helixor-pack run --pack internal_ids.hxpack \
      --license ~/.helixor/helixor.hxlic \
      --text "Status for PROJ-ZEUS-9X is green."
    
    {
      "pack_id": "custom.internal_ids.v1",
      "action": "block_internal_project_code",
      "invariants_passed": false,
      "reason": "Pack rule 'internal_project_code' triggered (block)",
      "triggers": [
        {
          "rule_id": "internal_project_code",
          "law": "internal_policy",
          "severity": "FATAL",
          "matched_items": [
            "PROJ-ZEUS-9X"
          ]
        }
      ],
      "remedy": {
        "clean_text": "[BLOCKED: Prohibited Content]",
        "redactions_count": 0,
        "redacted_categories": []
      },
      "latency_us": 45.92,
      "tokens_spent": 0,
      "egress_bytes": 0,
      "receipt_hash": "hx_proof_f7aee9a8ef8181783992c5dc",
      "license_status": "ACTIVE",
      "active_key_id": "ep1_2026",
      "active_epoch": 1,
      "edition": "DEVELOPER",
      "upgrade_notice": null
    }
    

    Now run it against four inputs. summarize.py prints the action, rule IDs and clean_text from each result:

    import json
    import sys
    
    d = json.load(sys.stdin)
    print(f"  {d['action']}  {[t['rule_id'] for t in d['triggers']]}  {d['remedy']['clean_text']!r}")
    
    for text in "Status for PROJ-ZEUS-9X is green." \
                "See TCK-004211 for the fix." \
                "Email ops@example.com about PROJ-ZEUS-9X." \
                "Lunch is at noon."; do
      echo "$text"
      helixor-pack run --pack internal_ids.hxpack --license ~/.helixor/helixor.hxlic --text "$text" \
        | python summarize.py
    done
    
    Status for PROJ-ZEUS-9X is green.
      block_internal_project_code  ['internal_project_code']  '[BLOCKED: Prohibited Content]'
    See TCK-004211 for the fix.
      redact_ticket_id  ['ticket_id']  'See [REDACTED_TICKET_ID] for the fix.'
    Email ops@example.com about PROJ-ZEUS-9X.
      block_internal_project_code  ['internal_project_code', 'RULE-GDPR-EMAIL-REDACT']  '[BLOCKED: Prohibited Content]'
    Lunch is at noon.
      permit_clean_payload  []  'Lunch is at noon.'
    

    Read the results: the redact rule replaces the ticket number with [REDACTED_TICKET_ID]. In the third input both your rule and the built-in email check fire; both are reported, your fatal rule decides the action, and the whole text is withheld. The result carries your license state: edition is DEVELOPER and license_status is ACTIVE.

    Changed in 0.3.0

    Any fired block rule withholds the whole text, so the third input's clean_text is [BLOCKED: Prohibited Content]; in 0.2.1 it kept PROJ-ZEUS-9X because the email was redacted. Receipts hash the input with the pack version, so each input gets its own; in 0.2.1 every input a block rule replaced whole shared one receipt. See Receipts. Still, never forward clean_text from a result whose action starts with block.

  5. Serve it

    To call the pack from other processes, serve it on loopback. The HTTP and WebSocket clients from Serve decisions to other languages work unchanged.

    helixor-pack serve --pack internal_ids.hxpack \
      --license ~/.helixor/helixor.hxlic \
      --host 127.0.0.1 --port 18734
    

    To use the pack in your own Python process instead, load it with HelixorEngine.load_pack("internal_ids.hxpack", license_file=...) and check engine.pack_id before you take traffic.

How it works#

helixor-pack compile parses the playbook, checks that your license is entitled to compile it, and encrypts the compiled form with AES-256-GCM using a key from your license. The pack ID and license ID are bound into the encryption, so the pack will not unseal with another license. At run time the CLI verifies the license signature and dates, unseals the pack in memory, and evaluates your rules and the example pack's built-in checks. Every check runs and every match is reported in triggers. The first fatal match decides the action, with your rules checked before the built-in ones; if nothing fatal matched, the first warning decides.

From your playbook, rules of type regex or luhn_checksum execute. The compiler rejects any codons or hard_rules other than the example pack's built-in ones with UnsupportedPackDeclarationError, rather than sealing declarations that would never run; see Playbooks and the playbook schema. Patterns run on every payload with no timeout, so avoid nested quantifiers.

If the CLI cannot find a license it prints LICENSE_NOT_FOUND with the locations it checked and exits with status 1. Without --license it looks in HELIXOR_LICENSE_FILE, then ./helixor.hxlic, then ~/.helixor/helixor.hxlic. The flags are in the CLI reference, and the error types in Errors.

Next steps#