softwareobservatory 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/data/sensors.json CHANGED
@@ -154,7 +154,8 @@
154
154
  "see_also_ids": [
155
155
  "SO-001",
156
156
  "SO-014",
157
- "SO-012b"
157
+ "SO-012b",
158
+ "SO-014d"
158
159
  ],
159
160
  "see_also_pages": [],
160
161
  "slug": "build-provenance-sbom",
@@ -664,8 +665,8 @@
664
665
  "url_path": "sensors/compiler/"
665
666
  },
666
667
  {
667
- "body_html": "<p>Behavioral guarantees checked before the code ever runs. Typestate systems,\nrefinement types, and design-by-contract annotations (Eiffel-style\npre/postconditions, Dafny <code>requires</code>/<code>ensures</code>) ask the compiler to prove\nthat certain behaviors are impossible, not merely unlikely.</p>\n<p>Where a <a class=\"body-link\" href=\"/sensors/type-checker/\">type checker</a> answers &ldquo;do the shapes fit?&rdquo;, a\nrefinement type answers &ldquo;can <code>withdraw(amount)</code> ever be called with\n<code>amount &gt; balance</code>?&rdquo; — a behavioral claim, discharged at build time.</p>\n<h2>In practice</h2>\n<p>A contract check reads like a proof obligation returned unpaid. The\ndiagnostic names the obligation that failed, and usually the related\nlocation where it was stated:</p>\n<pre><code>Program.dfy(14,4): Error: A precondition for this call could not be\nproved on an entry point of this program\nProgram.dfy(14,17): Related location: This is the precondition that\ncould not be proved\nProgram.dfy(9,11): Related location: this is the precondition\n</code></pre>\n<p>Three causes produce the same message, and the fix is different for\neach: the implementation violates its own contract, the contract is\nwrong, or the prover needs an intermediate lemma to connect the two.\nThe message alone does not say which. Unlike a test failure, though,\nthe verdict is deterministic: a contract that fails today fails on\nevery machine, and a proof that discharges never flakes. The counter-\nexamples some checkers print are worth keeping; they are the minimal\ninputs that break the claim.</p>\n<h2>Response playbook</h2>\n<p>When a contract fails to discharge:</p>\n<ol>\n<li><strong>Reproduce with the smallest failing input.</strong> If the checker\n emits a counter-example, run it; if not, derive one from the\n failed obligation.</li>\n<li><strong>Decide whether the implementation or the contract is wrong.</strong>\n A failed proof is a genuine disagreement between two claims the\n author made, and one of them must be retracted.</li>\n<li><strong>If both are right, supply the missing lemma.</strong> Splitting the\n obligation into smaller steps is how proofs go through; deleting\n the contract is how they get abandoned.</li>\n<li><strong>Demote what cannot be proved.</strong> An obligation that resists the\n prover can still ship as a\n <a class=\"body-link\" href=\"/sensors/runtime-invariants/\">runtime invariant</a>, checked on every\n execution instead of all executions.</li>\n</ol>\n<h2>How it gets gamed</h2>\n<p>The checker cannot be gamed, but the contracts are written by the\nsame mind that writes the code, so the specification itself can be\ndegraded:</p>\n<ul>\n<li><strong>Trivial contracts.</strong> <code>ensures true</code>, postconditions that restate\n the type signature. The checker passes and protects nothing.</li>\n<li><strong>Weaken until green.</strong> Each fight loosens the precondition one\n notch until the proof goes through by erosion rather than by\n correctness.</li>\n<li><strong>Verification off the merge path.</strong> A proof job that runs nightly\n or on demand, rather than on every merge, is a proof nobody is\n waiting for.</li>\n</ul>\n<p>The meta-signal is contract strength: sample annotated functions and\ncount how many have postconditions that are trivially true.</p>\n<h2>What it cannot detect</h2>\n<p>Contracts only cover what was specified. The specification itself is\nwritten by the same mind that wrote the code, which is why contract checking\ncomposes with rather than replaces <a class=\"body-link\" href=\"/sensors/example-based-tests/\">example-based tests</a>\nand <a class=\"body-link\" href=\"/sensors/fuzzing/\">property testing</a>.</p>",
668
- "body_text": "Behavioral guarantees checked before the code ever runs. Typestate systems, refinement types, and design-by-contract annotations (Eiffel-style pre/postconditions, Dafny requires / ensures ) ask the compiler to prove that certain behaviors are impossible, not merely unlikely. Where a type checker answers “do the shapes fit?”, a refinement type answers “can withdraw(amount) ever be called with amount > balance ?” — a behavioral claim, discharged at build time. In practice A contract check reads like a proof obligation returned unpaid. The diagnostic names the obligation that failed, and usually the related location where it was stated: Program.dfy(14,4): Error: A precondition for this call could not be proved on an entry point of this program Program.dfy(14,17): Related location: This is the precondition that could not be proved Program.dfy(9,11): Related location: this is the precondition Three causes produce the same message, and the fix is different for each: the implementation violates its own contract, the contract is wrong, or the prover needs an intermediate lemma to connect the two. The message alone does not say which. Unlike a test failure, though, the verdict is deterministic: a contract that fails today fails on every machine, and a proof that discharges never flakes. The counter- examples some checkers print are worth keeping; they are the minimal inputs that break the claim. Response playbook When a contract fails to discharge: Reproduce with the smallest failing input. If the checker emits a counter-example, run it; if not, derive one from the failed obligation. Decide whether the implementation or the contract is wrong. A failed proof is a genuine disagreement between two claims the author made, and one of them must be retracted. If both are right, supply the missing lemma. Splitting the obligation into smaller steps is how proofs go through; deleting the contract is how they get abandoned. Demote what cannot be proved. An obligation that resists the prover can still ship as a runtime invariant , checked on every execution instead of all executions. How it gets gamed The checker cannot be gamed, but the contracts are written by the same mind that writes the code, so the specification itself can be degraded: Trivial contracts. ensures true , postconditions that restate the type signature. The checker passes and protects nothing. Weaken until green. Each fight loosens the precondition one notch until the proof goes through by erosion rather than by correctness. Verification off the merge path. A proof job that runs nightly or on demand, rather than on every merge, is a proof nobody is waiting for. The meta-signal is contract strength: sample annotated functions and count how many have postconditions that are trivially true. What it cannot detect Contracts only cover what was specified. The specification itself is written by the same mind that wrote the code, which is why contract checking composes with rather than replaces example-based tests and property testing .",
668
+ "body_html": "<p>Behavioral guarantees checked before the code ever runs. Typestate systems,\nrefinement types, and design-by-contract annotations (Eiffel-style\npre/postconditions, Dafny <code>requires</code>/<code>ensures</code>) ask the compiler to prove\nthat certain behaviors are impossible, not merely unlikely.</p>\n<p>Where a <a class=\"body-link\" href=\"/sensors/type-checker/\">type checker</a> answers &ldquo;do the shapes fit?&rdquo;, a\nrefinement type answers &ldquo;can <code>withdraw(amount)</code> ever be called with\n<code>amount &gt; balance</code>?&rdquo; — a behavioral claim, discharged at build time.</p>\n<h2>In practice</h2>\n<p>A contract check reads like a proof obligation returned unpaid. The\ndiagnostic names the obligation that failed, and usually the related\nlocation where it was stated:</p>\n<pre><code>Program.dfy(14,4): Error: A precondition for this call could not be\nproved on an entry point of this program\nProgram.dfy(14,17): Related location: This is the precondition that\ncould not be proved\nProgram.dfy(9,11): Related location: this is the precondition\n</code></pre>\n<p>Three causes produce the same message, and the fix is different for\neach: the implementation violates its own contract, the contract is\nwrong, or the prover needs an intermediate lemma to connect the two.\nThe message alone does not say which. Unlike a test failure, though,\nthe verdict is deterministic: a contract that fails today fails on\nevery machine, and a proof that discharges never flakes. What the\nmessage will not hand you is a failing input. Dafny can extract one\nfrom the solver with <code>--extract-counterexample</code>, and says in the same\nbreath that it &ldquo;cannot guarantee that the counterexample it reports\nprovably violates the assertion&rdquo;, and that the output &ldquo;should be\ninspected manually and treated as a hint&rdquo;. A solver model is not a\nminimal input and is not always a reachable one.</p>\n<h2>Response playbook</h2>\n<p>When a contract fails to discharge:</p>\n<ol>\n<li><strong>Try to reproduce with a concrete input.</strong> If the checker will\n extract a counter-example, run it before believing it — the model\n is a hint, not a witness. If it will not, derive a candidate from\n the failed obligation by hand.</li>\n<li><strong>Decide whether the implementation or the contract is wrong.</strong>\n A failed proof is a genuine disagreement between two claims the\n author made, and one of them must be retracted.</li>\n<li><strong>If both are right, supply the missing lemma.</strong> Splitting the\n obligation into smaller steps is how proofs go through; deleting\n the contract is how they get abandoned.</li>\n<li><strong>Demote what cannot be proved.</strong> An obligation that resists the\n prover can still ship as a\n <a class=\"body-link\" href=\"/sensors/runtime-invariants/\">runtime invariant</a>, checked on every\n execution instead of all executions.</li>\n</ol>\n<h2>How it gets gamed</h2>\n<p>The checker cannot be gamed, but the contracts are written by the\nsame mind that writes the code, so the specification itself can be\ndegraded:</p>\n<ul>\n<li><strong>Trivial contracts.</strong> <code>ensures true</code>, postconditions that restate\n the type signature. The checker passes and protects nothing.</li>\n<li><strong>Weaken until green.</strong> Each fight loosens the precondition one\n notch until the proof goes through by erosion rather than by\n correctness.</li>\n<li><strong>Verification off the merge path.</strong> A proof job that runs nightly\n or on demand, rather than on every merge, is a proof nobody is\n waiting for.</li>\n</ul>\n<p>The meta-signal is contract strength: sample annotated functions and\ncount how many have postconditions that are trivially true.</p>\n<h2>What it cannot detect</h2>\n<p>Contracts only cover what was specified. The specification itself is\nwritten by the same mind that wrote the code, which is why contract checking\ncomposes with rather than replaces <a class=\"body-link\" href=\"/sensors/example-based-tests/\">example-based tests</a>\nand <a class=\"body-link\" href=\"/sensors/fuzzing/\">property testing</a>.</p>",
669
+ "body_text": "Behavioral guarantees checked before the code ever runs. Typestate systems, refinement types, and design-by-contract annotations (Eiffel-style pre/postconditions, Dafny requires / ensures ) ask the compiler to prove that certain behaviors are impossible, not merely unlikely. Where a type checker answers “do the shapes fit?”, a refinement type answers “can withdraw(amount) ever be called with amount > balance ?” — a behavioral claim, discharged at build time. In practice A contract check reads like a proof obligation returned unpaid. The diagnostic names the obligation that failed, and usually the related location where it was stated: Program.dfy(14,4): Error: A precondition for this call could not be proved on an entry point of this program Program.dfy(14,17): Related location: This is the precondition that could not be proved Program.dfy(9,11): Related location: this is the precondition Three causes produce the same message, and the fix is different for each: the implementation violates its own contract, the contract is wrong, or the prover needs an intermediate lemma to connect the two. The message alone does not say which. Unlike a test failure, though, the verdict is deterministic: a contract that fails today fails on every machine, and a proof that discharges never flakes. What the message will not hand you is a failing input. Dafny can extract one from the solver with --extract-counterexample , and says in the same breath that it “cannot guarantee that the counterexample it reports provably violates the assertion”, and that the output “should be inspected manually and treated as a hint”. A solver model is not a minimal input and is not always a reachable one. Response playbook When a contract fails to discharge: Try to reproduce with a concrete input. If the checker will extract a counter-example, run it before believing it — the model is a hint, not a witness. If it will not, derive a candidate from the failed obligation by hand. Decide whether the implementation or the contract is wrong. A failed proof is a genuine disagreement between two claims the author made, and one of them must be retracted. If both are right, supply the missing lemma. Splitting the obligation into smaller steps is how proofs go through; deleting the contract is how they get abandoned. Demote what cannot be proved. An obligation that resists the prover can still ship as a runtime invariant , checked on every execution instead of all executions. How it gets gamed The checker cannot be gamed, but the contracts are written by the same mind that writes the code, so the specification itself can be degraded: Trivial contracts. ensures true , postconditions that restate the type signature. The checker passes and protects nothing. Weaken until green. Each fight loosens the precondition one notch until the proof goes through by erosion rather than by correctness. Verification off the merge path. A proof job that runs nightly or on demand, rather than on every merge, is a proof nobody is waiting for. The meta-signal is contract strength: sample annotated functions and count how many have postconditions that are trivially true. What it cannot detect Contracts only cover what was specified. The specification itself is written by the same mind that wrote the code, which is why contract checking composes with rather than replaces example-based tests and property testing .",
669
670
  "family": "structural",
670
671
  "frontmatter": {
671
672
  "actionability": "blocking",
@@ -703,6 +704,11 @@
703
704
  "title": "Dafny",
704
705
  "url": "https://dafny.org"
705
706
  },
707
+ {
708
+ "kind": "other",
709
+ "title": "Dafny user guide, verification debugging",
710
+ "url": "https://dafny.org/latest/DafnyRef/DafnyRef#sec-counterexamples"
711
+ },
706
712
  {
707
713
  "description": "Static analysis and verification for C",
708
714
  "kind": "tool",
@@ -734,7 +740,8 @@
734
740
  "SO-001",
735
741
  "SO-001d",
736
742
  "SO-002",
737
- "SO-005"
743
+ "SO-005",
744
+ "SO-017"
738
745
  ],
739
746
  "see_also_pages": [],
740
747
  "slug": "contract-refinement-types",
@@ -744,6 +751,7 @@
744
751
  "abandoned",
745
752
  "all",
746
753
  "alone",
754
+ "always",
747
755
  "amount",
748
756
  "and",
749
757
  "annotated",
@@ -751,33 +759,36 @@
751
759
  "answers",
752
760
  "are",
753
761
  "ask",
762
+ "assertion",
754
763
  "author",
755
764
  "balance",
756
765
  "based",
757
766
  "before",
758
767
  "behavioral",
759
768
  "behaviors",
769
+ "believing",
760
770
  "between",
761
771
  "both",
762
- "break",
772
+ "breath",
763
773
  "build",
764
774
  "but",
765
775
  "call",
766
776
  "called",
767
777
  "can",
778
+ "candidate",
768
779
  "cannot",
769
780
  "causes",
770
781
  "certain",
771
782
  "check",
772
783
  "checked",
773
784
  "checker",
774
- "checkers",
775
785
  "checking",
776
786
  "claim",
777
787
  "claims",
778
788
  "code",
779
789
  "compiler",
780
790
  "composes",
791
+ "concrete",
781
792
  "connect",
782
793
  "contract",
783
794
  "contracts",
@@ -785,6 +796,7 @@
785
796
  "could",
786
797
  "count",
787
798
  "counter",
799
+ "counterexample",
788
800
  "cover",
789
801
  "dafny",
790
802
  "decide",
@@ -806,7 +818,6 @@
806
818
  "does",
807
819
  "each",
808
820
  "eiffel",
809
- "emits",
810
821
  "ensures",
811
822
  "entry",
812
823
  "erosion",
@@ -814,9 +825,9 @@
814
825
  "ever",
815
826
  "every",
816
827
  "example",
817
- "examples",
818
828
  "execution",
819
829
  "executions",
830
+ "extract",
820
831
  "failed",
821
832
  "failing",
822
833
  "fails",
@@ -834,13 +845,16 @@
834
845
  "gets",
835
846
  "goes",
836
847
  "green",
848
+ "guarantee",
837
849
  "guarantees",
850
+ "hand",
838
851
  "have",
852
+ "hint",
839
853
  "how",
840
854
  "implementation",
841
855
  "impossible",
842
856
  "input",
843
- "inputs",
857
+ "inspected",
844
858
  "instead",
845
859
  "intermediate",
846
860
  "into",
@@ -848,13 +862,13 @@
848
862
  "its",
849
863
  "itself",
850
864
  "job",
851
- "keeping",
852
865
  "lemma",
853
866
  "like",
854
867
  "location",
855
868
  "loosens",
856
869
  "machine",
857
870
  "made",
871
+ "manually",
858
872
  "many",
859
873
  "merely",
860
874
  "merge",
@@ -863,6 +877,7 @@
863
877
  "mind",
864
878
  "minimal",
865
879
  "missing",
880
+ "model",
866
881
  "must",
867
882
  "names",
868
883
  "needs",
@@ -876,6 +891,7 @@
876
891
  "off",
877
892
  "one",
878
893
  "only",
894
+ "output",
879
895
  "own",
880
896
  "passes",
881
897
  "path",
@@ -885,21 +901,23 @@
885
901
  "practice",
886
902
  "pre",
887
903
  "precondition",
888
- "print",
889
904
  "produce",
890
905
  "program",
891
906
  "proof",
892
907
  "proofs",
893
908
  "property",
894
909
  "protects",
910
+ "provably",
895
911
  "prove",
896
912
  "proved",
897
913
  "prover",
898
914
  "rather",
915
+ "reachable",
899
916
  "reads",
900
917
  "refinement",
901
918
  "related",
902
919
  "replaces",
920
+ "reports",
903
921
  "reproduce",
904
922
  "requires",
905
923
  "resists",
@@ -914,13 +932,14 @@
914
932
  "same",
915
933
  "sample",
916
934
  "say",
935
+ "says",
917
936
  "shapes",
918
937
  "ship",
938
+ "should",
919
939
  "signal",
920
940
  "signature",
921
941
  "smaller",
922
- "smallest",
923
- "some",
942
+ "solver",
924
943
  "specification",
925
944
  "specified",
926
945
  "splitting",
@@ -945,9 +964,11 @@
945
964
  "through",
946
965
  "time",
947
966
  "today",
967
+ "treated",
948
968
  "trivial",
949
969
  "trivially",
950
970
  "true",
971
+ "try",
951
972
  "two",
952
973
  "type",
953
974
  "types",
@@ -969,13 +990,15 @@
969
990
  "whether",
970
991
  "which",
971
992
  "why",
993
+ "will",
972
994
  "with",
973
995
  "withdraw",
974
- "worth",
996
+ "witness",
975
997
  "writes",
976
998
  "written",
977
999
  "wrong",
978
- "wrote"
1000
+ "wrote",
1001
+ "you"
979
1002
  ],
980
1003
  "url_path": "sensors/contract-refinement-types/"
981
1004
  },
@@ -1292,7 +1315,8 @@
1292
1315
  "SO-012",
1293
1316
  "SO-013",
1294
1317
  "SO-001",
1295
- "SO-001e"
1318
+ "SO-001e",
1319
+ "SO-017"
1296
1320
  ],
1297
1321
  "see_also_pages": [],
1298
1322
  "slug": "model-checking",
@@ -3199,8 +3223,8 @@
3199
3223
  "url_path": "sensors/contract-tests/"
3200
3224
  },
