Skip to main content
Jira progress: loading…

ZAPE

ZAYAZ Policy Engine

ZAPE is the deterministic “switchboard” that selects and composes rulesets and bundles for a given engine execution context.

It is designed to scale to 100+ engines, multiple domains (finance, emissions, risk, security), multiple environments, and strict governance/audit requirements.


1. Problem ZAPE solves

As the number of engines grows, we need a consistent answer to:

  • Which ruleset kinds apply to this engine?
  • Which ruleset version is active (dev/staging/prod)?
  • How do tenant/entity overrides work without breaking determinism?
  • How do we keep choices auditable and reproducible?

ZAPE provides a stable mechanism to answer these questions using:

  • Engine Profiles (capabilities + constraints)
  • Composer Policy (selection rules)
  • Overrides (global/tenant/entity)
  • Registries + pointers (what is active, and why)

2. Key Concepts

2.1. Engine Profile (versioned, declarative)

An engine profile is a small, versioned document that describes an engine’s policy surface.

It declares:

  • meid and engine family/category
  • supported ruleset_kind values (e.g., validation, tag_detection, classification, aggregation, etc.)
  • required slots (e.g., “tag-detection must exist”)
  • default domain/severity/enforcement hints
  • compatibility bounds (schema inputs/outputs)
  • audit requirements / environment gates

Profiles live in human-reviewable YAML:

  • code/engine-profiles/MEID_....engine_profile.v1.yaml

And are compiled into a manifest for the UI:

  • /generated/engineProfiles.v1.json

Profiles describe capability and constraints — not “business decisions”.


2.2. Composer Policy (selection logic)

Composer policy is the deterministic logic that decides which rulesets/bundles to pick.

It answers:

  • For engine X in domain Y, which slots are required?
  • Which statuses are eligible in each env (draft/approved/frozen)?
  • What is the precedence for choosing candidates?
  • How do we resolve conflicts?

Policy is also versioned and reviewable.


2.3 Overrides (global → tenant → entity)

Overrides are first-class and explicit.

Resolution order:

  1. Entity override (most specific)
  2. Tenant override
  3. Global default (fallback)

Overrides don’t mutate the ruleset — they change which already-registered artifact is selected.


3. How ZAPE makes “rulesets differ” (without magic)

A ruleset is identified by:

  • engine MEID (execution engine)
  • domain
  • ruleset_kind
  • metadata (CRID, severity, enforcement mode, compatibility, etc.)
  • rules payload

Multiple rulesets can share the same (meid, domain, ruleset_kind) but differ by:

  • scope (global vs tenant vs entity)
  • status (draft vs approved vs frozen)
  • compatibility bounds (schema v1 vs v2)
  • policy choices (thresholds, enforcement_mode, severity)
  • lineage (supersedes a previous ruleset)

ZAPE does not invent these differences. It selects among authored and registered artifacts.


4. Where “extra values” come from (thresholds, modes, compatibility)

Values like:

  • thresholds.min_tagged_fields_present: 1 vs 2
  • enforcement_mode: soft vs blocking
  • compatibility schema_min/max: v1 vs v2
  • audit_required: true

…come from one of three places:

  1. Explicit intent authored by the rule owner (via UI or YAML)
  2. Defaults from engine profile or policy (deterministic, documented)
  3. Overrides (global/tenant/entity pointers), not mutation

ZAPE is deterministic: given the same inputs + same registered artifacts + same policy version, it produces the same result.


5. Input sources

5.1 Engine frontmatter (engine identity & routing)

Micro-engine docs contain frontmatter like:

mice:
meid: MEID_CALC_GHG_AGGR
category: AGGR
domain: emissions
supported_modes: [aggregation]
metric_types_supported:
- ghg.abs
- ghg.scope1.abs
- ghg.scope2.abs
- ghg.scope3.abs

Frontmatter helps categorize and route, but ZAPE does not rely on MDX directly in production. Instead, we compile frontmatter → engine profiles / manifests.


5.2. Engine Profiles (the contract)

Profiles define what kinds/slots are valid and required.


5.3. Ruleset Registry + Bundle Registry

Registries contain the set of candidate artifacts that can be selected.


5.4. Pointers (what is active)

Pointers define which bundle/ruleset is active per env and per scope.


6. Output: Deterministic Recommendation

ZAPE outputs a deterministic recommendation result, for example:

  • required slots for MEID
  • chosen ruleset per slot
  • chosen bundle (optional)
  • rationale (policy version + match reasons)
  • audit snapshot (inputs/hashes)

This recommendation can be used by:

  • the Ruleset Generator UI (defaults + guided form)
  • engine runtime (load correct bundle)
  • auditors (show “why this was selected”)

7. Governance and audit

