testguard-cli 0.12.0__tar.gz → 0.13.0__tar.gz

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.
Files changed (328) hide show
  1. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/CHANGELOG.md +57 -0
  2. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/PKG-INFO +87 -9
  3. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/README.md +86 -8
  4. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/action.yml +1 -1
  5. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/docs/testguard-explained.html +1 -1
  6. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/docs/testguard-explained.pdf +0 -0
  7. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/docs-canonical/ARCHITECTURE.md +2 -1
  8. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/package-lock.json +2 -2
  9. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/package.json +1 -1
  10. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/packaging/gitlab/testguard.gitlab-ci.yml +2 -2
  11. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/packaging/homebrew/testguard.rb +2 -2
  12. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/pyproject.toml +1 -1
  13. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/lib/validate.mjs +38 -2
  14. testguard_cli-0.13.0/spec/schemas/concerns.schema.json +41 -0
  15. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/schemas/sweep.schema.json +56 -0
  16. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/cli.mjs +7 -1
  17. testguard_cli-0.13.0/src/commands/concerns.mjs +32 -0
  18. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/commands/sweep.mjs +52 -23
  19. testguard_cli-0.13.0/src/supply/concerns.mjs +162 -0
  20. testguard_cli-0.13.0/src/supply/feedback.mjs +146 -0
  21. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/supply/persistence.mjs +53 -0
  22. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/sweep/sweep.mjs +87 -11
  23. testguard_cli-0.13.0/test/supply-concerns.test.mjs +124 -0
  24. testguard_cli-0.13.0/test/supply-feedback.test.mjs +134 -0
  25. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/supply-persistence.test.mjs +41 -1
  26. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/sweep.test.mjs +29 -1
  27. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/testguard.claims.json +130 -0
  28. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/.agents/skills/testguard/SKILL.md +0 -0
  29. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/.codex/hooks.json +0 -0
  30. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/.docguard.json +0 -0
  31. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/.docguardignore +0 -0
  32. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/.gitattributes +0 -0
  33. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  34. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  35. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  36. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/.github/dependabot.yml +0 -0
  37. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/.github/scripts/approve-held-runs.sh +0 -0
  38. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/.github/scripts/build-one-pager.mjs +0 -0
  39. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/.github/scripts/check-cost-budget.mjs +0 -0
  40. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/.github/scripts/check-python-fixture.mjs +0 -0
  41. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/.github/scripts/install-smoke.mjs +0 -0
  42. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/.github/scripts/publish-homebrew-tap.sh +0 -0
  43. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/.github/scripts/sync-release-version.mjs +0 -0
  44. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/.github/workflows/auto-merge.yml +0 -0
  45. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/.github/workflows/ci.yml +0 -0
  46. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/.github/workflows/release.yml +0 -0
  47. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/.github/workflows/scheduled-release.yml +0 -0
  48. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/.github/workflows/supply-chain.yml +0 -0
  49. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/.gitignore +0 -0
  50. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/.npmignore +0 -0
  51. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/.npmrc +0 -0
  52. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/.pre-commit-config.yaml +0 -0
  53. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/.pre-commit-hooks.yaml +0 -0
  54. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/AGENTS.md +0 -0
  55. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/CLAUDE.md +0 -0
  56. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/CODE_OF_CONDUCT.md +0 -0
  57. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/CONTRIBUTING.md +0 -0
  58. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/DRIFT-LOG.md +0 -0
  59. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/LICENSE +0 -0
  60. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/PRIVACY.md +0 -0
  61. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/SECURITY.md +0 -0
  62. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/SUPPORT.md +0 -0
  63. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/bench/README.md +0 -0
  64. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/cli/testguard.mjs +0 -0
  65. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/docs-canonical/DATA-MODEL.md +0 -0
  66. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/docs-canonical/ENVIRONMENT.md +0 -0
  67. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/docs-canonical/PRIOR-ART.md +0 -0
  68. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/docs-canonical/REQUIREMENTS.md +0 -0
  69. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/docs-canonical/SECURITY.md +0 -0
  70. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/docs-canonical/TEST-SPEC.md +0 -0
  71. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer/.gitignore +0 -0
  72. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer/README.md +0 -0
  73. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer/expected.json +0 -0
  74. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer/package.json +0 -0
  75. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer/src/export.mjs +0 -0
  76. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer/src/redact.mjs +0 -0
  77. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer/test/export-mocked.test.mjs +0 -0
  78. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer/test/flaky.test.mjs +0 -0
  79. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer/test/redact.test.mjs +0 -0
  80. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer/testguard.claims.json +0 -0
  81. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer/vitest.config.mjs +0 -0
  82. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer-jest/.gitignore +0 -0
  83. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer-jest/README.md +0 -0
  84. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer-jest/expected.json +0 -0
  85. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer-jest/jest.config.js +0 -0
  86. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer-jest/package.json +0 -0
  87. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer-jest/src/export.js +0 -0
  88. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer-jest/src/redact.js +0 -0
  89. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer-jest/test/export-mocked.test.js +0 -0
  90. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer-jest/test/flaky.test.js +0 -0
  91. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer-jest/test/redact.test.js +0 -0
  92. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer-jest/testguard.claims.json +0 -0
  93. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer-playwright/.gitignore +0 -0
  94. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer-playwright/README.md +0 -0
  95. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer-playwright/e2e/flaky.spec.mjs +0 -0
  96. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer-playwright/e2e/toggle.spec.mjs +0 -0
  97. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer-playwright/expected.json +0 -0
  98. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer-playwright/package-lock.json +0 -0
  99. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer-playwright/package.json +0 -0
  100. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer-playwright/playwright.config.mjs +0 -0
  101. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer-playwright/src/toggle.mjs +0 -0
  102. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer-playwright/test/toggle.test.mjs +0 -0
  103. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer-playwright/test-results/.last-run.json +0 -0
  104. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer-playwright/testguard.claims.json +0 -0
  105. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer-playwright/vitest.config.mjs +0 -0
  106. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer-python/.gitignore +0 -0
  107. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer-python/README.md +0 -0
  108. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer-python/demo/__init__.py +0 -0
  109. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer-python/demo/export.py +0 -0
  110. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer-python/demo/orphan.py +0 -0
  111. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer-python/demo/pipeline.py +0 -0
  112. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer-python/demo/redact.py +0 -0
  113. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer-python/expected.json +0 -0
  114. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer-python/testguard.claims.json +0 -0
  115. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer-python/tests/test_export_mocked.py +0 -0
  116. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer-python/tests/test_flaky.py +0 -0
  117. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer-python/tests/test_patched.py +0 -0
  118. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/fixtures/known-answer-python/tests/test_redact.py +0 -0
  119. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/GATE-SEMANTICS.md +0 -0
  120. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/README.md +0 -0
  121. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/examples/baseline.json +0 -0
  122. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/examples/brief.json +0 -0
  123. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/examples/calibration-websec.json +0 -0
  124. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/examples/calibration.json +0 -0
  125. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/examples/claims-methods.json +0 -0
  126. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/examples/claims.json +0 -0
  127. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/examples/evidence-disturbed-run.json +0 -0
  128. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/examples/evidence-negative-control.json +0 -0
  129. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/examples/evidence-provisional.json +0 -0
  130. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/examples/evidence-python.json +0 -0
  131. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/examples/evidence-scan-method.json +0 -0
  132. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/examples/evidence.json +0 -0
  133. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/examples/gate.json +0 -0
  134. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/examples/ignore.json +0 -0
  135. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/examples/replay.json +0 -0
  136. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/examples/status.json +0 -0
  137. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/baseline.bad-fingerprint-key.json +0 -0
  138. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/baseline.zero-count.json +0 -0
  139. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/brief.text-without-heading.json +0 -0
  140. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/brief.unknown-verdict-key.json +0 -0
  141. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/calibration.backoff-cell-not-wilson.json +0 -0
  142. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/calibration.breakdown-does-not-sum-to-n.json +0 -0
  143. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/calibration.ci-not-wilson.json +0 -0
  144. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/calibration.compound-key-arity-mismatch.json +0 -0
  145. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/calibration.fallback-without-basis.json +0 -0
  146. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/calibration.p-not-positives-over-n.json +0 -0
  147. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/calibration.p-outside-ci.json +0 -0
  148. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/calibration.positives-exceed-n.json +0 -0
  149. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/claims.absolute-path.json +0 -0
  150. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/claims.duplicate-fault-id.json +0 -0
  151. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/claims.fault-injection-without-a-substitution.json +0 -0
  152. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/claims.method-detail-on-a-specified-method.json +0 -0
  153. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/claims.missing-provenance.json +0 -0
  154. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/claims.noop-fault.json +0 -0
  155. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/claims.occurrence-exceeds-hits.json +0 -0
  156. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/claims.parent-traversal.json +0 -0
  157. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/evidence.annotated-signal-without-reason.json +0 -0
  158. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/evidence.attribute-signal-not-a-defender.json +0 -0
  159. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/evidence.attribute-signal-without-attributes.json +0 -0
  160. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/evidence.byrunner-file-not-resolved.json +0 -0
  161. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/evidence.contention-without-runners.json +0 -0
  162. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/evidence.discovered-defender-mocks-subject.json +0 -0
  163. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/evidence.fault-injection-without-its-evidence.json +0 -0
  164. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/evidence.flaky-defender-without-flakiness.json +0 -0
  165. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/evidence.ignored-dirty-on-snapshot.json +0 -0
  166. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/evidence.killed-by-non-assertion.json +0 -0
  167. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/evidence.killed-on-red-baseline.json +0 -0
  168. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/evidence.killed-without-n-runs.json +0 -0
  169. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/evidence.method-detail-on-an-injecting-run.json +0 -0
  170. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/evidence.negative-control-not-reached-but-survived.json +0 -0
  171. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/evidence.negative-control-reached-on-a-kill.json +0 -0
  172. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/evidence.probe-error-without-message.json +0 -0
  173. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/evidence.provisional-flag-on-confirmed-run.json +0 -0
  174. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/evidence.rank-extra-property.json +0 -0
  175. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/evidence.restore-skipped-in-place.json +0 -0
  176. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/evidence.single-run-without-provisional.json +0 -0
  177. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/evidence.target-not-imported-but-killed.json +0 -0
  178. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/evidence.undeclared-killers-without-reason.json +0 -0
  179. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/evidence.unknown-verdict.json +0 -0
  180. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/evidence.unverifiable-without-reason.json +0 -0
  181. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/evidence.wrong-fingerprint.json +0 -0
  182. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/gate.counts-disagree.json +0 -0
  183. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/gate.exit-zero-with-uncovered.json +0 -0
  184. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/gate.ignore-cover-without-pattern.json +0 -0
  185. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/ignore.missing-reason.json +0 -0
  186. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/ignore.short-reason.json +0 -0
  187. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/replay.blind-with-a-failing-run.json +0 -0
  188. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/replay.duplicate-patch-id.json +0 -0
  189. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/status.claim-without-file.json +0 -0
  190. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/status.clean-with-new-findings.json +0 -0
  191. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/status.invalid-anchor-hidden.json +0 -0
  192. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/status.partial-scope-counts-disagree.json +0 -0
  193. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/status.surface-counts-disagree.json +0 -0
  194. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/status.unclaimed-hidden-behind-unproven.json +0 -0
  195. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/invalid/status.write-test-without-target.json +0 -0
  196. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/conformance/schemas.test.mjs +0 -0
  197. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/lib/fingerprint.mjs +0 -0
  198. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/lib/wilson.mjs +0 -0
  199. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/schemas/baseline.schema.json +0 -0
  200. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/schemas/brief.schema.json +0 -0
  201. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/schemas/calibration.schema.json +0 -0
  202. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/schemas/claims.schema.json +0 -0
  203. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/schemas/common.schema.json +0 -0
  204. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/schemas/evidence.schema.json +0 -0
  205. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/schemas/gate.schema.json +0 -0
  206. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/schemas/ignore.schema.json +0 -0
  207. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/schemas/replay.schema.json +0 -0
  208. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/spec/schemas/status.schema.json +0 -0
  209. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/admit/admit.mjs +0 -0
  210. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/baseline/baseline.mjs +0 -0
  211. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/brief/brief.mjs +0 -0
  212. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/claims/anchors.mjs +0 -0
  213. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/claims/annotations.mjs +0 -0
  214. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/claims/load.mjs +0 -0
  215. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/claims/removed.mjs +0 -0
  216. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/commands/admit.mjs +0 -0
  217. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/commands/baseline.mjs +0 -0
  218. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/commands/brief.mjs +0 -0
  219. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/commands/claims.mjs +0 -0
  220. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/commands/gate.mjs +0 -0
  221. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/commands/init.mjs +0 -0
  222. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/commands/mcp.mjs +0 -0
  223. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/commands/probe.mjs +0 -0
  224. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/commands/replay.mjs +0 -0
  225. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/commands/scaffold.mjs +0 -0
  226. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/commands/status.mjs +0 -0
  227. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/evidence/writer.mjs +0 -0
  228. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/gate/changed.mjs +0 -0
  229. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/git.mjs +0 -0
  230. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/init/init.mjs +0 -0
  231. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/init/templates/SKILL.md +0 -0
  232. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/mcp/server.mjs +0 -0
  233. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/mcp/tools.mjs +0 -0
  234. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/probe/attribution.mjs +0 -0
  235. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/probe/classify.mjs +0 -0
  236. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/probe/contention.mjs +0 -0
  237. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/probe/cost.mjs +0 -0
  238. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/probe/discover.mjs +0 -0
  239. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/probe/independence.mjs +0 -0
  240. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/probe/inject.mjs +0 -0
  241. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/probe/mocks.mjs +0 -0
  242. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/probe/probe.mjs +0 -0
  243. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/probe/progress.mjs +0 -0
  244. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/probe/pyimports.mjs +0 -0
  245. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/probe/rank.mjs +0 -0
  246. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/probe/runners/index.mjs +0 -0
  247. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/probe/runners/jest.mjs +0 -0
  248. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/probe/runners/playwright.mjs +0 -0
  249. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/probe/runners/python/_testguard_pytest_plugin.py +0 -0
  250. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/probe/runners/python/_testguard_report.py +0 -0
  251. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/probe/runners/python/_testguard_unittest_main.py +0 -0
  252. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/probe/runners/python.mjs +0 -0
  253. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/probe/runners/shared.mjs +0 -0
  254. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/probe/runners/vitest.mjs +0 -0
  255. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/probe/worktree.mjs +0 -0
  256. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/render.mjs +0 -0
  257. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/replay/label.mjs +0 -0
  258. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/replay/replay.mjs +0 -0
  259. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/scaffold/producers.mjs +0 -0
  260. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/scaffold/producers.python.mjs +0 -0
  261. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/scaffold/scaffold.mjs +0 -0
  262. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/status/status.mjs +0 -0
  263. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/status/surface.mjs +0 -0
  264. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/supply/savepath.mjs +0 -0
  265. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/supply/select.mjs +0 -0
  266. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/util/glob.mjs +0 -0
  267. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/src/util/hash.mjs +0 -0
  268. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/admit.test.mjs +0 -0
  269. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/annotations.test.mjs +0 -0
  270. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/attribution.test.mjs +0 -0
  271. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/baseline.test.mjs +0 -0
  272. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/brief.test.mjs +0 -0
  273. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/calibration.test.mjs +0 -0
  274. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/claim-selection.test.mjs +0 -0
  275. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/claims-anchors.test.mjs +0 -0
  276. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/claims-since.test.mjs +0 -0
  277. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/classify.test.mjs +0 -0
  278. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/contention.test.mjs +0 -0
  279. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/cost.test.mjs +0 -0
  280. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/discover.test.mjs +0 -0
  281. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/gate.test.mjs +0 -0
  282. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/gitlab-template.test.mjs +0 -0
  283. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/glob.test.mjs +0 -0
  284. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/independence.test.mjs +0 -0
  285. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/init.test.mjs +0 -0
  286. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/inject.test.mjs +0 -0
  287. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/label.test.mjs +0 -0
  288. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/mcp.test.mjs +0 -0
  289. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/mocks.test.mjs +0 -0
  290. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/negative-control.test.mjs +0 -0
  291. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/one-pager.test.mjs +0 -0
  292. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/probe-error.test.mjs +0 -0
  293. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/probe-preconditions.test.mjs +0 -0
  294. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/probe.fixture.test.mjs +0 -0
  295. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/probe.jest.test.mjs +0 -0
  296. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/probe.playwright.test.mjs +0 -0
  297. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/probe.python.test.mjs +0 -0
  298. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/progress.test.mjs +0 -0
  299. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/pyimports.test.mjs +0 -0
  300. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/rank-aliases.test.mjs +0 -0
  301. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/release-approval.test.mjs +0 -0
  302. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/release-sync.test.mjs +0 -0
  303. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/replay-verdict.test.mjs +0 -0
  304. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/replay.test.mjs +0 -0
  305. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/runner-argv.test.mjs +0 -0
  306. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/runner-command.test.mjs +0 -0
  307. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/runner-playwright.test.mjs +0 -0
  308. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/runner-python.test.mjs +0 -0
  309. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/runner-vitest.test.mjs +0 -0
  310. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/samples/playwright-report.json +0 -0
  311. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/samples/python-report-collect-error.json +0 -0
  312. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/samples/python-report.json +0 -0
  313. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/scaffold.python.test.mjs +0 -0
  314. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/scaffold.test.mjs +0 -0
  315. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/snapshot.test.mjs +0 -0
  316. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/status.test.mjs +0 -0
  317. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/supply-savepath.test.mjs +0 -0
  318. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/supply-select.test.mjs +0 -0
  319. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/surface.test.mjs +0 -0
  320. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/sweep.fixture.test.mjs +0 -0
  321. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/vitest-config.test.mjs +0 -0
  322. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/worktree.test.mjs +0 -0
  323. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/test/writer.test.mjs +0 -0
  324. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/testguard.cost-budget.json +0 -0
  325. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/testguard.ignore.json +0 -0
  326. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/testguard_cli/__init__.py +0 -0
  327. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/testguard_cli/wrapper.py +0 -0
  328. {testguard_cli-0.12.0 → testguard_cli-0.13.0}/vitest.config.mjs +0 -0
