regression/ policies (3): cap-013-adapter-dedup, cap-023-metrics-collector, cap-024-deck-structure — declarative mirrors of core/regression_verify.py over capability-inventory JSON. The imperative regression_verify.py is kept (drives CI gate); the policies are the declarative mirror (IDEATE I1 quality improvement). tests: test_regression_policies.py + clean/drifted fixtures. Skip-without-kj. docs: adapters/README.md (new kyverno-json row + PolicyEngine Protocol section with how-to-add-OpaEngine), adapters/kyverno-json/README.md (engine, install, policy directory layout, 4 categories, severity convention), schemas/README.md (D-116 engine enum reuse note), modules/STANDARDS.md §10 Policy Authoring Standard, docs/METRICS.md (swappable engine narrative). ---ci--- project: acdl phase: 4 milestone: v1.25 status: execute phase_role: execution requirements: covered: [REQ-304, REQ-305, REQ-306, REQ-307] partial: [] ---/ci---
28 KiB
Nova Module Engineering Standards
Standards for authoring and reviewing Nova modules. These standards govern the two module tiers — L1 primitives (single cloud resource or small group of related resources) and L2 modules (compositions that reference L1 primitives to deploy a complete stack) — and the engine adapter that compiles them to Terraform. They are written for platform engineers and AI agents that author or review new modules against the existing corpus (12 L1 primitives and 2 L2 modules shipped in v1.8).
A module that fails any section below is not ready to publish.
1. Overview
These standards codify the conventions already established by the
shipped modules (s3, vpc, ecs-cluster, ecs-service, iam-role,
alb, ecr, cloudfront, waf, rds, kms-key, uptime; the L2
modules static-assets and microservice). They exist so that:
- platform engineers can review a new module against a fixed checklist;
- AI agents authoring modules produce code that passes review without iteration; and
- the engine adapter (
adapters/terraform/adapter.py) can compile a module instance with no module-specific code in the adapter beyond the three tables in §8.
When this document and an existing module disagree, the existing module is the authority for v1.x. A change to this document is a MINOR version bump of the standards; a change that breaks shipped modules is a MAJOR bump and requires a migration plan.
2. L1 Primitive Standards
An L1 primitive is a single cloud resource or a small group of related
resources (e.g. a VPC with subnets and a route table). It is declared by
an interface.json and realized by the engine adapter; it does not
own Terraform code.
2.1 Required files
Every L1 primitive MUST contain, at minimum:
| File | Purpose |
|---|---|
interface.json |
Angine-agnostic declaration: inputs, outputs, NFRs, optional multi-resource graph. |
instance.json |
A concrete instance used as the adapter regression baseline. |
README.md |
Plain-language documentation following README-TEMPLATE.md (see §7). |
examples/simple.yml |
A minimal contract that uses the primitive with required inputs only. |
examples/complex.yml |
A contract that exercises optional inputs, NFRs, and (if applicable) the multi-resource graph. |
Directory layout:
modules/l1/<name>/
interface.json
instance.json
README.md
examples/
simple.yml
complex.yml
2.2 interface.json schema
interface.json MUST be a JSON object with the following required
fields:
| Field | Type | Constraint |
|---|---|---|
name |
string | ^[a-z][a-z0-9-]*$; MUST match the module folder name. |
version |
string | Semver (^\d+\d+\.\d+$); MUST match the registry entry semver. |
kind |
string | Literal "l1". |
type |
string | Stack type in aws:<service>:<kind> format (see §2.7). |
description |
string | One or two sentences in plain language; no Terraform jargon. |
inputs |
object | Keyed by input name; each value is an input declaration (§2.3). MAY be empty. |
outputs |
object | Keyed by output name; each value is an output declaration (§2.4). MAY be empty. |
nfrs |
object | Keyed by NFR name; each value is an NFR declaration (§2.5). MUST include deletion_protection and encryption_enabled. |
Optional fields for multi-resource primitives:
| Field | Type | Constraint |
|---|---|---|
resources |
array | One entry per distinct cloud resource; see §2.6. |
intra_refs |
array | Internal wiring between resources; see §2.6. |
A primitive that creates a single resource (e.g. s3, iam-role,
rds, kms-key) omits resources and intra_refs; its type field
is the single resource's stack type. A primitive that creates a small
group of related resources (e.g. vpc, alb, cloudfront) declares
resources[] with one entry per resource and intra_refs[] for the
internal wiring; its type field is the primary resource's stack
type.
2.3 Input declaration
Each entry in inputs is an object:
| Field | Type | Required | Notes |
|---|---|---|---|
type |
string | yes | One of: string, number, boolean, array, object. |
description |
string | yes | Plain language; no Terraform jargon. |
required |
boolean | yes | true if the consumer MUST supply this input. |
default |
(any) | no | Present only when required is false. MUST match the declared type. |
enum |
array | no | Allowed values for string/number inputs (e.g. RDS engine). |
region is a required string input on every primitive that creates a
regional resource. Global resources (e.g. CloudFront) still declare
region because the provider region is used for child resources (the
OAC in the cloudfront case).
Every primitive that holds at-rest data MUST declare an optional
kms_key_arn input (string, required: false); see §4.
2.4 Output declaration
Each entry in outputs is an object:
| Field | Type | Required | Notes |
|---|---|---|---|
type |
string | yes | arn for ARN outputs; string for all others. |
description |
string | yes | Plain language. |
Use arn (not string) for any output that returns an AWS ARN — the
adapter and policy engine key off the arn type to apply ARN-scoped
rules.
2.5 NFR declaration
Each entry in nfrs is an object:
| Field | Type | Required | Notes |
|---|---|---|---|
type |
string | yes | One of: string, number, boolean. |
description |
string | yes | Plain language. |
default |
(any) | yes | MUST match the declared type. NFRs always have a default. |
Mandatory NFRs on every L1:
| NFR | Type | Default | Notes |
|---|---|---|---|
deletion_protection |
boolean | true |
See §5. |
encryption_enabled |
boolean | true |
See §4. |
A primitive for which an NFR does not conceptually apply (e.g. an IAM
role has no at-rest data) still declares it with default: true and a
description noting the non-applicability, so the standards check and the
adapter emit logic stay uniform. The shipped iam-role primitive is the
reference for this case.
Additional NFRs are encouraged where they carry operational meaning
(e.g. s3.versioning, rds.backup_retention_period,
kms-key.enable_rotation, vpc.flow_logs_encrypted). Name them in
lowercase snake_case.
2.6 Multi-resource pattern
A primitive that creates more than one cloud resource (e.g. vpc
creates aws_vpc + aws_subnet + aws_route_table; alb creates
aws_lb + aws_lb_target_group + aws_lb_listener; cloudfront
creates aws_cloudfront_distribution +
aws_cloudfront_origin_access_control) declares a resources array.
Each resources[] entry:
| Field | Type | Notes |
|---|---|---|
type |
string | The resource's stack type (aws:<service>:<kind>). |
description |
string | Plain language. |
inputs |
array | Names (strings) of inputs from the top-level inputs object that this resource consumes. |
outputs |
array | Names (strings) of outputs from the top-level outputs object that this resource produces. |
The top-level inputs/outputs objects remain the single source of
truth; resources[].inputs and resources[].outputs are arrays of
names referencing those objects, not re-declarations.
intra_refs[] wires outputs of one resource to inputs of another
within the same primitive. Each entry:
| Field | Type | Notes |
|---|---|---|
from |
string | <resource-type>.<output-name> — the producing side. |
to |
string | <resource-type>.<input-name> — the consuming side. |
Reference: cloudfront/interface.json declares an intra-ref from
aws:cloudfront:distribution.oac_id to
aws:cloudfront:originaccesscontrol.oac_id; vpc/interface.json
declares intra-refs from the subnet and route table to the VPC's
vpc_id.
2.7 Naming and stack types
- Module folder names and
interface.jsonnamevalues MUST match^[a-z][a-z0-9-]*$(lowercase, hyphenated, leading letter). Examples:s3,ecs-cluster,kms-key,iam-role,uptime. - Input and output names are lowercase snake_case.
- Stack types follow
aws:<service>:<kind>:aws:s3:bucketaws:ec2:vpc,aws:ec2:subnet,aws:ec2:routetableaws:ecs:cluster,aws:ecs:task_definition,aws:ecs:service,aws:ecs:uptime-serviceaws:iam:roleaws:elbv2:loadbalancer,aws:elbv2:listener,aws:elbv2:targetgroupaws:ecr:repositoryaws:cloudfront:distribution,aws:cloudfront:originaccesscontrolaws:wafv2:webaclaws:rds:instanceaws:kms:key,aws:kms:alias
- The engine adapter is a stateless assembler (v1.11, D-098): it reads
the registry, emits a root
main.tfinstantiating each L1 asmodule "x" { source = "..." }with resolved inputs and wired refs. There is noTYPE_MAP(deleted in the v1.11 stateless rewrite). A new stack type requires aterraform/dir in the L1 module + a registry entry with aterraform_dirfield.
3. L2 Module Standards
An L2 module is a composition that references one or more L1 primitives
to deploy a complete stack (e.g. an ECS Fargate microservice, a static
asset site behind CloudFront + WAF). It is declared by a
composition.json; it does not own Terraform code and does not have an
instance.json.
3.1 Required files
| File | Purpose |
|---|---|
composition.json |
The composition tree: children, wires, outputs, optional features. |
README.md |
Plain-language documentation following README-TEMPLATE.md (see §7). |
examples/simple.yml |
A minimal contract that uses the module with required inputs only. |
examples/complex.yml |
A contract that exercises optional inputs and feature flags. |
Directory layout:
modules/l2/<name>/
composition.json
README.md
examples/
simple.yml
complex.yml
There is no instance.json for an L2 module — the L2 is deployed by
resolving the composition tree to L1 instances at compile time, not by
loading a pre-baked instance.
3.2 composition.json schema
composition.json MUST be a JSON object with the following fields:
| Field | Type | Required | Notes |
|---|---|---|---|
name |
string | yes | ^[a-z][a-z0-9-]*$; matches the module folder name. |
version |
string | yes | Semver; matches the registry entry. |
kind |
string | yes | Literal "l2". |
depth |
integer | yes | Literal 1 in v1 (see §3.5). |
description |
string | yes | Plain language. |
children |
array | yes | One entry per referenced L1 module (§3.3). |
wires |
array | yes | Wires from contract inputs / child outputs to child inputs / stack outputs (§3.4). |
outputs |
array | yes | Wires from child outputs to stack outputs (§3.4). |
features |
object | no | Feature flags propagated to children by the resolver (§3.6). |
3.3 Children
Each children[] entry:
| Field | Type | Notes |
|---|---|---|
id |
string | The child id, unique within the composition. ^[a-z][a-z0-9-]*$. The id is the local name used in wires (e.g. vpc, cluster, kms). |
module |
string | <name>@<semver> referencing a registered L1 module. |
Children MUST reference L1 modules registered in registry.json (see
§6). The referenced semver MUST exist in the registry. An L2 MUST NOT
reference another L2 (no L3 in v1; see §3.5).
Reference: microservice/composition.json declares seven children
(vpc, cluster, ecr, roles, alb, service, kms), each
referencing an L1 at @1.0.0.
3.4 Wire format
A wire is a JSON object {"from": "<source>", "to": "<target>"} with an
optional default field for contract-input wires.
Sources (the from side):
| Source form | Meaning |
|---|---|
contract.inputs.<name> |
A value supplied by the consumer's contract YAML. |
<childId>.outputs.<name> |
An output produced by a child L1 module. |
Targets (the to side):
| Target form | Meaning |
|---|---|
<childId>.inputs.<name> |
An input on a child L1 module. |
stack.outputs.<name> |
A value the L2 exposes as a stack output. |
Wires that source from contract.inputs.<name> MAY carry a default
value used when the consumer omits the input. Reference:
microservice/composition.json wires contract.inputs.bucket_name to
vpc.inputs.cidr with default: "10.0.0.0/16" (a historical quirk
preserved for regression).
The outputs[] array uses the same wire shape but its to is always
stack.outputs.<name> and its from is always
<childId>.outputs.<name>.
3.5 Maximum depth
depth is 1 for every L2 in v1. The composition tree is strictly L2
→ L1: an L2 may reference only L1 primitives, never another L2. There
is no L3 in v1. The stack schema permits depth up to 5 for forward
compatibility, but the v1 resolver and adapter only handle depth 1.
3.6 Feature flags
An L2 MAY declare a features object. Two flags are defined in v1:
| Flag | Type | Default | Effect |
|---|---|---|---|
deletion_protection |
boolean | true |
When true, the resolver propagates deletion_protection: true to every child's NFRs. When false, children are deployed with deletion_protection: false (used by decommission; see §5). |
uptime_enabled |
boolean | true |
When true, the uptime monitoring L1 is deployed after the L2 module in a separate terraform state. When false, the uptime deployment is skipped. |
Feature flags are propagated to children by the resolver; the L2
composition.json does not need to wire them explicitly as inputs. The
resolver reads features and injects the corresponding NFR/input on
each child.
4. Encryption by Default
Encryption is mandatory and on by default across the platform.
- Every L1 MUST declare an
encryption_enabledNFR (boolean, defaulttrue) ininterface.json. See §2.5. - Every L1 that holds at-rest data (S3, RDS, ECR, ECS task
definition env, VPC flow logs, CloudWatch log groups) MUST declare an
optional
kms_key_arninput (string,required: false). When supplied, the adapter wires it to the resource's KMS encryption argument. - L2 modules MUST wire a per-stack customer-managed KMS key to all
children that accept
kms_key_arn. The KMS key is akms-keychild of the L2 — one key per L2 deployment, no shared keys. Reference: bothstatic-assetsandmicroservicedeclare akmschild (kms-key@1.0.0) and wirekms.outputs.kms_key_arnto every child that accepts a CMK. - For a standalone L1 deployment (an L1 used outside an L2), if the
consumer does not supply
kms_key_arn, the adapter falls back to the AWS-managed default key for that service and emits a warning to stderr. The primitive is still encrypted; only the key manager differs. - The
kms-keyprimitive enables key rotation by default (enable_rotationNFR, defaulttrue), and the adapter emitsenable_key_rotation = trueon theaws_kms_keyresource.
A primitive that does not hold at-rest data (e.g. iam-role,
ecs-cluster, alb) still declares encryption_enabled for standards
uniformity (see §2.5) but does not declare kms_key_arn.
5. Deletion Protection by Default
Deletion protection is mandatory and on by default to prevent accidental teardown of production infrastructure.
- Every L1 MUST declare a
deletion_protectionNFR (boolean, defaulttrue) ininterface.json. See §2.5. - When
deletion_protectionistrue, the engine adapter emits alifecycle { prevent_destroy = true }block on the corresponding Terraform resource. Aterraform destroyagainst a protected resource fails with an error naming the resource. - L2 modules expose
features.deletion_protection(defaulttrue). The resolver propagates the flag to every child's NFRs (see §3.6). - Decommission mode. To tear down a stack that was deployed with
deletion protection, the consumer sets
inputs.deletion_protection: falseon the contract (orfeatures.deletion_protection: falseon an L2) and re-applies. The decommission transform (decommission_transform) zeroes capacity counts (e.g. ECS desired count to 0, RDS allocated storage to the minimum) so that the subsequentdestroyapplies against a quiesced stack. The transform is applied by the resolver before the adapter emits resources.
6. Registry
Every module — L1 and L2 — MUST be registered in
modules/registry.json at its semver. The registry is the source of
truth for what is published; the adapter and resolver refuse to compile
a module that is not registered.
Registry entry shape:
{
"<module-name>": {
"<semver>": {
"interface": "modules/<l1|l2>/<module-name>/<interface.json|composition.json>",
"published_at": "<ISO 8601 timestamp>",
"deprecated": false
}
}
}
interfaceis the path (relative to the repo root) to the module's interface file —interface.jsonfor an L1,composition.jsonfor an L2.published_atis an ISO 8601 timestamp. Use a fullYYYY-MM-DDTHH:MM:SSZform; do not omit the seconds or the timezone designator.deprecatedisfalsefor a live module. A MAJOR version bump does not delete the old entry; it flipsdeprecatedtotrueand starts a 12-month deprecation window (see §7 Versioning).
A new semver of an existing module is a new key under the module's object; old semvers are retained. The registry is append-only for published semvers — a published semver is never edited or deleted.
7. README Standards
Every module README MUST follow the structure of
modules/README-TEMPLATE.md. Required sections, in order:
# <name> — <plain-language description>— title with the module name and a one-line description.## Overview— one or two sentences in plain language.## Resources— a table of the Terraform resources the module creates (L1) or the primitives it references (L2).## Inputs— a table:| Name | Type | Required | Default | Description |.## Outputs— a table:| Name | Type | Description |.## NFRs— a table:| Name | Type | Default | Description |.deletion_protectionandencryption_enabledare mandatory NFRs for every L1; they MUST appear in this table.## Usage— a concrete snippet showing how a consumer references the module in a contract.## Compliance extension points— resources or behaviors that could be added for the future compliance milestone (GDPR, SOX, SOC2, DORA). Not implemented yet; listed so the redesign can plan for them.## Examples— links toexamples/simple.ymlandexamples/complex.ymlwith a one-line description of each.## Versioning— the module's semver policy: interface MAJOR, behavior MINOR, lifecycle PATCH. MAJOR bumps require a newregistry.jsonentry (immutable publication); old entries enter a 12-month deprecation window.
An L2 README's ## Resources section lists the referenced L1 children
rather than Terraform resources, and its ## Inputs/## Outputs
sections reflect the contract inputs and stack outputs of the
composition.
8. Stateless Assembler Pattern
The Terraform adapter (adapters/terraform/adapter.py) is a stateless
assembler (~80 lines). It owns no module content — no resource shape, no
nested HCL blocks, no defaults, no type-specific logic. It reads the
registry to find each L1 module's terraform/ dir, then emits a root
main.tf that instantiates each resource as a
module "<rid>" { source = ... } block with resolved inputs and wired refs.
Engine-specific knowledge (resource type, arg names, nested blocks,
defaults, NFRs) lives in the per-module terraform/ subdir, NOT in the
adapter. interface.json stays engine-agnostic (the contract); the
terraform/ dir is the engine binding. A future Azure adapter would add
an azure/ subdir per module without touching interface.json.
8.1 Per-module terraform dir
Each L1 module ships a terraform/ subdir:
modules/l1/<name>/terraform/
├── versions.tf # required_version + required_providers (aws ~> 5.0)
├── variables.tf # one variable {} per interface.json input
├── locals.tf # HEAVY: centralizes var-vs-default interpolation
├── main.tf # resource {} blocks referencing locals (not vars directly)
└── outputs.tf # one output {} per interface.json output
locals.tf is the key file. Every default that was previously
hardcoded in the adapter (CIDR blocks, assume_role_policy JSON, ECR/logs
inline policy, Fargate requires_compatibilities, assign_public_ip,
listener/target ports) moves here as a locals block that interpolates
the variable against its sensible default:
locals {
cidr_block = var.cidr != null ? var.cidr : "10.0.0.0/16"
assume_role_policy = var.assume_role_policy != null ? var.assume_role_policy : jsonencode({ ... })
}
main.tf stays clean — pure resource blocks referencing local.*, never
interpolating vars directly. Trivial single-resource modules (e.g.
kms-key, ecr) may inline locals in main.tf; multi-resource modules
get the full 5-file split.
8.2 How the adapter assembles
Given a resolved stack instance, the adapter:
- Reads
modules/registry.json→ builds amodule_name → terraform_dirmap. - For each resource, extracts the module name from the resource's
modulefield (e.g.s3@1.0.0→s3), looks upterraform_dir, and emits amodule "<rid>" { source = "<absolute terraform_dir>" ... }block. - Passes each input (except
region, which is provider-level) as a module argument. Forref:<rid>.<output>values, emitsmodule.<rid>.<output>interpolations (terraform-native module outputs). - Emits root
output {}blocks wiring module outputs to stack outputs. - Emits
providers.tf(aws provider, region from the first resource) +terraform.tf(required_version + required_providers + S3 backend).
The adapter owns NO resource shape, NO nested blocks, NO defaults, NO type-specific logic. It only assembles module instantiations and wires refs.
8.3 Adding a new L1
When a new L1 primitive is added:
- Author the
terraform/subdir (versions.tf/variables.tf/locals.tf/main.tf/outputs.tf) with the resource shape, nested blocks, and defaults. Defaults go inlocals.tf(heavy interpolation of vars against sensible defaults). - Add a
terraform_dirfield to the module'sregistry.jsonentry. - Author
interface.json(engine-agnostic),instance.json(regression baseline),README.md, andexamples/{simple,complex}.yml.
No adapter code changes. The adapter is generic; it assembles any
module that has a terraform_dir in the registry.
9. Code Review Checklist
Use this checklist when reviewing a new module (L1 or L2). Every box must be checked before the module is registered and published.
9.1 Files and structure
- All required files present:
- L1:
interface.json,instance.json,README.md,examples/simple.yml,examples/complex.yml,terraform/(versions.tf, variables.tf, locals.tf, main.tf, outputs.tf). - L2:
composition.json,README.md,examples/simple.yml,examples/complex.yml(noinstance.json, noterraform/).
- L1:
interface.json(L1) /composition.json(L2) validates againstschemas/stack.schema.json.examples/simple.ymlandexamples/complex.ymlvalidate againstschemas/contract.schema.json.- Module registered in
modules/registry.jsonat its semver with a full ISO 8601published_atanddeprecated: false.
9.2 Interface (L1)
namematches the folder name and^[a-z][a-z0-9-]*$.versionis semver and matches the registry entry.kindis"l1".typefollowsaws:<service>:<kind>.- Every input has
type,description,required; optional inputs carry adefaultof the correct type;enumpresent where the value set is constrained. - Every output has
type(arnfor ARNs,stringotherwise) anddescription. nfrsincludesdeletion_protection(boolean, defaulttrue) andencryption_enabled(boolean, defaulttrue).kms_key_arninput present if the primitive holds at-rest data.- Multi-resource primitives declare
resources[](withinputs/outputsas arrays of names) andintra_refs[]with{from, to}.
9.3 Composition (L2)
kindis"l2"anddepthis1.- Every
children[]entry is{id, module}withmodulein<name>@<semver>form referencing a registered L1. - No child references an L2 (no L3 in v1).
wires[]use thecontract.inputs.<name>/<childId>.outputs.<name>→<childId>.inputs.<name>/stack.outputs.<name>forms.outputs[]use<childId>.outputs.<name>→stack.outputs.<name>.- A
kmschild (kms-key@<semver>) is present and itskms_key_arnoutput is wired to every child that accepts a CMK. features(if present) only uses defined flags (deletion_protection,uptime_enabled).
9.4 Adapter (stateless assembler)
- The new primitive's
terraform/subdir exists withversions.tf/variables.tf/main.tf/outputs.tfand passesterraform init + validatestandalone.locals.tfis required for multi-resource modules; trivial single-resource modules (e.g.kms-key,ecr,ecs-cluster) may inline locals inmain.tf. registry.jsonhas aterraform_dirfield for the new primitive.- No adapter code changes are needed (the adapter is generic; it
assembles any module with a
terraform_dirin the registry). - The new primitive's
instance.jsonround-trips through the adapter without error (regression baseline — the adapter emits a rootmain.tfwith amodule "<rid>" { source = ... }block).
9.5 README and docs
- README follows
README-TEMPLATE.mdwith all required sections in order (§7). ## NFRstable listsdeletion_protectionandencryption_enabledfor an L1.## Compliance extension pointslists at least one plausible future extension.
9.6 Tests
- A test is added for the new primitive covering adapter emission
(the Terraform output for
instance.jsonmatches the expected fixture) and interface validation (interface.jsonvalidates againststack.schema.json). - For an L2, a test is added that the composition resolves to the expected set of L1 instances and that the adapter emits a root module calling the L1 modules.
10. Policy Authoring Standard (v1.25)
Module owners may ship per-module kyverno-json policies in
modules/<name>/policies/ (future convention; v1.25 policies live
under adapters/kyverno-json/policies/). A policy file is a
ValidatingPolicy resource (YAML or JSON).
10.1 Required fields
apiVersion: json.kyverno.io/v1alpha1kind: ValidatingPolicymetadata.name— matches the filename (e.g.require-tags.json→name: require-tags). This becomes theruleIdprefixKJ_<name>.metadata.annotations["nova.cloudinit.dev/severity"]— one ofcritical,high,medium,low,info. Drives the confidence signal's penalty mapping.spec.rules[].validate.assert— analloranylist of assertion trees with JMESPath expressions. NoforEach, pattern operators, anchors, or wildcards — use the~projection modifier to iterate.
10.2 Severity guidance
| Severity | When to use | Confidence penalty |
|---|---|---|
critical |
a violation makes the deploy unsafe (e.g. public ingress on a prod DB) | hard override (score = 0, block) |
high |
a violation is a security or compliance gap (e.g. plaintext secrets) | -0.20 |
medium |
a violation is a best-practice miss (e.g. missing tags) | -0.05 |
low |
a violation is a style or convention issue | -0.01 |
info |
a non-blocking observation (default) | 0.0 |
10.3 Assertion-tree patterns
- Iterate an array: use the
~modifier on the array key:check: ~.resources: (@ < `5`): true - Match a resource type: use the
match.anyblock:match: any: - type: aws:s3:bucket - Binding for descendant access: use
->name:(bar + bat)->sum: ($sum): 10
10.4 Testing
- Ship a fixture pair (
passing.json+failing.json) undertests/fixtures/<policy_target>/. - Add a test file
tests/test_<policy_target>_policies.pyusing theKyvernoJsonEngine(skip-without-kj pattern). - The regression gate (
pytest tests/) must remain green.