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.
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 asPROJ-ZEUS-9X, and flag ticket numbers such asTCK-004211. - A compiled pack,
internal_ids.hxpack, that you run and serve withhelixor-pack.
Prerequisites#
- The runtime installed, which includes the
helixor-packcommand. See Installation. - Your Developer license at
~/.helixor/helixor.hxlic. See Licensing. Check it withhelixor-pack inspect-license: it must listcustom_playbook_compilationunder features. - Read Playbooks for what a compiled pack executes in 0.3.1.
Steps#
- See that
compile_custom_rule()is goneChanged in 0.3.0:
compile_custom_rule()is removed fromHelixorEngine. 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 raisesRemovedCapabilityError, which is exported fromhelixor_runtimeand is anEnclaveCapabilityError: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.
- Write a playbook with regex rules
A
rulesentry withtype: regexis compiled into your pack and runs on every payload, next to the built-in checks. The returned action isblock_<id>foraction: blockandredact_<id>foraction: redact, so choose IDs that read well as actions and list those actions for reviewers. Any otheractionortype, or a pattern that is not a valid regular expression, fails compilation withUnsupportedPackDeclarationError.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_payloadactionslists 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 anydescriptionthat contains a colon followed by a space; otherwise the file does not parse. - 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.
- Run it
helixor-pack rununseals 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.pyprints the action, rule IDs andclean_textfrom 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 doneStatus 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:editionisDEVELOPERandlicense_statusisACTIVE.Changed in 0.3.0
Any fired block rule withholds the whole text, so the third input's
clean_textis[BLOCKED: Prohibited Content]; in 0.2.1 it keptPROJ-ZEUS-9Xbecause 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 forwardclean_textfrom a result whose action starts withblock. - 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 checkengine.pack_idbefore 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.