@@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.13.0] - 2026-09-22
11
+
12
+ Automated weekly release — everything merged since `v0.12.0`.
13
+
14
+ ### Changed
15
+
16
+ - chore(homebrew): sha256 for v0.12.0 (#159)
17
+ - feat: persistence provability, and an ordering that learns from this project's own verdicts (#157)
18
+
19
+
10
20
  ## [0.12.0] - 2026-09-22
11
21
 
12
22
  Automated weekly release — everything merged since `v0.11.0`.
@@ -18,6 +28,53 @@ Automated weekly release — everything merged since `v0.11.0`.
18
28
  - chore(homebrew): sha256 for v0.11.0 (#153)
19
29
 
20
30
 
31
+ ### Added
32
+
33
+ - **Concerns** — one sentence a human writes that generates faults across a
34
+ whole surface, the rung between writing nothing and writing a claim per
35
+ behaviour. A concern says *where* to look and *which shapes of break* are
36
+ relevant: `"every write persists the data it was given"` targets the write
37
+ sites and applies the payload producers. `testguard concerns` lists them,
38
+ `sweep --concern <ID>` aims a sweep by one, and `--save-paths` is now sugar
39
+ for the built-in `SAVE-PERSISTS`.
40
+ - Two built-ins ship, and only two: an authorization concern would need a
41
+ heuristic for "which files check permissions", and a guess there is the
42
+ non-actionable noise that gets a check switched off. A project that knows
43
+ its own auth layer says so with a `glob` target in three lines.
44
+ - A project concern with the same id **replaces** a built-in, so a team can
45
+ retune `SAVE-PERSISTS` for an ORM this tool does not recognise without
46
+ forking anything. The replacement is reported, never silent.
47
+ - `urn:claimspec:v1:concerns`, with rules that refuse a glob target naming no
48
+ globs (it would match nothing and report clean) and an empty fault-class
49
+ list (a typo for "every class", every time).
50
+ - **No model is required, deliberately.** A concern's useful core is a named
51
+ scope plus a producer selection, which the mechanical producers already
52
+ satisfy — so every verifying command stays offline and NFR-01 is unchanged.
53
+ An LLM can later propose faults a line-oriented producer cannot see, as an
54
+ additional source for a concern that already works.
55
+
56
+ ### Added
57
+
58
+ - A save-path sweep now reports **what its defenders could prove**, before it
59
+ reports what they did: per file, whether every defender mocks the persistence
60
+ layer (call shape only), at least one does not (a real write is possible), or
61
+ there is no defender at all. When nothing is unmocked the report says so
62
+ outright — a clean probe there is not evidence of persistence, it is evidence
63
+ that nothing could have measured it. Measured on a real AI-authored
64
+ application: of 37 files that write to storage, 32 were defended only by
65
+ mocking tests.
66
+ - `scope.provability` on the sweep document, with a validator rule that every
67
+ target is classified exactly once.
68
+ - The candidate ordering now **learns from this project's own probed verdicts**
69
+ instead of only the shipped productivity prior. Every evidence record already
70
+ carries a `faultClass` and a `verdict`, so "how often does a fault of this
71
+ class survive *here*" is a tally rather than a new thing to capture. A sweep
72
+ persists its own evidence to `.testguard/sweep-evidence.json` — beside the
73
+ canonical one, never replacing it — so the next run learns from
74
+ machine-proposed faults, the same distribution the ranker orders. The sweep
75
+ document records `ordering.observed` and `ordering.sources`, because an
76
+ ordering nobody can trace is a number nobody should trust.
77
+
21
78
  ### Added
22
79
 
23
80
  - `sweep --save-paths` — sweep every file that writes to storage instead of the
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: testguard-cli
3
- Version: 0.12.0
3
+ Version: 0.13.0
4
4
  Summary: Breaks your code on purpose and reports every promise your tests did not notice breaking. Mutation testing bound to the claims a project makes — vitest, jest, Playwright, pytest and unittest. Ships the Node.js CLI (requires Node.js 20+).
5
5
  Project-URL: Homepage, https://github.com/raccioly/testguard
6
6
  Project-URL: Documentation, https://github.com/raccioly/testguard#readme
@@ -151,9 +151,9 @@ raises coverage. TestGuard admits it because it fails when the claim is false.
151
151
  | npm | `npm i -D testguard-cli` then `npx testguard probe` |
152
152
  | pip | `pip install testguard-cli` then `testguard probe` (needs Node ≥ 20) |
153
153
  | Homebrew | `brew tap raccioly/tap && brew install testguard` |
154
- | GitHub Action | `uses: raccioly/testguard@v0.12.0` — see [`action.yml`](./action.yml) |
154
+ | GitHub Action | `uses: raccioly/testguard@v0.13.0` — see [`action.yml`](./action.yml) |
155
155
  | pre-commit | `repo: https://github.com/raccioly/testguard`, hooks `testguard-claims`, `testguard-probe` |
156
- | GitLab CI | `include: - remote: https://raw.githubusercontent.com/raccioly/testguard/v0.12.0/packaging/gitlab/testguard.gitlab-ci.yml` with `inputs:` — see [`packaging/gitlab/`](./packaging/gitlab/testguard.gitlab-ci.yml) |
156
+ | GitLab CI | `include: - remote: https://raw.githubusercontent.com/raccioly/testguard/v0.13.0/packaging/gitlab/testguard.gitlab-ci.yml` with `inputs:` — see [`packaging/gitlab/`](./packaging/gitlab/testguard.gitlab-ci.yml) |
157
157
 
158
158
  Projects that set `min-release-age` in `.npmrc` cannot see a version published
159
159
  less than that many days ago (`ENOVERSIONS`); install that one with
@@ -475,6 +475,14 @@ Three rules make it safe to run on a repository that has never seen this tool:
475
475
  is reported and never gates — a tool that fails because its own guess was bad
476
476
  is a tool people switch off.
477
477
 
478
+ The ordering **learns from your own runs**. Every evidence record already
479
+ carries a fault class and a verdict, so "how often does a fault of this class
480
+ survive here" is a tally, not a new thing to collect — a sweep persists its
481
+ evidence and the next one reads it. A project with no evidence falls back to
482
+ the shipped prior and says so; one with its own records overrides it in
483
+ proportion to how many it has. The report names which, because an ordering
484
+ nobody can trace is a number nobody should trust.
485
+
478
486
  The cap and the ordering are Google's: their mutation service surfaces at most
479
487
  7 × |files| mutants per change and orders candidates on the measured
480
488
  productivity of their operator in similar context, which took their productive
@@ -490,6 +498,60 @@ A sweep is weaker evidence than a probe, and says so. Nobody stated that the
490
498
  behaviour mattered. It is stronger than nothing, which is what a repository
491
499
  with no claims has — and the survivors worth defending become the first claims.
492
500
 
501
+ #### Concerns: one sentence, two hundred screens
502
+
503
+ A claim names one promise precisely, which is why a repository with two hundred
504
+ screens never finishes writing them. A **concern** names a *kind* of promise and
505
+ says where to look for it:
506
+
507
+ ```bash
508
+ npx testguard-cli concerns # what this project can be swept by
509
+ npx testguard-cli sweep --concern ADMIN-GUARDS # aim a sweep at one
510
+ ```
511
+
512
+ ```json
513
+ {
514
+ "schemaVersion": 1,
515
+ "concerns": [
516
+ {
517
+ "id": "ADMIN-GUARDS",
518
+ "statement": "Every admin route refuses a caller without the admin role.",
519
+ "severity": "critical",
520
+ "targets": { "kind": "glob", "globs": ["src/app/api/admin/**"] },
521
+ "faultClasses": ["guard-removed", "condition-forced", "return-altered"]
522
+ }
523
+ ]
524
+ }
525
+ ```
526
+
527
+ Five lines, and the sweep reports:
528
+
529
+ ```
530
+ concern ADMIN-GUARDS matches 24 files. Swept 23.
531
+ 158 further proposals were outside this concern's fault classes.
532
+
533
+ SURVIVED src/app/api/admin/claims/[id]/route.ts
534
+ [line 41] Guard never triggers: `if (!membership || (membership.role !== 'owner'
535
+ && membership.role !== 'manager'))` becomes `if (false)`.
536
+ ```
537
+
538
+ The idea is Meta's: ACH has an engineer describe an area of concern in plain
539
+ text and gates every generated step by execution. What is taken here is the
540
+ **unit** — the concern as the thing a human writes — not their generator, and
541
+ **no model is required**. A concern's useful core is a named scope plus a
542
+ producer selection, which the mechanical producers already satisfy, so every
543
+ verifying command stays offline.
544
+
545
+ Two concerns ship built in, and only two. An authorization concern would need a
546
+ heuristic for *"which files check permissions"*, and a guess there is exactly
547
+ the noise that gets a check switched off — so `SAVE-PERSISTS` and
548
+ `CHANGED-CODE` are the defaults, and a project that knows its own auth layer
549
+ says so in three lines. Declaring a concern with a built-in's id replaces it,
550
+ and the replacement is reported rather than silent.
551
+
552
+ A concern is **not** a claim and never becomes one by itself. It says where to
553
+ look; the probe says what it found; a human states the sentence worth defending.
554
+
493
555
  #### Sweeping the save surface instead of the diff
494
556
 
495
557
  A diff cannot answer *"when the user clicks save, does it actually save?"* — it
@@ -518,10 +580,26 @@ denominator comes first and the document carries `writeSites` and
518
580
  AI-authored application: **12 of 26 probed persistence faults survived a fully
519
581
  green suite, and all 12 were a dropped payload field.**
520
582
 
521
- This is a *sample* of the surface, and says so. It does not yet prove a write
522
- reached storage — only that a test would notice if the payload changed.
523
- Read-back oracles are the next rung
524
- ([#154](https://github.com/raccioly/testguard/issues/154)).
583
+ It also states, before any verdict, **what the defenders could prove at all**:
584
+
585
+ ```
586
+ of those 37 files: 32 defended only by tests that mock the persistence layer,
587
+ 1 with an unmocked defender, 4 with no defender at all.
588
+ ```
589
+
590
+ That line is the read-back answer. A test that replaced the database can prove
591
+ the *call shape* and nothing beyond it — a `where` that matches no rows, a
592
+ rolled-back transaction and a rejected constraint all pass against a mock that
593
+ recorded the arguments and returned a plausible object. So rather than pretend
594
+ to measure persistence, TestGuard states the limit, the same way `nocover` says
595
+ "no test imports this" instead of guessing.
596
+
597
+ When every defender mocks the layer, the report says so outright: *nothing in
598
+ this suite can prove a write reached storage*. A clean probe there is not
599
+ evidence of persistence — it is evidence that nothing could have measured it.
600
+
601
+ `unmocked` is deliberately weaker than "proves persistence": a test that does
602
+ not replace the database may simply never reach it. Possible is the honest word.
525
603
 
526
604
  ### Every change needs a claim
527
605
 
@@ -604,12 +682,12 @@ detected base that does not resolve is a warning for `status` and `brief`
604
682
  # GitHub Actions
605
683
  - uses: actions/checkout@v4
606
684
  with: { fetch-depth: 0 }
607
- - uses: raccioly/testguard@v0.12.0
685
+ - uses: raccioly/testguard@v0.13.0
608
686
  with: { command: gate }
609
687
 
610
688
  # GitLab CI — the component-shaped template: gate + probe, brief as an artifact and, opted in, as a merge-request note
611
689
  include:
612
- - remote: 'https://raw.githubusercontent.com/raccioly/testguard/v0.12.0/packaging/gitlab/testguard.gitlab-ci.yml'
690
+ - remote: 'https://raw.githubusercontent.com/raccioly/testguard/v0.13.0/packaging/gitlab/testguard.gitlab-ci.yml'
613
691
  inputs: { dir: backend, post_note: true } # post_note needs TESTGUARD_GITLAB_TOKEN (api scope); one note, updated in place
614
692
  ```
615
693
 
@@ -127,9 +127,9 @@ raises coverage. TestGuard admits it because it fails when the claim is false.
127
127
  | npm | `npm i -D testguard-cli` then `npx testguard probe` |
128
128
  | pip | `pip install testguard-cli` then `testguard probe` (needs Node ≥ 20) |
129
129
  | Homebrew | `brew tap raccioly/tap && brew install testguard` |
130
- | GitHub Action | `uses: raccioly/testguard@v0.12.0` — see [`action.yml`](./action.yml) |
130
+ | GitHub Action | `uses: raccioly/testguard@v0.13.0` — see [`action.yml`](./action.yml) |
131
131
  | pre-commit | `repo: https://github.com/raccioly/testguard`, hooks `testguard-claims`, `testguard-probe` |
132
- | GitLab CI | `include: - remote: https://raw.githubusercontent.com/raccioly/testguard/v0.12.0/packaging/gitlab/testguard.gitlab-ci.yml` with `inputs:` — see [`packaging/gitlab/`](./packaging/gitlab/testguard.gitlab-ci.yml) |
132
+ | GitLab CI | `include: - remote: https://raw.githubusercontent.com/raccioly/testguard/v0.13.0/packaging/gitlab/testguard.gitlab-ci.yml` with `inputs:` — see [`packaging/gitlab/`](./packaging/gitlab/testguard.gitlab-ci.yml) |
133
133
 
134
134
  Projects that set `min-release-age` in `.npmrc` cannot see a version published
135
135
  less than that many days ago (`ENOVERSIONS`); install that one with
@@ -451,6 +451,14 @@ Three rules make it safe to run on a repository that has never seen this tool:
451
451
  is reported and never gates — a tool that fails because its own guess was bad
452
452
  is a tool people switch off.
453
453
 
454
+ The ordering **learns from your own runs**. Every evidence record already
455
+ carries a fault class and a verdict, so "how often does a fault of this class
456
+ survive here" is a tally, not a new thing to collect — a sweep persists its
457
+ evidence and the next one reads it. A project with no evidence falls back to
458
+ the shipped prior and says so; one with its own records overrides it in
459
+ proportion to how many it has. The report names which, because an ordering
460
+ nobody can trace is a number nobody should trust.
461
+
454
462
  The cap and the ordering are Google's: their mutation service surfaces at most
455
463
  7 × |files| mutants per change and orders candidates on the measured
456
464
  productivity of their operator in similar context, which took their productive
@@ -466,6 +474,60 @@ A sweep is weaker evidence than a probe, and says so. Nobody stated that the
466
474
  behaviour mattered. It is stronger than nothing, which is what a repository
467
475
  with no claims has — and the survivors worth defending become the first claims.
468
476
 
477
+ #### Concerns: one sentence, two hundred screens
478
+
479
+ A claim names one promise precisely, which is why a repository with two hundred
480
+ screens never finishes writing them. A **concern** names a *kind* of promise and
481
+ says where to look for it:
482
+
483
+ ```bash
484
+ npx testguard-cli concerns # what this project can be swept by
485
+ npx testguard-cli sweep --concern ADMIN-GUARDS # aim a sweep at one
486
+ ```
487
+
488
+ ```json
489
+ {
490
+ "schemaVersion": 1,
491
+ "concerns": [
492
+ {
493
+ "id": "ADMIN-GUARDS",
494
+ "statement": "Every admin route refuses a caller without the admin role.",
495
+ "severity": "critical",
496
+ "targets": { "kind": "glob", "globs": ["src/app/api/admin/**"] },
497
+ "faultClasses": ["guard-removed", "condition-forced", "return-altered"]
498
+ }
499
+ ]
500
+ }
501
+ ```
502
+
503
+ Five lines, and the sweep reports:
504
+
505
+ ```
506
+ concern ADMIN-GUARDS matches 24 files. Swept 23.
507
+ 158 further proposals were outside this concern's fault classes.
508
+
509
+ SURVIVED src/app/api/admin/claims/[id]/route.ts
510
+ [line 41] Guard never triggers: `if (!membership || (membership.role !== 'owner'
511
+ && membership.role !== 'manager'))` becomes `if (false)`.
512
+ ```
513
+
514
+ The idea is Meta's: ACH has an engineer describe an area of concern in plain
515
+ text and gates every generated step by execution. What is taken here is the
516
+ **unit** — the concern as the thing a human writes — not their generator, and
517
+ **no model is required**. A concern's useful core is a named scope plus a
518
+ producer selection, which the mechanical producers already satisfy, so every
519
+ verifying command stays offline.
520
+
521
+ Two concerns ship built in, and only two. An authorization concern would need a
522
+ heuristic for *"which files check permissions"*, and a guess there is exactly
523
+ the noise that gets a check switched off — so `SAVE-PERSISTS` and
524
+ `CHANGED-CODE` are the defaults, and a project that knows its own auth layer
525
+ says so in three lines. Declaring a concern with a built-in's id replaces it,
526
+ and the replacement is reported rather than silent.
527
+
528
+ A concern is **not** a claim and never becomes one by itself. It says where to
529
+ look; the probe says what it found; a human states the sentence worth defending.
530
+
469
531
  #### Sweeping the save surface instead of the diff
470
532
 
471
533
  A diff cannot answer *"when the user clicks save, does it actually save?"* — it
@@ -494,10 +556,26 @@ denominator comes first and the document carries `writeSites` and
494
556
  AI-authored application: **12 of 26 probed persistence faults survived a fully
495
557
  green suite, and all 12 were a dropped payload field.**
496
558
 
497
- This is a *sample* of the surface, and says so. It does not yet prove a write
498
- reached storage — only that a test would notice if the payload changed.
499
- Read-back oracles are the next rung
500
- ([#154](https://github.com/raccioly/testguard/issues/154)).
559
+ It also states, before any verdict, **what the defenders could prove at all**:
560
+
561
+ ```
562
+ of those 37 files: 32 defended only by tests that mock the persistence layer,
563
+ 1 with an unmocked defender, 4 with no defender at all.
564
+ ```
565
+
566
+ That line is the read-back answer. A test that replaced the database can prove
567
+ the *call shape* and nothing beyond it — a `where` that matches no rows, a
568
+ rolled-back transaction and a rejected constraint all pass against a mock that
569
+ recorded the arguments and returned a plausible object. So rather than pretend
570
+ to measure persistence, TestGuard states the limit, the same way `nocover` says
571
+ "no test imports this" instead of guessing.
572
+
573
+ When every defender mocks the layer, the report says so outright: *nothing in
574
+ this suite can prove a write reached storage*. A clean probe there is not
575
+ evidence of persistence — it is evidence that nothing could have measured it.
576
+
577
+ `unmocked` is deliberately weaker than "proves persistence": a test that does
578
+ not replace the database may simply never reach it. Possible is the honest word.
501
579
 
502
580
  ### Every change needs a claim
503
581
 
@@ -580,12 +658,12 @@ detected base that does not resolve is a warning for `status` and `brief`
580
658
  # GitHub Actions
581
659
  - uses: actions/checkout@v4
582
660
  with: { fetch-depth: 0 }
583
- - uses: raccioly/testguard@v0.12.0
661
+ - uses: raccioly/testguard@v0.13.0
584
662
  with: { command: gate }
585
663
 
586
664
  # GitLab CI — the component-shaped template: gate + probe, brief as an artifact and, opted in, as a merge-request note
587
665
  include:
588
- - remote: 'https://raw.githubusercontent.com/raccioly/testguard/v0.12.0/packaging/gitlab/testguard.gitlab-ci.yml'
666
+ - remote: 'https://raw.githubusercontent.com/raccioly/testguard/v0.13.0/packaging/gitlab/testguard.gitlab-ci.yml'
589
667
  inputs: { dir: backend, post_note: true } # post_note needs TESTGUARD_GITLAB_TOKEN (api scope); one note, updated in place
590
668
  ```
591
669
 
@@ -46,7 +46,7 @@ inputs:
46
46
  version:
47
47
  description: 'testguard-cli version to run'
48
48
  required: false
49
- default: '0.12.0'
49
+ default: '0.13.0'
50
50
  node-version:
51
51
  description: 'Node.js version (20+)'
52
52
  required: false
@@ -144,7 +144,7 @@
144
144
  <div class="page">
145
145
  <div class="masthead">
146
146
  <div class="wordmark">Test<span class="mark">Guard</span></div>
147
- <div class="meta">Technical Brief · v0.12.0 · MIT</div>
147
+ <div class="meta">Technical Brief · v0.13.0 · MIT</div>
148
148
  </div>
149
149
 
150
150
  <h1>Your tests pass. That is not the same as your tests working.</h1>
@@ -50,7 +50,7 @@ code were wrong, would anything fail?*
50
50
  | Progress | How a running probe reports itself, per reader: tty, plain, ndjson | `src/probe/progress.mjs` |
51
51
  | Change gate | Claim coverage of a diff | `src/gate/changed.mjs` |
52
52
  | Sweep | The cold start: propose faults for changed files that carry no claim, probe a bounded selection, report what nothing noticed | `src/sweep/sweep.mjs` |
53
- | Claim supply | Which proposals are worth probing, where this project writes to storage, and why a survivor on a write path was missed | `src/supply/select.mjs`, `src/supply/savepath.mjs`, `src/supply/persistence.mjs` |
53
+ | Claim supply | The concerns a sweep can be aimed by, which proposals are worth probing, what this project's own runs say about that, where it writes to storage, and why a survivor on a write path was missed | `src/supply/concerns.mjs`, `src/supply/select.mjs`, `src/supply/feedback.mjs`, `src/supply/savepath.mjs`, `src/supply/persistence.mjs` |
54
54
  | Baseline | Freeze debt; gate the delta; re-stamp | `src/baseline/baseline.mjs` |
55
55
  | Status | The single state machine every rendering derives from | `src/status/status.mjs` |
56
56
  | Brief | The agent's session-start blind-spot block | `src/brief/brief.mjs` |
@@ -105,6 +105,7 @@ appears here but not on disk, or on disk but not here, is drift.
105
105
  | `action.yml` | The GitHub Action surface; `version` input synced from `package.json` | consumers' workflows |
106
106
  | `packaging/gitlab/testguard.gitlab-ci.yml` | The GitLab component; tag and `TESTGUARD_VERSION` synced | consumers' pipelines |
107
107
  | `packaging/homebrew/testguard.rb` | The staged Homebrew formula; tarball URL and registry-derived hash synced, then copied byte-for-byte | `raccioly/homebrew-tap` |
108
+ | `testguard.concerns.json` | The one-sentence scopes a sweep can be aimed by; optional, with two built-ins behind it | `testguard concerns`, `sweep --concern` |
108
109
  | `.docguard.json`, `.docguardignore` | Which canonical documents exist and which validators run | DocGuard, in CI and pre-commit |
109
110
  | `.npmrc`, `.npmignore`, `.gitattributes` | Packaging and line-ending hygiene | npm, git |
110
111
 
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "testguard-cli",
3
- "version": "0.12.0",
3
+ "version": "0.13.0",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "testguard-cli",
9
- "version": "0.12.0",
9
+ "version": "0.13.0",
10
10
  "license": "MIT",
11
11
  "dependencies": {
12
12
  "ajv": "8.20.0"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "testguard-cli",
3
- "version": "0.12.0",
3
+ "version": "0.13.0",
4
4
  "description": "Breaks your code on purpose and reports every promise your tests did not notice breaking. Mutation testing bound to the claims a project makes — vitest, jest, Playwright, pytest and unittest.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -3,7 +3,7 @@
3
3
  # Include it from a project's .gitlab-ci.yml, pinned to a release:
4
4
  #
5
5
  # include:
6
- # - remote: 'https://raw.githubusercontent.com/raccioly/testguard/v0.12.0/packaging/gitlab/testguard.gitlab-ci.yml'
6
+ # - remote: 'https://raw.githubusercontent.com/raccioly/testguard/v0.13.0/packaging/gitlab/testguard.gitlab-ci.yml'
7
7
  # inputs:
8
8
  # dir: backend # where testguard.claims.json lives (default ".")
9
9
  # post_note: true # post the brief as a merge-request note (needs TESTGUARD_GITLAB_TOKEN, api scope)
@@ -29,7 +29,7 @@ spec:
29
29
  inputs:
30
30
  version:
31
31
  description: testguard-cli version to run
32
- default: "0.12.0"
32
+ default: "0.13.0"
33
33
  dir:
34
34
  description: directory whose test runner is invoked (where testguard.claims.json lives)
35
35
  default: "."
@@ -15,8 +15,8 @@
15
15
  class Testguard < Formula
16
16
  desc "Proves a test suite defends the claims a project makes"
17
17
  homepage "https://github.com/raccioly/testguard"
18
- url "https://registry.npmjs.org/testguard-cli/-/testguard-cli-0.12.0.tgz"
19
- sha256 "e1201ef6dfeb6c7fbeb92dfe5d9709cced68d4ea2528b3d1de523504e76dc71c"
18
+ url "https://registry.npmjs.org/testguard-cli/-/testguard-cli-0.13.0.tgz"
19
+ sha256 "27079e0d5870ad86ccad4df967e50898eddacd9d66b2391120da58970ce57a34"
20
20
  license "MIT"
21
21
 
22
22
  depends_on "node"
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "testguard-cli"
7
- version = "0.12.0"
7
+ version = "0.13.0"
8
8
  description = "Breaks your code on purpose and reports every promise your tests did not notice breaking. Mutation testing bound to the claims a project makes — vitest, jest, Playwright, pytest and unittest. Ships the Node.js CLI (requires Node.js 20+)."
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }
@@ -7,7 +7,7 @@ import { reproduces, decimals, MAX_PLACES } from './wilson.mjs';
7
7
 
8
8
  const schemaDir = join(dirname(fileURLToPath(import.meta.url)), '..', 'schemas');
9
9
 
10
- export const KINDS = Object.freeze(['claims', 'evidence', 'baseline', 'ignore', 'calibration', 'brief', 'status', 'gate', 'replay', 'sweep']);
10
+ export const KINDS = Object.freeze(['claims', 'evidence', 'baseline', 'ignore', 'calibration', 'brief', 'status', 'gate', 'replay', 'sweep', 'concerns']);
11
11
  /**
12
12
  * How a document says it tried to falsify its claims. Absent means
13
13
  * `fault-injection`, so every document written before the field existed is
@@ -305,6 +305,29 @@ const semantic = {
305
305
  return errors;
306
306
  },
307
307
 
308
+ concerns(doc) {
309
+ const errors = [];
310
+ const seen = new Set();
311
+ doc.concerns.forEach((c, i) => {
312
+ if (seen.has(c.id)) errors.push({ path: `/concerns/${i}/id`, message: `duplicate concern id "${c.id}"` });
313
+ seen.add(c.id);
314
+ // A glob target with no globs matches nothing, which reads as "this
315
+ // concern found no problems" rather than "this concern was never aimed".
316
+ if (c.targets?.kind === 'glob' && !(c.targets.globs?.length > 0)) {
317
+ errors.push({ path: `/concerns/${i}/targets/globs`, message: `concern "${c.id}" targets a glob but names none; it would match nothing and report clean` });
318
+ }
319
+ if (c.targets?.kind !== 'glob' && c.targets?.globs) {
320
+ errors.push({ path: `/concerns/${i}/targets/globs`, message: `concern "${c.id}" names globs but does not target them` });
321
+ }
322
+ // An empty list is not "every class"; it is a concern that can never
323
+ // propose anything, which is a typo for `null` every time.
324
+ if (Array.isArray(c.faultClasses) && c.faultClasses.length === 0) {
325
+ errors.push({ path: `/concerns/${i}/faultClasses`, message: `concern "${c.id}" allows no fault class; omit the field for every class` });
326
+ }
327
+ });
328
+ return errors;
329
+ },
330
+
308
331
  sweep(doc) {
309
332
  const errors = [];
310
333
  const s = doc.selection;
@@ -318,10 +341,17 @@ const semantic = {
318
341
  // measured, and its absence on a save-paths sweep hides the denominator
319
342
  // entirely — which is the whole reason that mode exists.
320
343
  const saves = doc.scope.mode === 'save-paths';
321
- for (const k of ['writeSites', 'payloadFields']) {
344
+ for (const k of ['writeSites', 'payloadFields', 'provability']) {
322
345
  if (saves && doc.scope[k] === undefined) errors.push({ path: `/scope/${k}`, message: `a save-paths sweep must report ${k}: the surface is the denominator its findings are read against` });
323
346
  if (!saves && doc.scope[k] !== undefined) errors.push({ path: `/scope/${k}`, message: `${k} belongs to a save-paths sweep; a changed sweep did not measure the write surface` });
324
347
  }
348
+ if (saves && doc.scope.provability) {
349
+ const pv = doc.scope.provability;
350
+ const total = pv.mocked + pv.unmocked + pv.none;
351
+ // Every target is classified exactly once. A surface whose provability
352
+ // does not account for all of it has a bucket nobody looked in.
353
+ if (total !== doc.scope.targets) errors.push({ path: '/scope/provability', message: `provability accounts for ${total} files; targets is ${doc.scope.targets}` });
354
+ }
325
355
  if (saves && doc.scope.writeSites !== undefined && doc.scope.targets > doc.scope.writeSites) {
326
356
  errors.push({ path: '/scope/targets', message: `targets (${doc.scope.targets}) exceeds writeSites (${doc.scope.writeSites}); every target is a file with at least one write` });
327
357
  }
@@ -343,6 +373,12 @@ const semantic = {
343
373
  const notKilled = probed - (doc.counts.killed ?? 0);
344
374
  if (doc.findings.length !== notKilled) errors.push({ path: '/findings', message: `${doc.findings.length} findings for ${notKilled} records that were not killed; findings are every non-killed record` });
345
375
 
376
+ // An ordering that claims to have learned from something must name it.
377
+ if (doc.ordering) {
378
+ if (doc.ordering.observed > 0 && doc.ordering.sources.length === 0) errors.push({ path: '/ordering/sources', message: 'an ordering learned from evidence must name the documents it was learned from' });
379
+ if (doc.ordering.observed === 0 && doc.ordering.sources.length > 0) errors.push({ path: '/ordering/observed', message: 'sources are named but nothing was observed; the ordering used the shipped prior' });
380
+ }
381
+
346
382
  const gating = doc.findings.filter((f) => f.verdict === 'survived' || f.verdict === 'nocover').length;
347
383
  if (gating > 0 && doc.exitCode !== 1) errors.push({ path: '/exitCode', message: 'a survived or nocover finding must exit 1; a sweep never passes over a deliberate break nothing noticed' });
348
384
  if (gating === 0 && doc.exitCode !== 0) errors.push({ path: '/exitCode', message: 'exit 1 requires a survived or nocover finding; a sweep does not fail on its own unanchorable proposal' });
@@ -0,0 +1,41 @@
1
+ {
2
+ "$id": "urn:claimspec:v1:concerns",
3
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
4
+ "title": "claimspec v1 — concerns",
5
+ "description": "A concern is one sentence a human writes that generates faults across a whole surface: it says WHERE to look and WHICH shapes of break are relevant. It is the rung between writing nothing and writing a claim per behaviour. A concern is never a claim and never becomes one by itself — it says where to look, the probe says what it found, and a human states the sentence worth defending. Nothing in this document is a promise about the code; only a claim is that.",
6
+ "type": "object",
7
+ "required": ["schemaVersion", "concerns"],
8
+ "additionalProperties": false,
9
+ "properties": {
10
+ "$schema": { "type": "string" },
11
+ "schemaVersion": { "const": 1 },
12
+ "concerns": {
13
+ "type": "array",
14
+ "items": {
15
+ "type": "object",
16
+ "required": ["id", "statement"],
17
+ "additionalProperties": false,
18
+ "properties": {
19
+ "id": { "$ref": "urn:claimspec:v1:common#/$defs/identifier", "description": "Declaring an id that matches a built-in REPLACES it, which is how a project retunes a default for an ORM or a layout this tool does not recognise. The replacement is reported, never silent." },
20
+ "statement": { "type": "string", "minLength": 1, "description": "The sentence, in plain language. It describes a KIND of promise, not one behaviour: \"every write persists the data it was given\", not \"the register action stores password_hash\". The second is a claim." },
21
+ "severity": { "$ref": "urn:claimspec:v1:common#/$defs/severity" },
22
+ "targets": {
23
+ "type": "object",
24
+ "required": ["kind"],
25
+ "additionalProperties": false,
26
+ "description": "Where this concern applies. `write-sites` asks the write enumerator. `glob` matches paths. `changed` defers to whatever diff the caller is already measuring — a concern does not re-derive what the gate computed.",
27
+ "properties": {
28
+ "kind": { "enum": ["write-sites", "glob", "changed"] },
29
+ "globs": { "type": "array", "items": { "type": "string", "minLength": 1 } }
30
+ }
31
+ },
32
+ "faultClasses": {
33
+ "type": ["array", "null"],
34
+ "items": { "$ref": "urn:claimspec:v1:common#/$defs/faultClass" },
35
+ "description": "Which producers are relevant. `null` or absent means every class, the honest default for a concern with no shape to narrow by. Narrowing is what makes one sentence useful across two hundred files: a persistence concern that also reported every altered return value would bury the payload findings it exists to surface."
36
+ }
37
+ }
38
+ }
39
+ }
40
+ }
41
+ }