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:
meidand engine family/category- supported
ruleset_kindvalues (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:
- Entity override (most specific)
- Tenant override
- 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 2enforcement_mode: soft vs blocking- compatibility
schema_min/max: v1 vs v2 audit_required: true
…come from one of three places:
- Explicit intent authored by the rule owner (via UI or YAML)
- Defaults from engine profile or policy (deterministic, documented)
- 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
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:
{
"verification": {
"allow_retired_keys": true,
"reject_revoked_keys": true
}
}
Meaning:
| Policy | Behavior |
|---|---|
| allow_retired_keys | old rulesets signed with previous keys remain valid |
| reject_revoked_keys | compromised 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.)
{
"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:
{
"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:
- Load truststore.v1.json
- Lookup the signature
key_id - Enforce truststore verification policy
- Validate key validity window (
not_before,not_after) - Verify the cryptographic signature
- 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.
| Env | Min signatures | Required roles |
|---|---|---|
| dev | 1 | ci_pipeline |
| staging | 1 | ci_pipeline |
| prod | 2 | ci_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:
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.signaturesbefore 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