3201
3225
  {
3202
- "body_html": "<p>Given X, expect Y. The fundamental behavioral sensor.</p>\n<p>Fundamentally different from <a class=\"body-link\" href=\"/sensors/diff-coverage/\">coverage</a>: coverage says\n&ldquo;this code executed.&rdquo; A behavioral assertion says &ldquo;this code produced the\n<em>right result</em>.&rdquo; That is a massive distinction.</p>\n<h2>The weakness</h2>\n<p>Example-based tests are only as good as the examples chosen. They test what\nthe author thought to test. <a class=\"body-link\" href=\"/sensors/mutation-testing/\">Mutation testing</a> and\n<a class=\"body-link\" href=\"/sensors/metamorphic-testing/\">metamorphic testing</a> exist to find the gaps.</p>\n<h2>In practice</h2>\n<p>A reading is an assertion failure with both sides of the comparison\nspelled out:</p>\n<pre><code>FAILED tests/test_pricing.py::test_discount_applies_above_threshold\n\ntests/test_pricing.py:42: in test_discount_applies_above_threshold\n assert price_with_discount(100, tier=&quot;gold&quot;) == 90.0\nE AssertionError: assert 95.0 == 90.0\nE + where 95.0 = price_with_discount(100, tier='gold')\n\n================== 1 failed, 213 passed in 4.21s ==================\n</code></pre>\n<p>Reading it well takes three habits:</p>\n<ol>\n<li><strong>Read the assertion before the code.</strong> The failure shows two\n values; the first question is which one is wrong. The assertion\n encodes the intended behavior, and sometimes it is the test that\n needs fixing, not the code.</li>\n<li><strong>One failure is a signal, not a ratio.</strong> &ldquo;1 failed, 213 passed&rdquo;\n is not 99.5% healthy. The pass count is context; the failure is\n the reading.</li>\n<li><strong>Separate the verdict from the harness.</strong> An assertion error is a\n behavioral reading. A collection error, import error, or fixture\n timeout is the harness breaking, which means the suite produced no\n reading at all.</li>\n</ol>\n<h2>How it gets gamed</h2>\n<p>The suite belongs to the same people as the code, so it can be bent:</p>\n<ul>\n<li><strong>Delete or skip the failure.</strong> <code>skip</code>, <code>xfail</code>, and &ldquo;I will fix\n it tomorrow&rdquo; turn red to green without touching the code. A rising\n skip count is the suite telling you it is being silenced.</li>\n<li><strong>Weaken the assertion.</strong> Replacing <code>== 90.0</code> with <code>is not None</code>\n makes the test pass and the sensor blind. The test still runs, so\n the suite looks healthy while detecting less.</li>\n<li><strong>Pin the symptom.</strong> Hard-coding the current output into the\n expectation turns the test into a tautology that passes for any\n implementation.</li>\n<li><strong>Sample only the happy path.</strong> Choosing examples that avoid the\n buggy branch keeps the suite green and the bug alive. <a class=\"body-link\" href=\"/sensors/mutation-testing/\">Mutation\n testing</a> is the sensor for this gap.</li>\n</ul>\n<p>The meta-signal is the skip count plus the share of test-file diffs\nthat weaken an assertion. Neither shows up in coverage.</p>\n<h2>Response playbook</h2>\n<p>When a test fails:</p>\n<ol>\n<li><strong>Reproduce it locally.</strong> A failure you cannot reproduce is not\n yet a finding; it is a question. Get the exact input and the\n exact assertion before anything else.</li>\n<li><strong>Decide which side is wrong.</strong> The assertion encodes intended\n behavior. If the code violated it, fix the code. If the\n expectation was wrong, fix the test and say why, because a\n silently rewritten expectation is a deleted sensor.</li>\n<li><strong>Fix the cause, then re-run the full suite.</strong> A fix that breaks\n a sibling test has revealed a second assumption you did not know\n you had.</li>\n<li><strong>Add the neighbor cases.</strong> If <code>100</code> failed, the values just\n above and below the boundary are now suspects. The failure is a\n free tour of the edge of the behavior.</li>\n<li><strong>Never delete the test to unblock a merge.</strong> If the test is\n wrong, rewrite it with the reason in the commit. If it is right,\n the merge is not done.</li>\n</ol>\n<h2>What it cannot detect</h2>\n<p>Missing behavior, untested edge cases, and <a class=\"body-link\" href=\"/sensors/contract-tests/\">integration\nfailures</a> that emerge only when components are\nconnected.</p>",
3203
- "body_text": "Given X, expect Y. The fundamental behavioral sensor. Fundamentally different from coverage : coverage says “this code executed.” A behavioral assertion says “this code produced the right result .” That is a massive distinction. The weakness Example-based tests are only as good as the examples chosen. They test what the author thought to test. Mutation testing and metamorphic testing exist to find the gaps. In practice A reading is an assertion failure with both sides of the comparison spelled out: FAILED tests/test_pricing.py::test_discount_applies_above_threshold tests/test_pricing.py:42: in test_discount_applies_above_threshold assert price_with_discount(100, tier=\"gold\") == 90.0 E AssertionError: assert 95.0 == 90.0 E + where 95.0 = price_with_discount(100, tier='gold') ================== 1 failed, 213 passed in 4.21s ================== Reading it well takes three habits: Read the assertion before the code. The failure shows two values; the first question is which one is wrong. The assertion encodes the intended behavior, and sometimes it is the test that needs fixing, not the code. One failure is a signal, not a ratio. “1 failed, 213 passed” is not 99.5% healthy. The pass count is context; the failure is the reading. Separate the verdict from the harness. An assertion error is a behavioral reading. A collection error, import error, or fixture timeout is the harness breaking, which means the suite produced no reading at all. How it gets gamed The suite belongs to the same people as the code, so it can be bent: Delete or skip the failure. skip , xfail , and “I will fix it tomorrow” turn red to green without touching the code. A rising skip count is the suite telling you it is being silenced. Weaken the assertion. Replacing == 90.0 with is not None makes the test pass and the sensor blind. The test still runs, so the suite looks healthy while detecting less. Pin the symptom. Hard-coding the current output into the expectation turns the test into a tautology that passes for any implementation. Sample only the happy path. Choosing examples that avoid the buggy branch keeps the suite green and the bug alive. Mutation testing is the sensor for this gap. The meta-signal is the skip count plus the share of test-file diffs that weaken an assertion. Neither shows up in coverage. Response playbook When a test fails: Reproduce it locally. A failure you cannot reproduce is not yet a finding; it is a question. Get the exact input and the exact assertion before anything else. Decide which side is wrong. The assertion encodes intended behavior. If the code violated it, fix the code. If the expectation was wrong, fix the test and say why, because a silently rewritten expectation is a deleted sensor. Fix the cause, then re-run the full suite. A fix that breaks a sibling test has revealed a second assumption you did not know you had. Add the neighbor cases. If 100 failed, the values just above and below the boundary are now suspects. The failure is a free tour of the edge of the behavior. Never delete the test to unblock a merge. If the test is wrong, rewrite it with the reason in the commit. If it is right, the merge is not done. What it cannot detect Missing behavior, untested edge cases, and integration failures that emerge only when components are connected.",
3226
+ "body_html": "<p>Given X, expect Y. The fundamental behavioral sensor.</p>\n<p>Fundamentally different from <a class=\"body-link\" href=\"/sensors/diff-coverage/\">coverage</a>: coverage says\n&ldquo;this code executed.&rdquo; A behavioral assertion says &ldquo;this code produced the\n<em>right result</em>.&rdquo; That is a massive distinction.</p>\n<h2>The weakness</h2>\n<p>Example-based tests are only as good as the examples chosen. They test what\nthe author thought to test. <a class=\"body-link\" href=\"/sensors/property-based-testing/\">Property-based\ntesting</a>, <a class=\"body-link\" href=\"/sensors/mutation-testing/\">mutation\ntesting</a> and <a class=\"body-link\" href=\"/sensors/metamorphic-testing/\">metamorphic\ntesting</a> exist to find the gaps.</p>\n<h2>In practice</h2>\n<p>A reading is an assertion failure with both sides of the comparison\nspelled out:</p>\n<pre><code>FAILED tests/test_pricing.py::test_discount_applies_above_threshold\n\ntests/test_pricing.py:42: in test_discount_applies_above_threshold\n assert price_with_discount(100, tier=&quot;gold&quot;) == 90.0\nE AssertionError: assert 95.0 == 90.0\nE + where 95.0 = price_with_discount(100, tier='gold')\n\n================== 1 failed, 213 passed in 4.21s ==================\n</code></pre>\n<p>Reading it well takes three habits:</p>\n<ol>\n<li><strong>Read the assertion before the code.</strong> The failure shows two\n values; the first question is which one is wrong. The assertion\n encodes the intended behavior, and sometimes it is the test that\n needs fixing, not the code.</li>\n<li><strong>One failure is a signal, not a ratio.</strong> &ldquo;1 failed, 213 passed&rdquo;\n is not 99.5% healthy. The pass count is context; the failure is\n the reading.</li>\n<li><strong>Separate the verdict from the harness.</strong> An assertion error is a\n behavioral reading. A collection error, import error, or fixture\n timeout is the harness breaking, which means the suite produced no\n reading at all.</li>\n</ol>\n<h2>How it gets gamed</h2>\n<p>The suite belongs to the same people as the code, so it can be bent:</p>\n<ul>\n<li><strong>Delete or skip the failure.</strong> <code>skip</code>, <code>xfail</code>, and &ldquo;I will fix\n it tomorrow&rdquo; turn red to green without touching the code. A rising\n skip count is the suite telling you it is being silenced.</li>\n<li><strong>Weaken the assertion.</strong> Replacing <code>== 90.0</code> with <code>is not None</code>\n makes the test pass and the sensor blind. The test still runs, so\n the suite looks healthy while detecting less.</li>\n<li><strong>Pin the symptom.</strong> Hard-coding the current output into the\n expectation turns the test into a tautology that passes for any\n implementation.</li>\n<li><strong>Sample only the happy path.</strong> Choosing examples that avoid the\n buggy branch keeps the suite green and the bug alive. <a class=\"body-link\" href=\"/sensors/mutation-testing/\">Mutation\n testing</a> is the sensor for this gap.</li>\n</ul>\n<p>The meta-signal is the skip count plus the share of test-file diffs\nthat weaken an assertion. Neither shows up in coverage.</p>\n<h2>Response playbook</h2>\n<p>When a test fails:</p>\n<ol>\n<li><strong>Reproduce it locally.</strong> A failure you cannot reproduce is not\n yet a finding; it is a question. Get the exact input and the\n exact assertion before anything else.</li>\n<li><strong>Decide which side is wrong.</strong> The assertion encodes intended\n behavior. If the code violated it, fix the code. If the\n expectation was wrong, fix the test and say why, because a\n silently rewritten expectation is a deleted sensor.</li>\n<li><strong>Fix the cause, then re-run the full suite.</strong> A fix that breaks\n a sibling test has revealed a second assumption you did not know\n you had.</li>\n<li><strong>Add the neighbor cases.</strong> If <code>100</code> failed, the values just\n above and below the boundary are now suspects. The failure is a\n free tour of the edge of the behavior.</li>\n<li><strong>Never delete the test to unblock a merge.</strong> If the test is\n wrong, rewrite it with the reason in the commit. If it is right,\n the merge is not done.</li>\n</ol>\n<h2>What it cannot detect</h2>\n<p>Missing behavior, untested edge cases, and <a class=\"body-link\" href=\"/sensors/contract-tests/\">integration\nfailures</a> that emerge only when components are\nconnected.</p>",
3227
+ "body_text": "Given X, expect Y. The fundamental behavioral sensor. Fundamentally different from coverage : coverage says “this code executed.” A behavioral assertion says “this code produced the right result .” That is a massive distinction. The weakness Example-based tests are only as good as the examples chosen. They test what the author thought to test. Property-based testing , mutation testing and metamorphic testing exist to find the gaps. In practice A reading is an assertion failure with both sides of the comparison spelled out: FAILED tests/test_pricing.py::test_discount_applies_above_threshold tests/test_pricing.py:42: in test_discount_applies_above_threshold assert price_with_discount(100, tier=\"gold\") == 90.0 E AssertionError: assert 95.0 == 90.0 E + where 95.0 = price_with_discount(100, tier='gold') ================== 1 failed, 213 passed in 4.21s ================== Reading it well takes three habits: Read the assertion before the code. The failure shows two values; the first question is which one is wrong. The assertion encodes the intended behavior, and sometimes it is the test that needs fixing, not the code. One failure is a signal, not a ratio. “1 failed, 213 passed” is not 99.5% healthy. The pass count is context; the failure is the reading. Separate the verdict from the harness. An assertion error is a behavioral reading. A collection error, import error, or fixture timeout is the harness breaking, which means the suite produced no reading at all. How it gets gamed The suite belongs to the same people as the code, so it can be bent: Delete or skip the failure. skip , xfail , and “I will fix it tomorrow” turn red to green without touching the code. A rising skip count is the suite telling you it is being silenced. Weaken the assertion. Replacing == 90.0 with is not None makes the test pass and the sensor blind. The test still runs, so the suite looks healthy while detecting less. Pin the symptom. Hard-coding the current output into the expectation turns the test into a tautology that passes for any implementation. Sample only the happy path. Choosing examples that avoid the buggy branch keeps the suite green and the bug alive. Mutation testing is the sensor for this gap. The meta-signal is the skip count plus the share of test-file diffs that weaken an assertion. Neither shows up in coverage. Response playbook When a test fails: Reproduce it locally. A failure you cannot reproduce is not yet a finding; it is a question. Get the exact input and the exact assertion before anything else. Decide which side is wrong. The assertion encodes intended behavior. If the code violated it, fix the code. If the expectation was wrong, fix the test and say why, because a silently rewritten expectation is a deleted sensor. Fix the cause, then re-run the full suite. A fix that breaks a sibling test has revealed a second assumption you did not know you had. Add the neighbor cases. If 100 failed, the values just above and below the boundary are now suspects. The failure is a free tour of the edge of the behavior. Never delete the test to unblock a merge. If the test is wrong, rewrite it with the reason in the commit. If it is right, the merge is not done. What it cannot detect Missing behavior, untested edge cases, and integration failures that emerge only when components are connected.",
3204
3228
  "family": "behavioral",
3205
3229
  "frontmatter": {
3206
3230
  "actionability": "guiding",
@@ -3275,7 +3299,8 @@
3275
3299
  "see_also_ids": [
3276
3300
  "SO-002c",
3277
3301
  "SO-002d",
3278
- "SO-003"
3302
+ "SO-003",
3303
+ "SO-005e"
3279
3304
  ],
3280
3305
  "see_also_pages": [],
3281
3306
  "slug": "example-based-tests",
@@ -3431,6 +3456,7 @@
3431
3456
  "practice",
3432
3457
  "price_with_discount",
3433
3458
  "produced",
3459
+ "property",
3434
3460
  "question",
3435
3461
  "ratio",
3436
3462
  "read",
@@ -4483,7 +4509,8 @@
4483
4509
  "see_also_ids": [
4484
4510
  "SO-006c",
4485
4511
  "SO-007",
4486
- "SO-012b"
4512
+ "SO-012b",
4513
+ "SO-006e"
4487
4514
  ],
4488
4515
  "see_also_pages": [],
4489
4516
  "slug": "synthetic-monitoring",
@@ -6045,6 +6072,8 @@
6045
6072
  ],
6046
6073
  "see_also_ids": [
6047
6074
  "SO-003b",
6075
+ "SO-005e",
6076
+ "SO-012d",
6048
6077
  "SO-005",
6049
6078
  "SO-005b"
6050
6079
  ],
@@ -7810,8 +7839,8 @@
7810
7839
  "url_path": "sensors/runtime-invariants/"
7811
7840
  },