ZAPE supports auditability by construction:

  • every artifact is immutable (hash-based ZAR refs)
  • selection policy is versioned
  • pointers are explicit and logged
  • approvals are tied to identity and environment

8. Rule execution logging (runtime)

Every rule execution emits a Rule Execution Event:

  • which rule ran (CRID / zar_ref)
  • which signals/records it evaluated
  • result + enforcement action
  • override flags (if user exception applied)
  • audit hash (external integrity anchor if required)

This gives end-to-end accountability from authored intent → selected artifact → executed rule.


APPENDIX A - Truststore

JiraStoryUnlinkedZYZ-796ZAPE#appendix-a-truststore#1

To manage signing keys issued by AWS KMS, ZAPE uses /config/zape/truststore.v1.json.

This file must be updated when new Keys are issues. We store old keys in the file for reference and for verifying old rulesets (see line 24-38 and excerpt below).

The policy.verification section controls how the ZAYAZ Policy Engine treats retired and revoked keys when verifying rulesets.

Example:

policy-verification-example.jsonGitHub ↗
{
"verification": {
"allow_retired_keys": true,
"reject_revoked_keys": true
}
}

Meaning:

PolicyBehavior
allow_retired_keysold rulesets signed with previous keys remain valid
reject_revoked_keyscompromised keys invalidate all signatures

Retired keys remain in the truststore so previously published rulesets can still be verified even after a key rotation.

Note: The Ruleset Generator UI displays notifications when signing keys approach rotation or expiration. (Please see more information after the code examples.)

truststore.v1.jsonGitHub ↗
{
"version": 1,
"truststore_id": "ZAPE_TRUSTSTORE",
"generated_at": null,
"keys": [
{
"key_id": "zape-dev-ci-kms-2026-03",
"env": "dev",
"alg": "ecdsa_p256_sha256",
"signer_role": "ci_pipeline",
"public_key_b64": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEGvRKDYusZKWgQw15WuqZt2r1RMVfSkyiIRov5NIB66pVpXkvLJF3lQOjxvfRIKa3iZL3Nr5OwQuICy4GO+ZioA==",
"kms_key_arn": "arn:aws:kms:eu-north-1:861276122064:key/8b6b416c-c77d-41fe-b225-1f4e3fde9193",
"alias": "alias/zape-dev-ruleset-signing",
"status": "active",
"not_before": "2026-03-01T00:00:00Z",
"not_after": "2027-03-01T00:00:00Z",
"owner": "security@viroway.com",
"comment": "Primary dev signing key",
"rotation": {
"rotate_after_days": 180,
"grace_verify_days": 365
}
},
{
"key_id": "zape-dev-ci-kms-2025-09",
"env": "dev",
"alg": "ecdsa_p256_sha256",
"public_key_b64": "BASE64_SPKI_DER_DEV_2025_09...",
"kms_key_arn": "arn:aws:kms:eu-north-1:861276122064:key/DEV_OLD_KEY_UUID...",
"alias": "alias/zape-dev-ruleset-signing-prev",
"status": "retired",
"not_before": "2025-09-01T00:00:00Z",
"not_after": "2026-09-01T00:00:00Z",
"retired_at": "2026-03-01T00:00:00Z",
"superseded_by": "zape-dev-kms-2026-03",
"owner": "security@viroway.com",
"comment": "Retired; keep for verifying old rulesets"
},
{
"key_id": "zape-staging-ci-kms-2026-03",
"env": "staging",
"alg": "ecdsa_p256_sha256",
"signer_role": "ci_pipeline",
"public_key_b64": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE9krITHRnCvy61Ro70aOy+rumes4u5l1lvygyvpG3xtvQmDLBIFm+lMARI+O7WMMb7GB+QT+aJxt195UHkDkcdg==",
"kms_key_arn": "arn:aws:kms:eu-north-1:861276122064:key/ec1becec-72bc-4412-9222-4227e08084ad",
"alias": "alias/zape-staging-ruleset-signing",
"status": "active",
"not_before": "2026-03-01T00:00:00Z",
"not_after": "2027-03-01T00:00:00Z",
"owner": "security@viroway.com",
"comment": "Primary staging signing key",
"rotation": {
"rotate_after_days": 180,
"grace_verify_days": 365
}
},
{
"key_id": "zape-staging-governance-kms-2026-03",
"env": "staging",
"alg": "ecdsa_p256_sha256",
"signer_role": "governance_approval",
"public_key_b64": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEHWsq3a/5XNg9yLoxINA6xichV3R1ac2XuVDdaPD1DSrYhJnd5Lf5CNBpPicA4npexZqMNGpAODe4+GL1V2ly2w==",
"status": "active",
"not_before": "2026-03-01T00:00:00Z",
"not_after": "2027-03-01T00:00:00Z",
"owner": "security@viroway.com",
"comment": "Governance approval signing key",
"rotation": {
"rotate_after_days": 180,
"grace_verify_days": 365
}
},
{
"key_id": "zape-prod-ci-kms-2026-03",
"env": "prod",
"alg": "ecdsa_p256_sha256",
"signer_role": "ci_pipeline",
"public_key_b64": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEOcPBOqkKQ+DfW+NgloIwRN2GhS/IBX4LraIz0pVymX9VI5TyKFCvgZsjojO5kx6B6JjWwRChVwNjZGUVdIHjFA==",
"kms_key_arn": "arn:aws:kms:eu-north-1:861276122064:key/ab55d7da-8656-4943-98fa-5c6616dbdd89",
"alias": "alias/zape-prod-ruleset-signing",
"status": "active",
"not_before": "2026-03-01T00:00:00Z",
"not_after": "2027-03-01T00:00:00Z",
"owner": "security@viroway.com",
"comment": "Primary prod signing key",
"rotation": {
"rotate_after_days": 180,
"grace_verify_days": 365
}
},
{
"key_id": "zape-prod-governance-kms-2026-03",
"env": "prod",
"alg": "ecdsa_p256_sha256",
"signer_role": "governance_approval",
"public_key_b64": "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEG60VfCyjvkLc33w8SwzMmL0S1YeuMow/9enJ3n49Gpnd2Su7ZYHkRWSX4ZOpHTAhiOamK9QZ3mAgYafCfuEDdQ==",
"status": "active",
"not_before": "2026-03-01T00:00:00Z",
"not_after": "2027-03-01T00:00:00Z",
"owner": "security@viroway.com",
"comment": "Governance approval signing key",
"rotation": {
"rotate_after_days": 180,
"grace_verify_days": 365
}
}
],
"policy": {
"verification": {
"allow_retired_keys": true,
"reject_revoked_keys": true
},
"multi_signature": {
"required_by_env": {
"dev": {
"min_valid_signatures": 1,
"required_signer_roles": ["ci_pipeline"]
},
"staging": {
"min_valid_signatures": 1,
"required_signer_roles": ["ci_pipeline"]
},
"prod": {
"min_valid_signatures": 2,
"required_signer_roles": ["ci_pipeline", "governance_approval"]
}
}
},
"signing": {
"default_alg_by_env": {
"dev": "ecdsa_p256_sha256",
"staging": "ecdsa_p256_sha256",
"prod": "ecdsa_p256_sha256"
},
"active_signing_keys_by_env": {
"dev": {
"ci_pipeline": "zape-dev-ci-kms-2026-03"
},
"staging": {
"ci_pipeline": "zape-staging-ci-kms-2026-03"
},
"prod": {
"ci_pipeline": "zape-prod-ci-kms-2026-03",
"governance_approval": "zape-prod-governance-kms-2026-03"
}
}
}
}
}

