diff --git a/conformance/differential/README.md b/conformance/differential/README.md index 814554c..83310bb 100644 --- a/conformance/differential/README.md +++ b/conformance/differential/README.md @@ -43,6 +43,7 @@ differential/ digest/ -- F9: digestprojection.* materialized/ -- MaterializedObject::try_new / NewMaterializedObject verdict/ -- the assembled five-field verdict artifact (build_verdict / verdict.Build) + vcn_*: the OCI VCN slice, derived from ../../fixtures/vcn-v1 (see docs/INTEGRATION-CONTRACT.md) receipt/ -- the receipt verifier (verify_receipt / receipt.Verify) ``` diff --git a/conformance/differential/verdict/vcn_conformant/expected.json b/conformance/differential/verdict/vcn_conformant/expected.json new file mode 100644 index 0000000..28a828e --- /dev/null +++ b/conformance/differential/verdict/vcn_conformant/expected.json @@ -0,0 +1,17 @@ +{ + "verdict": { + "conformance_plan_digest": "sha256:91ddfbd8c4c8469c4e95c6a76ed9b8ac4fb9830f62b7f7d52a4802b1a41d5fd6", + "fields": { + "/cidr_block": "CONFORMANT", + "/dns_label": "CONFORMANT", + "/ipv6_enabled": "CONFORMANT", + "/tags/{key=env}/value": "CONFORMANT", + "/tags/{key=owner}/value": "CONFORMANT" + }, + "object": "CONFORMANT", + "observation_digest": "sha256:610943f4497316182d62c2066546873e30a026f4ac33848b43171e4d6b567dec", + "verdict_schema_version": 1 + }, + "canonical": "{\"conformance_plan_digest\":\"sha256:91ddfbd8c4c8469c4e95c6a76ed9b8ac4fb9830f62b7f7d52a4802b1a41d5fd6\",\"fields\":{\"/cidr_block\":\"CONFORMANT\",\"/dns_label\":\"CONFORMANT\",\"/ipv6_enabled\":\"CONFORMANT\",\"/tags/{key=env}/value\":\"CONFORMANT\",\"/tags/{key=owner}/value\":\"CONFORMANT\"},\"object\":\"CONFORMANT\",\"observation_digest\":\"sha256:610943f4497316182d62c2066546873e30a026f4ac33848b43171e4d6b567dec\",\"verdict_schema_version\":1}", + "why": "every planned path observed and equal: CONFORMANT" +} diff --git a/conformance/differential/verdict/vcn_conformant/input.json b/conformance/differential/verdict/vcn_conformant/input.json new file mode 100644 index 0000000..c568fdd --- /dev/null +++ b/conformance/differential/verdict/vcn_conformant/input.json @@ -0,0 +1,85 @@ +{ + "intent": { + "cidr_block": "10.0.0.0/16", + "dns_label": "prod", + "ipv6_enabled": false, + "tags": [ + { + "key": "env", + "value": "prod" + }, + { + "key": "owner", + "value": "net" + } + ] + }, + "observed": { + "cidr_block": "10.0.0.0/16", + "dns_label": "prod", + "ipv6_enabled": false, + "tags": [ + { + "key": "env", + "value": "prod" + }, + { + "key": "owner", + "value": "net" + } + ] + }, + "observation": [ + { + "path": "/cidr_block", + "coverage": "observed" + }, + { + "path": "/dns_label", + "coverage": "observed" + }, + { + "path": "/ipv6_enabled", + "coverage": "observed" + }, + { + "path": "/tags/{key=env}/value", + "coverage": "observed" + }, + { + "path": "/tags/{key=owner}/value", + "coverage": "observed" + } + ], + "plan": { + "collections": [ + { + "elements": [ + { + "compare": "exact", + "path": "value" + } + ], + "keys": [ + "key" + ], + "path": "/tags", + "topology": "map" + } + ], + "scalars": [ + { + "compare": "exact", + "path": "/cidr_block" + }, + { + "compare": "exact", + "path": "/dns_label" + }, + { + "compare": "exact", + "path": "/ipv6_enabled" + } + ] + } +} diff --git a/conformance/differential/verdict/vcn_desired_absent/expected.json b/conformance/differential/verdict/vcn_desired_absent/expected.json new file mode 100644 index 0000000..0dc7191 --- /dev/null +++ b/conformance/differential/verdict/vcn_desired_absent/expected.json @@ -0,0 +1,17 @@ +{ + "verdict": { + "conformance_plan_digest": "sha256:91ddfbd8c4c8469c4e95c6a76ed9b8ac4fb9830f62b7f7d52a4802b1a41d5fd6", + "fields": { + "/cidr_block": "CONFORMANT", + "/dns_label": "OBSERVED_ABSENT", + "/ipv6_enabled": "CONFORMANT", + "/tags/{key=env}/value": "CONFORMANT", + "/tags/{key=owner}/value": "CONFORMANT" + }, + "object": "CONFORMANT", + "observation_digest": "sha256:62287c8b3ebc9673d50cf62398c87d68f7186e93db26e4502efb00f3cca2bd24", + "verdict_schema_version": 1 + }, + "canonical": "{\"conformance_plan_digest\":\"sha256:91ddfbd8c4c8469c4e95c6a76ed9b8ac4fb9830f62b7f7d52a4802b1a41d5fd6\",\"fields\":{\"/cidr_block\":\"CONFORMANT\",\"/dns_label\":\"OBSERVED_ABSENT\",\"/ipv6_enabled\":\"CONFORMANT\",\"/tags/{key=env}/value\":\"CONFORMANT\",\"/tags/{key=owner}/value\":\"CONFORMANT\"},\"object\":\"CONFORMANT\",\"observation_digest\":\"sha256:62287c8b3ebc9673d50cf62398c87d68f7186e93db26e4502efb00f3cca2bd24\",\"verdict_schema_version\":1}", + "why": "a planned path the intent does not declare and OCI does not report is OBSERVED_ABSENT; the object conforms" +} diff --git a/conformance/differential/verdict/vcn_desired_absent/input.json b/conformance/differential/verdict/vcn_desired_absent/input.json new file mode 100644 index 0000000..cb250c7 --- /dev/null +++ b/conformance/differential/verdict/vcn_desired_absent/input.json @@ -0,0 +1,83 @@ +{ + "intent": { + "cidr_block": "10.0.0.0/16", + "ipv6_enabled": false, + "tags": [ + { + "key": "env", + "value": "prod" + }, + { + "key": "owner", + "value": "net" + } + ] + }, + "observed": { + "cidr_block": "10.0.0.0/16", + "ipv6_enabled": false, + "tags": [ + { + "key": "env", + "value": "prod" + }, + { + "key": "owner", + "value": "net" + } + ] + }, + "observation": [ + { + "path": "/cidr_block", + "coverage": "observed" + }, + { + "path": "/dns_label", + "coverage": "absent" + }, + { + "path": "/ipv6_enabled", + "coverage": "observed" + }, + { + "path": "/tags/{key=env}/value", + "coverage": "observed" + }, + { + "path": "/tags/{key=owner}/value", + "coverage": "observed" + } + ], + "plan": { + "collections": [ + { + "elements": [ + { + "compare": "exact", + "path": "value" + } + ], + "keys": [ + "key" + ], + "path": "/tags", + "topology": "map" + } + ], + "scalars": [ + { + "compare": "exact", + "path": "/cidr_block" + }, + { + "compare": "exact", + "path": "/dns_label" + }, + { + "compare": "exact", + "path": "/ipv6_enabled" + } + ] + } +} diff --git a/conformance/differential/verdict/vcn_desired_absent_observed_present/expected.json b/conformance/differential/verdict/vcn_desired_absent_observed_present/expected.json new file mode 100644 index 0000000..20f1751 --- /dev/null +++ b/conformance/differential/verdict/vcn_desired_absent_observed_present/expected.json @@ -0,0 +1,17 @@ +{ + "verdict": { + "conformance_plan_digest": "sha256:91ddfbd8c4c8469c4e95c6a76ed9b8ac4fb9830f62b7f7d52a4802b1a41d5fd6", + "fields": { + "/cidr_block": "CONFORMANT", + "/dns_label": "NOT_COMPARABLE", + "/ipv6_enabled": "CONFORMANT", + "/tags/{key=env}/value": "CONFORMANT", + "/tags/{key=owner}/value": "CONFORMANT" + }, + "object": "DRIFT", + "observation_digest": "sha256:610943f4497316182d62c2066546873e30a026f4ac33848b43171e4d6b567dec", + "verdict_schema_version": 1 + }, + "canonical": "{\"conformance_plan_digest\":\"sha256:91ddfbd8c4c8469c4e95c6a76ed9b8ac4fb9830f62b7f7d52a4802b1a41d5fd6\",\"fields\":{\"/cidr_block\":\"CONFORMANT\",\"/dns_label\":\"NOT_COMPARABLE\",\"/ipv6_enabled\":\"CONFORMANT\",\"/tags/{key=env}/value\":\"CONFORMANT\",\"/tags/{key=owner}/value\":\"CONFORMANT\"},\"object\":\"DRIFT\",\"observation_digest\":\"sha256:610943f4497316182d62c2066546873e30a026f4ac33848b43171e4d6b567dec\",\"verdict_schema_version\":1}", + "why": "OPEN QUESTION (cic-factory fixtures/vcn-v1): a planned path the intent does not declare but OCI reports is NOT_COMPARABLE, so the object is DRIFT -- the plan has to be derived from the intent, or the unmanaged field left out of the plan" +} diff --git a/conformance/differential/verdict/vcn_desired_absent_observed_present/input.json b/conformance/differential/verdict/vcn_desired_absent_observed_present/input.json new file mode 100644 index 0000000..2e7379e --- /dev/null +++ b/conformance/differential/verdict/vcn_desired_absent_observed_present/input.json @@ -0,0 +1,84 @@ +{ + "intent": { + "cidr_block": "10.0.0.0/16", + "ipv6_enabled": false, + "tags": [ + { + "key": "env", + "value": "prod" + }, + { + "key": "owner", + "value": "net" + } + ] + }, + "observed": { + "cidr_block": "10.0.0.0/16", + "dns_label": "prod", + "ipv6_enabled": false, + "tags": [ + { + "key": "env", + "value": "prod" + }, + { + "key": "owner", + "value": "net" + } + ] + }, + "observation": [ + { + "path": "/cidr_block", + "coverage": "observed" + }, + { + "path": "/dns_label", + "coverage": "observed" + }, + { + "path": "/ipv6_enabled", + "coverage": "observed" + }, + { + "path": "/tags/{key=env}/value", + "coverage": "observed" + }, + { + "path": "/tags/{key=owner}/value", + "coverage": "observed" + } + ], + "plan": { + "collections": [ + { + "elements": [ + { + "compare": "exact", + "path": "value" + } + ], + "keys": [ + "key" + ], + "path": "/tags", + "topology": "map" + } + ], + "scalars": [ + { + "compare": "exact", + "path": "/cidr_block" + }, + { + "compare": "exact", + "path": "/dns_label" + }, + { + "compare": "exact", + "path": "/ipv6_enabled" + } + ] + } +} diff --git a/conformance/differential/verdict/vcn_drift/expected.json b/conformance/differential/verdict/vcn_drift/expected.json new file mode 100644 index 0000000..93d7da4 --- /dev/null +++ b/conformance/differential/verdict/vcn_drift/expected.json @@ -0,0 +1,17 @@ +{ + "verdict": { + "conformance_plan_digest": "sha256:91ddfbd8c4c8469c4e95c6a76ed9b8ac4fb9830f62b7f7d52a4802b1a41d5fd6", + "fields": { + "/cidr_block": "CONFORMANT", + "/dns_label": "DRIFT", + "/ipv6_enabled": "CONFORMANT", + "/tags/{key=env}/value": "DRIFT", + "/tags/{key=owner}/value": "CONFORMANT" + }, + "object": "DRIFT", + "observation_digest": "sha256:7a84b901a28ce036f6b4ba4c9acc68f79d53e9671b64a55217b9ef2795097421", + "verdict_schema_version": 1 + }, + "canonical": "{\"conformance_plan_digest\":\"sha256:91ddfbd8c4c8469c4e95c6a76ed9b8ac4fb9830f62b7f7d52a4802b1a41d5fd6\",\"fields\":{\"/cidr_block\":\"CONFORMANT\",\"/dns_label\":\"DRIFT\",\"/ipv6_enabled\":\"CONFORMANT\",\"/tags/{key=env}/value\":\"DRIFT\",\"/tags/{key=owner}/value\":\"CONFORMANT\"},\"object\":\"DRIFT\",\"observation_digest\":\"sha256:7a84b901a28ce036f6b4ba4c9acc68f79d53e9671b64a55217b9ef2795097421\",\"verdict_schema_version\":1}", + "why": "a differing dns label and tag value are DRIFT; the rest stays CONFORMANT" +} diff --git a/conformance/differential/verdict/vcn_drift/input.json b/conformance/differential/verdict/vcn_drift/input.json new file mode 100644 index 0000000..15c75f4 --- /dev/null +++ b/conformance/differential/verdict/vcn_drift/input.json @@ -0,0 +1,85 @@ +{ + "intent": { + "cidr_block": "10.0.0.0/16", + "dns_label": "prod", + "ipv6_enabled": false, + "tags": [ + { + "key": "env", + "value": "prod" + }, + { + "key": "owner", + "value": "net" + } + ] + }, + "observed": { + "cidr_block": "10.0.0.0/16", + "dns_label": "staging", + "ipv6_enabled": false, + "tags": [ + { + "key": "env", + "value": "dev" + }, + { + "key": "owner", + "value": "net" + } + ] + }, + "observation": [ + { + "path": "/cidr_block", + "coverage": "observed" + }, + { + "path": "/dns_label", + "coverage": "observed" + }, + { + "path": "/ipv6_enabled", + "coverage": "observed" + }, + { + "path": "/tags/{key=env}/value", + "coverage": "observed" + }, + { + "path": "/tags/{key=owner}/value", + "coverage": "observed" + } + ], + "plan": { + "collections": [ + { + "elements": [ + { + "compare": "exact", + "path": "value" + } + ], + "keys": [ + "key" + ], + "path": "/tags", + "topology": "map" + } + ], + "scalars": [ + { + "compare": "exact", + "path": "/cidr_block" + }, + { + "compare": "exact", + "path": "/dns_label" + }, + { + "compare": "exact", + "path": "/ipv6_enabled" + } + ] + } +} diff --git a/conformance/differential/verdict/vcn_field_absent/expected.json b/conformance/differential/verdict/vcn_field_absent/expected.json new file mode 100644 index 0000000..f3df65c --- /dev/null +++ b/conformance/differential/verdict/vcn_field_absent/expected.json @@ -0,0 +1,17 @@ +{ + "verdict": { + "conformance_plan_digest": "sha256:91ddfbd8c4c8469c4e95c6a76ed9b8ac4fb9830f62b7f7d52a4802b1a41d5fd6", + "fields": { + "/cidr_block": "CONFORMANT", + "/dns_label": "DRIFT", + "/ipv6_enabled": "CONFORMANT", + "/tags/{key=env}/value": "CONFORMANT", + "/tags/{key=owner}/value": "CONFORMANT" + }, + "object": "DRIFT", + "observation_digest": "sha256:62287c8b3ebc9673d50cf62398c87d68f7186e93db26e4502efb00f3cca2bd24", + "verdict_schema_version": 1 + }, + "canonical": "{\"conformance_plan_digest\":\"sha256:91ddfbd8c4c8469c4e95c6a76ed9b8ac4fb9830f62b7f7d52a4802b1a41d5fd6\",\"fields\":{\"/cidr_block\":\"CONFORMANT\",\"/dns_label\":\"DRIFT\",\"/ipv6_enabled\":\"CONFORMANT\",\"/tags/{key=env}/value\":\"CONFORMANT\",\"/tags/{key=owner}/value\":\"CONFORMANT\"},\"object\":\"DRIFT\",\"observation_digest\":\"sha256:62287c8b3ebc9673d50cf62398c87d68f7186e93db26e4502efb00f3cca2bd24\",\"verdict_schema_version\":1}", + "why": "a wanted dns label OCI does not report (authoritative absence) is DRIFT" +} diff --git a/conformance/differential/verdict/vcn_field_absent/input.json b/conformance/differential/verdict/vcn_field_absent/input.json new file mode 100644 index 0000000..6dfc2ef --- /dev/null +++ b/conformance/differential/verdict/vcn_field_absent/input.json @@ -0,0 +1,84 @@ +{ + "intent": { + "cidr_block": "10.0.0.0/16", + "dns_label": "prod", + "ipv6_enabled": false, + "tags": [ + { + "key": "env", + "value": "prod" + }, + { + "key": "owner", + "value": "net" + } + ] + }, + "observed": { + "cidr_block": "10.0.0.0/16", + "ipv6_enabled": false, + "tags": [ + { + "key": "env", + "value": "prod" + }, + { + "key": "owner", + "value": "net" + } + ] + }, + "observation": [ + { + "path": "/cidr_block", + "coverage": "observed" + }, + { + "path": "/dns_label", + "coverage": "absent" + }, + { + "path": "/ipv6_enabled", + "coverage": "observed" + }, + { + "path": "/tags/{key=env}/value", + "coverage": "observed" + }, + { + "path": "/tags/{key=owner}/value", + "coverage": "observed" + } + ], + "plan": { + "collections": [ + { + "elements": [ + { + "compare": "exact", + "path": "value" + } + ], + "keys": [ + "key" + ], + "path": "/tags", + "topology": "map" + } + ], + "scalars": [ + { + "compare": "exact", + "path": "/cidr_block" + }, + { + "compare": "exact", + "path": "/dns_label" + }, + { + "compare": "exact", + "path": "/ipv6_enabled" + } + ] + } +} diff --git a/conformance/differential/verdict/vcn_malformed_cidr/expected.json b/conformance/differential/verdict/vcn_malformed_cidr/expected.json new file mode 100644 index 0000000..cf1c9b6 --- /dev/null +++ b/conformance/differential/verdict/vcn_malformed_cidr/expected.json @@ -0,0 +1,17 @@ +{ + "verdict": { + "conformance_plan_digest": "sha256:91ddfbd8c4c8469c4e95c6a76ed9b8ac4fb9830f62b7f7d52a4802b1a41d5fd6", + "fields": { + "/cidr_block": "UNOBSERVED", + "/dns_label": "CONFORMANT", + "/ipv6_enabled": "CONFORMANT", + "/tags/{key=env}/value": "CONFORMANT", + "/tags/{key=owner}/value": "CONFORMANT" + }, + "object": "INCOMPLETE", + "observation_digest": "sha256:462145a58a7f6cf0eda2c1389995a2930a9abe65734de3edb0bf0672fdaf1f4b", + "verdict_schema_version": 1 + }, + "canonical": "{\"conformance_plan_digest\":\"sha256:91ddfbd8c4c8469c4e95c6a76ed9b8ac4fb9830f62b7f7d52a4802b1a41d5fd6\",\"fields\":{\"/cidr_block\":\"UNOBSERVED\",\"/dns_label\":\"CONFORMANT\",\"/ipv6_enabled\":\"CONFORMANT\",\"/tags/{key=env}/value\":\"CONFORMANT\",\"/tags/{key=owner}/value\":\"CONFORMANT\"},\"object\":\"INCOMPLETE\",\"observation_digest\":\"sha256:462145a58a7f6cf0eda2c1389995a2930a9abe65734de3edb0bf0672fdaf1f4b\",\"verdict_schema_version\":1}", + "why": "cidrBlocks not a list: coverage unknown flows through as UNOBSERVED, INCOMPLETE" +} diff --git a/conformance/differential/verdict/vcn_malformed_cidr/input.json b/conformance/differential/verdict/vcn_malformed_cidr/input.json new file mode 100644 index 0000000..c182b67 --- /dev/null +++ b/conformance/differential/verdict/vcn_malformed_cidr/input.json @@ -0,0 +1,84 @@ +{ + "intent": { + "cidr_block": "10.0.0.0/16", + "dns_label": "prod", + "ipv6_enabled": false, + "tags": [ + { + "key": "env", + "value": "prod" + }, + { + "key": "owner", + "value": "net" + } + ] + }, + "observed": { + "dns_label": "prod", + "ipv6_enabled": false, + "tags": [ + { + "key": "env", + "value": "prod" + }, + { + "key": "owner", + "value": "net" + } + ] + }, + "observation": [ + { + "path": "/cidr_block", + "coverage": "unknown" + }, + { + "path": "/dns_label", + "coverage": "observed" + }, + { + "path": "/ipv6_enabled", + "coverage": "observed" + }, + { + "path": "/tags/{key=env}/value", + "coverage": "observed" + }, + { + "path": "/tags/{key=owner}/value", + "coverage": "observed" + } + ], + "plan": { + "collections": [ + { + "elements": [ + { + "compare": "exact", + "path": "value" + } + ], + "keys": [ + "key" + ], + "path": "/tags", + "topology": "map" + } + ], + "scalars": [ + { + "compare": "exact", + "path": "/cidr_block" + }, + { + "compare": "exact", + "path": "/dns_label" + }, + { + "compare": "exact", + "path": "/ipv6_enabled" + } + ] + } +} diff --git a/conformance/differential/verdict/vcn_not_found/expected.json b/conformance/differential/verdict/vcn_not_found/expected.json new file mode 100644 index 0000000..3a9eafc --- /dev/null +++ b/conformance/differential/verdict/vcn_not_found/expected.json @@ -0,0 +1,17 @@ +{ + "verdict": { + "conformance_plan_digest": "sha256:91ddfbd8c4c8469c4e95c6a76ed9b8ac4fb9830f62b7f7d52a4802b1a41d5fd6", + "fields": { + "/cidr_block": "UNOBSERVED", + "/dns_label": "UNOBSERVED", + "/ipv6_enabled": "UNOBSERVED", + "/tags/{key=env}/value": "UNOBSERVED", + "/tags/{key=owner}/value": "UNOBSERVED" + }, + "object": "INCOMPLETE", + "observation_digest": "sha256:7283447d63df630fae2347150c584468a3480e66e477e51f0ffb4b2970d61874", + "verdict_schema_version": 1 + }, + "canonical": "{\"conformance_plan_digest\":\"sha256:91ddfbd8c4c8469c4e95c6a76ed9b8ac4fb9830f62b7f7d52a4802b1a41d5fd6\",\"fields\":{\"/cidr_block\":\"UNOBSERVED\",\"/dns_label\":\"UNOBSERVED\",\"/ipv6_enabled\":\"UNOBSERVED\",\"/tags/{key=env}/value\":\"UNOBSERVED\",\"/tags/{key=owner}/value\":\"UNOBSERVED\"},\"object\":\"INCOMPLETE\",\"observation_digest\":\"sha256:7283447d63df630fae2347150c584468a3480e66e477e51f0ffb4b2970d61874\",\"verdict_schema_version\":1}", + "why": "404 NotAuthorizedOrNotFound is not authoritative absence: every path unknown, UNOBSERVED, INCOMPLETE" +} diff --git a/conformance/differential/verdict/vcn_not_found/input.json b/conformance/differential/verdict/vcn_not_found/input.json new file mode 100644 index 0000000..37a6d8a --- /dev/null +++ b/conformance/differential/verdict/vcn_not_found/input.json @@ -0,0 +1,71 @@ +{ + "intent": { + "cidr_block": "10.0.0.0/16", + "dns_label": "prod", + "ipv6_enabled": false, + "tags": [ + { + "key": "env", + "value": "prod" + }, + { + "key": "owner", + "value": "net" + } + ] + }, + "observed": {}, + "observation": [ + { + "path": "/cidr_block", + "coverage": "unknown" + }, + { + "path": "/dns_label", + "coverage": "unknown" + }, + { + "path": "/ipv6_enabled", + "coverage": "unknown" + }, + { + "path": "/tags/{key=env}/value", + "coverage": "unknown" + }, + { + "path": "/tags/{key=owner}/value", + "coverage": "unknown" + } + ], + "plan": { + "collections": [ + { + "elements": [ + { + "compare": "exact", + "path": "value" + } + ], + "keys": [ + "key" + ], + "path": "/tags", + "topology": "map" + } + ], + "scalars": [ + { + "compare": "exact", + "path": "/cidr_block" + }, + { + "compare": "exact", + "path": "/dns_label" + }, + { + "compare": "exact", + "path": "/ipv6_enabled" + } + ] + } +} diff --git a/conformance/differential/verdict/vcn_partial_read/expected.json b/conformance/differential/verdict/vcn_partial_read/expected.json new file mode 100644 index 0000000..ea62c69 --- /dev/null +++ b/conformance/differential/verdict/vcn_partial_read/expected.json @@ -0,0 +1,17 @@ +{ + "verdict": { + "conformance_plan_digest": "sha256:91ddfbd8c4c8469c4e95c6a76ed9b8ac4fb9830f62b7f7d52a4802b1a41d5fd6", + "fields": { + "/cidr_block": "CONFORMANT", + "/dns_label": "CONFORMANT", + "/ipv6_enabled": "UNOBSERVED", + "/tags/{key=env}/value": "CONFORMANT", + "/tags/{key=owner}/value": "CONFORMANT" + }, + "object": "INCOMPLETE", + "observation_digest": "sha256:4b419234ae459f6caa3ad08900d0f9299f92b7f855e79d05aeeabe4c5c64efcd", + "verdict_schema_version": 1 + }, + "canonical": "{\"conformance_plan_digest\":\"sha256:91ddfbd8c4c8469c4e95c6a76ed9b8ac4fb9830f62b7f7d52a4802b1a41d5fd6\",\"fields\":{\"/cidr_block\":\"CONFORMANT\",\"/dns_label\":\"CONFORMANT\",\"/ipv6_enabled\":\"UNOBSERVED\",\"/tags/{key=env}/value\":\"CONFORMANT\",\"/tags/{key=owner}/value\":\"CONFORMANT\"},\"object\":\"INCOMPLETE\",\"observation_digest\":\"sha256:4b419234ae459f6caa3ad08900d0f9299f92b7f855e79d05aeeabe4c5c64efcd\",\"verdict_schema_version\":1}", + "why": "ipv6CidrBlocks missing: ipv6_enabled is UNOBSERVED (never false), the object is INCOMPLETE" +} diff --git a/conformance/differential/verdict/vcn_partial_read/input.json b/conformance/differential/verdict/vcn_partial_read/input.json new file mode 100644 index 0000000..55a69c3 --- /dev/null +++ b/conformance/differential/verdict/vcn_partial_read/input.json @@ -0,0 +1,84 @@ +{ + "intent": { + "cidr_block": "10.0.0.0/16", + "dns_label": "prod", + "ipv6_enabled": false, + "tags": [ + { + "key": "env", + "value": "prod" + }, + { + "key": "owner", + "value": "net" + } + ] + }, + "observed": { + "cidr_block": "10.0.0.0/16", + "dns_label": "prod", + "tags": [ + { + "key": "env", + "value": "prod" + }, + { + "key": "owner", + "value": "net" + } + ] + }, + "observation": [ + { + "path": "/cidr_block", + "coverage": "observed" + }, + { + "path": "/dns_label", + "coverage": "observed" + }, + { + "path": "/ipv6_enabled", + "coverage": "unobserved" + }, + { + "path": "/tags/{key=env}/value", + "coverage": "observed" + }, + { + "path": "/tags/{key=owner}/value", + "coverage": "observed" + } + ], + "plan": { + "collections": [ + { + "elements": [ + { + "compare": "exact", + "path": "value" + } + ], + "keys": [ + "key" + ], + "path": "/tags", + "topology": "map" + } + ], + "scalars": [ + { + "compare": "exact", + "path": "/cidr_block" + }, + { + "compare": "exact", + "path": "/dns_label" + }, + { + "compare": "exact", + "path": "/ipv6_enabled" + } + ] + } +} diff --git a/conformance/differential/verdict/vcn_transport_error/expected.json b/conformance/differential/verdict/vcn_transport_error/expected.json new file mode 100644 index 0000000..520e71d --- /dev/null +++ b/conformance/differential/verdict/vcn_transport_error/expected.json @@ -0,0 +1,17 @@ +{ + "verdict": { + "conformance_plan_digest": "sha256:91ddfbd8c4c8469c4e95c6a76ed9b8ac4fb9830f62b7f7d52a4802b1a41d5fd6", + "fields": { + "/cidr_block": "UNOBSERVED", + "/dns_label": "UNOBSERVED", + "/ipv6_enabled": "UNOBSERVED", + "/tags/{key=env}/value": "UNOBSERVED", + "/tags/{key=owner}/value": "UNOBSERVED" + }, + "object": "INCOMPLETE", + "observation_digest": "sha256:a7ca7c103862ba9fec9adde41ae2f75b316abcf67abd6d5e10ae4ba22521b298", + "verdict_schema_version": 1 + }, + "canonical": "{\"conformance_plan_digest\":\"sha256:91ddfbd8c4c8469c4e95c6a76ed9b8ac4fb9830f62b7f7d52a4802b1a41d5fd6\",\"fields\":{\"/cidr_block\":\"UNOBSERVED\",\"/dns_label\":\"UNOBSERVED\",\"/ipv6_enabled\":\"UNOBSERVED\",\"/tags/{key=env}/value\":\"UNOBSERVED\",\"/tags/{key=owner}/value\":\"UNOBSERVED\"},\"object\":\"INCOMPLETE\",\"observation_digest\":\"sha256:a7ca7c103862ba9fec9adde41ae2f75b316abcf67abd6d5e10ae4ba22521b298\",\"verdict_schema_version\":1}", + "why": "no answer: every path unobserved, UNOBSERVED, INCOMPLETE" +} diff --git a/conformance/differential/verdict/vcn_transport_error/input.json b/conformance/differential/verdict/vcn_transport_error/input.json new file mode 100644 index 0000000..3c7f10c --- /dev/null +++ b/conformance/differential/verdict/vcn_transport_error/input.json @@ -0,0 +1,71 @@ +{ + "intent": { + "cidr_block": "10.0.0.0/16", + "dns_label": "prod", + "ipv6_enabled": false, + "tags": [ + { + "key": "env", + "value": "prod" + }, + { + "key": "owner", + "value": "net" + } + ] + }, + "observed": {}, + "observation": [ + { + "path": "/cidr_block", + "coverage": "unobserved" + }, + { + "path": "/dns_label", + "coverage": "unobserved" + }, + { + "path": "/ipv6_enabled", + "coverage": "unobserved" + }, + { + "path": "/tags/{key=env}/value", + "coverage": "unobserved" + }, + { + "path": "/tags/{key=owner}/value", + "coverage": "unobserved" + } + ], + "plan": { + "collections": [ + { + "elements": [ + { + "compare": "exact", + "path": "value" + } + ], + "keys": [ + "key" + ], + "path": "/tags", + "topology": "map" + } + ], + "scalars": [ + { + "compare": "exact", + "path": "/cidr_block" + }, + { + "compare": "exact", + "path": "/dns_label" + }, + { + "compare": "exact", + "path": "/ipv6_enabled" + } + ] + } +} diff --git a/docs/INTEGRATION-CONTRACT.md b/docs/INTEGRATION-CONTRACT.md new file mode 100644 index 0000000..e23bbfd --- /dev/null +++ b/docs/INTEGRATION-CONTRACT.md @@ -0,0 +1,115 @@ +# Integration contract: what the environment supplies, what the engine returns + +Status vocabulary (per statement): **implemented** = in code on `main`, covered by tests; **scaffold** = in code, deliberately not +wired or exercised end to end; **concept** = written down, no runtime counterpart. A statement without a status is a description of +a document, not a claim. + +This document is the integration contract of the existing `materialized` + `conformance` + `verdict` functionality, proven on one +slice: an OCI VCN (`fixtures/vcn-v1`, from cic-factory PR #37, nine cases). **The engine builds nothing new for it.** Everything +before the engine (reading the OCI answer, normalizing it, mapping OCI names to registry names, deciding what the coverage of each +path is, resolving references, applying defaults, validating) is the environment's job (`BOUNDARY.md`, `MATERIALIZATION-SPEC.md`, +"Scope correction"). *implemented* as a boundary. + +## What the environment must supply + +For `evaluate_verdict(intent, observed, observation, plan)` (Rust) / `verdict.EvaluateAndBuild` (Go): + +1. **`intent: MaterializedObject`** and **`observed: MaterializedObject`**, each built with `MaterializedObject::try_new(fields, + &expected_keys)` / `NewMaterializedObject`. The key set of the fields must be **exactly** `expected_keys`; any missing or extra + key is `E_INCOMPLETE_OBJECT`. *implemented.* The environment decides `expected_keys` (the schema's keys); the engine only + checks the set. *implemented.* +2. **A per-field `capability`** (`implemented` | `not_implemented` | `deprecated`) and **evidence**: on the intent side + `Authored(Some(v))`, `Authored(None)` (declared absent), `SchemaDefault(v)`, `Derived(v)`; on the observed side `Observation(c)` / + `DerivedObservation(c)` with `c` one of `Observed(v)`, `Absent`, `Unobserved`, `Unknown`. *implemented.* +3. **An `Observation`**: the coverage (`observed` | `absent` | `unobserved` | `unknown`) of **every path the plan names**. A path + not listed is `unobserved`. *implemented.* Deciding which of the four a given OCI answer is (a 404, a timeout, a missing source + of a derived value, a malformed field) is the environment's; the rules are in `fixtures/vcn-v1/README.md`. *concept* on the + engine side (it takes the decision as input), *implemented* only as fixture files. +4. **A `ConformancePlan`**: scalars `{path, compare: exact|numeric}` and collections `{path, topology: map|set|atomic, keys, + elements}`. *implemented.* Which paths are planned is the environment's decision; see "The open question". + +## What the engine returns + +`ConformanceVerdict { verdict_schema_version: 1, object, fields, conformance_plan_digest, observation_digest }` and its canonical +bytes (`verdict_canonical_bytes` / `Verdict.CanonicalBytes`). *implemented*, byte-identical in Rust and Go on every vector of +`conformance/differential/verdict/`. + +* `fields`: exactly one entry per path the plan classified, with `CONFORMANT` | `DRIFT` | `OBSERVED_ABSENT` | `UNOBSERVED` | + `NOT_COMPARABLE`. +* `object`: `DRIFT` if any field is `DRIFT` or `NOT_COMPARABLE`, else `INCOMPLETE` if any field is `UNOBSERVED`, else `CONFORMANT`. +* `evaluate_verdict` binds the verdict to the plan it ran; `build_verdict` is the low-level builder and cannot check that (use the + former). *implemented.* + +## How absent, unobserved and unknown are treated + +| Coverage | Intent declares a value | Intent declares nothing | +|---|---|---| +| `observed` | `CONFORMANT` if equal, `DRIFT` if not, `NOT_COMPARABLE` if the kinds differ | `NOT_COMPARABLE` if a value was observed (see below) | +| `absent` (authoritative) | `DRIFT` | `OBSERVED_ABSENT` | +| `unobserved` | `UNOBSERVED` | `UNOBSERVED` | +| `unknown` | `UNOBSERVED` (the same verdict as `unobserved`; the digests differ) | `UNOBSERVED` | + +`unobserved` and `unknown` can never produce `CONFORMANT`: the vectors `vcn_not_found` (a 404 `NotAuthorizedOrNotFound`, every path +`unknown`), `vcn_transport_error` (every path `unobserved`), `vcn_partial_read` (a derived value without its source, `unobserved` +and not `false`) and `vcn_malformed_cidr` (a value that cannot be trusted, `unknown`) pin that. *implemented.* The engine does not +distinguish `unknown` from `unobserved` in the verdict (`classify_field`); only the observation digest does. A dedicated verdict +for `unknown` is *concept* (`conformance.rs` says so). + +## The VCN slice, as pinned + +Nine vectors, per-path verdicts and object verdict equal to the fixture's `expected/verdict.fields.json` (checked by +`fixtures/make_vcn_vectors.py --check`); the digests and the canonical string are what the engine computes. The plan digest is the +same for all nine (`sha256:91ddfbd8c4c8469c4e95c6a76ed9b8ac4fb9830f62b7f7d52a4802b1a41d5fd6`). + +| Vector | Object | `observation_digest` | +|---|---|---| +| `vcn_conformant` | CONFORMANT | `sha256:610943f4497316182d62c2066546873e30a026f4ac33848b43171e4d6b567dec` | +| `vcn_drift` | DRIFT | `sha256:7a84b901a28ce036f6b4ba4c9acc68f79d53e9671b64a55217b9ef2795097421` | +| `vcn_field_absent` | DRIFT | `sha256:62287c8b3ebc9673d50cf62398c87d68f7186e93db26e4502efb00f3cca2bd24` | +| `vcn_desired_absent` | CONFORMANT | `sha256:62287c8b3ebc9673d50cf62398c87d68f7186e93db26e4502efb00f3cca2bd24` | +| `vcn_desired_absent_observed_present` | DRIFT | `sha256:610943f4497316182d62c2066546873e30a026f4ac33848b43171e4d6b567dec` | +| `vcn_partial_read` | INCOMPLETE | `sha256:4b419234ae459f6caa3ad08900d0f9299f92b7f855e79d05aeeabe4c5c64efcd` | +| `vcn_malformed_cidr` | INCOMPLETE | `sha256:462145a58a7f6cf0eda2c1389995a2930a9abe65734de3edb0bf0672fdaf1f4b` | +| `vcn_not_found` | INCOMPLETE | `sha256:7283447d63df630fae2347150c584468a3480e66e477e51f0ffb4b2970d61874` | +| `vcn_transport_error` | INCOMPLETE | `sha256:a7ca7c103862ba9fec9adde41ae2f75b316abcf67abd6d5e10ae4ba22521b298` | + +## The open question: a planned path the intent does not declare + +`vcn_desired_absent_observed_present`: `/dns_label` is in the plan, the intent declares nothing for it, OCI reports a value. The +engine's answer is `NOT_COMPARABLE` (the intent side resolves to null, null and a string are of different kinds), so the object is +`DRIFT`. The OCI module reads "absent in desired" as "unmanaged, do not touch". Both are consistent only if **the plan is derived +from the intent** (a path the intent does not declare is not planned) or the engine learns to tell an unmanaged field from a +declared one. This contract states the engine's current behaviour; changing either side is a decision for the owner. The existing +vector `oci_desired_absent_is_conformant` shows the *absent* half only (`OBSERVED_ABSENT`). *implemented* (behaviour), +*concept* (the resolution). + +## Collections + +`/tags/{key=env}/value` is the path of the `value` element of the element whose `key` is `env`, for a `map` collection keyed by +`key` (`collection::element_key`). *implemented*, as far as the vectors reach (two keys, one differing value). + +* The element identities are the **sorted union of the identities on the intent side and on the observed side** + (`plan.rs` `element_keys`; read from the source, **not** covered by a VCN vector). A tag that OCI reports and the intent does not + name therefore becomes a path of its own, and unless the environment lists its coverage it is `unobserved`, which makes the + object `INCOMPLETE`. The fixture lists coverage only for the intent's tag keys, so a real VCN with extra tags would need the + environment to list those paths too (as `observed`; the intent side then resolves to null and the pair is `NOT_COMPARABLE`, the + same question as below). Open for the owner. *concept.* +* A collection that is wholly unobserved is expressed through the coverage of each element path (`vcn_not_found`); there is no + collection-level coverage. *implemented.* + +## The receipt verifier + +`verify_receipt` / `receipt.Verify` (`RECEIPT-VERIFIER.md`) is on `main` (PR #61, merged). It checks the form and the internal +consistency of a materialization receipt against an object and the authored input. **No receipt producer exists in this crate or, +as far as this repository shows, anywhere in CIC**, so the VCN slice has no receipt to verify. *implemented* (verifier), +*concept* (any receipt for this slice). The verdict and the receipt are independent artifacts (`VERDICT-SCHEMA.md`). + +## What this slice does not exercise + +* The vectors build objects with test-side helpers: every top-level field gets capability `implemented` and `Authored` evidence on + the intent side. The registry's real objects (Access-wrapped values, `not_implemented` fields, schema defaults, derived values) + are not built or compared here. *scaffold.* +* `secondary_cidr_blocks`, `display_name`, `compartment_id`, `lifecycle_state`, `ipv6_cidr_blocks` are not planned: the verdict says + nothing about them (`fixtures/vcn-v1/not-planned.json`). +* Parse / Normalize / Resolve / Validate, name or OCID resolution, OCI-to-registry mapping: not in this crate, never will be. +* Evidence (ProofTrace) and anything that signs the verdict: the verdict is canonical bytes; signing and storing are not here. diff --git a/fixtures/make_vcn_vectors.py b/fixtures/make_vcn_vectors.py new file mode 100644 index 0000000..9f226bb --- /dev/null +++ b/fixtures/make_vcn_vectors.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python3 +"""Derives conformance/differential/verdict/vcn_/input.json from the vendored fixture fixtures/vcn-v1. + + make_vcn_vectors.py write the input.json files + make_vcn_vectors.py --check fail if they differ, if the manifest does not match, or if a pinned expected.json + disagrees with the fixture's per-path verdicts + +Only stage 5 is exercised: the intent and the observed object are the fixture's registry-named files, the observation list is its +coverage, the plan is its plan.json. The digests and the canonical string in expected.json are what the engine computes; this +script never writes them, it only checks that the per-path verdicts and the object verdict in them are the fixture's. +""" +import hashlib +import json +import os +import sys + +HERE = os.path.dirname(os.path.abspath(__file__)) +FX = os.path.join(HERE, "vcn-v1") +OUT = os.path.join(HERE, "..", "conformance", "differential", "verdict") + + +def load(*p): + with open(os.path.join(FX, *p), encoding="utf-8") as fh: + return json.load(fh) + + +def dump(doc): + return json.dumps(doc, indent=2, ensure_ascii=False) + "\n" + + +def vector_input(case): + intent = load("cases", case, "intent.registry.json") + obs = load("cases", case, "expected", "observed.registry.json") + return { + "intent": intent, + "observed": obs["config"], + "observation": [{"path": p, "coverage": c} for p, c in sorted(obs["coverage"].items())], + "plan": load("plan.json"), + } + + +def main(argv): + bad = [] + with open(os.path.join(FX, "MANIFEST.sha256"), encoding="utf-8") as fh: + for line in fh.read().splitlines(): + digest, name = line.split(" ", 1) + with open(os.path.join(FX, name), "rb") as f: + if hashlib.sha256(f.read()).hexdigest() != digest: + bad.append("manifest: " + name) + for case in sorted(load("cases.json")): + d = os.path.join(OUT, "vcn_" + case.replace("-", "_")) + want = dump(vector_input(case)) + path = os.path.join(d, "input.json") + if "--check" in argv: + if not os.path.exists(path) or open(path, encoding="utf-8").read() != want: + bad.append(path) + ex = os.path.join(d, "expected.json") + if os.path.exists(ex): + with open(ex, encoding="utf-8") as fh: + got = json.load(fh)["verdict"] + fx = load("cases", case, "expected", "verdict.fields.json") + if got["fields"] != fx["fields"] or got["object"] != fx["object"]: + bad.append("verdict differs from the fixture: " + case) + else: + bad.append("missing " + ex) + else: + os.makedirs(d, exist_ok=True) + with open(path, "w", encoding="utf-8") as fh: + fh.write(want) + if bad: + print("\n".join(bad)) + return 1 + print("ok" if "--check" in argv else "written") + return 0 + + +if __name__ == "__main__": + sys.exit(main(sys.argv[1:])) diff --git a/fixtures/vcn-v1/MANIFEST.sha256 b/fixtures/vcn-v1/MANIFEST.sha256 new file mode 100644 index 0000000..5458d48 --- /dev/null +++ b/fixtures/vcn-v1/MANIFEST.sha256 @@ -0,0 +1,58 @@ +e83181f3928a7cde7e8e27320b43237be86b9f61e1e42941658ab39d9c37e07d cases.json +8400c953622cd5ba5414898bbc85e51494eaa3dae6ac9051ab4ac67c4a6e3384 cases/conformant/expected/evidence.claims.json +2f7181e648cb5b41f87ab99f9848cd1cc0f50e8552f52f6e2a0f44b0b5f62c70 cases/conformant/expected/observed.oci.json +30537fdb726d740aa1c75c81cc60970334aea328f68541e11b2c843bc61f2203 cases/conformant/expected/observed.registry.json +7efd45c44d808639d5fe607cf4182e930be6b8d82709e716114e63512f69b55f cases/conformant/expected/verdict.fields.json +d5daed86d55d8b22d307e8b4df9ab5af7cfa9c2647750d28c5344daeb40f92f8 cases/conformant/intent.registry.json +85d63071061eab5921536bb9bd27e2117212ab18b931c36573903ce57444ec03 cases/conformant/oci/response.json +256506d6120cd3ad29d63578dc2c4f8f1dffb8f087270d0a74dd5683deb107cc cases/desired-absent-observed-present/expected/evidence.claims.json +2f7181e648cb5b41f87ab99f9848cd1cc0f50e8552f52f6e2a0f44b0b5f62c70 cases/desired-absent-observed-present/expected/observed.oci.json +30537fdb726d740aa1c75c81cc60970334aea328f68541e11b2c843bc61f2203 cases/desired-absent-observed-present/expected/observed.registry.json +4165cf6ae04fe133b57dfe070307029c25999af63b9afd718453eb5d6729f07b cases/desired-absent-observed-present/expected/verdict.fields.json +84e51775f32f77fad38796b52a8b247a7b6ce773247a9cba45e39d2a652a1e9f cases/desired-absent-observed-present/intent.registry.json +85d63071061eab5921536bb9bd27e2117212ab18b931c36573903ce57444ec03 cases/desired-absent-observed-present/oci/response.json +8400c953622cd5ba5414898bbc85e51494eaa3dae6ac9051ab4ac67c4a6e3384 cases/desired-absent/expected/evidence.claims.json +1f3a72bcaf3825d01a6fa6da0b0a781cb4028779181a498311e9c2de073e36fb cases/desired-absent/expected/observed.oci.json +8c607fe4c6cb87f10a73accd46657733fffaf3c0d98c1197a48ffacb7d2c9803 cases/desired-absent/expected/observed.registry.json +fb5779e0edb8a71595d9d8c81946bfb324b86066c6b94b73b036e3e110591b5c cases/desired-absent/expected/verdict.fields.json +84e51775f32f77fad38796b52a8b247a7b6ce773247a9cba45e39d2a652a1e9f cases/desired-absent/intent.registry.json +35bd42c0d55ea02ea9701070141dbb7ebc01d6b020e1cea9103bc5f0ec22c243 cases/desired-absent/oci/response.json +bef22a55d1559676a57b4a05b48efc7cd226d3d733a1c932e183666679046b53 cases/drift/expected/evidence.claims.json +526ab17415fc9a42967d35159db9438c3e696bf5a246e57a9f68c2dd0e596837 cases/drift/expected/observed.oci.json +f2b6dde95429bd1562c057c5ca974958608e8c4524fa33619d9095c195fd6df7 cases/drift/expected/observed.registry.json +fa13c93d96137cd60abaf523186ba2a0fa48ec28df52c55a49767cd58f88ef29 cases/drift/expected/verdict.fields.json +d5daed86d55d8b22d307e8b4df9ab5af7cfa9c2647750d28c5344daeb40f92f8 cases/drift/intent.registry.json +e9c15472e03aace968b9d2660036d9338dd513b4b11df38cebc033ebe0999dac cases/drift/oci/response.json +256506d6120cd3ad29d63578dc2c4f8f1dffb8f087270d0a74dd5683deb107cc cases/field-absent/expected/evidence.claims.json +1f3a72bcaf3825d01a6fa6da0b0a781cb4028779181a498311e9c2de073e36fb cases/field-absent/expected/observed.oci.json +8c607fe4c6cb87f10a73accd46657733fffaf3c0d98c1197a48ffacb7d2c9803 cases/field-absent/expected/observed.registry.json +05fca2112ed6319a91d0f373f9dcab23df35687daea510498110e9f1ce80e498 cases/field-absent/expected/verdict.fields.json +d5daed86d55d8b22d307e8b4df9ab5af7cfa9c2647750d28c5344daeb40f92f8 cases/field-absent/intent.registry.json +35bd42c0d55ea02ea9701070141dbb7ebc01d6b020e1cea9103bc5f0ec22c243 cases/field-absent/oci/response.json +cb147348de6aadbcef95a82efb66d8e5252d1c82f34aba14c716747dea57d10f cases/malformed-cidr/expected/evidence.claims.json +79f7c4ad204e39c79b7b55a0ee925a603250bb49610366a98e3210e1b925abd3 cases/malformed-cidr/expected/observed.oci.json +635384b1db18a7654d1e30b660ba825645d88a89e7bc0e808500a213b584f278 cases/malformed-cidr/expected/observed.registry.json +dcd74edeae0b4e8ffc610d2dfc01c302221eda1b1aac6df12f8334b2c96fbf20 cases/malformed-cidr/expected/verdict.fields.json +d5daed86d55d8b22d307e8b4df9ab5af7cfa9c2647750d28c5344daeb40f92f8 cases/malformed-cidr/intent.registry.json +97f3f81e90d839c74efbe7ad2e1732b08ac0612a38499d6ef5dedcc637c64eb6 cases/malformed-cidr/oci/response.json +79273b206c6143ede2e8a28f7c3d667d93c5978c764b3db4f543c7f4a477253b cases/not-found/expected/evidence.claims.json +a1cba33d61cd932030481da78da553eb0f31b536a7c3879d5bdd9992c69d189a cases/not-found/expected/observed.oci.json +7e18dd94212c07eb66f6db357067b71e870dc64d43336f9c1db3c08b30adf0f4 cases/not-found/expected/observed.registry.json +6a37f4257d4d1d1714e43b43bef32af8f00d71b219500f8e2820f46b046fc0b3 cases/not-found/expected/verdict.fields.json +d5daed86d55d8b22d307e8b4df9ab5af7cfa9c2647750d28c5344daeb40f92f8 cases/not-found/intent.registry.json +de838914aa32b597e9575a29096df381802c369f867f2655e7ca114106ddab92 cases/not-found/oci/error.json +311e9395faf9ba5ebf11644ba26be1e052614d0b4db06dd12bddb6a78a354769 cases/partial-read/expected/evidence.claims.json +3d7f7042b704b2f6b399e815b64d8430cf23983266d1cd5d0b987e17c64bd6fb cases/partial-read/expected/observed.oci.json +13b0b2a232d66df4fe26ec751b49b600fc63dbcde26b2bf17c675bfed78d30f8 cases/partial-read/expected/observed.registry.json +3c9bd79e75e97207cc669159b980e331ae8f75b36c0e9899b4a66f3deac0846d cases/partial-read/expected/verdict.fields.json +d5daed86d55d8b22d307e8b4df9ab5af7cfa9c2647750d28c5344daeb40f92f8 cases/partial-read/intent.registry.json +87b9bfd20b27104bbc113bc3e64f91cba9eb7f678a931a55123d0209131d3c67 cases/partial-read/oci/response.json +79273b206c6143ede2e8a28f7c3d667d93c5978c764b3db4f543c7f4a477253b cases/transport-error/expected/evidence.claims.json +cd509e106eb7e827109cca550f46afbcd0ec049c47a719de9debabbbc9c32c57 cases/transport-error/expected/observed.oci.json +17502a16b64b102182c1e2ffea7b51d9c39461dd8c9dbaad4427ce8ffd213bbf cases/transport-error/expected/observed.registry.json +6a37f4257d4d1d1714e43b43bef32af8f00d71b219500f8e2820f46b046fc0b3 cases/transport-error/expected/verdict.fields.json +d5daed86d55d8b22d307e8b4df9ab5af7cfa9c2647750d28c5344daeb40f92f8 cases/transport-error/intent.registry.json +b5fdf8f066fa2295376d100b62429c7a20edbb04dd0f31f1b7a83507a3374b2e cases/transport-error/oci/error.json +5c650f84f688d4d965721df74779228bb6fdfa47d5c08c46e8274e6d97b212c8 lifecycle-map.json +c637082ac035d4dda23ff27a31dfd7bd9a62437db24b53d48d585897fa1874a7 not-planned.json +b2c3bf78eb4368f14eb5d380cd7aee88bf33c39f88613fa1fae00f551f7e1d83 plan.json diff --git a/fixtures/vcn-v1/README.md b/fixtures/vcn-v1/README.md new file mode 100644 index 0000000..f10c6cc --- /dev/null +++ b/fixtures/vcn-v1/README.md @@ -0,0 +1,72 @@ +# Közös VCN-fixture (v1) — Intent → Observed → Conformance → Evidence + +Egy kicsi, rögzített fixture, amin a négy fejlesztési szál (registry, OCI modul, primitive engine, Relay) **egymásra várás nélkül** +dolgozik. Minden szál a saját szakaszát valósítja meg, és a saját tesztjében az itt rögzített `expected/` fájlokkal veti össze. +A fixture-t egy referencia-generátor (`generate.py`) állítja elő; **a generátor nem implementáció, amit át kell venni**, hanem +az egyeztetett szemantika egy helyen. A fájlokat a `generate.py --check` ellenőrzi, a `MANIFEST.sha256` rögzíti. + +``` +intent ──► [GetVcn] ──► oci/response.json | oci/error.json + │ 3. OCI modul normalizálása (effective config + state + coverage, OCI-nevekkel) + ▼ + expected/observed.oci.json + │ 4. a registry bindingja (OCI-nevek → registry-nevek, lifecycle leképezés) + ▼ + expected/observed.registry.json + intent.registry.json + │ 5. primitive engine: conformance (plan.json szerint) + ▼ + expected/verdict.fields.json + │ 6. Relay: bizonyíték, végrehajtás nélkül + ▼ + expected/evidence.claims.json +``` + +| Szakasz | Tulajdonos | Mit kell tudnia | Mivel veti össze | +|---|---|---|---| +| 3 | `cic-module-oracle-cloud` | nyers `GetVcn` válasz → `effective_config`, `state`, `coverage` | `expected/observed.oci.json` | +| 4 | `cic-schema-registry` | gépi binding OCI↔registry mezőnevek, `lifecycle-map.json` | `expected/observed.registry.json` | +| 5 | `cic-primitive-engine` | `MaterializedObject`-ek + `Observation` + `ConformancePlan` → verdikt | `expected/verdict.fields.json` | +| 6 | `CIC-Relay` | a szakaszok láncolása és a bizonyíték, unknown/absent explicit | `expected/evidence.claims.json` | + +## A coverage szabályai (a szakasz 3 és 4 szerződése) + +| Eset | Coverage | +|---|---| +| a mező a válaszban van és jó típusú | `observed` | +| a mező nincs a válaszban / null, és az OCI ezt „nincs beállítva"-ként jelenti (`dnsLabel`) | `absent` | +| a forrásmező hiányzik, ezért a származtatott érték nem állítható elő (`ipv6CidrBlocks` nélkül nincs `isIpv6Enabled`) | `unobserved` — **soha nem `false`** | +| a mező ott van, de nem hihető (`cidrBlocks` nem lista) | `unknown` | +| HTTP 404 `NotAuthorizedOrNotFound` | minden mező `unknown`: az OCI nem különbözteti meg a törölt és a nem engedélyezett erőforrást, a hiány **nem** authoritatív | +| nincs válasz (timeout) | minden mező `unobserved` | + +A verdiktben az `unknown` és az `unobserved` egyaránt `UNOBSERVED`: **soha nem `CONFORMANT`**. + +## A verdikt szemantikája (a primitive engine `origin/main` szerint) + +`observed`: egyezik → `CONFORMANT`, nem → `DRIFT`, eltérő típus → `NOT_COMPARABLE`. `absent` és a szándékban van érték → +`DRIFT`; `absent` és a szándékban nincs → `OBSERVED_ABSENT`. `unobserved`/`unknown` → `UNOBSERVED`. Az objektum: `DRIFT`, ha +bármely mező `DRIFT` vagy `NOT_COMPARABLE`; különben `INCOMPLETE`, ha bármely `UNOBSERVED`; különben `CONFORMANT`. + +A `plan.json` mezői: `/cidr_block`, `/dns_label`, `/ipv6_enabled` és a `/tags/{key=…}/value`. **Nincs a tervben** (a verdikt +nem mond róluk semmit): `secondary_cidr_blocks`, `display_name`, `compartment_id`, `lifecycle_state`, `ipv6_cidr_blocks` +(`not-planned.json`). + +## A nyitott kérdés (`desired-absent-observed-present`) + +Az OCI modul szerint a szándékban hiányzó mező „nem kezelt, nem nyúlunk hozzá". A primitive engine viszont egy **tervben szereplő, +de a szándékban nem deklarált** útvonalon, ha van megfigyelt érték, `NOT_COMPARABLE`-t ad (az objektum `DRIFT`). A fixture az +engine tényleges viselkedését rögzíti. A kérdés, amit a Relay-szál és az engine-szál jelent: a tervet a szándékból kell +származtatni (a nem deklarált útvonal nincs a tervben), vagy az engine-nek kell megkülönböztetnie a nem kezelt mezőt? A +fixture-t nem szabad úgy „javítani", hogy a kérdés eltűnjön. + +## Mit tegyen a szál + +* Másolja a fixture-t a saját repójába (vendored, a `MANIFEST.sha256`-tal és a forrás commit megjelölésével); a teszt ellenőrizze a + manifestet. Ne írja át az `expected/` fájlokat, hogy a saját kimenetéhez igazítsa. Ha szerinte hibás, jelezze. +* Csak a saját szakaszát implementálja; a többiét az `expected/` fájlokból veszi. +* Ami hiányzik a fixture-ből, azt jelezze, ne találja ki. + +## Karbantartás + +`python3 fixtures/vcn-v1/generate.py` újragenerál, `--check` ellenőriz. A szemantika változása új fixture-verziót (`vcn-v2`) +jelent, nem a v1 átírását. diff --git a/fixtures/vcn-v1/SOURCE.md b/fixtures/vcn-v1/SOURCE.md new file mode 100644 index 0000000..8d7e773 --- /dev/null +++ b/fixtures/vcn-v1/SOURCE.md @@ -0,0 +1,4 @@ +Vendored unchanged from cic-factory `fixtures/vcn-v1` at commit e5978f50d2512c5ac7536b9d81ffb1f054b75525 +(CentralInfraCore/cic-factory PR #37). `MANIFEST.sha256` pins every file; do not edit the files, regenerate a newer fixture version +instead. This repository owns stage 5 only (the conformance verdict); the vectors derived from it are +`conformance/differential/verdict/vcn_*`. diff --git a/fixtures/vcn-v1/cases.json b/fixtures/vcn-v1/cases.json new file mode 100644 index 0000000..55d5dc1 --- /dev/null +++ b/fixtures/vcn-v1/cases.json @@ -0,0 +1,11 @@ +{ + "conformant": "everything wanted is there", + "desired-absent": "the dns label is not managed (not in the intent) and OCI has none: OBSERVED_ABSENT, the object conforms", + "desired-absent-observed-present": "OPEN QUESTION: the dns label is not in the intent but is in the plan and OCI has one; the engine says NOT_COMPARABLE (object DRIFT), the module's 'absent = unmanaged' would say fine", + "drift": "the dns label and one tag value differ", + "field-absent": "a wanted dns label that OCI does not report (not set): DRIFT, not 'unknown'", + "malformed-cidr": "cidrBlocks is not a list: it was read but cannot be trusted: unknown", + "not-found": "HTTP 404 NotAuthorizedOrNotFound: OCI does not tell 'deleted' from 'not allowed', so absence is NOT authoritative", + "partial-read": "the answer has no ipv6CidrBlocks at all: ipv6_enabled was not observed, it is not 'false'", + "transport-error": "no answer (timeout): nothing was observed" +} diff --git a/fixtures/vcn-v1/cases/conformant/expected/evidence.claims.json b/fixtures/vcn-v1/cases/conformant/expected/evidence.claims.json new file mode 100644 index 0000000..16aa26e --- /dev/null +++ b/fixtures/vcn-v1/cases/conformant/expected/evidence.claims.json @@ -0,0 +1,6 @@ +{ + "conformance_claimed": true, + "drift_paths": [], + "object": "CONFORMANT", + "unobserved_paths": [] +} diff --git a/fixtures/vcn-v1/cases/conformant/expected/observed.oci.json b/fixtures/vcn-v1/cases/conformant/expected/observed.oci.json new file mode 100644 index 0000000..51f3c2e --- /dev/null +++ b/fixtures/vcn-v1/cases/conformant/expected/observed.oci.json @@ -0,0 +1,24 @@ +{ + "coverage": { + "/cidrBlocks": "observed", + "/dnsLabel": "observed", + "/freeformTags": "observed", + "/isIpv6Enabled": "observed" + }, + "effective_config": { + "cidrBlocks": [ + "10.0.0.0/16" + ], + "dnsLabel": "prod", + "freeformTags": { + "env": "prod", + "owner": "net" + }, + "isIpv6Enabled": false + }, + "state": { + "id": "ocid1.vcn.oc1.eu-frankfurt-1.aaaaaaaaexample", + "ipv6CidrBlocks": [], + "lifecycleState": "AVAILABLE" + } +} diff --git a/fixtures/vcn-v1/cases/conformant/expected/observed.registry.json b/fixtures/vcn-v1/cases/conformant/expected/observed.registry.json new file mode 100644 index 0000000..ffeffd2 --- /dev/null +++ b/fixtures/vcn-v1/cases/conformant/expected/observed.registry.json @@ -0,0 +1,28 @@ +{ + "config": { + "cidr_block": "10.0.0.0/16", + "dns_label": "prod", + "ipv6_enabled": false, + "tags": [ + { + "key": "env", + "value": "prod" + }, + { + "key": "owner", + "value": "net" + } + ] + }, + "coverage": { + "/cidr_block": "observed", + "/dns_label": "observed", + "/ipv6_enabled": "observed", + "/tags/{key=env}/value": "observed", + "/tags/{key=owner}/value": "observed" + }, + "state": { + "ipv6_cidr_blocks": [], + "lifecycle_state": "available" + } +} diff --git a/fixtures/vcn-v1/cases/conformant/expected/verdict.fields.json b/fixtures/vcn-v1/cases/conformant/expected/verdict.fields.json new file mode 100644 index 0000000..e5b89ef --- /dev/null +++ b/fixtures/vcn-v1/cases/conformant/expected/verdict.fields.json @@ -0,0 +1,10 @@ +{ + "fields": { + "/cidr_block": "CONFORMANT", + "/dns_label": "CONFORMANT", + "/ipv6_enabled": "CONFORMANT", + "/tags/{key=env}/value": "CONFORMANT", + "/tags/{key=owner}/value": "CONFORMANT" + }, + "object": "CONFORMANT" +} diff --git a/fixtures/vcn-v1/cases/conformant/intent.registry.json b/fixtures/vcn-v1/cases/conformant/intent.registry.json new file mode 100644 index 0000000..6a591c5 --- /dev/null +++ b/fixtures/vcn-v1/cases/conformant/intent.registry.json @@ -0,0 +1,15 @@ +{ + "cidr_block": "10.0.0.0/16", + "dns_label": "prod", + "ipv6_enabled": false, + "tags": [ + { + "key": "env", + "value": "prod" + }, + { + "key": "owner", + "value": "net" + } + ] +} diff --git a/fixtures/vcn-v1/cases/conformant/oci/response.json b/fixtures/vcn-v1/cases/conformant/oci/response.json new file mode 100644 index 0000000..f0cdb95 --- /dev/null +++ b/fixtures/vcn-v1/cases/conformant/oci/response.json @@ -0,0 +1,18 @@ +{ + "cidrBlocks": [ + "10.0.0.0/16" + ], + "compartmentId": "ocid1.compartment.oc1..bbbbbbbbexample", + "defaultRouteTableId": "ocid1.routetable.oc1.eu-frankfurt-1.cccccccexample", + "definedTags": {}, + "displayName": "prod-vcn", + "dnsLabel": "prod", + "freeformTags": { + "env": "prod", + "owner": "net" + }, + "id": "ocid1.vcn.oc1.eu-frankfurt-1.aaaaaaaaexample", + "ipv6CidrBlocks": [], + "lifecycleState": "AVAILABLE", + "timeCreated": "2026-07-19T00:00:00Z" +} diff --git a/fixtures/vcn-v1/cases/desired-absent-observed-present/expected/evidence.claims.json b/fixtures/vcn-v1/cases/desired-absent-observed-present/expected/evidence.claims.json new file mode 100644 index 0000000..d713e4f --- /dev/null +++ b/fixtures/vcn-v1/cases/desired-absent-observed-present/expected/evidence.claims.json @@ -0,0 +1,8 @@ +{ + "conformance_claimed": false, + "drift_paths": [ + "/dns_label" + ], + "object": "DRIFT", + "unobserved_paths": [] +} diff --git a/fixtures/vcn-v1/cases/desired-absent-observed-present/expected/observed.oci.json b/fixtures/vcn-v1/cases/desired-absent-observed-present/expected/observed.oci.json new file mode 100644 index 0000000..51f3c2e --- /dev/null +++ b/fixtures/vcn-v1/cases/desired-absent-observed-present/expected/observed.oci.json @@ -0,0 +1,24 @@ +{ + "coverage": { + "/cidrBlocks": "observed", + "/dnsLabel": "observed", + "/freeformTags": "observed", + "/isIpv6Enabled": "observed" + }, + "effective_config": { + "cidrBlocks": [ + "10.0.0.0/16" + ], + "dnsLabel": "prod", + "freeformTags": { + "env": "prod", + "owner": "net" + }, + "isIpv6Enabled": false + }, + "state": { + "id": "ocid1.vcn.oc1.eu-frankfurt-1.aaaaaaaaexample", + "ipv6CidrBlocks": [], + "lifecycleState": "AVAILABLE" + } +} diff --git a/fixtures/vcn-v1/cases/desired-absent-observed-present/expected/observed.registry.json b/fixtures/vcn-v1/cases/desired-absent-observed-present/expected/observed.registry.json new file mode 100644 index 0000000..ffeffd2 --- /dev/null +++ b/fixtures/vcn-v1/cases/desired-absent-observed-present/expected/observed.registry.json @@ -0,0 +1,28 @@ +{ + "config": { + "cidr_block": "10.0.0.0/16", + "dns_label": "prod", + "ipv6_enabled": false, + "tags": [ + { + "key": "env", + "value": "prod" + }, + { + "key": "owner", + "value": "net" + } + ] + }, + "coverage": { + "/cidr_block": "observed", + "/dns_label": "observed", + "/ipv6_enabled": "observed", + "/tags/{key=env}/value": "observed", + "/tags/{key=owner}/value": "observed" + }, + "state": { + "ipv6_cidr_blocks": [], + "lifecycle_state": "available" + } +} diff --git a/fixtures/vcn-v1/cases/desired-absent-observed-present/expected/verdict.fields.json b/fixtures/vcn-v1/cases/desired-absent-observed-present/expected/verdict.fields.json new file mode 100644 index 0000000..10be85d --- /dev/null +++ b/fixtures/vcn-v1/cases/desired-absent-observed-present/expected/verdict.fields.json @@ -0,0 +1,10 @@ +{ + "fields": { + "/cidr_block": "CONFORMANT", + "/dns_label": "NOT_COMPARABLE", + "/ipv6_enabled": "CONFORMANT", + "/tags/{key=env}/value": "CONFORMANT", + "/tags/{key=owner}/value": "CONFORMANT" + }, + "object": "DRIFT" +} diff --git a/fixtures/vcn-v1/cases/desired-absent-observed-present/intent.registry.json b/fixtures/vcn-v1/cases/desired-absent-observed-present/intent.registry.json new file mode 100644 index 0000000..fa57e92 --- /dev/null +++ b/fixtures/vcn-v1/cases/desired-absent-observed-present/intent.registry.json @@ -0,0 +1,14 @@ +{ + "cidr_block": "10.0.0.0/16", + "ipv6_enabled": false, + "tags": [ + { + "key": "env", + "value": "prod" + }, + { + "key": "owner", + "value": "net" + } + ] +} diff --git a/fixtures/vcn-v1/cases/desired-absent-observed-present/oci/response.json b/fixtures/vcn-v1/cases/desired-absent-observed-present/oci/response.json new file mode 100644 index 0000000..f0cdb95 --- /dev/null +++ b/fixtures/vcn-v1/cases/desired-absent-observed-present/oci/response.json @@ -0,0 +1,18 @@ +{ + "cidrBlocks": [ + "10.0.0.0/16" + ], + "compartmentId": "ocid1.compartment.oc1..bbbbbbbbexample", + "defaultRouteTableId": "ocid1.routetable.oc1.eu-frankfurt-1.cccccccexample", + "definedTags": {}, + "displayName": "prod-vcn", + "dnsLabel": "prod", + "freeformTags": { + "env": "prod", + "owner": "net" + }, + "id": "ocid1.vcn.oc1.eu-frankfurt-1.aaaaaaaaexample", + "ipv6CidrBlocks": [], + "lifecycleState": "AVAILABLE", + "timeCreated": "2026-07-19T00:00:00Z" +} diff --git a/fixtures/vcn-v1/cases/desired-absent/expected/evidence.claims.json b/fixtures/vcn-v1/cases/desired-absent/expected/evidence.claims.json new file mode 100644 index 0000000..16aa26e --- /dev/null +++ b/fixtures/vcn-v1/cases/desired-absent/expected/evidence.claims.json @@ -0,0 +1,6 @@ +{ + "conformance_claimed": true, + "drift_paths": [], + "object": "CONFORMANT", + "unobserved_paths": [] +} diff --git a/fixtures/vcn-v1/cases/desired-absent/expected/observed.oci.json b/fixtures/vcn-v1/cases/desired-absent/expected/observed.oci.json new file mode 100644 index 0000000..817f708 --- /dev/null +++ b/fixtures/vcn-v1/cases/desired-absent/expected/observed.oci.json @@ -0,0 +1,23 @@ +{ + "coverage": { + "/cidrBlocks": "observed", + "/dnsLabel": "absent", + "/freeformTags": "observed", + "/isIpv6Enabled": "observed" + }, + "effective_config": { + "cidrBlocks": [ + "10.0.0.0/16" + ], + "freeformTags": { + "env": "prod", + "owner": "net" + }, + "isIpv6Enabled": false + }, + "state": { + "id": "ocid1.vcn.oc1.eu-frankfurt-1.aaaaaaaaexample", + "ipv6CidrBlocks": [], + "lifecycleState": "AVAILABLE" + } +} diff --git a/fixtures/vcn-v1/cases/desired-absent/expected/observed.registry.json b/fixtures/vcn-v1/cases/desired-absent/expected/observed.registry.json new file mode 100644 index 0000000..790fca8 --- /dev/null +++ b/fixtures/vcn-v1/cases/desired-absent/expected/observed.registry.json @@ -0,0 +1,27 @@ +{ + "config": { + "cidr_block": "10.0.0.0/16", + "ipv6_enabled": false, + "tags": [ + { + "key": "env", + "value": "prod" + }, + { + "key": "owner", + "value": "net" + } + ] + }, + "coverage": { + "/cidr_block": "observed", + "/dns_label": "absent", + "/ipv6_enabled": "observed", + "/tags/{key=env}/value": "observed", + "/tags/{key=owner}/value": "observed" + }, + "state": { + "ipv6_cidr_blocks": [], + "lifecycle_state": "available" + } +} diff --git a/fixtures/vcn-v1/cases/desired-absent/expected/verdict.fields.json b/fixtures/vcn-v1/cases/desired-absent/expected/verdict.fields.json new file mode 100644 index 0000000..9740fb0 --- /dev/null +++ b/fixtures/vcn-v1/cases/desired-absent/expected/verdict.fields.json @@ -0,0 +1,10 @@ +{ + "fields": { + "/cidr_block": "CONFORMANT", + "/dns_label": "OBSERVED_ABSENT", + "/ipv6_enabled": "CONFORMANT", + "/tags/{key=env}/value": "CONFORMANT", + "/tags/{key=owner}/value": "CONFORMANT" + }, + "object": "CONFORMANT" +} diff --git a/fixtures/vcn-v1/cases/desired-absent/intent.registry.json b/fixtures/vcn-v1/cases/desired-absent/intent.registry.json new file mode 100644 index 0000000..fa57e92 --- /dev/null +++ b/fixtures/vcn-v1/cases/desired-absent/intent.registry.json @@ -0,0 +1,14 @@ +{ + "cidr_block": "10.0.0.0/16", + "ipv6_enabled": false, + "tags": [ + { + "key": "env", + "value": "prod" + }, + { + "key": "owner", + "value": "net" + } + ] +} diff --git a/fixtures/vcn-v1/cases/desired-absent/oci/response.json b/fixtures/vcn-v1/cases/desired-absent/oci/response.json new file mode 100644 index 0000000..4198ed6 --- /dev/null +++ b/fixtures/vcn-v1/cases/desired-absent/oci/response.json @@ -0,0 +1,17 @@ +{ + "cidrBlocks": [ + "10.0.0.0/16" + ], + "compartmentId": "ocid1.compartment.oc1..bbbbbbbbexample", + "defaultRouteTableId": "ocid1.routetable.oc1.eu-frankfurt-1.cccccccexample", + "definedTags": {}, + "displayName": "prod-vcn", + "freeformTags": { + "env": "prod", + "owner": "net" + }, + "id": "ocid1.vcn.oc1.eu-frankfurt-1.aaaaaaaaexample", + "ipv6CidrBlocks": [], + "lifecycleState": "AVAILABLE", + "timeCreated": "2026-07-19T00:00:00Z" +} diff --git a/fixtures/vcn-v1/cases/drift/expected/evidence.claims.json b/fixtures/vcn-v1/cases/drift/expected/evidence.claims.json new file mode 100644 index 0000000..9ea5141 --- /dev/null +++ b/fixtures/vcn-v1/cases/drift/expected/evidence.claims.json @@ -0,0 +1,9 @@ +{ + "conformance_claimed": false, + "drift_paths": [ + "/dns_label", + "/tags/{key=env}/value" + ], + "object": "DRIFT", + "unobserved_paths": [] +} diff --git a/fixtures/vcn-v1/cases/drift/expected/observed.oci.json b/fixtures/vcn-v1/cases/drift/expected/observed.oci.json new file mode 100644 index 0000000..e71e27e --- /dev/null +++ b/fixtures/vcn-v1/cases/drift/expected/observed.oci.json @@ -0,0 +1,24 @@ +{ + "coverage": { + "/cidrBlocks": "observed", + "/dnsLabel": "observed", + "/freeformTags": "observed", + "/isIpv6Enabled": "observed" + }, + "effective_config": { + "cidrBlocks": [ + "10.0.0.0/16" + ], + "dnsLabel": "staging", + "freeformTags": { + "env": "dev", + "owner": "net" + }, + "isIpv6Enabled": false + }, + "state": { + "id": "ocid1.vcn.oc1.eu-frankfurt-1.aaaaaaaaexample", + "ipv6CidrBlocks": [], + "lifecycleState": "AVAILABLE" + } +} diff --git a/fixtures/vcn-v1/cases/drift/expected/observed.registry.json b/fixtures/vcn-v1/cases/drift/expected/observed.registry.json new file mode 100644 index 0000000..cddf9bd --- /dev/null +++ b/fixtures/vcn-v1/cases/drift/expected/observed.registry.json @@ -0,0 +1,28 @@ +{ + "config": { + "cidr_block": "10.0.0.0/16", + "dns_label": "staging", + "ipv6_enabled": false, + "tags": [ + { + "key": "env", + "value": "dev" + }, + { + "key": "owner", + "value": "net" + } + ] + }, + "coverage": { + "/cidr_block": "observed", + "/dns_label": "observed", + "/ipv6_enabled": "observed", + "/tags/{key=env}/value": "observed", + "/tags/{key=owner}/value": "observed" + }, + "state": { + "ipv6_cidr_blocks": [], + "lifecycle_state": "available" + } +} diff --git a/fixtures/vcn-v1/cases/drift/expected/verdict.fields.json b/fixtures/vcn-v1/cases/drift/expected/verdict.fields.json new file mode 100644 index 0000000..b818ac9 --- /dev/null +++ b/fixtures/vcn-v1/cases/drift/expected/verdict.fields.json @@ -0,0 +1,10 @@ +{ + "fields": { + "/cidr_block": "CONFORMANT", + "/dns_label": "DRIFT", + "/ipv6_enabled": "CONFORMANT", + "/tags/{key=env}/value": "DRIFT", + "/tags/{key=owner}/value": "CONFORMANT" + }, + "object": "DRIFT" +} diff --git a/fixtures/vcn-v1/cases/drift/intent.registry.json b/fixtures/vcn-v1/cases/drift/intent.registry.json new file mode 100644 index 0000000..6a591c5 --- /dev/null +++ b/fixtures/vcn-v1/cases/drift/intent.registry.json @@ -0,0 +1,15 @@ +{ + "cidr_block": "10.0.0.0/16", + "dns_label": "prod", + "ipv6_enabled": false, + "tags": [ + { + "key": "env", + "value": "prod" + }, + { + "key": "owner", + "value": "net" + } + ] +} diff --git a/fixtures/vcn-v1/cases/drift/oci/response.json b/fixtures/vcn-v1/cases/drift/oci/response.json new file mode 100644 index 0000000..e5696d7 --- /dev/null +++ b/fixtures/vcn-v1/cases/drift/oci/response.json @@ -0,0 +1,18 @@ +{ + "cidrBlocks": [ + "10.0.0.0/16" + ], + "compartmentId": "ocid1.compartment.oc1..bbbbbbbbexample", + "defaultRouteTableId": "ocid1.routetable.oc1.eu-frankfurt-1.cccccccexample", + "definedTags": {}, + "displayName": "prod-vcn", + "dnsLabel": "staging", + "freeformTags": { + "env": "dev", + "owner": "net" + }, + "id": "ocid1.vcn.oc1.eu-frankfurt-1.aaaaaaaaexample", + "ipv6CidrBlocks": [], + "lifecycleState": "AVAILABLE", + "timeCreated": "2026-07-19T00:00:00Z" +} diff --git a/fixtures/vcn-v1/cases/field-absent/expected/evidence.claims.json b/fixtures/vcn-v1/cases/field-absent/expected/evidence.claims.json new file mode 100644 index 0000000..d713e4f --- /dev/null +++ b/fixtures/vcn-v1/cases/field-absent/expected/evidence.claims.json @@ -0,0 +1,8 @@ +{ + "conformance_claimed": false, + "drift_paths": [ + "/dns_label" + ], + "object": "DRIFT", + "unobserved_paths": [] +} diff --git a/fixtures/vcn-v1/cases/field-absent/expected/observed.oci.json b/fixtures/vcn-v1/cases/field-absent/expected/observed.oci.json new file mode 100644 index 0000000..817f708 --- /dev/null +++ b/fixtures/vcn-v1/cases/field-absent/expected/observed.oci.json @@ -0,0 +1,23 @@ +{ + "coverage": { + "/cidrBlocks": "observed", + "/dnsLabel": "absent", + "/freeformTags": "observed", + "/isIpv6Enabled": "observed" + }, + "effective_config": { + "cidrBlocks": [ + "10.0.0.0/16" + ], + "freeformTags": { + "env": "prod", + "owner": "net" + }, + "isIpv6Enabled": false + }, + "state": { + "id": "ocid1.vcn.oc1.eu-frankfurt-1.aaaaaaaaexample", + "ipv6CidrBlocks": [], + "lifecycleState": "AVAILABLE" + } +} diff --git a/fixtures/vcn-v1/cases/field-absent/expected/observed.registry.json b/fixtures/vcn-v1/cases/field-absent/expected/observed.registry.json new file mode 100644 index 0000000..790fca8 --- /dev/null +++ b/fixtures/vcn-v1/cases/field-absent/expected/observed.registry.json @@ -0,0 +1,27 @@ +{ + "config": { + "cidr_block": "10.0.0.0/16", + "ipv6_enabled": false, + "tags": [ + { + "key": "env", + "value": "prod" + }, + { + "key": "owner", + "value": "net" + } + ] + }, + "coverage": { + "/cidr_block": "observed", + "/dns_label": "absent", + "/ipv6_enabled": "observed", + "/tags/{key=env}/value": "observed", + "/tags/{key=owner}/value": "observed" + }, + "state": { + "ipv6_cidr_blocks": [], + "lifecycle_state": "available" + } +} diff --git a/fixtures/vcn-v1/cases/field-absent/expected/verdict.fields.json b/fixtures/vcn-v1/cases/field-absent/expected/verdict.fields.json new file mode 100644 index 0000000..48f037e --- /dev/null +++ b/fixtures/vcn-v1/cases/field-absent/expected/verdict.fields.json @@ -0,0 +1,10 @@ +{ + "fields": { + "/cidr_block": "CONFORMANT", + "/dns_label": "DRIFT", + "/ipv6_enabled": "CONFORMANT", + "/tags/{key=env}/value": "CONFORMANT", + "/tags/{key=owner}/value": "CONFORMANT" + }, + "object": "DRIFT" +} diff --git a/fixtures/vcn-v1/cases/field-absent/intent.registry.json b/fixtures/vcn-v1/cases/field-absent/intent.registry.json new file mode 100644 index 0000000..6a591c5 --- /dev/null +++ b/fixtures/vcn-v1/cases/field-absent/intent.registry.json @@ -0,0 +1,15 @@ +{ + "cidr_block": "10.0.0.0/16", + "dns_label": "prod", + "ipv6_enabled": false, + "tags": [ + { + "key": "env", + "value": "prod" + }, + { + "key": "owner", + "value": "net" + } + ] +} diff --git a/fixtures/vcn-v1/cases/field-absent/oci/response.json b/fixtures/vcn-v1/cases/field-absent/oci/response.json new file mode 100644 index 0000000..4198ed6 --- /dev/null +++ b/fixtures/vcn-v1/cases/field-absent/oci/response.json @@ -0,0 +1,17 @@ +{ + "cidrBlocks": [ + "10.0.0.0/16" + ], + "compartmentId": "ocid1.compartment.oc1..bbbbbbbbexample", + "defaultRouteTableId": "ocid1.routetable.oc1.eu-frankfurt-1.cccccccexample", + "definedTags": {}, + "displayName": "prod-vcn", + "freeformTags": { + "env": "prod", + "owner": "net" + }, + "id": "ocid1.vcn.oc1.eu-frankfurt-1.aaaaaaaaexample", + "ipv6CidrBlocks": [], + "lifecycleState": "AVAILABLE", + "timeCreated": "2026-07-19T00:00:00Z" +} diff --git a/fixtures/vcn-v1/cases/malformed-cidr/expected/evidence.claims.json b/fixtures/vcn-v1/cases/malformed-cidr/expected/evidence.claims.json new file mode 100644 index 0000000..1d8a38f --- /dev/null +++ b/fixtures/vcn-v1/cases/malformed-cidr/expected/evidence.claims.json @@ -0,0 +1,8 @@ +{ + "conformance_claimed": false, + "drift_paths": [], + "object": "INCOMPLETE", + "unobserved_paths": [ + "/cidr_block" + ] +} diff --git a/fixtures/vcn-v1/cases/malformed-cidr/expected/observed.oci.json b/fixtures/vcn-v1/cases/malformed-cidr/expected/observed.oci.json new file mode 100644 index 0000000..f5b5cc0 --- /dev/null +++ b/fixtures/vcn-v1/cases/malformed-cidr/expected/observed.oci.json @@ -0,0 +1,21 @@ +{ + "coverage": { + "/cidrBlocks": "unknown", + "/dnsLabel": "observed", + "/freeformTags": "observed", + "/isIpv6Enabled": "observed" + }, + "effective_config": { + "dnsLabel": "prod", + "freeformTags": { + "env": "prod", + "owner": "net" + }, + "isIpv6Enabled": false + }, + "state": { + "id": "ocid1.vcn.oc1.eu-frankfurt-1.aaaaaaaaexample", + "ipv6CidrBlocks": [], + "lifecycleState": "AVAILABLE" + } +} diff --git a/fixtures/vcn-v1/cases/malformed-cidr/expected/observed.registry.json b/fixtures/vcn-v1/cases/malformed-cidr/expected/observed.registry.json new file mode 100644 index 0000000..b3b5bd1 --- /dev/null +++ b/fixtures/vcn-v1/cases/malformed-cidr/expected/observed.registry.json @@ -0,0 +1,27 @@ +{ + "config": { + "dns_label": "prod", + "ipv6_enabled": false, + "tags": [ + { + "key": "env", + "value": "prod" + }, + { + "key": "owner", + "value": "net" + } + ] + }, + "coverage": { + "/cidr_block": "unknown", + "/dns_label": "observed", + "/ipv6_enabled": "observed", + "/tags/{key=env}/value": "observed", + "/tags/{key=owner}/value": "observed" + }, + "state": { + "ipv6_cidr_blocks": [], + "lifecycle_state": "available" + } +} diff --git a/fixtures/vcn-v1/cases/malformed-cidr/expected/verdict.fields.json b/fixtures/vcn-v1/cases/malformed-cidr/expected/verdict.fields.json new file mode 100644 index 0000000..5ab6f80 --- /dev/null +++ b/fixtures/vcn-v1/cases/malformed-cidr/expected/verdict.fields.json @@ -0,0 +1,10 @@ +{ + "fields": { + "/cidr_block": "UNOBSERVED", + "/dns_label": "CONFORMANT", + "/ipv6_enabled": "CONFORMANT", + "/tags/{key=env}/value": "CONFORMANT", + "/tags/{key=owner}/value": "CONFORMANT" + }, + "object": "INCOMPLETE" +} diff --git a/fixtures/vcn-v1/cases/malformed-cidr/intent.registry.json b/fixtures/vcn-v1/cases/malformed-cidr/intent.registry.json new file mode 100644 index 0000000..6a591c5 --- /dev/null +++ b/fixtures/vcn-v1/cases/malformed-cidr/intent.registry.json @@ -0,0 +1,15 @@ +{ + "cidr_block": "10.0.0.0/16", + "dns_label": "prod", + "ipv6_enabled": false, + "tags": [ + { + "key": "env", + "value": "prod" + }, + { + "key": "owner", + "value": "net" + } + ] +} diff --git a/fixtures/vcn-v1/cases/malformed-cidr/oci/response.json b/fixtures/vcn-v1/cases/malformed-cidr/oci/response.json new file mode 100644 index 0000000..110ca90 --- /dev/null +++ b/fixtures/vcn-v1/cases/malformed-cidr/oci/response.json @@ -0,0 +1,16 @@ +{ + "cidrBlocks": "10.0.0.0/16", + "compartmentId": "ocid1.compartment.oc1..bbbbbbbbexample", + "defaultRouteTableId": "ocid1.routetable.oc1.eu-frankfurt-1.cccccccexample", + "definedTags": {}, + "displayName": "prod-vcn", + "dnsLabel": "prod", + "freeformTags": { + "env": "prod", + "owner": "net" + }, + "id": "ocid1.vcn.oc1.eu-frankfurt-1.aaaaaaaaexample", + "ipv6CidrBlocks": [], + "lifecycleState": "AVAILABLE", + "timeCreated": "2026-07-19T00:00:00Z" +} diff --git a/fixtures/vcn-v1/cases/not-found/expected/evidence.claims.json b/fixtures/vcn-v1/cases/not-found/expected/evidence.claims.json new file mode 100644 index 0000000..347289c --- /dev/null +++ b/fixtures/vcn-v1/cases/not-found/expected/evidence.claims.json @@ -0,0 +1,12 @@ +{ + "conformance_claimed": false, + "drift_paths": [], + "object": "INCOMPLETE", + "unobserved_paths": [ + "/cidr_block", + "/dns_label", + "/ipv6_enabled", + "/tags/{key=env}/value", + "/tags/{key=owner}/value" + ] +} diff --git a/fixtures/vcn-v1/cases/not-found/expected/observed.oci.json b/fixtures/vcn-v1/cases/not-found/expected/observed.oci.json new file mode 100644 index 0000000..ebd398b --- /dev/null +++ b/fixtures/vcn-v1/cases/not-found/expected/observed.oci.json @@ -0,0 +1,10 @@ +{ + "coverage": { + "/cidrBlocks": "unknown", + "/dnsLabel": "unknown", + "/freeformTags": "unknown", + "/isIpv6Enabled": "unknown" + }, + "effective_config": {}, + "state": {} +} diff --git a/fixtures/vcn-v1/cases/not-found/expected/observed.registry.json b/fixtures/vcn-v1/cases/not-found/expected/observed.registry.json new file mode 100644 index 0000000..9a1f3b5 --- /dev/null +++ b/fixtures/vcn-v1/cases/not-found/expected/observed.registry.json @@ -0,0 +1,11 @@ +{ + "config": {}, + "coverage": { + "/cidr_block": "unknown", + "/dns_label": "unknown", + "/ipv6_enabled": "unknown", + "/tags/{key=env}/value": "unknown", + "/tags/{key=owner}/value": "unknown" + }, + "state": {} +} diff --git a/fixtures/vcn-v1/cases/not-found/expected/verdict.fields.json b/fixtures/vcn-v1/cases/not-found/expected/verdict.fields.json new file mode 100644 index 0000000..ad5d72e --- /dev/null +++ b/fixtures/vcn-v1/cases/not-found/expected/verdict.fields.json @@ -0,0 +1,10 @@ +{ + "fields": { + "/cidr_block": "UNOBSERVED", + "/dns_label": "UNOBSERVED", + "/ipv6_enabled": "UNOBSERVED", + "/tags/{key=env}/value": "UNOBSERVED", + "/tags/{key=owner}/value": "UNOBSERVED" + }, + "object": "INCOMPLETE" +} diff --git a/fixtures/vcn-v1/cases/not-found/intent.registry.json b/fixtures/vcn-v1/cases/not-found/intent.registry.json new file mode 100644 index 0000000..6a591c5 --- /dev/null +++ b/fixtures/vcn-v1/cases/not-found/intent.registry.json @@ -0,0 +1,15 @@ +{ + "cidr_block": "10.0.0.0/16", + "dns_label": "prod", + "ipv6_enabled": false, + "tags": [ + { + "key": "env", + "value": "prod" + }, + { + "key": "owner", + "value": "net" + } + ] +} diff --git a/fixtures/vcn-v1/cases/not-found/oci/error.json b/fixtures/vcn-v1/cases/not-found/oci/error.json new file mode 100644 index 0000000..74f1158 --- /dev/null +++ b/fixtures/vcn-v1/cases/not-found/oci/error.json @@ -0,0 +1,5 @@ +{ + "code": "NotAuthorizedOrNotFound", + "kind": "http", + "status": 404 +} diff --git a/fixtures/vcn-v1/cases/partial-read/expected/evidence.claims.json b/fixtures/vcn-v1/cases/partial-read/expected/evidence.claims.json new file mode 100644 index 0000000..eb34c55 --- /dev/null +++ b/fixtures/vcn-v1/cases/partial-read/expected/evidence.claims.json @@ -0,0 +1,8 @@ +{ + "conformance_claimed": false, + "drift_paths": [], + "object": "INCOMPLETE", + "unobserved_paths": [ + "/ipv6_enabled" + ] +} diff --git a/fixtures/vcn-v1/cases/partial-read/expected/observed.oci.json b/fixtures/vcn-v1/cases/partial-read/expected/observed.oci.json new file mode 100644 index 0000000..8a4e7a1 --- /dev/null +++ b/fixtures/vcn-v1/cases/partial-read/expected/observed.oci.json @@ -0,0 +1,22 @@ +{ + "coverage": { + "/cidrBlocks": "observed", + "/dnsLabel": "observed", + "/freeformTags": "observed", + "/isIpv6Enabled": "unobserved" + }, + "effective_config": { + "cidrBlocks": [ + "10.0.0.0/16" + ], + "dnsLabel": "prod", + "freeformTags": { + "env": "prod", + "owner": "net" + } + }, + "state": { + "id": "ocid1.vcn.oc1.eu-frankfurt-1.aaaaaaaaexample", + "lifecycleState": "AVAILABLE" + } +} diff --git a/fixtures/vcn-v1/cases/partial-read/expected/observed.registry.json b/fixtures/vcn-v1/cases/partial-read/expected/observed.registry.json new file mode 100644 index 0000000..e1c2bfa --- /dev/null +++ b/fixtures/vcn-v1/cases/partial-read/expected/observed.registry.json @@ -0,0 +1,26 @@ +{ + "config": { + "cidr_block": "10.0.0.0/16", + "dns_label": "prod", + "tags": [ + { + "key": "env", + "value": "prod" + }, + { + "key": "owner", + "value": "net" + } + ] + }, + "coverage": { + "/cidr_block": "observed", + "/dns_label": "observed", + "/ipv6_enabled": "unobserved", + "/tags/{key=env}/value": "observed", + "/tags/{key=owner}/value": "observed" + }, + "state": { + "lifecycle_state": "available" + } +} diff --git a/fixtures/vcn-v1/cases/partial-read/expected/verdict.fields.json b/fixtures/vcn-v1/cases/partial-read/expected/verdict.fields.json new file mode 100644 index 0000000..52d451e --- /dev/null +++ b/fixtures/vcn-v1/cases/partial-read/expected/verdict.fields.json @@ -0,0 +1,10 @@ +{ + "fields": { + "/cidr_block": "CONFORMANT", + "/dns_label": "CONFORMANT", + "/ipv6_enabled": "UNOBSERVED", + "/tags/{key=env}/value": "CONFORMANT", + "/tags/{key=owner}/value": "CONFORMANT" + }, + "object": "INCOMPLETE" +} diff --git a/fixtures/vcn-v1/cases/partial-read/intent.registry.json b/fixtures/vcn-v1/cases/partial-read/intent.registry.json new file mode 100644 index 0000000..6a591c5 --- /dev/null +++ b/fixtures/vcn-v1/cases/partial-read/intent.registry.json @@ -0,0 +1,15 @@ +{ + "cidr_block": "10.0.0.0/16", + "dns_label": "prod", + "ipv6_enabled": false, + "tags": [ + { + "key": "env", + "value": "prod" + }, + { + "key": "owner", + "value": "net" + } + ] +} diff --git a/fixtures/vcn-v1/cases/partial-read/oci/response.json b/fixtures/vcn-v1/cases/partial-read/oci/response.json new file mode 100644 index 0000000..2b12d6f --- /dev/null +++ b/fixtures/vcn-v1/cases/partial-read/oci/response.json @@ -0,0 +1,17 @@ +{ + "cidrBlocks": [ + "10.0.0.0/16" + ], + "compartmentId": "ocid1.compartment.oc1..bbbbbbbbexample", + "defaultRouteTableId": "ocid1.routetable.oc1.eu-frankfurt-1.cccccccexample", + "definedTags": {}, + "displayName": "prod-vcn", + "dnsLabel": "prod", + "freeformTags": { + "env": "prod", + "owner": "net" + }, + "id": "ocid1.vcn.oc1.eu-frankfurt-1.aaaaaaaaexample", + "lifecycleState": "AVAILABLE", + "timeCreated": "2026-07-19T00:00:00Z" +} diff --git a/fixtures/vcn-v1/cases/transport-error/expected/evidence.claims.json b/fixtures/vcn-v1/cases/transport-error/expected/evidence.claims.json new file mode 100644 index 0000000..347289c --- /dev/null +++ b/fixtures/vcn-v1/cases/transport-error/expected/evidence.claims.json @@ -0,0 +1,12 @@ +{ + "conformance_claimed": false, + "drift_paths": [], + "object": "INCOMPLETE", + "unobserved_paths": [ + "/cidr_block", + "/dns_label", + "/ipv6_enabled", + "/tags/{key=env}/value", + "/tags/{key=owner}/value" + ] +} diff --git a/fixtures/vcn-v1/cases/transport-error/expected/observed.oci.json b/fixtures/vcn-v1/cases/transport-error/expected/observed.oci.json new file mode 100644 index 0000000..0c74315 --- /dev/null +++ b/fixtures/vcn-v1/cases/transport-error/expected/observed.oci.json @@ -0,0 +1,10 @@ +{ + "coverage": { + "/cidrBlocks": "unobserved", + "/dnsLabel": "unobserved", + "/freeformTags": "unobserved", + "/isIpv6Enabled": "unobserved" + }, + "effective_config": {}, + "state": {} +} diff --git a/fixtures/vcn-v1/cases/transport-error/expected/observed.registry.json b/fixtures/vcn-v1/cases/transport-error/expected/observed.registry.json new file mode 100644 index 0000000..9c13d67 --- /dev/null +++ b/fixtures/vcn-v1/cases/transport-error/expected/observed.registry.json @@ -0,0 +1,11 @@ +{ + "config": {}, + "coverage": { + "/cidr_block": "unobserved", + "/dns_label": "unobserved", + "/ipv6_enabled": "unobserved", + "/tags/{key=env}/value": "unobserved", + "/tags/{key=owner}/value": "unobserved" + }, + "state": {} +} diff --git a/fixtures/vcn-v1/cases/transport-error/expected/verdict.fields.json b/fixtures/vcn-v1/cases/transport-error/expected/verdict.fields.json new file mode 100644 index 0000000..ad5d72e --- /dev/null +++ b/fixtures/vcn-v1/cases/transport-error/expected/verdict.fields.json @@ -0,0 +1,10 @@ +{ + "fields": { + "/cidr_block": "UNOBSERVED", + "/dns_label": "UNOBSERVED", + "/ipv6_enabled": "UNOBSERVED", + "/tags/{key=env}/value": "UNOBSERVED", + "/tags/{key=owner}/value": "UNOBSERVED" + }, + "object": "INCOMPLETE" +} diff --git a/fixtures/vcn-v1/cases/transport-error/intent.registry.json b/fixtures/vcn-v1/cases/transport-error/intent.registry.json new file mode 100644 index 0000000..6a591c5 --- /dev/null +++ b/fixtures/vcn-v1/cases/transport-error/intent.registry.json @@ -0,0 +1,15 @@ +{ + "cidr_block": "10.0.0.0/16", + "dns_label": "prod", + "ipv6_enabled": false, + "tags": [ + { + "key": "env", + "value": "prod" + }, + { + "key": "owner", + "value": "net" + } + ] +} diff --git a/fixtures/vcn-v1/cases/transport-error/oci/error.json b/fixtures/vcn-v1/cases/transport-error/oci/error.json new file mode 100644 index 0000000..46ed93f --- /dev/null +++ b/fixtures/vcn-v1/cases/transport-error/oci/error.json @@ -0,0 +1,4 @@ +{ + "kind": "transport", + "message": "timeout" +} diff --git a/fixtures/vcn-v1/generate.py b/fixtures/vcn-v1/generate.py new file mode 100644 index 0000000..2826353 --- /dev/null +++ b/fixtures/vcn-v1/generate.py @@ -0,0 +1,267 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: CC-BY-NC-SA-4.0 +# SPDX-FileCopyrightText: 2025-2026 Sinkó Gábor Zoltán / CentralInfraCore +"""Generates fixtures/vcn-v1: the common OCI VCN fixture of the Intent -> Observed -> Conformance -> Evidence slice. + + generate.py write the files next to this script + generate.py --check fail if the files on disk differ from what is generated + +The raw inputs are written down here by hand (the intent and the OCI responses of each case). Everything under `expected/` is +derived by the small reference rules below, which restate, in one place, what the four repositories must agree on: + + stage 1 intent.registry.json what is wanted, in the names of the registry schema (NetworkSpaceOracleCloud) + stage 2 oci/response.json | oci/error.json what OCI answered to GetVcn (or why it did not) + stage 3 expected/observed.oci.json the oracle module's normalization: effective config, state, coverage, in OCI names + stage 4 expected/observed.registry.json the same in registry names (the binding of the registry), with coverage per compared path + stage 5 expected/verdict.fields.json the conformance verdict per path and for the object (the engine semantics) + stage 6 expected/evidence.claims.json what an evidence record may claim about this case + +The reference rules are NOT an implementation to be reused: each repository implements its own stage and its tests compare the +result with these files. The semantics of stage 5 are those of cic-primitive-engine `classify_field` and `aggregate` +(origin/main): coverage Absent + intent present = DRIFT, Absent + intent absent = OBSERVED_ABSENT, Unobserved or Unknown = +UNOBSERVED, never CONFORMANT; the object is DRIFT if any field is DRIFT or NOT_COMPARABLE, else INCOMPLETE if any field is +UNOBSERVED, else CONFORMANT. +""" +import hashlib +import json +import os +import sys + +HERE = os.path.dirname(os.path.abspath(__file__)) + +# --- the plan: what is compared (case independent) ------------------------------------------------------------------------ +PLAN = { + "scalars": [ + {"path": "/cidr_block", "compare": "exact"}, + {"path": "/dns_label", "compare": "exact"}, + {"path": "/ipv6_enabled", "compare": "exact"}, + ], + "collections": [ + {"path": "/tags", "topology": "map", "keys": ["key"], "elements": [{"path": "value", "compare": "exact"}]}, + ], +} +# secondary_cidr_blocks, display_name, compartment_id and the state fields are NOT in the plan: the verdict says nothing about them. +NOT_PLANNED = ["/secondary_cidr_blocks", "/display_name", "/compartment_id", "/lifecycle_state", "/ipv6_cidr_blocks"] + +LIFECYCLE = { # OCI lifecycleState -> registry lifecycle_state enum [pending, available, deleting, deleted, unknown] + "PROVISIONING": "pending", "AVAILABLE": "available", "UPDATING": "unknown", "TERMINATING": "deleting", "TERMINATED": "deleted", +} +LIFECYCLE_OTHER = "unknown" + +BASE_INTENT = {"cidr_block": "10.0.0.0/16", "dns_label": "prod", "ipv6_enabled": False, + "tags": [{"key": "env", "value": "prod"}, {"key": "owner", "value": "net"}]} +BASE_RESPONSE = { + "id": "ocid1.vcn.oc1.eu-frankfurt-1.aaaaaaaaexample", "compartmentId": "ocid1.compartment.oc1..bbbbbbbbexample", + "displayName": "prod-vcn", "dnsLabel": "prod", "lifecycleState": "AVAILABLE", "cidrBlocks": ["10.0.0.0/16"], + "ipv6CidrBlocks": [], "freeformTags": {"env": "prod", "owner": "net"}, "definedTags": {}, + "defaultRouteTableId": "ocid1.routetable.oc1.eu-frankfurt-1.cccccccexample", "timeCreated": "2026-07-19T00:00:00Z", +} + + +def without(d, *keys): + return {k: v for k, v in d.items() if k not in keys} + + +def with_(d, **kw): + out = dict(d) + out.update(kw) + return out + + +# case id -> (description, intent, response | None, error | None) +CASES = { + "conformant": ("everything wanted is there", BASE_INTENT, BASE_RESPONSE, None), + "drift": ("the dns label and one tag value differ", BASE_INTENT, + with_(BASE_RESPONSE, dnsLabel="staging", freeformTags={"env": "dev", "owner": "net"}), None), + "field-absent": ("a wanted dns label that OCI does not report (not set): DRIFT, not 'unknown'", BASE_INTENT, + without(BASE_RESPONSE, "dnsLabel"), None), + "desired-absent": ("the dns label is not managed (not in the intent) and OCI has none: OBSERVED_ABSENT, the object conforms", + without(BASE_INTENT, "dns_label"), without(BASE_RESPONSE, "dnsLabel"), None), + "desired-absent-observed-present": ("OPEN QUESTION: the dns label is not in the intent but is in the plan and OCI has one; the engine says NOT_COMPARABLE (object DRIFT), the module's 'absent = unmanaged' would say fine", + without(BASE_INTENT, "dns_label"), BASE_RESPONSE, None), + "partial-read": ("the answer has no ipv6CidrBlocks at all: ipv6_enabled was not observed, it is not 'false'", BASE_INTENT, + without(BASE_RESPONSE, "ipv6CidrBlocks"), None), + "malformed-cidr": ("cidrBlocks is not a list: it was read but cannot be trusted: unknown", BASE_INTENT, + with_(BASE_RESPONSE, cidrBlocks="10.0.0.0/16"), None), + "not-found": ("HTTP 404 NotAuthorizedOrNotFound: OCI does not tell 'deleted' from 'not allowed', so absence is NOT authoritative", + BASE_INTENT, None, {"kind": "http", "status": 404, "code": "NotAuthorizedOrNotFound"}), + "transport-error": ("no answer (timeout): nothing was observed", BASE_INTENT, None, {"kind": "transport", "message": "timeout"}), +} + +OCI_PATHS = ["/cidrBlocks", "/dnsLabel", "/freeformTags", "/isIpv6Enabled"] + + +# --- stage 3: the module's normalization (reference) ---------------------------------------------------------------------- +def stage3(resp, err): + cov, eff, state = {}, {}, {} + if resp is None: + c = "unknown" if err["kind"] == "http" else "unobserved" # 404 cannot be told from 403; a timeout simply was not an answer + return {"effective_config": {}, "state": {}, "coverage": {p: c for p in OCI_PATHS}} + cb = resp.get("cidrBlocks") + if isinstance(cb, list) and cb and all(isinstance(x, str) for x in cb): + cov["/cidrBlocks"], eff["cidrBlocks"] = "observed", cb + else: + cov["/cidrBlocks"] = "unknown" + dl = resp.get("dnsLabel") + if isinstance(dl, str): + cov["/dnsLabel"], eff["dnsLabel"] = "observed", dl + else: + cov["/dnsLabel"] = "absent" + ft = resp.get("freeformTags") + if isinstance(ft, dict): + cov["/freeformTags"], eff["freeformTags"] = "observed", ft + else: + cov["/freeformTags"] = "unknown" + v6 = resp.get("ipv6CidrBlocks") + if isinstance(v6, list): + cov["/isIpv6Enabled"], eff["isIpv6Enabled"] = "observed", bool(v6) # derived; a partial read never fabricates a value + else: + cov["/isIpv6Enabled"] = "unobserved" + for k in ("id", "lifecycleState", "ipv6CidrBlocks"): + if k in resp: + state[k] = resp[k] + return {"effective_config": eff, "state": state, "coverage": cov} + + +# --- stage 4: the registry binding (reference) ---------------------------------------------------------------------------- +def stage4(obs3, intent): + eff, cov3 = obs3["effective_config"], obs3["coverage"] + config, cov = {}, {} + if "cidrBlocks" in eff: + config["cidr_block"] = eff["cidrBlocks"][0] + cov["/cidr_block"] = cov3["/cidrBlocks"] + if "dnsLabel" in eff: + config["dns_label"] = eff["dnsLabel"] + cov["/dns_label"] = cov3["/dnsLabel"] + if "isIpv6Enabled" in eff: + config["ipv6_enabled"] = eff["isIpv6Enabled"] + cov["/ipv6_enabled"] = cov3["/isIpv6Enabled"] + if "freeformTags" in eff: + config["tags"] = [{"key": k, "value": v} for k, v in sorted(eff["freeformTags"].items())] + for t in intent.get("tags", []): # the elements the plan can ask about: the keys of the intent + cov["/tags/{key=%s}/value" % t["key"]] = cov3["/freeformTags"] + state = {} + if "lifecycleState" in obs3["state"]: + state["lifecycle_state"] = LIFECYCLE.get(obs3["state"]["lifecycleState"], LIFECYCLE_OTHER) + if "ipv6CidrBlocks" in obs3["state"]: + state["ipv6_cidr_blocks"] = obs3["state"]["ipv6CidrBlocks"] + return {"config": config, "state": state, "coverage": cov} + + +# --- stage 5: the verdict (the engine semantics) -------------------------------------------------------------------------- +def intent_value(intent, path): + if path.startswith("/tags/{key="): + key = path[len("/tags/{key="):-len("}/value")] + for t in intent.get("tags", []): + if t["key"] == key: + return True, t["value"] + return False, None + name = path[1:] + return (name in intent), intent.get(name) + + +def observed_value(config, path): + if path.startswith("/tags/{key="): + key = path[len("/tags/{key="):-len("}/value")] + for t in config.get("tags", []): + if t["key"] == key: + return True, t["value"] + return False, None + name = path[1:] + return (name in config), config.get(name) + + +def kind(v): + return "null" if v is None else "bool" if isinstance(v, bool) else "number" if isinstance(v, (int, float)) else \ + "string" if isinstance(v, str) else "array" if isinstance(v, list) else "object" + + +def classify(coverage, present, matched, comparable=True): + if coverage == "observed": + if not comparable: + return "NOT_COMPARABLE" # compare() of two values of different kinds (engine: never a false DRIFT) + return "CONFORMANT" if matched else "DRIFT" + if coverage == "absent": + return "DRIFT" if present else "OBSERVED_ABSENT" + return "UNOBSERVED" # unobserved and unknown alike + + +def aggregate(fields): + vs = set(fields.values()) + if vs & {"DRIFT", "NOT_COMPARABLE"}: + return "DRIFT" + return "INCOMPLETE" if "UNOBSERVED" in vs else "CONFORMANT" + + +def stage5(obs4, intent): + fields = {} + paths = [s["path"] for s in PLAN["scalars"]] + ["/tags/{key=%s}/value" % t["key"] for t in intent.get("tags", [])] + for p in paths: + present, iv = intent_value(intent, p) + cov = obs4["coverage"][p] + have, ov = observed_value(obs4["config"], p) + # the engine compares even when the intent declares nothing for a planned path (its value is then null): an observed value + # of another kind is NOT_COMPARABLE, not 'unmanaged'. See the README, 'the open question'. + comparable = kind(iv if present else None) == kind(ov if have else None) + fields[p] = classify(cov, present, have and iv == ov, comparable) + return {"object": aggregate(fields), "fields": dict(sorted(fields.items()))} + + +def stage6(verdict): + f = verdict["fields"] + return {"conformance_claimed": verdict["object"] == "CONFORMANT", + "drift_paths": sorted(p for p, v in f.items() if v in ("DRIFT", "NOT_COMPARABLE")), + "unobserved_paths": sorted(p for p, v in f.items() if v == "UNOBSERVED"), + "object": verdict["object"]} + + +def dump(doc): + return json.dumps(doc, indent=2, sort_keys=True, ensure_ascii=False) + "\n" + + +def build(): + files = {"plan.json": dump(PLAN), "lifecycle-map.json": dump({"map": LIFECYCLE, "other": LIFECYCLE_OTHER}), + "not-planned.json": dump(NOT_PLANNED), "cases.json": dump({k: v[0] for k, v in sorted(CASES.items())})} + for cid, (_, intent, resp, err) in CASES.items(): + base = "cases/%s/" % cid + files[base + "intent.registry.json"] = dump(intent) + files[base + ("oci/response.json" if resp is not None else "oci/error.json")] = dump(resp if resp is not None else err) + o3 = stage3(resp, err) + o4 = stage4(o3, intent) + v = stage5(o4, intent) + files[base + "expected/observed.oci.json"] = dump(o3) + files[base + "expected/observed.registry.json"] = dump(o4) + files[base + "expected/verdict.fields.json"] = dump(v) + files[base + "expected/evidence.claims.json"] = dump(stage6(v)) + manifest = "".join("%s %s\n" % (hashlib.sha256(files[k].encode()).hexdigest(), k) for k in sorted(files)) + files["MANIFEST.sha256"] = manifest + return files + + +def main(argv): + files = build() + if "--check" in argv: + bad = [k for k, t in files.items() if not os.path.exists(os.path.join(HERE, k)) or open(os.path.join(HERE, k), encoding="utf-8").read() != t] + on_disk = set() + for root, _, names in os.walk(HERE): + for n in names: + rel = os.path.relpath(os.path.join(root, n), HERE) + if rel not in ("generate.py", "README.md") and not rel.endswith(".pyc") and "__pycache__" not in rel: + on_disk.add(rel) + extra = sorted(on_disk - set(files)) + if bad or extra: + print("differs: %s; not generated: %s" % (bad, extra)) + return 1 + print("fixture ok: %d files" % len(files)) + return 0 + for k, t in files.items(): + path = os.path.join(HERE, k) + os.makedirs(os.path.dirname(path), exist_ok=True) + with open(path, "w", encoding="utf-8") as fh: + fh.write(t) + print("wrote %d files" % len(files)) + return 0 + + +if __name__ == "__main__": + sys.exit(main(sys.argv[1:])) diff --git a/fixtures/vcn-v1/lifecycle-map.json b/fixtures/vcn-v1/lifecycle-map.json new file mode 100644 index 0000000..a56e46e --- /dev/null +++ b/fixtures/vcn-v1/lifecycle-map.json @@ -0,0 +1,10 @@ +{ + "map": { + "AVAILABLE": "available", + "PROVISIONING": "pending", + "TERMINATED": "deleted", + "TERMINATING": "deleting", + "UPDATING": "unknown" + }, + "other": "unknown" +} diff --git a/fixtures/vcn-v1/not-planned.json b/fixtures/vcn-v1/not-planned.json new file mode 100644 index 0000000..2ab022f --- /dev/null +++ b/fixtures/vcn-v1/not-planned.json @@ -0,0 +1,7 @@ +[ + "/secondary_cidr_blocks", + "/display_name", + "/compartment_id", + "/lifecycle_state", + "/ipv6_cidr_blocks" +] diff --git a/fixtures/vcn-v1/plan.json b/fixtures/vcn-v1/plan.json new file mode 100644 index 0000000..fb320b5 --- /dev/null +++ b/fixtures/vcn-v1/plan.json @@ -0,0 +1,31 @@ +{ + "collections": [ + { + "elements": [ + { + "compare": "exact", + "path": "value" + } + ], + "keys": [ + "key" + ], + "path": "/tags", + "topology": "map" + } + ], + "scalars": [ + { + "compare": "exact", + "path": "/cidr_block" + }, + { + "compare": "exact", + "path": "/dns_label" + }, + { + "compare": "exact", + "path": "/ipv6_enabled" + } + ] +}