testguard-cli 0.16.0__tar.gz → 0.18.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.18.0/.github/scripts/benchmark-node-runner.mjs +140 -0
- testguard_cli-0.18.0/.github/scripts/check-node-fixture.mjs +28 -0
- testguard_cli-0.18.0/.github/scripts/install-smoke.mjs +360 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/.github/workflows/ci.yml +18 -1
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/CHANGELOG.md +70 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/PKG-INFO +170 -15
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/README.md +169 -14
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/action.yml +1 -1
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/docs/testguard-explained.html +1 -1
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/docs/testguard-explained.pdf +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/docs-canonical/ARCHITECTURE.md +1 -1
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/docs-canonical/REQUIREMENTS.md +2 -2
- testguard_cli-0.18.0/fixtures/known-answer-node/.gitignore +3 -0
- testguard_cli-0.18.0/fixtures/known-answer-node/README.md +15 -0
- testguard_cli-0.18.0/fixtures/known-answer-node/expected.json +51 -0
- testguard_cli-0.18.0/fixtures/known-answer-node/package.json +9 -0
- testguard_cli-0.18.0/fixtures/known-answer-node/src/policy.mjs +15 -0
- testguard_cli-0.18.0/fixtures/known-answer-node/src/unused.mjs +1 -0
- testguard_cli-0.18.0/fixtures/known-answer-node/test/flaky.test.mjs +11 -0
- testguard_cli-0.18.0/fixtures/known-answer-node/test/mixed.test.mjs +11 -0
- testguard_cli-0.18.0/fixtures/known-answer-node/test/policy.test.mjs +15 -0
- testguard_cli-0.18.0/fixtures/known-answer-node/testguard.claims.json +285 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/package-lock.json +99 -117
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/package.json +1 -1
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/packaging/gitlab/testguard.gitlab-ci.yml +2 -2
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/packaging/homebrew/testguard.rb +2 -2
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/pyproject.toml +1 -1
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/GATE-SEMANTICS.md +316 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/README.md +27 -2
- testguard_cli-0.18.0/spec/conformance/annotations.test.mjs +47 -0
- testguard_cli-0.18.0/spec/conformance/authoring-input.test.mjs +76 -0
- testguard_cli-0.18.0/spec/conformance/draft-append.test.mjs +18 -0
- testguard_cli-0.18.0/spec/conformance/examples/annotations.json +9 -0
- testguard_cli-0.18.0/spec/conformance/examples/authoring-input.json +13 -0
- testguard_cli-0.18.0/spec/conformance/examples/evidence-resources.json +588 -0
- testguard_cli-0.18.0/spec/conformance/examples/status-resource-cost.json +119 -0
- testguard_cli-0.18.0/spec/conformance/invalid/evidence.resource-accounting.json +588 -0
- testguard_cli-0.18.0/spec/conformance/invalid/evidence.serial-worker-conflict.json +588 -0
- testguard_cli-0.18.0/spec/conformance/invalid/status.resource-accounting.json +119 -0
- testguard_cli-0.18.0/spec/conformance/origin-policy.test.mjs +71 -0
- testguard_cli-0.18.0/spec/conformance/origin-summaries.test.mjs +77 -0
- testguard_cli-0.18.0/spec/conformance/origins.test.mjs +66 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/conformance/schemas.test.mjs +14 -0
- testguard_cli-0.18.0/spec/lib/authoring-input.mjs +40 -0
- testguard_cli-0.18.0/spec/lib/origins.mjs +56 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/lib/validate.mjs +123 -1
- testguard_cli-0.18.0/spec/schemas/annotations.schema.json +65 -0
- testguard_cli-0.18.0/spec/schemas/authoring-input.schema.json +67 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/schemas/brief.schema.json +2 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/schemas/claims.schema.json +2 -8
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/schemas/common.schema.json +73 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/schemas/evidence.schema.json +40 -10
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/schemas/status.schema.json +6 -2
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/src/brief/brief.mjs +11 -4
- testguard_cli-0.18.0/src/claims/annotation-placement.mjs +357 -0
- testguard_cli-0.18.0/src/claims/annotations.mjs +118 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/src/cli.mjs +41 -6
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/src/commands/admit.mjs +13 -0
- testguard_cli-0.18.0/src/commands/annotations.mjs +67 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/src/commands/claims.mjs +8 -4
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/src/commands/probe.mjs +50 -7
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/src/commands/replay.mjs +7 -0
- testguard_cli-0.18.0/src/commands/scaffold.mjs +90 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/src/commands/sweep.mjs +7 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/src/evidence/writer.mjs +12 -4
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/src/git.mjs +4 -4
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/src/init/templates/SKILL.md +35 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/src/probe/attribution.mjs +10 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/src/probe/contention.mjs +20 -2
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/src/probe/cost.mjs +6 -1
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/src/probe/inject.mjs +2 -6
- testguard_cli-0.18.0/src/probe/origin-policy.mjs +69 -0
- testguard_cli-0.18.0/src/probe/policy-freshness.mjs +74 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/src/probe/probe.mjs +49 -24
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/src/probe/runners/discovery.mjs +4 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/src/probe/runners/index.mjs +3 -1
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/src/probe/runners/jest.mjs +2 -2
- testguard_cli-0.18.0/src/probe/runners/lifecycle.mjs +58 -0
- testguard_cli-0.18.0/src/probe/runners/node/entry.mjs +11 -0
- testguard_cli-0.18.0/src/probe/runners/node/report.mjs +66 -0
- testguard_cli-0.18.0/src/probe/runners/node/runner.mjs +42 -0
- testguard_cli-0.18.0/src/probe/runners/node-test.mjs +130 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/src/probe/runners/playwright.mjs +6 -2
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/src/probe/runners/python.mjs +10 -4
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/src/probe/runners/shared.mjs +14 -7
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/src/probe/runners/vitest.mjs +3 -3
- testguard_cli-0.18.0/src/probe/universe.mjs +47 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/src/probe/worktree.mjs +3 -1
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/src/render.mjs +17 -1
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/src/replay/replay.mjs +8 -4
- testguard_cli-0.18.0/src/scaffold/admission.mjs +117 -0
- testguard_cli-0.18.0/src/scaffold/append.mjs +41 -0
- testguard_cli-0.18.0/src/scaffold/apply-append.mjs +71 -0
- testguard_cli-0.18.0/src/scaffold/fix-input.mjs +119 -0
- testguard_cli-0.18.0/src/scaffold/fix-paths.mjs +90 -0
- testguard_cli-0.18.0/src/scaffold/intent.mjs +2 -0
- testguard_cli-0.18.0/src/scaffold/open-append.mjs +44 -0
- testguard_cli-0.18.0/src/scaffold/plan-append.mjs +37 -0
- testguard_cli-0.18.0/src/scaffold/plan-intent-input.mjs +38 -0
- testguard_cli-0.18.0/src/scaffold/prepare-append.mjs +46 -0
- testguard_cli-0.18.0/src/scaffold/request.mjs +30 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/src/scaffold/scaffold.mjs +23 -10
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/src/status/status.mjs +34 -9
- testguard_cli-0.18.0/src/sweep/pool.mjs +33 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/src/sweep/sweep.mjs +9 -4
- testguard_cli-0.18.0/test/anchor-admission.test.mjs +38 -0
- testguard_cli-0.18.0/test/annotation-advisory-limits.test.mjs +75 -0
- testguard_cli-0.18.0/test/annotation-advisory.test.mjs +118 -0
- testguard_cli-0.18.0/test/annotation-apply.test.mjs +328 -0
- testguard_cli-0.18.0/test/annotation-cli.test.mjs +201 -0
- testguard_cli-0.18.0/test/annotation-placement.test.mjs +190 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/test/attribution.test.mjs +20 -0
- testguard_cli-0.18.0/test/cli-unexpected-error.test.mjs +37 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/test/command-budget-write-boundaries.test.mjs +13 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/test/contention.test.mjs +8 -0
- testguard_cli-0.18.0/test/discovery-config.test.mjs +182 -0
- testguard_cli-0.18.0/test/draft-admission.test.mjs +123 -0
- testguard_cli-0.18.0/test/draft-append-apply.test.mjs +146 -0
- testguard_cli-0.18.0/test/draft-append-cli.test.mjs +96 -0
- testguard_cli-0.18.0/test/draft-append-open.test.mjs +90 -0
- testguard_cli-0.18.0/test/draft-append-plan.test.mjs +75 -0
- testguard_cli-0.18.0/test/draft-append-prepare.test.mjs +130 -0
- testguard_cli-0.18.0/test/draft-append.test.mjs +85 -0
- testguard_cli-0.18.0/test/fix-input.test.mjs +108 -0
- testguard_cli-0.18.0/test/fix-inventory.test.mjs +156 -0
- testguard_cli-0.18.0/test/fix-nested.test.mjs +106 -0
- testguard_cli-0.18.0/test/fix-paths.test.mjs +76 -0
- testguard_cli-0.18.0/test/fix-projects.test.mjs +66 -0
- testguard_cli-0.18.0/test/gate-ref.test.mjs +104 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/test/gate.test.mjs +1 -127
- testguard_cli-0.18.0/test/helpers/gate-project.mjs +35 -0
- testguard_cli-0.18.0/test/helpers/probe-assertions.mjs +26 -0
- testguard_cli-0.18.0/test/helpers/release-project.mjs +36 -0
- testguard_cli-0.18.0/test/intent-authoring.test.mjs +57 -0
- testguard_cli-0.18.0/test/intent-document.test.mjs +81 -0
- testguard_cli-0.18.0/test/intent-input-cli.test.mjs +85 -0
- testguard_cli-0.18.0/test/intent-input-plan.test.mjs +114 -0
- testguard_cli-0.18.0/test/intent-input-request.test.mjs +48 -0
- testguard_cli-0.18.0/test/intent-project.test.mjs +54 -0
- testguard_cli-0.18.0/test/mcp-next.test.mjs +32 -0
- testguard_cli-0.18.0/test/mcp-protocol.test.mjs +69 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/test/mcp.test.mjs +0 -60
- testguard_cli-0.18.0/test/native-universe-hash.test.mjs +25 -0
- testguard_cli-0.18.0/test/native-universe.test.mjs +51 -0
- testguard_cli-0.18.0/test/negative-control-capabilities.test.mjs +35 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/test/negative-control.test.mjs +0 -29
- testguard_cli-0.18.0/test/origin-policy-admission.test.mjs +13 -0
- testguard_cli-0.18.0/test/origin-policy-cli.test.mjs +80 -0
- testguard_cli-0.18.0/test/origin-policy-projections.test.mjs +78 -0
- testguard_cli-0.18.0/test/origin-policy-request.test.mjs +26 -0
- testguard_cli-0.18.0/test/origin-policy.test.mjs +95 -0
- testguard_cli-0.18.0/test/origin-reporting.test.mjs +91 -0
- testguard_cli-0.18.0/test/origin-summary.test.mjs +69 -0
- testguard_cli-0.18.0/test/policy-freshness.test.mjs +112 -0
- testguard_cli-0.18.0/test/probe-control.test.mjs +18 -0
- testguard_cli-0.18.0/test/probe-dirty-admission.test.mjs +35 -0
- testguard_cli-0.18.0/test/probe-error-boundary.test.mjs +107 -0
- testguard_cli-0.18.0/test/probe-isolation.test.mjs +23 -0
- testguard_cli-0.18.0/test/probe-records.test.mjs +110 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/test/probe.fixture.test.mjs +23 -0
- testguard_cli-0.18.0/test/probe.node.test.mjs +93 -0
- testguard_cli-0.18.0/test/release-sync.test.mjs +127 -0
- testguard_cli-0.18.0/test/release-tap.test.mjs +81 -0
- testguard_cli-0.18.0/test/release-version.test.mjs +156 -0
- testguard_cli-0.18.0/test/replay-selection.test.mjs +105 -0
- testguard_cli-0.18.0/test/resource-safety.test.mjs +130 -0
- testguard_cli-0.18.0/test/resource-timing.test.mjs +67 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/test/runner-argv.test.mjs +4 -1
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/test/runner-discovery.test.mjs +1 -1
- testguard_cli-0.18.0/test/runner-node-controller.test.mjs +52 -0
- testguard_cli-0.18.0/test/runner-node-report.test.mjs +82 -0
- testguard_cli-0.18.0/test/runner-node.test.mjs +127 -0
- testguard_cli-0.18.0/test/runner-output-bounds.test.mjs +30 -0
- testguard_cli-0.18.0/test/runner-project-resolution.test.mjs +18 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/test/runner-python.test.mjs +1 -1
- testguard_cli-0.18.0/test/runner-report.test.mjs +83 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/test/runner-vitest.test.mjs +1 -1
- testguard_cli-0.18.0/test/runner-worker-argv.test.mjs +20 -0
- testguard_cli-0.18.0/test/scaffold-admitted-source.test.mjs +56 -0
- testguard_cli-0.18.0/test/scaffold-placeholder.test.mjs +40 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/test/scaffold.python.test.mjs +1 -1
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/test/scaffold.test.mjs +2 -2
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/test/status.test.mjs +76 -6
- testguard_cli-0.18.0/test/sweep-pool.test.mjs +82 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/test/sweep.fixture.test.mjs +14 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/test/vitest-config.test.mjs +2 -0
- testguard_cli-0.18.0/test/worker-policy.test.mjs +14 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/testguard.claims.json +1669 -47
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/testguard.ignore.json +7 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/vitest.config.mjs +3 -0
- testguard_cli-0.16.0/.github/scripts/install-smoke.mjs +0 -94
- testguard_cli-0.16.0/src/claims/annotations.mjs +0 -59
- testguard_cli-0.16.0/src/commands/scaffold.mjs +0 -43
- testguard_cli-0.16.0/test/release-sync.test.mjs +0 -396
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/.agents/skills/testguard/SKILL.md +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/.docguard.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/.docguardignore +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/.gitattributes +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/.github/dependabot.yml +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/.github/scripts/approve-held-runs.sh +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/.github/scripts/build-one-pager.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/.github/scripts/check-cost-budget.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/.github/scripts/check-python-fixture.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/.github/scripts/publish-homebrew-tap.sh +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/.github/scripts/sync-release-version.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/.github/workflows/auto-merge.yml +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/.github/workflows/release.yml +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/.github/workflows/scheduled-release.yml +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/.github/workflows/supply-chain.yml +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/.gitignore +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/.npmignore +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/.npmrc +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/.pre-commit-config.yaml +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/.pre-commit-hooks.yaml +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/AGENTS.md +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/CLAUDE.md +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/CODE_OF_CONDUCT.md +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/CONTRIBUTING.md +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/DRIFT-LOG.md +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/LICENSE +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/PRIVACY.md +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/SECURITY.md +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/SUPPORT.md +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/bench/README.md +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/cli/testguard.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/docs-canonical/DATA-MODEL.md +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/docs-canonical/ENVIRONMENT.md +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/docs-canonical/PRIOR-ART.md +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/docs-canonical/SECURITY.md +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/docs-canonical/TEST-SPEC.md +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/fixtures/known-answer/.gitignore +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/fixtures/known-answer/README.md +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/fixtures/known-answer/expected.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/fixtures/known-answer/package.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/fixtures/known-answer/src/export.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/fixtures/known-answer/src/redact.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/fixtures/known-answer/test/export-mocked.test.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/fixtures/known-answer/test/flaky.test.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/fixtures/known-answer/test/redact.test.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/fixtures/known-answer/testguard.claims.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/fixtures/known-answer/vitest.config.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/fixtures/known-answer-jest/.gitignore +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/fixtures/known-answer-jest/README.md +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/fixtures/known-answer-jest/expected.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/fixtures/known-answer-jest/jest.config.js +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/fixtures/known-answer-jest/package.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/fixtures/known-answer-jest/src/export.js +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/fixtures/known-answer-jest/src/redact.js +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/fixtures/known-answer-jest/test/export-mocked.test.js +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/fixtures/known-answer-jest/test/flaky.test.js +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/fixtures/known-answer-jest/test/redact.test.js +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/fixtures/known-answer-jest/testguard.claims.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/fixtures/known-answer-playwright/.gitignore +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/fixtures/known-answer-playwright/README.md +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/fixtures/known-answer-playwright/e2e/flaky.spec.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/fixtures/known-answer-playwright/e2e/toggle.spec.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/fixtures/known-answer-playwright/expected.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/fixtures/known-answer-playwright/package-lock.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/fixtures/known-answer-playwright/package.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/fixtures/known-answer-playwright/playwright.config.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/fixtures/known-answer-playwright/src/toggle.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/fixtures/known-answer-playwright/test/toggle.test.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/fixtures/known-answer-playwright/testguard.claims.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/fixtures/known-answer-playwright/vitest.config.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/fixtures/known-answer-python/.gitignore +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/fixtures/known-answer-python/README.md +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/fixtures/known-answer-python/demo/__init__.py +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/fixtures/known-answer-python/demo/export.py +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/fixtures/known-answer-python/demo/orphan.py +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/fixtures/known-answer-python/demo/pipeline.py +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/fixtures/known-answer-python/demo/redact.py +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/fixtures/known-answer-python/expected.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/fixtures/known-answer-python/testguard.claims.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/fixtures/known-answer-python/tests/test_export_mocked.py +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/fixtures/known-answer-python/tests/test_flaky.py +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/fixtures/known-answer-python/tests/test_patched.py +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/fixtures/known-answer-python/tests/test_redact.py +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/conformance/examples/baseline.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/conformance/examples/brief.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/conformance/examples/calibration-websec.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/conformance/examples/calibration.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/conformance/examples/claims-methods.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/conformance/examples/claims.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/conformance/examples/evidence-disturbed-run.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/conformance/examples/evidence-negative-control.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/conformance/examples/evidence-persistence-signal.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/conformance/examples/evidence-provisional.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/conformance/examples/evidence-python.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/conformance/examples/evidence-scan-method.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/conformance/examples/evidence.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/conformance/examples/gate.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/conformance/examples/ignore.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/conformance/examples/replay.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/conformance/examples/status.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/conformance/invalid/baseline.bad-fingerprint-key.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/conformance/invalid/baseline.zero-count.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/conformance/invalid/brief.text-without-heading.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/conformance/invalid/brief.unknown-verdict-key.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/conformance/invalid/calibration.backoff-cell-not-wilson.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/conformance/invalid/calibration.breakdown-does-not-sum-to-n.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/conformance/invalid/calibration.ci-not-wilson.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/conformance/invalid/calibration.compound-key-arity-mismatch.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/conformance/invalid/calibration.fallback-without-basis.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/conformance/invalid/calibration.p-not-positives-over-n.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/conformance/invalid/calibration.p-outside-ci.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/conformance/invalid/calibration.positives-exceed-n.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/conformance/invalid/claims.absolute-path.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/conformance/invalid/claims.duplicate-fault-id.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/conformance/invalid/claims.fault-injection-without-a-substitution.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/conformance/invalid/claims.method-detail-on-a-specified-method.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/conformance/invalid/claims.missing-provenance.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/conformance/invalid/claims.noop-fault.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/conformance/invalid/claims.occurrence-exceeds-hits.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/conformance/invalid/claims.parent-traversal.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/conformance/invalid/evidence.annotated-signal-without-reason.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/conformance/invalid/evidence.attribute-signal-not-a-defender.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/conformance/invalid/evidence.attribute-signal-without-attributes.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/conformance/invalid/evidence.byrunner-file-not-resolved.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/conformance/invalid/evidence.contention-without-runners.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/conformance/invalid/evidence.discovered-defender-mocks-subject.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/conformance/invalid/evidence.fault-injection-without-its-evidence.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/conformance/invalid/evidence.flaky-defender-without-flakiness.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/conformance/invalid/evidence.ignored-dirty-on-snapshot.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/conformance/invalid/evidence.killed-by-non-assertion.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/conformance/invalid/evidence.killed-on-red-baseline.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/conformance/invalid/evidence.killed-without-n-runs.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/conformance/invalid/evidence.method-detail-on-an-injecting-run.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/conformance/invalid/evidence.negative-control-not-reached-but-survived.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/conformance/invalid/evidence.negative-control-reached-on-a-kill.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/conformance/invalid/evidence.persistence-signal-not-a-defender.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/conformance/invalid/evidence.persistence-signal-on-a-non-survivor.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/conformance/invalid/evidence.persistence-signal-without-reason.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/conformance/invalid/evidence.probe-error-without-message.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/conformance/invalid/evidence.provisional-flag-on-confirmed-run.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/conformance/invalid/evidence.rank-extra-property.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/conformance/invalid/evidence.restore-skipped-in-place.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/conformance/invalid/evidence.single-run-without-provisional.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/conformance/invalid/evidence.target-not-imported-but-killed.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/conformance/invalid/evidence.undeclared-killers-without-reason.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/conformance/invalid/evidence.unknown-verdict.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/conformance/invalid/evidence.unverifiable-without-reason.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/conformance/invalid/evidence.wrong-fingerprint.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/conformance/invalid/gate.counts-disagree.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/conformance/invalid/gate.exit-zero-with-uncovered.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/conformance/invalid/gate.ignore-cover-without-pattern.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/conformance/invalid/ignore.missing-reason.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/conformance/invalid/ignore.short-reason.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/conformance/invalid/replay.blind-with-a-failing-run.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/conformance/invalid/replay.duplicate-patch-id.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/conformance/invalid/replay.nocover-when-no-test-file-remained.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/conformance/invalid/status.claim-without-file.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/conformance/invalid/status.clean-with-new-findings.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/conformance/invalid/status.invalid-anchor-hidden.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/conformance/invalid/status.partial-scope-counts-disagree.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/conformance/invalid/status.surface-counts-disagree.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/conformance/invalid/status.unclaimed-hidden-behind-unproven.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/conformance/invalid/status.write-test-without-target.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/lib/fingerprint.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/lib/wilson.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/schemas/baseline.schema.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/schemas/calibration.schema.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/schemas/concerns.schema.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/schemas/gate.schema.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/schemas/ignore.schema.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/schemas/replay.schema.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/spec/schemas/sweep.schema.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/src/admit/admit.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/src/baseline/baseline.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/src/claims/anchors.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/src/claims/load.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/src/claims/removed.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/src/command-budget.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/src/commands/baseline.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/src/commands/brief.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/src/commands/concerns.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/src/commands/gate.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/src/commands/init.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/src/commands/mcp.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/src/commands/status.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/src/gate/changed.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/src/init/init.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/src/mcp/server.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/src/mcp/tools.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/src/probe/classify.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/src/probe/discover.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/src/probe/imports.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/src/probe/independence.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/src/probe/mocks.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/src/probe/progress.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/src/probe/pyimports.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/src/probe/rank.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/src/probe/runners/python/_testguard_pytest_plugin.py +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/src/probe/runners/python/_testguard_report.py +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/src/probe/runners/python/_testguard_unittest_main.py +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/src/replay/label.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/src/scaffold/producers.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/src/scaffold/producers.python.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/src/status/surface.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/src/supply/concerns.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/src/supply/feedback.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/src/supply/persistence.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/src/supply/savepath.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/src/supply/select.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/src/util/glob.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/src/util/hash.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/test/admit-decision.test.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/test/admit.test.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/test/annotations.test.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/test/baseline.test.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/test/brief.test.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/test/calibration.test.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/test/claim-selection.test.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/test/claims-anchors.test.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/test/claims-since.test.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/test/classify.test.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/test/command-budget.test.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/test/cost.test.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/test/discover.test.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/test/evidence-persistence-signal.test.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/test/fault-defender-orchestration.test.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/test/fault-defenders.test.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/test/gitlab-template.test.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/test/glob.test.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/test/imports.test.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/test/independence.test.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/test/init.test.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/test/inject.test.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/test/label.test.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/test/mocks.test.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/test/one-pager.test.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/test/probe-dirty-paths.test.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/test/probe-error.test.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/test/probe-native-manifest.test.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/test/probe-persistence-signal.test.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/test/probe-preconditions.test.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/test/probe.jest.test.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/test/probe.playwright.test.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/test/probe.python.test.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/test/progress.test.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/test/pyimports.test.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/test/rank-aliases.test.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/test/release-approval.test.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/test/replay-mixed-runners.test.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/test/replay-no-test-file-left.test.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/test/replay-python-defenders.test.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/test/replay-verdict.test.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/test/replay.test.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/test/runner-command.test.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/test/runner-playwright.test.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/test/runner-selection-blind.test.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/test/samples/playwright-report.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/test/samples/python-report-collect-error.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/test/samples/python-report.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/test/snapshot.test.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/test/supply-concerns.test.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/test/supply-feedback.test.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/test/supply-persistence.test.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/test/supply-savepath.test.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/test/supply-select.test.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/test/surface.test.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/test/sweep.test.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/test/worktree.test.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/test/writer.test.mjs +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/testguard.cost-budget.json +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/testguard_cli/__init__.py +0 -0
- {testguard_cli-0.16.0 → testguard_cli-0.18.0}/testguard_cli/wrapper.py +0 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Narrow, reproducible comparison: the same classifier inputs and four existing
|
|
3
|
+
// fault recipes, through real probe() calls with fresh workers and N=3.
|
|
4
|
+
// Run under each Node executable with no other test process active:
|
|
5
|
+
// node .github/scripts/benchmark-node-runner.mjs --out /absolute/receipt-dir
|
|
6
|
+
import assert from 'node:assert/strict';
|
|
7
|
+
import { execFileSync } from 'node:child_process';
|
|
8
|
+
import { mkdirSync, mkdtempSync, readFileSync, rmSync, symlinkSync, writeFileSync } from 'node:fs';
|
|
9
|
+
import { createHash } from 'node:crypto';
|
|
10
|
+
import { dirname, join, resolve } from 'node:path';
|
|
11
|
+
import { tmpdir, platform, arch } from 'node:os';
|
|
12
|
+
import { fileURLToPath } from 'node:url';
|
|
13
|
+
import { parseArgs } from 'node:util';
|
|
14
|
+
import { performance } from 'node:perf_hooks';
|
|
15
|
+
import { probe } from '../../src/probe/probe.mjs';
|
|
16
|
+
import { writeSpecDoc } from '../../src/evidence/writer.mjs';
|
|
17
|
+
|
|
18
|
+
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '../..');
|
|
19
|
+
const { values } = parseArgs({ options: { out: { type: 'string' }, pairs: { type: 'string', default: '2' } } });
|
|
20
|
+
assert(values.out, '--out is required; timings and proof receipts are retained there');
|
|
21
|
+
const pairs = Number(values.pairs);
|
|
22
|
+
assert(Number.isSafeInteger(pairs) && pairs >= 1 && pairs <= 10, '--pairs must be 1..10');
|
|
23
|
+
const out = resolve(values.out);
|
|
24
|
+
mkdirSync(out, { recursive: true });
|
|
25
|
+
const classifier = readFileSync(join(ROOT, 'src/probe/classify.mjs'), 'utf8');
|
|
26
|
+
const inventory = JSON.parse(readFileSync(join(ROOT, 'testguard.claims.json'), 'utf8'));
|
|
27
|
+
const ids = ['TG-KILL-NEEDS-N', 'TG-TIMEOUT-NEVER-KILLS', 'TG-COMPILE-ERROR-NAMED', 'TG-SURVIVOR-PROVES-THE-SUBJECT-RUNS'];
|
|
28
|
+
const claims = { ...inventory, claims: ids.map((id) => {
|
|
29
|
+
const claim = structuredClone(inventory.claims.find((c) => c.id === id));
|
|
30
|
+
assert(claim && claim.faults.length === 1, `expected one unchanged recipe for ${id}`);
|
|
31
|
+
claim.defendedBy = ['test/classify.test.mjs'];
|
|
32
|
+
return claim;
|
|
33
|
+
}) };
|
|
34
|
+
const pass = { outcome: 'pass', assertionFailures: 0, timeouts: 0 };
|
|
35
|
+
const kill = { outcome: 'fail', assertionFailures: 1, timeouts: 0 };
|
|
36
|
+
const timeout = { outcome: 'fail', assertionFailures: 0, timeouts: 1 };
|
|
37
|
+
const error = { outcome: 'error', assertionFailures: 0, timeouts: 0 };
|
|
38
|
+
const healthy = { defenders: ['test/classify.test.mjs'], anchor: { status: 'ok' }, baselineRuns: [pass, pass, pass], probeRuns: [pass, pass, pass], confirmRuns: 3 };
|
|
39
|
+
const rows = [
|
|
40
|
+
['nocover precedes missing anchor', { defenders: [], anchor: { status: 'anchor-missing' } }, { verdict: 'nocover' }],
|
|
41
|
+
['missing anchor', { anchor: { status: 'anchor-missing' } }, { verdict: 'unverifiable', reason: 'anchor-missing' }],
|
|
42
|
+
['ambiguous anchor', { anchor: { status: 'anchor-ambiguous' } }, { verdict: 'unverifiable', reason: 'anchor-ambiguous' }],
|
|
43
|
+
['failed defender load', { anchor: { status: 'defenders-failed-to-load' }, baselineRuns: [error] }, { verdict: 'unverifiable', reason: 'defenders-failed-to-load' }],
|
|
44
|
+
['red baseline', { baselineRuns: [pass, kill] }, { verdict: 'flaky-defender', reason: 'defenders-not-green' }],
|
|
45
|
+
['empty baseline', { baselineRuns: [] }, { verdict: 'flaky-defender', reason: 'defenders-not-green' }],
|
|
46
|
+
['load failure', { probeRuns: [error] }, { verdict: 'fault-invalid', reason: 'suite-failed-to-load' }],
|
|
47
|
+
['syntax error', { probeRuns: [{ ...error, loadMessage: 'Failed to parse source: invalid JS syntax' }] }, { verdict: 'fault-invalid', reason: 'replacement-does-not-compile' }],
|
|
48
|
+
['esbuild syntax error', { probeRuns: [{ ...error, loadMessage: 'Transform failed with 1 error: Expected ")" but found ";"' }] }, { verdict: 'fault-invalid', reason: 'replacement-does-not-compile' }],
|
|
49
|
+
['test timeout', { probeRuns: [timeout] }, { verdict: 'timeout', reason: 'test-timed-out' }],
|
|
50
|
+
['budget timeout', { probeRuns: [{ ...timeout, outcome: 'timeout' }] }, { verdict: 'timeout', reason: 'test-timed-out' }],
|
|
51
|
+
['confirmed kill', { probeRuns: [kill, kill, kill] }, { verdict: 'killed' }],
|
|
52
|
+
['confirmed survivor', {}, { verdict: 'survived' }],
|
|
53
|
+
['mixed confirmations', { probeRuns: [kill, pass, kill] }, { verdict: 'flaky-defender', reason: 'inconsistent-probe' }],
|
|
54
|
+
['failure without assertions', { probeRuns: [{ outcome: 'fail', assertionFailures: 0, timeouts: 0 }, pass, pass] }, { verdict: 'flaky-defender', reason: 'inconsistent-probe' }],
|
|
55
|
+
['unreached survivor', { subjectReached: false }, { verdict: 'unverifiable', reason: 'subject-not-executed' }],
|
|
56
|
+
['reached survivor', { subjectReached: true }, { verdict: 'survived' }],
|
|
57
|
+
];
|
|
58
|
+
const corpus = rows.map(([name, changes, expected]) => [name, { ...healthy, ...changes }, expected]);
|
|
59
|
+
const sha256 = (text) => createHash('sha256').update(text).digest('hex');
|
|
60
|
+
const toolSources = Object.fromEntries(['node-test.mjs', 'node/runner.mjs', 'node/entry.mjs', 'node/report.mjs', 'shared.mjs', 'discovery.mjs', 'lifecycle.mjs', 'vitest.mjs'].map((file) => [file, sha256(readFileSync(join(ROOT, 'src/probe/runners', file)))]));
|
|
61
|
+
const vitestVersion = JSON.parse(readFileSync(join(ROOT, 'node_modules/vitest/package.json'), 'utf8')).version;
|
|
62
|
+
const proofProjection = (evidence) => evidence.records.map((r) => ({
|
|
63
|
+
id: `${r.claim.id}/${r.subject.id}`, verdict: r.verdict, reason: r.detail.reason ?? null,
|
|
64
|
+
baseline: r.detail.baselineRuns.map(({ durationMs, ...run }) => run),
|
|
65
|
+
probe: r.detail.probeRuns.map(({ durationMs, ...run }) => run),
|
|
66
|
+
}));
|
|
67
|
+
const results = [];
|
|
68
|
+
async function measure(engine, label) {
|
|
69
|
+
const dir = mkdtempSync(join(tmpdir(), 'testguard-benchmark-'));
|
|
70
|
+
const receipts = join(out, `${label}-${engine}-workers.jsonl`);
|
|
71
|
+
const outcomes = join(out, `${label}-${engine}-outcomes.jsonl`);
|
|
72
|
+
writeFileSync(receipts, '', { mode: 0o600 });
|
|
73
|
+
writeFileSync(outcomes, '', { mode: 0o600 });
|
|
74
|
+
try {
|
|
75
|
+
mkdirSync(join(dir, 'src/probe'), { recursive: true }); mkdirSync(join(dir, 'test'));
|
|
76
|
+
writeFileSync(join(dir, 'src/probe/classify.mjs'), classifier);
|
|
77
|
+
writeFileSync(join(dir, 'package.json'), JSON.stringify({ private: true, type: 'module', ...(engine === 'vitest' ? { devDependencies: { vitest: vitestVersion } } : {}) }));
|
|
78
|
+
writeFileSync(join(dir, 'testguard.claims.json'), JSON.stringify(claims));
|
|
79
|
+
const imports = engine === 'vitest' ? "import {it,expect} from 'vitest';" : "import {test as it} from 'node:test'; import assert from 'node:assert/strict';";
|
|
80
|
+
const assertion = engine === 'vitest' ? 'expect(classify(input)).toEqual(expected)' : 'assert.deepStrictEqual(classify(input),expected)';
|
|
81
|
+
writeFileSync(join(dir, 'test/classify.test.mjs'), `${imports}
|
|
82
|
+
import {appendFileSync,readFileSync} from 'node:fs'; import {createHash} from 'node:crypto'; import {classify} from '../src/probe/classify.mjs';
|
|
83
|
+
const sourceHash=createHash('sha256').update(readFileSync(new URL('../src/probe/classify.mjs',import.meta.url))).digest('hex');
|
|
84
|
+
appendFileSync(${JSON.stringify(receipts)},JSON.stringify({pid:process.pid,execPath:process.execPath,version:process.versions.node,collect:process.env.TESTGUARD_NODE_COLLECT==='1'})+'\\n');
|
|
85
|
+
const record=(name,passed)=>appendFileSync(${JSON.stringify(outcomes)},JSON.stringify({pid:process.pid,sourceHash,name,passed})+'\\n');
|
|
86
|
+
for(const [name,input,expected] of ${JSON.stringify(corpus)}) it(name,()=>{try{${assertion};record(name,true);}catch(error){record(name,false);throw error;}});\n`);
|
|
87
|
+
if (engine === 'vitest') symlinkSync(join(ROOT, 'node_modules'), join(dir, 'node_modules'), 'junction');
|
|
88
|
+
writeFileSync(join(dir, '.gitignore'), 'node_modules/\n.testguard/\n');
|
|
89
|
+
for (const args of [['init', '-q'], ['add', '.'], ['-c', 'user.name=benchmark', '-c', 'user.email=benchmark@example.invalid', 'commit', '-qm', 'benchmark']]) execFileSync('git', args, { cwd: dir, timeout: 5000 });
|
|
90
|
+
const start = performance.now();
|
|
91
|
+
const evidence = await probe({ projectDir: dir, claims, runnerName: engine, confirmRuns: 3, budgetMs: 30_000, workers: 1, serial: true, escalate: false, toolVersion: 'benchmark' });
|
|
92
|
+
const wallMs = performance.now() - start;
|
|
93
|
+
writeSpecDoc('evidence', join(out, `${label}-${engine}-evidence.json`), evidence);
|
|
94
|
+
assert.equal(evidence.records.length, 4);
|
|
95
|
+
for (const r of evidence.records) {
|
|
96
|
+
assert.equal(r.verdict, 'killed', `${r.claim.id} must be caught by both frameworks`);
|
|
97
|
+
assert.equal(r.detail.baselineRuns.length, 3); assert.equal(r.detail.probeRuns.length, 3);
|
|
98
|
+
for (const run of r.detail.baselineRuns) { assert.equal(run.outcome, 'pass'); assert.equal(run.tests.total, corpus.length); }
|
|
99
|
+
for (const run of r.detail.probeRuns) { assert.equal(run.outcome, 'fail'); assert(run.assertionFailures > 0); assert.equal(run.tests.total, corpus.length); }
|
|
100
|
+
}
|
|
101
|
+
const workers = readFileSync(receipts, 'utf8').trim().split('\n').map(JSON.parse).filter((r) => !r.collect);
|
|
102
|
+
assert.equal(workers.length, 15, 'three shared baseline runs plus four faults times three confirmations');
|
|
103
|
+
assert.equal(new Set(workers.map((r) => r.pid)).size, 15, 'each confirmation must have a fresh worker');
|
|
104
|
+
for (const r of workers) { assert.equal(r.version, process.versions.node); assert.equal(r.execPath, process.execPath); }
|
|
105
|
+
const testResults = readFileSync(outcomes, 'utf8').trim().split('\n').map(JSON.parse);
|
|
106
|
+
assert.equal(testResults.length, 15 * corpus.length, 'every test must execute in every measured worker');
|
|
107
|
+
const bySource = new Map();
|
|
108
|
+
for (const worker of workers) {
|
|
109
|
+
const tests = testResults.filter((r) => r.pid === worker.pid);
|
|
110
|
+
assert.equal(tests.length, corpus.length); assert.equal(new Set(tests.map((r) => r.name)).size, corpus.length);
|
|
111
|
+
assert.equal(new Set(tests.map((r) => r.sourceHash)).size, 1);
|
|
112
|
+
const { sourceHash } = tests[0]; const projection = tests.map(({ name, passed }) => ({ name, passed }));
|
|
113
|
+
const prior = bySource.get(sourceHash);
|
|
114
|
+
if (prior) { assert.deepStrictEqual(prior.tests, projection, 'confirmations must agree per test'); prior.confirmations++; }
|
|
115
|
+
else bySource.set(sourceHash, { sourceHash, confirmations: 1, tests: projection });
|
|
116
|
+
}
|
|
117
|
+
assert.equal(bySource.size, 5, 'healthy source and four distinct mutations');
|
|
118
|
+
for (const group of bySource.values()) assert.equal(group.confirmations, 3);
|
|
119
|
+
assert.equal(execFileSync('git', ['status', '--porcelain'], { cwd: dir, encoding: 'utf8' }), '', 'probe must restore the measured tree');
|
|
120
|
+
const runnerMs = evidence.records.flatMap((r, i) => [...(i === 0 ? r.detail.baselineRuns : []), ...r.detail.probeRuns]).reduce((sum, r) => sum + r.durationMs, 0);
|
|
121
|
+
const result = { label, engine, wallMs, runnerMs, workerCount: workers.length, projection: proofProjection(evidence), testProjection: [...bySource.values()].sort((a,b) => a.sourceHash.localeCompare(b.sourceHash)) };
|
|
122
|
+
console.log(JSON.stringify({ label, engine, wallMs: Math.round(wallMs), runnerMs }));
|
|
123
|
+
return result;
|
|
124
|
+
} finally { rmSync(dir, { recursive: true, force: true }); }
|
|
125
|
+
}
|
|
126
|
+
// One unmeasured warm-up per engine, then reversed-order pairs. No parallel
|
|
127
|
+
// execution, evidence reuse or reduction in confirmations contributes to speed.
|
|
128
|
+
await measure('vitest', 'warmup'); await measure('node-test', 'warmup');
|
|
129
|
+
for (let i = 0; i < pairs; i++) {
|
|
130
|
+
const pair = [];
|
|
131
|
+
for (const engine of i % 2 ? ['node-test', 'vitest'] : ['vitest', 'node-test']) pair.push(await measure(engine, `pair-${i + 1}`));
|
|
132
|
+
assert.deepStrictEqual(pair[0].projection, pair[1].projection, 'every verdict and run count must agree');
|
|
133
|
+
assert.deepStrictEqual(pair[0].testProjection, pair[1].testProjection, 'every baseline, failed test identity and passed test must agree');
|
|
134
|
+
results.push(...pair);
|
|
135
|
+
}
|
|
136
|
+
const median = (numbers) => { const sorted = [...numbers].sort((a,b) => a-b); const middle = Math.floor(sorted.length/2); return sorted.length%2 ? sorted[middle] : (sorted[middle-1]+sorted[middle])/2; };
|
|
137
|
+
const medians = Object.fromEntries(['vitest', 'node-test'].map((engine) => [engine, median(results.filter((r) => r.engine === engine).map((r) => r.wallMs))]));
|
|
138
|
+
const receipt = { scope: 'One classifier file, 17 identical inputs, four unchanged fault recipes, N=3; no generic framework compatibility claim.', generatedAt: new Date().toISOString(), runtime: { version: process.versions.node, execPath: process.execPath, platform: platform(), arch: arch(), vitestVersion }, classifierHash: sha256(classifier), corpusHash: sha256(JSON.stringify(corpus)), claimsHash: sha256(JSON.stringify(claims)), toolSources, pairs, medians, speedup: medians.vitest/medians['node-test'], results };
|
|
139
|
+
writeFileSync(join(out, 'summary.json'), JSON.stringify(receipt, null, 2)+'\n');
|
|
140
|
+
console.log(JSON.stringify({ medians, speedup: receipt.speedup }));
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// No test-framework dependencies: this also verifies the exact Node20.0.0 floor.
|
|
3
|
+
import assert from 'node:assert/strict';
|
|
4
|
+
import { cpSync, mkdtempSync, readFileSync, rmSync } from 'node:fs';
|
|
5
|
+
import { execFileSync, spawnSync } from 'node:child_process';
|
|
6
|
+
import { tmpdir } from 'node:os';
|
|
7
|
+
import { dirname, join, resolve } from 'node:path';
|
|
8
|
+
import { fileURLToPath } from 'node:url';
|
|
9
|
+
import { readSpecDoc } from '../../src/evidence/writer.mjs';
|
|
10
|
+
const root = resolve(dirname(fileURLToPath(import.meta.url)), '../..');
|
|
11
|
+
const scratch = mkdtempSync(join(tmpdir(), 'tg-native-oracle-'));
|
|
12
|
+
try {
|
|
13
|
+
cpSync(join(root, 'fixtures/known-answer-node'), scratch, { recursive: true });
|
|
14
|
+
for (const args of [['init', '-q'], ['add', '.'], ['-c', 'user.name=fixture', '-c', 'user.email=fixture@example.invalid', 'commit', '-qm', 'fixture']]) execFileSync('git', args, { cwd: scratch, timeout: 5000 });
|
|
15
|
+
const result = spawnSync(process.execPath, [join(root, 'cli/testguard.mjs'), 'probe', scratch, '--runner', 'node-test', '--budget', '10000', '--quiet'], { encoding: 'utf8', timeout: 60_000 });
|
|
16
|
+
assert.equal(result.status, 1, `${result.error ?? ''}\n${result.stderr}\n${result.stdout}`);
|
|
17
|
+
const evidence = readSpecDoc('evidence', join(scratch, '.testguard/evidence.json'));
|
|
18
|
+
const expected = JSON.parse(readFileSync(join(root, 'fixtures/known-answer-node/expected.json'), 'utf8')).expected;
|
|
19
|
+
assert.deepStrictEqual(evidence.run.runner, { name: 'node-test', source: 'builtin', version: process.versions.node });
|
|
20
|
+
assert.equal(evidence.records.length, Object.keys(expected).length);
|
|
21
|
+
for (const record of evidence.records) {
|
|
22
|
+
const oracle = expected[`${record.claim.id}/${record.subject.id}`];
|
|
23
|
+
assert(oracle); assert.equal(record.verdict, oracle.verdict);
|
|
24
|
+
if (oracle.reason) assert.equal(record.detail.reason, oracle.reason);
|
|
25
|
+
}
|
|
26
|
+
assert.equal(execFileSync('git', ['status', '--porcelain'], { cwd: scratch, encoding: 'utf8' }), '');
|
|
27
|
+
console.log(`native Node ${process.versions.node}: all ${evidence.records.length} independently verified verdicts match; targets restored`);
|
|
28
|
+
} finally { rmSync(scratch, { recursive: true, force: true }); }
|
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Prove the package works AS INSTALLED, not as checked out: pack it, install
|
|
4
|
+
* the tarball into a scratch project with production dependencies only, and
|
|
5
|
+
* run the CLI from there. This is the gate that would have caught 0.1.0,
|
|
6
|
+
* which shipped with its schema validator's dependency declared as a
|
|
7
|
+
* devDependency and crashed on startup for every npm/npx/pip/brew user.
|
|
8
|
+
*/
|
|
9
|
+
import { execFileSync } from 'node:child_process';
|
|
10
|
+
import { mkdtempSync, mkdirSync, rmSync, cpSync, readdirSync, readFileSync, writeFileSync, existsSync } from 'node:fs';
|
|
11
|
+
import { tmpdir, devNull } from 'node:os';
|
|
12
|
+
import { join, resolve } from 'node:path';
|
|
13
|
+
|
|
14
|
+
const root = resolve(process.argv[1], '..', '..', '..');
|
|
15
|
+
const run = (cmd, args, cwd) => execFileSync(cmd, args, { cwd, encoding: 'utf8', stdio: ['ignore', 'pipe', 'inherit'] });
|
|
16
|
+
const npm = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
17
|
+
|
|
18
|
+
const scratch = mkdtempSync(join(tmpdir(), 'testguard-install-smoke-'));
|
|
19
|
+
const annotationRecovery = [];
|
|
20
|
+
try {
|
|
21
|
+
const packDir = join(scratch, 'pack');
|
|
22
|
+
mkdirSync(packDir);
|
|
23
|
+
run(npm, ['pack', '--pack-destination', packDir, '--ignore-scripts'], root);
|
|
24
|
+
const tgz = join(packDir, readdirSync(packDir).find((f) => f.endsWith('.tgz')));
|
|
25
|
+
|
|
26
|
+
const consumer = join(scratch, 'consumer');
|
|
27
|
+
cpSync(join(root, 'fixtures', 'known-answer'), consumer, { recursive: true, filter: (s) => !/node_modules|\.flake-counter|\.testguard/.test(s) });
|
|
28
|
+
run(npm, ['init', '-y'], consumer);
|
|
29
|
+
run(npm, ['install', '--omit=dev', '--ignore-scripts', '--no-audit', '--no-fund', tgz], consumer);
|
|
30
|
+
|
|
31
|
+
const bin = join(consumer, 'node_modules', '.bin', process.platform === 'win32' ? 'testguard.cmd' : 'testguard');
|
|
32
|
+
const version = run(bin, ['--version'], consumer).trim();
|
|
33
|
+
const nativeProject = join(scratch, 'native-node');
|
|
34
|
+
mkdirSync(join(nativeProject, 'test'), { recursive: true });
|
|
35
|
+
writeFileSync(join(nativeProject, 'package.json'), '{"type":"module"}');
|
|
36
|
+
writeFileSync(join(nativeProject, 'test', 'native.test.mjs'), "import {test} from 'node:test';import assert from 'node:assert/strict';test('installed native runner',()=>assert.equal(2+2,4));");
|
|
37
|
+
const installedNodeRunner = join(consumer, 'node_modules', 'testguard-cli', 'src', 'probe', 'runners', 'node-test.mjs');
|
|
38
|
+
run(process.execPath, ['--input-type=module', '-e',
|
|
39
|
+
`import {pathToFileURL} from 'node:url'; const {discoverTests,run}=await import(pathToFileURL(process.argv[1]));
|
|
40
|
+
const projectDir=process.argv[2];const manifest=await discoverTests({projectDir,version:process.versions.node});
|
|
41
|
+
if(JSON.stringify(manifest.files)!==JSON.stringify(['test/native.test.mjs']))throw new Error('installed native entry discovery failed');
|
|
42
|
+
const result=await run({projectDir,files:manifest.files,budgetMs:5000});
|
|
43
|
+
if(result.run.outcome!=='pass'||result.run.tests.passed!==1)throw new Error('production-only native Node runner failed');`,
|
|
44
|
+
installedNodeRunner, nativeProject,
|
|
45
|
+
], consumer);
|
|
46
|
+
const emptyProject = join(scratch, 'empty-adoption');
|
|
47
|
+
mkdirSync(emptyProject);
|
|
48
|
+
writeFileSync(join(emptyProject, 'testguard.claims.json'), JSON.stringify({ schemaVersion: 1, claims: [] }));
|
|
49
|
+
const emptyStatus = JSON.parse(run(bin, ['probe', emptyProject, '--allow-empty', '--json'], consumer));
|
|
50
|
+
if (emptyStatus.state !== 'no-claims' || emptyStatus.run || existsSync(join(emptyProject, '.testguard', 'evidence.json'))) throw new Error('installed empty-adoption probe invented verification evidence');
|
|
51
|
+
const replayHelp = run(bin, ['replay', '--help'], consumer);
|
|
52
|
+
if (!replayHelp.includes('--since') || replayHelp.includes('--node-modules')) throw new Error('installed command help is not command-specific');
|
|
53
|
+
writeFileSync(join(emptyProject, 'vite.config.mjs'), "export default { base: '/', plugins: [{ configureServer(s) { s.middlewares.use('/__rooms', () => {}); const parts = '/__room'.split('/'); } }] };\n");
|
|
54
|
+
const installedDiscovery = join(consumer, 'node_modules', 'testguard-cli', 'src', 'probe', 'runners', 'discovery.mjs');
|
|
55
|
+
run(process.execPath, ['--input-type=module', '-e',
|
|
56
|
+
"import { pathToFileURL } from 'node:url'; const { hashDiscoveryConfigs } = await import(pathToFileURL(process.argv[1])); hashDiscoveryConfigs(process.argv[2], ['vite.config.mjs']);",
|
|
57
|
+
installedDiscovery, emptyProject,
|
|
58
|
+
], consumer);
|
|
59
|
+
const installedRunner = join(consumer, 'node_modules', 'testguard-cli', 'src', 'probe', 'runners', 'shared.mjs');
|
|
60
|
+
run(process.execPath, ['--input-type=module', '-e',
|
|
61
|
+
`import { pathToFileURL } from 'node:url'; const { parseReport } = await import(pathToFileURL(process.argv[1]));
|
|
62
|
+
const result = parseReport({ success: false, numTotalTests: 2, numFailedTests: 2, testResults: [{ name: 'test.mjs', status: 'failed', assertionResults: [
|
|
63
|
+
{ status: 'failed', failureMessages: ['AssertionError: expected /specify timed out/'] },
|
|
64
|
+
{ status: 'failed', failureMessages: ['Error: Test timed out in 1000ms.'] },
|
|
65
|
+
] }] }, 1);
|
|
66
|
+
if (result.timeouts !== 1 || result.run.assertionFailures !== 1) throw new Error('installed runner confuses assertion text with timeout identity');`,
|
|
67
|
+
installedRunner,
|
|
68
|
+
], consumer);
|
|
69
|
+
const claims = run(bin, ['claims', '.'], consumer);
|
|
70
|
+
const draft = JSON.parse(run(bin, ['scaffold', 'src/redact.mjs', '--json'], consumer));
|
|
71
|
+
const installedValidator = join(consumer, 'node_modules', 'testguard-cli', 'spec', 'lib', 'validate.mjs');
|
|
72
|
+
run(process.execPath, ['--input-type=module', '-e',
|
|
73
|
+
`import { pathToFileURL } from 'node:url';
|
|
74
|
+
const { intentInputRequest } = await import(new URL('../../src/scaffold/request.mjs', pathToFileURL(process.argv[1])));
|
|
75
|
+
const doc = intentInputRequest({ command: 'scaffold', values: { 'from-document': ['requirements.md'] }, suppliedOptions: ['from-document', 'json'] });
|
|
76
|
+
if (JSON.stringify(doc) !== JSON.stringify({ kind: 'document', file: 'requirements.md' }) || !Object.isFrozen(doc)) throw new Error('installed intent request promotes or mutates input');
|
|
77
|
+
let refused = false;
|
|
78
|
+
try { intentInputRequest({ command: 'scaffold', values: { 'from-fix': ['HEAD'] } }); } catch (error) { refused = /full-object-id/.test(error.message); }
|
|
79
|
+
if (!refused) throw new Error('installed intent request accepts symbolic fix reference');`,
|
|
80
|
+
installedValidator,
|
|
81
|
+
], consumer);
|
|
82
|
+
const fixProject = join(scratch, 'fix-input');
|
|
83
|
+
mkdirSync(fixProject);
|
|
84
|
+
const fixGit = args => run('git', ['-c', 'user.name=fixture', '-c', 'user.email=fixture@example.invalid', '-c', `core.hooksPath=${devNull}`, ...args], fixProject).trim();
|
|
85
|
+
fixGit(['init', '-q']);
|
|
86
|
+
writeFileSync(join(fixProject, 'guard.mjs'), 'export const x = 1;');
|
|
87
|
+
mkdirSync(join(fixProject, 'child'));
|
|
88
|
+
writeFileSync(join(fixProject, 'child', 'testguard.claims.json'), 'untrusted historical marker');
|
|
89
|
+
fixGit(['add', '.']); fixGit(['commit', '-qm', 'Initial fixture']);
|
|
90
|
+
const fixParent = fixGit(['rev-parse', 'HEAD']);
|
|
91
|
+
writeFileSync(join(fixProject, 'guard.mjs'), 'export const x = 2;');
|
|
92
|
+
fixGit(['add', '.']); fixGit(['commit', '-qm', 'Supplied fix metadata']);
|
|
93
|
+
const fixCommit = fixGit(['rev-parse', 'HEAD']);
|
|
94
|
+
writeFileSync(join(fixProject, 'placeholder.mjs'), 'export function guard(x) {\n if (!x) return false;\n}\n');
|
|
95
|
+
const placeholderDraft = JSON.parse(run(bin, ['scaffold', 'placeholder.mjs', '--json'], fixProject));
|
|
96
|
+
if (!placeholderDraft.claims.length || placeholderDraft.claims.some(claim => !/^TODO-CLAIM-[1-9][0-9]*$/.test(claim.id))) throw new Error('installed scaffold still emits finished file-derived default IDs');
|
|
97
|
+
const rootInputCli = JSON.parse(run(bin, ['scaffold', '--from-fix', fixCommit, '--json'], fixProject));
|
|
98
|
+
if (rootInputCli.input.scope !== 'project-root' || rootInputCli.input.commit !== fixCommit || rootInputCli.verification !== 'not-performed') throw new Error('installed root input CLI lost fixed identity or truthful verification');
|
|
99
|
+
run(process.execPath, ['--input-type=module', '-e',
|
|
100
|
+
`import { pathToFileURL } from 'node:url';
|
|
101
|
+
const { readFixCommitMetadata, readFixCommitInventory } = await import(new URL('../../src/scaffold/fix-input.mjs', pathToFileURL(process.argv[1])));
|
|
102
|
+
const metadata = readFixCommitMetadata({ projectDir: process.argv[2], commit: process.argv[3] });
|
|
103
|
+
if (metadata.commit !== process.argv[3] || metadata.parent !== process.argv[4] || metadata.subject !== 'Supplied fix metadata' || metadata.claims !== undefined || !Object.isFrozen(metadata)) throw new Error('installed fix metadata failed identity or no-claims boundary');
|
|
104
|
+
const scoped = readFixCommitInventory({ projectDir: process.argv[2], commit: process.argv[3] });
|
|
105
|
+
const { planIntentInput } = await import(new URL('../../src/scaffold/plan-intent-input.mjs', pathToFileURL(process.argv[1])));
|
|
106
|
+
const plan = planIntentInput({ projectDir: process.argv[2], values: { 'from-fix': [process.argv[3]] } });
|
|
107
|
+
if (plan.doc.input.scope !== 'project-root' || plan.doc.input.commit !== metadata.commit || plan.doc.input.parent !== metadata.parent || plan.doc.input.counts.supported !== 1 || plan.doc.verification !== 'not-performed' || JSON.stringify(JSON.parse(plan.output)) !== JSON.stringify(plan.doc) || !Object.isFrozen(plan.doc.input.paths[0])) throw new Error('installed root input planner lost validated identity or scope');
|
|
108
|
+
if (scoped.commit !== metadata.commit || scoped.parent !== metadata.parent || scoped.counts.total !== 1 || scoped.counts.supported !== 1 || scoped.paths[0].file !== 'guard.mjs' || !Object.isFrozen(scoped) || !Object.isFrozen(scoped.paths) || scoped.claims !== undefined) throw new Error('installed scoped inventory failed identity or no-claims boundary');
|
|
109
|
+
const { parseFixChangedPaths, parseFixProjectMarkers } = await import(new URL('../../src/scaffold/fix-paths.mjs', pathToFileURL(process.argv[1])));
|
|
110
|
+
const { execFileSync } = await import('node:child_process');
|
|
111
|
+
const raw = execFileSync('git', ['--no-lazy-fetch', '--no-replace-objects', 'diff-tree', '--raw', '--no-abbrev', '-z', '--no-renames', '--no-ext-diff', '--no-textconv', '-r', '--no-commit-id', metadata.parent, metadata.commit, '--'], { cwd: process.argv[2], timeout: 5000, maxBuffer: 128 * 1024 });
|
|
112
|
+
const inventory = parseFixChangedPaths(raw);
|
|
113
|
+
if (inventory.counts.total !== 1 || inventory.counts.supported !== 1 || inventory.paths[0].file !== 'guard.mjs' || inventory.claims !== undefined || !Object.isFrozen(inventory.paths)) throw new Error('installed raw fix decoder lost complete identity or no-claims boundary');
|
|
114
|
+
const tree = id => execFileSync('git', ['--no-lazy-fetch', '--no-replace-objects', 'ls-tree', '-r', '--name-only', '-z', '--full-tree', id], { cwd: process.argv[2], timeout: 5000, maxBuffer: 128 * 1024 });
|
|
115
|
+
const projects = parseFixProjectMarkers(tree(metadata.parent), tree(metadata.commit));
|
|
116
|
+
if (projects.length !== 1 || projects[0] !== 'child' || !Object.isFrozen(projects) || projects.claims !== undefined) throw new Error('installed historical project decoder lost immutable delegation');`,
|
|
117
|
+
installedValidator, fixProject, fixCommit, fixParent,
|
|
118
|
+
], consumer);
|
|
119
|
+
const nestedFixProject = join(fixProject, 'nested[1]');
|
|
120
|
+
mkdirSync(nestedFixProject);
|
|
121
|
+
writeFileSync(join(nestedFixProject, 'testguard.claims.json'), '{"schemaVersion":1,"claims":[]}');
|
|
122
|
+
writeFileSync(join(nestedFixProject, 'guard.mjs'), 'export const x = 1;');
|
|
123
|
+
fixGit(['add', '.']); fixGit(['commit', '-qm', 'Nested fixture']);
|
|
124
|
+
const nestedFixParent = fixGit(['rev-parse', 'HEAD']);
|
|
125
|
+
writeFileSync(join(nestedFixProject, 'guard.mjs'), 'export const x = 2;');
|
|
126
|
+
fixGit(['add', '.']); fixGit(['commit', '-qm', 'Nested fix']);
|
|
127
|
+
const nestedFixCommit = fixGit(['rev-parse', 'HEAD']);
|
|
128
|
+
const nestedInputCli = JSON.parse(run(bin, ['scaffold', '--from-fix', nestedFixCommit, '--json'], nestedFixProject));
|
|
129
|
+
if (nestedInputCli.input.scope !== 'selected-nested-project' || nestedInputCli.input.counts.supported !== 1 || nestedInputCli.input.paths[0].file !== 'guard.mjs') throw new Error('installed nested input CLI lost literal project scope');
|
|
130
|
+
writeFileSync(join(fixProject, 'intent.md'), 'UNTRUSTED INPUT TEXT');
|
|
131
|
+
const documentInputCli = run(bin, ['scaffold', '--from-document', 'intent.md', '--json'], fixProject);
|
|
132
|
+
if (JSON.parse(documentInputCli).input.bytes !== Buffer.byteLength('UNTRUSTED INPUT TEXT') || documentInputCli.includes('UNTRUSTED INPUT TEXT')) throw new Error('installed document input CLI leaked text or lost size');
|
|
133
|
+
const humanInputCli = run(bin, ['scaffold', '--from-document', 'intent.md'], fixProject);
|
|
134
|
+
if (!humanInputCli.includes('verification not performed') || !humanInputCli.includes('independent intent') || humanInputCli.includes('UNTRUSTED INPUT TEXT') || existsSync(join(fixProject, '.testguard'))) throw new Error('installed human input CLI lost read-only or truthful boundary');
|
|
135
|
+
run(process.execPath, ['--input-type=module', '-e',
|
|
136
|
+
`import { pathToFileURL } from 'node:url';
|
|
137
|
+
const { readNestedFixCommitInventory } = await import(new URL('../../src/scaffold/fix-input.mjs', pathToFileURL(process.argv[1])));
|
|
138
|
+
const inventory = readNestedFixCommitInventory({ projectDir: process.argv[2], commit: process.argv[3] });
|
|
139
|
+
const { planIntentInput } = await import(new URL('../../src/scaffold/plan-intent-input.mjs', pathToFileURL(process.argv[1])));
|
|
140
|
+
const plan = planIntentInput({ projectDir: process.argv[2], values: { 'from-fix': [process.argv[3]] } });
|
|
141
|
+
if (plan.doc.input.scope !== 'selected-nested-project' || plan.doc.input.commit !== inventory.commit || plan.doc.input.parent !== inventory.parent || plan.doc.input.paths[0].file !== 'guard.mjs' || plan.doc.input.counts.total !== 1 || plan.doc.verification !== 'not-performed' || !Object.isFrozen(plan.doc.input.counts)) throw new Error('installed nested input planner lost validated literal scope');
|
|
142
|
+
if (inventory.commit !== process.argv[3] || inventory.parent !== process.argv[4] || inventory.counts.total !== 1 || inventory.counts.supported !== 1 || inventory.paths[0].file !== 'guard.mjs' || inventory.claims !== undefined || !Object.isFrozen(inventory) || !Object.isFrozen(inventory.paths[0])) throw new Error('installed nested inventory lost literal scope or immutable identity');`,
|
|
143
|
+
installedValidator, nestedFixProject, nestedFixCommit, nestedFixParent,
|
|
144
|
+
], consumer);
|
|
145
|
+
const annotationProject = join(scratch, 'annotation-authoring');
|
|
146
|
+
mkdirSync(annotationProject);
|
|
147
|
+
writeFileSync(join(annotationProject, 'guard.mjs'), 'export const allowed = true;\n');
|
|
148
|
+
const annotationClaims = JSON.stringify({ schemaVersion: 1, claims: [{ id: 'A-1', statement: 'The intended condition holds.', severity: 'high', source: { kind: 'manual' }, producedBy: { producer: 'human' }, faults: [{ id: 'F1', description: 'Break condition.', faultClass: 'other', file: 'guard.mjs', find: 'true', replace: 'false', producedBy: { producer: 'human' } }] }] });
|
|
149
|
+
writeFileSync(join(annotationProject, 'testguard.claims.json'), annotationClaims);
|
|
150
|
+
const annotationInspection = JSON.parse(run(bin, ['claims', annotationProject, '--json'], consumer));
|
|
151
|
+
if (annotationInspection.annotationAdvisory.missingIds.join(',') !== 'A-1' || !annotationInspection.annotationAdvisory.notes.some((n) => n.includes('testguard claims --annotate'))) throw new Error('installed claims omitted source-link advice');
|
|
152
|
+
let annotationStatusText;
|
|
153
|
+
try { annotationStatusText = run(bin, ['status', annotationProject, '--json'], consumer); }
|
|
154
|
+
catch (error) { if (error.status !== 2) throw error; annotationStatusText = error.stdout; }
|
|
155
|
+
const annotationStatus = JSON.parse(annotationStatusText);
|
|
156
|
+
if (annotationStatus.origins?.basis !== 'declared' || annotationStatus.origins.claims.total !== 1 || annotationStatus.origins.claims.byKind.manual !== 1) throw new Error('installed status omitted current declared origins');
|
|
157
|
+
if (annotationStatus.state !== 'unprobed' || annotationStatus.next.action !== 'probe' || !annotationStatus.notes.some((n) => n.includes('ANNOTATION ADVISORY'))) throw new Error('installed annotation advice changed or omitted verification guidance');
|
|
158
|
+
const annotationPreview = JSON.parse(run(bin, ['claims', annotationProject, '--annotate', '--json'], consumer));
|
|
159
|
+
if (annotationPreview.state !== 'preview' || readFileSync(join(annotationProject, 'guard.mjs'), 'utf8') !== 'export const allowed = true;\n') throw new Error('installed annotation preview wrote source or failed');
|
|
160
|
+
const annotationApply = JSON.parse(run(bin, ['claims', annotationProject, '--annotate', '--apply', '--json'], consumer));
|
|
161
|
+
if (annotationApply.outcome?.recoveryDir) annotationRecovery.push(annotationApply.outcome.recoveryDir);
|
|
162
|
+
if (annotationApply.state !== 'applied' || annotationApply.outcome?.changed.join(',') !== 'guard.mjs' || readFileSync(join(annotationProject, 'guard.mjs'), 'utf8') !== '// @claim A-1\nexport const allowed = true;\n' || readFileSync(join(annotationProject, 'testguard.claims.json'), 'utf8') !== annotationClaims || existsSync(join(annotationProject, '.testguard'))) throw new Error('installed annotation apply failed its source/claims/evidence boundary');
|
|
163
|
+
run(process.execPath, ['--input-type=module', '-e',
|
|
164
|
+
`import { pathToFileURL } from 'node:url'; const { validate } = await import(pathToFileURL(process.argv[1]));
|
|
165
|
+
for (const doc of JSON.parse(process.argv[2])) if (!validate('annotations', doc).ok) throw new Error('installed authoring result failed its own validator');`,
|
|
166
|
+
installedValidator, JSON.stringify([annotationPreview, annotationApply]),
|
|
167
|
+
], consumer);
|
|
168
|
+
const candidateEvidence = join(root, 'spec', 'conformance', 'examples', 'evidence.json');
|
|
169
|
+
const validatesCandidateEvidence = run(process.execPath, [
|
|
170
|
+
'--input-type=module',
|
|
171
|
+
'-e',
|
|
172
|
+
`import { pathToFileURL } from 'node:url'; import { readFileSync, writeFileSync, rmSync, mkdirSync } from 'node:fs'; import { createHash } from 'node:crypto';
|
|
173
|
+
const { validate } = await import(pathToFileURL(process.argv[1]));
|
|
174
|
+
const candidate = JSON.parse(readFileSync(process.argv[2], 'utf8'));
|
|
175
|
+
for (const r of candidate.records) {
|
|
176
|
+
r.defenders.selectionSource = r.defenders.requested.length ? 'claim' : 'discovery';
|
|
177
|
+
if (!r.defenders.requested.length) r.defenders.discovered = true;
|
|
178
|
+
}
|
|
179
|
+
candidate.run.workers = 1;
|
|
180
|
+
candidate.run.measurements = { elapsedMs: 12, runnerMs: 9, overheadMs: 3, runnerInvocations: 6 };
|
|
181
|
+
const result = validate('evidence', candidate);
|
|
182
|
+
if (!result.ok) throw new Error(result.errors.map((error) => \`${'${error.path}: ${error.message}'}\`).join('; '));
|
|
183
|
+
candidate.run.measurements.overheadMs = 4;
|
|
184
|
+
if (validate('evidence', candidate).ok) throw new Error('installed package accepted inconsistent resource timing');
|
|
185
|
+
candidate.run.measurements.overheadMs = 3;
|
|
186
|
+
const claims = JSON.parse(readFileSync(process.argv[4], 'utf8'));
|
|
187
|
+
claims.claims[0].faults[0].defendedBy = [];
|
|
188
|
+
if (!validate('claims', claims).ok) throw new Error('installed package rejected a fault discovery override');
|
|
189
|
+
claims.claims[0].faults[0].defendedBy = ['test/unit.test.mjs'];
|
|
190
|
+
if (!validate('claims', claims).ok) throw new Error('installed package rejected a fault defender override');
|
|
191
|
+
const { appendDraft } = await import(new URL('../../src/scaffold/append.mjs', pathToFileURL(process.argv[1])));
|
|
192
|
+
const appendProposals = structuredClone(claims);
|
|
193
|
+
appendProposals.claims = [appendProposals.claims[0]];
|
|
194
|
+
appendProposals.claims[0].faults = [{ ...appendProposals.claims[0].faults[0], file: 'src/append-new.mjs' }];
|
|
195
|
+
const originalDraft = JSON.stringify(claims);
|
|
196
|
+
const appended = appendDraft({ draft: claims, proposals: appendProposals, claimId: claims.claims[0].id });
|
|
197
|
+
if (!validate('claims', appended).ok || appended.claims[0].faults.length !== claims.claims[0].faults.length + 1 || JSON.stringify(claims) !== originalDraft || JSON.stringify(appended.claims[0].source) !== JSON.stringify(claims.claims[0].source)) throw new Error('installed append lost preservation or admission');
|
|
198
|
+
const repeated = appendDraft({ draft: appended, proposals: appendProposals, claimId: claims.claims[0].id });
|
|
199
|
+
if (JSON.stringify(repeated) !== JSON.stringify(appended)) throw new Error('installed append is not idempotent');
|
|
200
|
+
const { poolDrafts } = await import(new URL('../../src/sweep/pool.mjs', pathToFileURL(process.argv[1])));
|
|
201
|
+
const pooledDrafts = poolDrafts([claims, structuredClone(claims)]);
|
|
202
|
+
if (!validate('claims', { schemaVersion: 1, claims: pooledDrafts.flatMap((d) => d.claims) }).ok || pooledDrafts.flatMap((d) => d.claims).length !== claims.claims.length * 2 || JSON.stringify(claims) !== originalDraft) throw new Error('installed sweep pool lost identity or preservation');
|
|
203
|
+
const { scaffoldFile } = await import(new URL('../../src/scaffold/scaffold.mjs', pathToFileURL(process.argv[1])));
|
|
204
|
+
const admittedArgs = { projectDir: process.cwd(), file: 'missing-admitted-source.mjs', source: ['export function guard(x) {', ' if (!x) throw new Error("absent");', '}'].join(String.fromCharCode(10)) };
|
|
205
|
+
const admitted = scaffoldFile({ ...admittedArgs, maxProposals: 10 });
|
|
206
|
+
if (!validate('claims', admitted.doc).ok || admitted.stats.proposals < 1) throw new Error('installed scaffold did not scan admitted source');
|
|
207
|
+
if (scaffoldFile({ ...admittedArgs, source: '', maxProposals: 0 }).stats.proposals !== 0) throw new Error('installed scaffold reread empty source');
|
|
208
|
+
let refusedLimit = false;
|
|
209
|
+
try { scaffoldFile({ ...admittedArgs, maxProposals: 0 }); } catch (error) { refusedLimit = error.message.includes('proposal limit'); }
|
|
210
|
+
if (!refusedLimit) throw new Error('installed scaffold ignored proposal limit');
|
|
211
|
+
const { admitDraftAppend, admitIntentDocument, admitIntentProject } = await import(new URL('../../src/scaffold/admission.mjs', pathToFileURL(process.argv[1])));
|
|
212
|
+
const intentProjectRoot = process.cwd() + '/intent-project-marker';
|
|
213
|
+
mkdirSync(intentProjectRoot); const markerBytes = JSON.stringify({ schemaVersion: 1, claims: [] });
|
|
214
|
+
writeFileSync(intentProjectRoot + '/testguard.claims.json', markerBytes);
|
|
215
|
+
const intentProject = admitIntentProject({ projectDir: intentProjectRoot });
|
|
216
|
+
if (intentProject.marker.hash !== createHash('sha256').update(markerBytes).digest('hex') || !Object.isFrozen(intentProject) || !Object.isFrozen(intentProject.marker) || !Object.isFrozen(intentProject.marker.identity) || intentProject.claims !== undefined || intentProject.marker.source !== undefined || readFileSync(intentProjectRoot + '/testguard.claims.json', 'utf8') !== markerBytes) throw new Error('installed project marker admission lost bounded identity or no-claims boundary');
|
|
217
|
+
writeFileSync('intent-input.md', 'Absent input must be rejected.');
|
|
218
|
+
const intentInput = admitIntentDocument({ projectDir: process.cwd(), file: 'intent-input.md' });
|
|
219
|
+
if (intentInput.source !== 'Absent input must be rejected.' || !Object.isFrozen(intentInput) || !Object.isFrozen(intentInput.identity) || intentInput.claims !== undefined || readFileSync('intent-input.md', 'utf8') !== intentInput.source) throw new Error('installed document admission lost text, immutability or no-claims boundary');
|
|
220
|
+
const inputReport = { schemaVersion: 1, tool: { name: 'testguard', version: 'test' }, purpose: 'authoring-input', verification: 'not-performed', next: 'supply-independent-intent', input: { kind: 'document', file: intentInput.file, hash: intentInput.hash, bytes: intentInput.identity.size } };
|
|
221
|
+
if (!validate('authoring-input', inputReport).ok) throw new Error('installed input report contract rejected admitted metadata');
|
|
222
|
+
const { planIntentInput } = await import(new URL('../../src/scaffold/plan-intent-input.mjs', pathToFileURL(process.argv[1])));
|
|
223
|
+
const documentPlan = planIntentInput({ projectDir: process.cwd(), values: { 'from-document': ['intent-input.md'] }, toolVersion: 'test' });
|
|
224
|
+
if (JSON.stringify(documentPlan.doc) !== JSON.stringify(inputReport) || JSON.stringify(JSON.parse(documentPlan.output)) !== JSON.stringify(inputReport) || documentPlan.output.includes(intentInput.source) || !Object.isFrozen(documentPlan) || !Object.isFrozen(documentPlan.doc.input)) throw new Error('installed document planner lost metadata-only validated projection');
|
|
225
|
+
const { writeSpecDoc, readSpecDoc } = await import(new URL('../../src/evidence/writer.mjs', pathToFileURL(process.argv[1])));
|
|
226
|
+
let inputReportBytes;
|
|
227
|
+
writeSpecDoc('authoring-input', 'unused.json', inputReport, { publish: bytes => { inputReportBytes = bytes; } });
|
|
228
|
+
if (JSON.stringify(readSpecDoc('authoring-input', 'unused.json', { source: inputReportBytes })) !== JSON.stringify(inputReport) || inputReportBytes.includes(intentInput.source)) throw new Error('installed input report lost validation or leaked document text');
|
|
229
|
+
const optimisticReport = { ...inputReport, verification: 'killed' };
|
|
230
|
+
if (validate('authoring-input', optimisticReport).ok) throw new Error('installed input report admits an optimistic verification label');
|
|
231
|
+
let refusedPrivateDocument = false;
|
|
232
|
+
try { admitIntentDocument({ projectDir: process.cwd(), file: '.local/secret.md' }); } catch (error) { refusedPrivateDocument = error.message.includes('excluded-document'); }
|
|
233
|
+
if (!refusedPrivateDocument) throw new Error('installed document admission accepted private input');
|
|
234
|
+
writeFileSync('append-admission-draft.json', JSON.stringify(claims));
|
|
235
|
+
writeFileSync('append-admission-source.mjs', admittedArgs.source);
|
|
236
|
+
const admittedInputs = admitDraftAppend({ projectDir: process.cwd(), draftPath: 'append-admission-draft.json', files: ['append-admission-source.mjs'], claimId: claims.claims[0].id });
|
|
237
|
+
if (!validate('claims', admittedInputs.draft.doc).ok || admittedInputs.sources[0].source !== admittedArgs.source || readFileSync('append-admission-draft.json', 'utf8') !== JSON.stringify(claims)) throw new Error('installed draft admission lost validated input or wrote the draft');
|
|
238
|
+
const { planDraftAppend } = await import(new URL('../../src/scaffold/plan-append.mjs', pathToFileURL(process.argv[1])));
|
|
239
|
+
const planRequest = { projectDir: process.cwd(), draftPath: 'append-admission-draft.json', files: ['append-admission-source.mjs'], claimId: claims.claims[0].id };
|
|
240
|
+
const planned = planDraftAppend(planRequest);
|
|
241
|
+
if (!validate('claims', planned.doc).ok || !planned.changed || planned.stats.appended < 1 || readFileSync('append-admission-draft.json', 'utf8') !== JSON.stringify(claims)) throw new Error('installed append planner lost proposals or published during preview');
|
|
242
|
+
const { prepareDraftAppend } = await import(new URL('../../src/scaffold/prepare-append.mjs', pathToFileURL(process.argv[1])));
|
|
243
|
+
const preparedDraftBytes = readFileSync('append-admission-draft.json', 'utf8');
|
|
244
|
+
const prepared = prepareDraftAppend({ request: planRequest, plan: planned });
|
|
245
|
+
try {
|
|
246
|
+
if (!prepared.ready || readFileSync('append-admission-draft.json', 'utf8') !== preparedDraftBytes || readFileSync(prepared.recoveryDir + '/draft.original', 'utf8') !== preparedDraftBytes) throw new Error('installed append preparation changed draft or lost recovery');
|
|
247
|
+
prepared.assertOwned();
|
|
248
|
+
} finally {
|
|
249
|
+
const released = prepared.release();
|
|
250
|
+
rmSync(prepared.recoveryDir, { recursive: true, force: true });
|
|
251
|
+
if (!released.released) throw new Error('installed append preparation lost lock ownership');
|
|
252
|
+
}
|
|
253
|
+
const { openDraftAppend } = await import(new URL('../../src/scaffold/open-append.mjs', pathToFileURL(process.argv[1])));
|
|
254
|
+
const openedDraft = openDraftAppend({ request: planRequest, plan: planned });
|
|
255
|
+
try {
|
|
256
|
+
openedDraft.assertCurrent();
|
|
257
|
+
if (typeof openedDraft.fd !== 'number' || readFileSync('append-admission-draft.json', 'utf8') !== preparedDraftBytes) throw new Error('installed append descriptor changed the draft');
|
|
258
|
+
} finally {
|
|
259
|
+
const released = openedDraft.release();
|
|
260
|
+
rmSync(openedDraft.recoveryDir, { recursive: true, force: true });
|
|
261
|
+
if (!released.released) throw new Error('installed append descriptor release failed');
|
|
262
|
+
}
|
|
263
|
+
const { applyDraftAppend } = await import(new URL('../../src/scaffold/apply-append.mjs', pathToFileURL(process.argv[1])));
|
|
264
|
+
const appliedDraft = applyDraftAppend({ request: planRequest, plan: planned });
|
|
265
|
+
try {
|
|
266
|
+
if (!appliedDraft.ok || appliedDraft.state !== 'updated' || readFileSync('append-admission-draft.json', 'utf8') !== planned.output) throw new Error('installed append publication failed');
|
|
267
|
+
const repeatedDraft = applyDraftAppend({ request: planRequest, plan: planDraftAppend(planRequest) });
|
|
268
|
+
if (!repeatedDraft.ok || repeatedDraft.state !== 'unchanged' || repeatedDraft.recoveryDir) throw new Error('installed append repeat wrote or lost idempotence');
|
|
269
|
+
} finally { if (appliedDraft.recoveryDir) rmSync(appliedDraft.recoveryDir, { recursive: true, force: true }); }
|
|
270
|
+
if (planDraftAppend(planRequest).changed) throw new Error('installed append planner lost repeat idempotence');
|
|
271
|
+
for (const kind of ['spec', 'adr', 'annotation', 'comment', 'manual', 'doc', 'bug', 'incident', 'review', 'inferred']) {
|
|
272
|
+
claims.claims[0].source.kind = kind; candidate.records[0].claim.source.kind = kind;
|
|
273
|
+
if (!validate('claims', claims).ok || !validate('evidence', candidate).ok) throw new Error('installed package rejected a declared origin: ' + kind);
|
|
274
|
+
}
|
|
275
|
+
const { recordedOriginSummary } = await import(new URL('./origins.mjs', pathToFileURL(process.argv[1])));
|
|
276
|
+
candidate.origins = recordedOriginSummary(candidate.records);
|
|
277
|
+
if (!validate('evidence', candidate).ok) throw new Error('installed package rejected its recorded origin summary');
|
|
278
|
+
const { buildBrief } = await import(new URL('../../src/brief/brief.mjs', pathToFileURL(process.argv[1])));
|
|
279
|
+
const brief = buildBrief(candidate, undefined, { max: 0 });
|
|
280
|
+
if (!validate('brief', brief).ok || brief.origins.records.total !== candidate.records.length || !brief.text.includes('declared origins, not authenticated independence')) throw new Error('installed brief lost full recorded origins or caveat');
|
|
281
|
+
const oldKind = Object.keys(candidate.origins.claims.byKind).find((kind) => candidate.origins.claims.byKind[kind] > 0);
|
|
282
|
+
candidate.origins.claims.byKind[oldKind]--; candidate.origins.claims.byKind.bug++;
|
|
283
|
+
if (validate('evidence', candidate).ok) throw new Error('installed package accepted an invented origin split');
|
|
284
|
+
delete candidate.origins;
|
|
285
|
+
candidate.originPolicy = {
|
|
286
|
+
state: 'unavailable', eligibleKinds: ['spec'],
|
|
287
|
+
claims: new Set(candidate.records.map((r) => r.claim.id)).size,
|
|
288
|
+
faults: candidate.records.length, ineligibleClaims: [],
|
|
289
|
+
nonKilledFaults: [...new Map(candidate.records.filter((r) => r.verdict !== 'killed').map((r) => [JSON.stringify([r.claim.id, r.subject.id]), { claimId: r.claim.id, faultId: r.subject.id }])).values()],
|
|
290
|
+
unavailableReasons: ['stale-inputs'],
|
|
291
|
+
};
|
|
292
|
+
if (!validate('evidence', candidate).ok) throw new Error('installed package rejected the unavailable origin policy contract');
|
|
293
|
+
const policyBrief = buildBrief(candidate, undefined, { max: 0 });
|
|
294
|
+
if (!validate('brief', policyBrief).ok || JSON.stringify(policyBrief.originPolicy) !== JSON.stringify(candidate.originPolicy) || !policyBrief.text.includes('not current freshness') || policyBrief.text.includes('Every probed claim is defended')) throw new Error('installed brief lost policy scope or invented capped defense');
|
|
295
|
+
candidate.originPolicy.state = 'passed';
|
|
296
|
+
if (validate('evidence', candidate).ok) throw new Error('installed package accepted an optimistic origin policy');
|
|
297
|
+
delete candidate.originPolicy;
|
|
298
|
+
const { originPolicyRequest } = await import(new URL('../../src/probe/origin-policy.mjs', pathToFileURL(process.argv[1])));
|
|
299
|
+
if (JSON.stringify(originPolicyRequest({ 'require-origin': ['spec,bug', 'spec'] })) !== JSON.stringify(['bug', 'spec'])) throw new Error('installed candidate lost origin-policy request admission');
|
|
300
|
+
let refusedPartialPolicy = false;
|
|
301
|
+
try { originPolicyRequest({ 'require-origin': ['spec'], claim: ['C-1'] }); } catch (e) { refusedPartialPolicy = e instanceof TypeError; }
|
|
302
|
+
if (!refusedPartialPolicy) throw new Error('installed candidate accepted a partial origin policy request');
|
|
303
|
+
const { hashNativeTestUniverse } = await import(new URL('../../src/probe/universe.mjs', pathToFileURL(process.argv[1])));
|
|
304
|
+
const { createDiscoveryManifest } = await import(new URL('../../src/probe/runners/discovery.mjs', pathToFileURL(process.argv[1])));
|
|
305
|
+
const nativeManifest = createDiscoveryManifest({ runner: 'vitest', version: '1', files: ['test/a.test.mjs'] });
|
|
306
|
+
const initialUniverse = hashNativeTestUniverse(new Map([[{ name: 'vitest' }, nativeManifest]]));
|
|
307
|
+
const changedManifest = createDiscoveryManifest({ runner: 'vitest', version: '1', files: ['test/a.test.mjs', 'test/b.test.mjs'] });
|
|
308
|
+
if (initialUniverse === hashNativeTestUniverse(new Map([[{ name: 'vitest' }, changedManifest]]))) throw new Error('installed package ignored native universe membership');
|
|
309
|
+
const { originPolicyInputsFresh } = await import(new URL('../../src/probe/policy-freshness.mjs', pathToFileURL(process.argv[1])));
|
|
310
|
+
if (originPolicyInputsFresh({ claims, evidence: candidate }) !== false) throw new Error('installed package admitted missing runtime policy bindings');
|
|
311
|
+
const { main: candidateMain } = await import(new URL('../../src/cli.mjs', pathToFileURL(process.argv[1])));
|
|
312
|
+
writeFileSync('append-cli-draft.json', JSON.stringify(claims));
|
|
313
|
+
const appendCliArgs = ['scaffold', 'append-admission-source.mjs', '--into', 'append-cli-draft.json', '--claim', claims.claims[0].id];
|
|
314
|
+
const appendPreview = [];
|
|
315
|
+
const appendPreviewCode = await candidateMain([...appendCliArgs, '--json'], { out: s => appendPreview.push(s), err: s => { throw new Error(s); } });
|
|
316
|
+
if (appendPreviewCode !== 0 || !validate('claims', JSON.parse(appendPreview.join(''))).ok || readFileSync('append-cli-draft.json', 'utf8') !== JSON.stringify(claims)) throw new Error('installed append CLI preview failed or wrote');
|
|
317
|
+
const appendCliOutput = [];
|
|
318
|
+
try {
|
|
319
|
+
const appendCliCode = await candidateMain(appendCliArgs, { out: s => appendCliOutput.push(s), err: s => { throw new Error(s); } });
|
|
320
|
+
if (appendCliCode !== 0 || !appendCliOutput.some(s => s.startsWith('unproven draft updated:')) || !validate('claims', JSON.parse(readFileSync('append-cli-draft.json', 'utf8'))).ok) throw new Error('installed append CLI update failed');
|
|
321
|
+
} finally {
|
|
322
|
+
for (const line of appendCliOutput) if (line.startsWith('private recovery: ')) rmSync(line.slice('private recovery: '.length), { recursive: true, force: true });
|
|
323
|
+
}
|
|
324
|
+
const policyErrors = [];
|
|
325
|
+
const policyCode = await candidateMain(['probe', '--require-origin', 'spec', '--confirm', '1', '--claims', '/nonexistent/policy-smoke.claims.json'], { out: () => { throw new Error('invalid policy emitted output'); }, err: (s) => policyErrors.push(s) });
|
|
326
|
+
if (policyCode !== 3 || !policyErrors.some((s) => s.includes('--require-origin'))) throw new Error('installed candidate lost CLI origin-policy admission');
|
|
327
|
+
claims.claims[0].source.kind = 'authenticated'; candidate.records[0].claim.source.kind = 'authenticated';
|
|
328
|
+
if (validate('claims', claims).ok || validate('evidence', candidate).ok) throw new Error('installed package accepted an unsupported origin');
|
|
329
|
+
const gate = JSON.parse(readFileSync(process.argv[3], 'utf8'));
|
|
330
|
+
gate.nested = [{ project: 'backend', files: ['backend/src/guard.mjs'] }]; gate.changed++;
|
|
331
|
+
const nestedResult = validate('gate', gate);
|
|
332
|
+
if (!nestedResult.ok) throw new Error('installed package rejected the nested-project gate contract');
|
|
333
|
+
process.stdout.write('ok');`,
|
|
334
|
+
installedValidator,
|
|
335
|
+
candidateEvidence,
|
|
336
|
+
join(root, 'spec', 'conformance', 'examples', 'gate.json'),
|
|
337
|
+
join(root, 'spec', 'conformance', 'examples', 'claims.json'),
|
|
338
|
+
], consumer).trim();
|
|
339
|
+
if (!draft.claims?.length) throw new Error('scaffold produced no claims from the installed tarball');
|
|
340
|
+
const suppliedClaims = JSON.parse(readFileSync(join(consumer, 'testguard.claims.json'), 'utf8')).claims;
|
|
341
|
+
let inferredDrafts = 0;
|
|
342
|
+
for (const claim of draft.claims) {
|
|
343
|
+
const supplied = suppliedClaims.find((c) => c.id === claim.id);
|
|
344
|
+
if (supplied) {
|
|
345
|
+
if (claim.statement !== supplied.statement || JSON.stringify(claim.source) !== JSON.stringify(supplied.source)) throw new Error('installed scaffold overwrote supplied intent');
|
|
346
|
+
} else {
|
|
347
|
+
inferredDrafts++;
|
|
348
|
+
if (claim.source.kind !== 'inferred' || !claim.statement.includes('intended observable behavior')) throw new Error('installed scaffold lost inferred intent handoff');
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
if (!inferredDrafts) throw new Error('installed smoke did not exercise a new inferred draft');
|
|
352
|
+
if (!/^\d+\.\d+\.\d+/.test(version)) throw new Error(`unexpected --version output: ${version}`);
|
|
353
|
+
if (!/^\d+ claims in /.test(claims)) throw new Error(`claims did not list the fixture:\n${claims}`);
|
|
354
|
+
if (validatesCandidateEvidence !== 'ok') throw new Error('installed package did not validate candidate-format evidence');
|
|
355
|
+
const deps = Object.keys(JSON.parse(run(npm, ['ls', '--omit=dev', '--json', '--depth=0'], consumer)).dependencies ?? {});
|
|
356
|
+
console.log(`install smoke OK — testguard ${version} runs from the installed tarball; consumer deps: ${deps.join(', ')}`);
|
|
357
|
+
} finally {
|
|
358
|
+
for (const dir of annotationRecovery) rmSync(dir, { recursive: true, force: true });
|
|
359
|
+
rmSync(scratch, { recursive: true, force: true });
|
|
360
|
+
}
|
|
@@ -122,6 +122,9 @@ jobs:
|
|
|
122
122
|
console.log('jest fixture verdicts match expected.json:', ev.records.length);
|
|
123
123
|
"
|
|
124
124
|
|
|
125
|
+
- name: probe the native Node fixture without test dependencies
|
|
126
|
+
run: node .github/scripts/check-node-fixture.mjs
|
|
127
|
+
|
|
125
128
|
# ── The MCP server, driven as a client would: handshake, list, call.
|
|
126
129
|
# A harness-independent surface is only worth having if it actually
|
|
127
130
|
# speaks the protocol from a cold start. ──
|
|
@@ -205,7 +208,7 @@ jobs:
|
|
|
205
208
|
# a survivor here is a failure either way. ──
|
|
206
209
|
- name: Restore previous self-probe evidence (verdict reuse)
|
|
207
210
|
if: matrix.node-version == 22
|
|
208
|
-
uses: actions/cache@
|
|
211
|
+
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
|
209
212
|
with:
|
|
210
213
|
path: .testguard/ci-self-evidence.json
|
|
211
214
|
key: self-evidence-${{ hashFiles('testguard.claims.json') }}-${{ github.sha }}
|
|
@@ -244,6 +247,20 @@ jobs:
|
|
|
244
247
|
path: .testguard/ci-self-evidence.json
|
|
245
248
|
if-no-files-found: warn
|
|
246
249
|
|
|
250
|
+
native-node-floor:
|
|
251
|
+
runs-on: ubuntu-latest
|
|
252
|
+
steps:
|
|
253
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
254
|
+
with:
|
|
255
|
+
fetch-depth: 0
|
|
256
|
+
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
|
257
|
+
with:
|
|
258
|
+
node-version: '20.0.0'
|
|
259
|
+
- name: Install production dependencies only
|
|
260
|
+
run: npm ci --omit=dev
|
|
261
|
+
- name: Verify the native Node floor against the independent oracle
|
|
262
|
+
run: node .github/scripts/check-node-fixture.mjs
|
|
263
|
+
|
|
247
264
|
# ── Python: two engines, one oracle. The stdlib leg runs FIRST and with no
|
|
248
265
|
# pip install of any kind, because "a project with no test dependencies
|
|
249
266
|
# still probes" is a claim about the tool and the only way to keep it true
|