Retired Keys:

retired-excerpt-truststore.v1.jsonGitHub ↗
    {
"key_id": "zape-dev-ci-kms-2025-09",
"env": "dev",
"alg": "ecdsa_p256_sha256",
"public_key_b64": "BASE64_SPKI_DER_DEV_2025_09...",
"kms_key_arn": "arn:aws:kms:eu-north-1:861276122064:key/DEV_OLD_KEY_UUID...",
"alias": "alias/zape-dev-ruleset-signing-prev",
"status": "retired",
"not_before": "2025-09-01T00:00:00Z",
"not_after": "2026-09-01T00:00:00Z",
"retired_at": "2026-03-01T00:00:00Z",
"superseded_by": "zape-dev-kms-2026-03",
"owner": "security@viroway.com",
"comment": "Retired; keep for verifying old rulesets"
}

**Note: ** The Ruleset Generator UI (/workspaces/zayaz-docs/content/system/rulesets.mdx) will:

  • Identify the signing key for the current env (usually the only status: active key with matching env)
  • Compute:
    • days_to_not_after = not_after - now
    • days_since_not_before = now - not_before
    • If rotation.rotate_after_days is set:
    • days_to_rotation = (not_before + rotate_after_days) - now

Then show:

  • ✅ Healthy (green): > 30 days remaining
  • ⚠️ Warning (yellow): 30–7 days
  • ❌ Critical (red): < 7 days or already expired

Also: show “retired keys still trusted for verify until …” using grace_verify_days (or simply show not_after for retired keys).


Signature verification flow

When a ruleset is loaded, the engine performs the following steps:

  1. Load truststore.v1.json
  2. Lookup the signature key_id
  3. Enforce truststore verification policy
  4. Validate key validity window (not_before, not_after)
  5. Verify the cryptographic signature
  6. Reject rulesets with invalid signatures

