softwareobservatory 0.2.1 → 0.3.1
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/bin/softwareobservatory.mjs +250 -51
- package/data/sensors.json +326 -92
- package/lib/core.mjs +188 -22
- package/lib/mcp.mjs +3 -3
- package/package.json +9 -2
package/data/sensors.json
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
+
"attribution": "Software Observatory by Justin Abrahms — https://softwareobservatory.com. Licensed CC BY-SA 4.0; adaptations must carry the same license.",
|
|
2
3
|
"families": [
|
|
3
4
|
{
|
|
4
5
|
"count": 9,
|
|
@@ -82,6 +83,8 @@
|
|
|
82
83
|
}
|
|
83
84
|
],
|
|
84
85
|
"generated_at": "2026-08-24T00:00:00Z",
|
|
86
|
+
"license": "CC-BY-SA-4.0",
|
|
87
|
+
"license_url": "https://creativecommons.org/licenses/by-sa/4.0/",
|
|
85
88
|
"sensors": [
|
|
86
89
|
{
|
|
87
90
|
"body_html": "<p>Is the artifact you are about to deploy structurally the one your pipeline\nbuilt? A software bill of materials plus build provenance attestation\n(SLSA-style) answers: these sources, these dependencies, this builder, this\nhash. A structural sensor aimed at the moment of deployment, where the\nartifact leaves the world you control.</p>\n<h2>In practice</h2>\n<p>A reading is two artifacts: the SBOM, and the attestation tying it to\nthe artifact you are about to deploy.</p>\n<pre><code>artifact: payments-api@sha256:9f1c2b...\nbuilder: https://ci.internal/builders/release (SLSA level 3)\nbuild: run 8841, commit a41d2e9, 2026-08-19T14:02Z\nsignature: verified, sigstore keyless\n\ncomponents:\n log4j-core 2.14.1 pkg:maven/org.apache.logging.log4j/log4j-core@2.14.1\n jackson-databind 2.15.2 pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.15.2\n</code></pre>\n<p>Cross-referenced against the vulnerability feed, the same reading says:</p>\n<table>\n<thead>\n<tr>\n<th>Component</th>\n<th>Version</th>\n<th>Finding</th>\n<th>Severity</th>\n<th>Verdict</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>log4j-core</td>\n<td>2.14.1</td>\n<td>CVE-2021-44228, remote code execution</td>\n<td>critical</td>\n<td>block deploy</td>\n</tr>\n<tr>\n<td>jackson-databind</td>\n<td>2.15.2</td>\n<td>no known CVEs</td>\n<td>none</td>\n<td>clear</td>\n</tr>\n</tbody>\n</table>\n<p>Reading it well:</p>\n<ul>\n<li><strong>Verify the signature before reading the contents.</strong> An SBOM you\n cannot tie to the builder is a claim, not an attestation. The hash\n comparison is the whole sensor; everything else is commentary.</li>\n<li><strong>Read versions, not names.</strong> Two minor versions can be the\n difference between clear and critical, and name-only matching misses\n it.</li>\n<li><strong>Treat “not in the SBOM” as a finding.</strong> A component present in the\n artifact but absent from the manifest means the SBOM was generated\n from the wrong place, usually the source tree instead of the build.</li>\n</ul>\n<h2>Response playbook</h2>\n<p>When the sensor fires:</p>\n<ol>\n<li><strong>Block the deploy on any mismatch.</strong> Unverified signature, hash\n mismatch, or missing attestation all mean the artifact does not\n ship. Do not deploy first and reconcile later; the artifact leaves\n the world you control at deploy time.</li>\n<li><strong>Block on critical vulnerability findings.</strong> A critical CVE in a\n shipped component is a stop-ship condition. Confirm whether the\n component is reachable in your build; if it is dead weight,\n upgrade it anyway, because the next reader of the SBOM will not\n know.</li>\n<li><strong>Rebuild from the attested source, not from the cached artifact.</strong>\n A mismatch usually means the build ran somewhere else, or the\n cache is stale. Re-run the pipeline and compare hashes again.</li>\n<li><strong>Escalate signature failures, not just dependency failures.</strong> A\n vulnerable dependency is a fix; a bad or missing signature is a\n possible supply-chain event. Notify security before retrying.</li>\n<li><strong>Record the attestation hash at deploy time.</strong> When a CVE ships\n next quarter, you need to answer which deployments carry it, from\n the SBOM, not from memory.</li>\n</ol>\n<h2>What it cannot detect</h2>\n<p>Malice inside the sources themselves, or a compromised builder that signs\nits own output faithfully. Provenance tells you the artifact is the one the\npipeline produced; whether the pipeline was honest is a question for\n<a class=\"body-link\" href=\"/sensors/static-security-analysis/\">static security analysis</a> and dependency\nauditing.</p>\n<p>Supply chain security is a deeper topic than this catalog covers.\nBuild provenance and SBOMs are one sensor in the structural family — they\nverify the artifact matches what the pipeline built — but the broader\npractice of supply chain security (dependency vulnerabilities, artifact\nsigning trust roots, runtime attestation, policy enforcement) deserves its\nown resources. See <a class=\"body-link\" href=\"https://slsa.dev\">SLSA</a> and\n<a class=\"body-link\" href=\"https://sigstore.dev\">Sigstore</a> for dedicated treatment.</p>",
|
|
@@ -154,7 +157,8 @@
|
|
|
154
157
|
"see_also_ids": [
|
|
155
158
|
"SO-001",
|
|
156
159
|
"SO-014",
|
|
157
|
-
"SO-012b"
|
|
160
|
+
"SO-012b",
|
|
161
|
+
"SO-014d"
|
|
158
162
|
],
|
|
159
163
|
"see_also_pages": [],
|
|
160
164
|
"slug": "build-provenance-sbom",
|
|
@@ -664,8 +668,8 @@
|
|
|
664
668
|
"url_path": "sensors/compiler/"
|
|
665
669
|
},
|
|
666
670
|
{
|
|
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 “do the shapes fit?”, a\nrefinement type answers “can <code>withdraw(amount)</code> ever be called with\n<code>amount > balance</code>?” — 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.
|
|
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.
|
|
671
|
+
"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 “do the shapes fit?”, a\nrefinement type answers “can <code>withdraw(amount)</code> ever be called with\n<code>amount > balance</code>?” — 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 “cannot guarantee that the counterexample it reports\nprovably violates the assertion”, and that the output “should be\ninspected manually and treated as a hint”. 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>",
|
|
672
|
+
"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
673
|
"family": "structural",
|
|
670
674
|
"frontmatter": {
|
|
671
675
|
"actionability": "blocking",
|
|
@@ -703,6 +707,11 @@
|
|
|
703
707
|
"title": "Dafny",
|
|
704
708
|
"url": "https://dafny.org"
|
|
705
709
|
},
|
|
710
|
+
{
|
|
711
|
+
"kind": "other",
|
|
712
|
+
"title": "Dafny user guide, verification debugging",
|
|
713
|
+
"url": "https://dafny.org/latest/DafnyRef/DafnyRef#sec-counterexamples"
|
|
714
|
+
},
|
|
706
715
|
{
|
|
707
716
|
"description": "Static analysis and verification for C",
|
|
708
717
|
"kind": "tool",
|
|
@@ -734,7 +743,8 @@
|
|
|
734
743
|
"SO-001",
|
|
735
744
|
"SO-001d",
|
|
736
745
|
"SO-002",
|
|
737
|
-
"SO-005"
|
|
746
|
+
"SO-005",
|
|
747
|
+
"SO-017"
|
|
738
748
|
],
|
|
739
749
|
"see_also_pages": [],
|
|
740
750
|
"slug": "contract-refinement-types",
|
|
@@ -744,6 +754,7 @@
|
|
|
744
754
|
"abandoned",
|
|
745
755
|
"all",
|
|
746
756
|
"alone",
|
|
757
|
+
"always",
|
|
747
758
|
"amount",
|
|
748
759
|
"and",
|
|
749
760
|
"annotated",
|
|
@@ -751,33 +762,36 @@
|
|
|
751
762
|
"answers",
|
|
752
763
|
"are",
|
|
753
764
|
"ask",
|
|
765
|
+
"assertion",
|
|
754
766
|
"author",
|
|
755
767
|
"balance",
|
|
756
768
|
"based",
|
|
757
769
|
"before",
|
|
758
770
|
"behavioral",
|
|
759
771
|
"behaviors",
|
|
772
|
+
"believing",
|
|
760
773
|
"between",
|
|
761
774
|
"both",
|
|
762
|
-
"
|
|
775
|
+
"breath",
|
|
763
776
|
"build",
|
|
764
777
|
"but",
|
|
765
778
|
"call",
|
|
766
779
|
"called",
|
|
767
780
|
"can",
|
|
781
|
+
"candidate",
|
|
768
782
|
"cannot",
|
|
769
783
|
"causes",
|
|
770
784
|
"certain",
|
|
771
785
|
"check",
|
|
772
786
|
"checked",
|
|
773
787
|
"checker",
|
|
774
|
-
"checkers",
|
|
775
788
|
"checking",
|
|
776
789
|
"claim",
|
|
777
790
|
"claims",
|
|
778
791
|
"code",
|
|
779
792
|
"compiler",
|
|
780
793
|
"composes",
|
|
794
|
+
"concrete",
|
|
781
795
|
"connect",
|
|
782
796
|
"contract",
|
|
783
797
|
"contracts",
|
|
@@ -785,6 +799,7 @@
|
|
|
785
799
|
"could",
|
|
786
800
|
"count",
|
|
787
801
|
"counter",
|
|
802
|
+
"counterexample",
|
|
788
803
|
"cover",
|
|
789
804
|
"dafny",
|
|
790
805
|
"decide",
|
|
@@ -806,7 +821,6 @@
|
|
|
806
821
|
"does",
|
|
807
822
|
"each",
|
|
808
823
|
"eiffel",
|
|
809
|
-
"emits",
|
|
810
824
|
"ensures",
|
|
811
825
|
"entry",
|
|
812
826
|
"erosion",
|
|
@@ -814,9 +828,9 @@
|
|
|
814
828
|
"ever",
|
|
815
829
|
"every",
|
|
816
830
|
"example",
|
|
817
|
-
"examples",
|
|
818
831
|
"execution",
|
|
819
832
|
"executions",
|
|
833
|
+
"extract",
|
|
820
834
|
"failed",
|
|
821
835
|
"failing",
|
|
822
836
|
"fails",
|
|
@@ -834,13 +848,16 @@
|
|
|
834
848
|
"gets",
|
|
835
849
|
"goes",
|
|
836
850
|
"green",
|
|
851
|
+
"guarantee",
|
|
837
852
|
"guarantees",
|
|
853
|
+
"hand",
|
|
838
854
|
"have",
|
|
855
|
+
"hint",
|
|
839
856
|
"how",
|
|
840
857
|
"implementation",
|
|
841
858
|
"impossible",
|
|
842
859
|
"input",
|
|
843
|
-
"
|
|
860
|
+
"inspected",
|
|
844
861
|
"instead",
|
|
845
862
|
"intermediate",
|
|
846
863
|
"into",
|
|
@@ -848,13 +865,13 @@
|
|
|
848
865
|
"its",
|
|
849
866
|
"itself",
|
|
850
867
|
"job",
|
|
851
|
-
"keeping",
|
|
852
868
|
"lemma",
|
|
853
869
|
"like",
|
|
854
870
|
"location",
|
|
855
871
|
"loosens",
|
|
856
872
|
"machine",
|
|
857
873
|
"made",
|
|
874
|
+
"manually",
|
|
858
875
|
"many",
|
|
859
876
|
"merely",
|
|
860
877
|
"merge",
|
|
@@ -863,6 +880,7 @@
|
|
|
863
880
|
"mind",
|
|
864
881
|
"minimal",
|
|
865
882
|
"missing",
|
|
883
|
+
"model",
|
|
866
884
|
"must",
|
|
867
885
|
"names",
|
|
868
886
|
"needs",
|
|
@@ -876,6 +894,7 @@
|
|
|
876
894
|
"off",
|
|
877
895
|
"one",
|
|
878
896
|
"only",
|
|
897
|
+
"output",
|
|
879
898
|
"own",
|
|
880
899
|
"passes",
|
|
881
900
|
"path",
|
|
@@ -885,21 +904,23 @@
|
|
|
885
904
|
"practice",
|
|
886
905
|
"pre",
|
|
887
906
|
"precondition",
|
|
888
|
-
"print",
|
|
889
907
|
"produce",
|
|
890
908
|
"program",
|
|
891
909
|
"proof",
|
|
892
910
|
"proofs",
|
|
893
911
|
"property",
|
|
894
912
|
"protects",
|
|
913
|
+
"provably",
|
|
895
914
|
"prove",
|
|
896
915
|
"proved",
|
|
897
916
|
"prover",
|
|
898
917
|
"rather",
|
|
918
|
+
"reachable",
|
|
899
919
|
"reads",
|
|
900
920
|
"refinement",
|
|
901
921
|
"related",
|
|
902
922
|
"replaces",
|
|
923
|
+
"reports",
|
|
903
924
|
"reproduce",
|
|
904
925
|
"requires",
|
|
905
926
|
"resists",
|
|
@@ -914,13 +935,14 @@
|
|
|
914
935
|
"same",
|
|
915
936
|
"sample",
|
|
916
937
|
"say",
|
|
938
|
+
"says",
|
|
917
939
|
"shapes",
|
|
918
940
|
"ship",
|
|
941
|
+
"should",
|
|
919
942
|
"signal",
|
|
920
943
|
"signature",
|
|
921
944
|
"smaller",
|
|
922
|
-
"
|
|
923
|
-
"some",
|
|
945
|
+
"solver",
|
|
924
946
|
"specification",
|
|
925
947
|
"specified",
|
|
926
948
|
"splitting",
|
|
@@ -945,9 +967,11 @@
|
|
|
945
967
|
"through",
|
|
946
968
|
"time",
|
|
947
969
|
"today",
|
|
970
|
+
"treated",
|
|
948
971
|
"trivial",
|
|
949
972
|
"trivially",
|
|
950
973
|
"true",
|
|
974
|
+
"try",
|
|
951
975
|
"two",
|
|
952
976
|
"type",
|
|
953
977
|
"types",
|
|
@@ -969,13 +993,15 @@
|
|
|
969
993
|
"whether",
|
|
970
994
|
"which",
|
|
971
995
|
"why",
|
|
996
|
+
"will",
|
|
972
997
|
"with",
|
|
973
998
|
"withdraw",
|
|
974
|
-
"
|
|
999
|
+
"witness",
|
|
975
1000
|
"writes",
|
|
976
1001
|
"written",
|
|
977
1002
|
"wrong",
|
|
978
|
-
"wrote"
|
|
1003
|
+
"wrote",
|
|
1004
|
+
"you"
|
|
979
1005
|
],
|
|
980
1006
|
"url_path": "sensors/contract-refinement-types/"
|
|
981
1007
|
},
|
|
@@ -1292,7 +1318,8 @@
|
|
|
1292
1318
|
"SO-012",
|
|
1293
1319
|
"SO-013",
|
|
1294
1320
|
"SO-001",
|
|
1295
|
-
"SO-001e"
|
|
1321
|
+
"SO-001e",
|
|
1322
|
+
"SO-017"
|
|
1296
1323
|
],
|
|
1297
1324
|
"see_also_pages": [],
|
|
1298
1325
|
"slug": "model-checking",
|
|
@@ -3199,8 +3226,8 @@
|
|
|
3199
3226
|
"url_path": "sensors/contract-tests/"
|
|
3200
3227
|
},
|
|
3201
3228
|
{
|
|
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“this code executed.” A behavioral assertion says “this code produced the\n<em>right result</em>.” 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/
|
|
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.
|
|
3229
|
+
"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“this code executed.” A behavioral assertion says “this code produced the\n<em>right result</em>.” 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="gold") == 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> “1 failed, 213 passed”\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 “I will fix\n it tomorrow” 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>",
|
|
3230
|
+
"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
3231
|
"family": "behavioral",
|
|
3205
3232
|
"frontmatter": {
|
|
3206
3233
|
"actionability": "guiding",
|
|
@@ -3275,7 +3302,8 @@
|
|
|
3275
3302
|
"see_also_ids": [
|
|
3276
3303
|
"SO-002c",
|
|
3277
3304
|
"SO-002d",
|
|
3278
|
-
"SO-003"
|
|
3305
|
+
"SO-003",
|
|
3306
|
+
"SO-005e"
|
|
3279
3307
|
],
|
|
3280
3308
|
"see_also_pages": [],
|
|
3281
3309
|
"slug": "example-based-tests",
|
|
@@ -3431,6 +3459,7 @@
|
|
|
3431
3459
|
"practice",
|
|
3432
3460
|
"price_with_discount",
|
|
3433
3461
|
"produced",
|
|
3462
|
+
"property",
|
|
3434
3463
|
"question",
|
|
3435
3464
|
"ratio",
|
|
3436
3465
|
"read",
|
|
@@ -4483,7 +4512,8 @@
|
|
|
4483
4512
|
"see_also_ids": [
|
|
4484
4513
|
"SO-006c",
|
|
4485
4514
|
"SO-007",
|
|
4486
|
-
"SO-012b"
|
|
4515
|
+
"SO-012b",
|
|
4516
|
+
"SO-006e"
|
|
4487
4517
|
],
|
|
4488
4518
|
"see_also_pages": [],
|
|
4489
4519
|
"slug": "synthetic-monitoring",
|
|
@@ -6045,6 +6075,8 @@
|
|
|
6045
6075
|
],
|
|
6046
6076
|
"see_also_ids": [
|
|
6047
6077
|
"SO-003b",
|
|
6078
|
+
"SO-005e",
|
|
6079
|
+
"SO-012d",
|
|
6048
6080
|
"SO-005",
|
|
6049
6081
|
"SO-005b"
|
|
6050
6082
|
],
|
|
@@ -7810,8 +7842,8 @@
|
|
|
7810
7842
|
"url_path": "sensors/runtime-invariants/"
|
|
7811
7843
|
},
|
|
7812
7844
|
{
|
|
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
|
|
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:
|
|
7845
|
+
"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 >= 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’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>>= 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’s\n<code>--counter-examples</code> is flagged experimental, and Dafny’s\n<code>--extract-counterexample</code> ships with the warning that it “cannot\nguarantee that the counterexample it reports provably violates the\nassertion”, and should be “treated as a hint”. 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 “which of my two claims\nis false?” not “how do I make the message stop.”</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 “the invariant holds” and “the invariant is the one\nthe business needs” is exactly what <a class=\"body-link\" href=\"/sensors/business-invariants/\">business invariants</a>\nmeasure from the other direction.</p>",
|
|
7846
|
+
"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
7847
|
"family": "invariants",
|
|
7816
7848
|
"frontmatter": {
|
|
7817
7849
|
"actionability": "blocking",
|
|
@@ -7852,6 +7884,11 @@
|
|
|
7852
7884
|
"title": "Liquid Haskell",
|
|
7853
7885
|
"url": "https://ucsd-progsys.github.io/liquidhaskell/"
|
|
7854
7886
|
},
|
|
7887
|
+
{
|
|
7888
|
+
"kind": "other",
|
|
7889
|
+
"title": "Dafny user guide, verification debugging",
|
|
7890
|
+
"url": "https://dafny.org/latest/DafnyRef/DafnyRef#sec-counterexamples"
|
|
7891
|
+
},
|
|
7855
7892
|
{
|
|
7856
7893
|
"description": "Static analysis and verification for C",
|
|
7857
7894
|
"kind": "tool",
|
|
@@ -7874,20 +7911,22 @@
|
|
|
7874
7911
|
"see_also_ids": [
|
|
7875
7912
|
"SO-004",
|
|
7876
7913
|
"SO-012",
|
|
7877
|
-
"SO-001d"
|
|
7914
|
+
"SO-001d",
|
|
7915
|
+
"SO-017"
|
|
7878
7916
|
],
|
|
7879
7917
|
"see_also_pages": [],
|
|
7880
7918
|
"slug": "statically-checked-invariants",
|
|
7881
7919
|
"title": "Statically Checked Invariants",
|
|
7882
7920
|
"tokens": [
|
|
7883
7921
|
"013",
|
|
7884
|
-
"after",
|
|
7885
7922
|
"against",
|
|
7886
7923
|
"all",
|
|
7887
7924
|
"and",
|
|
7888
7925
|
"annotations",
|
|
7889
7926
|
"are",
|
|
7890
7927
|
"arrives",
|
|
7928
|
+
"assertion",
|
|
7929
|
+
"backed",
|
|
7891
7930
|
"because",
|
|
7892
7931
|
"between",
|
|
7893
7932
|
"both",
|
|
@@ -7900,48 +7939,72 @@
|
|
|
7900
7939
|
"carries",
|
|
7901
7940
|
"checked",
|
|
7902
7941
|
"checker",
|
|
7942
|
+
"checkers",
|
|
7943
|
+
"claimed",
|
|
7903
7944
|
"claims",
|
|
7904
7945
|
"clauses",
|
|
7905
7946
|
"code",
|
|
7906
7947
|
"compiler",
|
|
7907
7948
|
"confidence",
|
|
7949
|
+
"constraint",
|
|
7950
|
+
"context",
|
|
7908
7951
|
"could",
|
|
7909
7952
|
"counter",
|
|
7953
|
+
"counterexample",
|
|
7910
7954
|
"creates",
|
|
7911
7955
|
"dafny",
|
|
7912
7956
|
"data",
|
|
7913
7957
|
"database",
|
|
7958
|
+
"demanded",
|
|
7914
7959
|
"demote",
|
|
7915
7960
|
"dequeue",
|
|
7916
7961
|
"detect",
|
|
7917
7962
|
"direction",
|
|
7918
7963
|
"discharge",
|
|
7919
7964
|
"down",
|
|
7965
|
+
"each",
|
|
7920
7966
|
"either",
|
|
7967
|
+
"elems",
|
|
7921
7968
|
"emptiness",
|
|
7922
7969
|
"error",
|
|
7970
|
+
"establishes",
|
|
7923
7971
|
"every",
|
|
7924
7972
|
"exactly",
|
|
7925
7973
|
"example",
|
|
7974
|
+
"examples",
|
|
7926
7975
|
"execution",
|
|
7927
7976
|
"executions",
|
|
7977
|
+
"experimental",
|
|
7978
|
+
"extract",
|
|
7928
7979
|
"fail",
|
|
7980
|
+
"failing",
|
|
7929
7981
|
"fails",
|
|
7930
7982
|
"false",
|
|
7931
|
-
"
|
|
7983
|
+
"feature",
|
|
7984
|
+
"finding",
|
|
7932
7985
|
"first",
|
|
7933
7986
|
"fix",
|
|
7987
|
+
"flagged",
|
|
7934
7988
|
"for",
|
|
7935
7989
|
"frama",
|
|
7936
7990
|
"from",
|
|
7937
7991
|
"gap",
|
|
7938
7992
|
"genuinely",
|
|
7993
|
+
"ghc",
|
|
7994
|
+
"give",
|
|
7995
|
+
"guarantee",
|
|
7996
|
+
"had",
|
|
7997
|
+
"haskell",
|
|
7939
7998
|
"head",
|
|
7940
7999
|
"helper",
|
|
8000
|
+
"hint",
|
|
7941
8001
|
"holds",
|
|
7942
8002
|
"how",
|
|
7943
|
-
"
|
|
8003
|
+
"infer",
|
|
8004
|
+
"inferred",
|
|
8005
|
+
"input",
|
|
7944
8006
|
"instead",
|
|
8007
|
+
"int",
|
|
7945
8008
|
"intermediate",
|
|
7946
8009
|
"invariant",
|
|
7947
8010
|
"invariants",
|
|
@@ -7960,26 +8023,32 @@
|
|
|
7960
8023
|
"message",
|
|
7961
8024
|
"minimal",
|
|
7962
8025
|
"mismatch",
|
|
7963
|
-
"
|
|
8026
|
+
"models",
|
|
7964
8027
|
"most",
|
|
7965
8028
|
"needs",
|
|
7966
8029
|
"never",
|
|
8030
|
+
"next",
|
|
7967
8031
|
"non",
|
|
7968
8032
|
"nonemptylist",
|
|
7969
8033
|
"not",
|
|
8034
|
+
"note",
|
|
8035
|
+
"nothing",
|
|
8036
|
+
"often",
|
|
7970
8037
|
"once",
|
|
7971
8038
|
"one",
|
|
7972
8039
|
"only",
|
|
7973
8040
|
"other",
|
|
7974
|
-
"
|
|
8041
|
+
"out",
|
|
7975
8042
|
"playbook",
|
|
7976
8043
|
"possible",
|
|
7977
8044
|
"practice",
|
|
8045
|
+
"prints",
|
|
7978
8046
|
"produces",
|
|
7979
8047
|
"program",
|
|
7980
8048
|
"proof",
|
|
7981
8049
|
"proofs",
|
|
7982
8050
|
"property",
|
|
8051
|
+
"provably",
|
|
7983
8052
|
"proved",
|
|
7984
8053
|
"prover",
|
|
7985
8054
|
"question",
|
|
@@ -7987,52 +8056,72 @@
|
|
|
7987
8056
|
"raised",
|
|
7988
8057
|
"read",
|
|
7989
8058
|
"reading",
|
|
8059
|
+
"refinement",
|
|
8060
|
+
"refinements",
|
|
7990
8061
|
"refuses",
|
|
8062
|
+
"reports",
|
|
8063
|
+
"required",
|
|
7991
8064
|
"resists",
|
|
7992
8065
|
"response",
|
|
7993
8066
|
"right",
|
|
8067
|
+
"rules",
|
|
7994
8068
|
"run",
|
|
7995
8069
|
"runtime",
|
|
7996
8070
|
"same",
|
|
7997
|
-
"says",
|
|
7998
8071
|
"settles",
|
|
8072
|
+
"ships",
|
|
7999
8073
|
"should",
|
|
8074
|
+
"side",
|
|
8075
|
+
"signature",
|
|
8000
8076
|
"size",
|
|
8001
|
-
"
|
|
8077
|
+
"smt",
|
|
8078
|
+
"solver",
|
|
8002
8079
|
"specs",
|
|
8003
8080
|
"src",
|
|
8004
|
-
"state",
|
|
8005
8081
|
"statement",
|
|
8006
8082
|
"statically",
|
|
8007
8083
|
"step",
|
|
8008
8084
|
"still",
|
|
8009
8085
|
"stop",
|
|
8086
|
+
"subtype",
|
|
8010
8087
|
"supply",
|
|
8011
8088
|
"system",
|
|
8089
|
+
"tail",
|
|
8012
8090
|
"than",
|
|
8013
8091
|
"that",
|
|
8014
8092
|
"the",
|
|
8093
|
+
"them",
|
|
8094
|
+
"there",
|
|
8015
8095
|
"these",
|
|
8016
8096
|
"through",
|
|
8017
8097
|
"time",
|
|
8098
|
+
"treated",
|
|
8018
8099
|
"triage",
|
|
8019
8100
|
"two",
|
|
8020
8101
|
"type",
|
|
8102
|
+
"types",
|
|
8103
|
+
"unlike",
|
|
8021
8104
|
"unprovable",
|
|
8022
|
-
"
|
|
8105
|
+
"unreachable",
|
|
8106
|
+
"unsafe",
|
|
8107
|
+
"unsatisfiable",
|
|
8023
8108
|
"verdict",
|
|
8024
8109
|
"violate",
|
|
8025
8110
|
"violates",
|
|
8026
|
-
"
|
|
8111
|
+
"warning",
|
|
8112
|
+
"was",
|
|
8027
8113
|
"way",
|
|
8114
|
+
"weak",
|
|
8028
8115
|
"were",
|
|
8029
8116
|
"what",
|
|
8030
8117
|
"when",
|
|
8031
8118
|
"where",
|
|
8032
|
-
"whether",
|
|
8033
8119
|
"which",
|
|
8034
8120
|
"whose",
|
|
8121
|
+
"will",
|
|
8122
|
+
"with",
|
|
8035
8123
|
"without",
|
|
8124
|
+
"witness",
|
|
8036
8125
|
"witnesses",
|
|
8037
8126
|
"worse",
|
|
8038
8127
|
"written",
|
|
@@ -8481,7 +8570,8 @@
|
|
|
8481
8570
|
],
|
|
8482
8571
|
"see_also_ids": [
|
|
8483
8572
|
"SO-004",
|
|
8484
|
-
"SO-005"
|
|
8573
|
+
"SO-005",
|
|
8574
|
+
"SO-014b"
|
|
8485
8575
|
],
|
|
8486
8576
|
"see_also_pages": [],
|
|
8487
8577
|
"slug": "fault-injection",
|
|
@@ -8748,7 +8838,7 @@
|
|
|
8748
8838
|
"url_path": "sensors/fault-injection/"
|
|
8749
8839
|
},
|
|
8750
8840
|
{
|
|
8751
|
-
"body_html": "<p>What happens on inputs humans didn’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: “does the implementation obey generalized\nproperties across huge input spaces?” Fuzzing asks a simpler question: “does\nit crash?” 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’t.</p>\n</blockquote>\n<h2>In practice</h2>\n<p>A reading is a crash report: the sanitizer’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> “Nobody would send that input” 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/
|
|
8841
|
+
"body_html": "<p>What happens on inputs humans didn’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: “does the implementation obey generalized\nproperties across huge input spaces?” Fuzzing asks a simpler question: “does\nit crash?” 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’t.</p>\n</blockquote>\n<h2>In practice</h2>\n<p>A reading is a crash report: the sanitizer’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> “Nobody would send that input” 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
8842
|
"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
8843
|
"family": "adversarial",
|
|
8754
8844
|
"frontmatter": {
|
|
@@ -8827,7 +8917,8 @@
|
|
|
8827
8917
|
],
|
|
8828
8918
|
"see_also_ids": [
|
|
8829
8919
|
"SO-005b",
|
|
8830
|
-
"SO-003"
|
|
8920
|
+
"SO-003",
|
|
8921
|
+
"SO-005e"
|
|
8831
8922
|
],
|
|
8832
8923
|
"see_also_pages": [],
|
|
8833
8924
|
"slug": "fuzzing",
|
|
@@ -9169,7 +9260,8 @@
|
|
|
9169
9260
|
"see_also_ids": [
|
|
9170
9261
|
"SO-005c",
|
|
9171
9262
|
"SO-006c",
|
|
9172
|
-
"SO-004b"
|
|
9263
|
+
"SO-004b",
|
|
9264
|
+
"SO-005d"
|
|
9173
9265
|
],
|
|
9174
9266
|
"see_also_pages": [],
|
|
9175
9267
|
"slug": "live-chaos-experiments",
|
|
@@ -9406,8 +9498,8 @@
|
|
|
9406
9498
|
"url_path": "sensors/live-chaos-experiments/"
|
|
9407
9499
|
},
|
|
9408
9500
|
{
|
|
9409
|
-
"body_html": "<p>You don’t know the answer, but you know <em>how the answer should change</em>. This\nis a particularly beautiful sensor because you don’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) <= abs(x) + abs(y) # triangle inequality\nmax(x, y) == max(y, x) # commutativity\n</code></pre>\n<p>You don’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’t, something is wrong —\nand you’ve found a bug without ever needing to compute the correct answer.</p>\n<blockquote>\n<p>Metamorphic testing is the answer to “how do you test something when you\ncan’t compute the expected output?” 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,
|
|
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,
|
|
9501
|
+
"body_html": "<p>You don’t know the answer, but you know <em>how the answer should change</em>. This\nis a particularly beautiful sensor because you don’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) <= abs(x) + abs(y) # triangle inequality\nmax(x, y) == max(y, x) # commutativity\n</code></pre>\n<p>You don’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’t, something is wrong —\nand you’ve found a bug without ever needing to compute the correct answer.</p>\n<blockquote>\n<p>Metamorphic testing is the answer to “how do you test something when you\ncan’t compute the expected output?” 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> 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 “same length,” 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’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’s no function to check relations on.</p>",
|
|
9502
|
+
"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
9503
|
"family": "adversarial",
|
|
9412
9504
|
"frontmatter": {
|
|
9413
9505
|
"actionability": "guiding",
|
|
@@ -9477,27 +9569,30 @@
|
|
|
9477
9569
|
],
|
|
9478
9570
|
"see_also_ids": [
|
|
9479
9571
|
"SO-005",
|
|
9480
|
-
"SO-003"
|
|
9572
|
+
"SO-003",
|
|
9573
|
+
"SO-005e"
|
|
9481
9574
|
],
|
|
9482
9575
|
"see_also_pages": [],
|
|
9483
9576
|
"slug": "metamorphic-testing",
|
|
9484
9577
|
"title": "Metamorphic Testing",
|
|
9485
9578
|
"tokens": [
|
|
9486
9579
|
"005b",
|
|
9487
|
-
"______________",
|
|
9488
9580
|
"about",
|
|
9489
9581
|
"abs",
|
|
9490
9582
|
"absent",
|
|
9491
9583
|
"adding",
|
|
9492
9584
|
"adversarial",
|
|
9585
|
+
"against",
|
|
9586
|
+
"agree",
|
|
9493
9587
|
"all",
|
|
9588
|
+
"already",
|
|
9494
9589
|
"also",
|
|
9495
9590
|
"and",
|
|
9496
9591
|
"answer",
|
|
9497
9592
|
"appearing",
|
|
9498
9593
|
"are",
|
|
9499
9594
|
"assert",
|
|
9500
|
-
"
|
|
9595
|
+
"assume",
|
|
9501
9596
|
"assumed",
|
|
9502
9597
|
"assumption",
|
|
9503
9598
|
"away",
|
|
@@ -9508,9 +9603,13 @@
|
|
|
9508
9603
|
"before",
|
|
9509
9604
|
"behavior",
|
|
9510
9605
|
"being",
|
|
9606
|
+
"below",
|
|
9511
9607
|
"between",
|
|
9608
|
+
"big",
|
|
9609
|
+
"blocked",
|
|
9512
9610
|
"breaks",
|
|
9513
9611
|
"broke",
|
|
9612
|
+
"bubble",
|
|
9514
9613
|
"bug",
|
|
9515
9614
|
"but",
|
|
9516
9615
|
"campaign",
|
|
@@ -9520,12 +9619,14 @@
|
|
|
9520
9619
|
"change",
|
|
9521
9620
|
"changes",
|
|
9522
9621
|
"check",
|
|
9622
|
+
"checkable",
|
|
9523
9623
|
"checks",
|
|
9524
9624
|
"claims",
|
|
9525
9625
|
"climbs",
|
|
9526
9626
|
"code",
|
|
9527
9627
|
"commutativity",
|
|
9528
9628
|
"compute",
|
|
9629
|
+
"console",
|
|
9529
9630
|
"converts",
|
|
9530
9631
|
"correct",
|
|
9531
9632
|
"corrected",
|
|
@@ -9535,15 +9636,19 @@
|
|
|
9535
9636
|
"debugging",
|
|
9536
9637
|
"decide",
|
|
9537
9638
|
"decrypt",
|
|
9639
|
+
"def",
|
|
9538
9640
|
"deleted",
|
|
9539
9641
|
"deserves",
|
|
9540
9642
|
"detect",
|
|
9541
9643
|
"detects",
|
|
9644
|
+
"deterministic",
|
|
9645
|
+
"diff",
|
|
9542
9646
|
"disappear",
|
|
9543
9647
|
"discarded",
|
|
9544
9648
|
"discards",
|
|
9545
9649
|
"doesn",
|
|
9546
9650
|
"don",
|
|
9651
|
+
"else",
|
|
9547
9652
|
"encrypt",
|
|
9548
9653
|
"equal",
|
|
9549
9654
|
"equality",
|
|
@@ -9553,12 +9658,13 @@
|
|
|
9553
9658
|
"expected",
|
|
9554
9659
|
"explores",
|
|
9555
9660
|
"fail",
|
|
9661
|
+
"failed",
|
|
9556
9662
|
"failing",
|
|
9557
9663
|
"fails",
|
|
9558
9664
|
"failure",
|
|
9559
9665
|
"failures",
|
|
9560
|
-
"falsifying",
|
|
9561
9666
|
"feature",
|
|
9667
|
+
"filter",
|
|
9562
9668
|
"filters",
|
|
9563
9669
|
"finding",
|
|
9564
9670
|
"fired",
|
|
@@ -9569,12 +9675,15 @@
|
|
|
9569
9675
|
"found",
|
|
9570
9676
|
"free",
|
|
9571
9677
|
"from",
|
|
9678
|
+
"full",
|
|
9572
9679
|
"function",
|
|
9573
9680
|
"further",
|
|
9574
9681
|
"fuzzing",
|
|
9575
9682
|
"gamed",
|
|
9576
9683
|
"generated",
|
|
9684
|
+
"get",
|
|
9577
9685
|
"gets",
|
|
9686
|
+
"given",
|
|
9578
9687
|
"glance",
|
|
9579
9688
|
"guards",
|
|
9580
9689
|
"hand",
|
|
@@ -9582,11 +9691,16 @@
|
|
|
9582
9691
|
"have",
|
|
9583
9692
|
"how",
|
|
9584
9693
|
"human",
|
|
9694
|
+
"hypothesis",
|
|
9585
9695
|
"idempotence",
|
|
9586
9696
|
"implementation",
|
|
9587
9697
|
"independence",
|
|
9698
|
+
"index",
|
|
9588
9699
|
"inequality",
|
|
9589
9700
|
"input",
|
|
9701
|
+
"inputs",
|
|
9702
|
+
"instead",
|
|
9703
|
+
"integers",
|
|
9590
9704
|
"into",
|
|
9591
9705
|
"invariant",
|
|
9592
9706
|
"its",
|
|
@@ -9594,10 +9708,14 @@
|
|
|
9594
9708
|
"keeps",
|
|
9595
9709
|
"kind",
|
|
9596
9710
|
"know",
|
|
9711
|
+
"knowing",
|
|
9597
9712
|
"known",
|
|
9598
9713
|
"label",
|
|
9714
|
+
"larger",
|
|
9599
9715
|
"length",
|
|
9600
9716
|
"less",
|
|
9717
|
+
"list",
|
|
9718
|
+
"lists",
|
|
9601
9719
|
"look",
|
|
9602
9720
|
"make",
|
|
9603
9721
|
"makes",
|
|
@@ -9606,10 +9724,12 @@
|
|
|
9606
9724
|
"meta",
|
|
9607
9725
|
"metamorphic",
|
|
9608
9726
|
"milliseconds",
|
|
9609
|
-
"minimal",
|
|
9610
9727
|
"minimized",
|
|
9611
9728
|
"missing",
|
|
9729
|
+
"more",
|
|
9730
|
+
"must",
|
|
9612
9731
|
"mutation",
|
|
9732
|
+
"my_sort",
|
|
9613
9733
|
"name",
|
|
9614
9734
|
"named",
|
|
9615
9735
|
"narrowing",
|
|
@@ -9629,29 +9749,33 @@
|
|
|
9629
9749
|
"only",
|
|
9630
9750
|
"oracle",
|
|
9631
9751
|
"order",
|
|
9632
|
-
"original",
|
|
9633
9752
|
"output",
|
|
9634
9753
|
"outputs",
|
|
9635
9754
|
"overclaims",
|
|
9636
9755
|
"particularly",
|
|
9756
|
+
"pass",
|
|
9637
9757
|
"passes",
|
|
9638
9758
|
"perturbs",
|
|
9639
9759
|
"pin",
|
|
9640
9760
|
"playbook",
|
|
9641
9761
|
"practice",
|
|
9642
|
-
"
|
|
9762
|
+
"printed",
|
|
9643
9763
|
"rarely",
|
|
9644
9764
|
"ratio",
|
|
9645
9765
|
"reach",
|
|
9646
9766
|
"reading",
|
|
9647
9767
|
"reason",
|
|
9768
|
+
"reduced",
|
|
9648
9769
|
"regression",
|
|
9649
9770
|
"relation",
|
|
9650
9771
|
"relations",
|
|
9651
9772
|
"replacing",
|
|
9773
|
+
"reports",
|
|
9652
9774
|
"response",
|
|
9653
9775
|
"retrying",
|
|
9654
9776
|
"returns",
|
|
9777
|
+
"reverse",
|
|
9778
|
+
"reversed",
|
|
9655
9779
|
"round",
|
|
9656
9780
|
"run",
|
|
9657
9781
|
"runs",
|
|
@@ -9661,14 +9785,16 @@
|
|
|
9661
9785
|
"sensor",
|
|
9662
9786
|
"should",
|
|
9663
9787
|
"shrink",
|
|
9788
|
+
"shrinker",
|
|
9664
9789
|
"shrunk",
|
|
9665
9790
|
"shuffle",
|
|
9666
9791
|
"side",
|
|
9667
9792
|
"sign",
|
|
9668
9793
|
"signal",
|
|
9669
|
-
"smallest",
|
|
9670
9794
|
"something",
|
|
9671
9795
|
"sort",
|
|
9796
|
+
"sorted",
|
|
9797
|
+
"sorting",
|
|
9672
9798
|
"space",
|
|
9673
9799
|
"specific",
|
|
9674
9800
|
"statement",
|
|
@@ -9679,7 +9805,7 @@
|
|
|
9679
9805
|
"system",
|
|
9680
9806
|
"tells",
|
|
9681
9807
|
"test",
|
|
9682
|
-
"
|
|
9808
|
+
"test_sort_is_order_independent",
|
|
9683
9809
|
"testing",
|
|
9684
9810
|
"that",
|
|
9685
9811
|
"the",
|
|
@@ -9688,10 +9814,12 @@
|
|
|
9688
9814
|
"this",
|
|
9689
9815
|
"thousand",
|
|
9690
9816
|
"three",
|
|
9817
|
+
"too",
|
|
9691
9818
|
"triangle",
|
|
9692
9819
|
"trip",
|
|
9693
9820
|
"try",
|
|
9694
9821
|
"until",
|
|
9822
|
+
"use",
|
|
9695
9823
|
"usually",
|
|
9696
9824
|
"vacuous",
|
|
9697
9825
|
"verify",
|
|
@@ -9699,6 +9827,7 @@
|
|
|
9699
9827
|
"violated",
|
|
9700
9828
|
"violation",
|
|
9701
9829
|
"violations",
|
|
9830
|
+
"was",
|
|
9702
9831
|
"way",
|
|
9703
9832
|
"weaken",
|
|
9704
9833
|
"well",
|
|
@@ -9717,8 +9846,8 @@
|
|
|
9717
9846
|
"url_path": "sensors/metamorphic-testing/"
|
|
9718
9847
|
},
|
|
9719
9848
|
{
|
|
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’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
|
|
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
|
|
9849
|
+
"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’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> 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’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 “same length,” 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’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’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’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’t write down a property.</p>",
|
|
9850
|
+
"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
9851
|
"family": "adversarial",
|
|
9723
9852
|
"frontmatter": {
|
|
9724
9853
|
"actionability": "guiding",
|
|
@@ -9790,7 +9919,8 @@
|
|
|
9790
9919
|
"see_also_ids": [
|
|
9791
9920
|
"SO-005",
|
|
9792
9921
|
"SO-005b",
|
|
9793
|
-
"SO-003"
|
|
9922
|
+
"SO-003",
|
|
9923
|
+
"SO-002b"
|
|
9794
9924
|
],
|
|
9795
9925
|
"see_also_pages": [],
|
|
9796
9926
|
"slug": "property-based-testing",
|
|
@@ -9803,24 +9933,28 @@
|
|
|
9803
9933
|
"adding",
|
|
9804
9934
|
"adversarial",
|
|
9805
9935
|
"against",
|
|
9936
|
+
"already",
|
|
9806
9937
|
"also",
|
|
9807
9938
|
"and",
|
|
9808
9939
|
"answer",
|
|
9940
|
+
"any",
|
|
9809
9941
|
"appearing",
|
|
9810
9942
|
"are",
|
|
9811
9943
|
"assert",
|
|
9812
|
-
"assertionerror",
|
|
9813
9944
|
"assume",
|
|
9814
9945
|
"assumed",
|
|
9815
9946
|
"assumes",
|
|
9816
9947
|
"away",
|
|
9948
|
+
"banner",
|
|
9817
9949
|
"based",
|
|
9818
9950
|
"before",
|
|
9819
9951
|
"behavior",
|
|
9952
|
+
"behind",
|
|
9820
9953
|
"being",
|
|
9821
9954
|
"between",
|
|
9822
9955
|
"break",
|
|
9823
9956
|
"breaks",
|
|
9957
|
+
"broke",
|
|
9824
9958
|
"bug",
|
|
9825
9959
|
"calls",
|
|
9826
9960
|
"campaign",
|
|
@@ -9847,10 +9981,13 @@
|
|
|
9847
9981
|
"decide",
|
|
9848
9982
|
"def",
|
|
9849
9983
|
"deleted",
|
|
9984
|
+
"dependent",
|
|
9850
9985
|
"detect",
|
|
9851
9986
|
"detecting",
|
|
9852
9987
|
"didn",
|
|
9988
|
+
"diff",
|
|
9853
9989
|
"differs",
|
|
9990
|
+
"digits",
|
|
9854
9991
|
"directly",
|
|
9855
9992
|
"discard",
|
|
9856
9993
|
"discarded",
|
|
@@ -9858,11 +9995,13 @@
|
|
|
9858
9995
|
"distinction",
|
|
9859
9996
|
"don",
|
|
9860
9997
|
"down",
|
|
9998
|
+
"elements",
|
|
9861
9999
|
"equality",
|
|
9862
10000
|
"even",
|
|
9863
10001
|
"every",
|
|
9864
10002
|
"example",
|
|
9865
10003
|
"examples",
|
|
10004
|
+
"failed",
|
|
9866
10005
|
"failing",
|
|
9867
10006
|
"fails",
|
|
9868
10007
|
"failure",
|
|
@@ -9875,6 +10014,7 @@
|
|
|
9875
10014
|
"find",
|
|
9876
10015
|
"finding",
|
|
9877
10016
|
"finds",
|
|
10017
|
+
"first",
|
|
9878
10018
|
"fix",
|
|
9879
10019
|
"flaky",
|
|
9880
10020
|
"for",
|
|
@@ -9888,65 +10028,94 @@
|
|
|
9888
10028
|
"generated",
|
|
9889
10029
|
"generates",
|
|
9890
10030
|
"generator",
|
|
10031
|
+
"get",
|
|
9891
10032
|
"gets",
|
|
9892
10033
|
"given",
|
|
10034
|
+
"grep",
|
|
9893
10035
|
"guards",
|
|
9894
10036
|
"hand",
|
|
9895
10037
|
"has",
|
|
10038
|
+
"here",
|
|
10039
|
+
"hides",
|
|
9896
10040
|
"hold",
|
|
9897
10041
|
"how",
|
|
9898
10042
|
"human",
|
|
10043
|
+
"hypothesis",
|
|
9899
10044
|
"idempotence",
|
|
9900
10045
|
"implementation",
|
|
10046
|
+
"index",
|
|
9901
10047
|
"input",
|
|
9902
10048
|
"inputs",
|
|
9903
10049
|
"integers",
|
|
9904
10050
|
"into",
|
|
10051
|
+
"invisible",
|
|
9905
10052
|
"its",
|
|
9906
10053
|
"keeps",
|
|
10054
|
+
"key",
|
|
9907
10055
|
"know",
|
|
9908
10056
|
"label",
|
|
10057
|
+
"larger",
|
|
9909
10058
|
"length",
|
|
9910
10059
|
"less",
|
|
10060
|
+
"lexicographically",
|
|
10061
|
+
"line",
|
|
9911
10062
|
"lists",
|
|
9912
10063
|
"makes",
|
|
9913
10064
|
"meta",
|
|
9914
10065
|
"metamorphic",
|
|
9915
10066
|
"milliseconds",
|
|
9916
10067
|
"minimized",
|
|
10068
|
+
"minus",
|
|
9917
10069
|
"missing",
|
|
10070
|
+
"more",
|
|
9918
10071
|
"most",
|
|
9919
10072
|
"mostly",
|
|
10073
|
+
"my_sort",
|
|
9920
10074
|
"name",
|
|
10075
|
+
"negative",
|
|
9921
10076
|
"neighborhood",
|
|
10077
|
+
"never",
|
|
9922
10078
|
"noise",
|
|
10079
|
+
"non",
|
|
9923
10080
|
"not",
|
|
9924
10081
|
"nothing",
|
|
10082
|
+
"numbers",
|
|
9925
10083
|
"obvious",
|
|
9926
10084
|
"occasionally",
|
|
10085
|
+
"older",
|
|
9927
10086
|
"one",
|
|
9928
10087
|
"ones",
|
|
9929
10088
|
"only",
|
|
10089
|
+
"orders",
|
|
10090
|
+
"output",
|
|
9930
10091
|
"outputs",
|
|
9931
10092
|
"outright",
|
|
9932
10093
|
"overclaims",
|
|
10094
|
+
"parameter",
|
|
9933
10095
|
"part",
|
|
9934
10096
|
"passes",
|
|
9935
10097
|
"pin",
|
|
9936
10098
|
"playbook",
|
|
9937
10099
|
"practice",
|
|
10100
|
+
"printed",
|
|
10101
|
+
"prints",
|
|
9938
10102
|
"properties",
|
|
9939
10103
|
"property",
|
|
9940
|
-
"random",
|
|
9941
10104
|
"rarely",
|
|
9942
10105
|
"ratio",
|
|
9943
10106
|
"reach",
|
|
10107
|
+
"reading",
|
|
9944
10108
|
"reason",
|
|
10109
|
+
"reduced",
|
|
10110
|
+
"reduces",
|
|
10111
|
+
"reduction",
|
|
9945
10112
|
"regression",
|
|
9946
10113
|
"relation",
|
|
9947
10114
|
"relations",
|
|
10115
|
+
"releases",
|
|
9948
10116
|
"relies",
|
|
9949
10117
|
"replacing",
|
|
10118
|
+
"reports",
|
|
9950
10119
|
"response",
|
|
9951
10120
|
"retrying",
|
|
9952
10121
|
"round",
|
|
@@ -9961,19 +10130,23 @@
|
|
|
9961
10130
|
"shrunk",
|
|
9962
10131
|
"siblings",
|
|
9963
10132
|
"side",
|
|
10133
|
+
"sign",
|
|
9964
10134
|
"signal",
|
|
9965
|
-
"
|
|
10135
|
+
"single",
|
|
9966
10136
|
"sort",
|
|
10137
|
+
"sorted",
|
|
9967
10138
|
"space",
|
|
9968
10139
|
"state",
|
|
10140
|
+
"stated",
|
|
9969
10141
|
"statement",
|
|
9970
10142
|
"states",
|
|
9971
10143
|
"still",
|
|
9972
10144
|
"stop",
|
|
10145
|
+
"str",
|
|
9973
10146
|
"strangled",
|
|
9974
10147
|
"suites",
|
|
9975
10148
|
"test",
|
|
9976
|
-
"
|
|
10149
|
+
"test_sort_is_ordered",
|
|
9977
10150
|
"testing",
|
|
9978
10151
|
"that",
|
|
9979
10152
|
"the",
|
|
@@ -9983,13 +10156,15 @@
|
|
|
9983
10156
|
"tool",
|
|
9984
10157
|
"trip",
|
|
9985
10158
|
"trying",
|
|
9986
|
-
"turns",
|
|
9987
10159
|
"two",
|
|
9988
10160
|
"universal",
|
|
9989
10161
|
"unlike",
|
|
9990
10162
|
"until",
|
|
10163
|
+
"use",
|
|
9991
10164
|
"usually",
|
|
10165
|
+
"verifiable",
|
|
9992
10166
|
"verify",
|
|
10167
|
+
"version",
|
|
9993
10168
|
"violation",
|
|
9994
10169
|
"violations",
|
|
9995
10170
|
"weaken",
|
|
@@ -9999,6 +10174,7 @@
|
|
|
9999
10174
|
"whether",
|
|
10000
10175
|
"which",
|
|
10001
10176
|
"while",
|
|
10177
|
+
"whose",
|
|
10002
10178
|
"wide",
|
|
10003
10179
|
"with",
|
|
10004
10180
|
"work",
|
|
@@ -10010,8 +10186,8 @@
|
|
|
10010
10186
|
"url_path": "sensors/property-based-testing/"
|
|
10011
10187
|
},
|
|
10012
10188
|
{
|
|
10013
|
-
"body_html": "<p>Attacking the code before it runs. Taint tracking, dataflow analysis, and\npattern-based scanners (Semgrep, CodeQL) ask: “is there any path through\nthis program where an adversary’s input reaches a dangerous sink?” 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 = "SELECT * FROM items WHERE name = '%s'" % request.args["q"]\n taint: request.args["q"] -> cursor.execute(query)\n</code></pre>\n<p>The value is the path. The finding is not “this string looks\ndangerous” but “input from here reaches that sink through these\nlines,” and the reading takes minutes because the intermediate steps\nare printed.</p>\n<p>Severity is the scanner’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’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 “won’t fix”\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 “old findings are someone else’s problem.”</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 “our SAST.”</li>\n</ul>\n<h2>What it cannot detect</h2>\n<p>Vulnerabilities in the composition of services, in configuration, or in the\ndependencies’ runtime behavior — those belong to\n<a class=\"body-link\" href=\"/sensors/fault-injection/\">fault injection</a> and live
|
|
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.",
|
|
10189
|
+
"body_html": "<p>Attacking the code before it runs. Taint tracking, dataflow analysis, and\npattern-based scanners (Semgrep, CodeQL) ask: “is there any path through\nthis program where an adversary’s input reaches a dangerous sink?” 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 = "SELECT * FROM items WHERE name = '%s'" % request.args["q"]\n taint: request.args["q"] -> cursor.execute(query)\n</code></pre>\n<p>The value is the path. The finding is not “this string looks\ndangerous” but “input from here reaches that sink through these\nlines,” and the reading takes minutes because the intermediate steps\nare printed.</p>\n<p>Severity is the scanner’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’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 “won’t fix”\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 “old findings are someone else’s problem.”</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 “our SAST.”</li>\n</ul>\n<h2>What it cannot detect</h2>\n<p>Vulnerabilities in the composition of services, in configuration, or in the\ndependencies’ 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’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>",
|
|
10190
|
+
"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
10191
|
"family": "adversarial",
|
|
10016
10192
|
"frontmatter": {
|
|
10017
10193
|
"actionability": "blocking",
|
|
@@ -10079,7 +10255,8 @@
|
|
|
10079
10255
|
"see_also_ids": [
|
|
10080
10256
|
"SO-005",
|
|
10081
10257
|
"SO-005c",
|
|
10082
|
-
"SO-001c"
|
|
10258
|
+
"SO-001c",
|
|
10259
|
+
"SO-014b"
|
|
10083
10260
|
],
|
|
10084
10261
|
"see_also_pages": [],
|
|
10085
10262
|
"slug": "static-security-analysis",
|
|
@@ -11416,7 +11593,8 @@
|
|
|
11416
11593
|
"comprehension"
|
|
11417
11594
|
],
|
|
11418
11595
|
"see_also_ids": [
|
|
11419
|
-
"SO-004"
|
|
11596
|
+
"SO-004",
|
|
11597
|
+
"SO-006b"
|
|
11420
11598
|
],
|
|
11421
11599
|
"see_also_pages": [],
|
|
11422
11600
|
"slug": "observability-events",
|
|
@@ -11808,7 +11986,8 @@
|
|
|
11808
11986
|
"see_also_ids": [
|
|
11809
11987
|
"SO-006",
|
|
11810
11988
|
"SO-006b",
|
|
11811
|
-
"SO-006c"
|
|
11989
|
+
"SO-006c",
|
|
11990
|
+
"SO-006e"
|
|
11812
11991
|
],
|
|
11813
11992
|
"see_also_pages": [],
|
|
11814
11993
|
"slug": "resource-telemetry",
|
|
@@ -12173,7 +12352,8 @@
|
|
|
12173
12352
|
"SO-007",
|
|
12174
12353
|
"SO-007b",
|
|
12175
12354
|
"SO-015",
|
|
12176
|
-
"SO-009"
|
|
12355
|
+
"SO-009",
|
|
12356
|
+
"SO-012d"
|
|
12177
12357
|
],
|
|
12178
12358
|
"see_also_pages": [],
|
|
12179
12359
|
"slug": "ab-testing",
|
|
@@ -12840,7 +13020,8 @@
|
|
|
12840
13020
|
"SO-007b",
|
|
12841
13021
|
"SO-007c",
|
|
12842
13022
|
"SO-007d",
|
|
12843
|
-
"SO-005c"
|
|
13023
|
+
"SO-005c",
|
|
13024
|
+
"SO-007e"
|
|
12844
13025
|
],
|
|
12845
13026
|
"see_also_pages": [],
|
|
12846
13027
|
"slug": "canary-analysis",
|
|
@@ -13419,7 +13600,8 @@
|
|
|
13419
13600
|
"see_also_ids": [
|
|
13420
13601
|
"SO-007",
|
|
13421
13602
|
"SO-006c",
|
|
13422
|
-
"SO-012c"
|
|
13603
|
+
"SO-012c",
|
|
13604
|
+
"SO-007e"
|
|
13423
13605
|
],
|
|
13424
13606
|
"see_also_pages": [],
|
|
13425
13607
|
"slug": "feature-flag-exposure",
|
|
@@ -14336,7 +14518,8 @@
|
|
|
14336
14518
|
"see_also_ids": [
|
|
14337
14519
|
"SO-008",
|
|
14338
14520
|
"SO-008b",
|
|
14339
|
-
"SO-002"
|
|
14521
|
+
"SO-002",
|
|
14522
|
+
"SO-015c"
|
|
14340
14523
|
],
|
|
14341
14524
|
"see_also_pages": [],
|
|
14342
14525
|
"slug": "boundary-sensors",
|
|
@@ -14655,7 +14838,9 @@
|
|
|
14655
14838
|
"see_also_ids": [
|
|
14656
14839
|
"SO-008b",
|
|
14657
14840
|
"SO-008c",
|
|
14658
|
-
"SO-008d"
|
|
14841
|
+
"SO-008d",
|
|
14842
|
+
"SO-015c",
|
|
14843
|
+
"SO-014d"
|
|
14659
14844
|
],
|
|
14660
14845
|
"see_also_pages": [],
|
|
14661
14846
|
"slug": "dependency-graph",
|
|
@@ -15266,7 +15451,8 @@
|
|
|
15266
15451
|
"see_also_ids": [
|
|
15267
15452
|
"SO-008",
|
|
15268
15453
|
"SO-008b",
|
|
15269
|
-
"SO-008d"
|
|
15454
|
+
"SO-008d",
|
|
15455
|
+
"SO-012d"
|
|
15270
15456
|
],
|
|
15271
15457
|
"see_also_pages": [],
|
|
15272
15458
|
"slug": "hotspot-analysis",
|
|
@@ -15603,7 +15789,8 @@
|
|
|
15603
15789
|
"see_also_ids": [
|
|
15604
15790
|
"SO-008b",
|
|
15605
15791
|
"SO-006c",
|
|
15606
|
-
"SO-006"
|
|
15792
|
+
"SO-006",
|
|
15793
|
+
"SO-008"
|
|
15607
15794
|
],
|
|
15608
15795
|
"see_also_pages": [],
|
|
15609
15796
|
"slug": "live-service-graph",
|
|
@@ -16157,8 +16344,8 @@
|
|
|
16157
16344
|
"url_path": "sensors/change-coupling/"
|
|
16158
16345
|
},
|
|
16159
16346
|
{
|
|
16160
|
-
"body_html": "<p>
|
|
16161
|
-
"body_text": "
|
|
16347
|
+
"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 “does our recent past look like teams that ship\nsafely?” 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. “Time to restore service” 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’s own history records that the report calling it “the\nfifth metric” was inaccurate, and files it under operational rather than\ndelivery performance. A dashboard labelled “the DORA metrics” 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’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’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 “deploy” 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 “sudden”\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>",
|
|
16348
|
+
"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
16349
|
"family": "evolution",
|
|
16163
16350
|
"frontmatter": {
|
|
16164
16351
|
"actionability": "guiding",
|
|
@@ -16192,6 +16379,11 @@
|
|
|
16192
16379
|
"venue": "Puppet + DORA",
|
|
16193
16380
|
"year": 2017
|
|
16194
16381
|
},
|
|
16382
|
+
{
|
|
16383
|
+
"kind": "other",
|
|
16384
|
+
"title": "A history of DORA's software delivery metrics",
|
|
16385
|
+
"url": "https://dora.dev/insights/dora-metrics-history/"
|
|
16386
|
+
},
|
|
16195
16387
|
{
|
|
16196
16388
|
"kind": "tool",
|
|
16197
16389
|
"title": "DORA",
|
|
@@ -16211,7 +16403,7 @@
|
|
|
16211
16403
|
"url": "https://dora.dev"
|
|
16212
16404
|
},
|
|
16213
16405
|
{
|
|
16214
|
-
"description": "DORA's
|
|
16406
|
+
"description": "DORA's research assessment tool",
|
|
16215
16407
|
"kind": "tool",
|
|
16216
16408
|
"title": "DevOps Research Assessment",
|
|
16217
16409
|
"url": "https://dora.dev/research/"
|
|
@@ -16234,9 +16426,13 @@
|
|
|
16234
16426
|
"title": "DORA Metrics",
|
|
16235
16427
|
"tokens": [
|
|
16236
16428
|
"015b",
|
|
16429
|
+
"2021",
|
|
16237
16430
|
"2023",
|
|
16431
|
+
"2024",
|
|
16238
16432
|
"about",
|
|
16433
|
+
"across",
|
|
16239
16434
|
"acting",
|
|
16435
|
+
"actual",
|
|
16240
16436
|
"actually",
|
|
16241
16437
|
"add",
|
|
16242
16438
|
"added",
|
|
@@ -16246,6 +16442,7 @@
|
|
|
16246
16442
|
"almost",
|
|
16247
16443
|
"alone",
|
|
16248
16444
|
"alongside",
|
|
16445
|
+
"already",
|
|
16249
16446
|
"ambition",
|
|
16250
16447
|
"analysis",
|
|
16251
16448
|
"and",
|
|
@@ -16256,6 +16453,8 @@
|
|
|
16256
16453
|
"anything",
|
|
16257
16454
|
"approval",
|
|
16258
16455
|
"are",
|
|
16456
|
+
"arrived",
|
|
16457
|
+
"attach",
|
|
16259
16458
|
"attack",
|
|
16260
16459
|
"bad",
|
|
16261
16460
|
"baseline",
|
|
@@ -16265,10 +16464,12 @@
|
|
|
16265
16464
|
"before",
|
|
16266
16465
|
"build",
|
|
16267
16466
|
"buys",
|
|
16467
|
+
"calling",
|
|
16268
16468
|
"can",
|
|
16269
16469
|
"canary",
|
|
16270
16470
|
"cannot",
|
|
16271
16471
|
"cause",
|
|
16472
|
+
"caused",
|
|
16272
16473
|
"change",
|
|
16273
16474
|
"changed",
|
|
16274
16475
|
"changes",
|
|
@@ -16279,13 +16480,17 @@
|
|
|
16279
16480
|
"commits",
|
|
16280
16481
|
"compare",
|
|
16281
16482
|
"compared",
|
|
16483
|
+
"compares",
|
|
16484
|
+
"comparing",
|
|
16282
16485
|
"computed",
|
|
16283
16486
|
"confirm",
|
|
16284
16487
|
"correlate",
|
|
16285
16488
|
"correlation",
|
|
16286
16489
|
"count",
|
|
16287
16490
|
"counting",
|
|
16491
|
+
"counts",
|
|
16288
16492
|
"dashboard",
|
|
16493
|
+
"dated",
|
|
16289
16494
|
"decision",
|
|
16290
16495
|
"definition",
|
|
16291
16496
|
"definitional",
|
|
@@ -16293,11 +16498,12 @@
|
|
|
16293
16498
|
"degrades",
|
|
16294
16499
|
"deliberate",
|
|
16295
16500
|
"delivery",
|
|
16296
|
-
"denominator",
|
|
16297
16501
|
"depending",
|
|
16298
16502
|
"deploy",
|
|
16299
16503
|
"deployment",
|
|
16504
|
+
"deployments",
|
|
16300
16505
|
"deploys",
|
|
16506
|
+
"describes",
|
|
16301
16507
|
"detect",
|
|
16302
16508
|
"direction",
|
|
16303
16509
|
"distrust",
|
|
@@ -16309,26 +16515,30 @@
|
|
|
16309
16515
|
"early",
|
|
16310
16516
|
"easy",
|
|
16311
16517
|
"either",
|
|
16518
|
+
"entry",
|
|
16312
16519
|
"every",
|
|
16313
16520
|
"everyone",
|
|
16521
|
+
"exist",
|
|
16314
16522
|
"explain",
|
|
16523
|
+
"fail",
|
|
16524
|
+
"failed",
|
|
16315
16525
|
"failing",
|
|
16316
16526
|
"failure",
|
|
16317
16527
|
"failures",
|
|
16318
16528
|
"falling",
|
|
16319
16529
|
"falls",
|
|
16320
|
-
"
|
|
16530
|
+
"feature",
|
|
16321
16531
|
"fifth",
|
|
16532
|
+
"file",
|
|
16533
|
+
"files",
|
|
16322
16534
|
"finding",
|
|
16323
16535
|
"first",
|
|
16324
16536
|
"five",
|
|
16325
16537
|
"fix",
|
|
16326
|
-
"fixed",
|
|
16327
16538
|
"flat",
|
|
16328
16539
|
"flatters",
|
|
16329
16540
|
"flowed",
|
|
16330
16541
|
"follow",
|
|
16331
|
-
"for",
|
|
16332
16542
|
"forward",
|
|
16333
16543
|
"frequency",
|
|
16334
16544
|
"front",
|
|
@@ -16340,8 +16550,11 @@
|
|
|
16340
16550
|
"gets",
|
|
16341
16551
|
"give",
|
|
16342
16552
|
"goes",
|
|
16553
|
+
"grouped",
|
|
16343
16554
|
"guess",
|
|
16344
16555
|
"half",
|
|
16556
|
+
"happening",
|
|
16557
|
+
"has",
|
|
16345
16558
|
"have",
|
|
16346
16559
|
"health",
|
|
16347
16560
|
"here",
|
|
@@ -16350,18 +16563,20 @@
|
|
|
16350
16563
|
"hotfix",
|
|
16351
16564
|
"hours",
|
|
16352
16565
|
"how",
|
|
16566
|
+
"inaccurate",
|
|
16353
16567
|
"incident",
|
|
16354
16568
|
"incidents",
|
|
16355
16569
|
"increase",
|
|
16356
16570
|
"industry",
|
|
16357
16571
|
"inflate",
|
|
16572
|
+
"instability",
|
|
16358
16573
|
"instrumentation",
|
|
16359
16574
|
"into",
|
|
16360
16575
|
"invite",
|
|
16361
16576
|
"its",
|
|
16362
16577
|
"itself",
|
|
16363
16578
|
"judging",
|
|
16364
|
-
"
|
|
16579
|
+
"labelled",
|
|
16365
16580
|
"lags",
|
|
16366
16581
|
"last",
|
|
16367
16582
|
"lead",
|
|
@@ -16378,31 +16593,32 @@
|
|
|
16378
16593
|
"mature",
|
|
16379
16594
|
"mean",
|
|
16380
16595
|
"measure",
|
|
16381
|
-
"measured",
|
|
16382
|
-
"meeting",
|
|
16383
|
-
"meets",
|
|
16384
|
-
"met",
|
|
16385
16596
|
"meta",
|
|
16386
16597
|
"metric",
|
|
16387
16598
|
"metrics",
|
|
16388
16599
|
"might",
|
|
16389
16600
|
"minutes",
|
|
16601
|
+
"model",
|
|
16390
16602
|
"more",
|
|
16391
16603
|
"move",
|
|
16392
16604
|
"moved",
|
|
16393
16605
|
"moves",
|
|
16394
16606
|
"moving",
|
|
16395
|
-
"
|
|
16607
|
+
"names",
|
|
16608
|
+
"narrowing",
|
|
16396
16609
|
"never",
|
|
16610
|
+
"next",
|
|
16397
16611
|
"not",
|
|
16398
16612
|
"number",
|
|
16399
16613
|
"numbers",
|
|
16400
16614
|
"one",
|
|
16401
16615
|
"ones",
|
|
16402
16616
|
"only",
|
|
16617
|
+
"operational",
|
|
16403
16618
|
"organization",
|
|
16404
16619
|
"other",
|
|
16405
16620
|
"our",
|
|
16621
|
+
"outage",
|
|
16406
16622
|
"over",
|
|
16407
16623
|
"own",
|
|
16408
16624
|
"page",
|
|
@@ -16413,13 +16629,16 @@
|
|
|
16413
16629
|
"pause",
|
|
16414
16630
|
"people",
|
|
16415
16631
|
"perfection",
|
|
16632
|
+
"performance",
|
|
16416
16633
|
"pick",
|
|
16417
16634
|
"pipeline",
|
|
16635
|
+
"planned",
|
|
16418
16636
|
"playbook",
|
|
16419
16637
|
"practice",
|
|
16420
16638
|
"prescribing",
|
|
16421
16639
|
"problem",
|
|
16422
16640
|
"provenance",
|
|
16641
|
+
"published",
|
|
16423
16642
|
"quarter",
|
|
16424
16643
|
"quartiles",
|
|
16425
16644
|
"quietly",
|
|
@@ -16429,12 +16648,16 @@
|
|
|
16429
16648
|
"rather",
|
|
16430
16649
|
"read",
|
|
16431
16650
|
"reading",
|
|
16651
|
+
"readings",
|
|
16432
16652
|
"recent",
|
|
16433
16653
|
"records",
|
|
16654
|
+
"recovery",
|
|
16434
16655
|
"redefine",
|
|
16435
|
-
"reflecting",
|
|
16436
16656
|
"regressions",
|
|
16437
16657
|
"reliability",
|
|
16658
|
+
"rename",
|
|
16659
|
+
"renamed",
|
|
16660
|
+
"report",
|
|
16438
16661
|
"reported",
|
|
16439
16662
|
"requires",
|
|
16440
16663
|
"resolved",
|
|
@@ -16442,6 +16665,7 @@
|
|
|
16442
16665
|
"restore",
|
|
16443
16666
|
"restoring",
|
|
16444
16667
|
"review",
|
|
16668
|
+
"rework",
|
|
16445
16669
|
"rising",
|
|
16446
16670
|
"risk",
|
|
16447
16671
|
"rollbacks",
|
|
@@ -16451,24 +16675,25 @@
|
|
|
16451
16675
|
"say",
|
|
16452
16676
|
"see",
|
|
16453
16677
|
"sensor",
|
|
16678
|
+
"sensors",
|
|
16454
16679
|
"service",
|
|
16680
|
+
"set",
|
|
16455
16681
|
"ship",
|
|
16682
|
+
"shipped",
|
|
16456
16683
|
"shipping",
|
|
16457
16684
|
"signal",
|
|
16685
|
+
"since",
|
|
16458
16686
|
"size",
|
|
16459
16687
|
"small",
|
|
16460
16688
|
"smaller",
|
|
16689
|
+
"something",
|
|
16461
16690
|
"split",
|
|
16462
16691
|
"splitting",
|
|
16463
|
-
"stability",
|
|
16464
16692
|
"stage",
|
|
16465
16693
|
"stale",
|
|
16466
|
-
"started",
|
|
16467
16694
|
"stops",
|
|
16468
16695
|
"sudden",
|
|
16469
|
-
"system",
|
|
16470
16696
|
"table",
|
|
16471
|
-
"target",
|
|
16472
16697
|
"targets",
|
|
16473
16698
|
"team",
|
|
16474
16699
|
"teams",
|
|
@@ -16481,8 +16706,10 @@
|
|
|
16481
16706
|
"them",
|
|
16482
16707
|
"they",
|
|
16483
16708
|
"this",
|
|
16709
|
+
"three",
|
|
16484
16710
|
"through",
|
|
16485
|
-
"
|
|
16711
|
+
"throughput",
|
|
16712
|
+
"ticket",
|
|
16486
16713
|
"time",
|
|
16487
16714
|
"timeline",
|
|
16488
16715
|
"together",
|
|
@@ -16494,9 +16721,11 @@
|
|
|
16494
16721
|
"trivial",
|
|
16495
16722
|
"trouble",
|
|
16496
16723
|
"twenty",
|
|
16724
|
+
"two",
|
|
16497
16725
|
"type",
|
|
16498
16726
|
"unaffected",
|
|
16499
16727
|
"unannounced",
|
|
16728
|
+
"under",
|
|
16500
16729
|
"underlying",
|
|
16501
16730
|
"understanding",
|
|
16502
16731
|
"unmeasured",
|
|
@@ -16505,6 +16734,7 @@
|
|
|
16505
16734
|
"uses",
|
|
16506
16735
|
"usually",
|
|
16507
16736
|
"verify",
|
|
16737
|
+
"versioned",
|
|
16508
16738
|
"waits",
|
|
16509
16739
|
"was",
|
|
16510
16740
|
"way",
|
|
@@ -16521,8 +16751,8 @@
|
|
|
16521
16751
|
"window",
|
|
16522
16752
|
"with",
|
|
16523
16753
|
"without",
|
|
16754
|
+
"work",
|
|
16524
16755
|
"worse",
|
|
16525
|
-
"yes",
|
|
16526
16756
|
"you"
|
|
16527
16757
|
],
|
|
16528
16758
|
"url_path": "sensors/dora-metrics/"
|
|
@@ -16898,7 +17128,9 @@
|
|
|
16898
17128
|
"see_also_ids": [
|
|
16899
17129
|
"SO-009b",
|
|
16900
17130
|
"SO-009c",
|
|
16901
|
-
"SO-009d"
|
|
17131
|
+
"SO-009d",
|
|
17132
|
+
"SO-007e",
|
|
17133
|
+
"SO-015b"
|
|
16902
17134
|
],
|
|
16903
17135
|
"see_also_pages": [],
|
|
16904
17136
|
"slug": "revert-rate",
|
|
@@ -17242,7 +17474,8 @@
|
|
|
17242
17474
|
"see_also_ids": [
|
|
17243
17475
|
"SO-009",
|
|
17244
17476
|
"SO-009b",
|
|
17245
|
-
"SO-009c"
|
|
17477
|
+
"SO-009c",
|
|
17478
|
+
"SO-015b"
|
|
17246
17479
|
],
|
|
17247
17480
|
"see_also_pages": [],
|
|
17248
17481
|
"slug": "time-to-repair",
|
|
@@ -18358,7 +18591,8 @@
|
|
|
18358
18591
|
"see_also_ids": [
|
|
18359
18592
|
"SO-010b",
|
|
18360
18593
|
"SO-010c",
|
|
18361
|
-
"SO-010d"
|
|
18594
|
+
"SO-010d",
|
|
18595
|
+
"SO-011d"
|
|
18362
18596
|
],
|
|
18363
18597
|
"see_also_pages": [],
|
|
18364
18598
|
"slug": "independent-review",
|