testguard-cli 0.7.0__tar.gz → 0.8.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.
- testguard_cli-0.8.0/.github/scripts/check-cost-budget.mjs +43 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/.github/workflows/ci.yml +8 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/CHANGELOG.md +116 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/PKG-INFO +15 -12
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/README.md +14 -11
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/action.yml +1 -1
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/docs-canonical/DATA-MODEL.md +3 -1
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/docs-canonical/REQUIREMENTS.md +4 -4
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/docs-canonical/TEST-SPEC.md +2 -2
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/package-lock.json +2 -2
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/package.json +1 -1
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/packaging/gitlab/testguard.gitlab-ci.yml +2 -2
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/packaging/homebrew/testguard.rb +1 -1
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/pyproject.toml +1 -1
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/spec/GATE-SEMANTICS.md +98 -3
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/spec/README.md +5 -2
- testguard_cli-0.8.0/spec/conformance/examples/calibration-websec.json +98 -0
- testguard_cli-0.8.0/spec/conformance/examples/calibration.json +51 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/spec/conformance/examples/claims-methods.json +14 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/spec/conformance/examples/evidence-scan-method.json +5 -1
- testguard_cli-0.8.0/spec/conformance/invalid/calibration.backoff-cell-not-wilson.json +98 -0
- testguard_cli-0.8.0/spec/conformance/invalid/calibration.breakdown-does-not-sum-to-n.json +56 -0
- testguard_cli-0.8.0/spec/conformance/invalid/calibration.ci-not-wilson.json +44 -0
- testguard_cli-0.8.0/spec/conformance/invalid/calibration.compound-key-arity-mismatch.json +107 -0
- testguard_cli-0.8.0/spec/conformance/invalid/calibration.fallback-without-basis.json +97 -0
- testguard_cli-0.7.0/spec/conformance/examples/calibration.json → testguard_cli-0.8.0/spec/conformance/invalid/calibration.p-not-positives-over-n.json +2 -2
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/spec/conformance/invalid/calibration.p-outside-ci.json +1 -1
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/spec/conformance/invalid/calibration.positives-exceed-n.json +1 -1
- testguard_cli-0.8.0/spec/conformance/invalid/claims.method-detail-on-a-specified-method.json +37 -0
- testguard_cli-0.8.0/spec/conformance/invalid/evidence.method-detail-on-an-injecting-run.json +192 -0
- testguard_cli-0.8.0/spec/conformance/schemas.test.mjs +132 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/spec/lib/validate.mjs +41 -6
- testguard_cli-0.8.0/spec/lib/wilson.mjs +112 -0
- testguard_cli-0.8.0/spec/schemas/calibration.schema.json +124 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/spec/schemas/claims.schema.json +8 -1
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/spec/schemas/evidence.schema.json +6 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/src/commands/replay.mjs +1 -1
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/src/probe/cost.mjs +60 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/src/replay/replay.mjs +47 -20
- testguard_cli-0.8.0/test/calibration.test.mjs +73 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/test/cost.test.mjs +62 -1
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/test/replay.test.mjs +5 -39
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/testguard.claims.json +344 -9
- testguard_cli-0.8.0/testguard.cost-budget.json +37 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/testguard.ignore.json +7 -0
- testguard_cli-0.7.0/spec/conformance/schemas.test.mjs +0 -63
- testguard_cli-0.7.0/spec/schemas/calibration.schema.json +0 -54
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/.agents/skills/testguard/SKILL.md +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/.codex/hooks.json +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/.docguard.json +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/.docguardignore +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/.gitattributes +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/.github/dependabot.yml +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/.github/scripts/build-one-pager.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/.github/scripts/check-python-fixture.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/.github/scripts/install-smoke.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/.github/scripts/sync-release-version.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/.github/workflows/auto-merge.yml +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/.github/workflows/release.yml +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/.github/workflows/scheduled-release.yml +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/.github/workflows/supply-chain.yml +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/.gitignore +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/.npmignore +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/.npmrc +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/.pre-commit-config.yaml +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/.pre-commit-hooks.yaml +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/AGENTS.md +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/CLAUDE.md +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/CODE_OF_CONDUCT.md +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/CONTRIBUTING.md +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/DRIFT-LOG.md +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/LICENSE +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/PRIVACY.md +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/SECURITY.md +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/SUPPORT.md +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/bench/README.md +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/cli/testguard.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/docs/testguard-explained.html +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/docs/testguard-explained.pdf +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/docs-canonical/ARCHITECTURE.md +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/docs-canonical/ENVIRONMENT.md +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/docs-canonical/SECURITY.md +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/fixtures/known-answer/.gitignore +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/fixtures/known-answer/README.md +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/fixtures/known-answer/expected.json +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/fixtures/known-answer/package.json +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/fixtures/known-answer/src/export.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/fixtures/known-answer/src/redact.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/fixtures/known-answer/test/export-mocked.test.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/fixtures/known-answer/test/flaky.test.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/fixtures/known-answer/test/redact.test.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/fixtures/known-answer/testguard.claims.json +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/fixtures/known-answer/vitest.config.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/fixtures/known-answer-jest/.gitignore +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/fixtures/known-answer-jest/README.md +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/fixtures/known-answer-jest/expected.json +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/fixtures/known-answer-jest/jest.config.js +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/fixtures/known-answer-jest/package.json +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/fixtures/known-answer-jest/src/export.js +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/fixtures/known-answer-jest/src/redact.js +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/fixtures/known-answer-jest/test/export-mocked.test.js +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/fixtures/known-answer-jest/test/flaky.test.js +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/fixtures/known-answer-jest/test/redact.test.js +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/fixtures/known-answer-jest/testguard.claims.json +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/fixtures/known-answer-playwright/.gitignore +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/fixtures/known-answer-playwright/README.md +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/fixtures/known-answer-playwright/e2e/flaky.spec.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/fixtures/known-answer-playwright/e2e/toggle.spec.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/fixtures/known-answer-playwright/expected.json +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/fixtures/known-answer-playwright/package-lock.json +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/fixtures/known-answer-playwright/package.json +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/fixtures/known-answer-playwright/playwright.config.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/fixtures/known-answer-playwright/src/toggle.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/fixtures/known-answer-playwright/test/toggle.test.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/fixtures/known-answer-playwright/test-results/.last-run.json +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/fixtures/known-answer-playwright/testguard.claims.json +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/fixtures/known-answer-playwright/vitest.config.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/fixtures/known-answer-python/.gitignore +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/fixtures/known-answer-python/README.md +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/fixtures/known-answer-python/demo/__init__.py +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/fixtures/known-answer-python/demo/export.py +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/fixtures/known-answer-python/demo/orphan.py +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/fixtures/known-answer-python/demo/pipeline.py +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/fixtures/known-answer-python/demo/redact.py +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/fixtures/known-answer-python/expected.json +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/fixtures/known-answer-python/testguard.claims.json +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/fixtures/known-answer-python/tests/test_export_mocked.py +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/fixtures/known-answer-python/tests/test_flaky.py +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/fixtures/known-answer-python/tests/test_patched.py +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/fixtures/known-answer-python/tests/test_redact.py +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/spec/conformance/examples/baseline.json +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/spec/conformance/examples/brief.json +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/spec/conformance/examples/claims.json +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/spec/conformance/examples/evidence-disturbed-run.json +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/spec/conformance/examples/evidence-negative-control.json +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/spec/conformance/examples/evidence-provisional.json +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/spec/conformance/examples/evidence-python.json +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/spec/conformance/examples/evidence.json +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/spec/conformance/examples/gate.json +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/spec/conformance/examples/ignore.json +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/spec/conformance/examples/replay.json +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/spec/conformance/examples/status.json +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/spec/conformance/invalid/baseline.bad-fingerprint-key.json +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/spec/conformance/invalid/baseline.zero-count.json +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/spec/conformance/invalid/brief.text-without-heading.json +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/spec/conformance/invalid/brief.unknown-verdict-key.json +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/spec/conformance/invalid/claims.absolute-path.json +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/spec/conformance/invalid/claims.duplicate-fault-id.json +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/spec/conformance/invalid/claims.fault-injection-without-a-substitution.json +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/spec/conformance/invalid/claims.missing-provenance.json +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/spec/conformance/invalid/claims.noop-fault.json +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/spec/conformance/invalid/claims.occurrence-exceeds-hits.json +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/spec/conformance/invalid/claims.parent-traversal.json +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/spec/conformance/invalid/evidence.annotated-signal-without-reason.json +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/spec/conformance/invalid/evidence.attribute-signal-not-a-defender.json +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/spec/conformance/invalid/evidence.attribute-signal-without-attributes.json +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/spec/conformance/invalid/evidence.byrunner-file-not-resolved.json +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/spec/conformance/invalid/evidence.contention-without-runners.json +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/spec/conformance/invalid/evidence.discovered-defender-mocks-subject.json +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/spec/conformance/invalid/evidence.fault-injection-without-its-evidence.json +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/spec/conformance/invalid/evidence.flaky-defender-without-flakiness.json +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/spec/conformance/invalid/evidence.ignored-dirty-on-snapshot.json +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/spec/conformance/invalid/evidence.killed-by-non-assertion.json +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/spec/conformance/invalid/evidence.killed-on-red-baseline.json +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/spec/conformance/invalid/evidence.killed-without-n-runs.json +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/spec/conformance/invalid/evidence.negative-control-not-reached-but-survived.json +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/spec/conformance/invalid/evidence.negative-control-reached-on-a-kill.json +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/spec/conformance/invalid/evidence.probe-error-without-message.json +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/spec/conformance/invalid/evidence.provisional-flag-on-confirmed-run.json +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/spec/conformance/invalid/evidence.rank-extra-property.json +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/spec/conformance/invalid/evidence.restore-skipped-in-place.json +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/spec/conformance/invalid/evidence.single-run-without-provisional.json +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/spec/conformance/invalid/evidence.target-not-imported-but-killed.json +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/spec/conformance/invalid/evidence.undeclared-killers-without-reason.json +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/spec/conformance/invalid/evidence.unknown-verdict.json +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/spec/conformance/invalid/evidence.unverifiable-without-reason.json +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/spec/conformance/invalid/evidence.wrong-fingerprint.json +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/spec/conformance/invalid/gate.counts-disagree.json +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/spec/conformance/invalid/gate.exit-zero-with-uncovered.json +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/spec/conformance/invalid/gate.ignore-cover-without-pattern.json +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/spec/conformance/invalid/ignore.missing-reason.json +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/spec/conformance/invalid/ignore.short-reason.json +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/spec/conformance/invalid/replay.blind-with-a-failing-run.json +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/spec/conformance/invalid/replay.duplicate-patch-id.json +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/spec/conformance/invalid/status.claim-without-file.json +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/spec/conformance/invalid/status.clean-with-new-findings.json +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/spec/conformance/invalid/status.unclaimed-hidden-behind-unproven.json +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/spec/conformance/invalid/status.write-test-without-target.json +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/spec/lib/fingerprint.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/spec/schemas/baseline.schema.json +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/spec/schemas/brief.schema.json +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/spec/schemas/common.schema.json +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/spec/schemas/gate.schema.json +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/spec/schemas/ignore.schema.json +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/spec/schemas/replay.schema.json +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/spec/schemas/status.schema.json +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/src/admit/admit.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/src/baseline/baseline.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/src/brief/brief.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/src/claims/annotations.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/src/claims/load.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/src/claims/removed.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/src/cli.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/src/commands/admit.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/src/commands/baseline.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/src/commands/brief.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/src/commands/claims.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/src/commands/gate.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/src/commands/init.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/src/commands/mcp.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/src/commands/probe.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/src/commands/scaffold.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/src/commands/status.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/src/evidence/writer.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/src/gate/changed.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/src/git.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/src/init/init.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/src/init/templates/SKILL.md +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/src/mcp/server.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/src/mcp/tools.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/src/probe/attribution.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/src/probe/classify.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/src/probe/contention.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/src/probe/discover.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/src/probe/independence.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/src/probe/inject.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/src/probe/mocks.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/src/probe/probe.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/src/probe/progress.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/src/probe/pyimports.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/src/probe/rank.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/src/probe/runners/index.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/src/probe/runners/jest.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/src/probe/runners/playwright.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/src/probe/runners/python/_testguard_pytest_plugin.py +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/src/probe/runners/python/_testguard_report.py +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/src/probe/runners/python/_testguard_unittest_main.py +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/src/probe/runners/python.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/src/probe/runners/shared.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/src/probe/runners/vitest.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/src/probe/worktree.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/src/render.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/src/replay/label.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/src/scaffold/producers.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/src/scaffold/producers.python.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/src/scaffold/scaffold.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/src/status/status.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/src/util/glob.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/src/util/hash.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/test/admit.test.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/test/annotations.test.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/test/attribution.test.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/test/baseline.test.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/test/brief.test.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/test/claims-since.test.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/test/classify.test.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/test/contention.test.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/test/discover.test.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/test/gate.test.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/test/gitlab-template.test.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/test/glob.test.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/test/independence.test.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/test/init.test.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/test/inject.test.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/test/mcp.test.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/test/mocks.test.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/test/negative-control.test.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/test/probe-error.test.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/test/probe-preconditions.test.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/test/probe.fixture.test.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/test/probe.jest.test.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/test/probe.playwright.test.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/test/probe.python.test.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/test/progress.test.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/test/pyimports.test.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/test/rank-aliases.test.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/test/release-sync.test.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/test/runner-argv.test.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/test/runner-command.test.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/test/runner-playwright.test.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/test/runner-python.test.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/test/runner-vitest.test.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/test/samples/playwright-report.json +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/test/samples/python-report-collect-error.json +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/test/samples/python-report.json +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/test/scaffold.python.test.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/test/scaffold.test.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/test/snapshot.test.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/test/status.test.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/test/worktree.test.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/test/writer.test.mjs +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/testguard_cli/__init__.py +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/testguard_cli/wrapper.py +0 -0
- {testguard_cli-0.7.0 → testguard_cli-0.8.0}/vitest.config.mjs +0 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Fail CI when the self-probe gate outgrows its budget.
|
|
4
|
+
*
|
|
5
|
+
* The decision lives in `src/probe/cost.mjs` as a pure function, unit-tested
|
|
6
|
+
* and covered by a self-claim; this script is only wiring — read the evidence,
|
|
7
|
+
* read the budget, print, choose an exit code.
|
|
8
|
+
*
|
|
9
|
+
* usage: check-cost-budget.mjs <evidence.json> [previous-evidence.json]
|
|
10
|
+
*/
|
|
11
|
+
import { readFileSync } from 'node:fs';
|
|
12
|
+
import { dirname, join, resolve } from 'node:path';
|
|
13
|
+
import { fileURLToPath } from 'node:url';
|
|
14
|
+
import { costReport, checkCostBudget, renderCostBudget } from '../../src/probe/cost.mjs';
|
|
15
|
+
|
|
16
|
+
const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..', '..');
|
|
17
|
+
const [evidencePath, previousPath] = process.argv.slice(2);
|
|
18
|
+
if (!evidencePath) {
|
|
19
|
+
console.error('usage: check-cost-budget.mjs <evidence.json> [previous-evidence.json]');
|
|
20
|
+
process.exit(2);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const read = (p) => JSON.parse(readFileSync(resolve(p), 'utf8'));
|
|
24
|
+
const budget = read(join(ROOT, 'testguard.cost-budget.json'));
|
|
25
|
+
|
|
26
|
+
// A previous run is informational only. Its absence must never fail the check:
|
|
27
|
+
// the first run on a branch, or a cold cache, has nothing to compare against.
|
|
28
|
+
let previousMs;
|
|
29
|
+
if (previousPath) {
|
|
30
|
+
try {
|
|
31
|
+
previousMs = costReport(read(previousPath).records).totalMs;
|
|
32
|
+
} catch {
|
|
33
|
+
previousMs = undefined;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const decision = checkCostBudget(costReport(read(evidencePath).records), { budgetSeconds: budget.seconds, previousMs });
|
|
38
|
+
const text = renderCostBudget(decision);
|
|
39
|
+
console.log(text);
|
|
40
|
+
if (!decision.ok) {
|
|
41
|
+
console.log(`::error::${text.split('\n')[0]}`);
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
@@ -210,6 +210,14 @@ jobs:
|
|
|
210
210
|
if: matrix.node-version == 22
|
|
211
211
|
run: node cli/testguard.mjs probe . --budget 180000 --no-escalate --cost --out .testguard/ci-self-evidence.json
|
|
212
212
|
|
|
213
|
+
# The gate went 9.6 -> 23.6 min across one merged PR and nothing said a
|
|
214
|
+
# word, because --cost only ever printed into a log. `always()` so the
|
|
215
|
+
# cost is reported even when the probe itself failed: a run that got
|
|
216
|
+
# slower AND found a survivor should say both, not just the second.
|
|
217
|
+
- name: The gate must stay inside its cost budget
|
|
218
|
+
if: always() && matrix.node-version == 22
|
|
219
|
+
run: node .github/scripts/check-cost-budget.mjs .testguard/ci-self-evidence.json
|
|
220
|
+
|
|
213
221
|
# A stable, branch-named artifact so a developer can fetch the evidence
|
|
214
222
|
# CI produced without knowing a run id (README: read CI's evidence
|
|
215
223
|
# locally). One Node leg is enough; the verdicts are identical.
|
|
@@ -7,6 +7,122 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.8.0] - 2026-09-18
|
|
11
|
+
|
|
12
|
+
A calibration you can quote.
|
|
13
|
+
|
|
14
|
+
A calibration document now says what its number means and where its labels
|
|
15
|
+
came from, and it reproduces: `p` and every interval are recomputed by the
|
|
16
|
+
validator from `n`, `positives`, `method` and `confidence`, at the document's
|
|
17
|
+
own precision — the spec's own example had carried a truncated bound since
|
|
18
|
+
the day it was written, and nothing could notice. `nocover` counts as the
|
|
19
|
+
miss it is, so a project with no tests at all for a subsystem no longer
|
|
20
|
+
outscores one with weak tests. The format also carries what
|
|
21
|
+
websec-validator's shipped table already said — corpus, caveat, a producer
|
|
22
|
+
floor, backoff tiers, a labelled prior — so a second tool's honesty survives
|
|
23
|
+
translation; that table is now a conformance example, and "adoptable" means
|
|
24
|
+
the file validates rather than a sentence saying so. Underneath: the
|
|
25
|
+
self-probe gate is held to a cost budget (#89), and a reserved method has
|
|
26
|
+
somewhere to put its data (#91).
|
|
27
|
+
|
|
28
|
+
### Added
|
|
29
|
+
- **A calibration can now say what it measures and where it came from** (#82).
|
|
30
|
+
websec-validator's shipped `calibration.json` already carried `corpus`,
|
|
31
|
+
`min_n`, `caveat`, `evidence_status` and `limitation` — the fields that say
|
|
32
|
+
"indicative, do not quote this number" — and claimspec had nowhere to put
|
|
33
|
+
them, so adopting it would have stripped that honesty in translation. It
|
|
34
|
+
also had nowhere to say what `p` *was*: testguard's is a miss rate where
|
|
35
|
+
high is bad, websec's is P(real) where high is good, and both validated
|
|
36
|
+
identically. The schema gains `measures` (a registry: `escape-missed`,
|
|
37
|
+
`finding-real`), provenance in `source` (`corpus`, `caveat`, `limitation`,
|
|
38
|
+
`evidenceStatus`, a `tool-oracle` kind, and an unconstrained `detail` on the
|
|
39
|
+
`methodDetail` pattern), a producer floor `minN` that consumers may raise
|
|
40
|
+
and never lower, ordered `backoff` tiers so a class→label→prior cascade is
|
|
41
|
+
expressible, a `fallback` whose shape says it is a guess, a per-cell
|
|
42
|
+
`breakdown` that sums to `n`, `p: null` at `n = 0`, and `|`-compound bucket
|
|
43
|
+
keys whose arity the validator checks. Every field is optional and absence
|
|
44
|
+
means *unattributed* — readable, not quotable — so no existing document
|
|
45
|
+
breaks; testguard's emitter always writes them and a self-claim guards it.
|
|
46
|
+
The proof of adoption is a file, not a sentence: websec-validator's table,
|
|
47
|
+
translated field for field, is now `spec/conformance/examples/calibration-websec.json`
|
|
48
|
+
and validates with every number reproducing. `GATE-SEMANTICS.md` records
|
|
49
|
+
the merge rule (sum counts, recompute, never average; only when `measures`
|
|
50
|
+
and `bucketBy` agree) that websec's shipped-plus-local overlay already
|
|
51
|
+
depends on.
|
|
52
|
+
- **The gate is now gated on its own cost** (#89). `--cost` has printed the
|
|
53
|
+
self-probe's wall clock into every CI log since #67, and the gate still went
|
|
54
|
+
from 9.6 to 23.6 minutes across one merged change without anything saying a
|
|
55
|
+
word. A measurement nothing gates on is not a check — which is the premise
|
|
56
|
+
this tool rests on, applied to itself.
|
|
57
|
+
`testguard.cost-budget.json` carries the ceiling, its measurement and why it
|
|
58
|
+
is set where it is. CI fails when the probe exceeds it and opens with the
|
|
59
|
+
most expensive claims, so the first thing a failure shows is where to look.
|
|
60
|
+
Raising it is a committed diff with a reason: growth is allowed, going
|
|
61
|
+
unnoticed is not.
|
|
62
|
+
The budget is on the **total**, never a per-fault average. The regression
|
|
63
|
+
that prompted this took the corpus from 59 faults to 91 while per-fault cost
|
|
64
|
+
barely moved — an average would have reported everything fine while the gate
|
|
65
|
+
tripled, and a test says so. A budget that is missing or not a positive
|
|
66
|
+
number is refused rather than treated as unlimited, so a typo cannot quietly
|
|
67
|
+
disable the gate.
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
### Fixed
|
|
71
|
+
- **The cost budget is set against what CI measures.** 950 s was 18% over
|
|
72
|
+
the local 804 s figure and only 4.6% over the 906 s CI already measured on
|
|
73
|
+
`main`, so the first pull request to add any claim tripped it — #94 passed
|
|
74
|
+
its own leg only because the cache reused verdicts, and `main` went red at
|
|
75
|
+
981 s from scratch the moment it merged. Raised to 1100 s (~14% over the
|
|
76
|
+
962 s CI measures with #82's re-pointed defenders), with the CI figures
|
|
77
|
+
recorded beside the local one so the next person budgets against the
|
|
78
|
+
number the check actually sees. The heavy claims the failure now names are
|
|
79
|
+
the next #73-style cut.
|
|
80
|
+
- **Calibration's pure tests moved off the replay fixture** (the #73 pattern).
|
|
81
|
+
Three calibration claims were defended by `test/replay.test.mjs`, which
|
|
82
|
+
builds a scripted git corpus on every run; their kills come from pure
|
|
83
|
+
`calibrationFrom` tests that need none of it. The cost budget fired on the
|
|
84
|
+
first full run — 1022 s against 950 s, the top rows being exactly those
|
|
85
|
+
claims — so the tests now live in `test/calibration.test.mjs`, the claims
|
|
86
|
+
point there, and every fault was re-probed after the move, not assumed.
|
|
87
|
+
- **A cost-budget failure names its claims.** `checkCostBudget` read `id`
|
|
88
|
+
from a report whose claims carry `claimId`, so the one line meant to say
|
|
89
|
+
*which* claims to look at printed `undefined` five times — and its unit test
|
|
90
|
+
fed the shape the code wished for, so it passed. Found the first time the
|
|
91
|
+
budget actually fired. The test now goes through `costReport`, and
|
|
92
|
+
`TG-COST-BUDGET-NAMES-THE-CLAIM` guards it.
|
|
93
|
+
- **A calibration counts `nocover` as a miss** (#82). `nocover` — no test
|
|
94
|
+
even imports the broken file — is the worst replay outcome, and the ratio
|
|
95
|
+
excluded it: a project with no tests at all for a subsystem scored *better*
|
|
96
|
+
than one with weak tests, because its worst outcomes left the denominator
|
|
97
|
+
before the ratio was taken. It now enters both sides, as it does in Stryker
|
|
98
|
+
and PIT, and the replay summary says how many misses were blind and how many
|
|
99
|
+
had no test at all. The schema description, the emitter's docstring and
|
|
100
|
+
`GATE-SEMANTICS.md` had also disagreed about what `p` was — "P(finding is
|
|
101
|
+
real)" in one place, the miss rate in another, and the docstring's two
|
|
102
|
+
sentences conditioning on different denominators — and now say one thing:
|
|
103
|
+
for testguard, the share of real escaped bugs the suite failed to catch.
|
|
104
|
+
- **A calibration's arithmetic is checked, not trusted.** The validator
|
|
105
|
+
recomputed nothing: it checked that `p` sat inside `ci` and would pass a
|
|
106
|
+
document with every number invented. It now recomputes `p` from
|
|
107
|
+
`positives / n` and `ci` from `n`, `positives`, `method` and `confidence`
|
|
108
|
+
with `spec/lib/wilson.mjs` — one implementation, shared with the emitter —
|
|
109
|
+
at the document's own precision, accepting either the exact quantile or the
|
|
110
|
+
textbook 1.96. The spec's own example had carried a truncated lower bound
|
|
111
|
+
(6/30 → `0.09`; Wilson gives 0.0950 → `0.10`) since the day it was written,
|
|
112
|
+
and websec-validator's shipped table reproduces exactly.
|
|
113
|
+
- **A reserved method can now say something.** #81 reserved `assertion` and
|
|
114
|
+
`scan` without required fields, and `fault` / `detail` are both
|
|
115
|
+
`additionalProperties: false` — so a probe using a reserved method could
|
|
116
|
+
declare that it existed and **nothing whatsoever about it**: not which rule
|
|
117
|
+
was checked, not where. That is not a reservation, it is a dead end, and
|
|
118
|
+
DocGuard's writer would have hit it on its first line.
|
|
119
|
+
Both now carry `methodDetail`: an object the spec deliberately does not
|
|
120
|
+
constrain, on the probe and on the record. It is **forbidden under
|
|
121
|
+
`fault-injection`**, whose shape is specified and must not acquire a junk
|
|
122
|
+
drawer — a self-claim fails if that ever stops being enforced — and it is a
|
|
123
|
+
staging area rather than a permanent home. What the first real consumer puts
|
|
124
|
+
there is the evidence for what the specified shape should become.
|
|
125
|
+
|
|
10
126
|
## [0.7.0] - 2026-09-18
|
|
11
127
|
|
|
12
128
|
Python, and a fault that cannot hide.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: testguard-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.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
|
|
@@ -150,9 +150,9 @@ raises coverage. TestGuard admits it because it fails when the claim is false.
|
|
|
150
150
|
| npm | `npm i -D testguard-cli` then `npx testguard probe` |
|
|
151
151
|
| pip | `pip install testguard-cli` then `testguard probe` (needs Node ≥ 20) |
|
|
152
152
|
| Homebrew | `brew tap raccioly/tap && brew install testguard` |
|
|
153
|
-
| GitHub Action | `uses: raccioly/testguard@v0.
|
|
153
|
+
| GitHub Action | `uses: raccioly/testguard@v0.8.0` — see [`action.yml`](./action.yml) |
|
|
154
154
|
| pre-commit | `repo: https://github.com/raccioly/testguard`, hooks `testguard-claims`, `testguard-probe` |
|
|
155
|
-
| GitLab CI | `include: - remote: https://raw.githubusercontent.com/raccioly/testguard/v0.
|
|
155
|
+
| GitLab CI | `include: - remote: https://raw.githubusercontent.com/raccioly/testguard/v0.8.0/packaging/gitlab/testguard.gitlab-ci.yml` with `inputs:` — see [`packaging/gitlab/`](./packaging/gitlab/testguard.gitlab-ci.yml) |
|
|
156
156
|
|
|
157
157
|
Projects that set `min-release-age` in `.npmrc` cannot see a version published
|
|
158
158
|
less than that many days ago (`ENOVERSIONS`); install that one with
|
|
@@ -369,15 +369,18 @@ most resembles, which is the join key that turns an uninterpretable mutation
|
|
|
369
369
|
score into a statement with a sample size:
|
|
370
370
|
|
|
371
371
|
```
|
|
372
|
-
guard-removed
|
|
373
|
-
field-dropped
|
|
372
|
+
guard-removed missed 7/9 p=0.78 ci [0.45, 0.94]
|
|
373
|
+
field-dropped missed 4/4 p=1.00 ci [0.51, 1.00]
|
|
374
374
|
```
|
|
375
375
|
|
|
376
|
-
Read as *"when a
|
|
377
|
-
|
|
378
|
-
`calibration` document beside the replay one.
|
|
379
|
-
|
|
380
|
-
|
|
376
|
+
Read as *"when a real bug of this class escapes, how often does the suite
|
|
377
|
+
miss it"* — a miss rate, so a high `p` is bad. It is written as a
|
|
378
|
+
`calibration` document beside the replay one. `caught`, `blind` and
|
|
379
|
+
`nocover` are measurements and enter the ratio, `nocover` as a miss: leaving
|
|
380
|
+
it out would score a project with no tests at all for a subsystem *better*
|
|
381
|
+
than one with weak tests. `flaky` and `unverifiable` are failed measurements
|
|
382
|
+
and enter neither side. Every `p` and `ci` is recomputable from `n` and the
|
|
383
|
+
miss count, and the validator recomputes them.
|
|
381
384
|
|
|
382
385
|
**The open question this exists to answer.** Does a calibration learned on a
|
|
383
386
|
repository *with* history transfer to a greenfield one that has none? AI
|
|
@@ -448,12 +451,12 @@ detected base that does not resolve is a warning for `status` and `brief`
|
|
|
448
451
|
# GitHub Actions
|
|
449
452
|
- uses: actions/checkout@v4
|
|
450
453
|
with: { fetch-depth: 0 }
|
|
451
|
-
- uses: raccioly/testguard@v0.
|
|
454
|
+
- uses: raccioly/testguard@v0.8.0
|
|
452
455
|
with: { command: gate }
|
|
453
456
|
|
|
454
457
|
# GitLab CI — the component-shaped template: gate + probe, brief as an artifact and, opted in, as a merge-request note
|
|
455
458
|
include:
|
|
456
|
-
- remote: 'https://raw.githubusercontent.com/raccioly/testguard/v0.
|
|
459
|
+
- remote: 'https://raw.githubusercontent.com/raccioly/testguard/v0.8.0/packaging/gitlab/testguard.gitlab-ci.yml'
|
|
457
460
|
inputs: { dir: backend, post_note: true } # post_note needs TESTGUARD_GITLAB_TOKEN (api scope); one note, updated in place
|
|
458
461
|
```
|
|
459
462
|
|
|
@@ -126,9 +126,9 @@ raises coverage. TestGuard admits it because it fails when the claim is false.
|
|
|
126
126
|
| npm | `npm i -D testguard-cli` then `npx testguard probe` |
|
|
127
127
|
| pip | `pip install testguard-cli` then `testguard probe` (needs Node ≥ 20) |
|
|
128
128
|
| Homebrew | `brew tap raccioly/tap && brew install testguard` |
|
|
129
|
-
| GitHub Action | `uses: raccioly/testguard@v0.
|
|
129
|
+
| GitHub Action | `uses: raccioly/testguard@v0.8.0` — see [`action.yml`](./action.yml) |
|
|
130
130
|
| pre-commit | `repo: https://github.com/raccioly/testguard`, hooks `testguard-claims`, `testguard-probe` |
|
|
131
|
-
| GitLab CI | `include: - remote: https://raw.githubusercontent.com/raccioly/testguard/v0.
|
|
131
|
+
| GitLab CI | `include: - remote: https://raw.githubusercontent.com/raccioly/testguard/v0.8.0/packaging/gitlab/testguard.gitlab-ci.yml` with `inputs:` — see [`packaging/gitlab/`](./packaging/gitlab/testguard.gitlab-ci.yml) |
|
|
132
132
|
|
|
133
133
|
Projects that set `min-release-age` in `.npmrc` cannot see a version published
|
|
134
134
|
less than that many days ago (`ENOVERSIONS`); install that one with
|
|
@@ -345,15 +345,18 @@ most resembles, which is the join key that turns an uninterpretable mutation
|
|
|
345
345
|
score into a statement with a sample size:
|
|
346
346
|
|
|
347
347
|
```
|
|
348
|
-
guard-removed
|
|
349
|
-
field-dropped
|
|
348
|
+
guard-removed missed 7/9 p=0.78 ci [0.45, 0.94]
|
|
349
|
+
field-dropped missed 4/4 p=1.00 ci [0.51, 1.00]
|
|
350
350
|
```
|
|
351
351
|
|
|
352
|
-
Read as *"when a
|
|
353
|
-
|
|
354
|
-
`calibration` document beside the replay one.
|
|
355
|
-
|
|
356
|
-
|
|
352
|
+
Read as *"when a real bug of this class escapes, how often does the suite
|
|
353
|
+
miss it"* — a miss rate, so a high `p` is bad. It is written as a
|
|
354
|
+
`calibration` document beside the replay one. `caught`, `blind` and
|
|
355
|
+
`nocover` are measurements and enter the ratio, `nocover` as a miss: leaving
|
|
356
|
+
it out would score a project with no tests at all for a subsystem *better*
|
|
357
|
+
than one with weak tests. `flaky` and `unverifiable` are failed measurements
|
|
358
|
+
and enter neither side. Every `p` and `ci` is recomputable from `n` and the
|
|
359
|
+
miss count, and the validator recomputes them.
|
|
357
360
|
|
|
358
361
|
**The open question this exists to answer.** Does a calibration learned on a
|
|
359
362
|
repository *with* history transfer to a greenfield one that has none? AI
|
|
@@ -424,12 +427,12 @@ detected base that does not resolve is a warning for `status` and `brief`
|
|
|
424
427
|
# GitHub Actions
|
|
425
428
|
- uses: actions/checkout@v4
|
|
426
429
|
with: { fetch-depth: 0 }
|
|
427
|
-
- uses: raccioly/testguard@v0.
|
|
430
|
+
- uses: raccioly/testguard@v0.8.0
|
|
428
431
|
with: { command: gate }
|
|
429
432
|
|
|
430
433
|
# GitLab CI — the component-shaped template: gate + probe, brief as an artifact and, opted in, as a merge-request note
|
|
431
434
|
include:
|
|
432
|
-
- remote: 'https://raw.githubusercontent.com/raccioly/testguard/v0.
|
|
435
|
+
- remote: 'https://raw.githubusercontent.com/raccioly/testguard/v0.8.0/packaging/gitlab/testguard.gitlab-ci.yml'
|
|
433
436
|
inputs: { dir: backend, post_note: true } # post_note needs TESTGUARD_GITLAB_TOKEN (api scope); one note, updated in place
|
|
434
437
|
```
|
|
435
438
|
|
|
@@ -42,7 +42,9 @@ defaults to `fault-injection`, whose required fields — `file`, `find`,
|
|
|
42
42
|
baseline and probe runs on evidence — are unchanged and are enforced by the
|
|
43
43
|
validator rather than merely by the schema. `assertion` and `scan` are
|
|
44
44
|
reserved names for tools that verify by reading or scanning; their fields are
|
|
45
|
-
defined by their first consumer, not in advance.
|
|
45
|
+
defined by their first consumer, not in advance. Until then a reserved-method
|
|
46
|
+
probe and record carry `methodDetail`, an unconstrained object that is
|
|
47
|
+
forbidden under `fault-injection`.
|
|
46
48
|
`spec/lib/validate.mjs` applies the schema **and** the semantic rules a schema
|
|
47
49
|
cannot express. `spec/lib/fingerprint.mjs` is the single fingerprint
|
|
48
50
|
implementation every tool must use.
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
| NFR-04 | Every emitted document conforms to the published schema before it is written | `writeSpecDoc` refuses non-conforming output |
|
|
38
38
|
| NFR-05 | Verdicts are reproducible and attributable to a commit | evidence records `repo.head`, the snapshot when the working tree was probed, and the runner and its source |
|
|
39
39
|
| NFR-06 | A verdict is never optimistic under uncertainty | contention recorded; flaky and timeout verdicts gate; escalation cannot upgrade a verdict; a survivor is unverifiable until the defenders are shown to fail because of the subject |
|
|
40
|
-
| NFR-07 | Cost grows with what changed, not with the size of the project | verdict reuse on unchanged inputs; blast-radius-targeted escalation |
|
|
40
|
+
| NFR-07 | Cost grows with what changed, not with the size of the project | verdict reuse on unchanged inputs; blast-radius-targeted escalation; a committed cost budget gated in CI, so growth is signed for rather than discovered |
|
|
41
41
|
| NFR-08 | Portable contract | schemas are JSON Schema 2020-12, readable and implementable without this codebase |
|
|
42
42
|
|
|
43
43
|
## Success Criteria
|
|
@@ -97,7 +97,7 @@ fails the pull request naming the unclaimed file, and `status` reports
|
|
|
97
97
|
| FR-10 | `src/probe/runners/` | fixture acceptance per runner, `TG-RUNNER-FROM-PROJECT-FIRST` |
|
|
98
98
|
| FR-11 | `src/scaffold/producers.mjs` | `test/scaffold.test.mjs`, `TG-SCAFFOLD-ANCHORS-HIT` |
|
|
99
99
|
| FR-12 | `src/admit/admit.mjs` | `test/admit.test.mjs`, `TG-ADMIT-NEEDS-ALL-KILLED` |
|
|
100
|
-
| FR-13 | `src/replay/` | `test/replay.test.mjs`, `TG-REPLAY-FLAKY-IS-NEVER-CAUGHT` |
|
|
100
|
+
| FR-13 | `src/replay/` | `test/replay.test.mjs`, `test/calibration.test.mjs`, `TG-REPLAY-FLAKY-IS-NEVER-CAUGHT`, `TG-CALIBRATION-COUNTS-NOCOVER-AS-A-MISS` |
|
|
101
101
|
| FR-14 | `src/mcp/` | `test/mcp.test.mjs` |
|
|
102
102
|
| NFR-01 | `src/init/init.mjs`, runner resolution | `TG-INIT-HOOK-NO-NETWORK`, `TG-README-HOOK-MATCHES-THE-CODE` |
|
|
103
103
|
| NFR-02 | `package.json` | `npm run test:install` in CI |
|
|
@@ -105,8 +105,8 @@ fails the pull request naming the unclaimed file, and `status` reports
|
|
|
105
105
|
| NFR-04 | `src/evidence/writer.mjs` | `test/writer.test.mjs` |
|
|
106
106
|
| NFR-05 | `src/probe/probe.mjs` | `TG-FINGERPRINT-VERDICT`, `TG-FAULT-EDIT-VISIBLE` |
|
|
107
107
|
| NFR-06 | `src/probe/contention.mjs`, `classify.mjs` | `TG-CONTENTION-NEVER-FAILS-A-PROBE`, `TG-ESCALATION-N-RUNS`, `TG-ONE-BAD-FAULT-KEEPS-THE-EVIDENCE`, `TG-A-PRECONDITION-STILL-REFUSES-THE-RUN`, `TG-A-PROBE-ERROR-IS-NEVER-REUSED`, `TG-SURVIVOR-PROVES-THE-SUBJECT-RUNS`, `TG-CONTROL-READS-ANY-RED-AS-REACHED`, `TG-FATAL-EDIT-CANNOT-COMPILE` |
|
|
108
|
-
| NFR-07 | verdict reuse, `src/probe/rank.mjs` | `test/probe.fixture.test.mjs` reuse case |
|
|
109
|
-
| NFR-08 | `spec/` | `spec/conformance/schemas.test.mjs` |
|
|
108
|
+
| NFR-07 | verdict reuse, `src/probe/rank.mjs`, `src/probe/cost.mjs` | `test/probe.fixture.test.mjs` reuse case, `test/cost.test.mjs`, `TG-COST-BUDGET-ACTUALLY-GATES` |
|
|
109
|
+
| NFR-08 | `spec/` | `spec/conformance/schemas.test.mjs`, `TG-WILSON-REPRODUCES-AT-THE-DOCUMENTS-PRECISION` |
|
|
110
110
|
|
|
111
111
|
## Revision History
|
|
112
112
|
|
|
@@ -20,7 +20,7 @@ the test that defends it, and CI fails if any injected fault survives.
|
|
|
20
20
|
| Unit | `test/*.test.mjs` | One pure function's branches, without a runner | milliseconds |
|
|
21
21
|
| Conformance | `spec/conformance/` | Every valid example validates; every must-reject document is rejected for the defect its filename names | ~1 s |
|
|
22
22
|
| Fixture acceptance | `test/probe.fixture.test.mjs`, `test/probe.jest.test.mjs`, `test/probe.playwright.test.mjs` | The whole pipeline reproduces a known-answer oracle, one per runner | 10–50 s each |
|
|
23
|
-
| Self-verification | `testguard.claims.json`, run by `npm run self:probe` | The tool's own invariants survive fault injection | **
|
|
23
|
+
| Self-verification | `testguard.claims.json`, run by `npm run self:probe` | The tool's own invariants survive fault injection | **13.4 min** cold (measured 2026-09-18 at `cf5a083`: 96 faults, 576 defender runs, 804 s, `--serial`, idle machine); seconds when verdicts are reused, which is why CI restores the previous evidence from cache. Budgeted at 950 s in `testguard.cost-budget.json` and **gated** in CI — the figure above went 9.6 → 23.6 min across one merged change while `--cost` printed it into a log nobody read |
|
|
24
24
|
| Install smoke | `.github/scripts/install-smoke.mjs` | The **packed tarball** runs with production dependencies only | ~20 s |
|
|
25
25
|
| Runtime budget | `ci.yml` | The suite has not silently started walking the wrong tree | gate, not a test |
|
|
26
26
|
|
|
@@ -77,7 +77,7 @@ What is gated instead:
|
|
|
77
77
|
| `src/status/status.mjs` | `test/status.test.mjs` |
|
|
78
78
|
| `src/brief/brief.mjs` | `test/brief.test.mjs` |
|
|
79
79
|
| `src/init/init.mjs` | `test/init.test.mjs` |
|
|
80
|
-
| `src/replay/` | `test/replay.test.mjs` |
|
|
80
|
+
| `src/replay/` | `test/replay.test.mjs`, `test/calibration.test.mjs` (the pure half: `calibrationFrom`, `wilson`) |
|
|
81
81
|
| `src/mcp/` | `test/mcp.test.mjs` |
|
|
82
82
|
| `spec/lib/` | `spec/conformance/schemas.test.mjs` |
|
|
83
83
|
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "testguard-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "testguard-cli",
|
|
9
|
-
"version": "0.
|
|
9
|
+
"version": "0.8.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.
|
|
3
|
+
"version": "0.8.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.
|
|
6
|
+
# - remote: 'https://raw.githubusercontent.com/raccioly/testguard/v0.8.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.
|
|
32
|
+
default: "0.8.0"
|
|
33
33
|
dir:
|
|
34
34
|
description: directory whose test runner is invoked (where testguard.claims.json lives)
|
|
35
35
|
default: "."
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
class Testguard < Formula
|
|
14
14
|
desc "Proves a test suite defends the claims a project makes"
|
|
15
15
|
homepage "https://github.com/raccioly/testguard"
|
|
16
|
-
url "https://registry.npmjs.org/testguard-cli/-/testguard-cli-0.
|
|
16
|
+
url "https://registry.npmjs.org/testguard-cli/-/testguard-cli-0.8.0.tgz"
|
|
17
17
|
sha256 "385d69f9d3c153b934d9c1cb6a2c754eb9b221b0a8c0ba8b805858384d2d4678"
|
|
18
18
|
license "MIT"
|
|
19
19
|
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "testguard-cli"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.8.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" }
|
|
@@ -167,6 +167,14 @@ Rules that follow from the table:
|
|
|
167
167
|
under `fault-injection`, and the validator enforces them there in full: the
|
|
168
168
|
relaxation is for a tool that injects nothing, never a licence for an
|
|
169
169
|
injecting tool to stop showing its work.
|
|
170
|
+
A reserved method carries its own data in `methodDetail`, on the probe and
|
|
171
|
+
on the record: an object the spec deliberately does not constrain, so a tool
|
|
172
|
+
can say which rule it checked and where. Without it, "reserved" would mean a
|
|
173
|
+
probe that can declare it exists and nothing about it, which is not a
|
|
174
|
+
reservation but a dead end. It is **forbidden under `fault-injection`**,
|
|
175
|
+
whose shape is specified and must not acquire a junk drawer, and it is a
|
|
176
|
+
staging area rather than a permanent home: what the first consumer puts
|
|
177
|
+
there is the evidence for what the specified shape should become.
|
|
170
178
|
`assertion` and `scan` are **reserved**. Their required fields are
|
|
171
179
|
deliberately unspecified and will be defined by their first real consumer,
|
|
172
180
|
with its own conformance examples written from the shape that tool actually
|
|
@@ -201,9 +209,96 @@ Two rules that follow:
|
|
|
201
209
|
2. **The fix's own test is removed before the run.** It proves nothing about
|
|
202
210
|
what the suite knew before the fix existed.
|
|
203
211
|
|
|
204
|
-
|
|
205
|
-
`
|
|
206
|
-
|
|
212
|
+
`caught`, `blind` and `nocover` are measurements and enter the calibration
|
|
213
|
+
ratio: `caught` as a hit, the other two as misses. `nocover` counts as a miss
|
|
214
|
+
because it is one — the worst kind. Leaving it out would let a project with
|
|
215
|
+
**no tests at all** for a subsystem score better than one with weak tests,
|
|
216
|
+
since its worst outcomes would leave the denominator before the ratio is
|
|
217
|
+
taken; Stryker and PIT count no-coverage mutants against the headline score
|
|
218
|
+
for the same reason. `flaky` and `unverifiable` are failed measurements and
|
|
219
|
+
enter neither side.
|
|
220
|
+
|
|
221
|
+
### Calibration arithmetic is reproducible
|
|
222
|
+
|
|
223
|
+
A calibration cell declares `n` and `positives`, and with them has declared
|
|
224
|
+
`p`; a document declares `method: wilson` at `confidence`, and with them has
|
|
225
|
+
declared every `ci`. The validator recomputes both with `lib/wilson.mjs` —
|
|
226
|
+
the single implementation, as `lib/fingerprint.mjs` is for fingerprints — and
|
|
227
|
+
a cell that does not reproduce does not conform. Before this rule the
|
|
228
|
+
validator checked only that `p` lay inside `ci`, which a document with every
|
|
229
|
+
number invented satisfies, and the spec's own example shipped a truncated
|
|
230
|
+
lower bound (6/30 → `0.09`, where Wilson gives 0.0950 → `0.10`) that nothing
|
|
231
|
+
could notice.
|
|
232
|
+
|
|
233
|
+
- **Precision is the document's own**: the most decimal places any `p` or
|
|
234
|
+
`ci` value shows, capped at 6. Values are compared after rounding to that
|
|
235
|
+
precision, so a producer at 3 dp and one at 4 dp both reproduce and a
|
|
236
|
+
truncated bound does not.
|
|
237
|
+
- **z may be the exact quantile or the textbook rounding** (1.96, 2.576,
|
|
238
|
+
1.645). The two differ by up to 9.2e-6 in the interval — enough to flip a
|
|
239
|
+
rounding boundary at 4 dp — so either reproduces, and a document never fails
|
|
240
|
+
for having used the number in the textbook.
|
|
241
|
+
- **`p` is `positives / n`.** A smoothed or shrunk point estimate is a
|
|
242
|
+
different method and must say so; under `wilson` the point estimate is the
|
|
243
|
+
observed proportion.
|
|
244
|
+
|
|
245
|
+
### Calibration provenance, backoff and merging
|
|
246
|
+
|
|
247
|
+
Everything beyond a document's primary buckets is optional and additive, in
|
|
248
|
+
the way `method` is on claims and evidence: absence has a defined meaning, and
|
|
249
|
+
no document written before a field existed becomes invalid. The meaning of
|
|
250
|
+
absence is **unattributed** — readable, auditable, and not a source of
|
|
251
|
+
numbers. A tool that emits calibrations always emits these fields; a
|
|
252
|
+
self-claim guards that it does.
|
|
253
|
+
|
|
254
|
+
- **`measures`** says what `p` is the probability of. It is the one field
|
|
255
|
+
that lets two calibrations be told apart: testguard's `escape-missed` is
|
|
256
|
+
P(a real escaped bug of this class was not caught), and high is bad;
|
|
257
|
+
websec-validator's `finding-real` is P(a reported finding of this class is a
|
|
258
|
+
real vulnerability), and high is good. A document without it cannot be
|
|
259
|
+
quoted and cannot be merged. Registry: `escape-missed`, `finding-real`. A
|
|
260
|
+
new value is added here by the tool that first emits it.
|
|
261
|
+
- **`source`** carries provenance. `corpus` names external ground-truth
|
|
262
|
+
sources (a bug-replay's is the repository's own history, which `ref` already
|
|
263
|
+
names); `caveat` is the one sentence a consumer shows beside any quoted
|
|
264
|
+
number; `limitation` is the longer story; `evidenceStatus` says whether the
|
|
265
|
+
labels were reviewed; `kind: tool-oracle` is a tool confirming its own
|
|
266
|
+
findings, as websec-validator's dynamic probes do. `detail` is the tool's
|
|
267
|
+
own provenance, unconstrained, on the `methodDetail` pattern. A number
|
|
268
|
+
quoted without its `caveat` is a misquote.
|
|
269
|
+
- **`minN`** is the producer's floor. A cell below it is not quoted on its
|
|
270
|
+
own; the consumer backs off a tier. A consumer may raise it — a gate that
|
|
271
|
+
blocks a merge wants more certainty than a report — and may never lower
|
|
272
|
+
it: lowering it resurrects a cell the producer suppressed. Absent, the
|
|
273
|
+
producer sets no floor and the consumer applies its own. A spec-level
|
|
274
|
+
constant would be wrong in both directions: websec-validator's 5 would
|
|
275
|
+
suppress every cell of a corpus the size of DocGuard's benchmark.
|
|
276
|
+
- **`backoff`** is the ordered list of coarser tiers consulted below `minN`.
|
|
277
|
+
Each tier is held to the same arithmetic as the primary. Keys may be
|
|
278
|
+
compound (`attackClass|confidence` backs off to `confidence`), and every key
|
|
279
|
+
in a tier has as many `|`-parts as its `bucketBy` — a key with fewer was
|
|
280
|
+
translated from another table and lost a dimension on the way.
|
|
281
|
+
- **`fallback`** is what remains when no tier has a cell above `minN`: a
|
|
282
|
+
labelled guess, not a measurement, and anything quoted from it carries
|
|
283
|
+
`n = 0`, `ci = [0, 1]` and its `basis`. It lives in the document so that
|
|
284
|
+
"what did it assume when it had no data?" has one auditable answer instead
|
|
285
|
+
of one per consumer, each unlabelled.
|
|
286
|
+
- **`breakdown`** splits a cell's `n` by outcome when the producer can say
|
|
287
|
+
(testguard: `caught` / `blind` / `nocover`), and sums to `n`. It lets a
|
|
288
|
+
consumer recover a narrower rate — misses among covered code, say —
|
|
289
|
+
without the producer publishing a second, misleading headline.
|
|
290
|
+
|
|
291
|
+
**Merging.** Two calibrations merge by summing `n` and `positives` per cell
|
|
292
|
+
and recomputing `p` and `ci` from the sums — never by averaging `p` or `ci`,
|
|
293
|
+
which have no meaning combined. They merge only when `measures`, `bucketBy`
|
|
294
|
+
and `confidence` agree, and the merged document's `source.kind` is `mixed`.
|
|
295
|
+
This is how websec-validator folds an operator's own confirmed samples over
|
|
296
|
+
its shipped table, and it works only because cells carry raw counts.
|
|
297
|
+
|
|
298
|
+
**The second producer is a conformance example.** websec-validator's shipped
|
|
299
|
+
table, translated field for field, is `conformance/examples/calibration-websec.json`
|
|
300
|
+
— corpus, caveat, limitation, floor, backoff tier and labelled fallback all
|
|
301
|
+
present. That file, not a sentence in a README, is what "adoptable" means.
|
|
207
302
|
|
|
208
303
|
## Baseline and delta
|
|
209
304
|
|
|
@@ -28,7 +28,7 @@ that carry claims, baselines, scoping and briefs do not care.
|
|
|
28
28
|
| `evidence` | [`schemas/evidence.schema.json`](schemas/evidence.schema.json) | One run's findings on disk, readable without the tool. Every verdict carries every run that produced it. |
|
|
29
29
|
| `baseline` | [`schemas/baseline.schema.json`](schemas/baseline.schema.json) | Frozen fingerprints of existing debt. Gate only what is new. |
|
|
30
30
|
| `ignore` | [`schemas/ignore.schema.json`](schemas/ignore.schema.json) | Reviewable scoping. Every entry has a reason. |
|
|
31
|
-
| `calibration` | [`schemas/calibration.schema.json`](schemas/calibration.schema.json) |
|
|
31
|
+
| `calibration` | [`schemas/calibration.schema.json`](schemas/calibration.schema.json) | The observed rate of a bucket's outcome — what it measures is in the document — with a Wilson interval and the sample size behind it. Every number is recomputable and the validator recomputes it; provenance, a producer floor, backoff tiers and a labelled fallback ride along so a second tool's honesty survives translation. |
|
|
32
32
|
| `brief` | [`schemas/brief.schema.json`](schemas/brief.schema.json) | What to tell an agent before it writes code — ranked, capped, never a single score, and carrying the one next action. |
|
|
33
33
|
| `status` | [`schemas/status.schema.json`](schemas/status.schema.json) | Where the project is and what happens next — the single machine-readable truth every human rendering derives from. Surfaces faults whose content changed since they were probed, and changed files that carry no claim. |
|
|
34
34
|
| `replay` | [`schemas/replay.schema.json`](schemas/replay.schema.json) | Would this suite have caught the bugs that already escaped? Replays real fix commits: revert the source, remove the test the fix shipped, run what remains. A replayed bug is ground truth — a human already confirmed it was a defect — which is what a fault model is calibrated against. |
|
|
@@ -65,7 +65,10 @@ consumer. It moves to its own repository only when a second tool adopts it.
|
|
|
65
65
|
|
|
66
66
|
Adoption is additive and optional: an existing tool gains an output writer, and
|
|
67
67
|
nothing it already detects or already writes changes. That holds today for
|
|
68
|
-
`baseline`, `ignore`, `brief` and `
|
|
68
|
+
`baseline`, `ignore`, `brief`, `status` and `calibration` — websec-validator's
|
|
69
|
+
shipped calibration table is `conformance/examples/calibration-websec.json`,
|
|
70
|
+
translated field for field with its corpus, caveat, floor, backoff and
|
|
71
|
+
fallback intact. It does **not** yet hold for
|
|
69
72
|
`claims` and `evidence`, whose required fields assume fault injection
|
|
70
73
|
(`find`/`replace` anchors, `confirmRuns`, `baselineRuns`/`probeRuns`,
|
|
71
74
|
`defenders`) — a tool that scans or reads would have to emit empty arrays to
|