In the prod environment, unsigned or invalid rulesets are rejected.


Key Issuing Architecture

Signing Infrastructure (AWS / GitHub OIDC):

GitHub Actions


AWS OIDC


IAM Roles
├─ dev signer
│ ├─ KMS dev key
│ └─ S3 dev rulesets

├─ staging signer
│ ├─ KMS staging key
│ └─ S3 staging rulesets

└─ prod signer
├─ KMS prod key
└─ S3 prod rulesets

Ruleset Signature Verification Flow:

Ruleset YAML

│ signed by

CI signing key (dev / staging / prod)

│ optional additional approval signature

Governance signing key (staging / prod)


generateRulesetsCatalog.mjs

│ verifies:
│ • canonical hash
│ • all signatures
│ • signer roles
│ • environment policy

Rulesets Catalog

Multi-signature rulesets

ZAPE supports multi-signature rulesets through zar.signatures[].

This enables cryptographic separation of duties. A ruleset may require:

  • a CI pipeline signature, proving deterministic generation and publication
  • a governance/security signature, proving approval for production use

Environment policy determines how many valid signatures are required, and which signer roles must be present.

This lets us require rules like:

  • dev: 1 valid signature is enough
  • staging: 1 CI signature + optional governance signature
  • prod: at least 2 valid signatures:
    • CI pipeline
    • governance/security approval

Signer Role

signer_role defines the semantic purpose of a key, such as ci_pipeline or governance_approval. ZAPE uses signer roles, not just key IDs, when enforcing multi-signature policy.

EnvMin signaturesRequired roles
dev1ci_pipeline
staging1ci_pipeline
prod2ci_pipeline, governance_approval

Active Key Selection

ZAPE should resolve signing keys via:

  • env
  • signer_role
  • active status

not just env alone.


Signing ruleset YAMLs

The signRulesetsWithKms.mjs signs ruleset YAMLs with AWS KMS (ECDSA P-256 / SHA-256) and writes:

zar-signing-ruleset.yamlGitHub ↗
zar:
content_hash: "sha256:..."
signatures:
- alg: "ecdsa_p256_sha256"
key_id: "zape-prod-ci-kms-2026-03"
signer_role: "ci_pipeline"
signed_hash: "sha256:..."
signed_at: "2026-03-06T10:14:00Z"
sig_b64: "..."
- alg: "ecdsa_p256_sha256"
key_id: "zape-prod-governance-kms-2026-03"
signer_role: "governance_approval"
signed_hash: "sha256:..."
signed_at: "2026-03-06T10:18:00Z"
sig_b64: "..."

Usage:

  node scripts/signRulesetsWithKms.mjs \
--stage dev \
--rulesetsDir code/rulesets/dev \
--keyId alias/zape-dev-ruleset-signing \
--keyIdLabel zape-dev-kms-2026-03

Notes:

  • Requires AWS credentials in env (OIDC in GitHub Actions is ideal)
  • Requires: npm i -D yaml @aws-sdk/client-kms
  • Canonicalization matches generateRulesetsCatalog.mjs: removes zar.content_hash / zar.signature / zar.signatures before hashing/signing

Usage

Dev (bash):

node scripts/signRulesetsWithKms.mjs \
--stage dev \
--rulesetsDir code/rulesets/dev \
--keyId alias/zape-dev-ruleset-signing \
--keyIdLabel zape-dev-ci-kms-2026-03 \
--signerRole ci_pipeline

Staging CI (bash):

node scripts/signRulesetsWithKms.mjs \
--stage staging \
--rulesetsDir code/rulesets/staging \
--keyId alias/zape-staging-ruleset-signing \
--keyIdLabel zape-staging-ci-kms-2026-03 \
--signerRole ci_pipeline

Staging governance (bash):

node scripts/signRulesetsWithKms.mjs \
--stage staging \
--rulesetsDir code/rulesets/staging \
--keyId alias/zape-staging-governance-signing \
--keyIdLabel zape-staging-governance-kms-2026-03 \
--signerRole governance_approval

Prod CI (bash):

node scripts/signRulesetsWithKms.mjs \
--stage prod \
--rulesetsDir code/rulesets/prod \
--keyId alias/zape-prod-ruleset-signing \
--keyIdLabel zape-prod-ci-kms-2026-03 \
--signerRole ci_pipeline

Prod governance (bash):

node scripts/signRulesetsWithKms.mjs \
--stage prod \
--rulesetsDir code/rulesets/prod \
--keyId alias/zape-prod-governance-signing \
--keyIdLabel zape-prod-governance-kms-2026-03 \
--signerRole governance_approval

Install deps:

npm i -D yaml @aws-sdk/client-kms



GitHub RepoRequest for Change (RFC)