7812
7841
  {
7813
- "body_html": "<p>Invariants the compiler refuses to let you violate: Dafny <code>invariant</code>\nclauses, Frama-C annotations, JML specs, type-level witnesses like\n<code>NonEmptyList</code>. Where <a class=\"body-link\" href=\"/sensors/database-invariants/\">database invariants</a> are\nchecked by a live system against live data, these are proved once, at build\ntime, for all possible executions.</p>\n<h2>In practice</h2>\n<p>The reading is the same as a <a class=\"body-link\" href=\"/sensors/type-checker/\">type-checker</a> error,\nraised against a property of the program instead of a type:</p>\n<pre><code>src/Queue.hs:23:1: Error: Liquid Type Mismatch\n Invariant `size q &gt;= 0` could not be proved\n Counter-example: `q` after `dequeue (mkQueue [])`\n</code></pre>\n<p>Or, at the type level: a compiler that refuses <code>head []</code> because the\nlist&rsquo;s type carries a proof of non-emptiness. Either way the verdict\narrives at build time, once, for all possible executions, and a\ncounter-example when the checker can find one.</p>\n<p>The counter-example is the part to read first: it is the smallest\nstate that violates the invariant, and it usually says immediately\nwhether the invariant is wrong or the code is. An invariant that is\nright but unprovable without a helper lemma produces the same message\nas one the code genuinely breaks, so the triage question is &ldquo;which of\nmy two claims is false?&rdquo; not &ldquo;how do I make the message stop.&rdquo;</p>\n<h2>Response playbook</h2>\n<p>When an invariant fails to discharge:</p>\n<ol>\n<li><strong>Read the counter-example first.</strong> It is the minimal violating\n state, and it settles most triage in one look.</li>\n<li><strong>If the code is wrong, fix the code; if the invariant is wrong,\n fix the statement.</strong> A proof of the wrong invariant is worse than\n no proof, because it creates false confidence.</li>\n<li><strong>If both are right, supply the lemma.</strong> Proofs that fail for lack\n of an intermediate step go through once the step is written down.</li>\n<li><strong>Demote only what cannot be proved.</strong> An invariant that resists\n the prover can still run as a\n <a class=\"body-link\" href=\"/sensors/runtime-invariants/\">runtime invariant</a> on every execution,\n and it should.</li>\n</ol>\n<h2>What it cannot detect</h2>\n<p>Invariants that were never written down, and invariants whose statement is\nwrong. The gap between &ldquo;the invariant holds&rdquo; and &ldquo;the invariant is the one\nthe business needs&rdquo; is exactly what <a class=\"body-link\" href=\"/sensors/business-invariants/\">business invariants</a>\nmeasure from the other direction.</p>",
7814
- "body_text": "Invariants the compiler refuses to let you violate: Dafny invariant clauses, Frama-C annotations, JML specs, type-level witnesses like NonEmptyList . Where database invariants are checked by a live system against live data, these are proved once, at build time, for all possible executions. In practice The reading is the same as a type-checker error, raised against a property of the program instead of a type: src/Queue.hs:23:1: Error: Liquid Type Mismatch Invariant `size q >= 0` could not be proved Counter-example: `q` after `dequeue (mkQueue [])` Or, at the type level: a compiler that refuses head [] because the list’s type carries a proof of non-emptiness. Either way the verdict arrives at build time, once, for all possible executions, and a counter-example when the checker can find one. The counter-example is the part to read first: it is the smallest state that violates the invariant, and it usually says immediately whether the invariant is wrong or the code is. An invariant that is right but unprovable without a helper lemma produces the same message as one the code genuinely breaks, so the triage question is “which of my two claims is false?” not “how do I make the message stop.” Response playbook When an invariant fails to discharge: Read the counter-example first. It is the minimal violating state, and it settles most triage in one look. If the code is wrong, fix the code; if the invariant is wrong, fix the statement. A proof of the wrong invariant is worse than no proof, because it creates false confidence. If both are right, supply the lemma. Proofs that fail for lack of an intermediate step go through once the step is written down. Demote only what cannot be proved. An invariant that resists the prover can still run as a runtime invariant on every execution, and it should. What it cannot detect Invariants that were never written down, and invariants whose statement is wrong. The gap between “the invariant holds” and “the invariant is the one the business needs” is exactly what business invariants measure from the other direction.",
7842
+ "body_html": "<p>Invariants the compiler refuses to let you violate: Dafny <code>invariant</code>\nclauses, Frama-C annotations, JML specs, type-level witnesses like\n<code>NonEmptyList</code>. Where <a class=\"body-link\" href=\"/sensors/database-invariants/\">database invariants</a> are\nchecked by a live system against live data, these are proved once, at build\ntime, for all possible executions.</p>\n<h2>In practice</h2>\n<p>The reading is the same as a <a class=\"body-link\" href=\"/sensors/type-checker/\">type-checker</a> error,\nraised against a property of the program instead of a type. Liquid\nHaskell prints the refinement it could infer next to the one the\nsignature demanded:</p>\n<pre><code>**** LIQUID: UNSAFE ************************************************************\n\nsrc/Queue.hs:23:34: error:\n Liquid Type Mismatch\n .\n The inferred type\n VV : {v : GHC.Types.Int | v == size q - 1}\n .\n is not a subtype of the required type\n VV : {VV : GHC.Types.Int | VV &gt;= 0}\n .\n in the context\n q : Queue a\n |\n23 | dequeue q = Q (tail (elems q)) (size q - 1)\n | ^^^^^^^^^^\n</code></pre>\n<p>Or, at the type level: a compiler that refuses <code>head []</code> because the\nlist&rsquo;s type carries a proof of non-emptiness. Either way the verdict\narrives at build time, once, for all possible executions.</p>\n<p>The two refinements are the reading, and the gap between them is the\nfinding: <code>size q - 1</code> is what the code establishes, <code>&gt;= 0</code> is what was\nclaimed, and nothing rules out <code>size q == 0</code>. Note what is <em>not</em> in the\nmessage no failing input, no execution. An SMT-backed checker reports\nan unsatisfiable constraint, not a witness. Where a witness can be had at\nall it is a side feature and a weak one: Liquid Haskell&rsquo;s\n<code>--counter-examples</code> is flagged experimental, and Dafny&rsquo;s\n<code>--extract-counterexample</code> ships with the warning that it &ldquo;cannot\nguarantee that the counterexample it reports provably violates the\nassertion&rdquo;, and should be &ldquo;treated as a hint&rdquo;. Solver models are not\nminimal and are often unreachable. An invariant that is right but\nunprovable without a helper lemma produces the same message as one the\ncode genuinely breaks, so the triage question is &ldquo;which of my two claims\nis false?&rdquo; not &ldquo;how do I make the message stop.&rdquo;</p>\n<h2>Response playbook</h2>\n<p>When an invariant fails to discharge:</p>\n<ol>\n<li><strong>Read the inferred and required refinements against each other\n first.</strong> The gap between what the code establishes and what the\n signature demanded settles most triage in one look, and it is there\n in every message — unlike a counter-example, which most of these\n checkers will not give you.</li>\n<li><strong>If the code is wrong, fix the code; if the invariant is wrong,\n fix the statement.</strong> A proof of the wrong invariant is worse than\n no proof, because it creates false confidence.</li>\n<li><strong>If both are right, supply the lemma.</strong> Proofs that fail for lack\n of an intermediate step go through once the step is written down.</li>\n<li><strong>Demote only what cannot be proved.</strong> An invariant that resists\n the prover can still run as a\n <a class=\"body-link\" href=\"/sensors/runtime-invariants/\">runtime invariant</a> on every execution,\n and it should.</li>\n</ol>\n<h2>What it cannot detect</h2>\n<p>Invariants that were never written down, and invariants whose statement is\nwrong. The gap between &ldquo;the invariant holds&rdquo; and &ldquo;the invariant is the one\nthe business needs&rdquo; is exactly what <a class=\"body-link\" href=\"/sensors/business-invariants/\">business invariants</a>\nmeasure from the other direction.</p>",
7843
+ "body_text": "Invariants the compiler refuses to let you violate: Dafny invariant clauses, Frama-C annotations, JML specs, type-level witnesses like NonEmptyList . Where database invariants are checked by a live system against live data, these are proved once, at build time, for all possible executions. In practice The reading is the same as a type-checker error, raised against a property of the program instead of a type. Liquid Haskell prints the refinement it could infer next to the one the signature demanded: **** LIQUID: UNSAFE ************************************************************ src/Queue.hs:23:34: error: Liquid Type Mismatch . The inferred type VV : {v : GHC.Types.Int | v == size q - 1} . is not a subtype of the required type VV : {VV : GHC.Types.Int | VV >= 0} . in the context q : Queue a | 23 | dequeue q = Q (tail (elems q)) (size q - 1) | ^^^^^^^^^^ Or, at the type level: a compiler that refuses head [] because the list’s type carries a proof of non-emptiness. Either way the verdict arrives at build time, once, for all possible executions. The two refinements are the reading, and the gap between them is the finding: size q - 1 is what the code establishes, >= 0 is what was claimed, and nothing rules out size q == 0 . Note what is not in the message — no failing input, no execution. An SMT-backed checker reports an unsatisfiable constraint, not a witness. Where a witness can be had at all it is a side feature and a weak one: Liquid Haskell’s --counter-examples is flagged experimental, and Dafny’s --extract-counterexample ships with the warning that it “cannot guarantee that the counterexample it reports provably violates the assertion”, and should be “treated as a hint”. Solver models are not minimal and are often unreachable. An invariant that is right but unprovable without a helper lemma produces the same message as one the code genuinely breaks, so the triage question is “which of my two claims is false?” not “how do I make the message stop.” Response playbook When an invariant fails to discharge: Read the inferred and required refinements against each other first. The gap between what the code establishes and what the signature demanded settles most triage in one look, and it is there in every message — unlike a counter-example, which most of these checkers will not give you. If the code is wrong, fix the code; if the invariant is wrong, fix the statement. A proof of the wrong invariant is worse than no proof, because it creates false confidence. If both are right, supply the lemma. Proofs that fail for lack of an intermediate step go through once the step is written down. Demote only what cannot be proved. An invariant that resists the prover can still run as a runtime invariant on every execution, and it should. What it cannot detect Invariants that were never written down, and invariants whose statement is wrong. The gap between “the invariant holds” and “the invariant is the one the business needs” is exactly what business invariants measure from the other direction.",
7815
7844
  "family": "invariants",
7816
7845
  "frontmatter": {
7817
7846
  "actionability": "blocking",
@@ -7852,6 +7881,11 @@
7852
7881
  "title": "Liquid Haskell",
7853
7882
  "url": "https://ucsd-progsys.github.io/liquidhaskell/"
7854
7883
  },
7884
+ {
7885
+ "kind": "other",
7886
+ "title": "Dafny user guide, verification debugging",
7887
+ "url": "https://dafny.org/latest/DafnyRef/DafnyRef#sec-counterexamples"
7888
+ },
7855
7889
  {
7856
7890
  "description": "Static analysis and verification for C",
7857
7891
  "kind": "tool",
@@ -7874,20 +7908,22 @@
7874
7908
  "see_also_ids": [
7875
7909
  "SO-004",
7876
7910
  "SO-012",
7877
- "SO-001d"
7911
+ "SO-001d",
7912
+ "SO-017"
7878
7913
  ],
7879
7914
  "see_also_pages": [],
7880
7915
  "slug": "statically-checked-invariants",
7881
7916
  "title": "Statically Checked Invariants",
7882
7917
  "tokens": [
7883
7918
  "013",
7884
- "after",
7885
7919
  "against",
7886
7920
  "all",
7887
7921
  "and",
7888
7922
  "annotations",
7889
7923
  "are",
7890
7924
  "arrives",
7925
+ "assertion",
7926
+ "backed",
7891
7927
  "because",
7892
7928
  "between",
7893
7929
  "both",
@@ -7900,48 +7936,72 @@
7900
7936
  "carries",
7901
7937
  "checked",
7902
7938
  "checker",
7939
+ "checkers",
7940
+ "claimed",
7903
7941
  "claims",
7904
7942
  "clauses",
7905
7943
  "code",
7906
7944
  "compiler",
7907
7945
  "confidence",
7946
+ "constraint",
7947
+ "context",
7908
7948
  "could",
7909
7949
  "counter",
7950
+ "counterexample",
7910
7951
  "creates",
7911
7952
  "dafny",
7912
7953
  "data",
7913
7954
  "database",
7955
+ "demanded",
7914
7956
  "demote",
7915
7957
  "dequeue",
7916
7958
  "detect",
7917
7959
  "direction",
7918
7960
  "discharge",
7919
7961
  "down",
7962
+ "each",
7920
7963
  "either",
7964
+ "elems",
7921
7965
  "emptiness",
7922
7966
  "error",
7967
+ "establishes",
7923
7968
  "every",
7924
7969
  "exactly",
7925
7970
  "example",
7971
+ "examples",
7926
7972
  "execution",
7927
7973
  "executions",
7974
+ "experimental",
7975
+ "extract",
7928
7976
  "fail",
7977
+ "failing",
7929
7978
  "fails",
7930
7979
  "false",
7931
- "find",
7980
+ "feature",
7981
+ "finding",
7932
7982
  "first",
7933
7983
  "fix",
7984
+ "flagged",
7934
7985
  "for",
7935
7986
  "frama",
7936
7987
  "from",
7937
7988
  "gap",
7938
7989
  "genuinely",
7990
+ "ghc",
7991
+ "give",
7992
+ "guarantee",
7993
+ "had",
7994
+ "haskell",
7939
7995
  "head",
7940
7996
  "helper",
7997
+ "hint",
7941
7998
  "holds",
7942
7999
  "how",
7943
- "immediately",
8000
+ "infer",
8001
+ "inferred",
8002
+ "input",
7944
8003
  "instead",
8004
+ "int",
7945
8005
  "intermediate",
7946
8006
  "invariant",
7947
8007
  "invariants",
@@ -7960,26 +8020,32 @@
7960
8020
  "message",
7961
8021
  "minimal",
7962
8022
  "mismatch",
7963
- "mkqueue",
8023
+ "models",
7964
8024
  "most",
7965
8025
  "needs",
7966
8026
  "never",
8027
+ "next",
7967
8028
  "non",
7968
8029
  "nonemptylist",
7969
8030
  "not",
8031
+ "note",
8032
+ "nothing",
8033
+ "often",
7970
8034
  "once",
7971
8035
  "one",
7972
8036
  "only",
7973
8037
  "other",
7974
- "part",
8038
+ "out",
7975
8039
  "playbook",
7976
8040
  "possible",
7977
8041
  "practice",
8042
+ "prints",
7978
8043
  "produces",
7979
8044
  "program",
7980
8045
  "proof",
7981
8046
  "proofs",
7982
8047
  "property",
8048
+ "provably",
7983
8049
  "proved",
7984
8050
  "prover",
7985
8051
  "question",
@@ -7987,52 +8053,72 @@
7987
8053
  "raised",
7988
8054
  "read",
7989
8055
  "reading",
8056
+ "refinement",
8057
+ "refinements",
7990
8058
  "refuses",
8059
+ "reports",
8060
+ "required",
7991
8061
  "resists",
7992
8062
  "response",
7993
8063
  "right",
8064
+ "rules",
7994
8065
  "run",
7995
8066
  "runtime",
7996
8067
  "same",
7997
- "says",
7998
8068
  "settles",
8069
+ "ships",
7999
8070
  "should",
8071
+ "side",
8072
+ "signature",
8000
8073
  "size",
8001
- "smallest",
8074
+ "smt",
8075
+ "solver",
8002
8076
  "specs",
8003
8077
  "src",
8004
- "state",
8005
8078
  "statement",
8006
8079
  "statically",
8007
8080
  "step",
8008
8081
  "still",
8009
8082
  "stop",
8083
+ "subtype",
8010
8084
  "supply",
8011
8085
  "system",
8086
+ "tail",
8012
8087
  "than",
8013
8088
  "that",
8014
8089
  "the",
8090
+ "them",
8091
+ "there",
8015
8092
  "these",
8016
8093
  "through",
8017
8094
  "time",
8095
+ "treated",
8018
8096
  "triage",
8019
8097
  "two",
8020
8098
  "type",
8099
+ "types",
8100
+ "unlike",
8021
8101
  "unprovable",
8022
- "usually",
8102
+ "unreachable",
8103
+ "unsafe",
8104
+ "unsatisfiable",
8023
8105
  "verdict",
8024
8106
  "violate",
8025
8107
  "violates",
8026
- "violating",
8108
+ "warning",
8109
+ "was",
8027
8110
  "way",
8111
+ "weak",
8028
8112
  "were",
8029
8113
  "what",
8030
8114
  "when",
8031
8115
  "where",
8032
- "whether",
8033
8116
  "which",
8034
8117
  "whose",
8118
+ "will",
8119
+ "with",
8035
8120
  "without",
8121
+ "witness",
8036
8122
  "witnesses",
8037
8123
  "worse",
8038
8124
  "written",
@@ -8481,7 +8567,8 @@
8481
8567
  ],
8482
8568
  "see_also_ids": [
8483
8569
  "SO-004",
8484
- "SO-005"
8570
+ "SO-005",
8571
+ "SO-014b"
8485
8572
  ],
8486
8573
  "see_also_pages": [],
8487
8574
  "slug": "fault-injection",
@@ -8748,7 +8835,7 @@
8748
8835
  "url_path": "sensors/fault-injection/"
8749
8836
  },
8750
8837
  {
8751
- "body_html": "<p>What happens on inputs humans didn&rsquo;t think of? Fuzzing is a sensor of\n<em>robustness</em> against the infinite space of inputs the system will actually\nencounter — including inputs no engineer would ever write deliberately.</p>\n<h2>The oracle question</h2>\n<p>Property-based testing asks: &ldquo;does the implementation obey generalized\nproperties across huge input spaces?&rdquo; Fuzzing asks a simpler question: &ldquo;does\nit crash?&rdquo; The oracle is cheap — panics, exceptions, assertions, memory\nviolations — but the coverage of input space is enormous.</p>\n<pre><code># Coverage-guided fuzzing\n1. Generate random or mutated input\n2. Feed it to the system\n3. Did new code paths execute?\n Yes → keep this input, mutate further\n No → discard, try again\n4. Did the system crash, panic, or violate an assertion?\n Yes → save the input as a finding\n No → continue\n</code></pre>\n<blockquote>\n<p>Fuzzing is particularly powerful because it explores the input space that\nhumans systematically under-sample. An engineer writes tests for inputs\nthey can imagine. A fuzzer discovers inputs they can&rsquo;t.</p>\n</blockquote>\n<h2>In practice</h2>\n<p>A reading is a crash report: the sanitizer&rsquo;s verdict, the stack, and\nthe input that triggered it:</p>\n<pre><code>==4821==ERROR: AddressSanitizer: heap-buffer-overflow\n on address 0x60200000f7f1 at pc 0x4b2f1b\nREAD of size 1 at 0x60200000f7f1\n #0 parse_header parser.c:142:9\n #1 parse_message parser.c:87:12\n #2 main harness.c:14:5\n\nartifact_prefix='./crashes/';\ntest unit written to ./crashes/crash-8f3a2c\n</code></pre>\n<p>Reading it well:</p>\n<ol>\n<li><strong>The saved input is the reading.</strong> The stack trace explains where\n the system died; the file in <code>crashes/</code> is the evidence, and the\n only part that reproduces the bug. Commit it before anything else.</li>\n<li><strong>Deduplicate by stack signature.</strong> Ten thousand crash files can\n be one bug reached ten thousand ways. Grouping by the failing\n frames turns a wall of red into a triage list.</li>\n<li><strong>No findings is a real reading, with conditions.</strong> A long\n campaign that finds nothing says the explored input space is\n robust, and it means more when the coverage counter was still\n climbing. An empty report from a five-minute run means almost\n nothing.</li>\n</ol>\n<h2>How it gets gamed</h2>\n<ul>\n<li><strong>Exclude the crashing input.</strong> Adding the crash artifact to an\n ignore list turns the finding into nothing; the bug stays live and\n the report stays clean.</li>\n<li><strong>Run it too short to find.</strong> A five-minute campaign is a\n checkbox. Empty reports from short runs are not evidence of\n robustness, and campaign length is the lever being pulled.</li>\n<li><strong>Disable the oracle.</strong> Turning off the assertion or memory checks\n that would have caught the crash removes the sensor while keeping\n the run.</li>\n<li><strong>Mark crashes as theoretical.</strong> &ldquo;Nobody would send that input&rdquo; is\n how fuzzing findings die. The input space the fuzzer explores is\n exactly the one attackers and accidents sample.</li>\n</ul>\n<p>The meta-signal is the ignored-findings list. Anything on it is a\nlive bug the team chose to keep.</p>\n<h2>Response playbook</h2>\n<p>When the fuzzer finds a crash:</p>\n<ol>\n<li><strong>Commit the crashing input first.</strong> The file in the crashes\n directory is the reading; everything else is commentary. It is\n also the regression test for the fix.</li>\n<li><strong>Deduplicate by stack signature.</strong> Ten thousand crash files are\n often one bug reached ten thousand ways. Group by the failing\n frames before triaging.</li>\n<li><strong>Classify under the sanitizer.</strong> Memory error, assertion, or\n panic decides severity: the first two are often exploitable or\n corrupting, the last is a logic bug with a cheap fix.</li>\n<li><strong>Fix the root cause, not the site.</strong> Bounds-checking one\n location while the parser is still wrong invites the fuzzer to\n find the next location.</li>\n<li><strong>Re-run the campaign against the fix.</strong> The fix is confirmed\n when the original input and its neighborhood stop crashing, not\n when the code review lands.</li>\n</ol>\n<h2>What it cannot detect</h2>\n<p>Fuzzing with a crash oracle cannot detect <em>wrong but non-crashing</em> behavior.\nA function that returns the wrong answer without crashing will pass a fuzzer.\nFor correctness properties, pair fuzzing with <a class=\"body-link\" href=\"/sensors/metamorphic-testing/\">property-based\ntesting</a> or <a class=\"body-link\" href=\"/sensors/mutation-testing/\">mutation testing</a>.</p>",
8838
+ "body_html": "<p>What happens on inputs humans didn&rsquo;t think of? Fuzzing is a sensor of\n<em>robustness</em> against the infinite space of inputs the system will actually\nencounter — including inputs no engineer would ever write deliberately.</p>\n<h2>The oracle question</h2>\n<p>Property-based testing asks: &ldquo;does the implementation obey generalized\nproperties across huge input spaces?&rdquo; Fuzzing asks a simpler question: &ldquo;does\nit crash?&rdquo; The oracle is cheap — panics, exceptions, assertions, memory\nviolations — but the coverage of input space is enormous.</p>\n<pre><code># Coverage-guided fuzzing\n1. Generate random or mutated input\n2. Feed it to the system\n3. Did new code paths execute?\n Yes → keep this input, mutate further\n No → discard, try again\n4. Did the system crash, panic, or violate an assertion?\n Yes → save the input as a finding\n No → continue\n</code></pre>\n<blockquote>\n<p>Fuzzing is particularly powerful because it explores the input space that\nhumans systematically under-sample. An engineer writes tests for inputs\nthey can imagine. A fuzzer discovers inputs they can&rsquo;t.</p>\n</blockquote>\n<h2>In practice</h2>\n<p>A reading is a crash report: the sanitizer&rsquo;s verdict, the stack, and\nthe input that triggered it:</p>\n<pre><code>==4821==ERROR: AddressSanitizer: heap-buffer-overflow\n on address 0x60200000f7f1 at pc 0x4b2f1b\nREAD of size 1 at 0x60200000f7f1\n #0 parse_header parser.c:142:9\n #1 parse_message parser.c:87:12\n #2 main harness.c:14:5\n\nartifact_prefix='./crashes/';\ntest unit written to ./crashes/crash-8f3a2c\n</code></pre>\n<p>Reading it well:</p>\n<ol>\n<li><strong>The saved input is the reading.</strong> The stack trace explains where\n the system died; the file in <code>crashes/</code> is the evidence, and the\n only part that reproduces the bug. Commit it before anything else.</li>\n<li><strong>Deduplicate by stack signature.</strong> Ten thousand crash files can\n be one bug reached ten thousand ways. Grouping by the failing\n frames turns a wall of red into a triage list.</li>\n<li><strong>No findings is a real reading, with conditions.</strong> A long\n campaign that finds nothing says the explored input space is\n robust, and it means more when the coverage counter was still\n climbing. An empty report from a five-minute run means almost\n nothing.</li>\n</ol>\n<h2>How it gets gamed</h2>\n<ul>\n<li><strong>Exclude the crashing input.</strong> Adding the crash artifact to an\n ignore list turns the finding into nothing; the bug stays live and\n the report stays clean.</li>\n<li><strong>Run it too short to find.</strong> A five-minute campaign is a\n checkbox. Empty reports from short runs are not evidence of\n robustness, and campaign length is the lever being pulled.</li>\n<li><strong>Disable the oracle.</strong> Turning off the assertion or memory checks\n that would have caught the crash removes the sensor while keeping\n the run.</li>\n<li><strong>Mark crashes as theoretical.</strong> &ldquo;Nobody would send that input&rdquo; is\n how fuzzing findings die. The input space the fuzzer explores is\n exactly the one attackers and accidents sample.</li>\n</ul>\n<p>The meta-signal is the ignored-findings list. Anything on it is a\nlive bug the team chose to keep.</p>\n<h2>Response playbook</h2>\n<p>When the fuzzer finds a crash:</p>\n<ol>\n<li><strong>Commit the crashing input first.</strong> The file in the crashes\n directory is the reading; everything else is commentary. It is\n also the regression test for the fix.</li>\n<li><strong>Deduplicate by stack signature.</strong> Ten thousand crash files are\n often one bug reached ten thousand ways. Group by the failing\n frames before triaging.</li>\n<li><strong>Classify under the sanitizer.</strong> Memory error, assertion, or\n panic decides severity: the first two are often exploitable or\n corrupting, the last is a logic bug with a cheap fix.</li>\n<li><strong>Fix the root cause, not the site.</strong> Bounds-checking one\n location while the parser is still wrong invites the fuzzer to\n find the next location.</li>\n<li><strong>Re-run the campaign against the fix.</strong> The fix is confirmed\n when the original input and its neighborhood stop crashing, not\n when the code review lands.</li>\n</ol>\n<h2>What it cannot detect</h2>\n<p>Fuzzing with a crash oracle cannot detect <em>wrong but non-crashing</em> behavior.\nA function that returns the wrong answer without crashing will pass a fuzzer.\nFor correctness properties, pair fuzzing with <a class=\"body-link\" href=\"/sensors/property-based-testing/\">property-based\ntesting</a> or <a class=\"body-link\" href=\"/sensors/mutation-testing/\">mutation testing</a>.</p>",
8752
8839
  "body_text": "What happens on inputs humans didn’t think of? Fuzzing is a sensor of robustness against the infinite space of inputs the system will actually encounter — including inputs no engineer would ever write deliberately. The oracle question Property-based testing asks: “does the implementation obey generalized properties across huge input spaces?” Fuzzing asks a simpler question: “does it crash?” The oracle is cheap — panics, exceptions, assertions, memory violations — but the coverage of input space is enormous. # Coverage-guided fuzzing 1. Generate random or mutated input 2. Feed it to the system 3. Did new code paths execute? Yes → keep this input, mutate further No → discard, try again 4. Did the system crash, panic, or violate an assertion? Yes → save the input as a finding No → continue Fuzzing is particularly powerful because it explores the input space that humans systematically under-sample. An engineer writes tests for inputs they can imagine. A fuzzer discovers inputs they can’t. In practice A reading is a crash report: the sanitizer’s verdict, the stack, and the input that triggered it: ==4821==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x60200000f7f1 at pc 0x4b2f1b READ of size 1 at 0x60200000f7f1 #0 parse_header parser.c:142:9 #1 parse_message parser.c:87:12 #2 main harness.c:14:5 artifact_prefix='./crashes/'; test unit written to ./crashes/crash-8f3a2c Reading it well: The saved input is the reading. The stack trace explains where the system died; the file in crashes/ is the evidence, and the only part that reproduces the bug. Commit it before anything else. Deduplicate by stack signature. Ten thousand crash files can be one bug reached ten thousand ways. Grouping by the failing frames turns a wall of red into a triage list. No findings is a real reading, with conditions. A long campaign that finds nothing says the explored input space is robust, and it means more when the coverage counter was still climbing. An empty report from a five-minute run means almost nothing. How it gets gamed Exclude the crashing input. Adding the crash artifact to an ignore list turns the finding into nothing; the bug stays live and the report stays clean. Run it too short to find. A five-minute campaign is a checkbox. Empty reports from short runs are not evidence of robustness, and campaign length is the lever being pulled. Disable the oracle. Turning off the assertion or memory checks that would have caught the crash removes the sensor while keeping the run. Mark crashes as theoretical. “Nobody would send that input” is how fuzzing findings die. The input space the fuzzer explores is exactly the one attackers and accidents sample. The meta-signal is the ignored-findings list. Anything on it is a live bug the team chose to keep. Response playbook When the fuzzer finds a crash: Commit the crashing input first. The file in the crashes directory is the reading; everything else is commentary. It is also the regression test for the fix. Deduplicate by stack signature. Ten thousand crash files are often one bug reached ten thousand ways. Group by the failing frames before triaging. Classify under the sanitizer. Memory error, assertion, or panic decides severity: the first two are often exploitable or corrupting, the last is a logic bug with a cheap fix. Fix the root cause, not the site. Bounds-checking one location while the parser is still wrong invites the fuzzer to find the next location. Re-run the campaign against the fix. The fix is confirmed when the original input and its neighborhood stop crashing, not when the code review lands. What it cannot detect Fuzzing with a crash oracle cannot detect wrong but non-crashing behavior. A function that returns the wrong answer without crashing will pass a fuzzer. For correctness properties, pair fuzzing with property-based testing or mutation testing .",
8753
8840
  "family": "adversarial",
8754
8841
  "frontmatter": {
@@ -8827,7 +8914,8 @@
8827
8914
  ],
8828
8915
  "see_also_ids": [
8829
8916
  "SO-005b",
8830
- "SO-003"
8917
+ "SO-003",
8918
+ "SO-005e"
8831
8919
  ],
8832
8920
  "see_also_pages": [],
8833
8921
  "slug": "fuzzing",
@@ -9169,7 +9257,8 @@
9169
9257
  "see_also_ids": [
9170
9258
  "SO-005c",
9171
9259
  "SO-006c",
9172
- "SO-004b"
9260
+ "SO-004b",
9261
+ "SO-005d"
9173
9262
  ],
9174
9263
  "see_also_pages": [],
9175
9264
  "slug": "live-chaos-experiments",
@@ -9406,8 +9495,8 @@
9406
9495
  "url_path": "sensors/live-chaos-experiments/"
9407
9496
  },
9408
9497
  {
9409
- "body_html": "<p>You don&rsquo;t know the answer, but you know <em>how the answer should change</em>. This\nis a particularly beautiful sensor because you don&rsquo;t need an oracle.</p>\n<h2>Metamorphic relations</h2>\n<p>A metamorphic relation is a statement about how the output of a function\nshould change when the input changes in a specific way:</p>\n<pre><code>sort(sort(x)) == sort(x) # idempotence\ndecrypt(encrypt(x)) == x # round-trip\nsort(x) == sort(shuffle(x)) # order-independence\n\n# Numeric examples:\nabs(x) == abs(-x) # sign-independence\nabs(x + y) &lt;= abs(x) + abs(y) # triangle inequality\nmax(x, y) == max(y, x) # commutativity\n</code></pre>\n<p>You don&rsquo;t need to know what <code>sort(x)</code> returns. You just need to know that\n<code>sort(sort(x))</code> should equal <code>sort(x)</code>. If it doesn&rsquo;t, something is wrong —\nand you&rsquo;ve found a bug without ever needing to compute the correct answer.</p>\n<blockquote>\n<p>Metamorphic testing is the answer to &ldquo;how do you test something when you\ncan&rsquo;t compute the expected output?&rdquo; Fuzzing explores the input space.\nMutation testing perturbs the implementation. Metamorphic testing perturbs\nthe input and checks relations between outputs. All three are adversarial\n— all three try to make the system fail.</p>\n</blockquote>\n<h2>In practice</h2>\n<p>A reading is a relation violation, shrunk to the smallest input that\nbreaks it:</p>\n<pre><code>______________ test_sort_is_idempotent ______________\n\nFalsifying example:\n x = [2, 1, 3]\n\nassert sort(sort(x)) == sort(x)\nAssertionError: [1, 2, 3] != [1, 3, 2]\n\nShrunk to minimal failing input: x = [2, 1]\n</code></pre>\n<p>Reading it well:</p>\n<ol>\n<li><strong>The named relation is the oracle.</strong> The failure says which\n relation broke (idempotence, round-trip, commutativity), which\n tells you what kind of bug to look for before you look at the\n code.</li>\n<li><strong>Work from the shrunk example.</strong> The original random input is\n noise; the minimized case is the one you can verify by hand in\n seconds. If you cannot hand-check it, shrink further.</li>\n<li><strong>A relation that never fails deserves a glance.</strong> It may be a\n strong invariant, or it may be vacuous. Check that it would have\n fired on a known-bad version of the code.</li>\n</ol>\n<h2>How it gets gamed</h2>\n<ul>\n<li><strong>Weaken the relation.</strong> Replacing equality with &ldquo;same length,&rdquo; or\n adding assumption filters, makes violations disappear by narrowing\n what the relation claims. The test still runs and now detects\n less.</li>\n<li><strong>Shrink the campaign.</strong> Cutting the example count until failures\n stop appearing keeps the sensor&rsquo;s name and discards its reach.</li>\n<li><strong>Label violations as flaky.</strong> A relation that fails on one input\n in a thousand is failing; retrying until it passes converts a\n finding into noise.</li>\n</ul>\n<p>The meta-signal is the ratio of discarded (assumed-away) examples to\ngenerated ones. As it climbs, the relation is being strangled.</p>\n<h2>Response playbook</h2>\n<p>When a relation is violated:</p>\n<ol>\n<li><strong>Work from the shrunk example.</strong> The minimized input is the one\n a human can verify by hand in seconds. If you cannot hand-check\n it, shrink it further before debugging.</li>\n<li><strong>Decide which side of the relation is wrong.</strong> Usually the\n implementation. Occasionally the relation overclaims, and then\n the fix is a corrected relation, written with the reason, not a\n deleted test.</li>\n<li><strong>Fix the implementation and re-run the campaign.</strong> A relation\n violation is rarely one input wide; the same bug usually breaks a\n neighborhood.</li>\n<li><strong>Pin the counterexample as a regression test.</strong> The shrunk input\n is a free <a class=\"body-link\" href=\"/sensors/example-based-tests/\">example-based test</a> that\n runs in milliseconds and guards the fix forever.</li>\n</ol>\n<h2>What it cannot detect</h2>\n<p>Metamorphic testing can only check relations you <em>know</em>. If a function has\nno obvious metamorphic relations, this sensor has nothing to test. It also\ncannot detect <a class=\"body-link\" href=\"/sensors/mutation-testing/\">missing behavior</a> — if a feature is\nabsent, there&rsquo;s no function to check relations on.</p>",
9410
- "body_text": "You don’t know the answer, but you know how the answer should change . This is a particularly beautiful sensor because you don’t need an oracle. Metamorphic relations A metamorphic relation is a statement about how the output of a function should change when the input changes in a specific way: sort(sort(x)) == sort(x) # idempotence decrypt(encrypt(x)) == x # round-trip sort(x) == sort(shuffle(x)) # order-independence # Numeric examples: abs(x) == abs(-x) # sign-independence abs(x + y) <= abs(x) + abs(y) # triangle inequality max(x, y) == max(y, x) # commutativity You don’t need to know what sort(x) returns. You just need to know that sort(sort(x)) should equal sort(x) . If it doesn’t, something is wrong — and you’ve found a bug without ever needing to compute the correct answer. Metamorphic testing is the answer to “how do you test something when you can’t compute the expected output?” Fuzzing explores the input space. Mutation testing perturbs the implementation. Metamorphic testing perturbs the input and checks relations between outputs. All three are adversarial — all three try to make the system fail. In practice A reading is a relation violation, shrunk to the smallest input that breaks it: ______________ test_sort_is_idempotent ______________ Falsifying example: x = [2, 1, 3] assert sort(sort(x)) == sort(x) AssertionError: [1, 2, 3] != [1, 3, 2] Shrunk to minimal failing input: x = [2, 1] Reading it well: The named relation is the oracle. The failure says which relation broke (idempotence, round-trip, commutativity), which tells you what kind of bug to look for before you look at the code. Work from the shrunk example. The original random input is noise; the minimized case is the one you can verify by hand in seconds. If you cannot hand-check it, shrink further. A relation that never fails deserves a glance. It may be a strong invariant, or it may be vacuous. Check that it would have fired on a known-bad version of the code. How it gets gamed Weaken the relation. Replacing equality with “same length,” or adding assumption filters, makes violations disappear by narrowing what the relation claims. The test still runs and now detects less. Shrink the campaign. Cutting the example count until failures stop appearing keeps the sensor’s name and discards its reach. Label violations as flaky. A relation that fails on one input in a thousand is failing; retrying until it passes converts a finding into noise. The meta-signal is the ratio of discarded (assumed-away) examples to generated ones. As it climbs, the relation is being strangled. Response playbook When a relation is violated: Work from the shrunk example. The minimized input is the one a human can verify by hand in seconds. If you cannot hand-check it, shrink it further before debugging. Decide which side of the relation is wrong. Usually the implementation. Occasionally the relation overclaims, and then the fix is a corrected relation, written with the reason, not a deleted test. Fix the implementation and re-run the campaign. A relation violation is rarely one input wide; the same bug usually breaks a neighborhood. Pin the counterexample as a regression test. The shrunk input is a free example-based test that runs in milliseconds and guards the fix forever. What it cannot detect Metamorphic testing can only check relations you know . If a function has no obvious metamorphic relations, this sensor has nothing to test. It also cannot detect missing behavior — if a feature is absent, there’s no function to check relations on.",
9498
+ "body_html": "<p>You don&rsquo;t know the answer, but you know <em>how the answer should change</em>. This\nis a particularly beautiful sensor because you don&rsquo;t need an oracle.</p>\n<h2>Metamorphic relations</h2>\n<p>A metamorphic relation is a statement about how the output of a function\nshould change when the input changes in a specific way:</p>\n<pre><code>sort(sort(x)) == sort(x) # idempotence\ndecrypt(encrypt(x)) == x # round-trip\nsort(x) == sort(shuffle(x)) # order-independence\n\n# Numeric examples:\nabs(x) == abs(-x) # sign-independence\nabs(x + y) &lt;= abs(x) + abs(y) # triangle inequality\nmax(x, y) == max(y, x) # commutativity\n</code></pre>\n<p>You don&rsquo;t need to know what <code>sort(x)</code> returns. You just need to know that\n<code>sort(sort(x))</code> should equal <code>sort(x)</code>. If it doesn&rsquo;t, something is wrong —\nand you&rsquo;ve found a bug without ever needing to compute the correct answer.</p>\n<blockquote>\n<p>Metamorphic testing is the answer to &ldquo;how do you test something when you\ncan&rsquo;t compute the expected output?&rdquo; Fuzzing explores the input space.\nMutation testing perturbs the implementation. Metamorphic testing perturbs\nthe input and checks relations between outputs. All three are adversarial\n— all three try to make the system fail.</p>\n</blockquote>\n<h2>In practice</h2>\n<p>A reading is a relation violation, already reduced. The relation below\nis order-independence — sorting a list and sorting its reverse must\nagree, which is checkable without knowing what the sorted answer is —\nrun against a <code>my_sort</code> that makes one bubble pass instead of a full\nsort:</p>\n<pre><code>xs = [0, 0, -1]\n\n @given(st.lists(st.integers()))\n def test_sort_is_order_independent(xs):\n&gt; assert my_sort(xs) == my_sort(list(reversed(xs)))\nE assert [0, -1, 0] == [-1, 0, 0]\nE At index 0 diff: 0 != -1\nE Use -v to get more diff\nE Failing test case: test_sort_is_order_independent(\nE xs=[0, 0, -1],\nE )\n</code></pre>\n<p>Reading it well:</p>\n<ol>\n<li><strong>The named relation is the oracle.</strong> The failure says which\n relation broke (idempotence, round-trip, commutativity), which\n tells you what kind of bug to look for before you look at the\n code.</li>\n<li><strong>The printed case is already the shrunk one.</strong> Hypothesis reports\n the reduced input and nothing else; the larger inputs that also\n failed never reach the console. If the printed case is still too\n big to hand-check, the shrinker was blocked usually by an\n <code>assume</code> filter or a test that is not deterministic.</li>\n<li><strong>A relation that never fails deserves a glance.</strong> It may be a\n strong invariant, or it may be vacuous. Check that it would have\n fired on a known-bad version of the code.</li>\n</ol>\n<h2>How it gets gamed</h2>\n<ul>\n<li><strong>Weaken the relation.</strong> Replacing equality with &ldquo;same length,&rdquo; or\n adding assumption filters, makes violations disappear by narrowing\n what the relation claims. The test still runs and now detects\n less.</li>\n<li><strong>Shrink the campaign.</strong> Cutting the example count until failures\n stop appearing keeps the sensor&rsquo;s name and discards its reach.</li>\n<li><strong>Label violations as flaky.</strong> A relation that fails on one input\n in a thousand is failing; retrying until it passes converts a\n finding into noise.</li>\n</ul>\n<p>The meta-signal is the ratio of discarded (assumed-away) examples to\ngenerated ones. As it climbs, the relation is being strangled.</p>\n<h2>Response playbook</h2>\n<p>When a relation is violated:</p>\n<ol>\n<li><strong>Work from the shrunk example.</strong> The minimized input is the one\n a human can verify by hand in seconds. If you cannot hand-check\n it, shrink it further before debugging.</li>\n<li><strong>Decide which side of the relation is wrong.</strong> Usually the\n implementation. Occasionally the relation overclaims, and then\n the fix is a corrected relation, written with the reason, not a\n deleted test.</li>\n<li><strong>Fix the implementation and re-run the campaign.</strong> A relation\n violation is rarely one input wide; the same bug usually breaks a\n neighborhood.</li>\n<li><strong>Pin the counterexample as a regression test.</strong> The shrunk input\n is a free <a class=\"body-link\" href=\"/sensors/example-based-tests/\">example-based test</a> that\n runs in milliseconds and guards the fix forever.</li>\n</ol>\n<h2>What it cannot detect</h2>\n<p>Metamorphic testing can only check relations you <em>know</em>. If a function has\nno obvious metamorphic relations, this sensor has nothing to test. It also\ncannot detect <a class=\"body-link\" href=\"/sensors/mutation-testing/\">missing behavior</a> — if a feature is\nabsent, there&rsquo;s no function to check relations on.</p>",
9499
+ "body_text": "You don’t know the answer, but you know how the answer should change . This is a particularly beautiful sensor because you don’t need an oracle. Metamorphic relations A metamorphic relation is a statement about how the output of a function should change when the input changes in a specific way: sort(sort(x)) == sort(x) # idempotence decrypt(encrypt(x)) == x # round-trip sort(x) == sort(shuffle(x)) # order-independence # Numeric examples: abs(x) == abs(-x) # sign-independence abs(x + y) <= abs(x) + abs(y) # triangle inequality max(x, y) == max(y, x) # commutativity You don’t need to know what sort(x) returns. You just need to know that sort(sort(x)) should equal sort(x) . If it doesn’t, something is wrong — and you’ve found a bug without ever needing to compute the correct answer. Metamorphic testing is the answer to “how do you test something when you can’t compute the expected output?” Fuzzing explores the input space. Mutation testing perturbs the implementation. Metamorphic testing perturbs the input and checks relations between outputs. All three are adversarial — all three try to make the system fail. In practice A reading is a relation violation, already reduced. The relation below is order-independence — sorting a list and sorting its reverse must agree, which is checkable without knowing what the sorted answer is — run against a my_sort that makes one bubble pass instead of a full sort: xs = [0, 0, -1] @given(st.lists(st.integers())) def test_sort_is_order_independent(xs): > assert my_sort(xs) == my_sort(list(reversed(xs))) E assert [0, -1, 0] == [-1, 0, 0] E At index 0 diff: 0 != -1 E Use -v to get more diff E Failing test case: test_sort_is_order_independent( E xs=[0, 0, -1], E ) Reading it well: The named relation is the oracle. The failure says which relation broke (idempotence, round-trip, commutativity), which tells you what kind of bug to look for before you look at the code. The printed case is already the shrunk one. Hypothesis reports the reduced input and nothing else; the larger inputs that also failed never reach the console. If the printed case is still too big to hand-check, the shrinker was blocked usually by an assume filter or a test that is not deterministic. A relation that never fails deserves a glance. It may be a strong invariant, or it may be vacuous. Check that it would have fired on a known-bad version of the code. How it gets gamed Weaken the relation. Replacing equality with “same length,” or adding assumption filters, makes violations disappear by narrowing what the relation claims. The test still runs and now detects less. Shrink the campaign. Cutting the example count until failures stop appearing keeps the sensor’s name and discards its reach. Label violations as flaky. A relation that fails on one input in a thousand is failing; retrying until it passes converts a finding into noise. The meta-signal is the ratio of discarded (assumed-away) examples to generated ones. As it climbs, the relation is being strangled. Response playbook When a relation is violated: Work from the shrunk example. The minimized input is the one a human can verify by hand in seconds. If you cannot hand-check it, shrink it further before debugging. Decide which side of the relation is wrong. Usually the implementation. Occasionally the relation overclaims, and then the fix is a corrected relation, written with the reason, not a deleted test. Fix the implementation and re-run the campaign. A relation violation is rarely one input wide; the same bug usually breaks a neighborhood. Pin the counterexample as a regression test. The shrunk input is a free example-based test that runs in milliseconds and guards the fix forever. What it cannot detect Metamorphic testing can only check relations you know . If a function has no obvious metamorphic relations, this sensor has nothing to test. It also cannot detect missing behavior — if a feature is absent, there’s no function to check relations on.",
9411
9500
  "family": "adversarial",
9412
9501
  "frontmatter": {
9413
9502
  "actionability": "guiding",
@@ -9477,27 +9566,30 @@
9477
9566
  ],
9478
9567
  "see_also_ids": [
9479
9568
  "SO-005",
9480
- "SO-003"
9569
+ "SO-003",
9570
+ "SO-005e"
9481
9571
  ],
9482
9572
  "see_also_pages": [],
9483
9573
  "slug": "metamorphic-testing",
9484
9574
  "title": "Metamorphic Testing",
9485
9575
  "tokens": [
9486
9576
  "005b",
9487
- "______________",
9488
9577
  "about",
9489
9578
  "abs",
9490
9579
  "absent",
9491
9580
  "adding",
9492
9581
  "adversarial",
9582
+ "against",
9583
+ "agree",
9493
9584
  "all",
9585
+ "already",
9494
9586
  "also",
9495
9587
  "and",
9496
9588
  "answer",
9497
9589
  "appearing",
9498
9590
  "are",
9499
9591
  "assert",
9500
- "assertionerror",
9592
+ "assume",
9501
9593
  "assumed",
9502
9594
  "assumption",
9503
9595
  "away",
@@ -9508,9 +9600,13 @@
9508
9600
  "before",
9509
9601
  "behavior",
9510
9602
  "being",
9603
+ "below",
9511
9604
  "between",
9605
+ "big",
9606
+ "blocked",
9512
9607
  "breaks",
9513
9608
  "broke",
9609
+ "bubble",
9514
9610
  "bug",
9515
9611
  "but",
9516
9612
  "campaign",
@@ -9520,12 +9616,14 @@
9520
9616
  "change",
9521
9617
  "changes",
9522
9618
  "check",
9619
+ "checkable",
9523
9620
  "checks",
9524
9621
  "claims",
9525
9622
  "climbs",
9526
9623
  "code",
9527
9624
  "commutativity",
9528
9625
  "compute",
9626
+ "console",
9529
9627
  "converts",
9530
9628
  "correct",
9531
9629
  "corrected",
@@ -9535,15 +9633,19 @@
9535
9633
  "debugging",
9536
9634
  "decide",
9537
9635
  "decrypt",
9636
+ "def",
9538
9637
  "deleted",
9539
9638
  "deserves",
9540
9639
  "detect",
9541
9640
  "detects",
9641
+ "deterministic",
9642
+ "diff",
9542
9643
  "disappear",
9543
9644
  "discarded",
9544
9645
  "discards",
9545
9646
  "doesn",
9546
9647
  "don",
9648
+ "else",
9547
9649
  "encrypt",
9548
9650
  "equal",
9549
9651
  "equality",
@@ -9553,12 +9655,13 @@
9553
9655
  "expected",
9554
9656
  "explores",
9555
9657
  "fail",
9658
+ "failed",
9556
9659
  "failing",
9557
9660
  "fails",
9558
9661
  "failure",
9559
9662
  "failures",
9560
- "falsifying",
9561
9663
  "feature",
9664
+ "filter",
9562
9665
  "filters",
9563
9666
  "finding",
9564
9667
  "fired",
@@ -9569,12 +9672,15 @@
9569
9672
  "found",
9570
9673
  "free",
9571
9674
  "from",
9675
+ "full",
9572
9676
  "function",
9573
9677
  "further",
9574
9678
  "fuzzing",
9575
9679
  "gamed",
9576
9680
  "generated",
9681
+ "get",
9577
9682
  "gets",
9683
+ "given",
9578
9684
  "glance",
9579
9685
  "guards",
9580
9686
  "hand",
@@ -9582,11 +9688,16 @@
9582
9688
  "have",
9583
9689
  "how",
9584
9690
  "human",
9691
+ "hypothesis",
9585
9692
  "idempotence",
9586
9693
  "implementation",
9587
9694
  "independence",
9695
+ "index",
9588
9696
  "inequality",
9589
9697
  "input",
9698
+ "inputs",
9699
+ "instead",
9700
+ "integers",
9590
9701
  "into",
9591
9702
  "invariant",
9592
9703
  "its",
@@ -9594,10 +9705,14 @@
9594
9705
  "keeps",
9595
9706
  "kind",
9596
9707
  "know",
9708
+ "knowing",
9597
9709
  "known",
9598
9710
  "label",
9711
+ "larger",
9599
9712
  "length",
9600
9713
  "less",
9714
+ "list",
9715
+ "lists",
9601
9716
  "look",
9602
9717
  "make",
9603
9718
  "makes",
@@ -9606,10 +9721,12 @@
9606
9721
  "meta",
9607
9722
  "metamorphic",
9608
9723
  "milliseconds",
9609
- "minimal",
9610
9724
  "minimized",
9611
9725
  "missing",
9726
+ "more",
9727
+ "must",
9612
9728
  "mutation",
9729
+ "my_sort",
9613
9730
  "name",
9614
9731
  "named",
9615
9732
  "narrowing",
@@ -9629,29 +9746,33 @@
9629
9746
  "only",
9630
9747
  "oracle",
9631
9748
  "order",
9632
- "original",
9633
9749
  "output",
9634
9750
  "outputs",
9635
9751
  "overclaims",
9636
9752
  "particularly",
9753
+ "pass",
9637
9754
  "passes",
9638
9755
  "perturbs",
9639
9756
  "pin",
9640
9757
  "playbook",
9641
9758
  "practice",
9642
- "random",
9759
+ "printed",
9643
9760
  "rarely",
9644
9761
  "ratio",
9645
9762
  "reach",
9646
9763
  "reading",
9647
9764
  "reason",
9765
+ "reduced",
9648
9766
  "regression",
9649
9767
  "relation",
9650
9768
  "relations",
9651
9769
  "replacing",
9770
+ "reports",
9652
9771
  "response",
9653
9772
  "retrying",
9654
9773
  "returns",
9774
+ "reverse",
9775
+ "reversed",
9655
9776
  "round",
9656
9777
  "run",
9657
9778
  "runs",
@@ -9661,14 +9782,16 @@
9661
9782
  "sensor",
9662
9783
  "should",
9663
9784
  "shrink",
9785
+ "shrinker",
9664
9786
  "shrunk",
9665
9787
  "shuffle",
9666
9788
  "side",
9667
9789
  "sign",
9668
9790
  "signal",
9669
- "smallest",
9670
9791
  "something",
9671
9792
  "sort",
9793
+ "sorted",
9794
+ "sorting",
9672
9795
  "space",
9673
9796
  "specific",
9674
9797
  "statement",
@@ -9679,7 +9802,7 @@
9679
9802
  "system",
9680
9803
  "tells",
9681
9804
  "test",
9682
- "test_sort_is_idempotent",
9805
+ "test_sort_is_order_independent",
9683
9806
  "testing",
9684
9807
  "that",
9685
9808
  "the",
@@ -9688,10 +9811,12 @@
9688
9811
  "this",
9689
9812
  "thousand",
9690
9813
  "three",
9814
+ "too",
9691
9815
  "triangle",
9692
9816
  "trip",
9693
9817
  "try",
9694
9818
  "until",
9819
+ "use",
9695
9820
  "usually",
9696
9821
  "vacuous",
9697
9822
  "verify",
@@ -9699,6 +9824,7 @@
9699
9824
  "violated",
9700
9825
  "violation",
9701
9826
  "violations",
9827
+ "was",
9702
9828
  "way",
9703
9829
  "weaken",
9704
9830
  "well",
@@ -9717,8 +9843,8 @@
9717
9843
  "url_path": "sensors/metamorphic-testing/"
9718
9844
  },
9719
9845
  {
9720
- "body_html": "<p>You state a property that should hold for <em>every</em> input, and the tool\ngenerates inputs trying to break it. Where <a class=\"body-link\" href=\"/sensors/example-based-tests/\">example-based\ntesting</a> checks one case you thought of,\nproperty-based testing searches the space of cases you didn&rsquo;t.</p>\n<h2>Properties vs examples</h2>\n<p>A property is a universal statement:</p>\n<pre><code class=\"language-python\">@given(st.lists(st.integers()))\ndef test_sort_idempotent(xs):\n assert sort(xs) == sort(sort(xs))\n</code></pre>\n<p>A failing run is a counterexample, shrunk to the smallest input that\nstill breaks it:</p>\n<pre><code>Falsifying example: test_sort_idempotent(xs=[1, 0])\nShrunk to: xs=[1, 0]\nassert sort([1, 0]) == sort(sort([1, 0]))\nAssertionError: [0, 1] != [1, 0]\n</code></pre>\n<p>The shrink is the actionable part it turns a random failure into a\ncase you can verify by hand in seconds.</p>\n<h2>How it differs from metamorphic testing</h2>\n<p>Property-based testing and <a class=\"body-link\" href=\"/sensors/metamorphic-testing/\">metamorphic testing</a>\nare siblings in the adversarial family. Property-based testing states a\nproperty directly (<code>f(x) == f(-x)</code>, <code>sort(sort(xs)) == sort(xs)</code>) and\nrelies on the generator to find an <code>x</code> that breaks it. Metamorphic\ntesting states a <em>relation between outputs</em> — you don&rsquo;t know the\nanswer, only how the answer should change when the input changes. In\npractice most property-based test suites contain metamorphic relations\n(idempotence, commutativity, round-trip) and the distinction is mostly\nabout whether you can name the property outright or only the relation\nbetween two calls.</p>\n<h2>How it gets gamed</h2>\n<ul>\n<li><strong>Weaken the property.</strong> Replacing equality with &ldquo;same length,&rdquo; or\n adding <code>assume</code> filters that discard failing inputs, makes the\n property hold while detecting less. The test still runs.</li>\n<li><strong>Shrink the campaign.</strong> Cutting the example count until failures\n stop appearing keeps the sensor&rsquo;s name and discards its reach.</li>\n<li><strong>Label violations as flaky.</strong> A property that fails on one input in\n a thousand is failing; retrying until it passes converts a finding\n into noise.</li>\n</ul>\n<p>The meta-signal is the ratio of discarded (assumed-away) examples to\ngenerated ones. As it climbs, the property is being strangled.</p>\n<h2>Response playbook</h2>\n<p>When a property fails:</p>\n<ol>\n<li><strong>Work from the shrunk example.</strong> The minimized input is the one a\n human can verify by hand in seconds. If you can&rsquo;t hand-check it,\n shrink further before debugging.</li>\n<li><strong>Decide which side is wrong.</strong> Usually the implementation.\n Occasionally the property overclaims, and the fix is a corrected\n property, written with the reason, not a deleted test.</li>\n<li><strong>Fix the implementation and re-run the campaign.</strong> A property\n violation is rarely one input wide; the same bug usually breaks a\n neighborhood.</li>\n<li><strong>Pin the counterexample as a regression test.</strong> The shrunk input\n is a free <a class=\"body-link\" href=\"/sensors/example-based-tests/\">example-based test</a> that runs\n in milliseconds and guards the fix forever.</li>\n</ol>\n<h2>What it cannot detect</h2>\n<p>Property-based testing can only check properties you <em>state</em>. If a\nfunction has no obvious property, the generator has nothing to falsify.\nIt also cannot detect <a class=\"body-link\" href=\"/sensors/mutation-testing/\">missing behavior</a> — if a\nfeature is absent, there&rsquo;s no property to check against it. And unlike\n<a class=\"body-link\" href=\"/sensors/fuzzing/\">fuzzing</a>, it assumes you can characterize correctness;\nfuzzing finds crashes even when you can&rsquo;t write down a property.</p>",
9721
- "body_text": "You state a property that should hold for every input, and the tool generates inputs trying to break it. Where example-based testing checks one case you thought of, property-based testing searches the space of cases you didn’t. Properties vs examples A property is a universal statement: @given(st.lists(st.integers())) def test_sort_idempotent(xs): assert sort(xs) == sort(sort(xs)) A failing run is a counterexample, shrunk to the smallest input that still breaks it: Falsifying example: test_sort_idempotent(xs=[1, 0]) Shrunk to: xs=[1, 0] assert sort([1, 0]) == sort(sort([1, 0])) AssertionError: [0, 1] != [1, 0] The shrink is the actionable part it turns a random failure into a case you can verify by hand in seconds. How it differs from metamorphic testing Property-based testing and metamorphic testing are siblings in the adversarial family. Property-based testing states a property directly ( f(x) == f(-x) , sort(sort(xs)) == sort(xs) ) and relies on the generator to find an x that breaks it. Metamorphic testing states a relation between outputs — you don’t know the answer, only how the answer should change when the input changes. In practice most property-based test suites contain metamorphic relations (idempotence, commutativity, round-trip) and the distinction is mostly about whether you can name the property outright or only the relation between two calls. How it gets gamed Weaken the property. Replacing equality with “same length,” or adding assume filters that discard failing inputs, makes the property hold while detecting less. The test still runs. Shrink the campaign. Cutting the example count until failures stop appearing keeps the sensor’s name and discards its reach. Label violations as flaky. A property that fails on one input in a thousand is failing; retrying until it passes converts a finding into noise. The meta-signal is the ratio of discarded (assumed-away) examples to generated ones. As it climbs, the property is being strangled. Response playbook When a property fails: Work from the shrunk example. The minimized input is the one a human can verify by hand in seconds. If you can’t hand-check it, shrink further before debugging. Decide which side is wrong. Usually the implementation. Occasionally the property overclaims, and the fix is a corrected property, written with the reason, not a deleted test. Fix the implementation and re-run the campaign. A property violation is rarely one input wide; the same bug usually breaks a neighborhood. Pin the counterexample as a regression test. The shrunk input is a free example-based test that runs in milliseconds and guards the fix forever. What it cannot detect Property-based testing can only check properties you state . If a function has no obvious property, the generator has nothing to falsify. It also cannot detect missing behavior — if a feature is absent, there’s no property to check against it. And unlike fuzzing , it assumes you can characterize correctness; fuzzing finds crashes even when you can’t write down a property.",
9846
+ "body_html": "<p>You state a property that should hold for <em>every</em> input, and the tool\ngenerates inputs trying to break it. Where <a class=\"body-link\" href=\"/sensors/example-based-tests/\">example-based\ntesting</a> checks one case you thought of,\nproperty-based testing searches the space of cases you didn&rsquo;t.</p>\n<h2>Properties vs examples</h2>\n<p>A property is a universal statement. Here it is stated against a\n<code>my_sort</code> that orders numbers lexicographically — <code>sorted(xs, key=str)</code>,\na bug that hides behind any example whose inputs are non-negative single\ndigits:</p>\n<pre><code class=\"language-python\">@given(st.lists(st.integers()))\ndef test_sort_is_ordered(xs):\n ys = my_sort(xs)\n assert ys == sorted(ys)\n</code></pre>\n<p>The reading is one falsifying input, already shrunk. Hypothesis runs the\nproperty until it fails, reduces the failure, and reports only the\nreduced case — never the input that first broke it:</p>\n<pre><code>xs = [-1, -2]\n\n @given(st.lists(st.integers()))\n def test_sort_is_ordered(xs):\n ys = my_sort(xs)\n&gt; assert ys == sorted(ys)\nE assert [-1, -2] == [-2, -1]\nE At index 0 diff: -1 != -2\nE Use -v to get more diff\nE Failing test case: test_sort_is_ordered(\nE xs=[-1, -2],\nE )\n</code></pre>\n<p>Two elements and a minus sign, verifiable by hand in seconds: that\nreduction is the actionable part of the sensor, and it is invisible in\nthe output: the larger inputs that also failed are never printed. (The\nbanner is version-dependent Hypothesis 6 prints\n<code>Failing test case:</code> where older releases printed <code>Falsifying\nexample:</code> — so grep for the parameter line, not the banner.)</p>\n<h2>How it differs from metamorphic testing</h2>\n<p>Property-based testing and <a class=\"body-link\" href=\"/sensors/metamorphic-testing/\">metamorphic testing</a>\nare siblings in the adversarial family. Property-based testing states a\nproperty directly (<code>f(x) == f(-x)</code>, <code>sort(sort(xs)) == sort(xs)</code>) and\nrelies on the generator to find an <code>x</code> that breaks it. Metamorphic\ntesting states a <em>relation between outputs</em> — you don&rsquo;t know the\nanswer, only how the answer should change when the input changes. In\npractice most property-based test suites contain metamorphic relations\n(idempotence, commutativity, round-trip) and the distinction is mostly\nabout whether you can name the property outright or only the relation\nbetween two calls.</p>\n<h2>How it gets gamed</h2>\n<ul>\n<li><strong>Weaken the property.</strong> Replacing equality with &ldquo;same length,&rdquo; or\n adding <code>assume</code> filters that discard failing inputs, makes the\n property hold while detecting less. The test still runs.</li>\n<li><strong>Shrink the campaign.</strong> Cutting the example count until failures\n stop appearing keeps the sensor&rsquo;s name and discards its reach.</li>\n<li><strong>Label violations as flaky.</strong> A property that fails on one input in\n a thousand is failing; retrying until it passes converts a finding\n into noise.</li>\n</ul>\n<p>The meta-signal is the ratio of discarded (assumed-away) examples to\ngenerated ones. As it climbs, the property is being strangled.</p>\n<h2>Response playbook</h2>\n<p>When a property fails:</p>\n<ol>\n<li><strong>Work from the shrunk example.</strong> The minimized input is the one a\n human can verify by hand in seconds. If you can&rsquo;t hand-check it,\n shrink further before debugging.</li>\n<li><strong>Decide which side is wrong.</strong> Usually the implementation.\n Occasionally the property overclaims, and the fix is a corrected\n property, written with the reason, not a deleted test.</li>\n<li><strong>Fix the implementation and re-run the campaign.</strong> A property\n violation is rarely one input wide; the same bug usually breaks a\n neighborhood.</li>\n<li><strong>Pin the counterexample as a regression test.</strong> The shrunk input\n is a free <a class=\"body-link\" href=\"/sensors/example-based-tests/\">example-based test</a> that runs\n in milliseconds and guards the fix forever.</li>\n</ol>\n<h2>What it cannot detect</h2>\n<p>Property-based testing can only check properties you <em>state</em>. If a\nfunction has no obvious property, the generator has nothing to falsify.\nIt also cannot detect <a class=\"body-link\" href=\"/sensors/mutation-testing/\">missing behavior</a> — if a\nfeature is absent, there&rsquo;s no property to check against it. And unlike\n<a class=\"body-link\" href=\"/sensors/fuzzing/\">fuzzing</a>, it assumes you can characterize correctness;\nfuzzing finds crashes even when you can&rsquo;t write down a property.</p>",
9847
+ "body_text": "You state a property that should hold for every input, and the tool generates inputs trying to break it. Where example-based testing checks one case you thought of, property-based testing searches the space of cases you didn’t. Properties vs examples A property is a universal statement. Here it is stated against a my_sort that orders numbers lexicographically — sorted(xs, key=str) , a bug that hides behind any example whose inputs are non-negative single digits: @given(st.lists(st.integers())) def test_sort_is_ordered(xs): ys = my_sort(xs) assert ys == sorted(ys) The reading is one falsifying input, already shrunk. Hypothesis runs the property until it fails, reduces the failure, and reports only the reduced case — never the input that first broke it: xs = [-1, -2] @given(st.lists(st.integers())) def test_sort_is_ordered(xs): ys = my_sort(xs) > assert ys == sorted(ys) E assert [-1, -2] == [-2, -1] E At index 0 diff: -1 != -2 E Use -v to get more diff E Failing test case: test_sort_is_ordered( E xs=[-1, -2], E ) Two elements and a minus sign, verifiable by hand in seconds: that reduction is the actionable part of the sensor, and it is invisible in the output: the larger inputs that also failed are never printed. (The banner is version-dependent — Hypothesis 6 prints Failing test case: where older releases printed Falsifying example: — so grep for the parameter line, not the banner.) How it differs from metamorphic testing Property-based testing and metamorphic testing are siblings in the adversarial family. Property-based testing states a property directly ( f(x) == f(-x) , sort(sort(xs)) == sort(xs) ) and relies on the generator to find an x that breaks it. Metamorphic testing states a relation between outputs — you don’t know the answer, only how the answer should change when the input changes. In practice most property-based test suites contain metamorphic relations (idempotence, commutativity, round-trip) and the distinction is mostly about whether you can name the property outright or only the relation between two calls. How it gets gamed Weaken the property. Replacing equality with “same length,” or adding assume filters that discard failing inputs, makes the property hold while detecting less. The test still runs. Shrink the campaign. Cutting the example count until failures stop appearing keeps the sensor’s name and discards its reach. Label violations as flaky. A property that fails on one input in a thousand is failing; retrying until it passes converts a finding into noise. The meta-signal is the ratio of discarded (assumed-away) examples to generated ones. As it climbs, the property is being strangled. Response playbook When a property fails: Work from the shrunk example. The minimized input is the one a human can verify by hand in seconds. If you can’t hand-check it, shrink further before debugging. Decide which side is wrong. Usually the implementation. Occasionally the property overclaims, and the fix is a corrected property, written with the reason, not a deleted test. Fix the implementation and re-run the campaign. A property violation is rarely one input wide; the same bug usually breaks a neighborhood. Pin the counterexample as a regression test. The shrunk input is a free example-based test that runs in milliseconds and guards the fix forever. What it cannot detect Property-based testing can only check properties you state . If a function has no obvious property, the generator has nothing to falsify. It also cannot detect missing behavior — if a feature is absent, there’s no property to check against it. And unlike fuzzing , it assumes you can characterize correctness; fuzzing finds crashes even when you can’t write down a property.",
9722
9848
  "family": "adversarial",
9723
9849
  "frontmatter": {
9724
9850
  "actionability": "guiding",
@@ -9790,7 +9916,8 @@
9790
9916
  "see_also_ids": [
9791
9917
  "SO-005",
9792
9918
  "SO-005b",
9793
- "SO-003"
9919
+ "SO-003",
9920
+ "SO-002b"
9794
9921
  ],
9795
9922
  "see_also_pages": [],
9796
9923
  "slug": "property-based-testing",
@@ -9803,24 +9930,28 @@
9803
9930
  "adding",
9804
9931
  "adversarial",
9805
9932
  "against",
9933
+ "already",
9806
9934
  "also",
9807
9935
  "and",
9808
9936
  "answer",
9937
+ "any",
9809
9938
  "appearing",
9810
9939
  "are",
9811
9940
  "assert",
9812
- "assertionerror",
9813
9941
  "assume",
9814
9942
  "assumed",
9815
9943
  "assumes",
9816
9944
  "away",
9945
+ "banner",
9817
9946
  "based",
9818
9947
  "before",
9819
9948
  "behavior",
9949
+ "behind",
9820
9950
  "being",
9821
9951
  "between",
9822
9952
  "break",
9823
9953
  "breaks",
9954
+ "broke",
9824
9955
  "bug",
9825
9956
  "calls",
9826
9957
  "campaign",
@@ -9847,10 +9978,13 @@
9847
9978
  "decide",
9848
9979
  "def",
9849
9980
  "deleted",
9981
+ "dependent",
9850
9982
  "detect",
9851
9983
  "detecting",
9852
9984
  "didn",
9985
+ "diff",
9853
9986
  "differs",
9987
+ "digits",
9854
9988
  "directly",
9855
9989
  "discard",
9856
9990
  "discarded",
@@ -9858,11 +9992,13 @@
9858
9992
  "distinction",
9859
9993
  "don",
9860
9994
  "down",
9995
+ "elements",
9861
9996
  "equality",
9862
9997
  "even",
9863
9998
  "every",
9864
9999
  "example",
9865
10000
  "examples",
10001
+ "failed",
9866
10002
  "failing",
9867
10003
  "fails",
9868
10004
  "failure",
@@ -9875,6 +10011,7 @@
9875
10011
  "find",
9876
10012
  "finding",
9877
10013
  "finds",
10014
+ "first",
9878
10015
  "fix",
9879
10016
  "flaky",
9880
10017
  "for",
@@ -9888,65 +10025,94 @@
9888
10025
  "generated",
9889
10026
  "generates",
9890
10027
  "generator",
10028
+ "get",
9891
10029
  "gets",
9892
10030
  "given",
10031
+ "grep",
9893
10032
  "guards",
9894
10033
  "hand",
9895
10034
  "has",
10035
+ "here",
10036
+ "hides",
9896
10037
  "hold",
9897
10038
  "how",
9898
10039
  "human",
10040
+ "hypothesis",
9899
10041
  "idempotence",
9900
10042
  "implementation",
10043
+ "index",
9901
10044
  "input",
9902
10045
  "inputs",
9903
10046
  "integers",
9904
10047
  "into",
10048
+ "invisible",
9905
10049
  "its",
9906
10050
  "keeps",
10051
+ "key",
9907
10052
  "know",
9908
10053
  "label",
10054
+ "larger",
9909
10055
  "length",
9910
10056
  "less",
10057
+ "lexicographically",
10058
+ "line",
9911
10059
  "lists",
9912
10060
  "makes",
9913
10061
  "meta",
9914
10062
  "metamorphic",
9915
10063
  "milliseconds",
9916
10064
  "minimized",
10065
+ "minus",
9917
10066
  "missing",
10067
+ "more",
9918
10068
  "most",
9919
10069
  "mostly",
10070
+ "my_sort",
9920
10071
  "name",
10072
+ "negative",
9921
10073
  "neighborhood",
10074
+ "never",
9922
10075
  "noise",
10076
+ "non",
9923
10077
  "not",
9924
10078
  "nothing",
10079
+ "numbers",
9925
10080
  "obvious",
9926
10081
  "occasionally",
10082
+ "older",
9927
10083
  "one",
9928
10084
  "ones",
9929
10085
  "only",
10086
+ "orders",
10087
+ "output",
9930
10088
  "outputs",
9931
10089
  "outright",
9932
10090
  "overclaims",
10091
+ "parameter",
9933
10092
  "part",
9934
10093
  "passes",
9935
10094
  "pin",
9936
10095
  "playbook",
9937
10096
  "practice",
10097
+ "printed",
10098
+ "prints",
9938
10099
  "properties",
9939
10100
  "property",
9940
- "random",
9941
10101
  "rarely",
9942
10102
  "ratio",
9943
10103
  "reach",
10104
+ "reading",
9944
10105
  "reason",
10106
+ "reduced",
10107
+ "reduces",
10108
+ "reduction",
9945
10109
  "regression",
9946
10110
  "relation",
9947
10111
  "relations",
10112
+ "releases",
9948
10113
  "relies",
9949
10114
  "replacing",
10115
+ "reports",
9950
10116
  "response",
9951
10117
  "retrying",
9952
10118
  "round",
@@ -9961,19 +10127,23 @@
9961
10127
  "shrunk",
9962
10128
  "siblings",
9963
10129
  "side",
10130
+ "sign",
9964
10131
  "signal",
9965
- "smallest",
10132
+ "single",
9966
10133
  "sort",
10134
+ "sorted",
9967
10135
  "space",
9968
10136
  "state",
10137
+ "stated",
9969
10138
  "statement",
9970
10139
  "states",
9971
10140
  "still",
9972
10141
  "stop",
10142
+ "str",
9973
10143
  "strangled",
9974
10144
  "suites",
9975
10145
  "test",
9976
- "test_sort_idempotent",
10146
+ "test_sort_is_ordered",
9977
10147
  "testing",
9978
10148
  "that",
9979
10149
  "the",
@@ -9983,13 +10153,15 @@
9983
10153
  "tool",
9984
10154
  "trip",
9985
10155
  "trying",
9986
- "turns",
9987
10156
  "two",
9988
10157
  "universal",
9989
10158
  "unlike",
9990
10159
  "until",
10160
+ "use",
9991
10161
  "usually",
10162
+ "verifiable",
9992
10163
  "verify",
10164
+ "version",
9993
10165
  "violation",
9994
10166
  "violations",
9995
10167
  "weaken",
@@ -9999,6 +10171,7 @@
9999
10171
  "whether",
10000
10172
  "which",
10001
10173
  "while",
10174
+ "whose",
10002
10175
  "wide",
10003
10176
  "with",
10004
10177
  "work",
@@ -10010,8 +10183,8 @@
10010
10183
  "url_path": "sensors/property-based-testing/"
10011
10184
  },
10012
10185
  {
10013
- "body_html": "<p>Attacking the code before it runs. Taint tracking, dataflow analysis, and\npattern-based scanners (Semgrep, CodeQL) ask: &ldquo;is there any path through\nthis program where an adversary&rsquo;s input reaches a dangerous sink?&rdquo; A sensor\nof exploitable structure, not of known-bad strings.</p>\n<h2>In practice</h2>\n<p>A scanner reading is a source-to-sink path with a severity attached,\nnot a bare pattern match:</p>\n<pre><code>semgrep --config p/owasp-top-ten src/\nsrc/api/search.py:31: Possible SQL injection (high)\n query = &quot;SELECT * FROM items WHERE name = '%s'&quot; % request.args[&quot;q&quot;]\n taint: request.args[&quot;q&quot;] -&gt; cursor.execute(query)\n</code></pre>\n<p>The value is the path. The finding is not &ldquo;this string looks\ndangerous&rdquo; but &ldquo;input from here reaches that sink through these\nlines,&rdquo; and the reading takes minutes because the intermediate steps\nare printed.</p>\n<p>Severity is the scanner&rsquo;s estimate, not the exploitability. A high\nfinding behind an authenticated, sanitized code path may matter less\nthan a medium one on an anonymous endpoint, and the scanner knows\nneither the auth model nor the traffic. Which is why triage reads the\npath first and the badge second, and why the same finding can be a\nblocker in one service and a note in another.</p>\n<h2>Response playbook</h2>\n<p>When the scanner fires:</p>\n<ol>\n<li><strong>Trace the full path before judging severity.</strong> The scanner shows\n the chain; confirm whether anything in between already\n sanitizes, authenticates, or bounds the input.</li>\n<li><strong>If reachable, fix at the sink.</strong> Parameterized queries,\n escaping APIs, allowlists. Point fixes outlive pattern fixes.</li>\n<li><strong>If unreachable, record why.</strong> A one-line justification at the\n finding site keeps the next reviewer from redoing the triage.</li>\n<li><strong>Fix the class, then add the rule.</strong> If the sink exists in ten\n other places, the scanner&rsquo;s job is to find all ten; wire that\n rule into CI as blocking before the next one ships.</li>\n<li><strong>Treat untriaged findings as broken builds.</strong> A backlog of\n undated security findings is a queue that never drains.</li>\n</ol>\n<h2>How it gets gamed</h2>\n<ul>\n<li><strong>Severity downgrade by triage.</strong> Marking a finding &ldquo;won&rsquo;t fix&rdquo;\n with a one-word justification is the cheapest override available\n to a reviewer. The meta-signal is the ratio of suppressed to\n confirmed findings, and the age of the suppressed ones.</li>\n<li><strong>Baseline erosion.</strong> Freezing the finding count in a baseline\n file and only failing on new ones, while the baseline quietly\n grows forever because &ldquo;old findings are someone else&rsquo;s problem.&rdquo;</li>\n<li><strong>Rule-set narrowing.</strong> Disabling taint rules because they are\n noisy and keeping only the pattern matches, which detect the\n least of the three.</li>\n<li><strong>Scanner shopping.</strong> Running the tool that finds the fewest\n issues in review and calling it &ldquo;our SAST.&rdquo;</li>\n</ul>\n<h2>What it cannot detect</h2>\n<p>Vulnerabilities in the composition of services, in configuration, or in the\ndependencies&rsquo; runtime behavior — those belong to\n<a class=\"body-link\" href=\"/sensors/fault-injection/\">fault injection</a> and live chaos. And a clean scan\nsays nothing about attacks that arrive through valid inputs:\n<a class=\"body-link\" href=\"/sensors/fuzzing/\">fuzzing</a> covers that side.</p>\n<p>Security is a deeper topic than this catalog covers. SAST is one sensor in\nthe adversarial family — it asks whether an adversary&rsquo;s input can reach a\ndangerous sink — but the broader practice of application security\n(threat modeling, penetration testing, dependency vulnerabilities, runtime\nexploit detection) deserves its own resources. See\n<a class=\"body-link\" href=\"https://owasp.org\">OWASP</a> and the\n<a class=\"body-link\" href=\"https://cwe.mitre.org\">CWE</a> for dedicated treatment.</p>",
10014
- "body_text": "Attacking the code before it runs. Taint tracking, dataflow analysis, and pattern-based scanners (Semgrep, CodeQL) ask: “is there any path through this program where an adversary’s input reaches a dangerous sink?” A sensor of exploitable structure, not of known-bad strings. In practice A scanner reading is a source-to-sink path with a severity attached, not a bare pattern match: semgrep --config p/owasp-top-ten src/ src/api/search.py:31: Possible SQL injection (high) query = \"SELECT * FROM items WHERE name = '%s'\" % request.args[\"q\"] taint: request.args[\"q\"] -> cursor.execute(query) The value is the path. The finding is not “this string looks dangerous” but “input from here reaches that sink through these lines,” and the reading takes minutes because the intermediate steps are printed. Severity is the scanner’s estimate, not the exploitability. A high finding behind an authenticated, sanitized code path may matter less than a medium one on an anonymous endpoint, and the scanner knows neither the auth model nor the traffic. Which is why triage reads the path first and the badge second, and why the same finding can be a blocker in one service and a note in another. Response playbook When the scanner fires: Trace the full path before judging severity. The scanner shows the chain; confirm whether anything in between already sanitizes, authenticates, or bounds the input. If reachable, fix at the sink. Parameterized queries, escaping APIs, allowlists. Point fixes outlive pattern fixes. If unreachable, record why. A one-line justification at the finding site keeps the next reviewer from redoing the triage. Fix the class, then add the rule. If the sink exists in ten other places, the scanner’s job is to find all ten; wire that rule into CI as blocking before the next one ships. Treat untriaged findings as broken builds. A backlog of undated security findings is a queue that never drains. How it gets gamed Severity downgrade by triage. Marking a finding “won’t fix” with a one-word justification is the cheapest override available to a reviewer. The meta-signal is the ratio of suppressed to confirmed findings, and the age of the suppressed ones. Baseline erosion. Freezing the finding count in a baseline file and only failing on new ones, while the baseline quietly grows forever because “old findings are someone else’s problem.” Rule-set narrowing. Disabling taint rules because they are noisy and keeping only the pattern matches, which detect the least of the three. Scanner shopping. Running the tool that finds the fewest issues in review and calling it “our SAST.” What it cannot detect Vulnerabilities in the composition of services, in configuration, or in the dependencies’ runtime behavior — those belong to fault injection and live chaos. And a clean scan says nothing about attacks that arrive through valid inputs: fuzzing covers that side. Security is a deeper topic than this catalog covers. SAST is one sensor in the adversarial family — it asks whether an adversary’s input can reach a dangerous sink — but the broader practice of application security (threat modeling, penetration testing, dependency vulnerabilities, runtime exploit detection) deserves its own resources. See OWASP and the CWE for dedicated treatment.",
10186
+ "body_html": "<p>Attacking the code before it runs. Taint tracking, dataflow analysis, and\npattern-based scanners (Semgrep, CodeQL) ask: &ldquo;is there any path through\nthis program where an adversary&rsquo;s input reaches a dangerous sink?&rdquo; A sensor\nof exploitable structure, not of known-bad strings.</p>\n<h2>In practice</h2>\n<p>A scanner reading is a source-to-sink path with a severity attached,\nnot a bare pattern match:</p>\n<pre><code>semgrep --config p/owasp-top-ten src/\nsrc/api/search.py:31: Possible SQL injection (high)\n query = &quot;SELECT * FROM items WHERE name = '%s'&quot; % request.args[&quot;q&quot;]\n taint: request.args[&quot;q&quot;] -&gt; cursor.execute(query)\n</code></pre>\n<p>The value is the path. The finding is not &ldquo;this string looks\ndangerous&rdquo; but &ldquo;input from here reaches that sink through these\nlines,&rdquo; and the reading takes minutes because the intermediate steps\nare printed.</p>\n<p>Severity is the scanner&rsquo;s estimate, not the exploitability. A high\nfinding behind an authenticated, sanitized code path may matter less\nthan a medium one on an anonymous endpoint, and the scanner knows\nneither the auth model nor the traffic. Which is why triage reads the\npath first and the badge second, and why the same finding can be a\nblocker in one service and a note in another.</p>\n<h2>Response playbook</h2>\n<p>When the scanner fires:</p>\n<ol>\n<li><strong>Trace the full path before judging severity.</strong> The scanner shows\n the chain; confirm whether anything in between already\n sanitizes, authenticates, or bounds the input.</li>\n<li><strong>If reachable, fix at the sink.</strong> Parameterized queries,\n escaping APIs, allowlists. Point fixes outlive pattern fixes.</li>\n<li><strong>If unreachable, record why.</strong> A one-line justification at the\n finding site keeps the next reviewer from redoing the triage.</li>\n<li><strong>Fix the class, then add the rule.</strong> If the sink exists in ten\n other places, the scanner&rsquo;s job is to find all ten; wire that\n rule into CI as blocking before the next one ships.</li>\n<li><strong>Treat untriaged findings as broken builds.</strong> A backlog of\n undated security findings is a queue that never drains.</li>\n</ol>\n<h2>How it gets gamed</h2>\n<ul>\n<li><strong>Severity downgrade by triage.</strong> Marking a finding &ldquo;won&rsquo;t fix&rdquo;\n with a one-word justification is the cheapest override available\n to a reviewer. The meta-signal is the ratio of suppressed to\n confirmed findings, and the age of the suppressed ones.</li>\n<li><strong>Baseline erosion.</strong> Freezing the finding count in a baseline\n file and only failing on new ones, while the baseline quietly\n grows forever because &ldquo;old findings are someone else&rsquo;s problem.&rdquo;</li>\n<li><strong>Rule-set narrowing.</strong> Disabling taint rules because they are\n noisy and keeping only the pattern matches, which detect the\n least of the three.</li>\n<li><strong>Scanner shopping.</strong> Running the tool that finds the fewest\n issues in review and calling it &ldquo;our SAST.&rdquo;</li>\n</ul>\n<h2>What it cannot detect</h2>\n<p>Vulnerabilities in the composition of services, in configuration, or in the\ndependencies&rsquo; runtime behavior — those belong to\n<a class=\"body-link\" href=\"/sensors/fault-injection/\">fault injection</a> and <a class=\"body-link\" href=\"/sensors/live-chaos-experiments/\">live\nchaos</a>. And a clean scan says nothing about\nattacks that arrive through valid inputs: <a class=\"body-link\" href=\"/sensors/fuzzing/\">fuzzing</a> covers\nthat side.</p>\n<p>Security is a deeper topic than this catalog covers. SAST is one sensor in\nthe adversarial family — it asks whether an adversary&rsquo;s input can reach a\ndangerous sink — but the broader practice of application security\n(threat modeling, penetration testing, dependency vulnerabilities, runtime\nexploit detection) deserves its own resources. See\n<a class=\"body-link\" href=\"https://owasp.org\">OWASP</a> and the\n<a class=\"body-link\" href=\"https://cwe.mitre.org\">CWE</a> for dedicated treatment.</p>",
10187
+ "body_text": "Attacking the code before it runs. Taint tracking, dataflow analysis, and pattern-based scanners (Semgrep, CodeQL) ask: “is there any path through this program where an adversary’s input reaches a dangerous sink?” A sensor of exploitable structure, not of known-bad strings. In practice A scanner reading is a source-to-sink path with a severity attached, not a bare pattern match: semgrep --config p/owasp-top-ten src/ src/api/search.py:31: Possible SQL injection (high) query = \"SELECT * FROM items WHERE name = '%s'\" % request.args[\"q\"] taint: request.args[\"q\"] -> cursor.execute(query) The value is the path. The finding is not “this string looks dangerous” but “input from here reaches that sink through these lines,” and the reading takes minutes because the intermediate steps are printed. Severity is the scanner’s estimate, not the exploitability. A high finding behind an authenticated, sanitized code path may matter less than a medium one on an anonymous endpoint, and the scanner knows neither the auth model nor the traffic. Which is why triage reads the path first and the badge second, and why the same finding can be a blocker in one service and a note in another. Response playbook When the scanner fires: Trace the full path before judging severity. The scanner shows the chain; confirm whether anything in between already sanitizes, authenticates, or bounds the input. If reachable, fix at the sink. Parameterized queries, escaping APIs, allowlists. Point fixes outlive pattern fixes. If unreachable, record why. A one-line justification at the finding site keeps the next reviewer from redoing the triage. Fix the class, then add the rule. If the sink exists in ten other places, the scanner’s job is to find all ten; wire that rule into CI as blocking before the next one ships. Treat untriaged findings as broken builds. A backlog of undated security findings is a queue that never drains. How it gets gamed Severity downgrade by triage. Marking a finding “won’t fix” with a one-word justification is the cheapest override available to a reviewer. The meta-signal is the ratio of suppressed to confirmed findings, and the age of the suppressed ones. Baseline erosion. Freezing the finding count in a baseline file and only failing on new ones, while the baseline quietly grows forever because “old findings are someone else’s problem.” Rule-set narrowing. Disabling taint rules because they are noisy and keeping only the pattern matches, which detect the least of the three. Scanner shopping. Running the tool that finds the fewest issues in review and calling it “our SAST.” What it cannot detect Vulnerabilities in the composition of services, in configuration, or in the dependencies’ runtime behavior — those belong to fault injection and live chaos . And a clean scan says nothing about attacks that arrive through valid inputs: fuzzing covers that side. Security is a deeper topic than this catalog covers. SAST is one sensor in the adversarial family — it asks whether an adversary’s input can reach a dangerous sink — but the broader practice of application security (threat modeling, penetration testing, dependency vulnerabilities, runtime exploit detection) deserves its own resources. See OWASP and the CWE for dedicated treatment.",
10015
10188
  "family": "adversarial",
10016
10189
  "frontmatter": {
10017
10190
  "actionability": "blocking",
@@ -10079,7 +10252,8 @@
10079
10252
  "see_also_ids": [
10080
10253
  "SO-005",
10081
10254
  "SO-005c",
10082
- "SO-001c"
10255
+ "SO-001c",
10256
+ "SO-014b"
10083
10257
  ],
10084
10258
  "see_also_pages": [],
10085
10259
  "slug": "static-security-analysis",
@@ -11416,7 +11590,8 @@
11416
11590
  "comprehension"
11417
11591
  ],
11418
11592
  "see_also_ids": [
11419
- "SO-004"
11593
+ "SO-004",
11594
+ "SO-006b"
11420
11595
  ],
11421
11596
  "see_also_pages": [],
11422
11597
  "slug": "observability-events",
@@ -11808,7 +11983,8 @@
11808
11983
  "see_also_ids": [
11809
11984
  "SO-006",
11810
11985
  "SO-006b",
11811
- "SO-006c"
11986
+ "SO-006c",
11987
+ "SO-006e"
11812
11988
  ],
11813
11989
  "see_also_pages": [],
11814
11990
  "slug": "resource-telemetry",
@@ -12173,7 +12349,8 @@
12173
12349
  "SO-007",
12174
12350
  "SO-007b",
12175
12351
  "SO-015",
12176
- "SO-009"
12352
+ "SO-009",
12353
+ "SO-012d"
12177
12354
  ],
12178
12355
  "see_also_pages": [],
12179
12356
  "slug": "ab-testing",
@@ -12840,7 +13017,8 @@
12840
13017
  "SO-007b",
12841
13018
  "SO-007c",
12842
13019
  "SO-007d",
12843
- "SO-005c"
13020
+ "SO-005c",
13021
+ "SO-007e"
12844
13022
  ],
12845
13023
  "see_also_pages": [],
12846
13024
  "slug": "canary-analysis",
@@ -13419,7 +13597,8 @@
13419
13597
  "see_also_ids": [
13420
13598
  "SO-007",
13421
13599
  "SO-006c",
13422
- "SO-012c"
13600
+ "SO-012c",
13601
+ "SO-007e"
13423
13602
  ],
13424
13603
  "see_also_pages": [],
13425
13604
  "slug": "feature-flag-exposure",
@@ -14336,7 +14515,8 @@
14336
14515
  "see_also_ids": [
14337
14516
  "SO-008",
14338
14517
  "SO-008b",
14339
- "SO-002"
14518
+ "SO-002",
14519
+ "SO-015c"
14340
14520
  ],
14341
14521
  "see_also_pages": [],
14342
14522
  "slug": "boundary-sensors",
@@ -14655,7 +14835,9 @@
14655
14835
  "see_also_ids": [
14656
14836
  "SO-008b",
14657
14837
  "SO-008c",
14658
- "SO-008d"
14838
+ "SO-008d",
14839
+ "SO-015c",
14840
+ "SO-014d"
14659
14841
  ],
14660
14842
  "see_also_pages": [],
14661
14843
  "slug": "dependency-graph",
@@ -15266,7 +15448,8 @@
15266
15448
  "see_also_ids": [
15267
15449
  "SO-008",
15268
15450
  "SO-008b",
15269
- "SO-008d"
15451
+ "SO-008d",
15452
+ "SO-012d"
15270
15453
  ],
15271
15454
  "see_also_pages": [],
15272
15455
  "slug": "hotspot-analysis",
@@ -15603,7 +15786,8 @@
15603
15786
  "see_also_ids": [
15604
15787
  "SO-008b",
15605
15788
  "SO-006c",
15606
- "SO-006"
15789
+ "SO-006",
15790
+ "SO-008"
15607
15791
  ],
15608
15792
  "see_also_pages": [],
15609
15793
  "slug": "live-service-graph",
@@ -16157,8 +16341,8 @@
16157
16341
  "url_path": "sensors/change-coupling/"
16158
16342
  },
16159
16343
  {
16160
- "body_html": "<p>Deployment frequency, lead time for changes, change failure rate,\nreliability, time to restore. Five numbers about how changes have\nhistorically flowed through this organization — a sensor of delivery\n<em>pattern</em>, answering &ldquo;does our recent past look like teams that ship\nsafely?&rdquo; Reliability was added in DORA 2023 as the fifth metric, reflecting\nthat stability is measured by whether the system meets its reliability\ntargets, not just by how fast failures are fixed.</p>\n<h2>In practice</h2>\n<p>The reading is a dashboard of the five numbers over a trailing window,\ncompared against the team&rsquo;s own history rather than an industry table:</p>\n<table>\n<thead>\n<tr>\n<th>Metric</th>\n<th>Last quarter</th>\n<th>This quarter</th>\n<th>Trend</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Deploy frequency</td>\n<td>14 / week</td>\n<td>6 / week</td>\n<td>down</td>\n</tr>\n<tr>\n<td>Lead time for changes</td>\n<td>36 hours</td>\n<td>61 hours</td>\n<td>up (worse)</td>\n</tr>\n<tr>\n<td>Change failure rate</td>\n<td>4%</td>\n<td>11%</td>\n<td>up (worse)</td>\n</tr>\n<tr>\n<td>Time to restore</td>\n<td>45 minutes</td>\n<td>3 hours</td>\n<td>up (worse)</td>\n</tr>\n<tr>\n<td>Reliability target met</td>\n<td>yes</td>\n<td>no</td>\n<td>worse</td>\n</tr>\n</tbody>\n</table>\n<p>Reading it well:</p>\n<ol>\n<li><strong>Read the five together.</strong> Deployment frequency falling alone might\n be a deliberate pause; falling alongside rising failure rate and\n lead time is a pipeline in trouble. One number is an anecdote, five\n are a pattern.</li>\n<li><strong>Compare against the team&rsquo;s own baseline.</strong> Industry quartiles\n tell you what other teams do, not what changed here. The trend line\n is the signal.</li>\n<li><strong>Distrust flat perfection.</strong> Metrics that never move usually mean\n the instrumentation is stale or the definitions quietly widened.</li>\n<li><strong>Follow a bad number to its records.</strong> The dashboard locates the\n quarter; the deploy log and incident timeline explain it.</li>\n</ol>\n<h2>How it gets gamed</h2>\n<p>DORA metrics are classically gameable, because every one of them can be\nmoved without moving the underlying delivery health:</p>\n<ul>\n<li><strong>Batch deploys to inflate frequency.</strong> Shipping twenty small commits\n as one &ldquo;deploy&rdquo; or splitting one change into ten trivial ones; the\n frequency number moves either way, depending on which direction\n flatters the dashboard.</li>\n<li><strong>Cherry-pick easy deploys.</strong> Counting only the low-risk service\n whose pipeline is mature, while the painful path everyone actually\n uses goes unmeasured.</li>\n<li><strong>Close incidents early.</strong> Marking an incident resolved when the page\n stops, not when users are unaffected, buys time-to-restore without\n restoring anything.</li>\n<li><strong>Redefine failure downward.</strong> If only rollbacks count as failures,\n teams hotfix forward and the change failure rate falls by definition.</li>\n<li><strong>Tighten the reliability denominator.</strong> Meeting a target is easy\n when the target was quietly negotiated down after the quarter\n started.</li>\n</ul>\n<p>The meta-signal is definitional drift: log the metric definitions and\nthe counting rules alongside the numbers, and treat any unannounced\nchange to them as a finding in itself.</p>\n<h2>Response playbook</h2>\n<p>When the dashboard degrades:</p>\n<ol>\n<li><strong>Verify the definitions first.</strong> Before acting, confirm the numbers\n were computed the same way as last quarter. Half of all &ldquo;sudden&rdquo;\n DORA regressions are a pipeline or counting change, not a delivery\n change.</li>\n<li><strong>Trace the lead-time increase stage by stage.</strong> Split lead time\n into review, build, test, and deploy waits; the longest stage is\n the one to attack, and it is almost never the one people guess.</li>\n<li><strong>Correlate failure rate with incident records.</strong> Use <a class=\"body-link\" href=\"/sensors/incident-correlation/\">incident\n correlation</a> to see whether failures are\n clustering in one service or one change type before prescribing a\n fix.</li>\n<li><strong>Lower batch size, not ambition.</strong> If deploys are failing more,\n ship smaller changes with <a class=\"body-link\" href=\"/sensors/canary-analysis/\">canary analysis</a>\n in front of them; do not add another approval gate, which raises\n lead time and failure rate together.</li>\n<li><strong>Re-measure after one quarter.</strong> The dashboard lags; give any fix a\n full window before judging it, and say so when the numbers are\n reported.</li>\n</ol>\n<h2>What it cannot detect</h2>\n<p>The cause of a bad number. DORA metrics locate the problem in time and\nstage; understanding it requires <a class=\"body-link\" href=\"/sensors/decision-provenance/\">decision provenance</a>\nand <a class=\"body-link\" href=\"/sensors/incident-correlation/\">incident correlation</a>. And like all metrics\nthat become targets, they invite gaming — deployment frequency can be raised\nby shipping trivia.</p>",
16161
- "body_text": "Deployment frequency, lead time for changes, change failure rate, reliability, time to restore. Five numbers about how changes have historically flowed through this organization — a sensor of delivery pattern , answering “does our recent past look like teams that ship safely?” Reliability was added in DORA 2023 as the fifth metric, reflecting that stability is measured by whether the system meets its reliability targets, not just by how fast failures are fixed. In practice The reading is a dashboard of the five numbers over a trailing window, compared against the team’s own history rather than an industry table: Metric Last quarter This quarter Trend Deploy frequency 14 / week 6 / week down Lead time for changes 36 hours 61 hours up (worse) Change failure rate 4% 11% up (worse) Time to restore 45 minutes 3 hours up (worse) Reliability target met yes no worse Reading it well: Read the five together. Deployment frequency falling alone might be a deliberate pause; falling alongside rising failure rate and lead time is a pipeline in trouble. One number is an anecdote, five are a pattern. Compare against the team’s own baseline. Industry quartiles tell you what other teams do, not what changed here. The trend line is the signal. Distrust flat perfection. Metrics that never move usually mean the instrumentation is stale or the definitions quietly widened. Follow a bad number to its records. The dashboard locates the quarter; the deploy log and incident timeline explain it. How it gets gamed DORA metrics are classically gameable, because every one of them can be moved without moving the underlying delivery health: Batch deploys to inflate frequency. Shipping twenty small commits as one “deploy” or splitting one change into ten trivial ones; the frequency number moves either way, depending on which direction flatters the dashboard. Cherry-pick easy deploys. Counting only the low-risk service whose pipeline is mature, while the painful path everyone actually uses goes unmeasured. Close incidents early. Marking an incident resolved when the page stops, not when users are unaffected, buys time-to-restore without restoring anything. Redefine failure downward. If only rollbacks count as failures, teams hotfix forward and the change failure rate falls by definition. Tighten the reliability denominator. Meeting a target is easy when the target was quietly negotiated down after the quarter started. The meta-signal is definitional drift: log the metric definitions and the counting rules alongside the numbers, and treat any unannounced change to them as a finding in itself. Response playbook When the dashboard degrades: Verify the definitions first. Before acting, confirm the numbers were computed the same way as last quarter. Half of all “sudden” DORA regressions are a pipeline or counting change, not a delivery change. Trace the lead-time increase stage by stage. Split lead time into review, build, test, and deploy waits; the longest stage is the one to attack, and it is almost never the one people guess. Correlate failure rate with incident records. Use incident correlation to see whether failures are clustering in one service or one change type before prescribing a fix. Lower batch size, not ambition. If deploys are failing more, ship smaller changes with canary analysis in front of them; do not add another approval gate, which raises lead time and failure rate together. Re-measure after one quarter. The dashboard lags; give any fix a full window before judging it, and say so when the numbers are reported. What it cannot detect The cause of a bad number. DORA metrics locate the problem in time and stage; understanding it requires decision provenance and incident correlation . And like all metrics that become targets, they invite gaming — deployment frequency can be raised by shipping trivia.",
16344
+ "body_html": "<p>Change lead time, deployment frequency, failed deployment recovery time,\nchange fail rate, deployment rework rate. Five numbers about how changes\nhave historically flowed through this organization — a sensor of delivery\n<em>pattern</em>, answering &ldquo;does our recent past look like teams that ship\nsafely?&rdquo; This entry describes the five-metric model DORA has published\nsince 2024: the first three grouped as throughput, the last two as\ninstability.</p>\n<p>The set is versioned, and the names move. &ldquo;Time to restore service&rdquo; was\nrenamed <em>failed deployment recovery time</em> in 2023, narrowing it to failures\na deployment caused rather than any outage; <em>deployment rework rate</em> arrived\nin 2024 as the actual fifth metric. Reliability was added in 2021 and is not\none of the five — DORA&rsquo;s own history records that the report calling it &ldquo;the\nfifth metric&rdquo; was inaccurate, and files it under operational rather than\ndelivery performance. A dashboard labelled &ldquo;the DORA metrics&rdquo; is dated by\nwhich names it uses, and comparing readings across a rename compares two\nsensors.</p>\n<h2>In practice</h2>\n<p>The reading is a dashboard of the five numbers over a trailing window,\ncompared against the team&rsquo;s own history rather than an industry table:</p>\n<table>\n<thead>\n<tr>\n<th>Metric</th>\n<th>Last quarter</th>\n<th>This quarter</th>\n<th>Trend</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Change lead time</td>\n<td>36 hours</td>\n<td>61 hours</td>\n<td>up (worse)</td>\n</tr>\n<tr>\n<td>Deployment frequency</td>\n<td>14 / week</td>\n<td>6 / week</td>\n<td>down</td>\n</tr>\n<tr>\n<td>Failed deployment recovery time</td>\n<td>45 minutes</td>\n<td>3 hours</td>\n<td>up (worse)</td>\n</tr>\n<tr>\n<td>Change fail rate</td>\n<td>4%</td>\n<td>11%</td>\n<td>up (worse)</td>\n</tr>\n<tr>\n<td>Deployment rework rate</td>\n<td>6%</td>\n<td>14%</td>\n<td>up (worse)</td>\n</tr>\n</tbody>\n</table>\n<p>Reading it well:</p>\n<ol>\n<li><strong>Read the five together.</strong> Deployment frequency falling alone might\n be a deliberate pause; falling alongside rising failure rate and\n lead time is a pipeline in trouble. One number is an anecdote, five\n are a pattern.</li>\n<li><strong>Compare against the team&rsquo;s own baseline.</strong> Industry quartiles\n tell you what other teams do, not what changed here. The trend line\n is the signal.</li>\n<li><strong>Distrust flat perfection.</strong> Metrics that never move usually mean\n the instrumentation is stale or the definitions quietly widened.</li>\n<li><strong>Follow a bad number to its records.</strong> The dashboard locates the\n quarter; the deploy log and incident timeline explain it.</li>\n</ol>\n<h2>How it gets gamed</h2>\n<p>DORA metrics are classically gameable, because every one of them can be\nmoved without moving the underlying delivery health:</p>\n<ul>\n<li><strong>Batch deploys to inflate frequency.</strong> Shipping twenty small commits\n as one &ldquo;deploy&rdquo; or splitting one change into ten trivial ones; the\n frequency number moves either way, depending on which direction\n flatters the dashboard.</li>\n<li><strong>Cherry-pick easy deploys.</strong> Counting only the low-risk service\n whose pipeline is mature, while the painful path everyone actually\n uses goes unmeasured.</li>\n<li><strong>Close incidents early.</strong> Marking an incident resolved when the page\n stops, not when users are unaffected, buys recovery time without\n restoring anything.</li>\n<li><strong>Redefine failure downward.</strong> If only rollbacks count as failures,\n teams hotfix forward and the change failure rate falls by definition.</li>\n<li><strong>File rework as planned work.</strong> Deployment rework rate counts the\n deployments that exist only to fix something already shipped. Attach\n the hotfix to the next feature ticket and it stops counting, while\n the rework goes on happening.</li>\n</ul>\n<p>The meta-signal is definitional drift: log the metric definitions and\nthe counting rules alongside the numbers, and treat any unannounced\nchange to them as a finding in itself.</p>\n<h2>Response playbook</h2>\n<p>When the dashboard degrades:</p>\n<ol>\n<li><strong>Verify the definitions first.</strong> Before acting, confirm the numbers\n were computed the same way as last quarter. Half of all &ldquo;sudden&rdquo;\n DORA regressions are a pipeline or counting change, not a delivery\n change.</li>\n<li><strong>Trace the lead-time increase stage by stage.</strong> Split lead time\n into review, build, test, and deploy waits; the longest stage is\n the one to attack, and it is almost never the one people guess.</li>\n<li><strong>Correlate failure rate with incident records.</strong> Use <a class=\"body-link\" href=\"/sensors/incident-correlation/\">incident\n correlation</a> to see whether failures are\n clustering in one service or one change type before prescribing a\n fix.</li>\n<li><strong>Lower batch size, not ambition.</strong> If deploys are failing more,\n ship smaller changes with <a class=\"body-link\" href=\"/sensors/canary-analysis/\">canary analysis</a>\n in front of them; do not add another approval gate, which raises\n lead time and failure rate together.</li>\n<li><strong>Re-measure after one quarter.</strong> The dashboard lags; give any fix a\n full window before judging it, and say so when the numbers are\n reported.</li>\n</ol>\n<h2>What it cannot detect</h2>\n<p>The cause of a bad number. DORA metrics locate the problem in time and\nstage; understanding it requires <a class=\"body-link\" href=\"/sensors/decision-provenance/\">decision provenance</a>\nand <a class=\"body-link\" href=\"/sensors/incident-correlation/\">incident correlation</a>. And like all metrics\nthat become targets, they invite gaming — deployment frequency can be raised\nby shipping trivia.</p>",
16345
+ "body_text": "Change lead time, deployment frequency, failed deployment recovery time, change fail rate, deployment rework rate. Five numbers about how changes have historically flowed through this organization — a sensor of delivery pattern , answering “does our recent past look like teams that ship safely?” This entry describes the five-metric model DORA has published since 2024: the first three grouped as throughput, the last two as instability. The set is versioned, and the names move. “Time to restore service” was renamed failed deployment recovery time in 2023, narrowing it to failures a deployment caused rather than any outage; deployment rework rate arrived in 2024 as the actual fifth metric. Reliability was added in 2021 and is not one of the five — DORA’s own history records that the report calling it “the fifth metric” was inaccurate, and files it under operational rather than delivery performance. A dashboard labelled “the DORA metrics” is dated by which names it uses, and comparing readings across a rename compares two sensors. In practice The reading is a dashboard of the five numbers over a trailing window, compared against the team’s own history rather than an industry table: Metric Last quarter This quarter Trend Change lead time 36 hours 61 hours up (worse) Deployment frequency 14 / week 6 / week down Failed deployment recovery time 45 minutes 3 hours up (worse) Change fail rate 4% 11% up (worse) Deployment rework rate 6% 14% up (worse) Reading it well: Read the five together. Deployment frequency falling alone might be a deliberate pause; falling alongside rising failure rate and lead time is a pipeline in trouble. One number is an anecdote, five are a pattern. Compare against the team’s own baseline. Industry quartiles tell you what other teams do, not what changed here. The trend line is the signal. Distrust flat perfection. Metrics that never move usually mean the instrumentation is stale or the definitions quietly widened. Follow a bad number to its records. The dashboard locates the quarter; the deploy log and incident timeline explain it. How it gets gamed DORA metrics are classically gameable, because every one of them can be moved without moving the underlying delivery health: Batch deploys to inflate frequency. Shipping twenty small commits as one “deploy” or splitting one change into ten trivial ones; the frequency number moves either way, depending on which direction flatters the dashboard. Cherry-pick easy deploys. Counting only the low-risk service whose pipeline is mature, while the painful path everyone actually uses goes unmeasured. Close incidents early. Marking an incident resolved when the page stops, not when users are unaffected, buys recovery time without restoring anything. Redefine failure downward. If only rollbacks count as failures, teams hotfix forward and the change failure rate falls by definition. File rework as planned work. Deployment rework rate counts the deployments that exist only to fix something already shipped. Attach the hotfix to the next feature ticket and it stops counting, while the rework goes on happening. The meta-signal is definitional drift: log the metric definitions and the counting rules alongside the numbers, and treat any unannounced change to them as a finding in itself. Response playbook When the dashboard degrades: Verify the definitions first. Before acting, confirm the numbers were computed the same way as last quarter. Half of all “sudden” DORA regressions are a pipeline or counting change, not a delivery change. Trace the lead-time increase stage by stage. Split lead time into review, build, test, and deploy waits; the longest stage is the one to attack, and it is almost never the one people guess. Correlate failure rate with incident records. Use incident correlation to see whether failures are clustering in one service or one change type before prescribing a fix. Lower batch size, not ambition. If deploys are failing more, ship smaller changes with canary analysis in front of them; do not add another approval gate, which raises lead time and failure rate together. Re-measure after one quarter. The dashboard lags; give any fix a full window before judging it, and say so when the numbers are reported. What it cannot detect The cause of a bad number. DORA metrics locate the problem in time and stage; understanding it requires decision provenance and incident correlation . And like all metrics that become targets, they invite gaming — deployment frequency can be raised by shipping trivia.",
16162
16346
  "family": "evolution",
16163
16347
  "frontmatter": {
16164
16348
  "actionability": "guiding",
@@ -16192,6 +16376,11 @@
16192
16376
  "venue": "Puppet + DORA",
16193
16377
  "year": 2017
16194
16378
  },
16379
+ {
16380
+ "kind": "other",
16381
+ "title": "A history of DORA's software delivery metrics",
16382
+ "url": "https://dora.dev/insights/dora-metrics-history/"
16383
+ },
16195
16384
  {
16196
16385
  "kind": "tool",
16197
16386
  "title": "DORA",
@@ -16211,7 +16400,7 @@
16211
16400
  "url": "https://dora.dev"
16212
16401
  },
16213
16402
  {
16214
- "description": "DORA's four-metric assessment tool",
16403
+ "description": "DORA's research assessment tool",
16215
16404
  "kind": "tool",
16216
16405
  "title": "DevOps Research Assessment",
16217
16406
  "url": "https://dora.dev/research/"
@@ -16234,9 +16423,13 @@
16234
16423
  "title": "DORA Metrics",
16235
16424
  "tokens": [
16236
16425
  "015b",
16426
+ "2021",
16237
16427
  "2023",
16428
+ "2024",
16238
16429
  "about",
16430
+ "across",
16239
16431
  "acting",
16432
+ "actual",
16240
16433
  "actually",
16241
16434
  "add",
16242
16435
  "added",
@@ -16246,6 +16439,7 @@
16246
16439
  "almost",
16247
16440
  "alone",
16248
16441
  "alongside",
16442
+ "already",
16249
16443
  "ambition",
16250
16444
  "analysis",
16251
16445
  "and",
@@ -16256,6 +16450,8 @@
16256
16450
  "anything",
16257
16451
  "approval",
16258
16452
  "are",
16453
+ "arrived",
16454
+ "attach",
16259
16455
  "attack",
16260
16456
  "bad",
16261
16457
  "baseline",
@@ -16265,10 +16461,12 @@
16265
16461
  "before",
16266
16462
  "build",
16267
16463
  "buys",
16464
+ "calling",
16268
16465
  "can",
16269
16466
  "canary",
16270
16467
  "cannot",
16271
16468
  "cause",
16469
+ "caused",
16272
16470
  "change",
16273
16471
  "changed",
16274
16472
  "changes",
@@ -16279,13 +16477,17 @@
16279
16477
  "commits",
16280
16478
  "compare",
16281
16479
  "compared",
16480
+ "compares",
16481
+ "comparing",
16282
16482
  "computed",
16283
16483
  "confirm",
16284
16484
  "correlate",
16285
16485
  "correlation",
16286
16486
  "count",
16287
16487
  "counting",
16488
+ "counts",
16288
16489
  "dashboard",
16490
+ "dated",
16289
16491
  "decision",
16290
16492
  "definition",
16291
16493
  "definitional",
@@ -16293,11 +16495,12 @@
16293
16495
  "degrades",
16294
16496
  "deliberate",
16295
16497
  "delivery",
16296
- "denominator",
16297
16498
  "depending",
16298
16499
  "deploy",
16299
16500
  "deployment",
16501
+ "deployments",
16300
16502
  "deploys",
16503
+ "describes",
16301
16504
  "detect",
16302
16505
  "direction",
16303
16506
  "distrust",
@@ -16309,26 +16512,30 @@
16309
16512
  "early",
16310
16513
  "easy",
16311
16514
  "either",
16515
+ "entry",
16312
16516
  "every",
16313
16517
  "everyone",
16518
+ "exist",
16314
16519
  "explain",
16520
+ "fail",
16521
+ "failed",
16315
16522
  "failing",
16316
16523
  "failure",
16317
16524
  "failures",
16318
16525
  "falling",
16319
16526
  "falls",
16320
- "fast",
16527
+ "feature",
16321
16528
  "fifth",
16529
+ "file",
16530
+ "files",
16322
16531
  "finding",
16323
16532
  "first",
16324
16533
  "five",
16325
16534
  "fix",
16326
- "fixed",
16327
16535
  "flat",
16328
16536
  "flatters",
16329
16537
  "flowed",
16330
16538
  "follow",
16331
- "for",
16332
16539
  "forward",
16333
16540
  "frequency",
16334
16541
  "front",
@@ -16340,8 +16547,11 @@
16340
16547
  "gets",
16341
16548
  "give",
16342
16549
  "goes",
16550
+ "grouped",
16343
16551
  "guess",
16344
16552
  "half",
16553
+ "happening",
16554
+ "has",
16345
16555
  "have",
16346
16556
  "health",
16347
16557
  "here",
@@ -16350,18 +16560,20 @@
16350
16560
  "hotfix",
16351
16561
  "hours",
16352
16562
  "how",
16563
+ "inaccurate",
16353
16564
  "incident",
16354
16565
  "incidents",
16355
16566
  "increase",
16356
16567
  "industry",
16357
16568
  "inflate",
16569
+ "instability",
16358
16570
  "instrumentation",
16359
16571
  "into",
16360
16572
  "invite",
16361
16573
  "its",
16362
16574
  "itself",
16363
16575
  "judging",
16364
- "just",
16576
+ "labelled",
16365
16577
  "lags",
16366
16578
  "last",
16367
16579
  "lead",
@@ -16378,31 +16590,32 @@
16378
16590
  "mature",
16379
16591
  "mean",
16380
16592
  "measure",
16381
- "measured",
16382
- "meeting",
16383
- "meets",
16384
- "met",
16385
16593
  "meta",
16386
16594
  "metric",
16387
16595
  "metrics",
16388
16596
  "might",
16389
16597
  "minutes",
16598
+ "model",
16390
16599
  "more",
16391
16600
  "move",
16392
16601
  "moved",
16393
16602
  "moves",
16394
16603
  "moving",
16395
- "negotiated",
16604
+ "names",
16605
+ "narrowing",
16396
16606
  "never",
16607
+ "next",
16397
16608
  "not",
16398
16609
  "number",
16399
16610
  "numbers",
16400
16611
  "one",
16401
16612
  "ones",
16402
16613
  "only",
16614
+ "operational",
16403
16615
  "organization",
16404
16616
  "other",
16405
16617
  "our",
16618
+ "outage",
16406
16619
  "over",
16407
16620
  "own",
16408
16621
  "page",
@@ -16413,13 +16626,16 @@
16413
16626
  "pause",
16414
16627
  "people",
16415
16628
  "perfection",
16629
+ "performance",
16416
16630
  "pick",
16417
16631
  "pipeline",
16632
+ "planned",
16418
16633
  "playbook",
16419
16634
  "practice",
16420
16635
  "prescribing",
16421
16636
  "problem",
16422
16637
  "provenance",
16638
+ "published",
16423
16639
  "quarter",
16424
16640
  "quartiles",
16425
16641
  "quietly",
@@ -16429,12 +16645,16 @@
16429
16645
  "rather",
16430
16646
  "read",
16431
16647
  "reading",
16648
+ "readings",
16432
16649
  "recent",
16433
16650
  "records",
16651
+ "recovery",
16434
16652
  "redefine",
16435
- "reflecting",
16436
16653
  "regressions",
16437
16654
  "reliability",
16655
+ "rename",
16656
+ "renamed",
16657
+ "report",
16438
16658
  "reported",
16439
16659
  "requires",
16440
16660
  "resolved",
@@ -16442,6 +16662,7 @@
16442
16662
  "restore",
16443
16663
  "restoring",
16444
16664
  "review",
16665
+ "rework",
16445
16666
  "rising",
16446
16667
  "risk",
16447
16668
  "rollbacks",
@@ -16451,24 +16672,25 @@
16451
16672
  "say",
16452
16673
  "see",
16453
16674
  "sensor",
16675
+ "sensors",
16454
16676
  "service",
16677
+ "set",
16455
16678
  "ship",
16679
+ "shipped",
16456
16680
  "shipping",
16457
16681
  "signal",
16682
+ "since",
16458
16683
  "size",
16459
16684
  "small",
16460
16685
  "smaller",
16686
+ "something",
16461
16687
  "split",
16462
16688
  "splitting",
16463
- "stability",
16464
16689
  "stage",
16465
16690
  "stale",
16466
- "started",
16467
16691
  "stops",
16468
16692
  "sudden",
16469
- "system",
16470
16693
  "table",
16471
- "target",
16472
16694
  "targets",
16473
16695
  "team",
16474
16696
  "teams",
@@ -16481,8 +16703,10 @@
16481
16703
  "them",
16482
16704
  "they",
16483
16705
  "this",
16706
+ "three",
16484
16707
  "through",
16485
- "tighten",
16708
+ "throughput",
16709
+ "ticket",
16486
16710
  "time",
16487
16711
  "timeline",
16488
16712
  "together",
@@ -16494,9 +16718,11 @@
16494
16718
  "trivial",
16495
16719
  "trouble",
16496
16720
  "twenty",
16721
+ "two",
16497
16722
  "type",
16498
16723
  "unaffected",
16499
16724
  "unannounced",
16725
+ "under",
16500
16726
  "underlying",
16501
16727
  "understanding",
16502
16728
  "unmeasured",
@@ -16505,6 +16731,7 @@
16505
16731
  "uses",
16506
16732
  "usually",
16507
16733
  "verify",
16734
+ "versioned",
16508
16735
  "waits",
16509
16736
  "was",
16510
16737
  "way",
@@ -16521,8 +16748,8 @@
16521
16748
  "window",
16522
16749
  "with",
16523
16750
  "without",
16751
+ "work",
16524
16752
  "worse",
16525
- "yes",
16526
16753
  "you"
16527
16754
  ],
16528
16755
  "url_path": "sensors/dora-metrics/"
@@ -16898,7 +17125,9 @@
16898
17125
  "see_also_ids": [
16899
17126
  "SO-009b",
16900
17127
  "SO-009c",
16901
- "SO-009d"
17128
+ "SO-009d",
17129
+ "SO-007e",
17130
+ "SO-015b"
16902
17131
  ],
16903
17132
  "see_also_pages": [],
16904
17133
  "slug": "revert-rate",
@@ -17242,7 +17471,8 @@
17242
17471
  "see_also_ids": [
17243
17472
  "SO-009",
17244
17473
  "SO-009b",
17245
- "SO-009c"
17474
+ "SO-009c",
17475
+ "SO-015b"
17246
17476
  ],
17247
17477
  "see_also_pages": [],
17248
17478
  "slug": "time-to-repair",
@@ -18358,7 +18588,8 @@
18358
18588
  "see_also_ids": [
18359
18589
  "SO-010b",
18360
18590
  "SO-010c",
18361
- "SO-010d"
18591
+ "SO-010d",
18592
+ "SO-011d"
18362
18593
  ],
18363
18594
  "see_also_pages": [],
18364
18595
  "slug": "independent-review",