From f1d20e782ec335ddd04766fbe4490f2703619bf8 Mon Sep 17 00:00:00 2001 From: Noah Gift Date: Wed, 23 Sep 2026 13:29:32 +0200 Subject: [PATCH 1/9] RHL-16: fleet/tickets v2 vocabularies, v2 contracts, and the action-attribute check - vocab/fleet-v2.yaml, vocab/tickets-v2.yaml: every v1 term carried forward unchanged, each with its own contracts/rhl---v2.yaml; file ticket gains attributes [title: Text, label: Text]; ticket count is a new measure (Count) with no effect. lowers_to and sigma_entity stay [U]. - Checker: in the with-block of an action that declares attributes, each line is an attribute assignment: an undeclared name is V001 (listing the declared attributes) / V002 / V003 by nearness; a value of the wrong type is T002. Actions without attributes keep the v1 behaviour. - rhl16_ tests: v2 vocabulary shape, the v2 corpus harness, attribute and no-effect-measure unit tests. Pmat-Ticket: RHLGA-1 Co-Authored-By: Claude Opus 5.5 (1M context) --- contracts/rhl-fleet-disk-free-of-v2.yaml | 64 ++++ contracts/rhl-fleet-disk-usage-of-v2.yaml | 64 ++++ contracts/rhl-fleet-gb-v2.yaml | 63 ++++ contracts/rhl-fleet-host-v2.yaml | 64 ++++ contracts/rhl-fleet-hour-v2.yaml | 63 ++++ contracts/rhl-fleet-pin-status-of-v2.yaml | 64 ++++ contracts/rhl-fleet-runner-load-of-v2.yaml | 64 ++++ contracts/rhl-fleet-runner-v2.yaml | 64 ++++ contracts/rhl-tickets-file-ticket-v2.yaml | 69 ++++ contracts/rhl-tickets-label-ticket-v2.yaml | 64 ++++ contracts/rhl-tickets-repo-v2.yaml | 64 ++++ contracts/rhl-tickets-ticket-count-v2.yaml | 66 ++++ contracts/rhl-tickets-ticket-label-v2.yaml | 63 ++++ contracts/rhl-tickets-ticket-title-v2.yaml | 63 ++++ .../rhl-tickets-tickets-filed-in-v2.yaml | 64 ++++ src/rhl/check/stmts.rs | 83 ++++- src/rhl/check_tests.rs | 306 ++++++++++++++++++ src/rhl/vocab.rs | 5 + vocab/fleet-v2.yaml | 69 ++++ vocab/tickets-v2.yaml | 67 ++++ 20 files changed, 1489 insertions(+), 4 deletions(-) create mode 100644 contracts/rhl-fleet-disk-free-of-v2.yaml create mode 100644 contracts/rhl-fleet-disk-usage-of-v2.yaml create mode 100644 contracts/rhl-fleet-gb-v2.yaml create mode 100644 contracts/rhl-fleet-host-v2.yaml create mode 100644 contracts/rhl-fleet-hour-v2.yaml create mode 100644 contracts/rhl-fleet-pin-status-of-v2.yaml create mode 100644 contracts/rhl-fleet-runner-load-of-v2.yaml create mode 100644 contracts/rhl-fleet-runner-v2.yaml create mode 100644 contracts/rhl-tickets-file-ticket-v2.yaml create mode 100644 contracts/rhl-tickets-label-ticket-v2.yaml create mode 100644 contracts/rhl-tickets-repo-v2.yaml create mode 100644 contracts/rhl-tickets-ticket-count-v2.yaml create mode 100644 contracts/rhl-tickets-ticket-label-v2.yaml create mode 100644 contracts/rhl-tickets-ticket-title-v2.yaml create mode 100644 contracts/rhl-tickets-tickets-filed-in-v2.yaml create mode 100644 vocab/fleet-v2.yaml create mode 100644 vocab/tickets-v2.yaml diff --git a/contracts/rhl-fleet-disk-free-of-v2.yaml b/contracts/rhl-fleet-disk-free-of-v2.yaml new file mode 100644 index 000000000..2204bce9b --- /dev/null +++ b/contracts/rhl-fleet-disk-free-of-v2.yaml @@ -0,0 +1,64 @@ +metadata: + version: "2.0.0" + author: "RHL-16" + description: "RHL-16 v2 contract for vocabulary term 'disk free of' (fleet v2, kind: measure)" + crate: "ruchy" + references: + - "RHL-001 §3.4" + +equations: + disk_free_of_shape: + formula: "kind('disk free of') = measure ∧ gives('disk free of') = Size" + domain: "t ∈ vocabulary fleet v2 terms named 'disk free of'" + invariants: + - "term kind is one of {noun, measure, action, unit, entity} (RHL-001 §3.4)" + - "term result type ('gives') is Size" + - "term declares effect 'read disk' (RHL-001 §3.1 rule 6)" + - "lowers_to stays the unbound marker `[U]` until RHL-4 — RHL-16 does not bind lowering" + +proof_obligations: + - type: invariant + property: "Term 'disk free of' carries a contract template on disk (F6)" + formal: "contract('disk free of') exists" + applies_to: all + - type: invariant + property: "Term 'disk free of' kind is drawn from RHL's closed kind list" + formal: "kind('disk free of') ∈ {noun, measure, action, unit, entity}" + applies_to: all + +falsification_tests: + - id: FALSIFY-RHL-FLEET-DISK-FREE-OF-V2-001 + rule: "Vocabulary term contract must exist (RHL-001 F6)" + test: "test_rhl16_v2_every_term_has_an_existing_contract" + prediction: "verified" + if_fails: "Contract violation — RHL-001 F6: term 'disk free of' has no contract template" + + - id: FALSIFY-RHL-FLEET-DISK-FREE-OF-V2-002 + rule: "Vocabulary term kind is drawn from the closed list" + test: "test_rhl16_v2_carries_every_v1_term_forward" + prediction: "verified" + if_fails: "Contract violation — term 'disk free of' kind not in {noun, measure, action, unit, entity}" + +# NOT APPLICABLE, declared because `pv validate` requires the block. A vocabulary +# term contract states a property of YAML bytes on disk; Kani symbolically executes +# Rust and cannot reach a file system. Declaring a harness that does not exist is the +# practice KANIDEBT-1 was filed against, so it is marked rather than pretended. +kani_harnesses: + - id: KANI-RHL-FLEET-DISK-FREE-OF-V2-001 + obligation: PO-RHL-FLEET-DISK-FREE-OF-V2-001 + property: "NOT APPLICABLE — a vocabulary term contract is a property of YAML bytes on disk, outside Kani's reach" + bound: 1 + strategy: bounded_int + harness: none + status: declared_not_implemented + discharged_by: "test_rhl16_v2_every_term_has_an_existing_contract" + +qa_gate: + id: QA-RHL-FLEET-DISK-FREE-OF-V2 + name: "rhl fleet vocabulary v2 term: disk free of" + min_coverage: 1.0 + max_complexity: 10 + required_tests: + - test_rhl16_v2_every_term_has_an_existing_contract + - test_rhl16_v2_carries_every_v1_term_forward + - test_rhl16_v2_lowers_to_and_sigma_entity_stay_unbound diff --git a/contracts/rhl-fleet-disk-usage-of-v2.yaml b/contracts/rhl-fleet-disk-usage-of-v2.yaml new file mode 100644 index 000000000..6749fdcca --- /dev/null +++ b/contracts/rhl-fleet-disk-usage-of-v2.yaml @@ -0,0 +1,64 @@ +metadata: + version: "2.0.0" + author: "RHL-16" + description: "RHL-16 v2 contract for vocabulary term 'disk usage of' (fleet v2, kind: measure)" + crate: "ruchy" + references: + - "RHL-001 §3.4" + +equations: + disk_usage_of_shape: + formula: "kind('disk usage of') = measure ∧ gives('disk usage of') = Size" + domain: "t ∈ vocabulary fleet v2 terms named 'disk usage of'" + invariants: + - "term kind is one of {noun, measure, action, unit, entity} (RHL-001 §3.4)" + - "term result type ('gives') is Size" + - "term declares effect 'read disk' (RHL-001 §3.1 rule 6)" + - "lowers_to stays the unbound marker `[U]` until RHL-4 — RHL-16 does not bind lowering" + +proof_obligations: + - type: invariant + property: "Term 'disk usage of' carries a contract template on disk (F6)" + formal: "contract('disk usage of') exists" + applies_to: all + - type: invariant + property: "Term 'disk usage of' kind is drawn from RHL's closed kind list" + formal: "kind('disk usage of') ∈ {noun, measure, action, unit, entity}" + applies_to: all + +falsification_tests: + - id: FALSIFY-RHL-FLEET-DISK-USAGE-OF-V2-001 + rule: "Vocabulary term contract must exist (RHL-001 F6)" + test: "test_rhl16_v2_every_term_has_an_existing_contract" + prediction: "verified" + if_fails: "Contract violation — RHL-001 F6: term 'disk usage of' has no contract template" + + - id: FALSIFY-RHL-FLEET-DISK-USAGE-OF-V2-002 + rule: "Vocabulary term kind is drawn from the closed list" + test: "test_rhl16_v2_carries_every_v1_term_forward" + prediction: "verified" + if_fails: "Contract violation — term 'disk usage of' kind not in {noun, measure, action, unit, entity}" + +# NOT APPLICABLE, declared because `pv validate` requires the block. A vocabulary +# term contract states a property of YAML bytes on disk; Kani symbolically executes +# Rust and cannot reach a file system. Declaring a harness that does not exist is the +# practice KANIDEBT-1 was filed against, so it is marked rather than pretended. +kani_harnesses: + - id: KANI-RHL-FLEET-DISK-USAGE-OF-V2-001 + obligation: PO-RHL-FLEET-DISK-USAGE-OF-V2-001 + property: "NOT APPLICABLE — a vocabulary term contract is a property of YAML bytes on disk, outside Kani's reach" + bound: 1 + strategy: bounded_int + harness: none + status: declared_not_implemented + discharged_by: "test_rhl16_v2_every_term_has_an_existing_contract" + +qa_gate: + id: QA-RHL-FLEET-DISK-USAGE-OF-V2 + name: "rhl fleet vocabulary v2 term: disk usage of" + min_coverage: 1.0 + max_complexity: 10 + required_tests: + - test_rhl16_v2_every_term_has_an_existing_contract + - test_rhl16_v2_carries_every_v1_term_forward + - test_rhl16_v2_lowers_to_and_sigma_entity_stay_unbound diff --git a/contracts/rhl-fleet-gb-v2.yaml b/contracts/rhl-fleet-gb-v2.yaml new file mode 100644 index 000000000..8e887a8ae --- /dev/null +++ b/contracts/rhl-fleet-gb-v2.yaml @@ -0,0 +1,63 @@ +metadata: + version: "2.0.0" + author: "RHL-16" + description: "RHL-16 v2 contract for vocabulary term 'GB' (fleet v2, kind: unit)" + crate: "ruchy" + references: + - "RHL-001 §3.4" + +equations: + gb_shape: + formula: "kind('GB') = unit ∧ gives('GB') = Size" + domain: "t ∈ vocabulary fleet v2 terms named 'GB'" + invariants: + - "term kind is one of {noun, measure, action, unit, entity} (RHL-001 §3.4)" + - "term result type ('gives') is Size" + - "lowers_to stays the unbound marker `[U]` until RHL-4 — RHL-16 does not bind lowering" + +proof_obligations: + - type: invariant + property: "Term 'GB' carries a contract template on disk (F6)" + formal: "contract('GB') exists" + applies_to: all + - type: invariant + property: "Term 'GB' kind is drawn from RHL's closed kind list" + formal: "kind('GB') ∈ {noun, measure, action, unit, entity}" + applies_to: all + +falsification_tests: + - id: FALSIFY-RHL-FLEET-GB-V2-001 + rule: "Vocabulary term contract must exist (RHL-001 F6)" + test: "test_rhl16_v2_every_term_has_an_existing_contract" + prediction: "verified" + if_fails: "Contract violation — RHL-001 F6: term 'GB' has no contract template" + + - id: FALSIFY-RHL-FLEET-GB-V2-002 + rule: "Vocabulary term kind is drawn from the closed list" + test: "test_rhl16_v2_carries_every_v1_term_forward" + prediction: "verified" + if_fails: "Contract violation — term 'GB' kind not in {noun, measure, action, unit, entity}" + +# NOT APPLICABLE, declared because `pv validate` requires the block. A vocabulary +# term contract states a property of YAML bytes on disk; Kani symbolically executes +# Rust and cannot reach a file system. Declaring a harness that does not exist is the +# practice KANIDEBT-1 was filed against, so it is marked rather than pretended. +kani_harnesses: + - id: KANI-RHL-FLEET-GB-V2-001 + obligation: PO-RHL-FLEET-GB-V2-001 + property: "NOT APPLICABLE — a vocabulary term contract is a property of YAML bytes on disk, outside Kani's reach" + bound: 1 + strategy: bounded_int + harness: none + status: declared_not_implemented + discharged_by: "test_rhl16_v2_every_term_has_an_existing_contract" + +qa_gate: + id: QA-RHL-FLEET-GB-V2 + name: "rhl fleet vocabulary v2 term: GB" + min_coverage: 1.0 + max_complexity: 10 + required_tests: + - test_rhl16_v2_every_term_has_an_existing_contract + - test_rhl16_v2_carries_every_v1_term_forward + - test_rhl16_v2_lowers_to_and_sigma_entity_stay_unbound diff --git a/contracts/rhl-fleet-host-v2.yaml b/contracts/rhl-fleet-host-v2.yaml new file mode 100644 index 000000000..688819105 --- /dev/null +++ b/contracts/rhl-fleet-host-v2.yaml @@ -0,0 +1,64 @@ +metadata: + version: "2.0.0" + author: "RHL-16" + description: "RHL-16 v2 contract for vocabulary term 'host' (fleet v2, kind: entity)" + crate: "ruchy" + references: + - "RHL-001 §3.4" + +equations: + host_closed_world: + formula: "instances('host') = { x : x ∈ glob('machines/*/forjar.yaml') }" + domain: "t ∈ vocabulary fleet v2 terms named 'host'" + invariants: + - "term kind is entity (RHL-001 §3.4)" + - "every instance of 'host' comes from the declared closed-world source 'machines/*/forjar.yaml'" + - "an undeclared instance is a compile error naming the nearest candidate (RHL-001 §3.4)" + - "lowers_to stays the unbound marker `[U]` until RHL-4 — RHL-16 does not bind lowering" + +proof_obligations: + - type: invariant + property: "Term 'host' carries a contract template on disk (F6)" + formal: "contract('host') exists" + applies_to: all + - type: completeness + property: "Every instance of entity term 'host' is enumerable from its closed-world source" + formal: "∀x. valid('host', x) ⟺ x ∈ glob('machines/*/forjar.yaml')" + applies_to: all + +falsification_tests: + - id: FALSIFY-RHL-FLEET-HOST-V2-001 + rule: "Vocabulary term contract must exist (RHL-001 F6)" + test: "test_rhl16_v2_every_term_has_an_existing_contract" + prediction: "verified" + if_fails: "Contract violation — RHL-001 F6: term 'host' has no contract template" + + - id: FALSIFY-RHL-FLEET-HOST-V2-002 + rule: "Entity term declares its closed-world instances_from source" + test: "test_rhl16_v2_carries_every_v1_term_forward" + prediction: "verified" + if_fails: "Contract violation — entity term 'host' has no instances_from source" + +# NOT APPLICABLE, declared because `pv validate` requires the block. A vocabulary +# term contract states a property of YAML bytes on disk; Kani symbolically executes +# Rust and cannot reach a file system. Declaring a harness that does not exist is the +# practice KANIDEBT-1 was filed against, so it is marked rather than pretended. +kani_harnesses: + - id: KANI-RHL-FLEET-HOST-V2-001 + obligation: PO-RHL-FLEET-HOST-V2-001 + property: "NOT APPLICABLE — a vocabulary term contract is a property of YAML bytes on disk, outside Kani's reach" + bound: 1 + strategy: bounded_int + harness: none + status: declared_not_implemented + discharged_by: "test_rhl16_v2_every_term_has_an_existing_contract" + +qa_gate: + id: QA-RHL-FLEET-HOST-V2 + name: "rhl fleet vocabulary v2 term: host" + min_coverage: 1.0 + max_complexity: 10 + required_tests: + - test_rhl16_v2_every_term_has_an_existing_contract + - test_rhl16_v2_carries_every_v1_term_forward + - test_rhl16_v2_lowers_to_and_sigma_entity_stay_unbound diff --git a/contracts/rhl-fleet-hour-v2.yaml b/contracts/rhl-fleet-hour-v2.yaml new file mode 100644 index 000000000..1bb170e55 --- /dev/null +++ b/contracts/rhl-fleet-hour-v2.yaml @@ -0,0 +1,63 @@ +metadata: + version: "2.0.0" + author: "RHL-16" + description: "RHL-16 v2 contract for vocabulary term 'hour' (fleet v2, kind: unit)" + crate: "ruchy" + references: + - "RHL-001 §3.4" + +equations: + hour_shape: + formula: "kind('hour') = unit ∧ gives('hour') = Duration" + domain: "t ∈ vocabulary fleet v2 terms named 'hour'" + invariants: + - "term kind is one of {noun, measure, action, unit, entity} (RHL-001 §3.4)" + - "term result type ('gives') is Duration" + - "lowers_to stays the unbound marker `[U]` until RHL-4 — RHL-16 does not bind lowering" + +proof_obligations: + - type: invariant + property: "Term 'hour' carries a contract template on disk (F6)" + formal: "contract('hour') exists" + applies_to: all + - type: invariant + property: "Term 'hour' kind is drawn from RHL's closed kind list" + formal: "kind('hour') ∈ {noun, measure, action, unit, entity}" + applies_to: all + +falsification_tests: + - id: FALSIFY-RHL-FLEET-HOUR-V2-001 + rule: "Vocabulary term contract must exist (RHL-001 F6)" + test: "test_rhl16_v2_every_term_has_an_existing_contract" + prediction: "verified" + if_fails: "Contract violation — RHL-001 F6: term 'hour' has no contract template" + + - id: FALSIFY-RHL-FLEET-HOUR-V2-002 + rule: "Vocabulary term kind is drawn from the closed list" + test: "test_rhl16_v2_carries_every_v1_term_forward" + prediction: "verified" + if_fails: "Contract violation — term 'hour' kind not in {noun, measure, action, unit, entity}" + +# NOT APPLICABLE, declared because `pv validate` requires the block. A vocabulary +# term contract states a property of YAML bytes on disk; Kani symbolically executes +# Rust and cannot reach a file system. Declaring a harness that does not exist is the +# practice KANIDEBT-1 was filed against, so it is marked rather than pretended. +kani_harnesses: + - id: KANI-RHL-FLEET-HOUR-V2-001 + obligation: PO-RHL-FLEET-HOUR-V2-001 + property: "NOT APPLICABLE — a vocabulary term contract is a property of YAML bytes on disk, outside Kani's reach" + bound: 1 + strategy: bounded_int + harness: none + status: declared_not_implemented + discharged_by: "test_rhl16_v2_every_term_has_an_existing_contract" + +qa_gate: + id: QA-RHL-FLEET-HOUR-V2 + name: "rhl fleet vocabulary v2 term: hour" + min_coverage: 1.0 + max_complexity: 10 + required_tests: + - test_rhl16_v2_every_term_has_an_existing_contract + - test_rhl16_v2_carries_every_v1_term_forward + - test_rhl16_v2_lowers_to_and_sigma_entity_stay_unbound diff --git a/contracts/rhl-fleet-pin-status-of-v2.yaml b/contracts/rhl-fleet-pin-status-of-v2.yaml new file mode 100644 index 000000000..7a9b72a97 --- /dev/null +++ b/contracts/rhl-fleet-pin-status-of-v2.yaml @@ -0,0 +1,64 @@ +metadata: + version: "2.0.0" + author: "RHL-16" + description: "RHL-16 v2 contract for vocabulary term 'pin status of' (fleet v2, kind: measure)" + crate: "ruchy" + references: + - "RHL-001 §3.4" + +equations: + pin_status_of_shape: + formula: "kind('pin status of') = measure ∧ gives('pin status of') = Text" + domain: "t ∈ vocabulary fleet v2 terms named 'pin status of'" + invariants: + - "term kind is one of {noun, measure, action, unit, entity} (RHL-001 §3.4)" + - "term result type ('gives') is Text" + - "term declares effect 'read host' (RHL-001 §3.1 rule 6)" + - "lowers_to stays the unbound marker `[U]` until RHL-4 — RHL-16 does not bind lowering" + +proof_obligations: + - type: invariant + property: "Term 'pin status of' carries a contract template on disk (F6)" + formal: "contract('pin status of') exists" + applies_to: all + - type: invariant + property: "Term 'pin status of' kind is drawn from RHL's closed kind list" + formal: "kind('pin status of') ∈ {noun, measure, action, unit, entity}" + applies_to: all + +falsification_tests: + - id: FALSIFY-RHL-FLEET-PIN-STATUS-OF-V2-001 + rule: "Vocabulary term contract must exist (RHL-001 F6)" + test: "test_rhl16_v2_every_term_has_an_existing_contract" + prediction: "verified" + if_fails: "Contract violation — RHL-001 F6: term 'pin status of' has no contract template" + + - id: FALSIFY-RHL-FLEET-PIN-STATUS-OF-V2-002 + rule: "Vocabulary term kind is drawn from the closed list" + test: "test_rhl16_v2_carries_every_v1_term_forward" + prediction: "verified" + if_fails: "Contract violation — term 'pin status of' kind not in {noun, measure, action, unit, entity}" + +# NOT APPLICABLE, declared because `pv validate` requires the block. A vocabulary +# term contract states a property of YAML bytes on disk; Kani symbolically executes +# Rust and cannot reach a file system. Declaring a harness that does not exist is the +# practice KANIDEBT-1 was filed against, so it is marked rather than pretended. +kani_harnesses: + - id: KANI-RHL-FLEET-PIN-STATUS-OF-V2-001 + obligation: PO-RHL-FLEET-PIN-STATUS-OF-V2-001 + property: "NOT APPLICABLE — a vocabulary term contract is a property of YAML bytes on disk, outside Kani's reach" + bound: 1 + strategy: bounded_int + harness: none + status: declared_not_implemented + discharged_by: "test_rhl16_v2_every_term_has_an_existing_contract" + +qa_gate: + id: QA-RHL-FLEET-PIN-STATUS-OF-V2 + name: "rhl fleet vocabulary v2 term: pin status of" + min_coverage: 1.0 + max_complexity: 10 + required_tests: + - test_rhl16_v2_every_term_has_an_existing_contract + - test_rhl16_v2_carries_every_v1_term_forward + - test_rhl16_v2_lowers_to_and_sigma_entity_stay_unbound diff --git a/contracts/rhl-fleet-runner-load-of-v2.yaml b/contracts/rhl-fleet-runner-load-of-v2.yaml new file mode 100644 index 000000000..011a94126 --- /dev/null +++ b/contracts/rhl-fleet-runner-load-of-v2.yaml @@ -0,0 +1,64 @@ +metadata: + version: "2.0.0" + author: "RHL-16" + description: "RHL-16 v2 contract for vocabulary term 'runner load of' (fleet v2, kind: measure)" + crate: "ruchy" + references: + - "RHL-001 §3.4" + +equations: + runner_load_of_shape: + formula: "kind('runner load of') = measure ∧ gives('runner load of') = Percent" + domain: "t ∈ vocabulary fleet v2 terms named 'runner load of'" + invariants: + - "term kind is one of {noun, measure, action, unit, entity} (RHL-001 §3.4)" + - "term result type ('gives') is Percent" + - "term declares effect 'read runner' (RHL-001 §3.1 rule 6)" + - "lowers_to stays the unbound marker `[U]` until RHL-4 — RHL-16 does not bind lowering" + +proof_obligations: + - type: invariant + property: "Term 'runner load of' carries a contract template on disk (F6)" + formal: "contract('runner load of') exists" + applies_to: all + - type: invariant + property: "Term 'runner load of' kind is drawn from RHL's closed kind list" + formal: "kind('runner load of') ∈ {noun, measure, action, unit, entity}" + applies_to: all + +falsification_tests: + - id: FALSIFY-RHL-FLEET-RUNNER-LOAD-OF-V2-001 + rule: "Vocabulary term contract must exist (RHL-001 F6)" + test: "test_rhl16_v2_every_term_has_an_existing_contract" + prediction: "verified" + if_fails: "Contract violation — RHL-001 F6: term 'runner load of' has no contract template" + + - id: FALSIFY-RHL-FLEET-RUNNER-LOAD-OF-V2-002 + rule: "Vocabulary term kind is drawn from the closed list" + test: "test_rhl16_v2_carries_every_v1_term_forward" + prediction: "verified" + if_fails: "Contract violation — term 'runner load of' kind not in {noun, measure, action, unit, entity}" + +# NOT APPLICABLE, declared because `pv validate` requires the block. A vocabulary +# term contract states a property of YAML bytes on disk; Kani symbolically executes +# Rust and cannot reach a file system. Declaring a harness that does not exist is the +# practice KANIDEBT-1 was filed against, so it is marked rather than pretended. +kani_harnesses: + - id: KANI-RHL-FLEET-RUNNER-LOAD-OF-V2-001 + obligation: PO-RHL-FLEET-RUNNER-LOAD-OF-V2-001 + property: "NOT APPLICABLE — a vocabulary term contract is a property of YAML bytes on disk, outside Kani's reach" + bound: 1 + strategy: bounded_int + harness: none + status: declared_not_implemented + discharged_by: "test_rhl16_v2_every_term_has_an_existing_contract" + +qa_gate: + id: QA-RHL-FLEET-RUNNER-LOAD-OF-V2 + name: "rhl fleet vocabulary v2 term: runner load of" + min_coverage: 1.0 + max_complexity: 10 + required_tests: + - test_rhl16_v2_every_term_has_an_existing_contract + - test_rhl16_v2_carries_every_v1_term_forward + - test_rhl16_v2_lowers_to_and_sigma_entity_stay_unbound diff --git a/contracts/rhl-fleet-runner-v2.yaml b/contracts/rhl-fleet-runner-v2.yaml new file mode 100644 index 000000000..e23a1f3ca --- /dev/null +++ b/contracts/rhl-fleet-runner-v2.yaml @@ -0,0 +1,64 @@ +metadata: + version: "2.0.0" + author: "RHL-16" + description: "RHL-16 v2 contract for vocabulary term 'runner' (fleet v2, kind: entity)" + crate: "ruchy" + references: + - "RHL-001 §3.4" + +equations: + runner_closed_world: + formula: "instances('runner') = { x : x ∈ glob('fleet/runners/*.yaml') }" + domain: "t ∈ vocabulary fleet v2 terms named 'runner'" + invariants: + - "term kind is entity (RHL-001 §3.4)" + - "every instance of 'runner' comes from the declared closed-world source 'fleet/runners/*.yaml'" + - "an undeclared instance is a compile error naming the nearest candidate (RHL-001 §3.4)" + - "lowers_to stays the unbound marker `[U]` until RHL-4 — RHL-16 does not bind lowering" + +proof_obligations: + - type: invariant + property: "Term 'runner' carries a contract template on disk (F6)" + formal: "contract('runner') exists" + applies_to: all + - type: completeness + property: "Every instance of entity term 'runner' is enumerable from its closed-world source" + formal: "∀x. valid('runner', x) ⟺ x ∈ glob('fleet/runners/*.yaml')" + applies_to: all + +falsification_tests: + - id: FALSIFY-RHL-FLEET-RUNNER-V2-001 + rule: "Vocabulary term contract must exist (RHL-001 F6)" + test: "test_rhl16_v2_every_term_has_an_existing_contract" + prediction: "verified" + if_fails: "Contract violation — RHL-001 F6: term 'runner' has no contract template" + + - id: FALSIFY-RHL-FLEET-RUNNER-V2-002 + rule: "Entity term declares its closed-world instances_from source" + test: "test_rhl16_v2_carries_every_v1_term_forward" + prediction: "verified" + if_fails: "Contract violation — entity term 'runner' has no instances_from source" + +# NOT APPLICABLE, declared because `pv validate` requires the block. A vocabulary +# term contract states a property of YAML bytes on disk; Kani symbolically executes +# Rust and cannot reach a file system. Declaring a harness that does not exist is the +# practice KANIDEBT-1 was filed against, so it is marked rather than pretended. +kani_harnesses: + - id: KANI-RHL-FLEET-RUNNER-V2-001 + obligation: PO-RHL-FLEET-RUNNER-V2-001 + property: "NOT APPLICABLE — a vocabulary term contract is a property of YAML bytes on disk, outside Kani's reach" + bound: 1 + strategy: bounded_int + harness: none + status: declared_not_implemented + discharged_by: "test_rhl16_v2_every_term_has_an_existing_contract" + +qa_gate: + id: QA-RHL-FLEET-RUNNER-V2 + name: "rhl fleet vocabulary v2 term: runner" + min_coverage: 1.0 + max_complexity: 10 + required_tests: + - test_rhl16_v2_every_term_has_an_existing_contract + - test_rhl16_v2_carries_every_v1_term_forward + - test_rhl16_v2_lowers_to_and_sigma_entity_stay_unbound diff --git a/contracts/rhl-tickets-file-ticket-v2.yaml b/contracts/rhl-tickets-file-ticket-v2.yaml new file mode 100644 index 000000000..71b39ffff --- /dev/null +++ b/contracts/rhl-tickets-file-ticket-v2.yaml @@ -0,0 +1,69 @@ +metadata: + version: "2.0.0" + author: "RHL-16" + description: "RHL-16 v2 contract for vocabulary term 'file ticket' (tickets v2, kind: action)" + crate: "ruchy" + references: + - "RHL-001 §3.4" + +equations: + file_ticket_shape: + formula: "kind('file ticket') = action ∧ gives('file ticket') = Ticket" + domain: "t ∈ vocabulary tickets v2 terms named 'file ticket'" + invariants: + - "term kind is one of {noun, measure, action, unit, entity} (RHL-001 §3.4)" + - "term result type ('gives') is Ticket" + - "term declares effect 'write tickets' (RHL-001 §3.1 rule 6)" + - "v2: attributes('file ticket') = [title: Text, label: Text]; each line of its `with … end` block names one of them and gives a value of its type (RHL-16)" + - "lowers_to stays the unbound marker `[U]` until RHL-4 — RHL-16 does not bind lowering" + +proof_obligations: + - type: invariant + property: "Term 'file ticket' carries a contract template on disk (F6)" + formal: "contract('file ticket') exists" + applies_to: all + - type: invariant + property: "Term 'file ticket' kind is drawn from RHL's closed kind list" + formal: "kind('file ticket') ∈ {noun, measure, action, unit, entity}" + applies_to: all + +falsification_tests: + - id: FALSIFY-RHL-TICKETS-FILE-TICKET-V2-001 + rule: "Vocabulary term contract must exist (RHL-001 F6)" + test: "test_rhl16_v2_every_term_has_an_existing_contract" + prediction: "verified" + if_fails: "Contract violation — RHL-001 F6: term 'file ticket' has no contract template" + + - id: FALSIFY-RHL-TICKETS-FILE-TICKET-V2-002 + rule: "Vocabulary term kind is drawn from the closed list" + test: "test_rhl16_v2_carries_every_v1_term_forward" + prediction: "verified" + if_fails: "Contract violation — term 'file ticket' kind not in {noun, measure, action, unit, entity}" + +# NOT APPLICABLE, declared because `pv validate` requires the block. A vocabulary +# term contract states a property of YAML bytes on disk; Kani symbolically executes +# Rust and cannot reach a file system. Declaring a harness that does not exist is the +# practice KANIDEBT-1 was filed against, so it is marked rather than pretended. +kani_harnesses: + - id: KANI-RHL-TICKETS-FILE-TICKET-V2-001 + obligation: PO-RHL-TICKETS-FILE-TICKET-V2-001 + property: "NOT APPLICABLE — a vocabulary term contract is a property of YAML bytes on disk, outside Kani's reach" + bound: 1 + strategy: bounded_int + harness: none + status: declared_not_implemented + discharged_by: "test_rhl16_v2_every_term_has_an_existing_contract" + +qa_gate: + id: QA-RHL-TICKETS-FILE-TICKET-V2 + name: "rhl tickets vocabulary v2 term: file ticket" + min_coverage: 1.0 + max_complexity: 10 + required_tests: + - test_rhl16_v2_every_term_has_an_existing_contract + - test_rhl16_v2_carries_every_v1_term_forward + - test_rhl16_v2_lowers_to_and_sigma_entity_stay_unbound + - test_rhl16_v2_file_ticket_has_attributes_and_ticket_count_has_no_effect + - test_rhl16_attribute_declared_is_ok + - test_rhl16_attribute_unknown_is_v001_listing_the_declared_attributes + - test_rhl16_attribute_wrong_type_is_t002 diff --git a/contracts/rhl-tickets-label-ticket-v2.yaml b/contracts/rhl-tickets-label-ticket-v2.yaml new file mode 100644 index 000000000..8a9ed345d --- /dev/null +++ b/contracts/rhl-tickets-label-ticket-v2.yaml @@ -0,0 +1,64 @@ +metadata: + version: "2.0.0" + author: "RHL-16" + description: "RHL-16 v2 contract for vocabulary term 'label ticket' (tickets v2, kind: action)" + crate: "ruchy" + references: + - "RHL-001 §3.4" + +equations: + label_ticket_shape: + formula: "kind('label ticket') = action ∧ gives('label ticket') = Ticket" + domain: "t ∈ vocabulary tickets v2 terms named 'label ticket'" + invariants: + - "term kind is one of {noun, measure, action, unit, entity} (RHL-001 §3.4)" + - "term result type ('gives') is Ticket" + - "term declares effect 'write tickets' (RHL-001 §3.1 rule 6)" + - "lowers_to stays the unbound marker `[U]` until RHL-4 — RHL-16 does not bind lowering" + +proof_obligations: + - type: invariant + property: "Term 'label ticket' carries a contract template on disk (F6)" + formal: "contract('label ticket') exists" + applies_to: all + - type: invariant + property: "Term 'label ticket' kind is drawn from RHL's closed kind list" + formal: "kind('label ticket') ∈ {noun, measure, action, unit, entity}" + applies_to: all + +falsification_tests: + - id: FALSIFY-RHL-TICKETS-LABEL-TICKET-V2-001 + rule: "Vocabulary term contract must exist (RHL-001 F6)" + test: "test_rhl16_v2_every_term_has_an_existing_contract" + prediction: "verified" + if_fails: "Contract violation — RHL-001 F6: term 'label ticket' has no contract template" + + - id: FALSIFY-RHL-TICKETS-LABEL-TICKET-V2-002 + rule: "Vocabulary term kind is drawn from the closed list" + test: "test_rhl16_v2_carries_every_v1_term_forward" + prediction: "verified" + if_fails: "Contract violation — term 'label ticket' kind not in {noun, measure, action, unit, entity}" + +# NOT APPLICABLE, declared because `pv validate` requires the block. A vocabulary +# term contract states a property of YAML bytes on disk; Kani symbolically executes +# Rust and cannot reach a file system. Declaring a harness that does not exist is the +# practice KANIDEBT-1 was filed against, so it is marked rather than pretended. +kani_harnesses: + - id: KANI-RHL-TICKETS-LABEL-TICKET-V2-001 + obligation: PO-RHL-TICKETS-LABEL-TICKET-V2-001 + property: "NOT APPLICABLE — a vocabulary term contract is a property of YAML bytes on disk, outside Kani's reach" + bound: 1 + strategy: bounded_int + harness: none + status: declared_not_implemented + discharged_by: "test_rhl16_v2_every_term_has_an_existing_contract" + +qa_gate: + id: QA-RHL-TICKETS-LABEL-TICKET-V2 + name: "rhl tickets vocabulary v2 term: label ticket" + min_coverage: 1.0 + max_complexity: 10 + required_tests: + - test_rhl16_v2_every_term_has_an_existing_contract + - test_rhl16_v2_carries_every_v1_term_forward + - test_rhl16_v2_lowers_to_and_sigma_entity_stay_unbound diff --git a/contracts/rhl-tickets-repo-v2.yaml b/contracts/rhl-tickets-repo-v2.yaml new file mode 100644 index 000000000..e57dc7493 --- /dev/null +++ b/contracts/rhl-tickets-repo-v2.yaml @@ -0,0 +1,64 @@ +metadata: + version: "2.0.0" + author: "RHL-16" + description: "RHL-16 v2 contract for vocabulary term 'repo' (tickets v2, kind: entity)" + crate: "ruchy" + references: + - "RHL-001 §3.4" + +equations: + repo_closed_world: + formula: "instances('repo') = { x : x ∈ glob('org/repos/*.yaml') }" + domain: "t ∈ vocabulary tickets v2 terms named 'repo'" + invariants: + - "term kind is entity (RHL-001 §3.4)" + - "every instance of 'repo' comes from the declared closed-world source 'org/repos/*.yaml'" + - "an undeclared instance is a compile error naming the nearest candidate (RHL-001 §3.4)" + - "lowers_to stays the unbound marker `[U]` until RHL-4 — RHL-16 does not bind lowering" + +proof_obligations: + - type: invariant + property: "Term 'repo' carries a contract template on disk (F6)" + formal: "contract('repo') exists" + applies_to: all + - type: completeness + property: "Every instance of entity term 'repo' is enumerable from its closed-world source" + formal: "∀x. valid('repo', x) ⟺ x ∈ glob('org/repos/*.yaml')" + applies_to: all + +falsification_tests: + - id: FALSIFY-RHL-TICKETS-REPO-V2-001 + rule: "Vocabulary term contract must exist (RHL-001 F6)" + test: "test_rhl16_v2_every_term_has_an_existing_contract" + prediction: "verified" + if_fails: "Contract violation — RHL-001 F6: term 'repo' has no contract template" + + - id: FALSIFY-RHL-TICKETS-REPO-V2-002 + rule: "Entity term declares its closed-world instances_from source" + test: "test_rhl16_v2_carries_every_v1_term_forward" + prediction: "verified" + if_fails: "Contract violation — entity term 'repo' has no instances_from source" + +# NOT APPLICABLE, declared because `pv validate` requires the block. A vocabulary +# term contract states a property of YAML bytes on disk; Kani symbolically executes +# Rust and cannot reach a file system. Declaring a harness that does not exist is the +# practice KANIDEBT-1 was filed against, so it is marked rather than pretended. +kani_harnesses: + - id: KANI-RHL-TICKETS-REPO-V2-001 + obligation: PO-RHL-TICKETS-REPO-V2-001 + property: "NOT APPLICABLE — a vocabulary term contract is a property of YAML bytes on disk, outside Kani's reach" + bound: 1 + strategy: bounded_int + harness: none + status: declared_not_implemented + discharged_by: "test_rhl16_v2_every_term_has_an_existing_contract" + +qa_gate: + id: QA-RHL-TICKETS-REPO-V2 + name: "rhl tickets vocabulary v2 term: repo" + min_coverage: 1.0 + max_complexity: 10 + required_tests: + - test_rhl16_v2_every_term_has_an_existing_contract + - test_rhl16_v2_carries_every_v1_term_forward + - test_rhl16_v2_lowers_to_and_sigma_entity_stay_unbound diff --git a/contracts/rhl-tickets-ticket-count-v2.yaml b/contracts/rhl-tickets-ticket-count-v2.yaml new file mode 100644 index 000000000..af1ddbb67 --- /dev/null +++ b/contracts/rhl-tickets-ticket-count-v2.yaml @@ -0,0 +1,66 @@ +metadata: + version: "2.0.0" + author: "RHL-16" + description: "RHL-16 v2 contract (new in v2) for vocabulary term 'ticket count' (tickets v2, kind: measure)" + crate: "ruchy" + references: + - "RHL-001 §3.4" + +equations: + ticket_count_shape: + formula: "kind('ticket count') = measure ∧ gives('ticket count') = Count" + domain: "t ∈ vocabulary tickets v2 terms named 'ticket count'" + invariants: + - "term kind is one of {noun, measure, action, unit, entity} (RHL-001 §3.4)" + - "term result type ('gives') is Count" + - "term declares NO effect: it is the number of tickets this run files, read from the run's own plan, so no `may` line is needed (RHL-16)" + - "term takes no argument" + - "lowers_to stays the unbound marker `[U]` until RHL-4 — RHL-16 does not bind lowering" + +proof_obligations: + - type: invariant + property: "Term 'ticket count' carries a contract template on disk (F6)" + formal: "contract('ticket count') exists" + applies_to: all + - type: invariant + property: "Term 'ticket count' kind is drawn from RHL's closed kind list" + formal: "kind('ticket count') ∈ {noun, measure, action, unit, entity}" + applies_to: all + +falsification_tests: + - id: FALSIFY-RHL-TICKETS-TICKET-COUNT-V2-001 + rule: "Vocabulary term contract must exist (RHL-001 F6)" + test: "test_rhl16_v2_every_term_has_an_existing_contract" + prediction: "verified" + if_fails: "Contract violation — RHL-001 F6: term 'ticket count' has no contract template" + + - id: FALSIFY-RHL-TICKETS-TICKET-COUNT-V2-002 + rule: "Vocabulary term kind is drawn from the closed list" + test: "test_rhl16_v2_file_ticket_has_attributes_and_ticket_count_has_no_effect" + prediction: "verified" + if_fails: "Contract violation — term 'ticket count' kind not in {noun, measure, action, unit, entity}" + +# NOT APPLICABLE, declared because `pv validate` requires the block. A vocabulary +# term contract states a property of YAML bytes on disk; Kani symbolically executes +# Rust and cannot reach a file system. Declaring a harness that does not exist is the +# practice KANIDEBT-1 was filed against, so it is marked rather than pretended. +kani_harnesses: + - id: KANI-RHL-TICKETS-TICKET-COUNT-V2-001 + obligation: PO-RHL-TICKETS-TICKET-COUNT-V2-001 + property: "NOT APPLICABLE — a vocabulary term contract is a property of YAML bytes on disk, outside Kani's reach" + bound: 1 + strategy: bounded_int + harness: none + status: declared_not_implemented + discharged_by: "test_rhl16_v2_every_term_has_an_existing_contract" + +qa_gate: + id: QA-RHL-TICKETS-TICKET-COUNT-V2 + name: "rhl tickets vocabulary v2 term: ticket count" + min_coverage: 1.0 + max_complexity: 10 + required_tests: + - test_rhl16_v2_every_term_has_an_existing_contract + - test_rhl16_v2_file_ticket_has_attributes_and_ticket_count_has_no_effect + - test_rhl16_v2_lowers_to_and_sigma_entity_stay_unbound + - test_rhl16_measure_without_effect_needs_no_may_line diff --git a/contracts/rhl-tickets-ticket-label-v2.yaml b/contracts/rhl-tickets-ticket-label-v2.yaml new file mode 100644 index 000000000..5243bbd64 --- /dev/null +++ b/contracts/rhl-tickets-ticket-label-v2.yaml @@ -0,0 +1,63 @@ +metadata: + version: "2.0.0" + author: "RHL-16" + description: "RHL-16 v2 contract for vocabulary term 'ticket label' (tickets v2, kind: noun)" + crate: "ruchy" + references: + - "RHL-001 §3.4" + +equations: + ticket_label_shape: + formula: "kind('ticket label') = noun ∧ gives('ticket label') = Text" + domain: "t ∈ vocabulary tickets v2 terms named 'ticket label'" + invariants: + - "term kind is one of {noun, measure, action, unit, entity} (RHL-001 §3.4)" + - "term result type ('gives') is Text" + - "lowers_to stays the unbound marker `[U]` until RHL-4 — RHL-16 does not bind lowering" + +proof_obligations: + - type: invariant + property: "Term 'ticket label' carries a contract template on disk (F6)" + formal: "contract('ticket label') exists" + applies_to: all + - type: invariant + property: "Term 'ticket label' kind is drawn from RHL's closed kind list" + formal: "kind('ticket label') ∈ {noun, measure, action, unit, entity}" + applies_to: all + +falsification_tests: + - id: FALSIFY-RHL-TICKETS-TICKET-LABEL-V2-001 + rule: "Vocabulary term contract must exist (RHL-001 F6)" + test: "test_rhl16_v2_every_term_has_an_existing_contract" + prediction: "verified" + if_fails: "Contract violation — RHL-001 F6: term 'ticket label' has no contract template" + + - id: FALSIFY-RHL-TICKETS-TICKET-LABEL-V2-002 + rule: "Vocabulary term kind is drawn from the closed list" + test: "test_rhl16_v2_carries_every_v1_term_forward" + prediction: "verified" + if_fails: "Contract violation — term 'ticket label' kind not in {noun, measure, action, unit, entity}" + +# NOT APPLICABLE, declared because `pv validate` requires the block. A vocabulary +# term contract states a property of YAML bytes on disk; Kani symbolically executes +# Rust and cannot reach a file system. Declaring a harness that does not exist is the +# practice KANIDEBT-1 was filed against, so it is marked rather than pretended. +kani_harnesses: + - id: KANI-RHL-TICKETS-TICKET-LABEL-V2-001 + obligation: PO-RHL-TICKETS-TICKET-LABEL-V2-001 + property: "NOT APPLICABLE — a vocabulary term contract is a property of YAML bytes on disk, outside Kani's reach" + bound: 1 + strategy: bounded_int + harness: none + status: declared_not_implemented + discharged_by: "test_rhl16_v2_every_term_has_an_existing_contract" + +qa_gate: + id: QA-RHL-TICKETS-TICKET-LABEL-V2 + name: "rhl tickets vocabulary v2 term: ticket label" + min_coverage: 1.0 + max_complexity: 10 + required_tests: + - test_rhl16_v2_every_term_has_an_existing_contract + - test_rhl16_v2_carries_every_v1_term_forward + - test_rhl16_v2_lowers_to_and_sigma_entity_stay_unbound diff --git a/contracts/rhl-tickets-ticket-title-v2.yaml b/contracts/rhl-tickets-ticket-title-v2.yaml new file mode 100644 index 000000000..4fae74088 --- /dev/null +++ b/contracts/rhl-tickets-ticket-title-v2.yaml @@ -0,0 +1,63 @@ +metadata: + version: "2.0.0" + author: "RHL-16" + description: "RHL-16 v2 contract for vocabulary term 'ticket title' (tickets v2, kind: noun)" + crate: "ruchy" + references: + - "RHL-001 §3.4" + +equations: + ticket_title_shape: + formula: "kind('ticket title') = noun ∧ gives('ticket title') = Text" + domain: "t ∈ vocabulary tickets v2 terms named 'ticket title'" + invariants: + - "term kind is one of {noun, measure, action, unit, entity} (RHL-001 §3.4)" + - "term result type ('gives') is Text" + - "lowers_to stays the unbound marker `[U]` until RHL-4 — RHL-16 does not bind lowering" + +proof_obligations: + - type: invariant + property: "Term 'ticket title' carries a contract template on disk (F6)" + formal: "contract('ticket title') exists" + applies_to: all + - type: invariant + property: "Term 'ticket title' kind is drawn from RHL's closed kind list" + formal: "kind('ticket title') ∈ {noun, measure, action, unit, entity}" + applies_to: all + +falsification_tests: + - id: FALSIFY-RHL-TICKETS-TICKET-TITLE-V2-001 + rule: "Vocabulary term contract must exist (RHL-001 F6)" + test: "test_rhl16_v2_every_term_has_an_existing_contract" + prediction: "verified" + if_fails: "Contract violation — RHL-001 F6: term 'ticket title' has no contract template" + + - id: FALSIFY-RHL-TICKETS-TICKET-TITLE-V2-002 + rule: "Vocabulary term kind is drawn from the closed list" + test: "test_rhl16_v2_carries_every_v1_term_forward" + prediction: "verified" + if_fails: "Contract violation — term 'ticket title' kind not in {noun, measure, action, unit, entity}" + +# NOT APPLICABLE, declared because `pv validate` requires the block. A vocabulary +# term contract states a property of YAML bytes on disk; Kani symbolically executes +# Rust and cannot reach a file system. Declaring a harness that does not exist is the +# practice KANIDEBT-1 was filed against, so it is marked rather than pretended. +kani_harnesses: + - id: KANI-RHL-TICKETS-TICKET-TITLE-V2-001 + obligation: PO-RHL-TICKETS-TICKET-TITLE-V2-001 + property: "NOT APPLICABLE — a vocabulary term contract is a property of YAML bytes on disk, outside Kani's reach" + bound: 1 + strategy: bounded_int + harness: none + status: declared_not_implemented + discharged_by: "test_rhl16_v2_every_term_has_an_existing_contract" + +qa_gate: + id: QA-RHL-TICKETS-TICKET-TITLE-V2 + name: "rhl tickets vocabulary v2 term: ticket title" + min_coverage: 1.0 + max_complexity: 10 + required_tests: + - test_rhl16_v2_every_term_has_an_existing_contract + - test_rhl16_v2_carries_every_v1_term_forward + - test_rhl16_v2_lowers_to_and_sigma_entity_stay_unbound diff --git a/contracts/rhl-tickets-tickets-filed-in-v2.yaml b/contracts/rhl-tickets-tickets-filed-in-v2.yaml new file mode 100644 index 000000000..4ae6f5a69 --- /dev/null +++ b/contracts/rhl-tickets-tickets-filed-in-v2.yaml @@ -0,0 +1,64 @@ +metadata: + version: "2.0.0" + author: "RHL-16" + description: "RHL-16 v2 contract for vocabulary term 'tickets filed in' (tickets v2, kind: measure)" + crate: "ruchy" + references: + - "RHL-001 §3.4" + +equations: + tickets_filed_in_shape: + formula: "kind('tickets filed in') = measure ∧ gives('tickets filed in') = Count" + domain: "t ∈ vocabulary tickets v2 terms named 'tickets filed in'" + invariants: + - "term kind is one of {noun, measure, action, unit, entity} (RHL-001 §3.4)" + - "term result type ('gives') is Count" + - "term declares effect 'read tickets' (RHL-001 §3.1 rule 6)" + - "lowers_to stays the unbound marker `[U]` until RHL-4 — RHL-16 does not bind lowering" + +proof_obligations: + - type: invariant + property: "Term 'tickets filed in' carries a contract template on disk (F6)" + formal: "contract('tickets filed in') exists" + applies_to: all + - type: invariant + property: "Term 'tickets filed in' kind is drawn from RHL's closed kind list" + formal: "kind('tickets filed in') ∈ {noun, measure, action, unit, entity}" + applies_to: all + +falsification_tests: + - id: FALSIFY-RHL-TICKETS-TICKETS-FILED-IN-V2-001 + rule: "Vocabulary term contract must exist (RHL-001 F6)" + test: "test_rhl16_v2_every_term_has_an_existing_contract" + prediction: "verified" + if_fails: "Contract violation — RHL-001 F6: term 'tickets filed in' has no contract template" + + - id: FALSIFY-RHL-TICKETS-TICKETS-FILED-IN-V2-002 + rule: "Vocabulary term kind is drawn from the closed list" + test: "test_rhl16_v2_carries_every_v1_term_forward" + prediction: "verified" + if_fails: "Contract violation — term 'tickets filed in' kind not in {noun, measure, action, unit, entity}" + +# NOT APPLICABLE, declared because `pv validate` requires the block. A vocabulary +# term contract states a property of YAML bytes on disk; Kani symbolically executes +# Rust and cannot reach a file system. Declaring a harness that does not exist is the +# practice KANIDEBT-1 was filed against, so it is marked rather than pretended. +kani_harnesses: + - id: KANI-RHL-TICKETS-TICKETS-FILED-IN-V2-001 + obligation: PO-RHL-TICKETS-TICKETS-FILED-IN-V2-001 + property: "NOT APPLICABLE — a vocabulary term contract is a property of YAML bytes on disk, outside Kani's reach" + bound: 1 + strategy: bounded_int + harness: none + status: declared_not_implemented + discharged_by: "test_rhl16_v2_every_term_has_an_existing_contract" + +qa_gate: + id: QA-RHL-TICKETS-TICKETS-FILED-IN-V2 + name: "rhl tickets vocabulary v2 term: tickets filed in" + min_coverage: 1.0 + max_complexity: 10 + required_tests: + - test_rhl16_v2_every_term_has_an_existing_contract + - test_rhl16_v2_carries_every_v1_term_forward + - test_rhl16_v2_lowers_to_and_sigma_entity_stay_unbound diff --git a/src/rhl/check/stmts.rs b/src/rhl/check/stmts.rs index 5a96dbcf2..8a605ba81 100644 --- a/src/rhl/check/stmts.rs +++ b/src/rhl/check/stmts.rs @@ -1,9 +1,12 @@ //! Statements: headers, bindings, actions and blocks (spec RHL-001 §3.3). -use super::checker::{app_span, longest_term, Checker}; +use super::checker::{app_span, longest_term, words_text, Checker}; +use super::lexicon::{LexTerm, Lexicon}; +use super::near; use super::types::{duration_code, mismatch, Ty, Val}; use crate::rhl::codes; -use crate::rhl::tree::{Action, App, Cond, Effect, Phrase, Quantity, Stmt, StmtKind}; +use crate::rhl::diag::Candidate; +use crate::rhl::tree::{Action, App, Cond, Effect, Phrase, Quantity, Span, Stmt, StmtKind, Word}; use crate::rhl::vocab::{Param, TermKind}; impl Checker<'_> { @@ -146,11 +149,73 @@ impl Checker<'_> { fn action(&mut self, a: &Action) { self.action_head(a); - if let Some(with) = &a.with { - self.body(with, false); + let Some(with) = &a.with else { return }; + match attributed_action(self.lex, a) { + Some(term) => self.attribute_block(term, with), + None => self.body(with, false), } } + /// The `with` block of an action that declares `attributes:` (v2, + /// RHL-16): each action-shaped line is an attribute assignment; any other + /// statement is checked as usual. + fn attribute_block(&mut self, term: &LexTerm, with: &[Stmt]) { + for s in with { + match &s.kind { + StmtKind::Action(line) => self.attribute_line(term, line, s.span), + _ => self.stmt(s, false), + } + } + } + + /// ` `: the attribute must be declared by `term` and the + /// value must have its type. + fn attribute_line(&mut self, term: &LexTerm, line: &Action, span: Span) { + let action = &term.term.term; + let App::Call { phrase, args } = &line.head else { + let message = format!("a line in the `with` block of `{action}` names an attribute"); + return self.error(codes::T002, app_span(&line.head), message); + }; + if line.target.is_some() || line.with.is_some() { + let message = format!("the attribute `{}` takes only a value", phrase.text()); + return self.error(codes::T002, span, message); + } + let attrs = &term.term.attributes; + match attrs.iter().find(|p| p.name == phrase.text()) { + Some(p) => self.check_args(&p.name, std::slice::from_ref(p), args, phrase.span), + None => self.unknown_attribute(term, &phrase.words), + } + } + + /// An attribute `term` does not declare: V002/V003 for a near miss, as for + /// any unknown word. The attribute namespace is closed and small, so a V001 + /// (nothing near) lists every declared attribute as a candidate, without a fix. + fn unknown_attribute(&mut self, term: &LexTerm, words: &[Word]) { + let action = &term.term.term; + let pool: Vec<(String, String)> = term + .term + .attributes + .iter() + .map(|p| (p.name.clone(), term.vocab.clone())) + .collect(); + let at = self.diags.len(); + self.unknown(words, &pool, &format!("attribute of `{action}`")); + let Some(d) = self.diags.get_mut(at).filter(|d| d.code == codes::V001) else { + return; + }; + let written = words_text(words); + d.candidates = pool + .iter() + .map(|(name, vocabulary)| Candidate { + term: name.clone(), + vocabulary: vocabulary.clone(), + distance: near::distance(&written, name), + }) + .collect(); + let names: Vec = pool.iter().map(|(n, _)| format!("`{n}`")).collect(); + d.message = format!("{}; `{action}` has {}", d.message, names.join(", ")); + } + /// The head of an action statement must be an action term. fn action_head(&mut self, a: &Action) { let App::Call { phrase, args } = &a.head else { @@ -205,6 +270,16 @@ impl Checker<'_> { } } +/// The action term that heads `a`, when it declares `attributes:` (v2). +fn attributed_action<'l>(lex: &'l Lexicon, a: &Action) -> Option<&'l LexTerm> { + let App::Call { phrase, .. } = &a.head else { + return None; + }; + let (k, t) = longest_term(lex, phrase)?; + let whole = k == phrase.words.len() && t.term.kind == TermKind::Action; + (whole && !t.term.attributes.is_empty()).then_some(t) +} + /// How a diagnostic names a value: `a bare number` or its type. fn describe(v: &Val) -> String { if v.bare { diff --git a/src/rhl/check_tests.rs b/src/rhl/check_tests.rs index 72aad3eb2..290d8e2e3 100644 --- a/src/rhl/check_tests.rs +++ b/src/rhl/check_tests.rs @@ -774,3 +774,309 @@ fn test_rhl_1_check_known_name_with_extra_words_is_t002_not_a_no_op_fix() { .flat_map(|d| &d.candidates) .all(|c| c.distance > 0)); } + +// ---------------------------------------------------------------- RHL-16: v2 vocabulary and corpus + +/// Where RHL-16 put the v2 corpus. v1 (`docs/rhl/breaks/{valid,planted}`) +/// stays byte-identical (§3.4 freeze). +const V2_BREAKS: &str = "docs/rhl/breaks/v2"; + +/// The v2 planted breaks whose expectation contradicts the v2 vocabulary. The +/// RHL-16 DONE WHEN: this list is empty, and a test holds it so. +const KNOWN_CORPUS_DEFECTS_V2: [(&str, &str); 0] = []; + +fn v2_breaks() -> Vec { + dirs(&root().join(V2_BREAKS).join("planted")) + .iter() + .flat_map(|class| dirs(class)) + .map(|d| load_break(&d)) + .collect() +} + +fn v2_valid_programs() -> Vec<(String, String, Report)> { + dirs(&root().join(V2_BREAKS).join("valid")) + .iter() + .map(|p| { + let name = p + .strip_prefix(root()) + .expect("under root") + .display() + .to_string(); + let source = read(p); + let report = check(&name, &source, Some(&root())); + (name, source, report) + }) + .collect() +} + +fn v2_vocab(name: &str) -> crate::rhl::vocab::Vocabulary { + crate::rhl::vocab::load(&root(), name, 2).unwrap_or_else(|e| panic!("{name} v2: {e}")) +} + +#[test] +fn test_rhl16_v2_every_term_has_an_existing_contract() { + for name in ["fleet", "tickets"] { + let v = v2_vocab(name); + let missing: Vec<&str> = crate::rhl::vocab::missing_contracts(&root(), &v) + .iter() + .map(|t| t.term.as_str()) + .collect(); + assert!(missing.is_empty(), "{name} v2: no contract for {missing:?}"); + for t in &v.terms { + assert!( + t.contract.starts_with(&format!("contracts/rhl-{name}-")) + && t.contract.ends_with("-v2.yaml"), + "{name} v2 `{}`: contract `{}` is not a v2 template", + t.term, + t.contract + ); + } + } +} + +#[test] +fn test_rhl16_v2_carries_every_v1_term_forward() { + for name in ["fleet", "tickets"] { + let v1 = crate::rhl::vocab::load(&root(), name, 1).expect("v1 loads"); + let v2 = v2_vocab(name); + for old in &v1.terms { + let new = v2 + .terms + .iter() + .find(|t| t.term == old.term) + .unwrap_or_else(|| panic!("{name} v2 drops `{}`", old.term)); + let shape = |t: &crate::rhl::vocab::Term| { + ( + t.kind, + t.takes.clone(), + t.gives.clone(), + t.effect.clone(), + t.instances_from.clone(), + ) + }; + assert_eq!(shape(new), shape(old), "{name} v2 changed `{}`", old.term); + } + } +} + +#[test] +fn test_rhl16_v2_lowers_to_and_sigma_entity_stay_unbound() { + for name in ["fleet", "tickets"] { + let path = root().join(format!("vocab/{name}-v2.yaml")); + let yaml: serde_yaml_ng::Value = + serde_yaml_ng::from_str(&read(&path)).expect("v2 vocabulary is YAML"); + assert_eq!(yaml["sigma_entity"].as_str(), Some("[U]"), "{name} v2"); + let terms = yaml["terms"].as_sequence().expect("terms"); + for t in terms { + assert_eq!(t["lowers_to"].as_str(), Some("[U]"), "{name} v2: {t:?}"); + } + } +} + +#[test] +fn test_rhl16_v2_file_ticket_has_attributes_and_ticket_count_has_no_effect() { + use crate::rhl::vocab::{Param, TermKind}; + let v = v2_vocab("tickets"); + let find = |s: &str| v.terms.iter().find(|t| t.term == s).expect(s).clone(); + let text = |n: &str| Param { + name: n.to_string(), + ty: "Text".to_string(), + }; + assert_eq!( + find("file ticket").attributes, + vec![text("title"), text("label")] + ); + let count = find("ticket count"); + assert_eq!( + (count.kind, count.gives.as_deref(), count.effect.as_deref()), + (TermKind::Measure, Some("Count"), None) + ); + assert!(count.takes.is_empty()); + for t in v.terms.iter().filter(|t| t.term != "file ticket") { + assert!(t.attributes.is_empty(), "`{}` has attributes", t.term); + } +} + +#[test] +fn test_rhl16_v2_valid_programs_check_clean() { + let programs = v2_valid_programs(); + assert_eq!(programs.len(), 12, "the v2 valid corpus has 12 programs"); + for (name, source, report) in &programs { + assert!( + source.starts_with("use vocabulary fleet v2\nuse vocabulary tickets v2\n"), + "{name}: not a v2 program" + ); + assert_eq!( + report.diagnostics, + Vec::::new(), + "{name}: a v2 valid program must check clean (Amendment A3: only unverified notes)" + ); + assert_eq!(report.exit_code(), 0, "{name}"); + } +} + +#[test] +fn test_rhl16_v2_valid_programs_are_in_fmt_normal_form() { + for (name, source, _) in v2_valid_programs() { + let formatted = crate::rhl::fmt::format_source(&source).expect("parses"); + assert_eq!(formatted, source, "{name}: not in fmt normal form"); + } +} + +#[test] +fn test_rhl16_v2_planted_breaks_are_six_classes_of_twelve_on_v2_bases() { + let breaks = v2_breaks(); + assert_eq!(breaks.len(), 72, "the v2 planted corpus has 6 classes x 12"); + let classes: BTreeSet<&str> = breaks.iter().map(|b| b.spec.class.as_str()).collect(); + let want = BTreeSet::from([ + "typo'd term", + "missing end", + "wrong unit", + "wrong type", + "undeclared effect", + "two-candidate typo", + ]); + assert_eq!(classes, want); + for b in &breaks { + assert!( + b.spec.base.starts_with("docs/rhl/breaks/v2/valid/"), + "{}: base `{}` is not a v2 valid program", + b.name, + b.spec.base + ); + assert_ne!(b.base, b.broken, "{}: not a mutation", b.name); + let dir = b.name.rsplit('/').next().unwrap_or(""); + assert!(b.spec.base.ends_with(&format!("{dir}.rhl")), "{}", b.name); + } +} + +#[test] +fn test_rhl16_v2_planted_breaks_all_yield_their_code_on_the_mutated_line() { + let failures: Vec = v2_breaks().iter().filter_map(break_failure).collect(); + assert!( + failures.is_empty(), + "{} v2 planted break(s) fail the gate:\n{}", + failures.len(), + failures.join("\n") + ); +} + +#[test] +fn test_rhl16_v2_every_planted_code_is_new_relative_to_its_base() { + let not_new: Vec = v2_breaks() + .iter() + .filter(|b| !is_new(b)) + .map(|b| format!("{} ({})", b.name, b.spec.expected_code)) + .collect(); + assert!( + not_new.is_empty(), + "v2 codes that are not new: {not_new:#?}" + ); +} + +#[test] +fn test_rhl16_v2_has_no_known_corpus_defects_and_v1_keeps_its_two() { + assert!(KNOWN_CORPUS_DEFECTS_V2.is_empty()); + let v1: Vec<&str> = KNOWN_CORPUS_DEFECTS.iter().map(|(n, _)| *n).collect(); + assert_eq!( + v1, + vec![ + "docs/rhl/breaks/planted/wrong-unit/04-gx13-pin-check", + "docs/rhl/breaks/planted/wrong-unit/09-gx18-pin-check-b", + ], + "the v1 exception list is frozen with v1" + ); +} + +// ---------------------------------------------------------------- RHL-16: attributes and effect-free measures + +/// A job with `body` (already indented) using both v2 vocabularies. +fn job_v2(body: &str) -> String { + format!("use vocabulary fleet v2\nuse vocabulary tickets v2\n\njob \"t\"\n{body}end\n") +} + +const FILE_V2: &str = " may write tickets\n file ticket in repo \"paiml/infra\" with\n title \"disk low\"\n label \"fleet\"\n end\n"; + +#[test] +fn test_rhl16_attribute_declared_is_ok() { + let report = check_src(&job_v2(FILE_V2)); + assert_eq!(report.diagnostics, Vec::::new()); + assert_eq!(report.exit_code(), 0); +} + +#[test] +fn test_rhl16_attribute_unknown_is_v001_listing_the_declared_attributes() { + let src = job_v2(&FILE_V2.replace("label \"fleet\"", "priority \"high\"")); + let report = check_src(&src); + assert_eq!( + codes_of(&report), + vec![codes::V001], + "{:#?}", + report.diagnostics + ); + let d = &report.diagnostics[0]; + assert_eq!(d.span.line, 8); + let names: Vec<&str> = d.candidates.iter().map(|c| c.term.as_str()).collect(); + assert_eq!(names, vec!["title", "label"]); + assert!(d.fixes.is_empty(), "V001 has no fix"); + assert_eq!( + d.expected.as_ref().and_then(|e| e.kind.as_deref()), + Some("attribute of `file ticket`") + ); +} + +#[test] +fn test_rhl16_attribute_near_miss_is_v002_with_a_safe_fix() { + let report = check_src(&job_v2(&FILE_V2.replace("title \"", "titl \""))); + let d = only_v002(&report); + assert_eq!(d.candidates[0].term, "title"); + assert_eq!(d.candidates[0].vocabulary, "tickets v2"); + assert!(d.fixes.iter().any(|f| f.safe)); +} + +#[test] +fn test_rhl16_attribute_wrong_type_is_t002() { + for value in ["5", "5 GB", "\"a\" \"b\""] { + let src = job_v2(&FILE_V2.replace("\"disk low\"", value)); + let report = check_src(&src); + assert_eq!(codes_of(&report), vec![codes::T002], "title {value}"); + assert_eq!(report.diagnostics[0].span.line, 7, "title {value}"); + } + let bare = check_src(&job_v2(&FILE_V2.replace("title \"disk low\"", "title"))); + assert_eq!(codes_of(&bare), vec![codes::T002]); +} + +#[test] +fn test_rhl16_attribute_line_takes_no_target() { + let src = job_v2(&FILE_V2.replace("title \"disk low\"", "title in repo \"x\"")); + let report = check_src(&src); + assert_eq!( + codes_of(&report), + vec![codes::T002], + "{:#?}", + report.diagnostics + ); +} + +#[test] +fn test_rhl16_action_without_attributes_keeps_the_v1_behaviour() { + let v1 = job(FILE_V2); + let report = check_src(&v1); + assert_eq!(codes_of(&report), vec![codes::V001, codes::V001]); + for d in &report.diagnostics { + assert!(d.candidates.is_empty()); + assert_eq!( + d.expected.as_ref().and_then(|e| e.kind.as_deref()), + Some("term or let name") + ); + } +} + +#[test] +fn test_rhl16_measure_without_effect_needs_no_may_line() { + let body = " expect ticket count is at most 1\n\n example \"one\"\n then ticket count is 1\n end\n"; + let report = check_src(&job_v2(body)); + assert_eq!(report.diagnostics, Vec::::new()); + let wrong_unit = check_src(&job_v2(&body.replace("at most 1", "at most 1 GB"))); + assert_eq!(codes_of(&wrong_unit), vec![codes::T001]); +} diff --git a/src/rhl/vocab.rs b/src/rhl/vocab.rs index 3ae9fb072..dad5ea24a 100644 --- a/src/rhl/vocab.rs +++ b/src/rhl/vocab.rs @@ -54,6 +54,11 @@ pub struct Term { /// For an entity, the glob that declares its instances. #[serde(default)] pub instances_from: Option, + /// For an action (v2, RHL-16): the attributes its `with … end` block may + /// set, each by name with a value of its type. Empty means the action's + /// block is checked as ordinary statements, as in v1. + #[serde(default)] + pub attributes: Vec, /// The contract template, relative to the vocabulary root. A term that /// omits it deserializes with an empty path, which names no file, so the /// vocabulary is refused with RHL-C001 by name (§3.4) rather than failing diff --git a/vocab/fleet-v2.yaml b/vocab/fleet-v2.yaml new file mode 100644 index 000000000..3923d33c9 --- /dev/null +++ b/vocab/fleet-v2.yaml @@ -0,0 +1,69 @@ +# RHL-16 vocabulary v2 (RHL-001 §3.4; roadmap row RHL-16). v1 stays frozen and +# byte-identical beside this file; v2 carries every v1 term forward unchanged, +# each with its own v2 contract template, and adds only what the v2 valid +# programs need (docs/rhl/rhl-16-v2.md). `lowers_to` stays the unbound marker +# `[U]` on every term until RHL-4 binds it. +# +# Sigma binding: unchanged from v1 — see vocab/fleet-v1.yaml's header comment +# (paiml-ontology.md ONT-001 v4.9 §3.1). The vocabulary FILE is the Sigma +# entity and its TYPE is not bound; binding it is not a ruchy decision. +sigma_entity: "[U]" + +vocabulary: fleet +version: 2 +terms: + - term: disk free of + kind: measure # noun | measure | action | unit | entity + takes: [{name: path, type: Text}] + gives: Size + effect: read disk + lowers_to: "[U]" # bound at RHL-4 + contract: contracts/rhl-fleet-disk-free-of-v2.yaml + + - term: disk usage of + kind: measure + takes: [{name: path, type: Text}] + gives: Size + effect: read disk + lowers_to: "[U]" + contract: contracts/rhl-fleet-disk-usage-of-v2.yaml + + - term: host + kind: entity + instances_from: machines/*/forjar.yaml # closed world: only declared hosts exist + lowers_to: "[U]" + contract: contracts/rhl-fleet-host-v2.yaml + + - term: runner + kind: entity + instances_from: fleet/runners/*.yaml # closed world: only declared runners exist + lowers_to: "[U]" + contract: contracts/rhl-fleet-runner-v2.yaml + + - term: runner load of + kind: measure + takes: [{name: runner, type: Text}] + gives: Percent + effect: read runner + lowers_to: "[U]" + contract: contracts/rhl-fleet-runner-load-of-v2.yaml + + - term: pin status of + kind: measure + takes: [{name: host, type: Text}] + gives: Text + effect: read host + lowers_to: "[U]" + contract: contracts/rhl-fleet-pin-status-of-v2.yaml + + - term: GB + kind: unit + gives: Size + lowers_to: "[U]" + contract: contracts/rhl-fleet-gb-v2.yaml + + - term: hour + kind: unit + gives: Duration + lowers_to: "[U]" + contract: contracts/rhl-fleet-hour-v2.yaml diff --git a/vocab/tickets-v2.yaml b/vocab/tickets-v2.yaml new file mode 100644 index 000000000..ecb0890ba --- /dev/null +++ b/vocab/tickets-v2.yaml @@ -0,0 +1,67 @@ +# RHL-16 vocabulary v2 (RHL-001 §3.4; roadmap row RHL-16). v1 stays frozen and +# byte-identical beside this file; v2 carries every v1 term forward unchanged, +# each with its own v2 contract template, and adds only what the v2 valid +# programs need (docs/rhl/rhl-16-v2.md). `lowers_to` stays the unbound marker +# `[U]` on every term until RHL-4 binds it. +# +# Sigma binding: unchanged from v1 — see vocab/fleet-v1.yaml's header comment +# (paiml-ontology.md ONT-001 v4.9 §3.1). The vocabulary FILE is the Sigma +# entity and its TYPE is not bound; binding it is not a ruchy decision. +sigma_entity: "[U]" + +vocabulary: tickets +version: 2 +terms: + - term: file ticket + kind: action + takes: [{name: repo, type: Text}] + gives: Ticket + effect: write tickets + # v2: the lines of a `with … end` block after this action are attribute + # assignments; each names one of these and gives a value of its type. + attributes: [{name: title, type: Text}, {name: label, type: Text}] + lowers_to: "[U]" # bound at RHL-4 + contract: contracts/rhl-tickets-file-ticket-v2.yaml + + - term: label ticket + kind: action + takes: [{name: ticket, type: Ticket}, {name: label, type: Text}] + gives: Ticket + effect: write tickets + lowers_to: "[U]" + contract: contracts/rhl-tickets-label-ticket-v2.yaml + + - term: repo + kind: entity + instances_from: org/repos/*.yaml # closed world: only declared org repos exist + lowers_to: "[U]" + contract: contracts/rhl-tickets-repo-v2.yaml + + - term: ticket title + kind: noun + gives: Text + lowers_to: "[U]" + contract: contracts/rhl-tickets-ticket-title-v2.yaml + + - term: ticket label + kind: noun + gives: Text + lowers_to: "[U]" + contract: contracts/rhl-tickets-ticket-label-v2.yaml + + - term: tickets filed in + kind: measure + takes: [{name: repo, type: Text}] + gives: Count + effect: read tickets + lowers_to: "[U]" + contract: contracts/rhl-tickets-tickets-filed-in-v2.yaml + + # v2: the number of tickets this run files. It is read from the run's own + # plan (RHL-4 binds it), not from the world, so it has NO effect and needs + # no `may` line. + - term: ticket count + kind: measure + gives: Count + lowers_to: "[U]" + contract: contracts/rhl-tickets-ticket-count-v2.yaml From 01d57f7c421758d3f2348dcda349ac7e805a5d09 Mon Sep 17 00:00:00 2001 From: Noah Gift Date: Wed, 23 Sep 2026 13:30:47 +0200 Subject: [PATCH 2/9] RHL-16: v2 valid and planted break corpus under docs/rhl/breaks/v2 - 12 valid programs rewritten for fleet v2 / tickets v2: each declares exactly the effects it uses; 03/08 compare runner load of with a Percent, 04/09 pin status of with a Text, 05/10 tickets filed in with a count; disk-usage programs file on high usage. Each checks clean (only unverified host/repo notes, Amendment A3) and is in fmt normal form. - 72 planted breaks, the same six classes, regenerated from the v2 bases with v1's break.yaml schema; wrong-unit/04 and /09 mutate every 1 hour -> 1 GB. Every code lands on its mutated line and is new relative to its base. - v1 (docs/rhl/breaks/valid, planted) is untouched. Pmat-Ticket: RHLGA-1 Co-Authored-By: Claude Opus 5.5 (1M context) --- .../missing-end/01-gx10-disk-watch/break.yaml | 4 +++ .../missing-end/01-gx10-disk-watch/broken.rhl | 24 ++++++++++++++++++ .../02-gx11-docker-disk-watch/break.yaml | 4 +++ .../02-gx11-docker-disk-watch/broken.rhl | 24 ++++++++++++++++++ .../03-gx12-runner-load-watch/break.yaml | 4 +++ .../03-gx12-runner-load-watch/broken.rhl | 24 ++++++++++++++++++ .../missing-end/04-gx13-pin-check/break.yaml | 4 +++ .../missing-end/04-gx13-pin-check/broken.rhl | 24 ++++++++++++++++++ .../05-gx14-ticket-sweep/break.yaml | 4 +++ .../05-gx14-ticket-sweep/broken.rhl | 24 ++++++++++++++++++ .../06-gx15-data-disk-watch/break.yaml | 4 +++ .../06-gx15-data-disk-watch/broken.rhl | 24 ++++++++++++++++++ .../07-gx16-root-disk-watch/break.yaml | 4 +++ .../07-gx16-root-disk-watch/broken.rhl | 24 ++++++++++++++++++ .../08-gx17-runner-load-alert/break.yaml | 4 +++ .../08-gx17-runner-load-alert/broken.rhl | 24 ++++++++++++++++++ .../09-gx18-pin-check-b/break.yaml | 4 +++ .../09-gx18-pin-check-b/broken.rhl | 24 ++++++++++++++++++ .../10-gx19-ticket-sweep-b/break.yaml | 4 +++ .../10-gx19-ticket-sweep-b/broken.rhl | 24 ++++++++++++++++++ .../11-gx20-var-disk-watch/break.yaml | 4 +++ .../11-gx20-var-disk-watch/broken.rhl | 24 ++++++++++++++++++ .../12-gx21-home-disk-watch/break.yaml | 4 +++ .../12-gx21-home-disk-watch/broken.rhl | 24 ++++++++++++++++++ .../01-gx10-disk-watch/break.yaml | 7 ++++++ .../01-gx10-disk-watch/broken.rhl | 25 +++++++++++++++++++ .../02-gx11-docker-disk-watch/break.yaml | 7 ++++++ .../02-gx11-docker-disk-watch/broken.rhl | 25 +++++++++++++++++++ .../03-gx12-runner-load-watch/break.yaml | 7 ++++++ .../03-gx12-runner-load-watch/broken.rhl | 25 +++++++++++++++++++ .../04-gx13-pin-check/break.yaml | 7 ++++++ .../04-gx13-pin-check/broken.rhl | 25 +++++++++++++++++++ .../05-gx14-ticket-sweep/break.yaml | 7 ++++++ .../05-gx14-ticket-sweep/broken.rhl | 25 +++++++++++++++++++ .../06-gx15-data-disk-watch/break.yaml | 7 ++++++ .../06-gx15-data-disk-watch/broken.rhl | 25 +++++++++++++++++++ .../07-gx16-root-disk-watch/break.yaml | 7 ++++++ .../07-gx16-root-disk-watch/broken.rhl | 25 +++++++++++++++++++ .../08-gx17-runner-load-alert/break.yaml | 7 ++++++ .../08-gx17-runner-load-alert/broken.rhl | 25 +++++++++++++++++++ .../09-gx18-pin-check-b/break.yaml | 7 ++++++ .../09-gx18-pin-check-b/broken.rhl | 25 +++++++++++++++++++ .../10-gx19-ticket-sweep-b/break.yaml | 7 ++++++ .../10-gx19-ticket-sweep-b/broken.rhl | 25 +++++++++++++++++++ .../11-gx20-var-disk-watch/break.yaml | 7 ++++++ .../11-gx20-var-disk-watch/broken.rhl | 25 +++++++++++++++++++ .../12-gx21-home-disk-watch/break.yaml | 7 ++++++ .../12-gx21-home-disk-watch/broken.rhl | 25 +++++++++++++++++++ .../typo-term/01-gx10-disk-watch/break.yaml | 4 +++ .../typo-term/01-gx10-disk-watch/broken.rhl | 25 +++++++++++++++++++ .../02-gx11-docker-disk-watch/break.yaml | 4 +++ .../02-gx11-docker-disk-watch/broken.rhl | 25 +++++++++++++++++++ .../03-gx12-runner-load-watch/break.yaml | 4 +++ .../03-gx12-runner-load-watch/broken.rhl | 25 +++++++++++++++++++ .../typo-term/04-gx13-pin-check/break.yaml | 4 +++ .../typo-term/04-gx13-pin-check/broken.rhl | 25 +++++++++++++++++++ .../typo-term/05-gx14-ticket-sweep/break.yaml | 4 +++ .../typo-term/05-gx14-ticket-sweep/broken.rhl | 25 +++++++++++++++++++ .../06-gx15-data-disk-watch/break.yaml | 4 +++ .../06-gx15-data-disk-watch/broken.rhl | 25 +++++++++++++++++++ .../07-gx16-root-disk-watch/break.yaml | 4 +++ .../07-gx16-root-disk-watch/broken.rhl | 25 +++++++++++++++++++ .../08-gx17-runner-load-alert/break.yaml | 4 +++ .../08-gx17-runner-load-alert/broken.rhl | 25 +++++++++++++++++++ .../typo-term/09-gx18-pin-check-b/break.yaml | 4 +++ .../typo-term/09-gx18-pin-check-b/broken.rhl | 25 +++++++++++++++++++ .../10-gx19-ticket-sweep-b/break.yaml | 4 +++ .../10-gx19-ticket-sweep-b/broken.rhl | 25 +++++++++++++++++++ .../11-gx20-var-disk-watch/break.yaml | 4 +++ .../11-gx20-var-disk-watch/broken.rhl | 25 +++++++++++++++++++ .../12-gx21-home-disk-watch/break.yaml | 4 +++ .../12-gx21-home-disk-watch/broken.rhl | 25 +++++++++++++++++++ .../01-gx10-disk-watch/break.yaml | 4 +++ .../01-gx10-disk-watch/broken.rhl | 24 ++++++++++++++++++ .../02-gx11-docker-disk-watch/break.yaml | 4 +++ .../02-gx11-docker-disk-watch/broken.rhl | 24 ++++++++++++++++++ .../03-gx12-runner-load-watch/break.yaml | 4 +++ .../03-gx12-runner-load-watch/broken.rhl | 24 ++++++++++++++++++ .../04-gx13-pin-check/break.yaml | 4 +++ .../04-gx13-pin-check/broken.rhl | 24 ++++++++++++++++++ .../05-gx14-ticket-sweep/break.yaml | 4 +++ .../05-gx14-ticket-sweep/broken.rhl | 24 ++++++++++++++++++ .../06-gx15-data-disk-watch/break.yaml | 4 +++ .../06-gx15-data-disk-watch/broken.rhl | 24 ++++++++++++++++++ .../07-gx16-root-disk-watch/break.yaml | 4 +++ .../07-gx16-root-disk-watch/broken.rhl | 24 ++++++++++++++++++ .../08-gx17-runner-load-alert/break.yaml | 4 +++ .../08-gx17-runner-load-alert/broken.rhl | 24 ++++++++++++++++++ .../09-gx18-pin-check-b/break.yaml | 4 +++ .../09-gx18-pin-check-b/broken.rhl | 24 ++++++++++++++++++ .../10-gx19-ticket-sweep-b/break.yaml | 4 +++ .../10-gx19-ticket-sweep-b/broken.rhl | 24 ++++++++++++++++++ .../11-gx20-var-disk-watch/break.yaml | 4 +++ .../11-gx20-var-disk-watch/broken.rhl | 24 ++++++++++++++++++ .../12-gx21-home-disk-watch/break.yaml | 4 +++ .../12-gx21-home-disk-watch/broken.rhl | 24 ++++++++++++++++++ .../wrong-type/01-gx10-disk-watch/break.yaml | 4 +++ .../wrong-type/01-gx10-disk-watch/broken.rhl | 25 +++++++++++++++++++ .../02-gx11-docker-disk-watch/break.yaml | 4 +++ .../02-gx11-docker-disk-watch/broken.rhl | 25 +++++++++++++++++++ .../03-gx12-runner-load-watch/break.yaml | 4 +++ .../03-gx12-runner-load-watch/broken.rhl | 25 +++++++++++++++++++ .../wrong-type/04-gx13-pin-check/break.yaml | 4 +++ .../wrong-type/04-gx13-pin-check/broken.rhl | 25 +++++++++++++++++++ .../05-gx14-ticket-sweep/break.yaml | 4 +++ .../05-gx14-ticket-sweep/broken.rhl | 25 +++++++++++++++++++ .../06-gx15-data-disk-watch/break.yaml | 4 +++ .../06-gx15-data-disk-watch/broken.rhl | 25 +++++++++++++++++++ .../07-gx16-root-disk-watch/break.yaml | 4 +++ .../07-gx16-root-disk-watch/broken.rhl | 25 +++++++++++++++++++ .../08-gx17-runner-load-alert/break.yaml | 4 +++ .../08-gx17-runner-load-alert/broken.rhl | 25 +++++++++++++++++++ .../wrong-type/09-gx18-pin-check-b/break.yaml | 4 +++ .../wrong-type/09-gx18-pin-check-b/broken.rhl | 25 +++++++++++++++++++ .../10-gx19-ticket-sweep-b/break.yaml | 4 +++ .../10-gx19-ticket-sweep-b/broken.rhl | 25 +++++++++++++++++++ .../11-gx20-var-disk-watch/break.yaml | 4 +++ .../11-gx20-var-disk-watch/broken.rhl | 25 +++++++++++++++++++ .../12-gx21-home-disk-watch/break.yaml | 4 +++ .../12-gx21-home-disk-watch/broken.rhl | 25 +++++++++++++++++++ .../wrong-unit/01-gx10-disk-watch/break.yaml | 4 +++ .../wrong-unit/01-gx10-disk-watch/broken.rhl | 25 +++++++++++++++++++ .../02-gx11-docker-disk-watch/break.yaml | 4 +++ .../02-gx11-docker-disk-watch/broken.rhl | 25 +++++++++++++++++++ .../03-gx12-runner-load-watch/break.yaml | 4 +++ .../03-gx12-runner-load-watch/broken.rhl | 25 +++++++++++++++++++ .../wrong-unit/04-gx13-pin-check/break.yaml | 4 +++ .../wrong-unit/04-gx13-pin-check/broken.rhl | 25 +++++++++++++++++++ .../05-gx14-ticket-sweep/break.yaml | 4 +++ .../05-gx14-ticket-sweep/broken.rhl | 25 +++++++++++++++++++ .../06-gx15-data-disk-watch/break.yaml | 4 +++ .../06-gx15-data-disk-watch/broken.rhl | 25 +++++++++++++++++++ .../07-gx16-root-disk-watch/break.yaml | 4 +++ .../07-gx16-root-disk-watch/broken.rhl | 25 +++++++++++++++++++ .../08-gx17-runner-load-alert/break.yaml | 4 +++ .../08-gx17-runner-load-alert/broken.rhl | 25 +++++++++++++++++++ .../wrong-unit/09-gx18-pin-check-b/break.yaml | 4 +++ .../wrong-unit/09-gx18-pin-check-b/broken.rhl | 25 +++++++++++++++++++ .../10-gx19-ticket-sweep-b/break.yaml | 4 +++ .../10-gx19-ticket-sweep-b/broken.rhl | 25 +++++++++++++++++++ .../11-gx20-var-disk-watch/break.yaml | 4 +++ .../11-gx20-var-disk-watch/broken.rhl | 25 +++++++++++++++++++ .../12-gx21-home-disk-watch/break.yaml | 4 +++ .../12-gx21-home-disk-watch/broken.rhl | 25 +++++++++++++++++++ .../breaks/v2/valid/01-gx10-disk-watch.rhl | 25 +++++++++++++++++++ .../v2/valid/02-gx11-docker-disk-watch.rhl | 25 +++++++++++++++++++ .../v2/valid/03-gx12-runner-load-watch.rhl | 25 +++++++++++++++++++ .../rhl/breaks/v2/valid/04-gx13-pin-check.rhl | 25 +++++++++++++++++++ .../breaks/v2/valid/05-gx14-ticket-sweep.rhl | 25 +++++++++++++++++++ .../v2/valid/06-gx15-data-disk-watch.rhl | 25 +++++++++++++++++++ .../v2/valid/07-gx16-root-disk-watch.rhl | 25 +++++++++++++++++++ .../v2/valid/08-gx17-runner-load-alert.rhl | 25 +++++++++++++++++++ .../breaks/v2/valid/09-gx18-pin-check-b.rhl | 25 +++++++++++++++++++ .../v2/valid/10-gx19-ticket-sweep-b.rhl | 25 +++++++++++++++++++ .../v2/valid/11-gx20-var-disk-watch.rhl | 25 +++++++++++++++++++ .../v2/valid/12-gx21-home-disk-watch.rhl | 25 +++++++++++++++++++ 156 files changed, 2400 insertions(+) create mode 100644 docs/rhl/breaks/v2/planted/missing-end/01-gx10-disk-watch/break.yaml create mode 100644 docs/rhl/breaks/v2/planted/missing-end/01-gx10-disk-watch/broken.rhl create mode 100644 docs/rhl/breaks/v2/planted/missing-end/02-gx11-docker-disk-watch/break.yaml create mode 100644 docs/rhl/breaks/v2/planted/missing-end/02-gx11-docker-disk-watch/broken.rhl create mode 100644 docs/rhl/breaks/v2/planted/missing-end/03-gx12-runner-load-watch/break.yaml create mode 100644 docs/rhl/breaks/v2/planted/missing-end/03-gx12-runner-load-watch/broken.rhl create mode 100644 docs/rhl/breaks/v2/planted/missing-end/04-gx13-pin-check/break.yaml create mode 100644 docs/rhl/breaks/v2/planted/missing-end/04-gx13-pin-check/broken.rhl create mode 100644 docs/rhl/breaks/v2/planted/missing-end/05-gx14-ticket-sweep/break.yaml create mode 100644 docs/rhl/breaks/v2/planted/missing-end/05-gx14-ticket-sweep/broken.rhl create mode 100644 docs/rhl/breaks/v2/planted/missing-end/06-gx15-data-disk-watch/break.yaml create mode 100644 docs/rhl/breaks/v2/planted/missing-end/06-gx15-data-disk-watch/broken.rhl create mode 100644 docs/rhl/breaks/v2/planted/missing-end/07-gx16-root-disk-watch/break.yaml create mode 100644 docs/rhl/breaks/v2/planted/missing-end/07-gx16-root-disk-watch/broken.rhl create mode 100644 docs/rhl/breaks/v2/planted/missing-end/08-gx17-runner-load-alert/break.yaml create mode 100644 docs/rhl/breaks/v2/planted/missing-end/08-gx17-runner-load-alert/broken.rhl create mode 100644 docs/rhl/breaks/v2/planted/missing-end/09-gx18-pin-check-b/break.yaml create mode 100644 docs/rhl/breaks/v2/planted/missing-end/09-gx18-pin-check-b/broken.rhl create mode 100644 docs/rhl/breaks/v2/planted/missing-end/10-gx19-ticket-sweep-b/break.yaml create mode 100644 docs/rhl/breaks/v2/planted/missing-end/10-gx19-ticket-sweep-b/broken.rhl create mode 100644 docs/rhl/breaks/v2/planted/missing-end/11-gx20-var-disk-watch/break.yaml create mode 100644 docs/rhl/breaks/v2/planted/missing-end/11-gx20-var-disk-watch/broken.rhl create mode 100644 docs/rhl/breaks/v2/planted/missing-end/12-gx21-home-disk-watch/break.yaml create mode 100644 docs/rhl/breaks/v2/planted/missing-end/12-gx21-home-disk-watch/broken.rhl create mode 100644 docs/rhl/breaks/v2/planted/two-candidate-typo/01-gx10-disk-watch/break.yaml create mode 100644 docs/rhl/breaks/v2/planted/two-candidate-typo/01-gx10-disk-watch/broken.rhl create mode 100644 docs/rhl/breaks/v2/planted/two-candidate-typo/02-gx11-docker-disk-watch/break.yaml create mode 100644 docs/rhl/breaks/v2/planted/two-candidate-typo/02-gx11-docker-disk-watch/broken.rhl create mode 100644 docs/rhl/breaks/v2/planted/two-candidate-typo/03-gx12-runner-load-watch/break.yaml create mode 100644 docs/rhl/breaks/v2/planted/two-candidate-typo/03-gx12-runner-load-watch/broken.rhl create mode 100644 docs/rhl/breaks/v2/planted/two-candidate-typo/04-gx13-pin-check/break.yaml create mode 100644 docs/rhl/breaks/v2/planted/two-candidate-typo/04-gx13-pin-check/broken.rhl create mode 100644 docs/rhl/breaks/v2/planted/two-candidate-typo/05-gx14-ticket-sweep/break.yaml create mode 100644 docs/rhl/breaks/v2/planted/two-candidate-typo/05-gx14-ticket-sweep/broken.rhl create mode 100644 docs/rhl/breaks/v2/planted/two-candidate-typo/06-gx15-data-disk-watch/break.yaml create mode 100644 docs/rhl/breaks/v2/planted/two-candidate-typo/06-gx15-data-disk-watch/broken.rhl create mode 100644 docs/rhl/breaks/v2/planted/two-candidate-typo/07-gx16-root-disk-watch/break.yaml create mode 100644 docs/rhl/breaks/v2/planted/two-candidate-typo/07-gx16-root-disk-watch/broken.rhl create mode 100644 docs/rhl/breaks/v2/planted/two-candidate-typo/08-gx17-runner-load-alert/break.yaml create mode 100644 docs/rhl/breaks/v2/planted/two-candidate-typo/08-gx17-runner-load-alert/broken.rhl create mode 100644 docs/rhl/breaks/v2/planted/two-candidate-typo/09-gx18-pin-check-b/break.yaml create mode 100644 docs/rhl/breaks/v2/planted/two-candidate-typo/09-gx18-pin-check-b/broken.rhl create mode 100644 docs/rhl/breaks/v2/planted/two-candidate-typo/10-gx19-ticket-sweep-b/break.yaml create mode 100644 docs/rhl/breaks/v2/planted/two-candidate-typo/10-gx19-ticket-sweep-b/broken.rhl create mode 100644 docs/rhl/breaks/v2/planted/two-candidate-typo/11-gx20-var-disk-watch/break.yaml create mode 100644 docs/rhl/breaks/v2/planted/two-candidate-typo/11-gx20-var-disk-watch/broken.rhl create mode 100644 docs/rhl/breaks/v2/planted/two-candidate-typo/12-gx21-home-disk-watch/break.yaml create mode 100644 docs/rhl/breaks/v2/planted/two-candidate-typo/12-gx21-home-disk-watch/broken.rhl create mode 100644 docs/rhl/breaks/v2/planted/typo-term/01-gx10-disk-watch/break.yaml create mode 100644 docs/rhl/breaks/v2/planted/typo-term/01-gx10-disk-watch/broken.rhl create mode 100644 docs/rhl/breaks/v2/planted/typo-term/02-gx11-docker-disk-watch/break.yaml create mode 100644 docs/rhl/breaks/v2/planted/typo-term/02-gx11-docker-disk-watch/broken.rhl create mode 100644 docs/rhl/breaks/v2/planted/typo-term/03-gx12-runner-load-watch/break.yaml create mode 100644 docs/rhl/breaks/v2/planted/typo-term/03-gx12-runner-load-watch/broken.rhl create mode 100644 docs/rhl/breaks/v2/planted/typo-term/04-gx13-pin-check/break.yaml create mode 100644 docs/rhl/breaks/v2/planted/typo-term/04-gx13-pin-check/broken.rhl create mode 100644 docs/rhl/breaks/v2/planted/typo-term/05-gx14-ticket-sweep/break.yaml create mode 100644 docs/rhl/breaks/v2/planted/typo-term/05-gx14-ticket-sweep/broken.rhl create mode 100644 docs/rhl/breaks/v2/planted/typo-term/06-gx15-data-disk-watch/break.yaml create mode 100644 docs/rhl/breaks/v2/planted/typo-term/06-gx15-data-disk-watch/broken.rhl create mode 100644 docs/rhl/breaks/v2/planted/typo-term/07-gx16-root-disk-watch/break.yaml create mode 100644 docs/rhl/breaks/v2/planted/typo-term/07-gx16-root-disk-watch/broken.rhl create mode 100644 docs/rhl/breaks/v2/planted/typo-term/08-gx17-runner-load-alert/break.yaml create mode 100644 docs/rhl/breaks/v2/planted/typo-term/08-gx17-runner-load-alert/broken.rhl create mode 100644 docs/rhl/breaks/v2/planted/typo-term/09-gx18-pin-check-b/break.yaml create mode 100644 docs/rhl/breaks/v2/planted/typo-term/09-gx18-pin-check-b/broken.rhl create mode 100644 docs/rhl/breaks/v2/planted/typo-term/10-gx19-ticket-sweep-b/break.yaml create mode 100644 docs/rhl/breaks/v2/planted/typo-term/10-gx19-ticket-sweep-b/broken.rhl create mode 100644 docs/rhl/breaks/v2/planted/typo-term/11-gx20-var-disk-watch/break.yaml create mode 100644 docs/rhl/breaks/v2/planted/typo-term/11-gx20-var-disk-watch/broken.rhl create mode 100644 docs/rhl/breaks/v2/planted/typo-term/12-gx21-home-disk-watch/break.yaml create mode 100644 docs/rhl/breaks/v2/planted/typo-term/12-gx21-home-disk-watch/broken.rhl create mode 100644 docs/rhl/breaks/v2/planted/undeclared-effect/01-gx10-disk-watch/break.yaml create mode 100644 docs/rhl/breaks/v2/planted/undeclared-effect/01-gx10-disk-watch/broken.rhl create mode 100644 docs/rhl/breaks/v2/planted/undeclared-effect/02-gx11-docker-disk-watch/break.yaml create mode 100644 docs/rhl/breaks/v2/planted/undeclared-effect/02-gx11-docker-disk-watch/broken.rhl create mode 100644 docs/rhl/breaks/v2/planted/undeclared-effect/03-gx12-runner-load-watch/break.yaml create mode 100644 docs/rhl/breaks/v2/planted/undeclared-effect/03-gx12-runner-load-watch/broken.rhl create mode 100644 docs/rhl/breaks/v2/planted/undeclared-effect/04-gx13-pin-check/break.yaml create mode 100644 docs/rhl/breaks/v2/planted/undeclared-effect/04-gx13-pin-check/broken.rhl create mode 100644 docs/rhl/breaks/v2/planted/undeclared-effect/05-gx14-ticket-sweep/break.yaml create mode 100644 docs/rhl/breaks/v2/planted/undeclared-effect/05-gx14-ticket-sweep/broken.rhl create mode 100644 docs/rhl/breaks/v2/planted/undeclared-effect/06-gx15-data-disk-watch/break.yaml create mode 100644 docs/rhl/breaks/v2/planted/undeclared-effect/06-gx15-data-disk-watch/broken.rhl create mode 100644 docs/rhl/breaks/v2/planted/undeclared-effect/07-gx16-root-disk-watch/break.yaml create mode 100644 docs/rhl/breaks/v2/planted/undeclared-effect/07-gx16-root-disk-watch/broken.rhl create mode 100644 docs/rhl/breaks/v2/planted/undeclared-effect/08-gx17-runner-load-alert/break.yaml create mode 100644 docs/rhl/breaks/v2/planted/undeclared-effect/08-gx17-runner-load-alert/broken.rhl create mode 100644 docs/rhl/breaks/v2/planted/undeclared-effect/09-gx18-pin-check-b/break.yaml create mode 100644 docs/rhl/breaks/v2/planted/undeclared-effect/09-gx18-pin-check-b/broken.rhl create mode 100644 docs/rhl/breaks/v2/planted/undeclared-effect/10-gx19-ticket-sweep-b/break.yaml create mode 100644 docs/rhl/breaks/v2/planted/undeclared-effect/10-gx19-ticket-sweep-b/broken.rhl create mode 100644 docs/rhl/breaks/v2/planted/undeclared-effect/11-gx20-var-disk-watch/break.yaml create mode 100644 docs/rhl/breaks/v2/planted/undeclared-effect/11-gx20-var-disk-watch/broken.rhl create mode 100644 docs/rhl/breaks/v2/planted/undeclared-effect/12-gx21-home-disk-watch/break.yaml create mode 100644 docs/rhl/breaks/v2/planted/undeclared-effect/12-gx21-home-disk-watch/broken.rhl create mode 100644 docs/rhl/breaks/v2/planted/wrong-type/01-gx10-disk-watch/break.yaml create mode 100644 docs/rhl/breaks/v2/planted/wrong-type/01-gx10-disk-watch/broken.rhl create mode 100644 docs/rhl/breaks/v2/planted/wrong-type/02-gx11-docker-disk-watch/break.yaml create mode 100644 docs/rhl/breaks/v2/planted/wrong-type/02-gx11-docker-disk-watch/broken.rhl create mode 100644 docs/rhl/breaks/v2/planted/wrong-type/03-gx12-runner-load-watch/break.yaml create mode 100644 docs/rhl/breaks/v2/planted/wrong-type/03-gx12-runner-load-watch/broken.rhl create mode 100644 docs/rhl/breaks/v2/planted/wrong-type/04-gx13-pin-check/break.yaml create mode 100644 docs/rhl/breaks/v2/planted/wrong-type/04-gx13-pin-check/broken.rhl create mode 100644 docs/rhl/breaks/v2/planted/wrong-type/05-gx14-ticket-sweep/break.yaml create mode 100644 docs/rhl/breaks/v2/planted/wrong-type/05-gx14-ticket-sweep/broken.rhl create mode 100644 docs/rhl/breaks/v2/planted/wrong-type/06-gx15-data-disk-watch/break.yaml create mode 100644 docs/rhl/breaks/v2/planted/wrong-type/06-gx15-data-disk-watch/broken.rhl create mode 100644 docs/rhl/breaks/v2/planted/wrong-type/07-gx16-root-disk-watch/break.yaml create mode 100644 docs/rhl/breaks/v2/planted/wrong-type/07-gx16-root-disk-watch/broken.rhl create mode 100644 docs/rhl/breaks/v2/planted/wrong-type/08-gx17-runner-load-alert/break.yaml create mode 100644 docs/rhl/breaks/v2/planted/wrong-type/08-gx17-runner-load-alert/broken.rhl create mode 100644 docs/rhl/breaks/v2/planted/wrong-type/09-gx18-pin-check-b/break.yaml create mode 100644 docs/rhl/breaks/v2/planted/wrong-type/09-gx18-pin-check-b/broken.rhl create mode 100644 docs/rhl/breaks/v2/planted/wrong-type/10-gx19-ticket-sweep-b/break.yaml create mode 100644 docs/rhl/breaks/v2/planted/wrong-type/10-gx19-ticket-sweep-b/broken.rhl create mode 100644 docs/rhl/breaks/v2/planted/wrong-type/11-gx20-var-disk-watch/break.yaml create mode 100644 docs/rhl/breaks/v2/planted/wrong-type/11-gx20-var-disk-watch/broken.rhl create mode 100644 docs/rhl/breaks/v2/planted/wrong-type/12-gx21-home-disk-watch/break.yaml create mode 100644 docs/rhl/breaks/v2/planted/wrong-type/12-gx21-home-disk-watch/broken.rhl create mode 100644 docs/rhl/breaks/v2/planted/wrong-unit/01-gx10-disk-watch/break.yaml create mode 100644 docs/rhl/breaks/v2/planted/wrong-unit/01-gx10-disk-watch/broken.rhl create mode 100644 docs/rhl/breaks/v2/planted/wrong-unit/02-gx11-docker-disk-watch/break.yaml create mode 100644 docs/rhl/breaks/v2/planted/wrong-unit/02-gx11-docker-disk-watch/broken.rhl create mode 100644 docs/rhl/breaks/v2/planted/wrong-unit/03-gx12-runner-load-watch/break.yaml create mode 100644 docs/rhl/breaks/v2/planted/wrong-unit/03-gx12-runner-load-watch/broken.rhl create mode 100644 docs/rhl/breaks/v2/planted/wrong-unit/04-gx13-pin-check/break.yaml create mode 100644 docs/rhl/breaks/v2/planted/wrong-unit/04-gx13-pin-check/broken.rhl create mode 100644 docs/rhl/breaks/v2/planted/wrong-unit/05-gx14-ticket-sweep/break.yaml create mode 100644 docs/rhl/breaks/v2/planted/wrong-unit/05-gx14-ticket-sweep/broken.rhl create mode 100644 docs/rhl/breaks/v2/planted/wrong-unit/06-gx15-data-disk-watch/break.yaml create mode 100644 docs/rhl/breaks/v2/planted/wrong-unit/06-gx15-data-disk-watch/broken.rhl create mode 100644 docs/rhl/breaks/v2/planted/wrong-unit/07-gx16-root-disk-watch/break.yaml create mode 100644 docs/rhl/breaks/v2/planted/wrong-unit/07-gx16-root-disk-watch/broken.rhl create mode 100644 docs/rhl/breaks/v2/planted/wrong-unit/08-gx17-runner-load-alert/break.yaml create mode 100644 docs/rhl/breaks/v2/planted/wrong-unit/08-gx17-runner-load-alert/broken.rhl create mode 100644 docs/rhl/breaks/v2/planted/wrong-unit/09-gx18-pin-check-b/break.yaml create mode 100644 docs/rhl/breaks/v2/planted/wrong-unit/09-gx18-pin-check-b/broken.rhl create mode 100644 docs/rhl/breaks/v2/planted/wrong-unit/10-gx19-ticket-sweep-b/break.yaml create mode 100644 docs/rhl/breaks/v2/planted/wrong-unit/10-gx19-ticket-sweep-b/broken.rhl create mode 100644 docs/rhl/breaks/v2/planted/wrong-unit/11-gx20-var-disk-watch/break.yaml create mode 100644 docs/rhl/breaks/v2/planted/wrong-unit/11-gx20-var-disk-watch/broken.rhl create mode 100644 docs/rhl/breaks/v2/planted/wrong-unit/12-gx21-home-disk-watch/break.yaml create mode 100644 docs/rhl/breaks/v2/planted/wrong-unit/12-gx21-home-disk-watch/broken.rhl create mode 100644 docs/rhl/breaks/v2/valid/01-gx10-disk-watch.rhl create mode 100644 docs/rhl/breaks/v2/valid/02-gx11-docker-disk-watch.rhl create mode 100644 docs/rhl/breaks/v2/valid/03-gx12-runner-load-watch.rhl create mode 100644 docs/rhl/breaks/v2/valid/04-gx13-pin-check.rhl create mode 100644 docs/rhl/breaks/v2/valid/05-gx14-ticket-sweep.rhl create mode 100644 docs/rhl/breaks/v2/valid/06-gx15-data-disk-watch.rhl create mode 100644 docs/rhl/breaks/v2/valid/07-gx16-root-disk-watch.rhl create mode 100644 docs/rhl/breaks/v2/valid/08-gx17-runner-load-alert.rhl create mode 100644 docs/rhl/breaks/v2/valid/09-gx18-pin-check-b.rhl create mode 100644 docs/rhl/breaks/v2/valid/10-gx19-ticket-sweep-b.rhl create mode 100644 docs/rhl/breaks/v2/valid/11-gx20-var-disk-watch.rhl create mode 100644 docs/rhl/breaks/v2/valid/12-gx21-home-disk-watch.rhl diff --git a/docs/rhl/breaks/v2/planted/missing-end/01-gx10-disk-watch/break.yaml b/docs/rhl/breaks/v2/planted/missing-end/01-gx10-disk-watch/break.yaml new file mode 100644 index 000000000..8833f8c1b --- /dev/null +++ b/docs/rhl/breaks/v2/planted/missing-end/01-gx10-disk-watch/break.yaml @@ -0,0 +1,4 @@ +base: docs/rhl/breaks/v2/valid/01-gx10-disk-watch.rhl +class: "missing end" +expected_code: RHL-P001 +safe_fix: false diff --git a/docs/rhl/breaks/v2/planted/missing-end/01-gx10-disk-watch/broken.rhl b/docs/rhl/breaks/v2/planted/missing-end/01-gx10-disk-watch/broken.rhl new file mode 100644 index 000000000..c5ca58e23 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/missing-end/01-gx10-disk-watch/broken.rhl @@ -0,0 +1,24 @@ +use vocabulary fleet v2 +use vocabulary tickets v2 + +job "gx10 disk watch" + runs on host gx10 + every 1 hour + may read disk + may write tickets + + let free be disk free of "/" + + when free is below 100 GB + file ticket in repo "paiml/infra" with + title "gx10 disk watch" + label "fleet" + end + + expect ticket count is at most 1 + + example "gx10 disk watch example" + given free is 90 GB + then ticket count is 1 + end +end diff --git a/docs/rhl/breaks/v2/planted/missing-end/02-gx11-docker-disk-watch/break.yaml b/docs/rhl/breaks/v2/planted/missing-end/02-gx11-docker-disk-watch/break.yaml new file mode 100644 index 000000000..fcb852541 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/missing-end/02-gx11-docker-disk-watch/break.yaml @@ -0,0 +1,4 @@ +base: docs/rhl/breaks/v2/valid/02-gx11-docker-disk-watch.rhl +class: "missing end" +expected_code: RHL-P001 +safe_fix: false diff --git a/docs/rhl/breaks/v2/planted/missing-end/02-gx11-docker-disk-watch/broken.rhl b/docs/rhl/breaks/v2/planted/missing-end/02-gx11-docker-disk-watch/broken.rhl new file mode 100644 index 000000000..5f1f7c728 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/missing-end/02-gx11-docker-disk-watch/broken.rhl @@ -0,0 +1,24 @@ +use vocabulary fleet v2 +use vocabulary tickets v2 + +job "gx11 docker disk watch" + runs on host gx11 + every 1 hour + may read disk + may write tickets + + let used be disk usage of "/var/lib/docker" + + when used is above 80 GB + file ticket in repo "paiml/fleet-ops" with + title "gx11 docker disk watch" + label "disk" + end + + expect ticket count is at most 1 + + example "gx11 docker disk watch example" + given used is 95 GB + then ticket count is 1 + end +end diff --git a/docs/rhl/breaks/v2/planted/missing-end/03-gx12-runner-load-watch/break.yaml b/docs/rhl/breaks/v2/planted/missing-end/03-gx12-runner-load-watch/break.yaml new file mode 100644 index 000000000..04ac7ef73 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/missing-end/03-gx12-runner-load-watch/break.yaml @@ -0,0 +1,4 @@ +base: docs/rhl/breaks/v2/valid/03-gx12-runner-load-watch.rhl +class: "missing end" +expected_code: RHL-P001 +safe_fix: false diff --git a/docs/rhl/breaks/v2/planted/missing-end/03-gx12-runner-load-watch/broken.rhl b/docs/rhl/breaks/v2/planted/missing-end/03-gx12-runner-load-watch/broken.rhl new file mode 100644 index 000000000..4a89ac5c3 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/missing-end/03-gx12-runner-load-watch/broken.rhl @@ -0,0 +1,24 @@ +use vocabulary fleet v2 +use vocabulary tickets v2 + +job "gx12 runner load watch" + runs on host gx12 + every 1 hour + may read runner + may write tickets + + let load be runner load of "runner-01" + + when load is above 90 % + file ticket in repo "paiml/ci-ops" with + title "gx12 runner load watch" + label "runner" + end + + expect ticket count is at most 1 + + example "gx12 runner load watch example" + given load is 95 % + then ticket count is 1 + end +end diff --git a/docs/rhl/breaks/v2/planted/missing-end/04-gx13-pin-check/break.yaml b/docs/rhl/breaks/v2/planted/missing-end/04-gx13-pin-check/break.yaml new file mode 100644 index 000000000..ddf2ea905 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/missing-end/04-gx13-pin-check/break.yaml @@ -0,0 +1,4 @@ +base: docs/rhl/breaks/v2/valid/04-gx13-pin-check.rhl +class: "missing end" +expected_code: RHL-P001 +safe_fix: false diff --git a/docs/rhl/breaks/v2/planted/missing-end/04-gx13-pin-check/broken.rhl b/docs/rhl/breaks/v2/planted/missing-end/04-gx13-pin-check/broken.rhl new file mode 100644 index 000000000..fd598a28c --- /dev/null +++ b/docs/rhl/breaks/v2/planted/missing-end/04-gx13-pin-check/broken.rhl @@ -0,0 +1,24 @@ +use vocabulary fleet v2 +use vocabulary tickets v2 + +job "gx13 pin check" + runs on host gx13 + every 1 hour + may read host + may write tickets + + let status be pin status of "gx13" + + when status is not "pinned" + file ticket in repo "paiml/infra" with + title "gx13 pin check" + label "pin" + end + + expect ticket count is at most 1 + + example "gx13 pin check example" + given status is "unpinned" + then ticket count is 1 + end +end diff --git a/docs/rhl/breaks/v2/planted/missing-end/05-gx14-ticket-sweep/break.yaml b/docs/rhl/breaks/v2/planted/missing-end/05-gx14-ticket-sweep/break.yaml new file mode 100644 index 000000000..3580bf9f1 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/missing-end/05-gx14-ticket-sweep/break.yaml @@ -0,0 +1,4 @@ +base: docs/rhl/breaks/v2/valid/05-gx14-ticket-sweep.rhl +class: "missing end" +expected_code: RHL-P001 +safe_fix: false diff --git a/docs/rhl/breaks/v2/planted/missing-end/05-gx14-ticket-sweep/broken.rhl b/docs/rhl/breaks/v2/planted/missing-end/05-gx14-ticket-sweep/broken.rhl new file mode 100644 index 000000000..f29098e07 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/missing-end/05-gx14-ticket-sweep/broken.rhl @@ -0,0 +1,24 @@ +use vocabulary fleet v2 +use vocabulary tickets v2 + +job "gx14 ticket sweep" + runs on host gx14 + every 1 hour + may read tickets + may write tickets + + let filed be tickets filed in "paiml/infra" + + when filed is above 5 + file ticket in repo "paiml/infra" with + title "gx14 ticket sweep" + label "tickets" + end + + expect ticket count is at most 1 + + example "gx14 ticket sweep example" + given filed is 7 + then ticket count is 1 + end +end diff --git a/docs/rhl/breaks/v2/planted/missing-end/06-gx15-data-disk-watch/break.yaml b/docs/rhl/breaks/v2/planted/missing-end/06-gx15-data-disk-watch/break.yaml new file mode 100644 index 000000000..fac07ec62 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/missing-end/06-gx15-data-disk-watch/break.yaml @@ -0,0 +1,4 @@ +base: docs/rhl/breaks/v2/valid/06-gx15-data-disk-watch.rhl +class: "missing end" +expected_code: RHL-P001 +safe_fix: false diff --git a/docs/rhl/breaks/v2/planted/missing-end/06-gx15-data-disk-watch/broken.rhl b/docs/rhl/breaks/v2/planted/missing-end/06-gx15-data-disk-watch/broken.rhl new file mode 100644 index 000000000..65315de00 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/missing-end/06-gx15-data-disk-watch/broken.rhl @@ -0,0 +1,24 @@ +use vocabulary fleet v2 +use vocabulary tickets v2 + +job "gx15 data disk watch" + runs on host gx15 + every 1 hour + may read disk + may write tickets + + let free be disk free of "/data" + + when free is below 50 GB + file ticket in repo "paiml/fleet-ops" with + title "gx15 data disk watch" + label "fleet" + end + + expect ticket count is at most 1 + + example "gx15 data disk watch example" + given free is 40 GB + then ticket count is 1 + end +end diff --git a/docs/rhl/breaks/v2/planted/missing-end/07-gx16-root-disk-watch/break.yaml b/docs/rhl/breaks/v2/planted/missing-end/07-gx16-root-disk-watch/break.yaml new file mode 100644 index 000000000..02eb61387 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/missing-end/07-gx16-root-disk-watch/break.yaml @@ -0,0 +1,4 @@ +base: docs/rhl/breaks/v2/valid/07-gx16-root-disk-watch.rhl +class: "missing end" +expected_code: RHL-P001 +safe_fix: false diff --git a/docs/rhl/breaks/v2/planted/missing-end/07-gx16-root-disk-watch/broken.rhl b/docs/rhl/breaks/v2/planted/missing-end/07-gx16-root-disk-watch/broken.rhl new file mode 100644 index 000000000..27c0201b6 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/missing-end/07-gx16-root-disk-watch/broken.rhl @@ -0,0 +1,24 @@ +use vocabulary fleet v2 +use vocabulary tickets v2 + +job "gx16 root disk watch" + runs on host gx16 + every 1 hour + may read disk + may write tickets + + let used be disk usage of "/" + + when used is above 70 GB + file ticket in repo "paiml/infra" with + title "gx16 root disk watch" + label "disk" + end + + expect ticket count is at most 1 + + example "gx16 root disk watch example" + given used is 80 GB + then ticket count is 1 + end +end diff --git a/docs/rhl/breaks/v2/planted/missing-end/08-gx17-runner-load-alert/break.yaml b/docs/rhl/breaks/v2/planted/missing-end/08-gx17-runner-load-alert/break.yaml new file mode 100644 index 000000000..00226c465 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/missing-end/08-gx17-runner-load-alert/break.yaml @@ -0,0 +1,4 @@ +base: docs/rhl/breaks/v2/valid/08-gx17-runner-load-alert.rhl +class: "missing end" +expected_code: RHL-P001 +safe_fix: false diff --git a/docs/rhl/breaks/v2/planted/missing-end/08-gx17-runner-load-alert/broken.rhl b/docs/rhl/breaks/v2/planted/missing-end/08-gx17-runner-load-alert/broken.rhl new file mode 100644 index 000000000..508b25a44 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/missing-end/08-gx17-runner-load-alert/broken.rhl @@ -0,0 +1,24 @@ +use vocabulary fleet v2 +use vocabulary tickets v2 + +job "gx17 runner load alert" + runs on host gx17 + every 1 hour + may read runner + may write tickets + + let load be runner load of "runner-02" + + when load is above 85 % + file ticket in repo "paiml/ci-ops" with + title "gx17 runner load alert" + label "runner" + end + + expect ticket count is at most 1 + + example "gx17 runner load alert example" + given load is 90 % + then ticket count is 1 + end +end diff --git a/docs/rhl/breaks/v2/planted/missing-end/09-gx18-pin-check-b/break.yaml b/docs/rhl/breaks/v2/planted/missing-end/09-gx18-pin-check-b/break.yaml new file mode 100644 index 000000000..0ec3ac831 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/missing-end/09-gx18-pin-check-b/break.yaml @@ -0,0 +1,4 @@ +base: docs/rhl/breaks/v2/valid/09-gx18-pin-check-b.rhl +class: "missing end" +expected_code: RHL-P001 +safe_fix: false diff --git a/docs/rhl/breaks/v2/planted/missing-end/09-gx18-pin-check-b/broken.rhl b/docs/rhl/breaks/v2/planted/missing-end/09-gx18-pin-check-b/broken.rhl new file mode 100644 index 000000000..a3fedb1c4 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/missing-end/09-gx18-pin-check-b/broken.rhl @@ -0,0 +1,24 @@ +use vocabulary fleet v2 +use vocabulary tickets v2 + +job "gx18 pin check" + runs on host gx18 + every 1 hour + may read host + may write tickets + + let status be pin status of "gx18" + + when status is not "pinned" + file ticket in repo "paiml/infra" with + title "gx18 pin check" + label "pin" + end + + expect ticket count is at most 1 + + example "gx18 pin check example" + given status is "unpinned" + then ticket count is 1 + end +end diff --git a/docs/rhl/breaks/v2/planted/missing-end/10-gx19-ticket-sweep-b/break.yaml b/docs/rhl/breaks/v2/planted/missing-end/10-gx19-ticket-sweep-b/break.yaml new file mode 100644 index 000000000..d2a6fa07d --- /dev/null +++ b/docs/rhl/breaks/v2/planted/missing-end/10-gx19-ticket-sweep-b/break.yaml @@ -0,0 +1,4 @@ +base: docs/rhl/breaks/v2/valid/10-gx19-ticket-sweep-b.rhl +class: "missing end" +expected_code: RHL-P001 +safe_fix: false diff --git a/docs/rhl/breaks/v2/planted/missing-end/10-gx19-ticket-sweep-b/broken.rhl b/docs/rhl/breaks/v2/planted/missing-end/10-gx19-ticket-sweep-b/broken.rhl new file mode 100644 index 000000000..80b718845 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/missing-end/10-gx19-ticket-sweep-b/broken.rhl @@ -0,0 +1,24 @@ +use vocabulary fleet v2 +use vocabulary tickets v2 + +job "gx19 ticket sweep" + runs on host gx19 + every 1 hour + may read tickets + may write tickets + + let filed be tickets filed in "paiml/fleet-ops" + + when filed is above 3 + file ticket in repo "paiml/fleet-ops" with + title "gx19 ticket sweep" + label "tickets" + end + + expect ticket count is at most 1 + + example "gx19 ticket sweep example" + given filed is 4 + then ticket count is 1 + end +end diff --git a/docs/rhl/breaks/v2/planted/missing-end/11-gx20-var-disk-watch/break.yaml b/docs/rhl/breaks/v2/planted/missing-end/11-gx20-var-disk-watch/break.yaml new file mode 100644 index 000000000..74fda7469 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/missing-end/11-gx20-var-disk-watch/break.yaml @@ -0,0 +1,4 @@ +base: docs/rhl/breaks/v2/valid/11-gx20-var-disk-watch.rhl +class: "missing end" +expected_code: RHL-P001 +safe_fix: false diff --git a/docs/rhl/breaks/v2/planted/missing-end/11-gx20-var-disk-watch/broken.rhl b/docs/rhl/breaks/v2/planted/missing-end/11-gx20-var-disk-watch/broken.rhl new file mode 100644 index 000000000..44466add1 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/missing-end/11-gx20-var-disk-watch/broken.rhl @@ -0,0 +1,24 @@ +use vocabulary fleet v2 +use vocabulary tickets v2 + +job "gx20 var disk watch" + runs on host gx20 + every 1 hour + may read disk + may write tickets + + let free be disk free of "/var" + + when free is below 30 GB + file ticket in repo "paiml/infra" with + title "gx20 var disk watch" + label "fleet" + end + + expect ticket count is at most 1 + + example "gx20 var disk watch example" + given free is 20 GB + then ticket count is 1 + end +end diff --git a/docs/rhl/breaks/v2/planted/missing-end/12-gx21-home-disk-watch/break.yaml b/docs/rhl/breaks/v2/planted/missing-end/12-gx21-home-disk-watch/break.yaml new file mode 100644 index 000000000..9c70aa584 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/missing-end/12-gx21-home-disk-watch/break.yaml @@ -0,0 +1,4 @@ +base: docs/rhl/breaks/v2/valid/12-gx21-home-disk-watch.rhl +class: "missing end" +expected_code: RHL-P001 +safe_fix: false diff --git a/docs/rhl/breaks/v2/planted/missing-end/12-gx21-home-disk-watch/broken.rhl b/docs/rhl/breaks/v2/planted/missing-end/12-gx21-home-disk-watch/broken.rhl new file mode 100644 index 000000000..6338c7083 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/missing-end/12-gx21-home-disk-watch/broken.rhl @@ -0,0 +1,24 @@ +use vocabulary fleet v2 +use vocabulary tickets v2 + +job "gx21 home disk watch" + runs on host gx21 + every 1 hour + may read disk + may write tickets + + let used be disk usage of "/home" + + when used is above 60 GB + file ticket in repo "paiml/fleet-ops" with + title "gx21 home disk watch" + label "disk" + end + + expect ticket count is at most 1 + + example "gx21 home disk watch example" + given used is 65 GB + then ticket count is 1 + end +end diff --git a/docs/rhl/breaks/v2/planted/two-candidate-typo/01-gx10-disk-watch/break.yaml b/docs/rhl/breaks/v2/planted/two-candidate-typo/01-gx10-disk-watch/break.yaml new file mode 100644 index 000000000..d62018bfc --- /dev/null +++ b/docs/rhl/breaks/v2/planted/two-candidate-typo/01-gx10-disk-watch/break.yaml @@ -0,0 +1,7 @@ +base: docs/rhl/breaks/v2/valid/01-gx10-disk-watch.rhl +class: "two-candidate typo" +expected_code: RHL-V003 +safe_fix: false +candidates: + - host + - hour diff --git a/docs/rhl/breaks/v2/planted/two-candidate-typo/01-gx10-disk-watch/broken.rhl b/docs/rhl/breaks/v2/planted/two-candidate-typo/01-gx10-disk-watch/broken.rhl new file mode 100644 index 000000000..3cf29e904 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/two-candidate-typo/01-gx10-disk-watch/broken.rhl @@ -0,0 +1,25 @@ +use vocabulary fleet v2 +use vocabulary tickets v2 + +job "gx10 disk watch" + runs on hosr gx10 + every 1 hour + may read disk + may write tickets + + let free be disk free of "/" + + when free is below 100 GB + file ticket in repo "paiml/infra" with + title "gx10 disk watch" + label "fleet" + end + end + + expect ticket count is at most 1 + + example "gx10 disk watch example" + given free is 90 GB + then ticket count is 1 + end +end diff --git a/docs/rhl/breaks/v2/planted/two-candidate-typo/02-gx11-docker-disk-watch/break.yaml b/docs/rhl/breaks/v2/planted/two-candidate-typo/02-gx11-docker-disk-watch/break.yaml new file mode 100644 index 000000000..a6792de44 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/two-candidate-typo/02-gx11-docker-disk-watch/break.yaml @@ -0,0 +1,7 @@ +base: docs/rhl/breaks/v2/valid/02-gx11-docker-disk-watch.rhl +class: "two-candidate typo" +expected_code: RHL-V003 +safe_fix: false +candidates: + - host + - hour diff --git a/docs/rhl/breaks/v2/planted/two-candidate-typo/02-gx11-docker-disk-watch/broken.rhl b/docs/rhl/breaks/v2/planted/two-candidate-typo/02-gx11-docker-disk-watch/broken.rhl new file mode 100644 index 000000000..7b879578a --- /dev/null +++ b/docs/rhl/breaks/v2/planted/two-candidate-typo/02-gx11-docker-disk-watch/broken.rhl @@ -0,0 +1,25 @@ +use vocabulary fleet v2 +use vocabulary tickets v2 + +job "gx11 docker disk watch" + runs on hosr gx11 + every 1 hour + may read disk + may write tickets + + let used be disk usage of "/var/lib/docker" + + when used is above 80 GB + file ticket in repo "paiml/fleet-ops" with + title "gx11 docker disk watch" + label "disk" + end + end + + expect ticket count is at most 1 + + example "gx11 docker disk watch example" + given used is 95 GB + then ticket count is 1 + end +end diff --git a/docs/rhl/breaks/v2/planted/two-candidate-typo/03-gx12-runner-load-watch/break.yaml b/docs/rhl/breaks/v2/planted/two-candidate-typo/03-gx12-runner-load-watch/break.yaml new file mode 100644 index 000000000..e84f7c185 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/two-candidate-typo/03-gx12-runner-load-watch/break.yaml @@ -0,0 +1,7 @@ +base: docs/rhl/breaks/v2/valid/03-gx12-runner-load-watch.rhl +class: "two-candidate typo" +expected_code: RHL-V003 +safe_fix: false +candidates: + - host + - hour diff --git a/docs/rhl/breaks/v2/planted/two-candidate-typo/03-gx12-runner-load-watch/broken.rhl b/docs/rhl/breaks/v2/planted/two-candidate-typo/03-gx12-runner-load-watch/broken.rhl new file mode 100644 index 000000000..49a080810 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/two-candidate-typo/03-gx12-runner-load-watch/broken.rhl @@ -0,0 +1,25 @@ +use vocabulary fleet v2 +use vocabulary tickets v2 + +job "gx12 runner load watch" + runs on hosr gx12 + every 1 hour + may read runner + may write tickets + + let load be runner load of "runner-01" + + when load is above 90 % + file ticket in repo "paiml/ci-ops" with + title "gx12 runner load watch" + label "runner" + end + end + + expect ticket count is at most 1 + + example "gx12 runner load watch example" + given load is 95 % + then ticket count is 1 + end +end diff --git a/docs/rhl/breaks/v2/planted/two-candidate-typo/04-gx13-pin-check/break.yaml b/docs/rhl/breaks/v2/planted/two-candidate-typo/04-gx13-pin-check/break.yaml new file mode 100644 index 000000000..46b5cbc4c --- /dev/null +++ b/docs/rhl/breaks/v2/planted/two-candidate-typo/04-gx13-pin-check/break.yaml @@ -0,0 +1,7 @@ +base: docs/rhl/breaks/v2/valid/04-gx13-pin-check.rhl +class: "two-candidate typo" +expected_code: RHL-V003 +safe_fix: false +candidates: + - host + - hour diff --git a/docs/rhl/breaks/v2/planted/two-candidate-typo/04-gx13-pin-check/broken.rhl b/docs/rhl/breaks/v2/planted/two-candidate-typo/04-gx13-pin-check/broken.rhl new file mode 100644 index 000000000..61d006e3b --- /dev/null +++ b/docs/rhl/breaks/v2/planted/two-candidate-typo/04-gx13-pin-check/broken.rhl @@ -0,0 +1,25 @@ +use vocabulary fleet v2 +use vocabulary tickets v2 + +job "gx13 pin check" + runs on hosr gx13 + every 1 hour + may read host + may write tickets + + let status be pin status of "gx13" + + when status is not "pinned" + file ticket in repo "paiml/infra" with + title "gx13 pin check" + label "pin" + end + end + + expect ticket count is at most 1 + + example "gx13 pin check example" + given status is "unpinned" + then ticket count is 1 + end +end diff --git a/docs/rhl/breaks/v2/planted/two-candidate-typo/05-gx14-ticket-sweep/break.yaml b/docs/rhl/breaks/v2/planted/two-candidate-typo/05-gx14-ticket-sweep/break.yaml new file mode 100644 index 000000000..c23e76932 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/two-candidate-typo/05-gx14-ticket-sweep/break.yaml @@ -0,0 +1,7 @@ +base: docs/rhl/breaks/v2/valid/05-gx14-ticket-sweep.rhl +class: "two-candidate typo" +expected_code: RHL-V003 +safe_fix: false +candidates: + - host + - hour diff --git a/docs/rhl/breaks/v2/planted/two-candidate-typo/05-gx14-ticket-sweep/broken.rhl b/docs/rhl/breaks/v2/planted/two-candidate-typo/05-gx14-ticket-sweep/broken.rhl new file mode 100644 index 000000000..be1e79171 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/two-candidate-typo/05-gx14-ticket-sweep/broken.rhl @@ -0,0 +1,25 @@ +use vocabulary fleet v2 +use vocabulary tickets v2 + +job "gx14 ticket sweep" + runs on hosr gx14 + every 1 hour + may read tickets + may write tickets + + let filed be tickets filed in "paiml/infra" + + when filed is above 5 + file ticket in repo "paiml/infra" with + title "gx14 ticket sweep" + label "tickets" + end + end + + expect ticket count is at most 1 + + example "gx14 ticket sweep example" + given filed is 7 + then ticket count is 1 + end +end diff --git a/docs/rhl/breaks/v2/planted/two-candidate-typo/06-gx15-data-disk-watch/break.yaml b/docs/rhl/breaks/v2/planted/two-candidate-typo/06-gx15-data-disk-watch/break.yaml new file mode 100644 index 000000000..c4b1a00d8 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/two-candidate-typo/06-gx15-data-disk-watch/break.yaml @@ -0,0 +1,7 @@ +base: docs/rhl/breaks/v2/valid/06-gx15-data-disk-watch.rhl +class: "two-candidate typo" +expected_code: RHL-V003 +safe_fix: false +candidates: + - host + - hour diff --git a/docs/rhl/breaks/v2/planted/two-candidate-typo/06-gx15-data-disk-watch/broken.rhl b/docs/rhl/breaks/v2/planted/two-candidate-typo/06-gx15-data-disk-watch/broken.rhl new file mode 100644 index 000000000..e147cb384 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/two-candidate-typo/06-gx15-data-disk-watch/broken.rhl @@ -0,0 +1,25 @@ +use vocabulary fleet v2 +use vocabulary tickets v2 + +job "gx15 data disk watch" + runs on hosr gx15 + every 1 hour + may read disk + may write tickets + + let free be disk free of "/data" + + when free is below 50 GB + file ticket in repo "paiml/fleet-ops" with + title "gx15 data disk watch" + label "fleet" + end + end + + expect ticket count is at most 1 + + example "gx15 data disk watch example" + given free is 40 GB + then ticket count is 1 + end +end diff --git a/docs/rhl/breaks/v2/planted/two-candidate-typo/07-gx16-root-disk-watch/break.yaml b/docs/rhl/breaks/v2/planted/two-candidate-typo/07-gx16-root-disk-watch/break.yaml new file mode 100644 index 000000000..8a9951b28 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/two-candidate-typo/07-gx16-root-disk-watch/break.yaml @@ -0,0 +1,7 @@ +base: docs/rhl/breaks/v2/valid/07-gx16-root-disk-watch.rhl +class: "two-candidate typo" +expected_code: RHL-V003 +safe_fix: false +candidates: + - host + - hour diff --git a/docs/rhl/breaks/v2/planted/two-candidate-typo/07-gx16-root-disk-watch/broken.rhl b/docs/rhl/breaks/v2/planted/two-candidate-typo/07-gx16-root-disk-watch/broken.rhl new file mode 100644 index 000000000..d7bb8c7ea --- /dev/null +++ b/docs/rhl/breaks/v2/planted/two-candidate-typo/07-gx16-root-disk-watch/broken.rhl @@ -0,0 +1,25 @@ +use vocabulary fleet v2 +use vocabulary tickets v2 + +job "gx16 root disk watch" + runs on hosr gx16 + every 1 hour + may read disk + may write tickets + + let used be disk usage of "/" + + when used is above 70 GB + file ticket in repo "paiml/infra" with + title "gx16 root disk watch" + label "disk" + end + end + + expect ticket count is at most 1 + + example "gx16 root disk watch example" + given used is 80 GB + then ticket count is 1 + end +end diff --git a/docs/rhl/breaks/v2/planted/two-candidate-typo/08-gx17-runner-load-alert/break.yaml b/docs/rhl/breaks/v2/planted/two-candidate-typo/08-gx17-runner-load-alert/break.yaml new file mode 100644 index 000000000..582282d87 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/two-candidate-typo/08-gx17-runner-load-alert/break.yaml @@ -0,0 +1,7 @@ +base: docs/rhl/breaks/v2/valid/08-gx17-runner-load-alert.rhl +class: "two-candidate typo" +expected_code: RHL-V003 +safe_fix: false +candidates: + - host + - hour diff --git a/docs/rhl/breaks/v2/planted/two-candidate-typo/08-gx17-runner-load-alert/broken.rhl b/docs/rhl/breaks/v2/planted/two-candidate-typo/08-gx17-runner-load-alert/broken.rhl new file mode 100644 index 000000000..6dc04efb7 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/two-candidate-typo/08-gx17-runner-load-alert/broken.rhl @@ -0,0 +1,25 @@ +use vocabulary fleet v2 +use vocabulary tickets v2 + +job "gx17 runner load alert" + runs on hosr gx17 + every 1 hour + may read runner + may write tickets + + let load be runner load of "runner-02" + + when load is above 85 % + file ticket in repo "paiml/ci-ops" with + title "gx17 runner load alert" + label "runner" + end + end + + expect ticket count is at most 1 + + example "gx17 runner load alert example" + given load is 90 % + then ticket count is 1 + end +end diff --git a/docs/rhl/breaks/v2/planted/two-candidate-typo/09-gx18-pin-check-b/break.yaml b/docs/rhl/breaks/v2/planted/two-candidate-typo/09-gx18-pin-check-b/break.yaml new file mode 100644 index 000000000..0223622d1 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/two-candidate-typo/09-gx18-pin-check-b/break.yaml @@ -0,0 +1,7 @@ +base: docs/rhl/breaks/v2/valid/09-gx18-pin-check-b.rhl +class: "two-candidate typo" +expected_code: RHL-V003 +safe_fix: false +candidates: + - host + - hour diff --git a/docs/rhl/breaks/v2/planted/two-candidate-typo/09-gx18-pin-check-b/broken.rhl b/docs/rhl/breaks/v2/planted/two-candidate-typo/09-gx18-pin-check-b/broken.rhl new file mode 100644 index 000000000..e37a73fcb --- /dev/null +++ b/docs/rhl/breaks/v2/planted/two-candidate-typo/09-gx18-pin-check-b/broken.rhl @@ -0,0 +1,25 @@ +use vocabulary fleet v2 +use vocabulary tickets v2 + +job "gx18 pin check" + runs on hosr gx18 + every 1 hour + may read host + may write tickets + + let status be pin status of "gx18" + + when status is not "pinned" + file ticket in repo "paiml/infra" with + title "gx18 pin check" + label "pin" + end + end + + expect ticket count is at most 1 + + example "gx18 pin check example" + given status is "unpinned" + then ticket count is 1 + end +end diff --git a/docs/rhl/breaks/v2/planted/two-candidate-typo/10-gx19-ticket-sweep-b/break.yaml b/docs/rhl/breaks/v2/planted/two-candidate-typo/10-gx19-ticket-sweep-b/break.yaml new file mode 100644 index 000000000..e7c4b39b0 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/two-candidate-typo/10-gx19-ticket-sweep-b/break.yaml @@ -0,0 +1,7 @@ +base: docs/rhl/breaks/v2/valid/10-gx19-ticket-sweep-b.rhl +class: "two-candidate typo" +expected_code: RHL-V003 +safe_fix: false +candidates: + - host + - hour diff --git a/docs/rhl/breaks/v2/planted/two-candidate-typo/10-gx19-ticket-sweep-b/broken.rhl b/docs/rhl/breaks/v2/planted/two-candidate-typo/10-gx19-ticket-sweep-b/broken.rhl new file mode 100644 index 000000000..88b87d7b1 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/two-candidate-typo/10-gx19-ticket-sweep-b/broken.rhl @@ -0,0 +1,25 @@ +use vocabulary fleet v2 +use vocabulary tickets v2 + +job "gx19 ticket sweep" + runs on hosr gx19 + every 1 hour + may read tickets + may write tickets + + let filed be tickets filed in "paiml/fleet-ops" + + when filed is above 3 + file ticket in repo "paiml/fleet-ops" with + title "gx19 ticket sweep" + label "tickets" + end + end + + expect ticket count is at most 1 + + example "gx19 ticket sweep example" + given filed is 4 + then ticket count is 1 + end +end diff --git a/docs/rhl/breaks/v2/planted/two-candidate-typo/11-gx20-var-disk-watch/break.yaml b/docs/rhl/breaks/v2/planted/two-candidate-typo/11-gx20-var-disk-watch/break.yaml new file mode 100644 index 000000000..767354a42 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/two-candidate-typo/11-gx20-var-disk-watch/break.yaml @@ -0,0 +1,7 @@ +base: docs/rhl/breaks/v2/valid/11-gx20-var-disk-watch.rhl +class: "two-candidate typo" +expected_code: RHL-V003 +safe_fix: false +candidates: + - host + - hour diff --git a/docs/rhl/breaks/v2/planted/two-candidate-typo/11-gx20-var-disk-watch/broken.rhl b/docs/rhl/breaks/v2/planted/two-candidate-typo/11-gx20-var-disk-watch/broken.rhl new file mode 100644 index 000000000..b604e1884 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/two-candidate-typo/11-gx20-var-disk-watch/broken.rhl @@ -0,0 +1,25 @@ +use vocabulary fleet v2 +use vocabulary tickets v2 + +job "gx20 var disk watch" + runs on hosr gx20 + every 1 hour + may read disk + may write tickets + + let free be disk free of "/var" + + when free is below 30 GB + file ticket in repo "paiml/infra" with + title "gx20 var disk watch" + label "fleet" + end + end + + expect ticket count is at most 1 + + example "gx20 var disk watch example" + given free is 20 GB + then ticket count is 1 + end +end diff --git a/docs/rhl/breaks/v2/planted/two-candidate-typo/12-gx21-home-disk-watch/break.yaml b/docs/rhl/breaks/v2/planted/two-candidate-typo/12-gx21-home-disk-watch/break.yaml new file mode 100644 index 000000000..61f6884d1 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/two-candidate-typo/12-gx21-home-disk-watch/break.yaml @@ -0,0 +1,7 @@ +base: docs/rhl/breaks/v2/valid/12-gx21-home-disk-watch.rhl +class: "two-candidate typo" +expected_code: RHL-V003 +safe_fix: false +candidates: + - host + - hour diff --git a/docs/rhl/breaks/v2/planted/two-candidate-typo/12-gx21-home-disk-watch/broken.rhl b/docs/rhl/breaks/v2/planted/two-candidate-typo/12-gx21-home-disk-watch/broken.rhl new file mode 100644 index 000000000..9be3ccfd2 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/two-candidate-typo/12-gx21-home-disk-watch/broken.rhl @@ -0,0 +1,25 @@ +use vocabulary fleet v2 +use vocabulary tickets v2 + +job "gx21 home disk watch" + runs on hosr gx21 + every 1 hour + may read disk + may write tickets + + let used be disk usage of "/home" + + when used is above 60 GB + file ticket in repo "paiml/fleet-ops" with + title "gx21 home disk watch" + label "disk" + end + end + + expect ticket count is at most 1 + + example "gx21 home disk watch example" + given used is 65 GB + then ticket count is 1 + end +end diff --git a/docs/rhl/breaks/v2/planted/typo-term/01-gx10-disk-watch/break.yaml b/docs/rhl/breaks/v2/planted/typo-term/01-gx10-disk-watch/break.yaml new file mode 100644 index 000000000..b325bf72b --- /dev/null +++ b/docs/rhl/breaks/v2/planted/typo-term/01-gx10-disk-watch/break.yaml @@ -0,0 +1,4 @@ +base: docs/rhl/breaks/v2/valid/01-gx10-disk-watch.rhl +class: "typo'd term" +expected_code: RHL-V002 +safe_fix: true diff --git a/docs/rhl/breaks/v2/planted/typo-term/01-gx10-disk-watch/broken.rhl b/docs/rhl/breaks/v2/planted/typo-term/01-gx10-disk-watch/broken.rhl new file mode 100644 index 000000000..d58238a45 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/typo-term/01-gx10-disk-watch/broken.rhl @@ -0,0 +1,25 @@ +use vocabulary fleet v2 +use vocabulary tickets v2 + +job "gx10 disk watch" + runs on host gx10 + every 1 hour + may read disk + may write tickets + + let free be disk fre of "/" + + when free is below 100 GB + file ticket in repo "paiml/infra" with + title "gx10 disk watch" + label "fleet" + end + end + + expect ticket count is at most 1 + + example "gx10 disk watch example" + given free is 90 GB + then ticket count is 1 + end +end diff --git a/docs/rhl/breaks/v2/planted/typo-term/02-gx11-docker-disk-watch/break.yaml b/docs/rhl/breaks/v2/planted/typo-term/02-gx11-docker-disk-watch/break.yaml new file mode 100644 index 000000000..e0edf30c7 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/typo-term/02-gx11-docker-disk-watch/break.yaml @@ -0,0 +1,4 @@ +base: docs/rhl/breaks/v2/valid/02-gx11-docker-disk-watch.rhl +class: "typo'd term" +expected_code: RHL-V002 +safe_fix: true diff --git a/docs/rhl/breaks/v2/planted/typo-term/02-gx11-docker-disk-watch/broken.rhl b/docs/rhl/breaks/v2/planted/typo-term/02-gx11-docker-disk-watch/broken.rhl new file mode 100644 index 000000000..68bc73740 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/typo-term/02-gx11-docker-disk-watch/broken.rhl @@ -0,0 +1,25 @@ +use vocabulary fleet v2 +use vocabulary tickets v2 + +job "gx11 docker disk watch" + runs on host gx11 + every 1 hour + may read disk + may write tickets + + let used be disk usge of "/var/lib/docker" + + when used is above 80 GB + file ticket in repo "paiml/fleet-ops" with + title "gx11 docker disk watch" + label "disk" + end + end + + expect ticket count is at most 1 + + example "gx11 docker disk watch example" + given used is 95 GB + then ticket count is 1 + end +end diff --git a/docs/rhl/breaks/v2/planted/typo-term/03-gx12-runner-load-watch/break.yaml b/docs/rhl/breaks/v2/planted/typo-term/03-gx12-runner-load-watch/break.yaml new file mode 100644 index 000000000..516832090 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/typo-term/03-gx12-runner-load-watch/break.yaml @@ -0,0 +1,4 @@ +base: docs/rhl/breaks/v2/valid/03-gx12-runner-load-watch.rhl +class: "typo'd term" +expected_code: RHL-V002 +safe_fix: true diff --git a/docs/rhl/breaks/v2/planted/typo-term/03-gx12-runner-load-watch/broken.rhl b/docs/rhl/breaks/v2/planted/typo-term/03-gx12-runner-load-watch/broken.rhl new file mode 100644 index 000000000..5eff6f901 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/typo-term/03-gx12-runner-load-watch/broken.rhl @@ -0,0 +1,25 @@ +use vocabulary fleet v2 +use vocabulary tickets v2 + +job "gx12 runner load watch" + runs on host gx12 + every 1 hour + may read runner + may write tickets + + let load be runer load of "runner-01" + + when load is above 90 % + file ticket in repo "paiml/ci-ops" with + title "gx12 runner load watch" + label "runner" + end + end + + expect ticket count is at most 1 + + example "gx12 runner load watch example" + given load is 95 % + then ticket count is 1 + end +end diff --git a/docs/rhl/breaks/v2/planted/typo-term/04-gx13-pin-check/break.yaml b/docs/rhl/breaks/v2/planted/typo-term/04-gx13-pin-check/break.yaml new file mode 100644 index 000000000..c2a085c56 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/typo-term/04-gx13-pin-check/break.yaml @@ -0,0 +1,4 @@ +base: docs/rhl/breaks/v2/valid/04-gx13-pin-check.rhl +class: "typo'd term" +expected_code: RHL-V002 +safe_fix: true diff --git a/docs/rhl/breaks/v2/planted/typo-term/04-gx13-pin-check/broken.rhl b/docs/rhl/breaks/v2/planted/typo-term/04-gx13-pin-check/broken.rhl new file mode 100644 index 000000000..24d90694a --- /dev/null +++ b/docs/rhl/breaks/v2/planted/typo-term/04-gx13-pin-check/broken.rhl @@ -0,0 +1,25 @@ +use vocabulary fleet v2 +use vocabulary tickets v2 + +job "gx13 pin check" + runs on host gx13 + every 1 hour + may read host + may write tickets + + let status be pin staus of "gx13" + + when status is not "pinned" + file ticket in repo "paiml/infra" with + title "gx13 pin check" + label "pin" + end + end + + expect ticket count is at most 1 + + example "gx13 pin check example" + given status is "unpinned" + then ticket count is 1 + end +end diff --git a/docs/rhl/breaks/v2/planted/typo-term/05-gx14-ticket-sweep/break.yaml b/docs/rhl/breaks/v2/planted/typo-term/05-gx14-ticket-sweep/break.yaml new file mode 100644 index 000000000..e56dcb5ea --- /dev/null +++ b/docs/rhl/breaks/v2/planted/typo-term/05-gx14-ticket-sweep/break.yaml @@ -0,0 +1,4 @@ +base: docs/rhl/breaks/v2/valid/05-gx14-ticket-sweep.rhl +class: "typo'd term" +expected_code: RHL-V002 +safe_fix: true diff --git a/docs/rhl/breaks/v2/planted/typo-term/05-gx14-ticket-sweep/broken.rhl b/docs/rhl/breaks/v2/planted/typo-term/05-gx14-ticket-sweep/broken.rhl new file mode 100644 index 000000000..5e016cfc5 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/typo-term/05-gx14-ticket-sweep/broken.rhl @@ -0,0 +1,25 @@ +use vocabulary fleet v2 +use vocabulary tickets v2 + +job "gx14 ticket sweep" + runs on host gx14 + every 1 hour + may read tickets + may write tickets + + let filed be ticket filed in "paiml/infra" + + when filed is above 5 + file ticket in repo "paiml/infra" with + title "gx14 ticket sweep" + label "tickets" + end + end + + expect ticket count is at most 1 + + example "gx14 ticket sweep example" + given filed is 7 + then ticket count is 1 + end +end diff --git a/docs/rhl/breaks/v2/planted/typo-term/06-gx15-data-disk-watch/break.yaml b/docs/rhl/breaks/v2/planted/typo-term/06-gx15-data-disk-watch/break.yaml new file mode 100644 index 000000000..f27cbb6e3 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/typo-term/06-gx15-data-disk-watch/break.yaml @@ -0,0 +1,4 @@ +base: docs/rhl/breaks/v2/valid/06-gx15-data-disk-watch.rhl +class: "typo'd term" +expected_code: RHL-V002 +safe_fix: true diff --git a/docs/rhl/breaks/v2/planted/typo-term/06-gx15-data-disk-watch/broken.rhl b/docs/rhl/breaks/v2/planted/typo-term/06-gx15-data-disk-watch/broken.rhl new file mode 100644 index 000000000..1e7506636 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/typo-term/06-gx15-data-disk-watch/broken.rhl @@ -0,0 +1,25 @@ +use vocabulary fleet v2 +use vocabulary tickets v2 + +job "gx15 data disk watch" + runs on host gx15 + every 1 hour + may read disk + may write tickets + + let free be disk fre of "/data" + + when free is below 50 GB + file ticket in repo "paiml/fleet-ops" with + title "gx15 data disk watch" + label "fleet" + end + end + + expect ticket count is at most 1 + + example "gx15 data disk watch example" + given free is 40 GB + then ticket count is 1 + end +end diff --git a/docs/rhl/breaks/v2/planted/typo-term/07-gx16-root-disk-watch/break.yaml b/docs/rhl/breaks/v2/planted/typo-term/07-gx16-root-disk-watch/break.yaml new file mode 100644 index 000000000..4d835e6af --- /dev/null +++ b/docs/rhl/breaks/v2/planted/typo-term/07-gx16-root-disk-watch/break.yaml @@ -0,0 +1,4 @@ +base: docs/rhl/breaks/v2/valid/07-gx16-root-disk-watch.rhl +class: "typo'd term" +expected_code: RHL-V002 +safe_fix: true diff --git a/docs/rhl/breaks/v2/planted/typo-term/07-gx16-root-disk-watch/broken.rhl b/docs/rhl/breaks/v2/planted/typo-term/07-gx16-root-disk-watch/broken.rhl new file mode 100644 index 000000000..cd4d3e8e1 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/typo-term/07-gx16-root-disk-watch/broken.rhl @@ -0,0 +1,25 @@ +use vocabulary fleet v2 +use vocabulary tickets v2 + +job "gx16 root disk watch" + runs on host gx16 + every 1 hour + may read disk + may write tickets + + let used be disk usge of "/" + + when used is above 70 GB + file ticket in repo "paiml/infra" with + title "gx16 root disk watch" + label "disk" + end + end + + expect ticket count is at most 1 + + example "gx16 root disk watch example" + given used is 80 GB + then ticket count is 1 + end +end diff --git a/docs/rhl/breaks/v2/planted/typo-term/08-gx17-runner-load-alert/break.yaml b/docs/rhl/breaks/v2/planted/typo-term/08-gx17-runner-load-alert/break.yaml new file mode 100644 index 000000000..5d096e2b6 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/typo-term/08-gx17-runner-load-alert/break.yaml @@ -0,0 +1,4 @@ +base: docs/rhl/breaks/v2/valid/08-gx17-runner-load-alert.rhl +class: "typo'd term" +expected_code: RHL-V002 +safe_fix: true diff --git a/docs/rhl/breaks/v2/planted/typo-term/08-gx17-runner-load-alert/broken.rhl b/docs/rhl/breaks/v2/planted/typo-term/08-gx17-runner-load-alert/broken.rhl new file mode 100644 index 000000000..0feb8eee0 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/typo-term/08-gx17-runner-load-alert/broken.rhl @@ -0,0 +1,25 @@ +use vocabulary fleet v2 +use vocabulary tickets v2 + +job "gx17 runner load alert" + runs on host gx17 + every 1 hour + may read runner + may write tickets + + let load be runer load of "runner-02" + + when load is above 85 % + file ticket in repo "paiml/ci-ops" with + title "gx17 runner load alert" + label "runner" + end + end + + expect ticket count is at most 1 + + example "gx17 runner load alert example" + given load is 90 % + then ticket count is 1 + end +end diff --git a/docs/rhl/breaks/v2/planted/typo-term/09-gx18-pin-check-b/break.yaml b/docs/rhl/breaks/v2/planted/typo-term/09-gx18-pin-check-b/break.yaml new file mode 100644 index 000000000..79a2fe923 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/typo-term/09-gx18-pin-check-b/break.yaml @@ -0,0 +1,4 @@ +base: docs/rhl/breaks/v2/valid/09-gx18-pin-check-b.rhl +class: "typo'd term" +expected_code: RHL-V002 +safe_fix: true diff --git a/docs/rhl/breaks/v2/planted/typo-term/09-gx18-pin-check-b/broken.rhl b/docs/rhl/breaks/v2/planted/typo-term/09-gx18-pin-check-b/broken.rhl new file mode 100644 index 000000000..10fe5ed76 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/typo-term/09-gx18-pin-check-b/broken.rhl @@ -0,0 +1,25 @@ +use vocabulary fleet v2 +use vocabulary tickets v2 + +job "gx18 pin check" + runs on host gx18 + every 1 hour + may read host + may write tickets + + let status be pin staus of "gx18" + + when status is not "pinned" + file ticket in repo "paiml/infra" with + title "gx18 pin check" + label "pin" + end + end + + expect ticket count is at most 1 + + example "gx18 pin check example" + given status is "unpinned" + then ticket count is 1 + end +end diff --git a/docs/rhl/breaks/v2/planted/typo-term/10-gx19-ticket-sweep-b/break.yaml b/docs/rhl/breaks/v2/planted/typo-term/10-gx19-ticket-sweep-b/break.yaml new file mode 100644 index 000000000..d032272ee --- /dev/null +++ b/docs/rhl/breaks/v2/planted/typo-term/10-gx19-ticket-sweep-b/break.yaml @@ -0,0 +1,4 @@ +base: docs/rhl/breaks/v2/valid/10-gx19-ticket-sweep-b.rhl +class: "typo'd term" +expected_code: RHL-V002 +safe_fix: true diff --git a/docs/rhl/breaks/v2/planted/typo-term/10-gx19-ticket-sweep-b/broken.rhl b/docs/rhl/breaks/v2/planted/typo-term/10-gx19-ticket-sweep-b/broken.rhl new file mode 100644 index 000000000..41c20bc85 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/typo-term/10-gx19-ticket-sweep-b/broken.rhl @@ -0,0 +1,25 @@ +use vocabulary fleet v2 +use vocabulary tickets v2 + +job "gx19 ticket sweep" + runs on host gx19 + every 1 hour + may read tickets + may write tickets + + let filed be ticket filed in "paiml/fleet-ops" + + when filed is above 3 + file ticket in repo "paiml/fleet-ops" with + title "gx19 ticket sweep" + label "tickets" + end + end + + expect ticket count is at most 1 + + example "gx19 ticket sweep example" + given filed is 4 + then ticket count is 1 + end +end diff --git a/docs/rhl/breaks/v2/planted/typo-term/11-gx20-var-disk-watch/break.yaml b/docs/rhl/breaks/v2/planted/typo-term/11-gx20-var-disk-watch/break.yaml new file mode 100644 index 000000000..49fe6e3a6 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/typo-term/11-gx20-var-disk-watch/break.yaml @@ -0,0 +1,4 @@ +base: docs/rhl/breaks/v2/valid/11-gx20-var-disk-watch.rhl +class: "typo'd term" +expected_code: RHL-V002 +safe_fix: true diff --git a/docs/rhl/breaks/v2/planted/typo-term/11-gx20-var-disk-watch/broken.rhl b/docs/rhl/breaks/v2/planted/typo-term/11-gx20-var-disk-watch/broken.rhl new file mode 100644 index 000000000..d4563a223 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/typo-term/11-gx20-var-disk-watch/broken.rhl @@ -0,0 +1,25 @@ +use vocabulary fleet v2 +use vocabulary tickets v2 + +job "gx20 var disk watch" + runs on host gx20 + every 1 hour + may read disk + may write tickets + + let free be disk fre of "/var" + + when free is below 30 GB + file ticket in repo "paiml/infra" with + title "gx20 var disk watch" + label "fleet" + end + end + + expect ticket count is at most 1 + + example "gx20 var disk watch example" + given free is 20 GB + then ticket count is 1 + end +end diff --git a/docs/rhl/breaks/v2/planted/typo-term/12-gx21-home-disk-watch/break.yaml b/docs/rhl/breaks/v2/planted/typo-term/12-gx21-home-disk-watch/break.yaml new file mode 100644 index 000000000..ba3bfb928 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/typo-term/12-gx21-home-disk-watch/break.yaml @@ -0,0 +1,4 @@ +base: docs/rhl/breaks/v2/valid/12-gx21-home-disk-watch.rhl +class: "typo'd term" +expected_code: RHL-V002 +safe_fix: true diff --git a/docs/rhl/breaks/v2/planted/typo-term/12-gx21-home-disk-watch/broken.rhl b/docs/rhl/breaks/v2/planted/typo-term/12-gx21-home-disk-watch/broken.rhl new file mode 100644 index 000000000..74d481bf6 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/typo-term/12-gx21-home-disk-watch/broken.rhl @@ -0,0 +1,25 @@ +use vocabulary fleet v2 +use vocabulary tickets v2 + +job "gx21 home disk watch" + runs on host gx21 + every 1 hour + may read disk + may write tickets + + let used be disk usge of "/home" + + when used is above 60 GB + file ticket in repo "paiml/fleet-ops" with + title "gx21 home disk watch" + label "disk" + end + end + + expect ticket count is at most 1 + + example "gx21 home disk watch example" + given used is 65 GB + then ticket count is 1 + end +end diff --git a/docs/rhl/breaks/v2/planted/undeclared-effect/01-gx10-disk-watch/break.yaml b/docs/rhl/breaks/v2/planted/undeclared-effect/01-gx10-disk-watch/break.yaml new file mode 100644 index 000000000..40169b9c9 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/undeclared-effect/01-gx10-disk-watch/break.yaml @@ -0,0 +1,4 @@ +base: docs/rhl/breaks/v2/valid/01-gx10-disk-watch.rhl +class: "undeclared effect" +expected_code: RHL-E001 +safe_fix: false diff --git a/docs/rhl/breaks/v2/planted/undeclared-effect/01-gx10-disk-watch/broken.rhl b/docs/rhl/breaks/v2/planted/undeclared-effect/01-gx10-disk-watch/broken.rhl new file mode 100644 index 000000000..2ae552f57 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/undeclared-effect/01-gx10-disk-watch/broken.rhl @@ -0,0 +1,24 @@ +use vocabulary fleet v2 +use vocabulary tickets v2 + +job "gx10 disk watch" + runs on host gx10 + every 1 hour + may read disk + + let free be disk free of "/" + + when free is below 100 GB + file ticket in repo "paiml/infra" with + title "gx10 disk watch" + label "fleet" + end + end + + expect ticket count is at most 1 + + example "gx10 disk watch example" + given free is 90 GB + then ticket count is 1 + end +end diff --git a/docs/rhl/breaks/v2/planted/undeclared-effect/02-gx11-docker-disk-watch/break.yaml b/docs/rhl/breaks/v2/planted/undeclared-effect/02-gx11-docker-disk-watch/break.yaml new file mode 100644 index 000000000..adef78ce7 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/undeclared-effect/02-gx11-docker-disk-watch/break.yaml @@ -0,0 +1,4 @@ +base: docs/rhl/breaks/v2/valid/02-gx11-docker-disk-watch.rhl +class: "undeclared effect" +expected_code: RHL-E001 +safe_fix: false diff --git a/docs/rhl/breaks/v2/planted/undeclared-effect/02-gx11-docker-disk-watch/broken.rhl b/docs/rhl/breaks/v2/planted/undeclared-effect/02-gx11-docker-disk-watch/broken.rhl new file mode 100644 index 000000000..07e5ce990 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/undeclared-effect/02-gx11-docker-disk-watch/broken.rhl @@ -0,0 +1,24 @@ +use vocabulary fleet v2 +use vocabulary tickets v2 + +job "gx11 docker disk watch" + runs on host gx11 + every 1 hour + may read disk + + let used be disk usage of "/var/lib/docker" + + when used is above 80 GB + file ticket in repo "paiml/fleet-ops" with + title "gx11 docker disk watch" + label "disk" + end + end + + expect ticket count is at most 1 + + example "gx11 docker disk watch example" + given used is 95 GB + then ticket count is 1 + end +end diff --git a/docs/rhl/breaks/v2/planted/undeclared-effect/03-gx12-runner-load-watch/break.yaml b/docs/rhl/breaks/v2/planted/undeclared-effect/03-gx12-runner-load-watch/break.yaml new file mode 100644 index 000000000..7a4ba69fa --- /dev/null +++ b/docs/rhl/breaks/v2/planted/undeclared-effect/03-gx12-runner-load-watch/break.yaml @@ -0,0 +1,4 @@ +base: docs/rhl/breaks/v2/valid/03-gx12-runner-load-watch.rhl +class: "undeclared effect" +expected_code: RHL-E001 +safe_fix: false diff --git a/docs/rhl/breaks/v2/planted/undeclared-effect/03-gx12-runner-load-watch/broken.rhl b/docs/rhl/breaks/v2/planted/undeclared-effect/03-gx12-runner-load-watch/broken.rhl new file mode 100644 index 000000000..cd80f5c63 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/undeclared-effect/03-gx12-runner-load-watch/broken.rhl @@ -0,0 +1,24 @@ +use vocabulary fleet v2 +use vocabulary tickets v2 + +job "gx12 runner load watch" + runs on host gx12 + every 1 hour + may read runner + + let load be runner load of "runner-01" + + when load is above 90 % + file ticket in repo "paiml/ci-ops" with + title "gx12 runner load watch" + label "runner" + end + end + + expect ticket count is at most 1 + + example "gx12 runner load watch example" + given load is 95 % + then ticket count is 1 + end +end diff --git a/docs/rhl/breaks/v2/planted/undeclared-effect/04-gx13-pin-check/break.yaml b/docs/rhl/breaks/v2/planted/undeclared-effect/04-gx13-pin-check/break.yaml new file mode 100644 index 000000000..da8bbd4e4 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/undeclared-effect/04-gx13-pin-check/break.yaml @@ -0,0 +1,4 @@ +base: docs/rhl/breaks/v2/valid/04-gx13-pin-check.rhl +class: "undeclared effect" +expected_code: RHL-E001 +safe_fix: false diff --git a/docs/rhl/breaks/v2/planted/undeclared-effect/04-gx13-pin-check/broken.rhl b/docs/rhl/breaks/v2/planted/undeclared-effect/04-gx13-pin-check/broken.rhl new file mode 100644 index 000000000..50f5fe35c --- /dev/null +++ b/docs/rhl/breaks/v2/planted/undeclared-effect/04-gx13-pin-check/broken.rhl @@ -0,0 +1,24 @@ +use vocabulary fleet v2 +use vocabulary tickets v2 + +job "gx13 pin check" + runs on host gx13 + every 1 hour + may read host + + let status be pin status of "gx13" + + when status is not "pinned" + file ticket in repo "paiml/infra" with + title "gx13 pin check" + label "pin" + end + end + + expect ticket count is at most 1 + + example "gx13 pin check example" + given status is "unpinned" + then ticket count is 1 + end +end diff --git a/docs/rhl/breaks/v2/planted/undeclared-effect/05-gx14-ticket-sweep/break.yaml b/docs/rhl/breaks/v2/planted/undeclared-effect/05-gx14-ticket-sweep/break.yaml new file mode 100644 index 000000000..2966734f1 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/undeclared-effect/05-gx14-ticket-sweep/break.yaml @@ -0,0 +1,4 @@ +base: docs/rhl/breaks/v2/valid/05-gx14-ticket-sweep.rhl +class: "undeclared effect" +expected_code: RHL-E001 +safe_fix: false diff --git a/docs/rhl/breaks/v2/planted/undeclared-effect/05-gx14-ticket-sweep/broken.rhl b/docs/rhl/breaks/v2/planted/undeclared-effect/05-gx14-ticket-sweep/broken.rhl new file mode 100644 index 000000000..1eccb7a86 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/undeclared-effect/05-gx14-ticket-sweep/broken.rhl @@ -0,0 +1,24 @@ +use vocabulary fleet v2 +use vocabulary tickets v2 + +job "gx14 ticket sweep" + runs on host gx14 + every 1 hour + may read tickets + + let filed be tickets filed in "paiml/infra" + + when filed is above 5 + file ticket in repo "paiml/infra" with + title "gx14 ticket sweep" + label "tickets" + end + end + + expect ticket count is at most 1 + + example "gx14 ticket sweep example" + given filed is 7 + then ticket count is 1 + end +end diff --git a/docs/rhl/breaks/v2/planted/undeclared-effect/06-gx15-data-disk-watch/break.yaml b/docs/rhl/breaks/v2/planted/undeclared-effect/06-gx15-data-disk-watch/break.yaml new file mode 100644 index 000000000..6e2ea269f --- /dev/null +++ b/docs/rhl/breaks/v2/planted/undeclared-effect/06-gx15-data-disk-watch/break.yaml @@ -0,0 +1,4 @@ +base: docs/rhl/breaks/v2/valid/06-gx15-data-disk-watch.rhl +class: "undeclared effect" +expected_code: RHL-E001 +safe_fix: false diff --git a/docs/rhl/breaks/v2/planted/undeclared-effect/06-gx15-data-disk-watch/broken.rhl b/docs/rhl/breaks/v2/planted/undeclared-effect/06-gx15-data-disk-watch/broken.rhl new file mode 100644 index 000000000..5fe5ff721 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/undeclared-effect/06-gx15-data-disk-watch/broken.rhl @@ -0,0 +1,24 @@ +use vocabulary fleet v2 +use vocabulary tickets v2 + +job "gx15 data disk watch" + runs on host gx15 + every 1 hour + may read disk + + let free be disk free of "/data" + + when free is below 50 GB + file ticket in repo "paiml/fleet-ops" with + title "gx15 data disk watch" + label "fleet" + end + end + + expect ticket count is at most 1 + + example "gx15 data disk watch example" + given free is 40 GB + then ticket count is 1 + end +end diff --git a/docs/rhl/breaks/v2/planted/undeclared-effect/07-gx16-root-disk-watch/break.yaml b/docs/rhl/breaks/v2/planted/undeclared-effect/07-gx16-root-disk-watch/break.yaml new file mode 100644 index 000000000..ca09bb0b5 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/undeclared-effect/07-gx16-root-disk-watch/break.yaml @@ -0,0 +1,4 @@ +base: docs/rhl/breaks/v2/valid/07-gx16-root-disk-watch.rhl +class: "undeclared effect" +expected_code: RHL-E001 +safe_fix: false diff --git a/docs/rhl/breaks/v2/planted/undeclared-effect/07-gx16-root-disk-watch/broken.rhl b/docs/rhl/breaks/v2/planted/undeclared-effect/07-gx16-root-disk-watch/broken.rhl new file mode 100644 index 000000000..5b1a473e1 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/undeclared-effect/07-gx16-root-disk-watch/broken.rhl @@ -0,0 +1,24 @@ +use vocabulary fleet v2 +use vocabulary tickets v2 + +job "gx16 root disk watch" + runs on host gx16 + every 1 hour + may read disk + + let used be disk usage of "/" + + when used is above 70 GB + file ticket in repo "paiml/infra" with + title "gx16 root disk watch" + label "disk" + end + end + + expect ticket count is at most 1 + + example "gx16 root disk watch example" + given used is 80 GB + then ticket count is 1 + end +end diff --git a/docs/rhl/breaks/v2/planted/undeclared-effect/08-gx17-runner-load-alert/break.yaml b/docs/rhl/breaks/v2/planted/undeclared-effect/08-gx17-runner-load-alert/break.yaml new file mode 100644 index 000000000..4eab3bfbf --- /dev/null +++ b/docs/rhl/breaks/v2/planted/undeclared-effect/08-gx17-runner-load-alert/break.yaml @@ -0,0 +1,4 @@ +base: docs/rhl/breaks/v2/valid/08-gx17-runner-load-alert.rhl +class: "undeclared effect" +expected_code: RHL-E001 +safe_fix: false diff --git a/docs/rhl/breaks/v2/planted/undeclared-effect/08-gx17-runner-load-alert/broken.rhl b/docs/rhl/breaks/v2/planted/undeclared-effect/08-gx17-runner-load-alert/broken.rhl new file mode 100644 index 000000000..7aa4837c0 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/undeclared-effect/08-gx17-runner-load-alert/broken.rhl @@ -0,0 +1,24 @@ +use vocabulary fleet v2 +use vocabulary tickets v2 + +job "gx17 runner load alert" + runs on host gx17 + every 1 hour + may read runner + + let load be runner load of "runner-02" + + when load is above 85 % + file ticket in repo "paiml/ci-ops" with + title "gx17 runner load alert" + label "runner" + end + end + + expect ticket count is at most 1 + + example "gx17 runner load alert example" + given load is 90 % + then ticket count is 1 + end +end diff --git a/docs/rhl/breaks/v2/planted/undeclared-effect/09-gx18-pin-check-b/break.yaml b/docs/rhl/breaks/v2/planted/undeclared-effect/09-gx18-pin-check-b/break.yaml new file mode 100644 index 000000000..cc78923d9 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/undeclared-effect/09-gx18-pin-check-b/break.yaml @@ -0,0 +1,4 @@ +base: docs/rhl/breaks/v2/valid/09-gx18-pin-check-b.rhl +class: "undeclared effect" +expected_code: RHL-E001 +safe_fix: false diff --git a/docs/rhl/breaks/v2/planted/undeclared-effect/09-gx18-pin-check-b/broken.rhl b/docs/rhl/breaks/v2/planted/undeclared-effect/09-gx18-pin-check-b/broken.rhl new file mode 100644 index 000000000..c6a77c82f --- /dev/null +++ b/docs/rhl/breaks/v2/planted/undeclared-effect/09-gx18-pin-check-b/broken.rhl @@ -0,0 +1,24 @@ +use vocabulary fleet v2 +use vocabulary tickets v2 + +job "gx18 pin check" + runs on host gx18 + every 1 hour + may read host + + let status be pin status of "gx18" + + when status is not "pinned" + file ticket in repo "paiml/infra" with + title "gx18 pin check" + label "pin" + end + end + + expect ticket count is at most 1 + + example "gx18 pin check example" + given status is "unpinned" + then ticket count is 1 + end +end diff --git a/docs/rhl/breaks/v2/planted/undeclared-effect/10-gx19-ticket-sweep-b/break.yaml b/docs/rhl/breaks/v2/planted/undeclared-effect/10-gx19-ticket-sweep-b/break.yaml new file mode 100644 index 000000000..cef8424b6 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/undeclared-effect/10-gx19-ticket-sweep-b/break.yaml @@ -0,0 +1,4 @@ +base: docs/rhl/breaks/v2/valid/10-gx19-ticket-sweep-b.rhl +class: "undeclared effect" +expected_code: RHL-E001 +safe_fix: false diff --git a/docs/rhl/breaks/v2/planted/undeclared-effect/10-gx19-ticket-sweep-b/broken.rhl b/docs/rhl/breaks/v2/planted/undeclared-effect/10-gx19-ticket-sweep-b/broken.rhl new file mode 100644 index 000000000..10ea8e7ae --- /dev/null +++ b/docs/rhl/breaks/v2/planted/undeclared-effect/10-gx19-ticket-sweep-b/broken.rhl @@ -0,0 +1,24 @@ +use vocabulary fleet v2 +use vocabulary tickets v2 + +job "gx19 ticket sweep" + runs on host gx19 + every 1 hour + may read tickets + + let filed be tickets filed in "paiml/fleet-ops" + + when filed is above 3 + file ticket in repo "paiml/fleet-ops" with + title "gx19 ticket sweep" + label "tickets" + end + end + + expect ticket count is at most 1 + + example "gx19 ticket sweep example" + given filed is 4 + then ticket count is 1 + end +end diff --git a/docs/rhl/breaks/v2/planted/undeclared-effect/11-gx20-var-disk-watch/break.yaml b/docs/rhl/breaks/v2/planted/undeclared-effect/11-gx20-var-disk-watch/break.yaml new file mode 100644 index 000000000..dbbcbef9e --- /dev/null +++ b/docs/rhl/breaks/v2/planted/undeclared-effect/11-gx20-var-disk-watch/break.yaml @@ -0,0 +1,4 @@ +base: docs/rhl/breaks/v2/valid/11-gx20-var-disk-watch.rhl +class: "undeclared effect" +expected_code: RHL-E001 +safe_fix: false diff --git a/docs/rhl/breaks/v2/planted/undeclared-effect/11-gx20-var-disk-watch/broken.rhl b/docs/rhl/breaks/v2/planted/undeclared-effect/11-gx20-var-disk-watch/broken.rhl new file mode 100644 index 000000000..924055227 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/undeclared-effect/11-gx20-var-disk-watch/broken.rhl @@ -0,0 +1,24 @@ +use vocabulary fleet v2 +use vocabulary tickets v2 + +job "gx20 var disk watch" + runs on host gx20 + every 1 hour + may read disk + + let free be disk free of "/var" + + when free is below 30 GB + file ticket in repo "paiml/infra" with + title "gx20 var disk watch" + label "fleet" + end + end + + expect ticket count is at most 1 + + example "gx20 var disk watch example" + given free is 20 GB + then ticket count is 1 + end +end diff --git a/docs/rhl/breaks/v2/planted/undeclared-effect/12-gx21-home-disk-watch/break.yaml b/docs/rhl/breaks/v2/planted/undeclared-effect/12-gx21-home-disk-watch/break.yaml new file mode 100644 index 000000000..659ed40fc --- /dev/null +++ b/docs/rhl/breaks/v2/planted/undeclared-effect/12-gx21-home-disk-watch/break.yaml @@ -0,0 +1,4 @@ +base: docs/rhl/breaks/v2/valid/12-gx21-home-disk-watch.rhl +class: "undeclared effect" +expected_code: RHL-E001 +safe_fix: false diff --git a/docs/rhl/breaks/v2/planted/undeclared-effect/12-gx21-home-disk-watch/broken.rhl b/docs/rhl/breaks/v2/planted/undeclared-effect/12-gx21-home-disk-watch/broken.rhl new file mode 100644 index 000000000..70ca36373 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/undeclared-effect/12-gx21-home-disk-watch/broken.rhl @@ -0,0 +1,24 @@ +use vocabulary fleet v2 +use vocabulary tickets v2 + +job "gx21 home disk watch" + runs on host gx21 + every 1 hour + may read disk + + let used be disk usage of "/home" + + when used is above 60 GB + file ticket in repo "paiml/fleet-ops" with + title "gx21 home disk watch" + label "disk" + end + end + + expect ticket count is at most 1 + + example "gx21 home disk watch example" + given used is 65 GB + then ticket count is 1 + end +end diff --git a/docs/rhl/breaks/v2/planted/wrong-type/01-gx10-disk-watch/break.yaml b/docs/rhl/breaks/v2/planted/wrong-type/01-gx10-disk-watch/break.yaml new file mode 100644 index 000000000..d9480674f --- /dev/null +++ b/docs/rhl/breaks/v2/planted/wrong-type/01-gx10-disk-watch/break.yaml @@ -0,0 +1,4 @@ +base: docs/rhl/breaks/v2/valid/01-gx10-disk-watch.rhl +class: "wrong type" +expected_code: RHL-T002 +safe_fix: false diff --git a/docs/rhl/breaks/v2/planted/wrong-type/01-gx10-disk-watch/broken.rhl b/docs/rhl/breaks/v2/planted/wrong-type/01-gx10-disk-watch/broken.rhl new file mode 100644 index 000000000..426049e5a --- /dev/null +++ b/docs/rhl/breaks/v2/planted/wrong-type/01-gx10-disk-watch/broken.rhl @@ -0,0 +1,25 @@ +use vocabulary fleet v2 +use vocabulary tickets v2 + +job "gx10 disk watch" + runs on host gx10 + every 1 hour + may read disk + may write tickets + + let free be disk free of "/" + + when free is below "low" + file ticket in repo "paiml/infra" with + title "gx10 disk watch" + label "fleet" + end + end + + expect ticket count is at most 1 + + example "gx10 disk watch example" + given free is 90 GB + then ticket count is 1 + end +end diff --git a/docs/rhl/breaks/v2/planted/wrong-type/02-gx11-docker-disk-watch/break.yaml b/docs/rhl/breaks/v2/planted/wrong-type/02-gx11-docker-disk-watch/break.yaml new file mode 100644 index 000000000..678a2befd --- /dev/null +++ b/docs/rhl/breaks/v2/planted/wrong-type/02-gx11-docker-disk-watch/break.yaml @@ -0,0 +1,4 @@ +base: docs/rhl/breaks/v2/valid/02-gx11-docker-disk-watch.rhl +class: "wrong type" +expected_code: RHL-T002 +safe_fix: false diff --git a/docs/rhl/breaks/v2/planted/wrong-type/02-gx11-docker-disk-watch/broken.rhl b/docs/rhl/breaks/v2/planted/wrong-type/02-gx11-docker-disk-watch/broken.rhl new file mode 100644 index 000000000..ee3136b79 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/wrong-type/02-gx11-docker-disk-watch/broken.rhl @@ -0,0 +1,25 @@ +use vocabulary fleet v2 +use vocabulary tickets v2 + +job "gx11 docker disk watch" + runs on host gx11 + every 1 hour + may read disk + may write tickets + + let used be disk usage of "/var/lib/docker" + + when used is above "low" + file ticket in repo "paiml/fleet-ops" with + title "gx11 docker disk watch" + label "disk" + end + end + + expect ticket count is at most 1 + + example "gx11 docker disk watch example" + given used is 95 GB + then ticket count is 1 + end +end diff --git a/docs/rhl/breaks/v2/planted/wrong-type/03-gx12-runner-load-watch/break.yaml b/docs/rhl/breaks/v2/planted/wrong-type/03-gx12-runner-load-watch/break.yaml new file mode 100644 index 000000000..528f2089d --- /dev/null +++ b/docs/rhl/breaks/v2/planted/wrong-type/03-gx12-runner-load-watch/break.yaml @@ -0,0 +1,4 @@ +base: docs/rhl/breaks/v2/valid/03-gx12-runner-load-watch.rhl +class: "wrong type" +expected_code: RHL-T002 +safe_fix: false diff --git a/docs/rhl/breaks/v2/planted/wrong-type/03-gx12-runner-load-watch/broken.rhl b/docs/rhl/breaks/v2/planted/wrong-type/03-gx12-runner-load-watch/broken.rhl new file mode 100644 index 000000000..cb7e20e27 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/wrong-type/03-gx12-runner-load-watch/broken.rhl @@ -0,0 +1,25 @@ +use vocabulary fleet v2 +use vocabulary tickets v2 + +job "gx12 runner load watch" + runs on host gx12 + every 1 hour + may read runner + may write tickets + + let load be runner load of "runner-01" + + when load is above "low" + file ticket in repo "paiml/ci-ops" with + title "gx12 runner load watch" + label "runner" + end + end + + expect ticket count is at most 1 + + example "gx12 runner load watch example" + given load is 95 % + then ticket count is 1 + end +end diff --git a/docs/rhl/breaks/v2/planted/wrong-type/04-gx13-pin-check/break.yaml b/docs/rhl/breaks/v2/planted/wrong-type/04-gx13-pin-check/break.yaml new file mode 100644 index 000000000..839976608 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/wrong-type/04-gx13-pin-check/break.yaml @@ -0,0 +1,4 @@ +base: docs/rhl/breaks/v2/valid/04-gx13-pin-check.rhl +class: "wrong type" +expected_code: RHL-T002 +safe_fix: false diff --git a/docs/rhl/breaks/v2/planted/wrong-type/04-gx13-pin-check/broken.rhl b/docs/rhl/breaks/v2/planted/wrong-type/04-gx13-pin-check/broken.rhl new file mode 100644 index 000000000..c6b49224b --- /dev/null +++ b/docs/rhl/breaks/v2/planted/wrong-type/04-gx13-pin-check/broken.rhl @@ -0,0 +1,25 @@ +use vocabulary fleet v2 +use vocabulary tickets v2 + +job "gx13 pin check" + runs on host gx13 + every 1 hour + may read host + may write tickets + + let status be pin status of "gx13" + + when status is not 1 + file ticket in repo "paiml/infra" with + title "gx13 pin check" + label "pin" + end + end + + expect ticket count is at most 1 + + example "gx13 pin check example" + given status is "unpinned" + then ticket count is 1 + end +end diff --git a/docs/rhl/breaks/v2/planted/wrong-type/05-gx14-ticket-sweep/break.yaml b/docs/rhl/breaks/v2/planted/wrong-type/05-gx14-ticket-sweep/break.yaml new file mode 100644 index 000000000..5ca5f644c --- /dev/null +++ b/docs/rhl/breaks/v2/planted/wrong-type/05-gx14-ticket-sweep/break.yaml @@ -0,0 +1,4 @@ +base: docs/rhl/breaks/v2/valid/05-gx14-ticket-sweep.rhl +class: "wrong type" +expected_code: RHL-T002 +safe_fix: false diff --git a/docs/rhl/breaks/v2/planted/wrong-type/05-gx14-ticket-sweep/broken.rhl b/docs/rhl/breaks/v2/planted/wrong-type/05-gx14-ticket-sweep/broken.rhl new file mode 100644 index 000000000..f4333d858 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/wrong-type/05-gx14-ticket-sweep/broken.rhl @@ -0,0 +1,25 @@ +use vocabulary fleet v2 +use vocabulary tickets v2 + +job "gx14 ticket sweep" + runs on host gx14 + every 1 hour + may read tickets + may write tickets + + let filed be tickets filed in "paiml/infra" + + when filed is above "low" + file ticket in repo "paiml/infra" with + title "gx14 ticket sweep" + label "tickets" + end + end + + expect ticket count is at most 1 + + example "gx14 ticket sweep example" + given filed is 7 + then ticket count is 1 + end +end diff --git a/docs/rhl/breaks/v2/planted/wrong-type/06-gx15-data-disk-watch/break.yaml b/docs/rhl/breaks/v2/planted/wrong-type/06-gx15-data-disk-watch/break.yaml new file mode 100644 index 000000000..8db88f689 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/wrong-type/06-gx15-data-disk-watch/break.yaml @@ -0,0 +1,4 @@ +base: docs/rhl/breaks/v2/valid/06-gx15-data-disk-watch.rhl +class: "wrong type" +expected_code: RHL-T002 +safe_fix: false diff --git a/docs/rhl/breaks/v2/planted/wrong-type/06-gx15-data-disk-watch/broken.rhl b/docs/rhl/breaks/v2/planted/wrong-type/06-gx15-data-disk-watch/broken.rhl new file mode 100644 index 000000000..52dcf89c4 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/wrong-type/06-gx15-data-disk-watch/broken.rhl @@ -0,0 +1,25 @@ +use vocabulary fleet v2 +use vocabulary tickets v2 + +job "gx15 data disk watch" + runs on host gx15 + every 1 hour + may read disk + may write tickets + + let free be disk free of "/data" + + when free is below "low" + file ticket in repo "paiml/fleet-ops" with + title "gx15 data disk watch" + label "fleet" + end + end + + expect ticket count is at most 1 + + example "gx15 data disk watch example" + given free is 40 GB + then ticket count is 1 + end +end diff --git a/docs/rhl/breaks/v2/planted/wrong-type/07-gx16-root-disk-watch/break.yaml b/docs/rhl/breaks/v2/planted/wrong-type/07-gx16-root-disk-watch/break.yaml new file mode 100644 index 000000000..8c4053a49 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/wrong-type/07-gx16-root-disk-watch/break.yaml @@ -0,0 +1,4 @@ +base: docs/rhl/breaks/v2/valid/07-gx16-root-disk-watch.rhl +class: "wrong type" +expected_code: RHL-T002 +safe_fix: false diff --git a/docs/rhl/breaks/v2/planted/wrong-type/07-gx16-root-disk-watch/broken.rhl b/docs/rhl/breaks/v2/planted/wrong-type/07-gx16-root-disk-watch/broken.rhl new file mode 100644 index 000000000..e87ae08e6 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/wrong-type/07-gx16-root-disk-watch/broken.rhl @@ -0,0 +1,25 @@ +use vocabulary fleet v2 +use vocabulary tickets v2 + +job "gx16 root disk watch" + runs on host gx16 + every 1 hour + may read disk + may write tickets + + let used be disk usage of "/" + + when used is above "low" + file ticket in repo "paiml/infra" with + title "gx16 root disk watch" + label "disk" + end + end + + expect ticket count is at most 1 + + example "gx16 root disk watch example" + given used is 80 GB + then ticket count is 1 + end +end diff --git a/docs/rhl/breaks/v2/planted/wrong-type/08-gx17-runner-load-alert/break.yaml b/docs/rhl/breaks/v2/planted/wrong-type/08-gx17-runner-load-alert/break.yaml new file mode 100644 index 000000000..ee67b4cce --- /dev/null +++ b/docs/rhl/breaks/v2/planted/wrong-type/08-gx17-runner-load-alert/break.yaml @@ -0,0 +1,4 @@ +base: docs/rhl/breaks/v2/valid/08-gx17-runner-load-alert.rhl +class: "wrong type" +expected_code: RHL-T002 +safe_fix: false diff --git a/docs/rhl/breaks/v2/planted/wrong-type/08-gx17-runner-load-alert/broken.rhl b/docs/rhl/breaks/v2/planted/wrong-type/08-gx17-runner-load-alert/broken.rhl new file mode 100644 index 000000000..7d40c4c11 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/wrong-type/08-gx17-runner-load-alert/broken.rhl @@ -0,0 +1,25 @@ +use vocabulary fleet v2 +use vocabulary tickets v2 + +job "gx17 runner load alert" + runs on host gx17 + every 1 hour + may read runner + may write tickets + + let load be runner load of "runner-02" + + when load is above "low" + file ticket in repo "paiml/ci-ops" with + title "gx17 runner load alert" + label "runner" + end + end + + expect ticket count is at most 1 + + example "gx17 runner load alert example" + given load is 90 % + then ticket count is 1 + end +end diff --git a/docs/rhl/breaks/v2/planted/wrong-type/09-gx18-pin-check-b/break.yaml b/docs/rhl/breaks/v2/planted/wrong-type/09-gx18-pin-check-b/break.yaml new file mode 100644 index 000000000..018d56386 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/wrong-type/09-gx18-pin-check-b/break.yaml @@ -0,0 +1,4 @@ +base: docs/rhl/breaks/v2/valid/09-gx18-pin-check-b.rhl +class: "wrong type" +expected_code: RHL-T002 +safe_fix: false diff --git a/docs/rhl/breaks/v2/planted/wrong-type/09-gx18-pin-check-b/broken.rhl b/docs/rhl/breaks/v2/planted/wrong-type/09-gx18-pin-check-b/broken.rhl new file mode 100644 index 000000000..979cfe2df --- /dev/null +++ b/docs/rhl/breaks/v2/planted/wrong-type/09-gx18-pin-check-b/broken.rhl @@ -0,0 +1,25 @@ +use vocabulary fleet v2 +use vocabulary tickets v2 + +job "gx18 pin check" + runs on host gx18 + every 1 hour + may read host + may write tickets + + let status be pin status of "gx18" + + when status is not 1 + file ticket in repo "paiml/infra" with + title "gx18 pin check" + label "pin" + end + end + + expect ticket count is at most 1 + + example "gx18 pin check example" + given status is "unpinned" + then ticket count is 1 + end +end diff --git a/docs/rhl/breaks/v2/planted/wrong-type/10-gx19-ticket-sweep-b/break.yaml b/docs/rhl/breaks/v2/planted/wrong-type/10-gx19-ticket-sweep-b/break.yaml new file mode 100644 index 000000000..2ca7e82d4 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/wrong-type/10-gx19-ticket-sweep-b/break.yaml @@ -0,0 +1,4 @@ +base: docs/rhl/breaks/v2/valid/10-gx19-ticket-sweep-b.rhl +class: "wrong type" +expected_code: RHL-T002 +safe_fix: false diff --git a/docs/rhl/breaks/v2/planted/wrong-type/10-gx19-ticket-sweep-b/broken.rhl b/docs/rhl/breaks/v2/planted/wrong-type/10-gx19-ticket-sweep-b/broken.rhl new file mode 100644 index 000000000..2a2a5914c --- /dev/null +++ b/docs/rhl/breaks/v2/planted/wrong-type/10-gx19-ticket-sweep-b/broken.rhl @@ -0,0 +1,25 @@ +use vocabulary fleet v2 +use vocabulary tickets v2 + +job "gx19 ticket sweep" + runs on host gx19 + every 1 hour + may read tickets + may write tickets + + let filed be tickets filed in "paiml/fleet-ops" + + when filed is above "low" + file ticket in repo "paiml/fleet-ops" with + title "gx19 ticket sweep" + label "tickets" + end + end + + expect ticket count is at most 1 + + example "gx19 ticket sweep example" + given filed is 4 + then ticket count is 1 + end +end diff --git a/docs/rhl/breaks/v2/planted/wrong-type/11-gx20-var-disk-watch/break.yaml b/docs/rhl/breaks/v2/planted/wrong-type/11-gx20-var-disk-watch/break.yaml new file mode 100644 index 000000000..384646353 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/wrong-type/11-gx20-var-disk-watch/break.yaml @@ -0,0 +1,4 @@ +base: docs/rhl/breaks/v2/valid/11-gx20-var-disk-watch.rhl +class: "wrong type" +expected_code: RHL-T002 +safe_fix: false diff --git a/docs/rhl/breaks/v2/planted/wrong-type/11-gx20-var-disk-watch/broken.rhl b/docs/rhl/breaks/v2/planted/wrong-type/11-gx20-var-disk-watch/broken.rhl new file mode 100644 index 000000000..7c2df3b21 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/wrong-type/11-gx20-var-disk-watch/broken.rhl @@ -0,0 +1,25 @@ +use vocabulary fleet v2 +use vocabulary tickets v2 + +job "gx20 var disk watch" + runs on host gx20 + every 1 hour + may read disk + may write tickets + + let free be disk free of "/var" + + when free is below "low" + file ticket in repo "paiml/infra" with + title "gx20 var disk watch" + label "fleet" + end + end + + expect ticket count is at most 1 + + example "gx20 var disk watch example" + given free is 20 GB + then ticket count is 1 + end +end diff --git a/docs/rhl/breaks/v2/planted/wrong-type/12-gx21-home-disk-watch/break.yaml b/docs/rhl/breaks/v2/planted/wrong-type/12-gx21-home-disk-watch/break.yaml new file mode 100644 index 000000000..caccaf82a --- /dev/null +++ b/docs/rhl/breaks/v2/planted/wrong-type/12-gx21-home-disk-watch/break.yaml @@ -0,0 +1,4 @@ +base: docs/rhl/breaks/v2/valid/12-gx21-home-disk-watch.rhl +class: "wrong type" +expected_code: RHL-T002 +safe_fix: false diff --git a/docs/rhl/breaks/v2/planted/wrong-type/12-gx21-home-disk-watch/broken.rhl b/docs/rhl/breaks/v2/planted/wrong-type/12-gx21-home-disk-watch/broken.rhl new file mode 100644 index 000000000..7e71a5454 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/wrong-type/12-gx21-home-disk-watch/broken.rhl @@ -0,0 +1,25 @@ +use vocabulary fleet v2 +use vocabulary tickets v2 + +job "gx21 home disk watch" + runs on host gx21 + every 1 hour + may read disk + may write tickets + + let used be disk usage of "/home" + + when used is above "low" + file ticket in repo "paiml/fleet-ops" with + title "gx21 home disk watch" + label "disk" + end + end + + expect ticket count is at most 1 + + example "gx21 home disk watch example" + given used is 65 GB + then ticket count is 1 + end +end diff --git a/docs/rhl/breaks/v2/planted/wrong-unit/01-gx10-disk-watch/break.yaml b/docs/rhl/breaks/v2/planted/wrong-unit/01-gx10-disk-watch/break.yaml new file mode 100644 index 000000000..b7cc4b2bd --- /dev/null +++ b/docs/rhl/breaks/v2/planted/wrong-unit/01-gx10-disk-watch/break.yaml @@ -0,0 +1,4 @@ +base: docs/rhl/breaks/v2/valid/01-gx10-disk-watch.rhl +class: "wrong unit" +expected_code: RHL-T001 +safe_fix: false diff --git a/docs/rhl/breaks/v2/planted/wrong-unit/01-gx10-disk-watch/broken.rhl b/docs/rhl/breaks/v2/planted/wrong-unit/01-gx10-disk-watch/broken.rhl new file mode 100644 index 000000000..d0bc26d1e --- /dev/null +++ b/docs/rhl/breaks/v2/planted/wrong-unit/01-gx10-disk-watch/broken.rhl @@ -0,0 +1,25 @@ +use vocabulary fleet v2 +use vocabulary tickets v2 + +job "gx10 disk watch" + runs on host gx10 + every 1 hour + may read disk + may write tickets + + let free be disk free of "/" + + when free is below 100 hour + file ticket in repo "paiml/infra" with + title "gx10 disk watch" + label "fleet" + end + end + + expect ticket count is at most 1 + + example "gx10 disk watch example" + given free is 90 GB + then ticket count is 1 + end +end diff --git a/docs/rhl/breaks/v2/planted/wrong-unit/02-gx11-docker-disk-watch/break.yaml b/docs/rhl/breaks/v2/planted/wrong-unit/02-gx11-docker-disk-watch/break.yaml new file mode 100644 index 000000000..461dd79c5 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/wrong-unit/02-gx11-docker-disk-watch/break.yaml @@ -0,0 +1,4 @@ +base: docs/rhl/breaks/v2/valid/02-gx11-docker-disk-watch.rhl +class: "wrong unit" +expected_code: RHL-T001 +safe_fix: false diff --git a/docs/rhl/breaks/v2/planted/wrong-unit/02-gx11-docker-disk-watch/broken.rhl b/docs/rhl/breaks/v2/planted/wrong-unit/02-gx11-docker-disk-watch/broken.rhl new file mode 100644 index 000000000..04a1d6b42 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/wrong-unit/02-gx11-docker-disk-watch/broken.rhl @@ -0,0 +1,25 @@ +use vocabulary fleet v2 +use vocabulary tickets v2 + +job "gx11 docker disk watch" + runs on host gx11 + every 1 hour + may read disk + may write tickets + + let used be disk usage of "/var/lib/docker" + + when used is above 80 hour + file ticket in repo "paiml/fleet-ops" with + title "gx11 docker disk watch" + label "disk" + end + end + + expect ticket count is at most 1 + + example "gx11 docker disk watch example" + given used is 95 GB + then ticket count is 1 + end +end diff --git a/docs/rhl/breaks/v2/planted/wrong-unit/03-gx12-runner-load-watch/break.yaml b/docs/rhl/breaks/v2/planted/wrong-unit/03-gx12-runner-load-watch/break.yaml new file mode 100644 index 000000000..644180abd --- /dev/null +++ b/docs/rhl/breaks/v2/planted/wrong-unit/03-gx12-runner-load-watch/break.yaml @@ -0,0 +1,4 @@ +base: docs/rhl/breaks/v2/valid/03-gx12-runner-load-watch.rhl +class: "wrong unit" +expected_code: RHL-T001 +safe_fix: false diff --git a/docs/rhl/breaks/v2/planted/wrong-unit/03-gx12-runner-load-watch/broken.rhl b/docs/rhl/breaks/v2/planted/wrong-unit/03-gx12-runner-load-watch/broken.rhl new file mode 100644 index 000000000..6c50dca91 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/wrong-unit/03-gx12-runner-load-watch/broken.rhl @@ -0,0 +1,25 @@ +use vocabulary fleet v2 +use vocabulary tickets v2 + +job "gx12 runner load watch" + runs on host gx12 + every 1 hour + may read runner + may write tickets + + let load be runner load of "runner-01" + + when load is above 90 hour + file ticket in repo "paiml/ci-ops" with + title "gx12 runner load watch" + label "runner" + end + end + + expect ticket count is at most 1 + + example "gx12 runner load watch example" + given load is 95 % + then ticket count is 1 + end +end diff --git a/docs/rhl/breaks/v2/planted/wrong-unit/04-gx13-pin-check/break.yaml b/docs/rhl/breaks/v2/planted/wrong-unit/04-gx13-pin-check/break.yaml new file mode 100644 index 000000000..75833b1b4 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/wrong-unit/04-gx13-pin-check/break.yaml @@ -0,0 +1,4 @@ +base: docs/rhl/breaks/v2/valid/04-gx13-pin-check.rhl +class: "wrong unit" +expected_code: RHL-T001 +safe_fix: false diff --git a/docs/rhl/breaks/v2/planted/wrong-unit/04-gx13-pin-check/broken.rhl b/docs/rhl/breaks/v2/planted/wrong-unit/04-gx13-pin-check/broken.rhl new file mode 100644 index 000000000..513f28576 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/wrong-unit/04-gx13-pin-check/broken.rhl @@ -0,0 +1,25 @@ +use vocabulary fleet v2 +use vocabulary tickets v2 + +job "gx13 pin check" + runs on host gx13 + every 1 GB + may read host + may write tickets + + let status be pin status of "gx13" + + when status is not "pinned" + file ticket in repo "paiml/infra" with + title "gx13 pin check" + label "pin" + end + end + + expect ticket count is at most 1 + + example "gx13 pin check example" + given status is "unpinned" + then ticket count is 1 + end +end diff --git a/docs/rhl/breaks/v2/planted/wrong-unit/05-gx14-ticket-sweep/break.yaml b/docs/rhl/breaks/v2/planted/wrong-unit/05-gx14-ticket-sweep/break.yaml new file mode 100644 index 000000000..d667b7919 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/wrong-unit/05-gx14-ticket-sweep/break.yaml @@ -0,0 +1,4 @@ +base: docs/rhl/breaks/v2/valid/05-gx14-ticket-sweep.rhl +class: "wrong unit" +expected_code: RHL-T001 +safe_fix: false diff --git a/docs/rhl/breaks/v2/planted/wrong-unit/05-gx14-ticket-sweep/broken.rhl b/docs/rhl/breaks/v2/planted/wrong-unit/05-gx14-ticket-sweep/broken.rhl new file mode 100644 index 000000000..fe105c55e --- /dev/null +++ b/docs/rhl/breaks/v2/planted/wrong-unit/05-gx14-ticket-sweep/broken.rhl @@ -0,0 +1,25 @@ +use vocabulary fleet v2 +use vocabulary tickets v2 + +job "gx14 ticket sweep" + runs on host gx14 + every 1 hour + may read tickets + may write tickets + + let filed be tickets filed in "paiml/infra" + + when filed is above 5 hour + file ticket in repo "paiml/infra" with + title "gx14 ticket sweep" + label "tickets" + end + end + + expect ticket count is at most 1 + + example "gx14 ticket sweep example" + given filed is 7 + then ticket count is 1 + end +end diff --git a/docs/rhl/breaks/v2/planted/wrong-unit/06-gx15-data-disk-watch/break.yaml b/docs/rhl/breaks/v2/planted/wrong-unit/06-gx15-data-disk-watch/break.yaml new file mode 100644 index 000000000..b4ed18674 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/wrong-unit/06-gx15-data-disk-watch/break.yaml @@ -0,0 +1,4 @@ +base: docs/rhl/breaks/v2/valid/06-gx15-data-disk-watch.rhl +class: "wrong unit" +expected_code: RHL-T001 +safe_fix: false diff --git a/docs/rhl/breaks/v2/planted/wrong-unit/06-gx15-data-disk-watch/broken.rhl b/docs/rhl/breaks/v2/planted/wrong-unit/06-gx15-data-disk-watch/broken.rhl new file mode 100644 index 000000000..648c26a48 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/wrong-unit/06-gx15-data-disk-watch/broken.rhl @@ -0,0 +1,25 @@ +use vocabulary fleet v2 +use vocabulary tickets v2 + +job "gx15 data disk watch" + runs on host gx15 + every 1 hour + may read disk + may write tickets + + let free be disk free of "/data" + + when free is below 50 hour + file ticket in repo "paiml/fleet-ops" with + title "gx15 data disk watch" + label "fleet" + end + end + + expect ticket count is at most 1 + + example "gx15 data disk watch example" + given free is 40 GB + then ticket count is 1 + end +end diff --git a/docs/rhl/breaks/v2/planted/wrong-unit/07-gx16-root-disk-watch/break.yaml b/docs/rhl/breaks/v2/planted/wrong-unit/07-gx16-root-disk-watch/break.yaml new file mode 100644 index 000000000..2349da614 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/wrong-unit/07-gx16-root-disk-watch/break.yaml @@ -0,0 +1,4 @@ +base: docs/rhl/breaks/v2/valid/07-gx16-root-disk-watch.rhl +class: "wrong unit" +expected_code: RHL-T001 +safe_fix: false diff --git a/docs/rhl/breaks/v2/planted/wrong-unit/07-gx16-root-disk-watch/broken.rhl b/docs/rhl/breaks/v2/planted/wrong-unit/07-gx16-root-disk-watch/broken.rhl new file mode 100644 index 000000000..d89211c56 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/wrong-unit/07-gx16-root-disk-watch/broken.rhl @@ -0,0 +1,25 @@ +use vocabulary fleet v2 +use vocabulary tickets v2 + +job "gx16 root disk watch" + runs on host gx16 + every 1 hour + may read disk + may write tickets + + let used be disk usage of "/" + + when used is above 70 hour + file ticket in repo "paiml/infra" with + title "gx16 root disk watch" + label "disk" + end + end + + expect ticket count is at most 1 + + example "gx16 root disk watch example" + given used is 80 GB + then ticket count is 1 + end +end diff --git a/docs/rhl/breaks/v2/planted/wrong-unit/08-gx17-runner-load-alert/break.yaml b/docs/rhl/breaks/v2/planted/wrong-unit/08-gx17-runner-load-alert/break.yaml new file mode 100644 index 000000000..dfc00b3aa --- /dev/null +++ b/docs/rhl/breaks/v2/planted/wrong-unit/08-gx17-runner-load-alert/break.yaml @@ -0,0 +1,4 @@ +base: docs/rhl/breaks/v2/valid/08-gx17-runner-load-alert.rhl +class: "wrong unit" +expected_code: RHL-T001 +safe_fix: false diff --git a/docs/rhl/breaks/v2/planted/wrong-unit/08-gx17-runner-load-alert/broken.rhl b/docs/rhl/breaks/v2/planted/wrong-unit/08-gx17-runner-load-alert/broken.rhl new file mode 100644 index 000000000..5c4984b51 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/wrong-unit/08-gx17-runner-load-alert/broken.rhl @@ -0,0 +1,25 @@ +use vocabulary fleet v2 +use vocabulary tickets v2 + +job "gx17 runner load alert" + runs on host gx17 + every 1 hour + may read runner + may write tickets + + let load be runner load of "runner-02" + + when load is above 85 hour + file ticket in repo "paiml/ci-ops" with + title "gx17 runner load alert" + label "runner" + end + end + + expect ticket count is at most 1 + + example "gx17 runner load alert example" + given load is 90 % + then ticket count is 1 + end +end diff --git a/docs/rhl/breaks/v2/planted/wrong-unit/09-gx18-pin-check-b/break.yaml b/docs/rhl/breaks/v2/planted/wrong-unit/09-gx18-pin-check-b/break.yaml new file mode 100644 index 000000000..1ebca4526 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/wrong-unit/09-gx18-pin-check-b/break.yaml @@ -0,0 +1,4 @@ +base: docs/rhl/breaks/v2/valid/09-gx18-pin-check-b.rhl +class: "wrong unit" +expected_code: RHL-T001 +safe_fix: false diff --git a/docs/rhl/breaks/v2/planted/wrong-unit/09-gx18-pin-check-b/broken.rhl b/docs/rhl/breaks/v2/planted/wrong-unit/09-gx18-pin-check-b/broken.rhl new file mode 100644 index 000000000..999a8be9b --- /dev/null +++ b/docs/rhl/breaks/v2/planted/wrong-unit/09-gx18-pin-check-b/broken.rhl @@ -0,0 +1,25 @@ +use vocabulary fleet v2 +use vocabulary tickets v2 + +job "gx18 pin check" + runs on host gx18 + every 1 GB + may read host + may write tickets + + let status be pin status of "gx18" + + when status is not "pinned" + file ticket in repo "paiml/infra" with + title "gx18 pin check" + label "pin" + end + end + + expect ticket count is at most 1 + + example "gx18 pin check example" + given status is "unpinned" + then ticket count is 1 + end +end diff --git a/docs/rhl/breaks/v2/planted/wrong-unit/10-gx19-ticket-sweep-b/break.yaml b/docs/rhl/breaks/v2/planted/wrong-unit/10-gx19-ticket-sweep-b/break.yaml new file mode 100644 index 000000000..bc7faa7e5 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/wrong-unit/10-gx19-ticket-sweep-b/break.yaml @@ -0,0 +1,4 @@ +base: docs/rhl/breaks/v2/valid/10-gx19-ticket-sweep-b.rhl +class: "wrong unit" +expected_code: RHL-T001 +safe_fix: false diff --git a/docs/rhl/breaks/v2/planted/wrong-unit/10-gx19-ticket-sweep-b/broken.rhl b/docs/rhl/breaks/v2/planted/wrong-unit/10-gx19-ticket-sweep-b/broken.rhl new file mode 100644 index 000000000..0126043ae --- /dev/null +++ b/docs/rhl/breaks/v2/planted/wrong-unit/10-gx19-ticket-sweep-b/broken.rhl @@ -0,0 +1,25 @@ +use vocabulary fleet v2 +use vocabulary tickets v2 + +job "gx19 ticket sweep" + runs on host gx19 + every 1 hour + may read tickets + may write tickets + + let filed be tickets filed in "paiml/fleet-ops" + + when filed is above 3 hour + file ticket in repo "paiml/fleet-ops" with + title "gx19 ticket sweep" + label "tickets" + end + end + + expect ticket count is at most 1 + + example "gx19 ticket sweep example" + given filed is 4 + then ticket count is 1 + end +end diff --git a/docs/rhl/breaks/v2/planted/wrong-unit/11-gx20-var-disk-watch/break.yaml b/docs/rhl/breaks/v2/planted/wrong-unit/11-gx20-var-disk-watch/break.yaml new file mode 100644 index 000000000..6cd6d07d7 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/wrong-unit/11-gx20-var-disk-watch/break.yaml @@ -0,0 +1,4 @@ +base: docs/rhl/breaks/v2/valid/11-gx20-var-disk-watch.rhl +class: "wrong unit" +expected_code: RHL-T001 +safe_fix: false diff --git a/docs/rhl/breaks/v2/planted/wrong-unit/11-gx20-var-disk-watch/broken.rhl b/docs/rhl/breaks/v2/planted/wrong-unit/11-gx20-var-disk-watch/broken.rhl new file mode 100644 index 000000000..b5fe87933 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/wrong-unit/11-gx20-var-disk-watch/broken.rhl @@ -0,0 +1,25 @@ +use vocabulary fleet v2 +use vocabulary tickets v2 + +job "gx20 var disk watch" + runs on host gx20 + every 1 hour + may read disk + may write tickets + + let free be disk free of "/var" + + when free is below 30 hour + file ticket in repo "paiml/infra" with + title "gx20 var disk watch" + label "fleet" + end + end + + expect ticket count is at most 1 + + example "gx20 var disk watch example" + given free is 20 GB + then ticket count is 1 + end +end diff --git a/docs/rhl/breaks/v2/planted/wrong-unit/12-gx21-home-disk-watch/break.yaml b/docs/rhl/breaks/v2/planted/wrong-unit/12-gx21-home-disk-watch/break.yaml new file mode 100644 index 000000000..7a3a6b2f6 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/wrong-unit/12-gx21-home-disk-watch/break.yaml @@ -0,0 +1,4 @@ +base: docs/rhl/breaks/v2/valid/12-gx21-home-disk-watch.rhl +class: "wrong unit" +expected_code: RHL-T001 +safe_fix: false diff --git a/docs/rhl/breaks/v2/planted/wrong-unit/12-gx21-home-disk-watch/broken.rhl b/docs/rhl/breaks/v2/planted/wrong-unit/12-gx21-home-disk-watch/broken.rhl new file mode 100644 index 000000000..cbd019711 --- /dev/null +++ b/docs/rhl/breaks/v2/planted/wrong-unit/12-gx21-home-disk-watch/broken.rhl @@ -0,0 +1,25 @@ +use vocabulary fleet v2 +use vocabulary tickets v2 + +job "gx21 home disk watch" + runs on host gx21 + every 1 hour + may read disk + may write tickets + + let used be disk usage of "/home" + + when used is above 60 hour + file ticket in repo "paiml/fleet-ops" with + title "gx21 home disk watch" + label "disk" + end + end + + expect ticket count is at most 1 + + example "gx21 home disk watch example" + given used is 65 GB + then ticket count is 1 + end +end diff --git a/docs/rhl/breaks/v2/valid/01-gx10-disk-watch.rhl b/docs/rhl/breaks/v2/valid/01-gx10-disk-watch.rhl new file mode 100644 index 000000000..3288ec5dd --- /dev/null +++ b/docs/rhl/breaks/v2/valid/01-gx10-disk-watch.rhl @@ -0,0 +1,25 @@ +use vocabulary fleet v2 +use vocabulary tickets v2 + +job "gx10 disk watch" + runs on host gx10 + every 1 hour + may read disk + may write tickets + + let free be disk free of "/" + + when free is below 100 GB + file ticket in repo "paiml/infra" with + title "gx10 disk watch" + label "fleet" + end + end + + expect ticket count is at most 1 + + example "gx10 disk watch example" + given free is 90 GB + then ticket count is 1 + end +end diff --git a/docs/rhl/breaks/v2/valid/02-gx11-docker-disk-watch.rhl b/docs/rhl/breaks/v2/valid/02-gx11-docker-disk-watch.rhl new file mode 100644 index 000000000..7c473d76f --- /dev/null +++ b/docs/rhl/breaks/v2/valid/02-gx11-docker-disk-watch.rhl @@ -0,0 +1,25 @@ +use vocabulary fleet v2 +use vocabulary tickets v2 + +job "gx11 docker disk watch" + runs on host gx11 + every 1 hour + may read disk + may write tickets + + let used be disk usage of "/var/lib/docker" + + when used is above 80 GB + file ticket in repo "paiml/fleet-ops" with + title "gx11 docker disk watch" + label "disk" + end + end + + expect ticket count is at most 1 + + example "gx11 docker disk watch example" + given used is 95 GB + then ticket count is 1 + end +end diff --git a/docs/rhl/breaks/v2/valid/03-gx12-runner-load-watch.rhl b/docs/rhl/breaks/v2/valid/03-gx12-runner-load-watch.rhl new file mode 100644 index 000000000..65f461053 --- /dev/null +++ b/docs/rhl/breaks/v2/valid/03-gx12-runner-load-watch.rhl @@ -0,0 +1,25 @@ +use vocabulary fleet v2 +use vocabulary tickets v2 + +job "gx12 runner load watch" + runs on host gx12 + every 1 hour + may read runner + may write tickets + + let load be runner load of "runner-01" + + when load is above 90 % + file ticket in repo "paiml/ci-ops" with + title "gx12 runner load watch" + label "runner" + end + end + + expect ticket count is at most 1 + + example "gx12 runner load watch example" + given load is 95 % + then ticket count is 1 + end +end diff --git a/docs/rhl/breaks/v2/valid/04-gx13-pin-check.rhl b/docs/rhl/breaks/v2/valid/04-gx13-pin-check.rhl new file mode 100644 index 000000000..51d49cc76 --- /dev/null +++ b/docs/rhl/breaks/v2/valid/04-gx13-pin-check.rhl @@ -0,0 +1,25 @@ +use vocabulary fleet v2 +use vocabulary tickets v2 + +job "gx13 pin check" + runs on host gx13 + every 1 hour + may read host + may write tickets + + let status be pin status of "gx13" + + when status is not "pinned" + file ticket in repo "paiml/infra" with + title "gx13 pin check" + label "pin" + end + end + + expect ticket count is at most 1 + + example "gx13 pin check example" + given status is "unpinned" + then ticket count is 1 + end +end diff --git a/docs/rhl/breaks/v2/valid/05-gx14-ticket-sweep.rhl b/docs/rhl/breaks/v2/valid/05-gx14-ticket-sweep.rhl new file mode 100644 index 000000000..48aae210c --- /dev/null +++ b/docs/rhl/breaks/v2/valid/05-gx14-ticket-sweep.rhl @@ -0,0 +1,25 @@ +use vocabulary fleet v2 +use vocabulary tickets v2 + +job "gx14 ticket sweep" + runs on host gx14 + every 1 hour + may read tickets + may write tickets + + let filed be tickets filed in "paiml/infra" + + when filed is above 5 + file ticket in repo "paiml/infra" with + title "gx14 ticket sweep" + label "tickets" + end + end + + expect ticket count is at most 1 + + example "gx14 ticket sweep example" + given filed is 7 + then ticket count is 1 + end +end diff --git a/docs/rhl/breaks/v2/valid/06-gx15-data-disk-watch.rhl b/docs/rhl/breaks/v2/valid/06-gx15-data-disk-watch.rhl new file mode 100644 index 000000000..5936dff49 --- /dev/null +++ b/docs/rhl/breaks/v2/valid/06-gx15-data-disk-watch.rhl @@ -0,0 +1,25 @@ +use vocabulary fleet v2 +use vocabulary tickets v2 + +job "gx15 data disk watch" + runs on host gx15 + every 1 hour + may read disk + may write tickets + + let free be disk free of "/data" + + when free is below 50 GB + file ticket in repo "paiml/fleet-ops" with + title "gx15 data disk watch" + label "fleet" + end + end + + expect ticket count is at most 1 + + example "gx15 data disk watch example" + given free is 40 GB + then ticket count is 1 + end +end diff --git a/docs/rhl/breaks/v2/valid/07-gx16-root-disk-watch.rhl b/docs/rhl/breaks/v2/valid/07-gx16-root-disk-watch.rhl new file mode 100644 index 000000000..4ec8a6196 --- /dev/null +++ b/docs/rhl/breaks/v2/valid/07-gx16-root-disk-watch.rhl @@ -0,0 +1,25 @@ +use vocabulary fleet v2 +use vocabulary tickets v2 + +job "gx16 root disk watch" + runs on host gx16 + every 1 hour + may read disk + may write tickets + + let used be disk usage of "/" + + when used is above 70 GB + file ticket in repo "paiml/infra" with + title "gx16 root disk watch" + label "disk" + end + end + + expect ticket count is at most 1 + + example "gx16 root disk watch example" + given used is 80 GB + then ticket count is 1 + end +end diff --git a/docs/rhl/breaks/v2/valid/08-gx17-runner-load-alert.rhl b/docs/rhl/breaks/v2/valid/08-gx17-runner-load-alert.rhl new file mode 100644 index 000000000..bc2f0c0ca --- /dev/null +++ b/docs/rhl/breaks/v2/valid/08-gx17-runner-load-alert.rhl @@ -0,0 +1,25 @@ +use vocabulary fleet v2 +use vocabulary tickets v2 + +job "gx17 runner load alert" + runs on host gx17 + every 1 hour + may read runner + may write tickets + + let load be runner load of "runner-02" + + when load is above 85 % + file ticket in repo "paiml/ci-ops" with + title "gx17 runner load alert" + label "runner" + end + end + + expect ticket count is at most 1 + + example "gx17 runner load alert example" + given load is 90 % + then ticket count is 1 + end +end diff --git a/docs/rhl/breaks/v2/valid/09-gx18-pin-check-b.rhl b/docs/rhl/breaks/v2/valid/09-gx18-pin-check-b.rhl new file mode 100644 index 000000000..25325bfbd --- /dev/null +++ b/docs/rhl/breaks/v2/valid/09-gx18-pin-check-b.rhl @@ -0,0 +1,25 @@ +use vocabulary fleet v2 +use vocabulary tickets v2 + +job "gx18 pin check" + runs on host gx18 + every 1 hour + may read host + may write tickets + + let status be pin status of "gx18" + + when status is not "pinned" + file ticket in repo "paiml/infra" with + title "gx18 pin check" + label "pin" + end + end + + expect ticket count is at most 1 + + example "gx18 pin check example" + given status is "unpinned" + then ticket count is 1 + end +end diff --git a/docs/rhl/breaks/v2/valid/10-gx19-ticket-sweep-b.rhl b/docs/rhl/breaks/v2/valid/10-gx19-ticket-sweep-b.rhl new file mode 100644 index 000000000..2b494bef3 --- /dev/null +++ b/docs/rhl/breaks/v2/valid/10-gx19-ticket-sweep-b.rhl @@ -0,0 +1,25 @@ +use vocabulary fleet v2 +use vocabulary tickets v2 + +job "gx19 ticket sweep" + runs on host gx19 + every 1 hour + may read tickets + may write tickets + + let filed be tickets filed in "paiml/fleet-ops" + + when filed is above 3 + file ticket in repo "paiml/fleet-ops" with + title "gx19 ticket sweep" + label "tickets" + end + end + + expect ticket count is at most 1 + + example "gx19 ticket sweep example" + given filed is 4 + then ticket count is 1 + end +end diff --git a/docs/rhl/breaks/v2/valid/11-gx20-var-disk-watch.rhl b/docs/rhl/breaks/v2/valid/11-gx20-var-disk-watch.rhl new file mode 100644 index 000000000..60fe4f3ce --- /dev/null +++ b/docs/rhl/breaks/v2/valid/11-gx20-var-disk-watch.rhl @@ -0,0 +1,25 @@ +use vocabulary fleet v2 +use vocabulary tickets v2 + +job "gx20 var disk watch" + runs on host gx20 + every 1 hour + may read disk + may write tickets + + let free be disk free of "/var" + + when free is below 30 GB + file ticket in repo "paiml/infra" with + title "gx20 var disk watch" + label "fleet" + end + end + + expect ticket count is at most 1 + + example "gx20 var disk watch example" + given free is 20 GB + then ticket count is 1 + end +end diff --git a/docs/rhl/breaks/v2/valid/12-gx21-home-disk-watch.rhl b/docs/rhl/breaks/v2/valid/12-gx21-home-disk-watch.rhl new file mode 100644 index 000000000..45fdf40c3 --- /dev/null +++ b/docs/rhl/breaks/v2/valid/12-gx21-home-disk-watch.rhl @@ -0,0 +1,25 @@ +use vocabulary fleet v2 +use vocabulary tickets v2 + +job "gx21 home disk watch" + runs on host gx21 + every 1 hour + may read disk + may write tickets + + let used be disk usage of "/home" + + when used is above 60 GB + file ticket in repo "paiml/fleet-ops" with + title "gx21 home disk watch" + label "disk" + end + end + + expect ticket count is at most 1 + + example "gx21 home disk watch example" + given used is 65 GB + then ticket count is 1 + end +end From f1fb92b9e5526c4bc0b7f69dfbf24c527cab71c2 Mon Sep 17 00:00:00 2001 From: Noah Gift Date: Wed, 23 Sep 2026 13:31:10 +0200 Subject: [PATCH 3/9] RHL-16: pre-registration manifest adds the v2 files; rhl-16-v2.md - docs/rhl/PREREGISTRATION.sha256 regenerated with its documented command: 173 added hashes (156 v2 corpus files, 2 vocabularies, 15 contracts) and the file count; no v1 hash changed. - docs/rhl/rhl-16-v2.md: what changed v1 -> v2 and why, per program; the attribute rule; the no-effect measure rule. No diagnostic code added. Pmat-Ticket: RHLGA-1 Co-Authored-By: Claude Opus 5.5 (1M context) --- docs/rhl/PREREGISTRATION.sha256 | 175 +++++++++++++++++++++++++++++++- docs/rhl/rhl-16-v2.md | 94 +++++++++++++++++ 2 files changed, 268 insertions(+), 1 deletion(-) create mode 100644 docs/rhl/rhl-16-v2.md diff --git a/docs/rhl/PREREGISTRATION.sha256 b/docs/rhl/PREREGISTRATION.sha256 index de5d649d4..1618bd117 100644 --- a/docs/rhl/PREREGISTRATION.sha256 +++ b/docs/rhl/PREREGISTRATION.sha256 @@ -9,23 +9,38 @@ # Regenerate: RHL0_WRITE_MANIFEST=1 cargo test --lib -p ruchy \ # rhl_manifest_0_regenerate -- --ignored # -# files: 301 +# files: 474 c8d438383d8a35c73f3151200f6487464f43493069d852022accc6d8d8de056b contracts/rhl-fleet-disk-free-of-v1.yaml +1a1190a008e55430fcb72a9bfaa7cf37dd7aae33a59e105b3b21e69b9f2be228 contracts/rhl-fleet-disk-free-of-v2.yaml 8b62ba758018adb354c0ada4fcbdb40bc71bafb773165a30d9383c0cbbf2fdd7 contracts/rhl-fleet-disk-usage-of-v1.yaml +8c475726b68f30f99bc59eb27c43d8570096f35d68474d9d0da469aae98ea48a contracts/rhl-fleet-disk-usage-of-v2.yaml 169283b8c6d1feada731456e19b6430a5174f527f22912e2d8322d0f68fe0a93 contracts/rhl-fleet-gb-v1.yaml +d315a4a672b6fb65a63b3e022662fad5583583930fde89bdf14617362c1433c3 contracts/rhl-fleet-gb-v2.yaml 51227ba39e62fa8efa4e17d9d271af7321907c40337c8c85182067c5d25fc498 contracts/rhl-fleet-host-v1.yaml +6be15b803b602ce4470f8fb2180c3356ab4fc39732b364a5326d185f9e7e547f contracts/rhl-fleet-host-v2.yaml a18dcb5a3447679f2dd95f824a6629e649deb82246760c24847191a9e0ddd068 contracts/rhl-fleet-hour-v1.yaml +76caaac7b6a21766f297e550bd05bc87d7a4813bf73d19bfc6bbdf884d5d32ca contracts/rhl-fleet-hour-v2.yaml 395f65fb8a5aedf65e8650223cbb58d4ab322f048d484ccebad7a9e6d4d6c312 contracts/rhl-fleet-pin-status-of-v1.yaml +0f84421fe303482a514692daf21c3b3e67d804b7ba57ad517bcfabc18936f61d contracts/rhl-fleet-pin-status-of-v2.yaml 3d6f512269aa9ae278e3dd83dd4ffb5480ae866e5dec2829e09cf3295320fb0f contracts/rhl-fleet-runner-load-of-v1.yaml +e058a56c3bc73554af3ae5879a53487e62baa49dbf00a213be31bdc14dd3ae1d contracts/rhl-fleet-runner-load-of-v2.yaml db3201bc894d17a95e8f35f5ce04c7f6b2a11b0fe54c4592091460909411ef62 contracts/rhl-fleet-runner-v1.yaml +56e7423a667c78f75178b8adc12041f141f1223a0807c1e59b8ca7061537b5bc contracts/rhl-fleet-runner-v2.yaml ee7c394faedfce57a89c71538f27d6ece01bafba99f48c1631a7b4b4d546d955 contracts/rhl-pre-registration-v1.yaml ff4d5f7a6310af829dc3393efc6c07ecf58842b0e794d00d7a0ad85849aae742 contracts/rhl-tickets-file-ticket-v1.yaml +eb3bad52cede3575654d65fac0f3a612475b49d2608ff3f5e35b21bb3ba45b6a contracts/rhl-tickets-file-ticket-v2.yaml 0545ddb72e8c962e668138c1b7ab76935330e9c5d671259317eda12a42fce5b3 contracts/rhl-tickets-label-ticket-v1.yaml +55be82d11a2fc676e1052a6ff7bb8716e6f759e325337bbdcf867cb46f8b2a0b contracts/rhl-tickets-label-ticket-v2.yaml 4e2e82b5fd3709808d3460167fea424762caa1ac011aaf92d9c4c645e9d73fd6 contracts/rhl-tickets-repo-v1.yaml +9eb25170ed379543a1e563974c1518f900d2a7f990edfa7a3f3424f1e805efbf contracts/rhl-tickets-repo-v2.yaml +4027eb6d510f3633084e5f28060dcdbc4504aba1cb5d95498bcf626c935b0605 contracts/rhl-tickets-ticket-count-v2.yaml 438a7579cda38055d3fa6c65d2096bfc674e5ad441c74712a94bec40952cb081 contracts/rhl-tickets-ticket-label-v1.yaml +192c0949df951c63c8348fd2f116b4285bfc6d537cea818c79467d001d8fd2da contracts/rhl-tickets-ticket-label-v2.yaml 3c6d8ad6ad19c8b968c27737d7a31ce35345336f9dea34cf967f4397c8d13bbe contracts/rhl-tickets-ticket-title-v1.yaml +5f792ba857a23c25328995204e55dba33ca7d4783dfa672f3f57cd867336f2d6 contracts/rhl-tickets-ticket-title-v2.yaml 898b0b1fdde1ef30d54686635ba38dc6463f0490fc6963e3f9c8a5b308db17c7 contracts/rhl-tickets-tickets-filed-in-v1.yaml +e385aa114554e616fc98975ba6495163cd098fa3e5741d20fc3f44f3633b8c52 contracts/rhl-tickets-tickets-filed-in-v2.yaml 223a0be9ceb6c5d5d68151d7b90f7e6c3a11ddb75920c5d131394ff67bf8d8c5 docs/rhl/breaks/planted/missing-end/01-gx10-disk-watch/break.yaml 79f4440eec56565dfc6317c71c4ff5095e6e21be2d1f44fe7f501a33b67ebd8e docs/rhl/breaks/planted/missing-end/01-gx10-disk-watch/broken.rhl 0e85cf55ec425524366ee648da8461d85042c8434cb436067a0418b40aea14f9 docs/rhl/breaks/planted/missing-end/02-gx11-docker-disk-watch/break.yaml @@ -170,6 +185,162 @@ a78233ca22a4b756abc61715cd8a020c20a82346a80e0e3a97c2bc915aaac64a docs/rhl/break fffaf3b6a1a956701149bc7b0ef8d652bb37f43bdccf920015a8c32c1ec3b98f docs/rhl/breaks/planted/wrong-unit/11-gx20-var-disk-watch/broken.rhl 54f685b405fcf244df37749ef3ee888113fe050be09b175480d13d00ca19ce9a docs/rhl/breaks/planted/wrong-unit/12-gx21-home-disk-watch/break.yaml 8852f1f5c79d8c5144eeb2ca85ff001692b3a7585991a0fefd7827bbb38821d7 docs/rhl/breaks/planted/wrong-unit/12-gx21-home-disk-watch/broken.rhl +e6f94ec0edadd09dae272418434c47fc116bff064aa227b031e14b17c508b09c docs/rhl/breaks/v2/planted/missing-end/01-gx10-disk-watch/break.yaml +ae34b9b8edbe9cbaa5e851ef3efe92b051db03a7351f694e54baf2e893f31a8a docs/rhl/breaks/v2/planted/missing-end/01-gx10-disk-watch/broken.rhl +e3f4bb54a2b4f909c85c61dd808e9e142d09a0c823663b32632728474f3c0798 docs/rhl/breaks/v2/planted/missing-end/02-gx11-docker-disk-watch/break.yaml +94c39bba0533b84121fd492ac15019ffd029c588ba41343370ae36f465c1a51c docs/rhl/breaks/v2/planted/missing-end/02-gx11-docker-disk-watch/broken.rhl +8a29180ee36a1bd5dbdf1280e4446d4d5f0d437f796e1ce44e2c53c0444f47b3 docs/rhl/breaks/v2/planted/missing-end/03-gx12-runner-load-watch/break.yaml +c56f89af70d16262f720343f1878d1b0dfbbdbdc39e81af6c4f09487a11ba6c9 docs/rhl/breaks/v2/planted/missing-end/03-gx12-runner-load-watch/broken.rhl +ae82897c00fddd290fe38dee7b596a23b9bb8df9c2409ac7962b8de70d131746 docs/rhl/breaks/v2/planted/missing-end/04-gx13-pin-check/break.yaml +ab359de89fd35adfc41c35a7878748b9450340168279bc197479521c676033e8 docs/rhl/breaks/v2/planted/missing-end/04-gx13-pin-check/broken.rhl +3d3ca83686482afc5b5df408cf449122b4a2f84f26a90994b7eb016a27b6e426 docs/rhl/breaks/v2/planted/missing-end/05-gx14-ticket-sweep/break.yaml +4b5e995026588f4a4ff14207bdda7d82f846efa160480e905d6baa26a3c8e502 docs/rhl/breaks/v2/planted/missing-end/05-gx14-ticket-sweep/broken.rhl +60da40c281f2d6f0c7dd99d176d7615d38c25388aa79ca438fffd3237828f66a docs/rhl/breaks/v2/planted/missing-end/06-gx15-data-disk-watch/break.yaml +7649263ca3075f2c01a321823c8a7b9105adac8596c863dadf0b34edbf387eb5 docs/rhl/breaks/v2/planted/missing-end/06-gx15-data-disk-watch/broken.rhl +122d5b3add1f8fa87160b7c0db2d5cad11fd8e4aeecfb1e64e7c5a8ea4190fea docs/rhl/breaks/v2/planted/missing-end/07-gx16-root-disk-watch/break.yaml +83d17fdf6844a63760fa56f5e3039af0de7bed4bbf5f481348950ef788df221c docs/rhl/breaks/v2/planted/missing-end/07-gx16-root-disk-watch/broken.rhl +e15b74118525e2e57128c6dcbca5909920ee5ec29df8dceb779eacbf0ee36958 docs/rhl/breaks/v2/planted/missing-end/08-gx17-runner-load-alert/break.yaml +7d903e9aba98718f3c7d6766284274d32127d291781a7b193c5e55b26eedc9ea docs/rhl/breaks/v2/planted/missing-end/08-gx17-runner-load-alert/broken.rhl +3f6be756beb579fc36535476e726e205b296013145a3092045592c43bf1a591f docs/rhl/breaks/v2/planted/missing-end/09-gx18-pin-check-b/break.yaml +1aa5cdb83da1c2ffa0353efaf61cb901367b6d90493e91ffe8e311c5e15c0eb3 docs/rhl/breaks/v2/planted/missing-end/09-gx18-pin-check-b/broken.rhl +d6fcb764eb6a9f6cdf1527de3293bd445bed551da2ed4ef112a5b61945f2d600 docs/rhl/breaks/v2/planted/missing-end/10-gx19-ticket-sweep-b/break.yaml +33cbfb746e59c0a5ae71b3000fa4bb1239e9a247929605f622b6072b94dd8ca7 docs/rhl/breaks/v2/planted/missing-end/10-gx19-ticket-sweep-b/broken.rhl +f8c3066e574b05e96781e982fccb2f6d73de6a67a5bd932cb00109867575c6dc docs/rhl/breaks/v2/planted/missing-end/11-gx20-var-disk-watch/break.yaml +b6b91f49cda4d88928d62923f294166c6eb59bcc9bd5f0618983a714b814907f docs/rhl/breaks/v2/planted/missing-end/11-gx20-var-disk-watch/broken.rhl +d9c9d498e94c653b5eb5f54ac25598bf2d3d2261da6a9c39bd24682fa6676872 docs/rhl/breaks/v2/planted/missing-end/12-gx21-home-disk-watch/break.yaml +0b45a0dadda449b99fe95f55963ec252c07897d86f5ca97992f1b5f6d63fc4d1 docs/rhl/breaks/v2/planted/missing-end/12-gx21-home-disk-watch/broken.rhl +e7cacaab60b01962940e53607c3d7795e0f517cf2814fa41dbedd69fc524beb8 docs/rhl/breaks/v2/planted/two-candidate-typo/01-gx10-disk-watch/break.yaml +4e7098f2f31134e097232965f9feb1db954f7822e111b1985a255a57f053dafb docs/rhl/breaks/v2/planted/two-candidate-typo/01-gx10-disk-watch/broken.rhl +4ffca75c04502ce4e44b3d0aa5ec54e9cb0a28dc9916fc37bac884614195c222 docs/rhl/breaks/v2/planted/two-candidate-typo/02-gx11-docker-disk-watch/break.yaml +2c61d2e48b7e2931a96ed0d2c848d6eb9de6c9c1597daac00e74ab932b26cf5f docs/rhl/breaks/v2/planted/two-candidate-typo/02-gx11-docker-disk-watch/broken.rhl +4ecd58c44e311e65acd6fe26db4a2171846b45068626c87ef6a25ff5112cb6b9 docs/rhl/breaks/v2/planted/two-candidate-typo/03-gx12-runner-load-watch/break.yaml +f5d3b8f1cd5358d06b67c3018d55ac5d326d1def130ce4bee1317f249a04ffb7 docs/rhl/breaks/v2/planted/two-candidate-typo/03-gx12-runner-load-watch/broken.rhl +16058f9c61f51907b1a9e0fb4e020615a7743f15c6bc264d5bf2c29af7b09e3b docs/rhl/breaks/v2/planted/two-candidate-typo/04-gx13-pin-check/break.yaml +9764c72941bf100e123a43df89e085f4fee2f01ee8d0b925635ab43dea6ae7da docs/rhl/breaks/v2/planted/two-candidate-typo/04-gx13-pin-check/broken.rhl +e46414c12fbe56e6d3f25cd35afe73f6c228ef4c1e532d52e22b0453c1e261ca docs/rhl/breaks/v2/planted/two-candidate-typo/05-gx14-ticket-sweep/break.yaml +17cbbe52b9fe2a86e0a02265b1fff249fc0ed32982fdb261fe80d0c60ff60bd7 docs/rhl/breaks/v2/planted/two-candidate-typo/05-gx14-ticket-sweep/broken.rhl +b54a4ef0024d0c325cc3c03de51d479e6ef1634c701e6200f901e2dc6739aaad docs/rhl/breaks/v2/planted/two-candidate-typo/06-gx15-data-disk-watch/break.yaml +fcf9df4a50f8a15ba3b5e7618c00d75de11ea30a47c36243109bccc85e8c297d docs/rhl/breaks/v2/planted/two-candidate-typo/06-gx15-data-disk-watch/broken.rhl +ad21c982a53f265eaadd74422aa09a8cd8202fb7db6dcf921cc02dbcbd4916c3 docs/rhl/breaks/v2/planted/two-candidate-typo/07-gx16-root-disk-watch/break.yaml +45de1a732fcd15b8a72f73dbac5af33fc16e83c70b052430c86b24aa95cb774b docs/rhl/breaks/v2/planted/two-candidate-typo/07-gx16-root-disk-watch/broken.rhl +915e4894e7771531313ec881848996863cd8f724b158d37f666633f0d585cb4a docs/rhl/breaks/v2/planted/two-candidate-typo/08-gx17-runner-load-alert/break.yaml +05c7b079a8db446d120b5783e895c2055644fb82f184befc754023433a84fc2e docs/rhl/breaks/v2/planted/two-candidate-typo/08-gx17-runner-load-alert/broken.rhl +8525f3835258b52d6590755bef7b050ed6de06c23d7eb964d2245b5c4910bb6d docs/rhl/breaks/v2/planted/two-candidate-typo/09-gx18-pin-check-b/break.yaml +fcc67a099ea68f753758d7a796c793caf6348b2954dbb7ec440676146a7588a0 docs/rhl/breaks/v2/planted/two-candidate-typo/09-gx18-pin-check-b/broken.rhl +25ad48b2163a0b34e78fc8023cb4ecd6096238cd1c6a927dccd02c979214bec4 docs/rhl/breaks/v2/planted/two-candidate-typo/10-gx19-ticket-sweep-b/break.yaml +8baa660c1fb171729a9321af723d3d809af62101187f91d60b9b219e4beff679 docs/rhl/breaks/v2/planted/two-candidate-typo/10-gx19-ticket-sweep-b/broken.rhl +56b92c5984484fc31ea350dc5f5160bf3c9390f5be75b59da2daef4b482989b3 docs/rhl/breaks/v2/planted/two-candidate-typo/11-gx20-var-disk-watch/break.yaml +c34a36194e4304a634951968a85a22cc78bbba58fca74ab0e4388b3ddfc800c4 docs/rhl/breaks/v2/planted/two-candidate-typo/11-gx20-var-disk-watch/broken.rhl +366f5e7ea758fa71351551925ca30d1d8a166118e531a22a6dacbef2bb5286c2 docs/rhl/breaks/v2/planted/two-candidate-typo/12-gx21-home-disk-watch/break.yaml +48c9bfdabc90ff5b25743214dac110f0edd444908cff6fabcdca41ec6736f742 docs/rhl/breaks/v2/planted/two-candidate-typo/12-gx21-home-disk-watch/broken.rhl +2254f1b6a94d1436c02f2ad8b1960d66ff83c9e500c22e7715fd88f0544b506d docs/rhl/breaks/v2/planted/typo-term/01-gx10-disk-watch/break.yaml +13ccc14b519b19343b8aa7ecd932ebd387a75433813c875668dcefdb92de52f7 docs/rhl/breaks/v2/planted/typo-term/01-gx10-disk-watch/broken.rhl +1397079aeb2939f40e813d4c9409e1c7d97a3b597af00633b0188e7dabb3f6a6 docs/rhl/breaks/v2/planted/typo-term/02-gx11-docker-disk-watch/break.yaml +36380283e01c1a0993ec865ed9c94c49eb2a8462c158050a624d3ba99df01304 docs/rhl/breaks/v2/planted/typo-term/02-gx11-docker-disk-watch/broken.rhl +daef4854a927e15f9672d641806250401bfb982b2a95354d89327fd01f3fe85e docs/rhl/breaks/v2/planted/typo-term/03-gx12-runner-load-watch/break.yaml +e70365db03a982f9c8abee8603731b264c4d4db735b5fc6da8991b07bfa75382 docs/rhl/breaks/v2/planted/typo-term/03-gx12-runner-load-watch/broken.rhl +997cdf7617dea501b233743d74a17b0f1163b420a331a8008cfea4678f4ebdd3 docs/rhl/breaks/v2/planted/typo-term/04-gx13-pin-check/break.yaml +de2c821a46f9c6b841b50ac2735dff62dd394d65cb141b9e1a36c7f1d330902d docs/rhl/breaks/v2/planted/typo-term/04-gx13-pin-check/broken.rhl +1a3ca403a83d57266d177b789c743c19b7bc06a9caefe788a6799b42258f43cb docs/rhl/breaks/v2/planted/typo-term/05-gx14-ticket-sweep/break.yaml +0a6079aaae953b88f6484a7c122307a45cd712e2a90a6f673abed6cadc1fdfe7 docs/rhl/breaks/v2/planted/typo-term/05-gx14-ticket-sweep/broken.rhl +ced3a05b1b0357d116a94e79aed8bd8c143638c7014d1951d838d2d6f3e16c28 docs/rhl/breaks/v2/planted/typo-term/06-gx15-data-disk-watch/break.yaml +8b25105553d18674d318123a68d0fae8e7fe89c57e4d7f3ca7d7b265953c1f61 docs/rhl/breaks/v2/planted/typo-term/06-gx15-data-disk-watch/broken.rhl +15d268a701becdaebce173d880ca56e43df289c9d3a891b2afed567b4f5dad12 docs/rhl/breaks/v2/planted/typo-term/07-gx16-root-disk-watch/break.yaml +782449772fe5ea18ed382d51a5f1917b272237d3ab82d3428eb117e22162ce7b docs/rhl/breaks/v2/planted/typo-term/07-gx16-root-disk-watch/broken.rhl +e4fa710adc0e213725fe12d16e8b441d9fd80a0127cf106bfd0b1a2a2e61617b docs/rhl/breaks/v2/planted/typo-term/08-gx17-runner-load-alert/break.yaml +cc88b56fe4cc77501f2206042037682ce61492e3e98e9481b90d68d094f87a74 docs/rhl/breaks/v2/planted/typo-term/08-gx17-runner-load-alert/broken.rhl +7b71d69dfa38e7454a8e8a3ac4189cc6492343e1649201b294ea7552578c59fb docs/rhl/breaks/v2/planted/typo-term/09-gx18-pin-check-b/break.yaml +b225884d5620e81ed0f70fc1ab0fbcc4c7f32f0cbea51a2b3650ce0640a112a7 docs/rhl/breaks/v2/planted/typo-term/09-gx18-pin-check-b/broken.rhl +2ebff17299f151ff525c2785dbdad1790578f8156a89e66241c315f8194e2872 docs/rhl/breaks/v2/planted/typo-term/10-gx19-ticket-sweep-b/break.yaml +4fec1cc2f631bad9f31b0282287152703be5e7277d739f2be3bbcd4e1924c481 docs/rhl/breaks/v2/planted/typo-term/10-gx19-ticket-sweep-b/broken.rhl +86d534744ff4b8e699d513b4ad62945aa050cd287cb09aec857eec634589059b docs/rhl/breaks/v2/planted/typo-term/11-gx20-var-disk-watch/break.yaml +66a7c9200aecf3b2dd1a809f6f0441f19b130c1ce9bd3b5ce611911bc0779657 docs/rhl/breaks/v2/planted/typo-term/11-gx20-var-disk-watch/broken.rhl +c39f49f0f8e35689e9d8b5963b1281d968101ad843aa185e95cda0b3ca68aa2c docs/rhl/breaks/v2/planted/typo-term/12-gx21-home-disk-watch/break.yaml +9421560db8424e3eaf1b54b40ca461608c1e0c7bf0c7daed3e01a5cb5d2332fd docs/rhl/breaks/v2/planted/typo-term/12-gx21-home-disk-watch/broken.rhl +88fc85828488bdeb1c5ca1c809cd0c6c426c02ff5eb362f15d388c7ceb872c21 docs/rhl/breaks/v2/planted/undeclared-effect/01-gx10-disk-watch/break.yaml +d5ea59320918f5e4ce31d95720b553d0ede055bbb2d549a56839adbcabb93e6f docs/rhl/breaks/v2/planted/undeclared-effect/01-gx10-disk-watch/broken.rhl +cd083bb4f4e2a78252d674fd5b6647ad08ed8c5ee388f1ac06d653c936ba6382 docs/rhl/breaks/v2/planted/undeclared-effect/02-gx11-docker-disk-watch/break.yaml +110c5779104d9bd0566969680fd3691da217d0975859761d29f42d5f8ba4f0e6 docs/rhl/breaks/v2/planted/undeclared-effect/02-gx11-docker-disk-watch/broken.rhl +ab2bc3d2f0fc8e46d7a21fbf9e3763e3130b794d14e6a2649f33b520ee7b5f06 docs/rhl/breaks/v2/planted/undeclared-effect/03-gx12-runner-load-watch/break.yaml +54211c2cc090f8ea42808565e4237b42ebbe35048f527a20619a8dacc18b3d3d docs/rhl/breaks/v2/planted/undeclared-effect/03-gx12-runner-load-watch/broken.rhl +5502dbf250bca1a88ef21e23b8904568c8f6a888be6b57e1fac55e43babf0ba9 docs/rhl/breaks/v2/planted/undeclared-effect/04-gx13-pin-check/break.yaml +9a46c0e978068fe7dca54616a02fb987a19c7a9044aa6c1b711db0156a68a93f docs/rhl/breaks/v2/planted/undeclared-effect/04-gx13-pin-check/broken.rhl +1ab7e29bb74b3bf3bdcaed1111c5ef3df23ecda62dbd1adb6c11b96a37d59eff docs/rhl/breaks/v2/planted/undeclared-effect/05-gx14-ticket-sweep/break.yaml +b5a8e96f7c953242edb00e486a3846ed7dfa48bdffc5ecd235bbf5101d46fc8e docs/rhl/breaks/v2/planted/undeclared-effect/05-gx14-ticket-sweep/broken.rhl +3732b572e609fd26e0935223126731bb6db41928f914d1347b70d5132652cc29 docs/rhl/breaks/v2/planted/undeclared-effect/06-gx15-data-disk-watch/break.yaml +92aa0f36333f26360b8804d3ae00bf63a093f7443508e645d150c3790ff8c2c2 docs/rhl/breaks/v2/planted/undeclared-effect/06-gx15-data-disk-watch/broken.rhl +d93675a16456855b42609d323b52377ce62b3b77f626db25ba7269b64c13d3a4 docs/rhl/breaks/v2/planted/undeclared-effect/07-gx16-root-disk-watch/break.yaml +baa2d7788f4b6ee69b27d8b4e1e2dcbc2999af1577c3ada77023194f537cb774 docs/rhl/breaks/v2/planted/undeclared-effect/07-gx16-root-disk-watch/broken.rhl +bc39a7d5fafe63ab36f4808b2bf708b7f96adf07b47868c2785572cb6e300a70 docs/rhl/breaks/v2/planted/undeclared-effect/08-gx17-runner-load-alert/break.yaml +847955382ab084d5aed3e06d9c26f79f3c668576fb71d09a00765b2b22d5324b docs/rhl/breaks/v2/planted/undeclared-effect/08-gx17-runner-load-alert/broken.rhl +a8e5ece5cc42ae3007b6a9ac6634aceafba93100c33c7a80b9fea26d32f95bf1 docs/rhl/breaks/v2/planted/undeclared-effect/09-gx18-pin-check-b/break.yaml +8794a7dbdb368dda74f5429ee661a05ce6cebc596faa0939d48e469b688cb233 docs/rhl/breaks/v2/planted/undeclared-effect/09-gx18-pin-check-b/broken.rhl +833ccbb3c0dba0fa03cdb230693248a2e4731389f3df6da69effc7429e9975e1 docs/rhl/breaks/v2/planted/undeclared-effect/10-gx19-ticket-sweep-b/break.yaml +b1b3ff0c9c7b60d17147cfaff0d83a097ce0d3ee262e1821f09f3258b72b0bd0 docs/rhl/breaks/v2/planted/undeclared-effect/10-gx19-ticket-sweep-b/broken.rhl +ddeb8838d118f8ac65518fb6131dbbc0879ae5681ff04901dc709967ce101edc docs/rhl/breaks/v2/planted/undeclared-effect/11-gx20-var-disk-watch/break.yaml +fc730c1b246ffd8e58c8003b70b26e9c3ea39087203b371d193f7ca282d2d729 docs/rhl/breaks/v2/planted/undeclared-effect/11-gx20-var-disk-watch/broken.rhl +1072f571a2425b293932f5c32b1fdbd38b3c83425ad9ab8aa0335782aeb3c605 docs/rhl/breaks/v2/planted/undeclared-effect/12-gx21-home-disk-watch/break.yaml +60760736ca739ec06044bb2fe590aa8358fae95f81380f6cf7966098be6a6fcb docs/rhl/breaks/v2/planted/undeclared-effect/12-gx21-home-disk-watch/broken.rhl +6761ac2d6f87167d2b24ae09199c8ab38b49474385ebb87f364d7b7bed1fcde5 docs/rhl/breaks/v2/planted/wrong-type/01-gx10-disk-watch/break.yaml +ade84925cefd2f2207e9f83a46804f647a72cd0928cf021ee61b6965f82eca6f docs/rhl/breaks/v2/planted/wrong-type/01-gx10-disk-watch/broken.rhl +53a9f8b15476d5452a2bf58d00d38c3a1296b1a7989cae41a47556c25e09e00d docs/rhl/breaks/v2/planted/wrong-type/02-gx11-docker-disk-watch/break.yaml +f98d89a6a28960f7f69782f447d2ac6e5edbd80d4ef25e75f2a4136e5e1f32d4 docs/rhl/breaks/v2/planted/wrong-type/02-gx11-docker-disk-watch/broken.rhl +5d77d7021ed6bc2e5190db88cbf4803efd7e5cdae748c1ee5a18faa963d2b56b docs/rhl/breaks/v2/planted/wrong-type/03-gx12-runner-load-watch/break.yaml +4964d7fe24cf570113ea811bfd0725cb710f33aedfa3f40c2f45ea050b1e6aae docs/rhl/breaks/v2/planted/wrong-type/03-gx12-runner-load-watch/broken.rhl +88dd6c93ded04a17a825b50b3f4c3228b93936f9d1879b7f5c633657a319c841 docs/rhl/breaks/v2/planted/wrong-type/04-gx13-pin-check/break.yaml +202893145e27a35fc3995abc6717327d4987758f4f67256050483d9550655df1 docs/rhl/breaks/v2/planted/wrong-type/04-gx13-pin-check/broken.rhl +0084948214f62eb6e86d40d60db9d60d90d14cacf55df4a93de815333dff385d docs/rhl/breaks/v2/planted/wrong-type/05-gx14-ticket-sweep/break.yaml +b98edce7237b5d5766c47676e393f8c317db1d5eef9bff49fecaea2ddc81994b docs/rhl/breaks/v2/planted/wrong-type/05-gx14-ticket-sweep/broken.rhl +7ed853ccfd8cdbf06f64828bef9d739b0cdaa2d5524ffac7b3371580110d2931 docs/rhl/breaks/v2/planted/wrong-type/06-gx15-data-disk-watch/break.yaml +7c010bde69b6835efbc6d1bc01b01eee2eaa61cb0b86f687b29ff82c11b4001d docs/rhl/breaks/v2/planted/wrong-type/06-gx15-data-disk-watch/broken.rhl +7042b6eab81ca59d60826937d6e12620aa1bb28bc98169f926c2c204d6a81327 docs/rhl/breaks/v2/planted/wrong-type/07-gx16-root-disk-watch/break.yaml +5f028bf0afc9d0c97735fc3e37bb7c6d317a96c4b82543bd68f0d60da25799a5 docs/rhl/breaks/v2/planted/wrong-type/07-gx16-root-disk-watch/broken.rhl +3574e0fccf09c57b1f51d5c8006b52931e1f3a55e34c9c6cab9b131dc89213ff docs/rhl/breaks/v2/planted/wrong-type/08-gx17-runner-load-alert/break.yaml +2e0c7a5fe263131846041bef464eaaf4b7a769ea86bd99714f412ef8a49c6d40 docs/rhl/breaks/v2/planted/wrong-type/08-gx17-runner-load-alert/broken.rhl +80c1a3ad08cae7815e375499c585cf190d1353b0188706c6f084c0abec031a7e docs/rhl/breaks/v2/planted/wrong-type/09-gx18-pin-check-b/break.yaml +92fac82dbfaab49d599b3819066796394d91181049f16d5f740e79cb909500c4 docs/rhl/breaks/v2/planted/wrong-type/09-gx18-pin-check-b/broken.rhl +81832e674400f5dd7c91c2c2ea70521c3ee79d213d0932abe97ec5dce4a2518c docs/rhl/breaks/v2/planted/wrong-type/10-gx19-ticket-sweep-b/break.yaml +14bf9bf878c2427434763d5dff544ed44db3ff03fac51f3547bc99abf9b0fcaa docs/rhl/breaks/v2/planted/wrong-type/10-gx19-ticket-sweep-b/broken.rhl +b28dae973b32dd09c178bf46550e21e2f3d5c94727fb175166dd9cf5e55aa4ee docs/rhl/breaks/v2/planted/wrong-type/11-gx20-var-disk-watch/break.yaml +1d4d3e63b0d8e1dd92941266e4d33a5ded3496ac6950e82094182f2bc97d77aa docs/rhl/breaks/v2/planted/wrong-type/11-gx20-var-disk-watch/broken.rhl +440ecb691006823266a9a3642c014796d5cb1d858dcb8c5bbcf6568613b5597d docs/rhl/breaks/v2/planted/wrong-type/12-gx21-home-disk-watch/break.yaml +a503ba447037419c536420c42ae4b594be1bf0ceb1ade2de76f27b3dc5cdfe44 docs/rhl/breaks/v2/planted/wrong-type/12-gx21-home-disk-watch/broken.rhl +aacde1ce3af9d7b985b222c72d9203bcc646eb1aa2b710095136757a6ce4f750 docs/rhl/breaks/v2/planted/wrong-unit/01-gx10-disk-watch/break.yaml +db3936ca31258e7e0f1b0294adbd25937988b476ed648df9283bd456f3ec381b docs/rhl/breaks/v2/planted/wrong-unit/01-gx10-disk-watch/broken.rhl +69e09d6e338dfaaef356c23a8a8699ab98af1854249b30470011cf1e40cc4fa3 docs/rhl/breaks/v2/planted/wrong-unit/02-gx11-docker-disk-watch/break.yaml +0c63bb2a9ce228e7be00cde5c9085bec58e49150f6719d822d3531eb0646da6c docs/rhl/breaks/v2/planted/wrong-unit/02-gx11-docker-disk-watch/broken.rhl +ae3569382824cffd3eaca340be185ba73940a9dd3f6eee3c8cad253bdf203cef docs/rhl/breaks/v2/planted/wrong-unit/03-gx12-runner-load-watch/break.yaml +389c6b9ddfaee4a7d6c34e532264e3b6a13fee5f20f7dd70b509d6bfa2440f1e docs/rhl/breaks/v2/planted/wrong-unit/03-gx12-runner-load-watch/broken.rhl +f13aa32fe338befa5eb8ba7597a90d31a5296cbaba305d9a34cce3e046d66c36 docs/rhl/breaks/v2/planted/wrong-unit/04-gx13-pin-check/break.yaml +82b8434b964204fb37069fbb09995d3ec08a93ea152da91016563b41d86c6078 docs/rhl/breaks/v2/planted/wrong-unit/04-gx13-pin-check/broken.rhl +5fb51ff9efc9f9d440bbd354c8f96f356a75b4e725efd8b86e902879e1943a0a docs/rhl/breaks/v2/planted/wrong-unit/05-gx14-ticket-sweep/break.yaml +07f22e21e6ef07097915945197216e7996e19a458552258d4f4644e2871122e7 docs/rhl/breaks/v2/planted/wrong-unit/05-gx14-ticket-sweep/broken.rhl +731e8bab7a203f6e853d6ba4c237627c10c20798e8e05c0827e72fbb73230a95 docs/rhl/breaks/v2/planted/wrong-unit/06-gx15-data-disk-watch/break.yaml +5e3fc07181999751860791f94c1619d81a0526c7293f6a52812258e878f06f4f docs/rhl/breaks/v2/planted/wrong-unit/06-gx15-data-disk-watch/broken.rhl +bd64dde22fc68a461bb768d97d4984bd0d474ec95b645e807914c723734ba185 docs/rhl/breaks/v2/planted/wrong-unit/07-gx16-root-disk-watch/break.yaml +ee592a54f4ac95d2f68058a5705c6b55207837c979603a3d58b99556f96453ee docs/rhl/breaks/v2/planted/wrong-unit/07-gx16-root-disk-watch/broken.rhl +b9294e421de61b194b6dfc9cf957deeaf84746bc07bd5c4ea306545033a352f0 docs/rhl/breaks/v2/planted/wrong-unit/08-gx17-runner-load-alert/break.yaml +78ce975742f9a683ce03e98268988a6911291ef28912c6c215e2aff6782a5d7c docs/rhl/breaks/v2/planted/wrong-unit/08-gx17-runner-load-alert/broken.rhl +b60dd4c240b69018cf53e2d46dd7e8d82f4b0951a3e2cd38a7dcea9b34da0d86 docs/rhl/breaks/v2/planted/wrong-unit/09-gx18-pin-check-b/break.yaml +923522b62a716c848e9071ee527f8af71c2171bf2f16081b89a61d7e3e00d4af docs/rhl/breaks/v2/planted/wrong-unit/09-gx18-pin-check-b/broken.rhl +6bbec9449f4454d087bebe912197aab8c92d271daac40212acb287d05bcc88c9 docs/rhl/breaks/v2/planted/wrong-unit/10-gx19-ticket-sweep-b/break.yaml +4f8221b44b24757576dba1be865fd2500ace7477e5f42c74745c6d4f346cffb7 docs/rhl/breaks/v2/planted/wrong-unit/10-gx19-ticket-sweep-b/broken.rhl +b9793d672ed21cc3eca5e44bb8f89f2206021f6d1e895bd2c8011f21c8bf0100 docs/rhl/breaks/v2/planted/wrong-unit/11-gx20-var-disk-watch/break.yaml +8922d077e4ae3ef32974f01cb85ab3781adc126d17ebbbf3915ff0da06a54eb3 docs/rhl/breaks/v2/planted/wrong-unit/11-gx20-var-disk-watch/broken.rhl +aabab96b90dac32143c07194ec31f0c81178c197e726c7ebea4374653c42cf00 docs/rhl/breaks/v2/planted/wrong-unit/12-gx21-home-disk-watch/break.yaml +8e662e7ba03f0562d52d84fc0020cf15c79cc78a723347a2654b1ff8aa1c2f73 docs/rhl/breaks/v2/planted/wrong-unit/12-gx21-home-disk-watch/broken.rhl +293b885ce79b199ee7f72fb686c08c40be60f55f3b46049402df54cf11232e2f docs/rhl/breaks/v2/valid/01-gx10-disk-watch.rhl +8a2e2e8aa5b9e53645311c397a2f6783849de9d59b7d8ee2c1ba8bbe2538c32e docs/rhl/breaks/v2/valid/02-gx11-docker-disk-watch.rhl +6b3b35c837d5778be070cc5084d1b313829f85db398e5e781351ef417a841dd2 docs/rhl/breaks/v2/valid/03-gx12-runner-load-watch.rhl +e7315fc3d81b888edb6677b95eb0f955e2afff20bcaed1f9f23b838cac0ba93f docs/rhl/breaks/v2/valid/04-gx13-pin-check.rhl +a11a5875c4142ef8465fa72e68202b2acfddeb276613ce5bf1c9caa13ecabdcf docs/rhl/breaks/v2/valid/05-gx14-ticket-sweep.rhl +d5b49786091ad25e4423f62aaebb469ac2f02dc977350efbbd13b740cf6bd04a docs/rhl/breaks/v2/valid/06-gx15-data-disk-watch.rhl +956975ee124e88a0686ab7b9601ed4928f765cf61a043a603e5967ca561f1608 docs/rhl/breaks/v2/valid/07-gx16-root-disk-watch.rhl +fe36e15b0efcb83d3d3abfc66280d92fe587d551bf76b731e6a8c22839bb90de docs/rhl/breaks/v2/valid/08-gx17-runner-load-alert.rhl +dc088e055c04d4bafffd1d909ea2b78a8750835cb50f53871d583fc6ec834e10 docs/rhl/breaks/v2/valid/09-gx18-pin-check-b.rhl +825bdbf237632502bd755dde5310e20f3df6f9968fa420b2d3e901cfe1c8598d docs/rhl/breaks/v2/valid/10-gx19-ticket-sweep-b.rhl +32f18448ff09ed478e3b77acaa474cb5b0c357eb111fa3d4dffebd602f9b8f6a docs/rhl/breaks/v2/valid/11-gx20-var-disk-watch.rhl +23aee8e3c9c70f7e5da19dafeece0b74735ee7c5fb4e522954bbf8ca694a4eca docs/rhl/breaks/v2/valid/12-gx21-home-disk-watch.rhl 9fe6987a91af588b344750ba384d880b8aad8830a0e746e7f76415713e3409e5 docs/rhl/breaks/valid/01-gx10-disk-watch.rhl fdbcc29735603873d6f9703eb6550e3b04e906aec50d3577168a5b639871adec docs/rhl/breaks/valid/02-gx11-docker-disk-watch.rhl 49fb973a3e1dc6b4ea74d36eb15867be9782b588836d426665ee73ebe32c4036 docs/rhl/breaks/valid/03-gx12-runner-load-watch.rhl @@ -311,4 +482,6 @@ a270e5ec4706ac2f66bdb0e3d5632f4cfc31056bcf6afc8571d3d7709fefc0f8 docs/rhl/phase c7f34521310dfa3ef5799d5cca83458746884709004a388ddbddc166aadebdcd grammar/rhl.conflict-report.txt 3ae463833317b59ecf480aa5dd6e8caba15d4bcf3dd785fa5debae91208b625c grammar/rhl.lalrpop 784c9b5abe1c3d2de88586ce6183f59fcaf7c28ae6ae81c6b377224b87bf425e vocab/fleet-v1.yaml +f55f01ef60e6d2a80c8e3ac31bb9b7fc03406830a6351614ad04b7e450a41134 vocab/fleet-v2.yaml 0348081bcb4567bc1d7f1e41836f8784a4c2a1baabc9d83facb63b1a4a3bc625 vocab/tickets-v1.yaml +f6a372e7c7cc76d9029d5247395ebc374aa0b789562f43d87ac3dc6aaa7928b1 vocab/tickets-v2.yaml diff --git a/docs/rhl/rhl-16-v2.md b/docs/rhl/rhl-16-v2.md new file mode 100644 index 000000000..aaacc4b05 --- /dev/null +++ b/docs/rhl/rhl-16-v2.md @@ -0,0 +1,94 @@ +# RHL-16 — vocabulary and break corpus v2 + +Roadmap row RHL-16; plan `docs/rhl/rhlga-1-plan.md` §3 P1. Spec RHL-001 §3.4 freezes v1: +`vocab/*-v1.yaml`, `contracts/rhl-*-v1.yaml`, `docs/rhl/breaks/{valid,planted}/` and +`docs/rhl/corpus/` are byte-identical before and after this row. v2 lives beside them. + +## Why v2 + +RHL-1's checker measured three defects in the pre-registered data (Amendment A3; RHL-1 plan M3–M5): + +1. All 12 valid programs used `ticket count`, `title` and `label`. None is a v1 term: RHL-V001 three times per program. +2. Programs 03/08 bound `free` to `runner load of` (Percent, `read runner`), 04/09 to `pin status of` (Text, `read host`) and 05/10 to `tickets filed in` (Count, `read tickets`). All six compared it with a GB quantity and declared only `read disk`, `write tickets` and `call tickets`. +3. So wrong-unit/04 and /09 gave RHL-T002 where RHL-T001 was expected. Those two are the v1 `KNOWN_CORPUS_DEFECTS`. Six more breaks repeated a code their base already had on the mutated line. + +## What changed in the vocabularies + +| File | Change | +|---|---| +| `vocab/fleet-v2.yaml` | Every v1 term, unchanged: same kind, `takes`, `gives`, `effect` and `instances_from`. Each term points to its own `contracts/rhl-fleet--v2.yaml`. | +| `vocab/tickets-v2.yaml` | Every v1 term, unchanged, each with a v2 contract. **`file ticket`** gains `attributes: [{name: title, type: Text}, {name: label, type: Text}]`. **`ticket count`** is new: a measure that gives Count and has **no effect**. | + +`lowers_to: "[U]"` stays on every term (RHL-4 binds it), and so does `sigma_entity: "[U]"`. +The v2 contracts copy the v1 template's shape: version `2.0.0`, IDs suffixed `-V2`. Each +cites the `test_rhl16_*` tests that check v2. + +### The attribute rule (new) + +For an action that declares `attributes:`, each action-shaped line of its `with … end` block +assigns an attribute. The line is ` `: + +- The name must be one of the action's declared attributes. If it is not, the code depends on how close the name is to a declared one, as for any unknown word (plan D4). With one near candidate it is RHL-V002, whose fix is safe (`titl` → `title`). With several it is RHL-V003. With none it is **RHL-V001**. Because the attribute namespace is closed and small, that V001 lists every declared attribute as a candidate, with its distance, and offers no fix. +- The value must be exactly one literal of the attribute's type. Otherwise the checker reports RHL-T002: `title 5`, `title 5 GB`, `title "a" "b"` or a bare `title`. +- An attribute line takes no `in` target and no nested `with` block. Either one gives RHL-T002. +- Any other statement in the block (`let`, `when`, …) is checked as an ordinary statement. + +An action without `attributes:` keeps the v1 behaviour: its block is checked as ordinary +statements. That covers every v1 action and v2's `label ticket`. The v1 harness in +`src/rhl/check_tests.rs` still gets exactly the codes RHL-1 recorded. + +### The no-effect measure rule (new) + +`ticket count` is the number of tickets the run files. RHL-4 reads it from the run's own +plan, not from the world. So it has no `effect:`, and using it needs no `may` line. The +checker already recorded effects only for terms that declare one, so this needed no code +change. `test_rhl16_measure_without_effect_needs_no_may_line` covers it. + +No diagnostic code was added. `docs/rhl/diagnostic-codes.md` is unchanged. + +## The v2 corpus — `docs/rhl/breaks/v2/` + +Every valid program starts `use vocabulary fleet v2` / `use vocabulary tickets v2`. It +declares exactly the effects it uses: one `read` for its measure, plus `write tickets` for +`file ticket`. v1's unused `may call tickets` is dropped. Each program keeps its job name, +host, schedule, repo, ticket title and label. The `given` line sets the `let` name and the +`then` line asserts `ticket count is 1`. + +| # | Measure | v1 comparison | v2 comparison | v2 `given` | +|---|---|---|---|---| +| 01, 06, 11 | `disk free of` (Size) | `free is below N GB` | unchanged | unchanged | +| 02, 07, 12 | `disk usage of` (Size) | `free is below N GB` | `used is above N GB` (high usage files the ticket) | `used is 95 / 80 / 65 GB` | +| 03, 08 | `runner load of` (Percent) | `free is below 90 / 85 GB` | `load is above 90 / 85 %` | `load is 95 / 90 %` | +| 04, 09 | `pin status of` (Text) | `free is below 1 GB` | `status is not "pinned"` | `status is "unpinned"` | +| 05, 10 | `tickets filed in` (Count) | `free is below 5 / 3 GB` | `filed is above 5 / 3` | `filed is 7 / 4` | + +The `let` name now says what it holds (`free`, `used`, `load`, `status`, `filed`). Every +v2 valid program checks with no diagnostic. Its only notes are the unverified instances +of `host` and `repo` (Amendment A3), because this repository has no `machines/` or +`org/repos/`. Every program is also in `ruchy fmt` normal form. + +### Planted breaks + +There are 6 classes × 12 programs. Each `break.yaml` keeps v1's schema (`base`, `class`, +`expected_code`, `safe_fix`, plus `candidates` for V003), and its base is the v2 valid +program. + +| Class | Mutation | Code | +|---|---|---| +| missing-end | delete the `end` of the `when` block | RHL-P001 | +| two-candidate-typo | `runs on host` → `runs on hosr` (candidates `host`, `hour`) | RHL-V003 | +| typo-term | the measure on the `let` line (`disk fre of`, `disk usge of`, `runer load of`, `pin staus of`, `ticket filed in`) | RHL-V002, safe fix | +| undeclared-effect | delete `may write tickets` | RHL-E001 | +| wrong-type | the `when` right-hand side becomes `"low"`, or `1` for the Text comparisons of 04/09 | RHL-T002 | +| wrong-unit | the `when` threshold's unit becomes `hour`. For 04/09, whose `when` compares Text, the mutation is `every 1 hour` → `every 1 GB` | RHL-T001 | + +The substitution classes yield their code on the mutated line. Every code is new relative +to its base, because the base checks clean. The deletion classes are held as in v1: the +code occurs more often than in the base. v2 has **no** known-defect exceptions: +`KNOWN_CORPUS_DEFECTS_V2` is empty and a test asserts it. The v1 list of two stays as it was. + +## Manifest + +`docs/rhl/PREREGISTRATION.sha256` was regenerated with the command in its header. The diff +only adds lines: 173 new hashes (156 corpus files, 2 vocabularies, 15 contracts) and the +`# files:` count. No v1 hash changed. From 3e8fb5c316583dcbe1795cf74d1e00b4813720b7 Mon Sep 17 00:00:00 2001 From: Noah Gift Date: Wed, 23 Sep 2026 14:02:23 +0200 Subject: [PATCH 4/9] RHL-2: an unknown attribute with no near candidate is V001 with the declared names in expected V001 means no candidate, so the declared attributes move from candidates to expected {kind: attribute, of, names}; Expected gains of and names. Pmat-Ticket: RHLGA-1 Co-Authored-By: Claude Opus 5.5 (1M context) --- docs/rhl/diagnostic-codes.md | 8 ++++++++ src/rhl/check/stmts.rs | 39 ++++++++++++++++++++---------------- src/rhl/check_tests.rs | 14 +++++++++---- src/rhl/diag.rs | 8 ++++++++ 4 files changed, 48 insertions(+), 21 deletions(-) diff --git a/docs/rhl/diagnostic-codes.md b/docs/rhl/diagnostic-codes.md index 7fd6c504e..9ac56b250 100644 --- a/docs/rhl/diagnostic-codes.md +++ b/docs/rhl/diagnostic-codes.md @@ -32,3 +32,11 @@ When there are errors but no refusal, it exits **1**. With no errors it exits ** Six codes were pre-registered by RHL-0 in `docs/rhl/breaks/planted/*/*/break.yaml` before any checker existed. Their meaning is fixed by that data: `RHL-P001`, `RHL-V002`, `RHL-V003`, `RHL-T001`, `RHL-T002` and `RHL-E001`. + +## The `expected` set of an unknown attribute + +An unknown attribute in the `with` block of an action that declares `attributes:` +(RHL-16) has `expected` = `{"kind": "attribute", "of": "", "names": []}`. When no declared attribute is near, the code is `RHL-V001`, which has no +candidates. The declared names are listed in `expected.names` and not as candidates +(RHL-2). diff --git a/src/rhl/check/stmts.rs b/src/rhl/check/stmts.rs index 8a605ba81..15d5a4e99 100644 --- a/src/rhl/check/stmts.rs +++ b/src/rhl/check/stmts.rs @@ -1,11 +1,10 @@ //! Statements: headers, bindings, actions and blocks (spec RHL-001 §3.3). -use super::checker::{app_span, longest_term, words_text, Checker}; +use super::checker::{app_span, longest_term, Checker}; use super::lexicon::{LexTerm, Lexicon}; -use super::near; use super::types::{duration_code, mismatch, Ty, Val}; use crate::rhl::codes; -use crate::rhl::diag::Candidate; +use crate::rhl::diag::Expected; use crate::rhl::tree::{Action, App, Cond, Effect, Phrase, Quantity, Span, Stmt, StmtKind, Word}; use crate::rhl::vocab::{Param, TermKind}; @@ -188,8 +187,10 @@ impl Checker<'_> { } /// An attribute `term` does not declare: V002/V003 for a near miss, as for - /// any unknown word. The attribute namespace is closed and small, so a V001 - /// (nothing near) lists every declared attribute as a candidate, without a fix. + /// any unknown word, and V001 when nothing is near. V001 means "no + /// candidate" (the catalogue), so the declared attributes travel in the + /// diagnostic's `expected` set — `{kind: attribute, of, names}` — never as + /// candidates (RHL-2's ruling on RHL-16's open question). fn unknown_attribute(&mut self, term: &LexTerm, words: &[Word]) { let action = &term.term.term; let pool: Vec<(String, String)> = term @@ -200,20 +201,24 @@ impl Checker<'_> { .collect(); let at = self.diags.len(); self.unknown(words, &pool, &format!("attribute of `{action}`")); - let Some(d) = self.diags.get_mut(at).filter(|d| d.code == codes::V001) else { + let Some(d) = self + .diags + .get_mut(at) + .filter(|d| d.code.starts_with("RHL-V")) + else { return; }; - let written = words_text(words); - d.candidates = pool - .iter() - .map(|(name, vocabulary)| Candidate { - term: name.clone(), - vocabulary: vocabulary.clone(), - distance: near::distance(&written, name), - }) - .collect(); - let names: Vec = pool.iter().map(|(n, _)| format!("`{n}`")).collect(); - d.message = format!("{}; `{action}` has {}", d.message, names.join(", ")); + let names: Vec = pool.into_iter().map(|(n, _)| n).collect(); + if d.code == codes::V001 { + let listed: Vec = names.iter().map(|n| format!("`{n}`")).collect(); + d.message = format!("{}; `{action}` has {}", d.message, listed.join(", ")); + } + d.expected = Some(Expected { + kind: Some("attribute".to_string()), + of: Some(action.clone()), + names, + ..Expected::default() + }); } /// The head of an action statement must be an action term. diff --git a/src/rhl/check_tests.rs b/src/rhl/check_tests.rs index 290d8e2e3..175572e21 100644 --- a/src/rhl/check_tests.rs +++ b/src/rhl/check_tests.rs @@ -1016,12 +1016,18 @@ fn test_rhl16_attribute_unknown_is_v001_listing_the_declared_attributes() { ); let d = &report.diagnostics[0]; assert_eq!(d.span.line, 8); - let names: Vec<&str> = d.candidates.iter().map(|c| c.term.as_str()).collect(); - assert_eq!(names, vec!["title", "label"]); + // RHL-2 ruling on RHL-16's open question: V001 is "no candidate", so the + // declared attributes travel in `expected`, never as candidates. + assert!(d.candidates.is_empty(), "V001 has no candidate: {d:#?}"); assert!(d.fixes.is_empty(), "V001 has no fix"); + let expected = d.expected.as_ref().expect("expected set"); + assert_eq!(expected.kind.as_deref(), Some("attribute")); + assert_eq!(expected.of.as_deref(), Some("file ticket")); + assert_eq!(expected.names, vec!["title", "label"]); + let json = serde_json::to_value(expected).expect("serializable"); assert_eq!( - d.expected.as_ref().and_then(|e| e.kind.as_deref()), - Some("attribute of `file ticket`") + json, + serde_json::json!({"kind": "attribute", "of": "file ticket", "names": ["title", "label"]}) ); } diff --git a/src/rhl/diag.rs b/src/rhl/diag.rs index 5cc39aabe..70719afb0 100644 --- a/src/rhl/diag.rs +++ b/src/rhl/diag.rs @@ -68,6 +68,14 @@ pub struct Expected { /// The tokens the parser would have accepted. #[serde(skip_serializing_if = "Vec::is_empty", default)] pub tokens: Vec, + /// The term the expected word belongs to, for example the action + /// `file ticket` whose attribute was expected (RHL-2). + #[serde(skip_serializing_if = "Option::is_none", default)] + pub of: Option, + /// The closed set of names allowed at this position, for example the + /// attributes an action declares (RHL-2). + #[serde(skip_serializing_if = "Vec::is_empty", default)] + pub names: Vec, } /// A known word close to an unknown one. From 5ed763985160c55b65a0fd51c3e9cd23785c0e19 Mon Sep 17 00:00:00 2001 From: Noah Gift Date: Wed, 23 Sep 2026 14:02:52 +0200 Subject: [PATCH 5/9] RHL-2: ruchy fix --safe with revert-on-worse, ruchy vocab list/show/validate, loader shape checks fix --safe applies safe fixes one at a time, re-checks the whole file, reverts a fix after which the error count rose (ruling Q5), bounded by the initial safe-fix count, idempotent. vocab verbs read through the checker's loader; entity without instances_from and unit without gives break the shape (RHL-V004). Pmat-Ticket: RHLGA-1 Co-Authored-By: Claude Opus 5.5 (1M context) --- src/bin/handlers/mod.rs | 1 + src/bin/handlers/rhl_handler.rs | 79 ++++++++ src/bin/ruchy.rs | 82 ++++++++ src/rhl/check.rs | 6 + src/rhl/cli.rs | 8 +- src/rhl/fix.rs | 256 +++++++++++++++++++++++++ src/rhl/fix_tests.rs | 271 ++++++++++++++++++++++++++ src/rhl/mod.rs | 2 + src/rhl/vocab.rs | 92 +++++++-- src/rhl/vocab_cli.rs | 327 ++++++++++++++++++++++++++++++++ src/rhl/vocab_cli_tests.rs | 251 ++++++++++++++++++++++++ tests/rhl_cli.rs | 144 ++++++++++++++ 12 files changed, 1504 insertions(+), 15 deletions(-) create mode 100644 src/bin/handlers/rhl_handler.rs create mode 100644 src/rhl/fix.rs create mode 100644 src/rhl/fix_tests.rs create mode 100644 src/rhl/vocab_cli.rs create mode 100644 src/rhl/vocab_cli_tests.rs create mode 100644 tests/rhl_cli.rs diff --git a/src/bin/handlers/mod.rs b/src/bin/handlers/mod.rs index 33ce5c84d..ef7379f07 100644 --- a/src/bin/handlers/mod.rs +++ b/src/bin/handlers/mod.rs @@ -22,6 +22,7 @@ pub mod execution_handler; pub mod new; pub mod parse_handler; pub mod repl_handler; +pub mod rhl_handler; pub mod run_handler; pub mod transpile_handler; pub mod wasm_handler; diff --git a/src/bin/handlers/rhl_handler.rs b/src/bin/handlers/rhl_handler.rs new file mode 100644 index 000000000..9c07194a3 --- /dev/null +++ b/src/bin/handlers/rhl_handler.rs @@ -0,0 +1,79 @@ +//! `ruchy fix` and `ruchy vocab` for RHL (spec RHL-001 §5; roadmap row RHL-2). +//! +//! Every decision lives in the library (`ruchy::rhl::fix`, +//! `ruchy::rhl::vocab_cli`), under `cargo test --lib`. This handler only +//! prints an outcome and exits with its code: exit 2 cannot travel through +//! the generic `Err` path, which `main` maps to 1. + +use anyhow::Result; +use ruchy::rhl::cli::{Outcome, OutputFormat}; +use ruchy::rhl::fix::fix_files; +use ruchy::rhl::vocab_cli::{list_outcome, no_root, resolve_root, show_outcome, validate_outcome}; +use std::path::{Path, PathBuf}; + +/// `ruchy fix [--safe] ...`. +/// +/// # Errors +/// +/// Returns an error for an unknown `--format` or an empty file list. +pub fn handle_fix_command(files: &[PathBuf], safe: bool, format: &str) -> Result<()> { + if files.is_empty() { + anyhow::bail!("No files specified for fixing"); + } + let format = OutputFormat::parse(format).map_err(anyhow::Error::msg)?; + let paths: Vec<&Path> = files.iter().map(PathBuf::as_path).collect(); + finish(&fix_files(&paths, safe, format)) +} + +/// What `ruchy vocab` was asked to do. +pub enum VocabRequest { + /// `vocab list`. + List, + /// `vocab show `. + Show { + /// Vocabulary name. + name: String, + /// Version, `v2` or `2`. + version: String, + }, + /// `vocab validate | `. + Validate { + /// The file, or the name and version. + target: Vec, + }, +} + +/// `ruchy vocab list | show | validate`. +/// +/// # Errors +/// +/// Returns an error for an unknown `--format`, an unreadable current +/// directory, or no vocabulary root where one is needed. +pub fn handle_vocab_command( + request: VocabRequest, + format: &str, + root: Option<&Path>, +) -> Result<()> { + let format = OutputFormat::parse(format).map_err(anyhow::Error::msg)?; + let found = resolve_root(root, &std::env::current_dir()?); + let outcome = match (request, found) { + (VocabRequest::Validate { target }, found) => { + validate_outcome(found.as_deref(), &target, format) + } + (_, None) => anyhow::bail!(no_root()), + (VocabRequest::List, Some(r)) => list_outcome(&r, format), + (VocabRequest::Show { name, version }, Some(r)) => { + show_outcome(&r, &name, &version, format) + } + }; + finish(&outcome) +} + +fn finish(outcome: &Outcome) -> Result<()> { + print!("{}", outcome.stdout); + eprint!("{}", outcome.stderr); + if outcome.exit != 0 { + std::process::exit(outcome.exit); + } + Ok(()) +} diff --git a/src/bin/ruchy.rs b/src/bin/ruchy.rs index 34e53448c..841d80d4b 100644 --- a/src/bin/ruchy.rs +++ b/src/bin/ruchy.rs @@ -170,6 +170,22 @@ enum Commands { #[arg(long, default_value = "text")] format: String, }, + /// Apply the safe fixes of `ruchy check` to .rhl files, in place (RHL-2) + Fix { + /// The .rhl file(s) to fix + files: Vec, + /// Apply only fixes marked safe; the one mode that exists for .rhl files + #[arg(long)] + safe: bool, + /// Output format: text, or json (the check report plus `applied`) + #[arg(long, default_value = "text")] + format: String, + }, + /// List, show and validate RHL vocabularies (RHL-2) + Vocab { + #[command(subcommand)] + command: VocabCommands, + }, /// Run tests for Ruchy code with optional coverage reporting Test { /// The test file or directory to run @@ -1260,6 +1276,46 @@ enum ModelCommands { }, } +/// `ruchy vocab` subcommands (RHL-2). The vocabulary root is `--root`, else +/// the nearest directory from the current one that holds `vocab/`. +#[derive(Subcommand, Debug)] +enum VocabCommands { + /// Every vocabulary under the root: name, version, term count + List { + /// Output format: text or json + #[arg(long, default_value = "text")] + format: String, + /// The vocabulary root (the directory that holds vocab/) + #[arg(long)] + root: Option, + }, + /// The terms of one vocabulary, with kind, takes, gives, effect, attributes, contract + Show { + /// Vocabulary name, for example `fleet` + name: String, + /// Version, `v2` or `2` + version: String, + /// Output format: text or json + #[arg(long, default_value = "text")] + format: String, + /// The vocabulary root (the directory that holds vocab/) + #[arg(long)] + root: Option, + }, + /// Validate a vocabulary file, or ` `, through the checker's loader + Validate { + /// `` or ` ` + #[arg(required = true, num_args = 1..=2)] + target: Vec, + /// Output format: text or json + #[arg(long, default_value = "text")] + format: String, + /// The vocabulary root (the directory that holds vocab/) + #[arg(long)] + root: Option, + }, +} + #[derive(Subcommand, Debug)] enum ContractsCommands { /// Generate YAML contract manifests from source @@ -1417,6 +1473,12 @@ fn handle_command_dispatch( watch, format, }) => handle_check_command(&files, watch, &format), + Some(Commands::Fix { + files, + safe, + format, + }) => handlers::rhl_handler::handle_fix_command(&files, safe, &format), + Some(Commands::Vocab { command }) => handle_vocab(command), Some(Commands::Test { path, watch, @@ -2120,6 +2182,26 @@ fn check_syntax(file: &Path) -> Result<()> { } } +/// Route `ruchy vocab` (RHL-2) to its handler. +fn handle_vocab(command: VocabCommands) -> Result<()> { + use handlers::rhl_handler::{handle_vocab_command, VocabRequest}; + let (request, format, root) = match command { + VocabCommands::List { format, root } => (VocabRequest::List, format, root), + VocabCommands::Show { + name, + version, + format, + root, + } => (VocabRequest::Show { name, version }, format, root), + VocabCommands::Validate { + target, + format, + root, + } => (VocabRequest::Validate { target }, format, root), + }; + handle_vocab_command(request, &format, root.as_deref()) +} + #[cfg(test)] #[path = "ruchy_tests.rs"] mod tests; diff --git a/src/rhl/check.rs b/src/rhl/check.rs index 3f27d3215..327f9f0f9 100644 --- a/src/rhl/check.rs +++ b/src/rhl/check.rs @@ -93,6 +93,12 @@ impl Report { } } + /// A report of `diagnostics` alone, for tests of code that consumes reports. + #[cfg(test)] + pub(crate) fn for_test(file: &str, diagnostics: Vec) -> Self { + Self::new(file, diagnostics, Vec::new()) + } + /// The process exit code: 2 for a refusal, 1 for any other error, else 0. #[must_use] pub fn exit_code(&self) -> i32 { diff --git a/src/rhl/cli.rs b/src/rhl/cli.rs index 3a2183c46..100749f60 100644 --- a/src/rhl/cli.rs +++ b/src/rhl/cli.rs @@ -1,5 +1,6 @@ //! The `.rhl` side of the existing `ruchy check` and `ruchy fmt` verbs (spec -//! RHL-001 §5: extend by file extension, add no verb; plan D8). +//! RHL-001 §5: extend by file extension; plan D8). The new verbs `ruchy fix` +//! and `ruchy vocab` (RHL-2) live in [`super::fix`] and [`super::vocab_cli`]. //! //! Everything a verb decides lives here, in the library, so the required //! check (`cargo test --lib`, binding B4) covers it. The binary only prints an @@ -91,7 +92,8 @@ fn render(reports: &[Report], format: OutputFormat) -> String { } } -fn json(value: &T) -> String { +/// `value` as pretty JSON and a newline. +pub(crate) fn json(value: &T) -> String { match serde_json::to_string_pretty(value) { Ok(s) => s + "\n", Err(e) => format!("{{\"error\": \"cannot serialize the report: {e}\"}}\n"), @@ -99,7 +101,7 @@ fn json(value: &T) -> String { } /// A report for a person: its diagnostics, its unverified instances, a verdict line. -fn text(report: &Report) -> String { +pub(crate) fn text(report: &Report) -> String { let mut out = render_text(&report.diagnostics); for u in &report.unverified { out.push_str(&format!( diff --git a/src/rhl/fix.rs b/src/rhl/fix.rs new file mode 100644 index 000000000..4e0732417 --- /dev/null +++ b/src/rhl/fix.rs @@ -0,0 +1,256 @@ +//! `ruchy fix --safe` (spec RHL-001 §3.5, §5; roadmap row RHL-2). +//! +//! The checker marks a fix `safe` by RHL-1's local rule: it is the unique +//! candidate and the fixed program has no type or unit error on an edited +//! line. `fix --safe` is the global guard (plan `docs/rhl/rhlga-1-plan.md` +//! ruling Q5): it applies the safe fixes one at a time, re-checks the whole +//! file after each, and reverts any fix after which the file's error count +//! rose. It repeats until no safe fix applies, for at most as many rounds as +//! the file had safe fixes to begin with. + +use super::check::{check, Report}; +use super::cli::{is_rhl, json, text, Outcome, OutputFormat}; +use super::diag::{apply_edits, Diagnostic, Edit, Fix, Severity}; +use super::vocab::find_root; +use serde::Serialize; +use std::path::Path; + +/// One fix `fix --safe` applied or reverted. +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] +pub struct Applied { + /// The code of the diagnostic the fix repairs. + pub code: String, + /// The diagnostic's line. + pub line: usize, + /// The diagnostic's column. + pub col: usize, + /// What the fix does. + pub title: String, + /// Its edits, against the text as it was when the fix was tried. + pub edits: Vec, +} + +/// What `fix --safe` did to one file: the check report of the result, plus +/// the fixes it applied and the ones it reverted. +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] +pub struct FixReport { + /// The check of the fixed file. + #[serde(flatten)] + pub report: Report, + /// The fixes that stayed, in the order applied. + pub applied: Vec, + /// The fixes undone because the error count rose after them. + pub reverted: Vec, + /// Rounds run; never more than the number of initial safe fixes. + pub rounds: usize, +} + +/// The fixed text and its report. +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Fixed { + /// The source after every fix that stayed. + pub source: String, + /// What was done. + pub report: FixReport, +} + +/// `fix --safe` on `source`, named `file`, with vocabularies under `root`. +#[must_use] +pub fn fix_safe(file: &str, source: &str, root: Option<&Path>) -> Fixed { + fix_with(source, |text| check(file, text, root)) +} + +/// The fix engine over any checker: `check` maps a text to its report. +#[must_use] +pub fn fix_with Report>(source: &str, check: F) -> Fixed { + let mut engine = Engine { + text: source.to_string(), + applied: Vec::new(), + reverted: Vec::new(), + check: &check, + }; + let bound = safe_fixes(&check(source)).len(); + let mut rounds = 0; + while rounds < bound && engine.round() { + rounds += 1; + } + let report = check(&engine.text); + Fixed { + source: engine.text, + report: FixReport { + report, + applied: engine.applied, + reverted: engine.reverted, + rounds, + }, + } +} + +/// The number of errors in `report`. +#[must_use] +pub fn error_count(report: &Report) -> usize { + report + .diagnostics + .iter() + .filter(|d| d.severity == Severity::Error) + .count() +} + +/// Every fix of `report` that `fix --safe` may try: marked safe, and on a +/// diagnostic with at most one candidate (two candidates are a choice, §3.5, +/// whatever the fix says). Last position first, so applying one leaves the +/// positions of the others valid. +fn safe_fixes(report: &Report) -> Vec { + let mut out: Vec = report + .diagnostics + .iter() + .filter(|d| d.candidates.len() <= 1) + .flat_map(|d| { + d.fixes + .iter() + .filter(|f| f.safe) + .map(move |f| applied(d, f)) + }) + .collect(); + out.sort_by_key(|a| std::cmp::Reverse(last_position(&a.edits))); + out +} + +fn applied(d: &Diagnostic, f: &Fix) -> Applied { + Applied { + code: d.code.clone(), + line: d.span.line, + col: d.span.col, + title: f.title.clone(), + edits: f.edits.clone(), + } +} + +fn last_position(edits: &[Edit]) -> (usize, usize) { + edits + .iter() + .map(|e| (e.span.line, e.span.col)) + .max() + .unwrap_or((0, 0)) +} + +struct Engine<'c, F> { + text: String, + applied: Vec, + reverted: Vec, + check: &'c F, +} + +impl Report> Engine<'_, F> { + /// One round: try every safe fix of the current text that was not + /// reverted before. True when at least one fix stayed. + fn round(&mut self) -> bool { + let report = (self.check)(&self.text); + let mut errors = error_count(&report); + let mut progress = false; + for fix in safe_fixes(&report) { + if self.reverted.iter().any(|r| r.edits == fix.edits) { + continue; + } + let next = apply_edits(&self.text, &fix.edits); + let after = error_count(&(self.check)(&next)); + if after > errors { + self.reverted.push(fix); + } else { + self.text = next; + errors = after; + self.applied.push(fix); + progress = true; + } + } + progress + } +} + +/// `ruchy fix` over `.rhl` files. Only `--safe` exists (v0); without it the +/// verb is refused (exit 2). Each changed file is written in place. The exit +/// code is `ruchy check`'s on the result: the worst of the files. +#[must_use] +pub fn fix_files(paths: &[&Path], safe: bool, format: OutputFormat) -> Outcome { + if !safe { + let msg = "ruchy fix: only `--safe` exists for .rhl files: it applies the fixes `ruchy check` marks safe\n"; + return failure(msg.to_string(), 2); + } + if let Some(other) = paths.iter().find(|p| !is_rhl(p)) { + let msg = format!( + "{} is not an .rhl file; ruchy fix applies to .rhl files\n", + other.display() + ); + return failure(msg, 1); + } + let mut reports = Vec::new(); + let mut stderr = String::new(); + for path in paths { + match fix_one(path) { + Ok(r) => reports.push(r), + Err(e) => stderr.push_str(&e), + } + } + let io_exit = i32::from(!stderr.is_empty()); + let exit = reports + .iter() + .map(|r| r.report.exit_code()) + .fold(io_exit, i32::max); + Outcome { + stdout: render(&reports, format), + stderr, + exit, + } +} + +fn failure(stderr: String, exit: i32) -> Outcome { + Outcome { + stdout: String::new(), + stderr, + exit, + } +} + +/// Fix one file and write it back when it changed. +fn fix_one(path: &Path) -> Result { + let shown = path.display(); + let source = + std::fs::read_to_string(path).map_err(|e| format!("{shown}: cannot read: {e}\n"))?; + let root = find_root(path); + let fixed = fix_safe(&shown.to_string(), &source, root.as_deref()); + if fixed.source != source { + std::fs::write(path, &fixed.source).map_err(|e| format!("{shown}: cannot write: {e}\n"))?; + } + Ok(fixed.report) +} + +fn render(reports: &[FixReport], format: OutputFormat) -> String { + match (format, reports) { + (OutputFormat::Json, [one]) => json(one), + (OutputFormat::Json, many) => json(&many), + (OutputFormat::Text, many) => many.iter().map(render_text).collect(), + } +} + +/// For a person: one line per fix applied or reverted, then the check. +fn render_text(r: &FixReport) -> String { + let file = &r.report.file; + let mut out = String::new(); + for a in &r.applied { + out.push_str(&format!( + "{file}:{}:{}: fixed[{}]: {}\n", + a.line, a.col, a.code, a.title + )); + } + for a in &r.reverted { + out.push_str(&format!( + "{file}:{}:{}: reverted[{}]: {} (the file's error count rose)\n", + a.line, a.col, a.code, a.title + )); + } + out + &text(&r.report) +} + +#[cfg(test)] +#[path = "fix_tests.rs"] +mod fix_tests; diff --git a/src/rhl/fix_tests.rs b/src/rhl/fix_tests.rs new file mode 100644 index 000000000..f1f52a10a --- /dev/null +++ b/src/rhl/fix_tests.rs @@ -0,0 +1,271 @@ +//! RHL-2 fix engine tests: `ruchy fix --safe` applies safe fixes only, +//! reverts a fix that raises the error count (plan ruling Q5), is idempotent, +//! and runs a bounded number of rounds. + +use super::*; +use crate::rhl::check::{check, Report}; +use crate::rhl::codes; +use crate::rhl::diag::{Candidate, Diagnostic, Edit, Fix, LineSpan}; +use crate::rhl::tree::Span; +use std::cell::Cell; +use std::path::{Path, PathBuf}; + +fn root() -> PathBuf { + crate::rhl::repo_root() +} + +fn read(path: &Path) -> String { + std::fs::read_to_string(path).unwrap_or_else(|e| panic!("cannot read {}: {e}", path.display())) +} + +/// Every `broken.rhl` of a v2 planted-break class, with its base program. +fn v2_breaks(class: &str) -> Vec<(PathBuf, PathBuf)> { + let dir = root().join("docs/rhl/breaks/v2/planted").join(class); + let mut out: Vec<(PathBuf, PathBuf)> = std::fs::read_dir(&dir) + .unwrap_or_else(|e| panic!("cannot list {}: {e}", dir.display())) + .filter_map(Result::ok) + .map(|e| e.path()) + .map(|d| { + let meta = read(&d.join("break.yaml")); + let base = meta + .lines() + .find_map(|l| l.strip_prefix("base: ")) + .expect("break.yaml names its base") + .trim() + .to_string(); + (d.join("broken.rhl"), root().join(base)) + }) + .collect(); + out.sort(); + assert_eq!(out.len(), 12, "{class}: 12 breaks"); + out +} + +fn fix_file(path: &Path) -> Fixed { + let source = read(path); + fix_safe(&path.display().to_string(), &source, Some(&root())) +} + +/// A diagnostic at `line` whose one fix replaces the whole line with `text`. +fn diag(line: usize, text: &str, safe: bool, candidates: usize) -> Diagnostic { + let mut d = Diagnostic::error(codes::V002, "t.rhl", "", Span::new(0, 0), "m".into()); + d.span = LineSpan { + line, + col: 1, + len: 1, + }; + d.candidates = (0..candidates) + .map(|i| Candidate { + term: format!("c{i}"), + vocabulary: "t v1".into(), + distance: 1, + }) + .collect(); + d.fixes.push(Fix { + title: format!("replace with `{text}`"), + safe, + edits: vec![Edit { + span: d.span, + text: text.to_string(), + }], + }); + d +} + +/// A report with one error per lower-case letter left in `text`, each with a +/// safe fix that upper-cases it — unless `worse` names a letter whose fix +/// is followed by two new errors (`!` marks them). +fn letters_report(text: &str, worse: char) -> Report { + let mut diags = Vec::new(); + for (i, line) in text.lines().enumerate() { + let c = line.chars().next().unwrap_or(' '); + if c.is_ascii_lowercase() { + diags.push(diag(i + 1, &c.to_ascii_uppercase().to_string(), true, 1)); + } + if c == worse.to_ascii_uppercase() { + diags.push(diag(i + 1, "!", false, 0)); + diags.push(diag(i + 1, "!", false, 0)); + } + } + Report::for_test("t.rhl", diags) +} + +// ---------------------------------------------------------------- the engine, on synthetic reports + +#[test] +fn test_rhl2_fix_applies_every_safe_fix() { + let fixed = fix_with("a\nb\nc\n", |t| letters_report(t, ' ')); + assert_eq!(fixed.source, "A\nB\nC\n"); + assert_eq!(fixed.report.applied.len(), 3); + assert!(fixed.report.reverted.is_empty()); +} + +#[test] +fn test_rhl2_fix_leaves_an_unsafe_fix_untouched() { + let check = |t: &str| { + let mut d = diag(1, "X", false, 1); + if t.starts_with('X') { + d.fixes.clear(); + } + Report::for_test("t.rhl", vec![d]) + }; + let fixed = fix_with("a\n", check); + assert_eq!(fixed.source, "a\n"); + assert!(fixed.report.applied.is_empty()); +} + +#[test] +fn test_rhl2_fix_never_applies_a_fix_with_two_candidates() { + let fixed = fix_with("a\n", |_| { + Report::for_test("t.rhl", vec![diag(1, "X", true, 2)]) + }); + assert_eq!(fixed.source, "a\n", "two candidates: the fix is a guess"); + assert!(fixed.report.applied.is_empty()); +} + +#[test] +fn test_rhl2_fix_reverts_a_fix_after_which_the_error_count_rose() { + let fixed = fix_with("a\nb\nc\n", |t| letters_report(t, 'b')); + assert_eq!(fixed.source, "A\nb\nC\n", "the fix of `b` adds two errors"); + assert_eq!(fixed.report.reverted.len(), 1); + assert_eq!(fixed.report.reverted[0].line, 2); + assert_eq!(fixed.report.applied.len(), 2); +} + +#[test] +fn test_rhl2_fix_is_idempotent() { + let once = fix_with("a\nb\nc\n", |t| letters_report(t, 'b')); + let twice = fix_with(&once.source, |t| letters_report(t, 'b')); + assert_eq!(twice.source, once.source); + assert!(twice.report.applied.is_empty()); +} + +#[test] +fn test_rhl2_fix_rounds_are_bounded_by_the_initial_safe_fixes() { + // Every fix proposes another: without a bound this never ends. + let calls = Cell::new(0usize); + let check = |t: &str| { + calls.set(calls.get() + 1); + let n = t.lines().count(); + Report::for_test("t.rhl", vec![diag(n, &format!("x\n{n}"), true, 1)]) + }; + let fixed = fix_with("x\n", check); + assert_eq!(fixed.report.rounds, 1, "one initial safe fix, one round"); + assert_eq!(fixed.report.applied.len(), 1); + assert!(calls.get() < 10, "{} checks", calls.get()); +} + +#[test] +fn test_rhl2_fix_with_no_safe_fix_runs_no_round() { + let fixed = fix_with("a\n", |_| Report::for_test("t.rhl", Vec::new())); + assert_eq!(fixed.report.rounds, 0); + assert_eq!(fixed.source, "a\n"); +} + +// ---------------------------------------------------------------- the engine, on real programs + +#[test] +fn test_rhl2_fix_repairs_every_v2_typo_term_break_to_its_base() { + for (broken, base) in v2_breaks("typo-term") { + let fixed = fix_file(&broken); + assert_eq!(fixed.source, read(&base), "{}", broken.display()); + assert_eq!(fixed.report.report.exit_code(), 0, "{}", broken.display()); + assert_eq!(fixed.report.applied.len(), 1, "{}", broken.display()); + } +} + +#[test] +fn test_rhl2_fix_leaves_every_v2_two_candidate_break_unchanged() { + for (broken, _) in v2_breaks("two-candidate-typo") { + let fixed = fix_file(&broken); + assert_eq!(fixed.source, read(&broken), "{}", broken.display()); + assert!(fixed.report.applied.is_empty()); + assert!(fixed + .report + .report + .diagnostics + .iter() + .any(|d| d.code == codes::V003)); + } +} + +#[test] +fn test_rhl2_fix_reverts_a_real_fix_that_exposes_two_unit_errors() { + // `hou` → `hour` is safe at the edit site (RHL-1's local rule), but the + // Duration it gives is compared with a Size twice: two new errors for one. + let src = "use vocabulary fleet v2\n\njob \"d\"\n every 1 hour\n may read disk\n\n let t be 10 hou\n\n expect disk free of \"/\" is at least t\n expect disk free of \"/\" is above t\nend\n"; + let before = check("t.rhl", src, Some(&root())); + assert!( + before + .diagnostics + .iter() + .any(|d| d.fixes.iter().any(|f| f.safe)), + "check proposes the fix as safe: {before:#?}" + ); + let fixed = fix_safe("t.rhl", src, Some(&root())); + assert_eq!(fixed.source, src, "the fix raised the error count"); + assert_eq!(fixed.report.reverted.len(), 1, "{:#?}", fixed.report); +} + +#[test] +fn test_rhl2_fix_json_is_the_report_plus_applied() { + let broken = &v2_breaks("typo-term")[0].0; + let fixed = fix_file(broken); + let v = serde_json::to_value(&fixed.report).expect("serializable"); + for key in ["file", "verdict", "diagnostics", "unverified", "applied"] { + assert!(v.get(key).is_some(), "missing `{key}` in {v}"); + } + assert_eq!(v["verdict"], "pass"); + assert_eq!(v["applied"][0]["code"], "RHL-V002"); + assert_eq!(v["applied"][0]["edits"][0]["text"], "disk free of"); +} + +// ---------------------------------------------------------------- the verb + +#[test] +fn test_rhl2_fix_files_rewrites_in_place_and_exits_as_check() { + let tmp = tempfile::tempdir().expect("tempdir"); + for sub in ["vocab", "contracts"] { + copy_dir(&root().join(sub), &tmp.path().join(sub)); + } + let (broken, base) = &v2_breaks("typo-term")[1]; + let file = tmp.path().join("x.rhl"); + std::fs::copy(broken, &file).expect("copy"); + let out = fix_files(&[file.as_path()], true, OutputFormat::Text); + assert_eq!(out.exit, 0, "{out:?}"); + assert!(out.stdout.contains("fixed[RHL-V002]"), "{}", out.stdout); + assert_eq!(read(&file), read(base)); + let again = fix_files(&[file.as_path()], true, OutputFormat::Json); + assert_eq!(again.exit, 0); + let v: serde_json::Value = serde_json::from_str(&again.stdout).expect("JSON"); + assert_eq!(v["applied"].as_array().map(Vec::len), Some(0)); + assert_eq!(read(&file), read(base), "idempotent"); +} + +#[test] +fn test_rhl2_fix_without_safe_is_refused_and_writes_nothing() { + let (broken, _) = &v2_breaks("typo-term")[0]; + let out = fix_files(&[broken.as_path()], false, OutputFormat::Text); + assert_eq!(out.exit, 2); + assert!(out.stderr.contains("--safe"), "{}", out.stderr); +} + +#[test] +fn test_rhl2_fix_of_a_non_rhl_file_is_exit_1() { + let out = fix_files(&[Path::new("a.ruchy")], true, OutputFormat::Text); + assert_eq!(out.exit, 1); + assert!(out.stderr.contains("not an .rhl file"), "{}", out.stderr); +} + +fn copy_dir(from: &Path, to: &Path) { + std::fs::create_dir_all(to).expect("mkdir"); + for entry in std::fs::read_dir(from) + .expect("read_dir") + .filter_map(Result::ok) + { + let path = entry.path(); + if path.is_file() { + std::fs::copy(&path, to.join(entry.file_name())).expect("copy"); + } + } +} diff --git a/src/rhl/mod.rs b/src/rhl/mod.rs index 6ac752d31..2dfdaaa2c 100644 --- a/src/rhl/mod.rs +++ b/src/rhl/mod.rs @@ -14,10 +14,12 @@ pub mod check; pub mod cli; pub mod codes; pub mod diag; +pub mod fix; pub mod fmt; pub mod parse; pub mod tree; pub mod vocab; +pub mod vocab_cli; lalrpop_util::lalrpop_mod!(grammar, "/rhl/grammar.rs"); diff --git a/src/rhl/vocab.rs b/src/rhl/vocab.rs index dad5ea24a..55e81ca09 100644 --- a/src/rhl/vocab.rs +++ b/src/rhl/vocab.rs @@ -6,11 +6,11 @@ //! ([`find_root`]). Every relative path inside the file — a term's `contract:` //! and an entity's `instances_from:` — resolves against that same ``. -use serde::Deserialize; +use serde::{Deserialize, Serialize}; use std::path::{Path, PathBuf}; /// RHL's own closed list of term kinds (§3.4). -#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)] #[serde(rename_all = "snake_case")] pub enum TermKind { /// A value with no argument, for example `ticket title`. @@ -26,7 +26,7 @@ pub enum TermKind { } /// One parameter of a term: `{name: path, type: Text}`. -#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] pub struct Param { /// The parameter name. pub name: String, @@ -36,7 +36,7 @@ pub struct Param { } /// One vocabulary term. -#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] pub struct Term { /// The spelling, for example `disk free of`. pub term: String, @@ -76,7 +76,7 @@ impl Term { } /// A loaded vocabulary file. -#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)] pub struct Vocabulary { /// The name, for example `fleet`. pub vocabulary: String, @@ -119,14 +119,20 @@ pub fn parse_reference(words: &[&str]) -> Option<(String, u32)> { /// /// # Errors /// -/// Returns a message when the file cannot be read or parsed, or when it -/// declares a different name or version than the one asked for. +/// Returns a message when the file cannot be read or parsed, when it breaks +/// the vocabulary shape ([`shape_violations`]), or when it declares a +/// different name or version than the one asked for. pub fn load(root: &Path, name: &str, version: u32) -> Result { - let path = root.join("vocab").join(format!("{name}-v{version}.yaml")); - let text = std::fs::read_to_string(&path) - .map_err(|e| format!("cannot read {}: {e}", path.display()))?; - let vocab: Vocabulary = serde_yaml_ng::from_str(&text) - .map_err(|e| format!("cannot parse {}: {e}", path.display()))?; + let path = vocab_path(root, name, version); + let vocab = parse_file(&path)?; + let broken = shape_violations(&vocab); + if !broken.is_empty() { + return Err(format!( + "{} breaks the vocabulary shape: {}", + path.display(), + broken.join("; ") + )); + } if vocab.vocabulary != name || vocab.version != version { return Err(format!( "{} declares `{}`, not `{name} v{version}`", @@ -137,6 +143,68 @@ pub fn load(root: &Path, name: &str, version: u32) -> Result Ok(vocab) } +/// Where vocabulary `name` version `version` lives under `root`. +#[must_use] +pub fn vocab_path(root: &Path, name: &str, version: u32) -> PathBuf { + root.join("vocab").join(format!("{name}-v{version}.yaml")) +} + +/// Read and deserialize one vocabulary file. The fields and the closed list +/// of kinds are the serde shape of [`Vocabulary`]: a missing `vocabulary`, +/// `version`, `terms`, `term` or `kind`, an unknown kind, or an attribute or +/// parameter without a `type` fails here. +/// +/// # Errors +/// +/// Returns a message when the file cannot be read or does not have the shape. +pub fn parse_file(path: &Path) -> Result { + let text = std::fs::read_to_string(path) + .map_err(|e| format!("cannot read {}: {e}", path.display()))?; + serde_yaml_ng::from_str(&text).map_err(|e| format!("cannot parse {}: {e}", path.display())) +} + +/// The shape rules serde cannot state (`contracts/rhl-vocabulary-shape-v1.yaml`): +/// an entity names where its instances come from, a unit gives the quantity +/// it measures, and every spelling, parameter and attribute is non-empty and +/// typed. One message per violation, naming the term. +#[must_use] +pub fn shape_violations(vocab: &Vocabulary) -> Vec { + vocab.terms.iter().flat_map(term_violations).collect() +} + +fn term_violations(t: &Term) -> Vec { + let mut out = Vec::new(); + if t.term.trim().is_empty() { + out.push("a term has an empty spelling".to_string()); + } + let missing = match t.kind { + TermKind::Entity if blank(t.instances_from.as_deref()) => { + Some("entity has no `instances_from`") + } + TermKind::Unit if blank(t.gives.as_deref()) => Some("unit has no `gives`"), + _ => None, + }; + out.extend(missing.map(|m| format!("term `{}`: {m}", t.term))); + for (what, p) in t + .takes + .iter() + .map(|p| ("parameter", p)) + .chain(t.attributes.iter().map(|p| ("attribute", p))) + { + if p.name.trim().is_empty() || p.ty.trim().is_empty() { + out.push(format!( + "term `{}`: {what} `{}` has no name or no type", + t.term, p.name + )); + } + } + out +} + +fn blank(value: Option<&str>) -> bool { + value.is_none_or(|v| v.trim().is_empty()) +} + /// The terms of `vocab` whose `contract:` file does not exist under `root`. #[must_use] pub fn missing_contracts<'v>(root: &Path, vocab: &'v Vocabulary) -> Vec<&'v Term> { diff --git a/src/rhl/vocab_cli.rs b/src/rhl/vocab_cli.rs new file mode 100644 index 000000000..9e186362b --- /dev/null +++ b/src/rhl/vocab_cli.rs @@ -0,0 +1,327 @@ +//! `ruchy vocab list | show | validate` (spec RHL-001 §5; roadmap row RHL-2). +//! +//! Every subcommand reads vocabularies through the checker's own loader +//! ([`vocab::load`], [`vocab::parse_file`], [`vocab::shape_violations`], +//! [`vocab::missing_contracts`]), so `vocab validate` and `ruchy check` can +//! never disagree about whether a vocabulary is well formed. The shape is +//! stated in `contracts/rhl-vocabulary-shape-v1.yaml`. + +use super::check::Verdict; +use super::cli::{json, Outcome, OutputFormat}; +use super::codes; +use super::vocab::{self, Term, Vocabulary}; +use serde::Serialize; +use std::path::{Path, PathBuf}; + +/// The vocabulary root: `explicit` (`--root`), else the nearest ancestor of +/// `cwd`, `cwd` included, that holds a `vocab/` directory (the checker's +/// [`vocab::find_root`]). +#[must_use] +pub fn resolve_root(explicit: Option<&Path>, cwd: &Path) -> Option { + match explicit { + Some(root) => Some(root.to_path_buf()), + None => vocab::find_root(&cwd.join("_")), + } +} + +/// One row of `vocab list`. +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] +pub struct Listed { + /// The name, for example `fleet`. + pub name: String, + /// The version number. + pub version: u32, + /// How many terms it has (0 when it does not load). + pub terms: usize, + /// The file, relative to the root. + pub file: String, + /// Why the loader refused it, if it did. + #[serde(skip_serializing_if = "Option::is_none")] + pub error: Option, +} + +/// Every `vocab/-v.yaml` under `root`, by name then version. +#[must_use] +pub fn list(root: &Path) -> Vec { + let mut rows: Vec = std::fs::read_dir(root.join("vocab")) + .map(|dir| dir.filter_map(Result::ok).collect::>()) + .unwrap_or_default() + .iter() + .filter_map(|e| e.file_name().into_string().ok()) + .filter_map(|f| file_reference(&f)) + .map(|(name, version)| listed(root, name, version)) + .collect(); + rows.sort_by(|a, b| (&a.name, a.version).cmp(&(&b.name, b.version))); + rows +} + +/// `fleet-v2.yaml` → `("fleet", 2)`. +fn file_reference(file: &str) -> Option<(String, u32)> { + let stem = file.strip_suffix(".yaml")?; + let (name, version) = stem.rsplit_once("-v")?; + Some((name.to_string(), version.parse().ok()?)) +} + +fn listed(root: &Path, name: String, version: u32) -> Listed { + let file = format!("vocab/{name}-v{version}.yaml"); + let loaded = vocab::load(root, &name, version); + Listed { + terms: loaded.as_ref().map_or(0, |v| v.terms.len()), + error: loaded.err(), + name, + version, + file, + } +} + +/// `ruchy vocab list`: exit 0, or 1 when a vocabulary file does not load. +#[must_use] +pub fn list_outcome(root: &Path, format: OutputFormat) -> Outcome { + let rows = list(root); + let exit = i32::from(rows.iter().any(|r| r.error.is_some())); + let stdout = match format { + OutputFormat::Json => json(&rows), + OutputFormat::Text => rows.iter().map(list_line).collect(), + }; + Outcome { + stdout, + stderr: String::new(), + exit, + } +} + +fn list_line(r: &Listed) -> String { + match &r.error { + None => format!("{} v{} {} terms {}\n", r.name, r.version, r.terms, r.file), + Some(e) => format!("{} v{} does not load: {e}\n", r.name, r.version), + } +} + +/// `v2` or `2` → 2. +#[must_use] +pub fn parse_version(text: &str) -> Option { + text.strip_prefix('v').unwrap_or(text).parse().ok() +} + +/// `ruchy vocab show `: every term with its kind, `takes`, +/// `gives`, effect, attributes and contract. A vocabulary the loader refuses +/// is RHL-V004 on standard error, exit 2. +#[must_use] +pub fn show_outcome(root: &Path, name: &str, version: &str, format: OutputFormat) -> Outcome { + let loaded = parse_version(version) + .ok_or_else(|| format!("`{version}` is not a version: write `v2` or `2`")) + .and_then(|n| vocab::load(root, name, n)); + match loaded { + Ok(v) => Outcome { + stdout: render_show(&v, format), + stderr: String::new(), + exit: 0, + }, + Err(e) => Outcome { + stdout: String::new(), + stderr: format!( + "error[{}]: unknown vocabulary `{name} {version}`: {e}\n", + codes::V004 + ), + exit: 2, + }, + } +} + +fn render_show(v: &Vocabulary, format: OutputFormat) -> String { + match format { + OutputFormat::Json => json(v), + OutputFormat::Text => { + let head = format!("{} ({} terms)\n", v.label(), v.terms.len()); + head + &v.terms.iter().map(term_line).collect::() + } + } +} + +fn term_line(t: &Term) -> String { + let params = |ps: &[vocab::Param]| { + ps.iter() + .map(|p| format!("{}: {}", p.name, p.ty)) + .collect::>() + .join(", ") + }; + let mut parts = vec![ + format!(" {}", t.term), + format!("{:?}", t.kind).to_lowercase(), + ]; + let optional = [ + ("takes", Some(params(&t.takes)).filter(|s| !s.is_empty())), + ("gives", t.gives.clone()), + ("effect", t.effect.clone()), + ( + "attributes", + Some(params(&t.attributes)).filter(|s| !s.is_empty()), + ), + ("instances from", t.instances_from.clone()), + ("contract", Some(t.contract.clone())), + ]; + parts.extend( + optional + .into_iter() + .filter_map(|(k, v)| v.map(|v| format!("{k} {v}"))), + ); + parts.join(" ") + "\n" +} + +/// One finding of `vocab validate`. +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] +pub struct Finding { + /// RHL-V004 (the loader refuses the file) or RHL-C001 (a term with no + /// contract template). + pub code: String, + /// The term, when the finding is about one. + #[serde(skip_serializing_if = "Option::is_none")] + pub term: Option, + /// A one-line message. + pub message: String, +} + +/// The result of `vocab validate`. +#[derive(Debug, Clone, PartialEq, Eq, Serialize)] +pub struct Validation { + /// The file validated. + pub file: String, + /// `fleet v2`, when the file deserialized. + #[serde(skip_serializing_if = "Option::is_none")] + pub vocabulary: Option, + /// Pass, fail or refused, as for `ruchy check`. + pub verdict: Verdict, + /// Every finding. + pub diagnostics: Vec, +} + +impl Validation { + /// 0 pass, 1 error, 2 refusal. + #[must_use] + pub fn exit_code(&self) -> i32 { + match self.verdict { + Verdict::Pass => 0, + Verdict::Fail => 1, + Verdict::Refused => 2, + } + } +} + +/// Validate the vocabulary file `path`, whose contract paths resolve under `root`. +#[must_use] +pub fn validate(path: &Path, root: &Path) -> Validation { + let (vocabulary, diagnostics) = match vocab::parse_file(path) { + Err(e) => (None, vec![finding(codes::V004, None, e)]), + Ok(v) => (Some(v.label()), findings(&v, root)), + }; + let verdict = verdict_of(&diagnostics); + Validation { + file: path.display().to_string(), + vocabulary, + verdict, + diagnostics, + } +} + +/// Shape violations (RHL-V004: the loader refuses the file) then missing +/// contract templates (RHL-C001, §3.4). +fn findings(v: &Vocabulary, root: &Path) -> Vec { + let shape = vocab::shape_violations(v).into_iter().map(|m| { + finding( + codes::V004, + None, + format!("breaks the vocabulary shape: {m}"), + ) + }); + let contracts = vocab::missing_contracts(root, v).into_iter().map(|t| { + let message = format!( + "term `{}` has no contract template `{}`", + t.term, t.contract + ); + finding(codes::C001, Some(t.term.clone()), message) + }); + shape.chain(contracts).collect() +} + +fn finding(code: &str, term: Option, message: String) -> Finding { + Finding { + code: code.to_string(), + term, + message, + } +} + +fn verdict_of(findings: &[Finding]) -> Verdict { + let refused = findings + .iter() + .any(|f| codes::lookup(&f.code).is_some_and(|c| c.refusal.is_some())); + match (refused, findings.is_empty()) { + (true, _) => Verdict::Refused, + (false, true) => Verdict::Pass, + (false, false) => Verdict::Fail, + } +} + +/// `ruchy vocab validate ` or ` `. +#[must_use] +pub fn validate_outcome(root: Option<&Path>, target: &[String], format: OutputFormat) -> Outcome { + let resolved = match target { + [file] => Ok(PathBuf::from(file)), + [name, version] => parse_version(version) + .ok_or_else(|| format!("`{version}` is not a version: write `v2` or `2`")) + .and_then(|n| { + root.map(|r| vocab::vocab_path(r, name, n)) + .ok_or_else(no_root) + }), + _ => Err("vocab validate takes or ".to_string()), + }; + let path = match resolved { + Ok(p) => p, + Err(e) => return usage(e), + }; + let Some(root) = root + .map(Path::to_path_buf) + .or_else(|| vocab::find_root(&path)) + else { + return usage(no_root()); + }; + let v = validate(&path, &root); + Outcome { + stdout: render_validation(&v, format), + stderr: String::new(), + exit: v.exit_code(), + } +} + +/// The message for a vocabulary command run outside any vocabulary root. +#[must_use] +pub fn no_root() -> String { + "no `vocab/` directory in this directory or any parent; pass --root".to_string() +} + +fn usage(message: String) -> Outcome { + Outcome { + stdout: String::new(), + stderr: message + "\n", + exit: 1, + } +} + +fn render_validation(v: &Validation, format: OutputFormat) -> String { + match format { + OutputFormat::Json => json(v), + OutputFormat::Text => { + let mut out: String = v + .diagnostics + .iter() + .map(|f| format!("{}: error[{}]: {}\n", v.file, f.code, f.message)) + .collect(); + out.push_str(&format!("{}: {}\n", v.file, v.verdict.word())); + out + } + } +} + +#[cfg(test)] +#[path = "vocab_cli_tests.rs"] +mod vocab_cli_tests; diff --git a/src/rhl/vocab_cli_tests.rs b/src/rhl/vocab_cli_tests.rs new file mode 100644 index 000000000..0e4c18e9c --- /dev/null +++ b/src/rhl/vocab_cli_tests.rs @@ -0,0 +1,251 @@ +//! RHL-2 `ruchy vocab` tests: list, show, validate, and one test per rule of +//! the vocabulary shape (`contracts/rhl-vocabulary-shape-v1.yaml`). + +use super::*; +use crate::rhl::check::check; +use std::path::Path; + +fn root() -> PathBuf { + crate::rhl::repo_root() +} + +/// A root holding one vocabulary `demo v1` with `terms` (YAML list items) +/// and a contract file `contracts/c.yaml`. +fn shaped(terms: &str) -> tempfile::TempDir { + let tmp = tempfile::tempdir().expect("tempdir"); + std::fs::create_dir_all(tmp.path().join("vocab")).expect("mkdir"); + std::fs::create_dir_all(tmp.path().join("contracts")).expect("mkdir"); + std::fs::write(tmp.path().join("contracts/c.yaml"), "x: 1\n").expect("write"); + let yaml = format!("vocabulary: demo\nversion: 1\nterms:\n{terms}"); + std::fs::write(tmp.path().join("vocab/demo-v1.yaml"), yaml).expect("write"); + tmp +} + +const GOOD: &str = " - {term: disk free of, kind: measure, takes: [{name: path, type: Text}], gives: Size, effect: read disk, contract: contracts/c.yaml}\n - {term: GB, kind: unit, gives: Size, contract: contracts/c.yaml}\n - {term: host, kind: entity, instances_from: machines/*.yaml, contract: contracts/c.yaml}\n - {term: file ticket, kind: action, attributes: [{name: title, type: Text}], contract: contracts/c.yaml}\n"; + +fn codes_of(tmp: &tempfile::TempDir) -> Vec { + let v = validate(&tmp.path().join("vocab/demo-v1.yaml"), tmp.path()); + v.diagnostics.iter().map(|f| f.code.clone()).collect() +} + +// ---------------------------------------------------------------- the shape, one test per rule + +#[test] +fn test_rhl2_shape_a_well_formed_vocabulary_passes() { + let tmp = shaped(GOOD); + let v = validate(&tmp.path().join("vocab/demo-v1.yaml"), tmp.path()); + assert_eq!(v.diagnostics, Vec::::new()); + assert_eq!((v.verdict, v.exit_code()), (Verdict::Pass, 0)); + assert_eq!(v.vocabulary.as_deref(), Some("demo v1")); +} + +#[test] +fn test_rhl2_shape_every_pre_registered_vocabulary_passes() { + for (name, version) in [("fleet", 1), ("fleet", 2), ("tickets", 1), ("tickets", 2)] { + let v = validate(&vocab::vocab_path(&root(), name, version), &root()); + assert_eq!(v.diagnostics, Vec::::new(), "{name} v{version}"); + } +} + +#[test] +fn test_rhl2_shape_missing_field_is_v004() { + let tmp = shaped(" - {kind: noun, gives: Text, contract: contracts/c.yaml}\n"); + assert_eq!(codes_of(&tmp), vec![codes::V004]); +} + +#[test] +fn test_rhl2_shape_unknown_kind_is_v004() { + let tmp = shaped(" - {term: x, kind: verb, contract: contracts/c.yaml}\n"); + assert_eq!(codes_of(&tmp), vec![codes::V004]); +} + +#[test] +fn test_rhl2_shape_term_without_existing_contract_is_c001() { + let missing = shaped(" - {term: x, kind: noun, gives: Text, contract: contracts/gone.yaml}\n"); + assert_eq!(codes_of(&missing), vec![codes::C001]); + let omitted = shaped(" - {term: x, kind: noun, gives: Text}\n"); + let v = validate(&omitted.path().join("vocab/demo-v1.yaml"), omitted.path()); + assert_eq!(v.diagnostics[0].code, codes::C001); + assert_eq!(v.diagnostics[0].term.as_deref(), Some("x")); + assert_eq!((v.verdict, v.exit_code()), (Verdict::Refused, 2)); +} + +#[test] +fn test_rhl2_shape_untyped_attribute_is_v004() { + let no_type = shaped( + " - {term: a, kind: action, attributes: [{name: title}], contract: contracts/c.yaml}\n", + ); + assert_eq!(codes_of(&no_type), vec![codes::V004]); + let empty = shaped(" - {term: a, kind: action, attributes: [{name: title, type: \"\"}], contract: contracts/c.yaml}\n"); + assert_eq!(codes_of(&empty), vec![codes::V004]); +} + +#[test] +fn test_rhl2_shape_entity_without_instances_from_is_v004() { + let tmp = shaped(" - {term: host, kind: entity, contract: contracts/c.yaml}\n"); + assert_eq!(codes_of(&tmp), vec![codes::V004]); + let v = validate(&tmp.path().join("vocab/demo-v1.yaml"), tmp.path()); + assert!(v.diagnostics[0].message.contains("instances_from"), "{v:?}"); +} + +#[test] +fn test_rhl2_shape_unit_without_gives_is_v004() { + let tmp = shaped(" - {term: GB, kind: unit, contract: contracts/c.yaml}\n"); + assert_eq!(codes_of(&tmp), vec![codes::V004]); + let v = validate(&tmp.path().join("vocab/demo-v1.yaml"), tmp.path()); + assert!(v.diagnostics[0].message.contains("gives"), "{v:?}"); +} + +#[test] +fn test_rhl2_shape_is_the_checkers_loader() { + // One implementation: a vocabulary `vocab validate` refuses for its shape + // is one `ruchy check` cannot load (RHL-V004). + let tmp = shaped(" - {term: host, kind: entity, contract: contracts/c.yaml}\n"); + assert!(vocab::load(tmp.path(), "demo", 1).is_err()); + let src = "use vocabulary demo v1\n\njob \"j\"\n every 1 hour\nend\n"; + let report = check("t.rhl", src, Some(tmp.path())); + let v004 = report + .diagnostics + .iter() + .find(|d| d.code == codes::V004) + .unwrap_or_else(|| panic!("the vocabulary is refused: {report:#?}")); + assert!(v004.message.contains("instances_from"), "{v004:?}"); +} + +// ---------------------------------------------------------------- list, show, validate + +#[test] +fn test_rhl2_vocab_list_names_every_vocabulary_with_its_term_count() { + let rows = list(&root()); + let names: Vec = rows + .iter() + .map(|r| format!("{} v{}", r.name, r.version)) + .collect(); + assert_eq!( + names, + vec!["fleet v1", "fleet v2", "tickets v1", "tickets v2"] + ); + for r in &rows { + let loaded = vocab::load(&root(), &r.name, r.version).expect("loads"); + assert_eq!(r.terms, loaded.terms.len()); + assert_eq!(r.error, None); + } + let out = list_outcome(&root(), OutputFormat::Json); + assert_eq!(out.exit, 0); + let v: serde_json::Value = serde_json::from_str(&out.stdout).expect("JSON"); + assert_eq!(v[3]["name"], "tickets"); + assert_eq!(v[3]["version"], 2); +} + +#[test] +fn test_rhl2_vocab_list_reports_a_file_that_does_not_load_exit_1() { + let tmp = shaped(" - {term: GB, kind: unit, contract: contracts/c.yaml}\n"); + let out = list_outcome(tmp.path(), OutputFormat::Text); + assert_eq!(out.exit, 1); + assert!( + out.stdout.contains("demo v1 does not load"), + "{}", + out.stdout + ); +} + +#[test] +fn test_rhl2_vocab_show_gives_every_field_of_every_term() { + let out = show_outcome(&root(), "tickets", "v2", OutputFormat::Json); + assert_eq!(out.exit, 0, "{}", out.stderr); + let v: serde_json::Value = serde_json::from_str(&out.stdout).expect("JSON"); + let file = v["terms"] + .as_array() + .expect("terms") + .iter() + .find(|t| t["term"] == "file ticket") + .expect("file ticket"); + assert_eq!(file["kind"], "action"); + assert_eq!(file["takes"][0]["type"], "Text"); + assert_eq!(file["gives"], "Ticket"); + assert_eq!(file["effect"], "write tickets"); + assert_eq!(file["attributes"][1]["name"], "label"); + assert_eq!( + file["contract"], + "contracts/rhl-tickets-file-ticket-v2.yaml" + ); + let text = show_outcome(&root(), "fleet", "1", OutputFormat::Text); + assert!(text.stdout.starts_with("fleet v1 ("), "{}", text.stdout); + assert!( + text.stdout.contains("disk free of measure"), + "{}", + text.stdout + ); +} + +#[test] +fn test_rhl2_vocab_show_of_an_unknown_vocabulary_is_v004_exit_2() { + let out = show_outcome(&root(), "fleet", "v9", OutputFormat::Text); + assert_eq!(out.exit, 2); + assert!(out.stderr.contains("RHL-V004"), "{}", out.stderr); +} + +#[test] +fn test_rhl2_vocab_validate_by_name_or_by_file() { + let r = root(); + let by_name = validate_outcome(Some(&r), &["fleet".into(), "v2".into()], OutputFormat::Json); + assert_eq!(by_name.exit, 0, "{}{}", by_name.stdout, by_name.stderr); + let file = r.join("vocab/tickets-v1.yaml").display().to_string(); + let by_file = validate_outcome(None, &[file], OutputFormat::Text); + assert_eq!(by_file.exit, 0, "{}{}", by_file.stdout, by_file.stderr); + assert!( + by_file.stdout.trim_end().ends_with(": pass"), + "{}", + by_file.stdout + ); + let bad = validate_outcome( + Some(&r), + &["a".into(), "b".into(), "c".into()], + OutputFormat::Text, + ); + assert_eq!(bad.exit, 1); +} + +#[test] +fn test_rhl2_vocab_validate_of_a_deleted_contract_is_c001_exit_2() { + let tmp = tempfile::tempdir().expect("tempdir"); + for sub in ["vocab", "contracts"] { + let to = tmp.path().join(sub); + std::fs::create_dir_all(&to).expect("mkdir"); + for e in std::fs::read_dir(root().join(sub)) + .expect("dir") + .filter_map(Result::ok) + { + if e.path().is_file() { + std::fs::copy(e.path(), to.join(e.file_name())).expect("copy"); + } + } + } + std::fs::remove_file(tmp.path().join("contracts/rhl-fleet-gb-v2.yaml")).expect("rm"); + let out = validate_outcome( + Some(tmp.path()), + &["fleet".into(), "2".into()], + OutputFormat::Json, + ); + assert_eq!(out.exit, 2); + let v: serde_json::Value = serde_json::from_str(&out.stdout).expect("JSON"); + assert_eq!(v["verdict"], "refused"); + assert_eq!(v["diagnostics"][0]["code"], "RHL-C001"); + assert_eq!(v["diagnostics"][0]["term"], "GB"); +} + +#[test] +fn test_rhl2_vocab_root_is_explicit_or_found_from_the_cwd() { + let r = root(); + assert_eq!(resolve_root(None, &r), Some(r.clone())); + assert_eq!(resolve_root(None, &r.join("docs/rhl")), Some(r.clone())); + let tmp = tempfile::tempdir().expect("tempdir"); + assert_eq!(resolve_root(None, tmp.path()), None); + assert_eq!( + resolve_root(Some(Path::new("/x")), tmp.path()), + Some(PathBuf::from("/x")) + ); + assert_eq!(parse_version("v2"), Some(2)); + assert_eq!(parse_version("2"), Some(2)); + assert_eq!(parse_version("two"), None); +} diff --git a/tests/rhl_cli.rs b/tests/rhl_cli.rs new file mode 100644 index 000000000..648ae0b75 --- /dev/null +++ b/tests/rhl_cli.rs @@ -0,0 +1,144 @@ +//! RHL-2: `ruchy fix --safe` and `ruchy vocab`, through the built binary +//! (spec RHL-001 §5). Every decision is in the library and gated by +//! `cargo test --lib` (`src/rhl/fix.rs`, `src/rhl/vocab_cli.rs`); this file +//! shows what only a process can: the exit code, the file written in place, +//! and the root found from the working directory. + +use assert_cmd::Command; +use predicates::prelude::*; +use std::path::{Path, PathBuf}; + +fn ruchy_cmd() -> Command { + assert_cmd::cargo::cargo_bin_cmd!("ruchy") +} + +fn repo() -> PathBuf { + PathBuf::from(env!("CARGO_MANIFEST_DIR")) +} + +fn copy_dir(from: &Path, to: &Path) { + std::fs::create_dir_all(to).expect("mkdir"); + for entry in std::fs::read_dir(from) + .expect("read_dir") + .filter_map(Result::ok) + { + if entry.path().is_file() { + std::fs::copy(entry.path(), to.join(entry.file_name())).expect("copy"); + } + } +} + +/// A temporary vocabulary root (vocab/ and contracts/) holding a copy of `rel` as `x.rhl`. +fn rooted(rel: &str) -> (tempfile::TempDir, PathBuf) { + let dir = tempfile::tempdir().expect("temp dir"); + for sub in ["vocab", "contracts"] { + copy_dir(&repo().join(sub), &dir.path().join(sub)); + } + let file = dir.path().join("x.rhl"); + std::fs::copy(repo().join(rel), &file).expect("copy"); + (dir, file) +} + +const TYPO: &str = "docs/rhl/breaks/v2/planted/typo-term/03-gx12-runner-load-watch/broken.rhl"; +const TYPO_BASE: &str = "docs/rhl/breaks/v2/valid/03-gx12-runner-load-watch.rhl"; +const TWO: &str = "docs/rhl/breaks/v2/planted/two-candidate-typo/01-gx10-disk-watch/broken.rhl"; + +#[test] +fn test_rhl2_cli_fix_safe_rewrites_a_typo_file_and_exits_0() { + let (_dir, file) = rooted(TYPO); + ruchy_cmd() + .arg("fix") + .arg("--safe") + .arg(&file) + .assert() + .code(0) + .stdout(predicate::str::contains("fixed[RHL-V002]")); + let fixed = std::fs::read_to_string(&file).expect("read"); + assert_eq!( + fixed, + std::fs::read_to_string(repo().join(TYPO_BASE)).expect("base") + ); + ruchy_cmd().arg("check").arg(&file).assert().code(0); + // Idempotent: the second run applies nothing and changes nothing. + ruchy_cmd() + .args(["fix", "--safe", "--format", "json"]) + .arg(&file) + .assert() + .code(0) + .stdout(predicate::str::contains("\"applied\": []")); + assert_eq!(std::fs::read_to_string(&file).expect("read"), fixed); +} + +#[test] +fn test_rhl2_cli_fix_safe_leaves_a_two_candidate_file_unchanged() { + let (_dir, file) = rooted(TWO); + let before = std::fs::read_to_string(&file).expect("read"); + // V003 is the `Ambiguous` refusal: exit 2, as `ruchy check` gives it. + ruchy_cmd() + .args(["fix", "--safe"]) + .arg(&file) + .assert() + .code(2) + .stdout(predicate::str::contains("error[RHL-V003]")); + assert_eq!(std::fs::read_to_string(&file).expect("read"), before); +} + +#[test] +fn test_rhl2_cli_fix_without_safe_is_refused() { + let (_dir, file) = rooted(TYPO); + let before = std::fs::read_to_string(&file).expect("read"); + ruchy_cmd() + .arg("fix") + .arg(&file) + .assert() + .code(2) + .stderr(predicate::str::contains("--safe")); + assert_eq!(std::fs::read_to_string(&file).expect("read"), before); +} + +#[test] +fn test_rhl2_cli_vocab_list_json_lists_the_four_vocabularies() { + let out = ruchy_cmd() + .current_dir(repo().join("docs/rhl")) + .args(["vocab", "list", "--format", "json"]) + .assert() + .code(0) + .get_output() + .stdout + .clone(); + let v: serde_json::Value = serde_json::from_slice(&out).expect("JSON"); + let names: Vec = v + .as_array() + .expect("array") + .iter() + .map(|r| format!("{} v{}", r["name"].as_str().unwrap_or(""), r["version"])) + .collect(); + assert_eq!(names, ["fleet v1", "fleet v2", "tickets v1", "tickets v2"]); +} + +#[test] +fn test_rhl2_cli_vocab_show_json_has_the_terms() { + ruchy_cmd() + .current_dir(repo()) + .args(["vocab", "show", "tickets", "v2", "--format", "json"]) + .assert() + .code(0) + .stdout(predicate::str::contains("\"term\": \"ticket count\"")); +} + +#[test] +fn test_rhl2_cli_vocab_validate_with_a_deleted_contract_exits_2_c001() { + let (dir, _) = rooted(TYPO); + std::fs::remove_file(dir.path().join("contracts/rhl-fleet-host-v2.yaml")).expect("rm"); + ruchy_cmd() + .current_dir(dir.path()) + .args(["vocab", "validate", "fleet", "v2"]) + .assert() + .code(2) + .stdout(predicate::str::contains("error[RHL-C001]")); + ruchy_cmd() + .current_dir(dir.path()) + .args(["vocab", "validate", "vocab/tickets-v2.yaml"]) + .assert() + .code(0); +} From aa9cbf7023bbfad36aefefbf584c52e035756768 Mon Sep 17 00:00:00 2001 From: Noah Gift Date: Wed, 23 Sep 2026 14:02:57 +0200 Subject: [PATCH 6/9] RHL-2: vocabulary shape contract and rhl-2.md Pmat-Ticket: RHLGA-1 Co-Authored-By: Claude Opus 5.5 (1M context) --- contracts/rhl-vocabulary-shape-v1.yaml | 162 +++++++++++++++++++++++++ docs/rhl/rhl-2.md | 76 ++++++++++++ 2 files changed, 238 insertions(+) create mode 100644 contracts/rhl-vocabulary-shape-v1.yaml create mode 100644 docs/rhl/rhl-2.md diff --git a/contracts/rhl-vocabulary-shape-v1.yaml b/contracts/rhl-vocabulary-shape-v1.yaml new file mode 100644 index 000000000..6a716a816 --- /dev/null +++ b/contracts/rhl-vocabulary-shape-v1.yaml @@ -0,0 +1,162 @@ +metadata: + version: "1.0.0" + author: "RHL-2" + description: "RHL-2 vocabulary shape: the shape every vocab/-v.yaml must have for `ruchy check` to load it and `ruchy vocab validate` to pass it. One implementation: src/rhl/vocab.rs (parse_file, shape_violations, missing_contracts)." + crate: "ruchy" + references: + - "RHL-001 §3.4: vocabularies are versioned; every term carries a contract template" + - "RHL-001 §5: `ruchy vocab` lists, shows and validates vocabularies" + - "docs/rhl/rhl-2.md: the two verbs, the safe-fix rule and this shape" + +equations: + vocabulary_fields: + formula: "∀ f ∈ vocab/*.yaml : has(f, vocabulary) ∧ has(f, version ∈ ℕ) ∧ has(f, terms) ∧ ∀ t ∈ f.terms : has(t, term) ∧ has(t, kind)" + domain: "every vocabulary file under a vocabulary root" + invariants: + - "A file missing `vocabulary`, `version`, `terms`, or a term missing `term` or `kind`, does not deserialize; the loader refuses it and the checker reports RHL-V004" + - "A term's spelling is not empty" + + kinds_closed: + formula: "∀ t : t.kind ∈ {noun, measure, action, unit, entity}" + domain: "every term of every vocabulary file" + invariants: + - "Any other kind fails to deserialize: RHL-V004" + + every_term_contracted: + formula: "∀ t : exists(root / t.contract)" + domain: "every term; `contract` resolves against the vocabulary root" + invariants: + - "A term with no contract, or one naming a missing file, refuses the vocabulary by name: RHL-C001 (NoContractTemplate)" + + parameters_typed: + formula: "∀ t, ∀ p ∈ t.takes ∪ t.attributes : p.name ≠ '' ∧ p.type ≠ ''" + domain: "every parameter and every attribute of every term" + invariants: + - "An attribute or parameter with no `type` fails to deserialize; one with an empty name or type breaks the shape: RHL-V004" + + kind_specific_fields: + formula: "∀ t : (t.kind = entity ⇒ t.instances_from ≠ '') ∧ (t.kind = unit ⇒ t.gives ≠ '')" + domain: "every entity and every unit term" + invariants: + - "An entity names the glob its instances come from (plan D6)" + - "A unit names the quantity type it measures" + - "Either violation breaks the shape: RHL-V004" + + one_loader: + formula: "load(root, n, v) = Ok ⇔ validate(root/vocab/n-vv.yaml) has no RHL-V004 finding ∧ names n vv" + domain: "`ruchy check` (src/rhl/check.rs Loader) and `ruchy vocab validate` (src/rhl/vocab_cli.rs)" + invariants: + - "The checker and the vocab verb call the same parse and shape functions, so they cannot disagree" + +proof_obligations: + - type: completeness + property: "Every field the shape requires is required by the loader" + formal: "missing field ⇒ RHL-V004" + applies_to: all + - type: invariant + property: "Term kinds are RHL's closed list" + formal: "kind ∉ {noun, measure, action, unit, entity} ⇒ RHL-V004" + applies_to: all + - type: completeness + property: "Every term carries an existing contract template" + formal: "¬exists(root / t.contract) ⇒ RHL-C001" + applies_to: all + - type: invariant + property: "Attributes are typed" + formal: "attribute without a type ⇒ RHL-V004" + applies_to: all + - type: invariant + property: "Entities have instances_from and units have gives" + formal: "entity without instances_from ∨ unit without gives ⇒ RHL-V004" + applies_to: all + - type: equivalence + property: "vocab validate and the checker share one loader" + formal: "shape violation ⇒ `ruchy check` of a program using it reports RHL-V004" + applies_to: all + +falsification_tests: + - id: FALSIFY-RHL2-SHAPE-001 + rule: "A well-formed vocabulary passes" + test: "test_rhl2_shape_a_well_formed_vocabulary_passes" + prediction: "verified" + if_fails: "The shape refuses a vocabulary it should accept; every check would be V004" + - id: FALSIFY-RHL2-SHAPE-002 + rule: "The four committed vocabularies have the shape" + test: "test_rhl2_shape_every_pre_registered_vocabulary_passes" + prediction: "verified" + if_fails: "fleet or tickets v1/v2 break the shape they are checked against" + - id: FALSIFY-RHL2-SHAPE-003 + rule: "A missing required field is RHL-V004" + test: "test_rhl2_shape_missing_field_is_v004" + prediction: "verified" + if_fails: "A term without a spelling would load" + - id: FALSIFY-RHL2-SHAPE-004 + rule: "A kind outside the closed list is RHL-V004" + test: "test_rhl2_shape_unknown_kind_is_v004" + prediction: "verified" + if_fails: "The kind list is open and the checker's namespaces are undefined" + - id: FALSIFY-RHL2-SHAPE-005 + rule: "A term with no existing contract is RHL-C001" + test: "test_rhl2_shape_term_without_existing_contract_is_c001" + prediction: "verified" + if_fails: "§3.4 is violated: a term without a contract template is accepted" + - id: FALSIFY-RHL2-SHAPE-006 + rule: "An untyped attribute is RHL-V004" + test: "test_rhl2_shape_untyped_attribute_is_v004" + prediction: "verified" + if_fails: "An attribute value could not be type-checked" + - id: FALSIFY-RHL2-SHAPE-007 + rule: "An entity without instances_from is RHL-V004" + test: "test_rhl2_shape_entity_without_instances_from_is_v004" + prediction: "verified" + if_fails: "An entity's instances have no declared source (plan D6)" + - id: FALSIFY-RHL2-SHAPE-008 + rule: "A unit without gives is RHL-V004" + test: "test_rhl2_shape_unit_without_gives_is_v004" + prediction: "verified" + if_fails: "A quantity's dimension is undefined and unit checks (RHL-T001) are vacuous" + - id: FALSIFY-RHL2-SHAPE-009 + rule: "The checker refuses what vocab validate refuses" + test: "test_rhl2_shape_is_the_checkers_loader" + prediction: "verified" + if_fails: "Two implementations of the shape exist and can disagree" + - id: FALSIFY-RHL2-SHAPE-010 + rule: "vocab validate reports a deleted contract as RHL-C001, exit 2" + test: "test_rhl2_vocab_validate_of_a_deleted_contract_is_c001_exit_2" + prediction: "verified" + if_fails: "The verb does not refuse a vocabulary the checker refuses" + +# The shape is a property of YAML files on disk, checked by deserialization +# and by src/rhl/vocab.rs shape_violations. Kani symbolically executes Rust and +# cannot reach a file system, so no obligation here is dischargeable by a Kani +# harness. `pv validate` requires the block; each entry is marked and names the +# falsification test that discharges it (the pattern of rhl-pre-registration-v1). +kani_harnesses: + - id: KANI-RHL2-SHAPE-001 + obligation: PO-RHL2-SHAPE-001 + property: "NOT APPLICABLE — the vocabulary shape is a property of files on disk, outside Kani's reach" + bound: 1 + strategy: bounded_int + harness: none + status: declared_not_implemented + discharged_by: "test_rhl2_shape_missing_field_is_v004" + - id: KANI-RHL2-SHAPE-002 + obligation: PO-RHL2-SHAPE-006 + property: "NOT APPLICABLE — one-loader equivalence is a property of two call paths reading one file" + bound: 1 + strategy: bounded_int + harness: none + status: declared_not_implemented + discharged_by: "test_rhl2_shape_is_the_checkers_loader" + +qa_gate: + id: QA-RHL2-VOCABULARY-SHAPE + name: "Every vocabulary has the RHL-2 shape, enforced by the checker's own loader" + min_coverage: 1.0 + max_complexity: 10 + required_tests: + - "test_rhl2_shape_every_pre_registered_vocabulary_passes" + - "test_rhl2_shape_term_without_existing_contract_is_c001" + - "test_rhl2_shape_entity_without_instances_from_is_v004" + - "test_rhl2_shape_unit_without_gives_is_v004" + - "test_rhl2_shape_is_the_checkers_loader" diff --git a/docs/rhl/rhl-2.md b/docs/rhl/rhl-2.md new file mode 100644 index 000000000..740bd9c1a --- /dev/null +++ b/docs/rhl/rhl-2.md @@ -0,0 +1,76 @@ +# RHL-2 — `ruchy fix --safe`, `ruchy vocab`, and the vocabulary shape + +Roadmap row RHL-2; spec RHL-001 §3.4, §3.5 and §5; plan `docs/rhl/rhlga-1-plan.md` ruling Q5. +The code is `src/rhl/fix.rs`, `src/rhl/vocab_cli.rs` and `src/rhl/vocab.rs`. The binary +only prints the result and exits with its code (`src/bin/handlers/rhl_handler.rs`). + +## `ruchy fix --safe ...` + +`ruchy check` marks a fix `safe` when it is the only candidate and the fixed program has +no type or unit error on the line it edits. That is RHL-1's local rule, and `check` keeps +it (ruling Q5). `fix --safe` adds the global guard: + +1. Check the file. The number of safe fixes it proposes, N, bounds the rounds. +2. In each round, try every safe fix of the current text, last position first. After each + fix, check the whole file again. If the file's error count rose, revert that fix and do + not try it again. Otherwise keep it. +3. Stop when a round keeps no fix, or after N rounds. +4. Write the file in place if it changed. Print one `fixed[CODE]` line for each fix kept + and one `reverted[CODE]` line for each fix reverted, then the check of the result. + `--format json` prints the check report object with three more fields: `applied`, + `reverted` and `rounds`. + +A fix is never applied when its diagnostic has two or more candidates, even if it is +marked safe. The checker never marks such a fix safe (RHL-V003), and `fix` checks again. +Running `fix --safe` a second time changes nothing: every fix left is either unsafe or +already reverted once, and a reverted fix is reverted again. + +The exit codes are the same as for `ruchy check` on the result: 0 clean, 1 errors +remain, 2 a refusal remains. So a file whose only error is a two-candidate typo exits 2 +unchanged. `ruchy fix` without `--safe` is refused (exit 2): v0 has only safe mode. A +file that is not `.rhl` gives exit 1. + +Measured on the v2 break corpus: each of the 12 `typo-term` breaks becomes, byte for +byte, its valid base program, and that program checks clean. None of the 12 +`two-candidate-typo` breaks is changed. The case that needs the global guard is +`let t be 10 hou` compared with a Size twice. `hou` → `hour` is safe at the edit site, +but it turns one error into two RHL-T001s, so `fix --safe` reverts it. + +## `ruchy vocab` + +The vocabulary root is `--root`, or else the nearest directory, starting at the current +one, that holds `vocab/`. This is the same rule `ruchy check` uses (`find_root`). + +| Command | Prints | Exit | +|---|---|---| +| `vocab list [--format json]` | every `vocab/-v.yaml`: name, version, term count, file | 0, or 1 if a file does not load | +| `vocab show [--format json]` | every term: kind, `takes`, `gives`, effect, attributes, `instances_from`, contract | 0, or 2 with RHL-V004 | +| `vocab validate \| [--format json]` | the report `{file, vocabulary, verdict, diagnostics}` | 0 pass, 1 error, 2 refusal | + +The version can be written `v2` or `2`. + +## The vocabulary shape + +`contracts/rhl-vocabulary-shape-v1.yaml` states the shape. There is one implementation, +the loader in `src/rhl/vocab.rs`, and both `ruchy check` and `ruchy vocab` use it: + +| Rule | Where | Code | +|---|---|---| +| `vocabulary`, `version`, `terms`; each term has `term` and `kind` | serde (`parse_file`) | RHL-V004 | +| `kind` is one of noun, measure, action, unit, entity | serde | RHL-V004 | +| every `takes` parameter and every attribute has a name and a type | serde + `shape_violations` | RHL-V004 | +| an entity has `instances_from` | `shape_violations` | RHL-V004 | +| a unit has `gives` | `shape_violations` | RHL-V004 | +| every term's `contract` exists under the root | `missing_contracts` | RHL-C001 | + +A file that breaks the shape cannot be loaded, so a program that uses it gets RHL-V004 +from `ruchy check`. `vocab validate` reports each violation as its own finding. +No new diagnostic code was added. + +## Ruling on RHL-16's open question + +An unknown attribute that is close to no declared attribute is RHL-V001, and the catalogue +defines V001 as "no candidate". So the diagnostic has no candidates. The declared +attribute names go in `expected` instead: +`{"kind": "attribute", "of": "file ticket", "names": ["title", "label"]}`. An attribute near +miss (V002/V003) keeps its candidates and gets the same `expected`. From b00b331b189ba0ad9a1e6383bbf4f9b6c2428dc3 Mon Sep 17 00:00:00 2001 From: Noah Gift Date: Wed, 23 Sep 2026 14:11:36 +0200 Subject: [PATCH 7/9] RHL-2: manifest covers the vocabulary shape contract; rhl-16-v2.md states the V001 ruling Pmat-Ticket: RHLGA-1 Co-Authored-By: Claude Opus 5.5 (1M context) --- docs/rhl/PREREGISTRATION.sha256 | 3 ++- docs/rhl/rhl-16-v2.md | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/rhl/PREREGISTRATION.sha256 b/docs/rhl/PREREGISTRATION.sha256 index 1618bd117..32100208e 100644 --- a/docs/rhl/PREREGISTRATION.sha256 +++ b/docs/rhl/PREREGISTRATION.sha256 @@ -9,7 +9,7 @@ # Regenerate: RHL0_WRITE_MANIFEST=1 cargo test --lib -p ruchy \ # rhl_manifest_0_regenerate -- --ignored # -# files: 474 +# files: 475 c8d438383d8a35c73f3151200f6487464f43493069d852022accc6d8d8de056b contracts/rhl-fleet-disk-free-of-v1.yaml 1a1190a008e55430fcb72a9bfaa7cf37dd7aae33a59e105b3b21e69b9f2be228 contracts/rhl-fleet-disk-free-of-v2.yaml @@ -41,6 +41,7 @@ eb3bad52cede3575654d65fac0f3a612475b49d2608ff3f5e35b21bb3ba45b6a contracts/rhl- 5f792ba857a23c25328995204e55dba33ca7d4783dfa672f3f57cd867336f2d6 contracts/rhl-tickets-ticket-title-v2.yaml 898b0b1fdde1ef30d54686635ba38dc6463f0490fc6963e3f9c8a5b308db17c7 contracts/rhl-tickets-tickets-filed-in-v1.yaml e385aa114554e616fc98975ba6495163cd098fa3e5741d20fc3f44f3633b8c52 contracts/rhl-tickets-tickets-filed-in-v2.yaml +8eb33b3b176a2fef669eab49bb87755ad7abd2c3fd520e2613c027e4fe96817e contracts/rhl-vocabulary-shape-v1.yaml 223a0be9ceb6c5d5d68151d7b90f7e6c3a11ddb75920c5d131394ff67bf8d8c5 docs/rhl/breaks/planted/missing-end/01-gx10-disk-watch/break.yaml 79f4440eec56565dfc6317c71c4ff5095e6e21be2d1f44fe7f501a33b67ebd8e docs/rhl/breaks/planted/missing-end/01-gx10-disk-watch/broken.rhl 0e85cf55ec425524366ee648da8461d85042c8434cb436067a0418b40aea14f9 docs/rhl/breaks/planted/missing-end/02-gx11-docker-disk-watch/break.yaml diff --git a/docs/rhl/rhl-16-v2.md b/docs/rhl/rhl-16-v2.md index aaacc4b05..932314eb4 100644 --- a/docs/rhl/rhl-16-v2.md +++ b/docs/rhl/rhl-16-v2.md @@ -28,7 +28,7 @@ cites the `test_rhl16_*` tests that check v2. For an action that declares `attributes:`, each action-shaped line of its `with … end` block assigns an attribute. The line is ` `: -- The name must be one of the action's declared attributes. If it is not, the code depends on how close the name is to a declared one, as for any unknown word (plan D4). With one near candidate it is RHL-V002, whose fix is safe (`titl` → `title`). With several it is RHL-V003. With none it is **RHL-V001**. Because the attribute namespace is closed and small, that V001 lists every declared attribute as a candidate, with its distance, and offers no fix. +- The name must be one of the action's declared attributes. If it is not, the code depends on how close the name is to a declared one, as for any unknown word (plan D4). With one near candidate it is RHL-V002, whose fix is safe (`titl` → `title`). With several it is RHL-V003. With none it is **RHL-V001**, which by its catalogue definition carries no candidate; the declared attribute names are given in the diagnostic's `expected` field instead (`{"kind": "attribute", "of": "file ticket", "names": ["title", "label"]}`), and it offers no fix (RHL-2 ruling). - The value must be exactly one literal of the attribute's type. Otherwise the checker reports RHL-T002: `title 5`, `title 5 GB`, `title "a" "b"` or a bare `title`. - An attribute line takes no `in` target and no nested `with` block. Either one gives RHL-T002. - Any other statement in the block (`let`, `when`, …) is checked as an ordinary statement. From cd0745b457eda9df849ac7c4fe189178afe25c22 Mon Sep 17 00:00:00 2001 From: Noah Gift Date: Wed, 23 Sep 2026 14:14:01 +0200 Subject: [PATCH 8/9] RHL-16 + RHL-2: CHANGELOG and roadmap status Pmat-Ticket: RHLGA-1 Co-Authored-By: Claude Opus 5.5 (1M context) --- CHANGELOG.md | 22 ++++++++++++++++++++++ docs/roadmaps/roadmap.yaml | 8 ++++---- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a6661676..9397bb7c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,28 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added — RHL-16 and RHL-2: vocabulary v2, `ruchy fix --safe`, `ruchy vocab` (RHL-001) + +- `vocab/fleet-v2.yaml`, `vocab/tickets-v2.yaml`: every v1 term carried forward, + each with its own `-v2` contract. `file ticket` declares the attributes + `title` and `label`; `ticket count` (the number of tickets a run files) is a + measure with no effect. v1 is unchanged. +- `docs/rhl/breaks/v2/`: the 12 valid programs rewritten so each checks clean + under v2, and the six planted-break classes regenerated from them. Every v2 + break yields its pre-registered code on the mutated line, new relative to its + base; v2 has no known-defect exceptions. `docs/rhl/PREREGISTRATION.sha256` + only adds lines. Details: `docs/rhl/rhl-16-v2.md`. +- The checker reads a `with … end` block under an action that declares + attributes as attribute assignments (name and type checked). An unknown + attribute with no near match is RHL-V001 with the declared names in + `expected`. +- `ruchy fix --safe ` applies every safe fix, re-checks the whole + file, and reverts any fix after which the error count rose. It is idempotent + and never applies a fix with two candidates. +- `ruchy vocab list | show | validate [--format json]`, backed by the checker's + own loader. `contracts/rhl-vocabulary-shape-v1.yaml` states the shape every + vocabulary satisfies. Details: `docs/rhl/rhl-2.md`. + ### Fixed — RHLGA-1: `ruchy doc` tests no longer rewrite tracked files - `tests/cli_contract_doc.rs` ran `ruchy doc` with the repository as the diff --git a/docs/roadmaps/roadmap.yaml b/docs/roadmaps/roadmap.yaml index 3888433a0..2ad4b1a3e 100644 --- a/docs/roadmaps/roadmap.yaml +++ b/docs/roadmaps/roadmap.yaml @@ -2925,7 +2925,7 @@ roadmap: github_issue: null item_type: task title: 'RHL-2: fix --safe, vocab verb, vocabulary shape in pv' - status: planned + status: inprogress priority: medium assigned_to: null created: 2026-09-20 10:27:37+00:00 @@ -2940,7 +2940,7 @@ roadmap: - kind:code - rhl - depends:RHL-1 - notes: 'Carried from RHL-1 (pre-PR review round 2, 1 FAIL / 2 PASS): RHL-1 judges a V002 fix type-preserving at the EDIT SITE — check() applies each safe fix, re-checks once, and demotes it on an RHL-T code on an edited line. A type error the fix exposes on ANOTHER line is left to the re-check that fix --safe performs (§3.5). Measured case: let t be 10 hou, fixed to hour, gives RHL-T001 three lines later and the fix stays safe. The global reading (demote on any new RHL-T) would also demote the pre-registered safe fixes of typo-term 03/04/05/08/09/10, whose base programs are mistyped (RHL-16). RHL-2 must rule local vs global when it builds fix --safe.' + notes: 'In progress under RHLGA-1 (plan P2), branch RHLGA-P1-rhl16. Q5 ruled (plan §5): check keeps the local safe rule; fix --safe reverts a fix after which the error count rose. Earlier: Carried from RHL-1 (pre-PR review round 2, 1 FAIL / 2 PASS): RHL-1 judges a V002 fix type-preserving at the EDIT SITE — check() applies each safe fix, re-checks once, and demotes it on an RHL-T code on an edited line. A type error the fix exposes on ANOTHER line is left to the re-check that fix --safe performs (§3.5). Measured case: let t be 10 hou, fixed to hour, gives RHL-T001 three lines later and the fix stays safe. The global reading (demote on any new RHL-T) would also demote the pre-registered safe fixes of typo-term 03/04/05/08/09/10, whose base programs are mistyped (RHL-16). RHL-2 must rule local vs global when it builds fix --safe.' - id: RHL-3 github_issue: null item_type: task @@ -3479,7 +3479,7 @@ roadmap: github_issue: null item_type: task title: 'RHL-16: repair the pre-registered corpus and vocabulary as v2 — the valid programs are not valid under their vocabulary' - status: planned + status: inprogress priority: medium assigned_to: null created: 2026-09-21T08:52:40Z @@ -3494,7 +3494,7 @@ roadmap: - kind:code - rhl - depends:RHL-1 - notes: null + notes: 'In progress under RHLGA-1 (plan docs/rhl/rhlga-1-plan.md P1), branch RHLGA-P1-rhl16. v2 vocabularies, v2 corpus, attribute check; KNOWN_CORPUS_DEFECTS empty for v2.' - id: CIPOOL-1 github_issue: null item_type: task From a942b01f6131c0ed9e587725aca828269ece205c Mon Sep 17 00:00:00 2001 From: Noah Gift Date: Wed, 23 Sep 2026 14:26:41 +0200 Subject: [PATCH 9/9] RHLGA-1: pre-PR quorum artifact for RHL-16 + RHL-2 (3/3 PASS) Pmat-Ticket: RHLGA-1 Co-Authored-By: Claude Opus 5.5 (1M context) --- docs/audits/quorum-RHLGA-1.json | 1 + 1 file changed, 1 insertion(+) create mode 100644 docs/audits/quorum-RHLGA-1.json diff --git a/docs/audits/quorum-RHLGA-1.json b/docs/audits/quorum-RHLGA-1.json new file mode 100644 index 000000000..1f3b6d750 --- /dev/null +++ b/docs/audits/quorum-RHLGA-1.json @@ -0,0 +1 @@ +{"width":3,"executor":"agy","lanes":[{"lane":1,"status":"SUCCESS","verdict":"PASS","summary":"All criteria have been successfully verified. The PR strictly preserves v1 semantics while correctly implementing v2 attribute parsing, safe-fix logic without unbounded loops or regressions, and strict, non-vacuous tests. The witness token has been extracted.","findings":[{"claim":"Every existing v1 artifact (vocab/*-v1.yaml, contracts/*-v1.yaml, docs/rhl/breaks, docs/rhl/corpus) is byte-identical to main since none appear in the diff, and PREREGISTRATION.sha256 only has added lines.","file":"docs/rhl/PREREGISTRATION.sha256","grounding":"measured"},{"claim":"The 12 v2 valid programs are semantically right. For instance, 'disk free of' gives Size and compares with 'GB', exact effects 'read disk' and 'write tickets' are declared, and the given/then blocks match the condition logic.","file":"docs/rhl/breaks/v2/valid/01-gx10-disk-watch.rhl","grounding":"cited"},{"claim":"'fix --safe' explicitly reverts any applied fix if the error count rises, bounds its loops to the initial number of safe fixes, and skips diagnostics with >1 candidates via a '<= 1' filter.","file":"src/rhl/fix.rs","grounding":"cited","line":127},{"claim":"The attribute check is strictly confined to actions that declare attributes via 'attributed_action' returning None if 't.term.attributes.is_empty()', falling back to v1 behaviour.","file":"src/rhl/check/stmts.rs","grounding":"cited","line":285},{"claim":"Tests do not pass vacuously; directory iterations are paired with strict assertions (e.g., asserting 'breaks.len() == 72' and 'programs.len() == 12').","file":"src/rhl/check_tests.rs","grounding":"cited","line":929},{"claim":"Diagnostic codes and catalogue entries maintain their original meaning. 'RHL-V001' only adds 'of' and 'names' to its JSON output to improve contextual error reporting for attributes, without changing its semantic 'no candidate' meaning.","file":"src/rhl/diag.rs","grounding":"cited","line":71}],"raw_bytes":5266,"err_bytes":715,"envelope_status":"SUCCESS","verdict_source":"structured_output","grounding_check":"parity","model":"gemini-3.1-pro-high","model_measured":"gemini-3.1-pro-high","model_source":"measured","family":"gemini"},{"lane":2,"status":"SUCCESS","verdict":"PASS","summary":"Comprehensive audit of git diff main...RHLGA-P1-rhl16 (head cd0745b4) across rows RHL-16 and RHL-2 confirms full compliance: (1) All v1 artifacts are byte-identical to main and PREREGISTRATION.sha256 only adds lines. (2) All 12 v2 valid programs are semantically sound with matched types, units, declared effects, and given/then examples. (3) fix --safe cannot degrade files, loop unboundedly, or apply two-candidate fixes. (4) Attribute checking is strictly confined to actions declaring attributes. (5) Tests assert strict non-vacuous floors and exact bounds. (6) Diagnostic codes catalogue is unchanged. Verdict: PASS.","findings":[{"claim":"All v1 artifacts (vocab/*-v1.yaml, contracts/*-v1.yaml from main, docs/rhl/breaks/{valid,planted}, docs/rhl/corpus) are byte-identical to main; PREREGISTRATION.sha256 preserved all 301 original hashes verbatim and only added 174 hashes for v2 files plus updated the file count comment from 301 to 475.","file":"docs/rhl/PREREGISTRATION.sha256","grounding":"measured","line":6},{"claim":"All 12 v2 valid programs are semantically right: each measure is compared with a value of its own type/unit, declared effects match used effects exactly, with blocks match declared attributes, and given/then examples are consistent with the bodies.","file":"docs/rhl/breaks/v2/valid/01-gx10-disk-watch.rhl","grounding":"measured","line":1},{"claim":"fix --safe can never make a file worse (reverts any fix where error count rose, line 157), can never loop unboundedly (bounded by initial safe fixes count, line 72), and can never apply a two-candidate fix (candidates.len() <= 1 filter, line 107).","file":"src/rhl/fix.rs","grounding":"cited","line":72},{"claim":"The attribute check is strictly confined to actions declaring attributes via attributed_action; actions without attributes preserve v1 statement body behavior identically.","file":"src/rhl/check/stmts.rs","grounding":"cited","line":153},{"claim":"No tests pass vacuously; all corpus tests assert exact counts (12 valid programs, 72 planted breaks, 4 vocabularies) and verify strict non-empty properties without loose assertions.","file":"src/rhl/check_tests.rs","grounding":"cited","line":830},{"claim":"Diagnostic codes catalogue in src/rhl/codes.rs is untouched; no diagnostic codes or catalogue entries have changed meaning.","file":"src/rhl/codes.rs","grounding":"cited","line":51}],"raw_bytes":6215,"err_bytes":681,"envelope_status":"SUCCESS","verdict_source":"structured_output","grounding_check":"parity","model":"gemini-3.8-flash-high","model_measured":"gemini-3.8-flash-high","model_source":"measured","family":"gemini"},{"lane":3,"status":"SUCCESS","verdict":"PASS","summary":"Review of git diff main...RHLGA-P1-rhl16 (head cd0745b4) for rows RHL-16 and RHL-2 against specification RHL-001 and plan RHLGA-1. All 6 criteria have been verified and confirmed: (1) All v1 artifacts remain byte-identical to main and PREREGISTRATION.sha256 only adds lines; (2) The 12 v2 valid programs are semantically correct with respect to types, units, declared effects, and given/then blocks; (3) ruchy fix --safe is provably monotonic, terminates within bounded rounds, and never applies two-candidate fixes; (4) Attribute checking is properly confined to actions declaring attributes, maintaining v1 behavior unchanged; (5) Tests enforce strict count assertions with zero vacuous passes; (6) Diagnostic codes and catalogue definitions are completely stable.","findings":[{"claim":"All v1 artifacts (vocab/*-v1.yaml, contracts/*-v1.yaml, docs/rhl/breaks/valid, docs/rhl/breaks/planted, docs/rhl/corpus) are byte-identical to main; contracts/rhl-vocabulary-shape-v1.yaml is newly added.","file":"contracts/rhl-vocabulary-shape-v1.yaml","grounding":"measured","line":1},{"claim":"PREREGISTRATION.sha256 diff only updates the header count line (# files: 301 -> 475) and adds sha256 checksums for the 174 new v2 and vocabulary shape contract artifacts; no v1 entry was modified or removed.","file":"docs/rhl/PREREGISTRATION.sha256","grounding":"measured","line":12},{"claim":"All 12 v2 valid programs are semantically valid: each measure is compared against matching types/units (GB for Size, % for Percent, Text literal for Text, Count integer for Count), declared effects strictly match usage, and given/then clauses are consistent with the body.","file":"docs/rhl/breaks/v2/valid/01-gx10-disk-watch.rhl","grounding":"cited","line":1},{"claim":"ruchy fix --safe cannot worsen a file because candidate edits are evaluated against the whole file and reverted if the error count increases; progress is strictly monotonic.","file":"src/rhl/fix.rs","grounding":"cited","line":151},{"claim":"ruchy fix --safe terminates in bounded rounds, capped by the initial number of safe fixes (bound = safe_fixes(&check(source)).len()).","file":"src/rhl/fix.rs","grounding":"cited","line":72},{"claim":"ruchy fix --safe filters diagnostics to d.candidates.len() <= 1, strictly refusing to apply fixes for two-candidate ambiguities (such as RHL-V003).","file":"src/rhl/fix.rs","grounding":"cited","line":107},{"claim":"Attribute checking is strictly confined to actions declaring non-empty attributes: attributed_action returns None for actions without attributes, preserving v1 ordinary statement body checking.","file":"src/rhl/check/stmts.rs","grounding":"cited","line":152},{"claim":"Test suites assert non-empty collections and exact counts (e.g. 72 breaks, 12 valid programs, 4 vocabularies), preventing vacuous pass conditions.","file":"src/rhl/check_tests.rs","grounding":"cited","line":929},{"claim":"All 14 diagnostic codes in CATALOGUE (RHL-P001..X001) remain unmodified and retain their exact semantics; docs/rhl/diagnostic-codes.md matches src/rhl/codes.rs.","file":"src/rhl/codes.rs","grounding":"cited","line":51}],"raw_bytes":7753,"err_bytes":681,"envelope_status":"SUCCESS","verdict_source":"structured_output","grounding_check":"parity","model":"gemini-3.7-flash-high","model_measured":"gemini-3.7-flash-high","model_source":"measured","family":"gemini"}],"agreed":true,"dissent":[],"dedup":[{"file":"contracts/rhl-vocabulary-shape-v1.yaml","line":1,"lanes_agreeing":[3],"claims":["All v1 artifacts (vocab/*-v1.yaml, contracts/*-v1.yaml, docs/rhl/breaks/valid, docs/rhl/breaks/planted, docs/rhl/corpus) are byte-identical to main; contracts/rhl-vocabulary-shape-v1.yaml is newly added."]},{"file":"docs/rhl/PREREGISTRATION.sha256","line":null,"lanes_agreeing":[1],"claims":["Every existing v1 artifact (vocab/*-v1.yaml, contracts/*-v1.yaml, docs/rhl/breaks, docs/rhl/corpus) is byte-identical to main since none appear in the diff, and PREREGISTRATION.sha256 only has added lines."]},{"file":"docs/rhl/PREREGISTRATION.sha256","line":6,"lanes_agreeing":[2],"claims":["All v1 artifacts (vocab/*-v1.yaml, contracts/*-v1.yaml from main, docs/rhl/breaks/{valid,planted}, docs/rhl/corpus) are byte-identical to main; PREREGISTRATION.sha256 preserved all 301 original hashes verbatim and only added 174 hashes for v2 files plus updated the file count comment from 301 to 475."]},{"file":"docs/rhl/PREREGISTRATION.sha256","line":12,"lanes_agreeing":[3],"claims":["PREREGISTRATION.sha256 diff only updates the header count line (# files: 301 -> 475) and adds sha256 checksums for the 174 new v2 and vocabulary shape contract artifacts; no v1 entry was modified or removed."]},{"file":"docs/rhl/breaks/v2/valid/01-gx10-disk-watch.rhl","line":null,"lanes_agreeing":[1],"claims":["The 12 v2 valid programs are semantically right. For instance, 'disk free of' gives Size and compares with 'GB', exact effects 'read disk' and 'write tickets' are declared, and the given/then blocks match the condition logic."]},{"file":"docs/rhl/breaks/v2/valid/01-gx10-disk-watch.rhl","line":1,"lanes_agreeing":[2,3],"claims":["All 12 v2 valid programs are semantically right: each measure is compared with a value of its own type/unit, declared effects match used effects exactly, with blocks match declared attributes, and given/then examples are consistent with the bodies.","All 12 v2 valid programs are semantically valid: each measure is compared against matching types/units (GB for Size, % for Percent, Text literal for Text, Count integer for Count), declared effects strictly match usage, and given/then clauses are consistent with the body."]},{"file":"src/rhl/check/stmts.rs","line":152,"lanes_agreeing":[3],"claims":["Attribute checking is strictly confined to actions declaring non-empty attributes: attributed_action returns None for actions without attributes, preserving v1 ordinary statement body checking."]},{"file":"src/rhl/check/stmts.rs","line":153,"lanes_agreeing":[2],"claims":["The attribute check is strictly confined to actions declaring attributes via attributed_action; actions without attributes preserve v1 statement body behavior identically."]},{"file":"src/rhl/check/stmts.rs","line":285,"lanes_agreeing":[1],"claims":["The attribute check is strictly confined to actions that declare attributes via 'attributed_action' returning None if 't.term.attributes.is_empty()', falling back to v1 behaviour."]},{"file":"src/rhl/check_tests.rs","line":830,"lanes_agreeing":[2],"claims":["No tests pass vacuously; all corpus tests assert exact counts (12 valid programs, 72 planted breaks, 4 vocabularies) and verify strict non-empty properties without loose assertions."]},{"file":"src/rhl/check_tests.rs","line":929,"lanes_agreeing":[1,3],"claims":["Test suites assert non-empty collections and exact counts (e.g. 72 breaks, 12 valid programs, 4 vocabularies), preventing vacuous pass conditions.","Tests do not pass vacuously; directory iterations are paired with strict assertions (e.g., asserting 'breaks.len() == 72' and 'programs.len() == 12')."]},{"file":"src/rhl/codes.rs","line":51,"lanes_agreeing":[2,3],"claims":["All 14 diagnostic codes in CATALOGUE (RHL-P001..X001) remain unmodified and retain their exact semantics; docs/rhl/diagnostic-codes.md matches src/rhl/codes.rs.","Diagnostic codes catalogue in src/rhl/codes.rs is untouched; no diagnostic codes or catalogue entries have changed meaning."]},{"file":"src/rhl/diag.rs","line":71,"lanes_agreeing":[1],"claims":["Diagnostic codes and catalogue entries maintain their original meaning. 'RHL-V001' only adds 'of' and 'names' to its JSON output to improve contextual error reporting for attributes, without changing its semantic 'no candidate' meaning."]},{"file":"src/rhl/fix.rs","line":72,"lanes_agreeing":[2,3],"claims":["fix --safe can never make a file worse (reverts any fix where error count rose, line 157), can never loop unboundedly (bounded by initial safe fixes count, line 72), and can never apply a two-candidate fix (candidates.len() <= 1 filter, line 107).","ruchy fix --safe terminates in bounded rounds, capped by the initial number of safe fixes (bound = safe_fixes(&check(source)).len())."]},{"file":"src/rhl/fix.rs","line":107,"lanes_agreeing":[3],"claims":["ruchy fix --safe filters diagnostics to d.candidates.len() <= 1, strictly refusing to apply fixes for two-candidate ambiguities (such as RHL-V003)."]},{"file":"src/rhl/fix.rs","line":127,"lanes_agreeing":[1],"claims":["'fix --safe' explicitly reverts any applied fix if the error count rises, bounds its loops to the initial number of safe fixes, and skips diagnostics with >1 candidates via a '<= 1' filter."]},{"file":"src/rhl/fix.rs","line":151,"lanes_agreeing":[3],"claims":["ruchy fix --safe cannot worsen a file because candidate edits are evaluated against the whole file and reverted if the error count increases; progress is strictly monotonic."]}],"uncovered":[],"coverage_source":"lanes","partial":true,"partial_reasons":["lane 1: non-empty .err (715 bytes, 1 line(s) beyond agy-lane's workspace narration)","lane 2: non-empty .err (681 bytes, 1 line(s) beyond agy-lane's workspace narration)","lane 3: non-empty .err (681 bytes, 1 line(s) beyond agy-lane's workspace narration)"],"extra":{},"author":{"model":"Opus 5.5 (1M context)","family":"claude","source":"flag"}} \ No newline at end of file