@clear-capabilities/agentic-security-scanner 0.161.0 → 0.162.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/bin/agentic-security.js +11 -2
  3. package/dist/4113.index.js +59 -8
  4. package/dist/5613.index.js +500 -0
  5. package/dist/6239.index.js +469 -0
  6. package/dist/7838.index.js +38 -2
  7. package/dist/8798.index.js +1 -1
  8. package/dist/9207.index.js +5 -0
  9. package/dist/9560.index.js +36 -22
  10. package/dist/{1905.index.js → 9999.index.js} +265 -8
  11. package/dist/agentic-security.mjs +2 -2
  12. package/dist/agentic-security.mjs.sha256 +1 -1
  13. package/package.json +9 -4
  14. package/src/discovery/CLAUDE.md +9 -0
  15. package/src/discovery/index.js +28 -0
  16. package/src/discovery/memory.js +7 -5
  17. package/src/fix/apply-fix-service.js +33 -3
  18. package/src/mcp/CLAUDE.md +9 -1
  19. package/src/mcp/tools.js +17 -4
  20. package/src/posture/CLAUDE.md +174 -0
  21. package/src/posture/assurance/baseline-inventory.js +218 -0
  22. package/src/posture/assurance/baseline.js +322 -0
  23. package/src/posture/assurance/bounded-io.js +145 -0
  24. package/src/posture/assurance/config.js +299 -0
  25. package/src/posture/assurance/contracts.js +315 -0
  26. package/src/posture/assurance/identity.js +62 -0
  27. package/src/posture/assurance/migrations.js +259 -0
  28. package/src/posture/assurance/schema-kit.js +111 -0
  29. package/src/posture/assurance/verification-record.js +258 -0
  30. package/src/posture/attestation.js +5 -0
  31. package/src/posture/autopilot.js +28 -1
  32. package/src/posture/evidence-bundle.js +93 -7
  33. package/src/posture/execution-proof.js +11 -1
  34. package/src/posture/fix-verify-loop.js +20 -6
  35. package/src/posture/fix-verify.js +37 -2
  36. package/src/posture/oracles/adapters.js +360 -0
  37. package/src/posture/oracles/conformance.js +275 -0
  38. package/src/posture/oracles/oracle.js +433 -0
  39. package/src/posture/oracles/registry.js +76 -0
  40. package/src/posture/oracles/scenario-classes.js +223 -0
  41. package/src/posture/replay/replay.js +249 -0
  42. package/src/posture/verification/advisory-state.js +71 -0
  43. package/src/posture/verification/emit.js +257 -0
  44. package/src/posture/verification/hypothesis-promotion.js +211 -0
  45. package/src/posture/verification/patch-negative.js +214 -0
  46. package/src/posture/verification/patch-promotion.js +105 -0
  47. package/src/posture/verification/projection.js +183 -0
  48. package/src/posture/verification/repair-records.js +124 -0
  49. package/src/posture/verifier.js +16 -0
  50. package/src/report/index.js +49 -0
  51. package/src/sandbox/CLAUDE.md +41 -0
  52. package/src/sandbox/backend-namespace.js +7 -0
  53. package/src/sandbox/backend-userspace.js +68 -29
  54. package/src/sandbox/control-probes.js +215 -0
  55. package/src/sandbox/supervise.js +201 -0
  56. package/src/sandbox/trust-boundary.js +113 -0
  57. package/src/sandbox/trust-domains.js +171 -0
@@ -1 +1 @@
1
- faee309bc53830c682a4e967147e6dbc6073b046d2220278b30e64f2e19b1675 agentic-security.mjs
1
+ e8b2c157e5948dbc9edd255a29b5b96f77378e8310baac39fc5f27a171cb2ec8 agentic-security.mjs
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clear-capabilities/agentic-security-scanner",
3
- "version": "0.161.0",
3
+ "version": "0.162.0",
4
4
  "description": "Scanner engine for the agentic-security Claude Code plugin: SAST, SCA with reachability and CISA KEV, secrets, IaC, Haskell and Nix/NixOS, prompt-injection, MCP/agent-tool audit, authZ analysis, SBOM, compliance attestation, and more.",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -67,13 +67,13 @@
67
67
  "test": "node ../scripts/run-unit-tests.mjs",
68
68
  "test:extras": "AGENTIC_SECURITY_CPP_DATAFLOW=1 node --test test/cpp-dataflow.test.js && node --experimental-test-module-mocks --test test/fault-injection.test.js && node --experimental-test-module-mocks --test test/detector-fault-injection.test.js && node --experimental-test-module-mocks --test test/detector-fault-injection-k8s-admission.test.js && node --experimental-test-module-mocks --test test/lineage-fault-injection.test.js && npm run test:python",
69
69
  "loop": "node ../scripts/loop-engineering/run.mjs",
70
- "test:loop": "node --test --test-concurrency=1 ../scripts/loop-engineering/test/loop-manifest.test.js ../scripts/loop-engineering/test/loop-watchdog.test.js ../scripts/loop-engineering/test/loop-background.test.js ../scripts/loop-engineering/test/loop-dashboard.test.js ../scripts/loop-engineering/test/loop-evidence.test.js ../scripts/loop-engineering/test/loop-recovery.test.js ../scripts/loop-engineering/test/loop-remote.test.js ../scripts/loop-engineering/test/loop-runbook.test.js",
70
+ "test:loop": "node --test --test-concurrency=1 ../scripts/loop-engineering/test/loop-manifest.test.js ../scripts/loop-engineering/test/loop-watchdog.test.js ../scripts/loop-engineering/test/loop-background.test.js ../scripts/loop-engineering/test/loop-dashboard.test.js ../scripts/loop-engineering/test/loop-evidence.test.js ../scripts/loop-engineering/test/loop-recovery.test.js ../scripts/loop-engineering/test/loop-remote.test.js ../scripts/loop-engineering/test/loop-runbook.test.js ../scripts/loop-engineering/test/loop-assurance-profile.test.js",
71
71
  "test:python": "node ../scripts/run-python-tests.mjs",
72
72
  "test:smoke": "node --test test/smoke.test.js test/demo-app.test.js",
73
73
  "test:ollama-e2e": "AGENTIC_SECURITY_OLLAMA_E2E=1 node --test test/ollama-e2e.test.js",
74
74
  "test:glob": "node --test test/glob-compat.test.js",
75
75
  "test:sast": "node --test test/cpp-hardcoded-secret.test.js test/sibling-guard.test.js test/comment-blindness.test.js test/crypto-specialist.test.js test/llm.test.js test/llm-cost-advisor.test.js test/llm-owasp.test.js test/logic.test.js test/authz.test.js test/model-load.test.js test/prompt-template.test.js test/business-logic.test.js test/python-sinks.test.js test/phase1-detectors.test.js test/phase2-detectors.test.js test/phase3-v3.test.js test/phase7-extensions.test.js test/phase8-extensions.test.js test/new-cwe-detectors.test.js test/file-upload.test.js test/llmsecops-detectors.test.js test/db-taint.test.js test/dart-swift.test.js test/zip-slip-containment-guard.test.js test/redos-anchoring.test.js test/path-guard-validator-call.test.js test/redos-nfa.test.js test/weak-randomness.test.js test/csharp-pipeline.test.js test/csharp-cross-file.test.js test/post-quantum-crypto.test.js test/web3-advanced.test.js test/cloud-iam-k8s.test.js test/crypto-protocol.test.js test/ml-supply-chain.test.js test/mcp-audit-scan.test.js test/wrong-context-sanitizer.test.js test/sanitizer-context.test.js test/frontend-hygiene.test.js test/csv-injection.test.js test/stored-taint.test.js test/tree-sitter-sinks.test.js test/kotlin-structural.test.js test/ruby-php-structural.test.js test/java-csharp-structural.test.js test/java-structural-cross-file.test.js test/guard-recognition.test.js test/guard-window-identifier-correlation.test.js test/js-python-framework-structural.test.js test/go-structural.test.js test/secret-concat.test.js test/secret-redaction.test.js test/xss-reflected-multilang.test.js test/code-injection-multilang.test.js test/xxe-multilang.test.js test/xpath-injection-multilang.test.js test/gapfill-batch13.test.js test/agent-untrusted-flow.test.js test/api-authz.test.js test/event-entrypoint.test.js test/iac-terraform.test.js test/cross-service.test.js test/rbac-consistency.test.js test/adversarial-robustness.test.js test/secret-entropy.test.js test/comment-strip.test.js test/juliet-path-filter-gate.test.js test/java-bench-shape-opt-in.test.js test/java-ast-folding.test.js test/java-bench-extras.test.js test/reachability-missing-callgraph-data.test.js test/rate-limit.test.js test/auth-signals.test.js test/convention-deviation.test.js test/resource-exhaustion.test.js test/resource-exhaustion-realworld.test.js test/redirect-toctou.test.js test/redirect-toctou-realworld.test.js test/codegen-sink.test.js test/ownership-authz.test.js test/ownership-authz-realworld.test.js test/t21-precision-audit.test.js test/secrets-coverage.test.js test/prompt-injection-payloads.test.js test/iac-cloud-templates.test.js test/ruby-path-join.test.js test/sast/manifest-line-tracking.test.js test/rust-sast.test.js test/java-deserialization-precision.test.js test/pipeline-env-secret.test.js test/mark-used-vuln-functions.test.js",
76
- "test:posture": "node --test test/threat-model.test.js test/git-hardening.test.js test/privacy-framework.test.js test/material-change.test.js test/drift.test.js test/scorecard.test.js test/accuracy-scorecard.test.js test/scorecard-gate.test.js test/predeploy-gate.test.js test/release-check.test.js test/pre-push-gate.test.js test/dependency-currency.test.js test/package-contents-check.test.js test/gate-verdict-cache.test.js test/run-unit-tests.test.js test/temp-leak-guard.test.js test/release-workflow.test.js test/release-workflow-structure.test.js test/independent-population.test.js test/why-missed.test.js test/independent-scoring.test.js test/evidence-bundle.test.js test/obligation-evidence-pack.test.js test/policy-bundle.test.js test/discovery-memory.test.js test/mttr.test.js test/license-policy.test.js test/aibom.test.js test/sbom.test.js test/api-inventory.test.js test/iam-policy.test.js test/container.test.js test/container-runtime.test.js test/image-packages.test.js test/kev.test.js test/dep-confusion.test.js test/sca-deprecated.test.js test/sca-batch.test.js test/composite-risk.test.js test/epss-enrich.test.js test/sca-coverage.test.js test/gradle-deps.test.js test/sca-route-reachable.test.js test/sca-policy.test.js test/sca-verdict.test.js test/install-script.test.js test/sca-linked-findings.test.js test/packs.test.js test/flow-narration.test.js test/regression-test-gen.test.js test/deterministic-fix.test.js test/falsification.test.js test/verification-separation.test.js test/logic-claims.test.js test/attestation.test.js test/verify-attestation-cli.test.js test/comparison.test.js test/determinism-cross-machine.test.js test/fix-honesty-gate.test.js test/model-routing.test.js test/root-cause-sweep.test.js test/entrypoint-inventory.test.js test/relevance.test.js test/untrusted.test.js test/agent-hardening.test.js test/rule-synthesis.test.js test/policy-gate.test.js test/agents-memory.test.js test/cve-lookup.test.js test/cve-alert-daemon.test.js test/fix-verify-loop.test.js test/fix-verify-tests.test.js test/fix-acceptance.test.js test/exploitability-probability.test.js test/history-scan.test.js test/viral-features.test.js test/viral-v074.test.js test/state-dir.test.js test/ignore-pragma.test.js test/license-graph.test.js test/secret-live-check.test.js test/attack-taxonomy.test.js test/triage-memory.test.js test/pr-augment.test.js test/chat-batch2.test.js test/chat-batch3.test.js test/chat-batch4.test.js test/chat-batch5.test.js test/chat-batch6.test.js test/risk-scenario-disclosure.test.js test/egress-policy.test.js test/egress-policy-completeness.test.js test/egress-policy-integration.test.js test/router.test.js test/legacy-alias-redirect.test.js test/cache-economics.test.js test/coverage-report.test.js test/corpus-status.test.js test/provenance.test.js test/secret-history.test.js test/execution-proof.test.js test/fix-metrics.test.js test/corpus-enroll.test.js test/prove-findings.test.js test/corpus-match.test.js test/corpus-provenance.test.js test/learning-quorum.test.js test/llm-cache-integrity.test.js test/proof-artifact.test.js test/providers.test.js test/fleet.test.js test/autopilot.test.js test/autopilot-cli.test.js test/verifier-independence.test.js test/integrity-legacy-key.test.js test/suppression-visibility.test.js test/cost-ceiling.test.js test/local-endpoint.test.js test/ollama-provider.test.js test/ollama-capabilities.test.js test/ollama-offline-egress.test.js test/ollama-fix-proposal.test.js test/ollama-explain-proposal.test.js test/ollama-poc-proposal.test.js test/ollama-model-probe.test.js test/ollama-agent-tools.test.js test/ollama-agent-loop.test.js test/ollama-e2e.test.js test/ollama-oom-feedback.test.js test/ollama-doc-drift.test.js test/model-trust.test.js test/vuln-archaeology.test.js test/scan-checkpoint.test.js test/llm-redact.test.js test/world-class-batch2.test.js test/world-class-modules.test.js test/privacy-ir-adapter.test.js test/privacy-taxonomy.test.js test/privacy-sink-policy.test.js test/privacy-governance.test.js test/privacy-inventory.test.js test/integration-end-to-end.test.js test/streak-cli-wiring.test.js test/stack-playbook.test.js test/machine-output-flag.test.js test/stdout-flush.test.js test/format-determinism.test.js test/only-flag-channels.test.js test/confidence.test.js test/security-trend.test.js test/calibration-drift.test.js test/validator-metrics.test.js test/clustering-ir-taint-line.test.js test/concurrency-cwe.test.js test/compliance-mapping-liveness.test.js test/module-artifact-liveness.test.js test/supply-chain-provenance-completeness.test.js test/compliance-severity-threshold.test.js test/compliance-severity-policy.test.js test/compliance-evidence-binding.test.js test/compliance-evidence-signing.test.js test/compliance-file-contains.test.js test/label-isolation.test.js test/evidence-grade-wording.test.js test/llm-model-status.test.js test/egress-policy-constraints.test.js test/egress-redact.test.js test/egress-audit.test.js test/egress-approved-providers.test.js test/family-registry.test.js test/coverage-strength.test.js test/framework-provenance.test.js test/framework-provenance-controlrefs.test.js test/third-party-verification.test.js test/proof-coverage.test.js test/calibration-holdout.test.js test/independent-population-gate.test.js test/catalog-freshness.test.js test/incremental-parity.test.js test/surface-smoke.test.js test/fix-coverage.test.js test/raw-source-carveout.test.js test/mlbom-conformance.test.js test/deploy-gate-replay.test.js test/sbom-conformance.test.js test/unscored-claims.test.js test/mcp-rug-pull.test.js test/agent-boundary-taint.test.js test/confinement-adversarial.test.js test/comment-strip-cost.test.js test/reachability-claim.test.js test/entrypoint-breadth.test.js test/dep-file-admission.test.js test/custom-rules-freshness.test.js test/posture/provenance-schema.test.js test/posture/provenance-cache.test.js test/helpers/build-git-fixture.test.js test/posture/provenance-git-evidence.test.js test/posture/provenance-dag-walk.test.js test/posture/provenance-confidence.test.js test/posture/provenance-predicate-replay.test.js test/posture/provenance-origin-resolver.test.js test/posture/provenance-ai-authorship.test.js test/posture/provenance-branch-entry.test.js test/posture/provenance-evidence-attribution.test.js test/posture/provenance-lifecycle.test.js test/posture/provenance-missing-control-resolver.test.js test/posture/provenance-missing-control-wiring.test.js test/posture/provenance-coordinator.test.js test/posture/provenance-sca-origin.test.js test/posture/provenance-transitive-sca.test.js test/posture/provenance-coordinator-sca.test.js test/posture/provenance-providers.test.js test/posture/provenance-evidence-bundle.test.js test/posture/provenance-repo-lineage.test.js test/posture/provenance-secrets-logic.test.js test/cli/provenance-flags.test.js test/cli/attest-provenance.test.js test/cli/attest-obligations.test.js test/cli/models.test.js test/cli/setup-llm.test.js test/cli/fix-retry.test.js test/cli/compliance-walkthrough-lineage.test.js test/cli/dataflow-export-privacy.test.js test/cli/dataflow-export-briefing.test.js test/cli/lineage-snapshot-persist.test.js test/cli/dataflow-diff.test.js test/cli/dataflow-watch.test.js test/cli/dataflow-recipients.test.js test/cli/dataflow-coverage.test.js test/security/provenance-safety.test.js test/security/provenance-injection.test.js test/posture/remediation-ledger.test.js",
76
+ "test:posture": "node --test test/threat-model.test.js test/git-hardening.test.js test/privacy-framework.test.js test/material-change.test.js test/drift.test.js test/scorecard.test.js test/accuracy-scorecard.test.js test/scorecard-gate.test.js test/predeploy-gate.test.js test/release-check.test.js test/pre-push-gate.test.js test/dependency-currency.test.js test/package-contents-check.test.js test/gate-verdict-cache.test.js test/run-unit-tests.test.js test/temp-leak-guard.test.js test/release-workflow.test.js test/release-workflow-structure.test.js test/independent-population.test.js test/why-missed.test.js test/independent-scoring.test.js test/evidence-bundle.test.js test/obligation-evidence-pack.test.js test/policy-bundle.test.js test/discovery-memory.test.js test/mttr.test.js test/license-policy.test.js test/aibom.test.js test/sbom.test.js test/api-inventory.test.js test/iam-policy.test.js test/container.test.js test/container-runtime.test.js test/image-packages.test.js test/kev.test.js test/dep-confusion.test.js test/sca-deprecated.test.js test/sca-batch.test.js test/composite-risk.test.js test/epss-enrich.test.js test/sca-coverage.test.js test/gradle-deps.test.js test/sca-route-reachable.test.js test/sca-policy.test.js test/sca-verdict.test.js test/install-script.test.js test/sca-linked-findings.test.js test/packs.test.js test/flow-narration.test.js test/regression-test-gen.test.js test/deterministic-fix.test.js test/falsification.test.js test/verification-separation.test.js test/logic-claims.test.js test/attestation.test.js test/verify-attestation-cli.test.js test/comparison.test.js test/determinism-cross-machine.test.js test/fix-honesty-gate.test.js test/model-routing.test.js test/root-cause-sweep.test.js test/entrypoint-inventory.test.js test/relevance.test.js test/untrusted.test.js test/agent-hardening.test.js test/rule-synthesis.test.js test/policy-gate.test.js test/agents-memory.test.js test/cve-lookup.test.js test/cve-alert-daemon.test.js test/fix-verify-loop.test.js test/fix-verify-tests.test.js test/fix-acceptance.test.js test/exploitability-probability.test.js test/history-scan.test.js test/viral-features.test.js test/viral-v074.test.js test/state-dir.test.js test/ignore-pragma.test.js test/license-graph.test.js test/secret-live-check.test.js test/attack-taxonomy.test.js test/triage-memory.test.js test/pr-augment.test.js test/chat-batch2.test.js test/chat-batch3.test.js test/chat-batch4.test.js test/chat-batch5.test.js test/chat-batch6.test.js test/risk-scenario-disclosure.test.js test/egress-policy.test.js test/egress-policy-completeness.test.js test/egress-policy-integration.test.js test/router.test.js test/legacy-alias-redirect.test.js test/cache-economics.test.js test/coverage-report.test.js test/corpus-status.test.js test/provenance.test.js test/secret-history.test.js test/execution-proof.test.js test/fix-metrics.test.js test/corpus-enroll.test.js test/prove-findings.test.js test/corpus-match.test.js test/corpus-provenance.test.js test/learning-quorum.test.js test/llm-cache-integrity.test.js test/proof-artifact.test.js test/providers.test.js test/fleet.test.js test/autopilot.test.js test/autopilot-cli.test.js test/verifier-independence.test.js test/integrity-legacy-key.test.js test/suppression-visibility.test.js test/cost-ceiling.test.js test/local-endpoint.test.js test/ollama-provider.test.js test/ollama-capabilities.test.js test/ollama-offline-egress.test.js test/ollama-fix-proposal.test.js test/ollama-explain-proposal.test.js test/ollama-poc-proposal.test.js test/ollama-model-probe.test.js test/ollama-agent-tools.test.js test/ollama-agent-loop.test.js test/ollama-e2e.test.js test/ollama-oom-feedback.test.js test/ollama-doc-drift.test.js test/model-trust.test.js test/vuln-archaeology.test.js test/scan-checkpoint.test.js test/llm-redact.test.js test/world-class-batch2.test.js test/world-class-modules.test.js test/privacy-ir-adapter.test.js test/privacy-taxonomy.test.js test/privacy-sink-policy.test.js test/privacy-governance.test.js test/privacy-inventory.test.js test/integration-end-to-end.test.js test/streak-cli-wiring.test.js test/stack-playbook.test.js test/machine-output-flag.test.js test/stdout-flush.test.js test/format-determinism.test.js test/only-flag-channels.test.js test/confidence.test.js test/security-trend.test.js test/calibration-drift.test.js test/validator-metrics.test.js test/clustering-ir-taint-line.test.js test/concurrency-cwe.test.js test/compliance-mapping-liveness.test.js test/module-artifact-liveness.test.js test/supply-chain-provenance-completeness.test.js test/compliance-severity-threshold.test.js test/compliance-severity-policy.test.js test/compliance-evidence-binding.test.js test/compliance-evidence-signing.test.js test/compliance-file-contains.test.js test/label-isolation.test.js test/evidence-grade-wording.test.js test/llm-model-status.test.js test/egress-policy-constraints.test.js test/egress-redact.test.js test/egress-audit.test.js test/egress-approved-providers.test.js test/family-registry.test.js test/coverage-strength.test.js test/framework-provenance.test.js test/framework-provenance-controlrefs.test.js test/third-party-verification.test.js test/proof-coverage.test.js test/calibration-holdout.test.js test/independent-population-gate.test.js test/catalog-freshness.test.js test/incremental-parity.test.js test/surface-smoke.test.js test/fix-coverage.test.js test/raw-source-carveout.test.js test/mlbom-conformance.test.js test/deploy-gate-replay.test.js test/sbom-conformance.test.js test/unscored-claims.test.js test/mcp-rug-pull.test.js test/agent-boundary-taint.test.js test/confinement-adversarial.test.js test/comment-strip-cost.test.js test/reachability-claim.test.js test/entrypoint-breadth.test.js test/dep-file-admission.test.js test/custom-rules-freshness.test.js test/posture/provenance-schema.test.js test/posture/provenance-cache.test.js test/helpers/build-git-fixture.test.js test/posture/provenance-git-evidence.test.js test/posture/provenance-dag-walk.test.js test/posture/provenance-confidence.test.js test/posture/provenance-predicate-replay.test.js test/posture/provenance-origin-resolver.test.js test/posture/provenance-ai-authorship.test.js test/posture/provenance-branch-entry.test.js test/posture/provenance-evidence-attribution.test.js test/posture/provenance-lifecycle.test.js test/posture/provenance-missing-control-resolver.test.js test/posture/provenance-missing-control-wiring.test.js test/posture/provenance-coordinator.test.js test/posture/provenance-sca-origin.test.js test/posture/provenance-transitive-sca.test.js test/posture/provenance-coordinator-sca.test.js test/posture/provenance-providers.test.js test/posture/provenance-evidence-bundle.test.js test/posture/provenance-repo-lineage.test.js test/posture/provenance-secrets-logic.test.js test/cli/provenance-flags.test.js test/cli/attest-provenance.test.js test/cli/attest-obligations.test.js test/cli/models.test.js test/cli/setup-llm.test.js test/cli/fix-retry.test.js test/cli/compliance-walkthrough-lineage.test.js test/cli/dataflow-export-privacy.test.js test/cli/dataflow-export-briefing.test.js test/cli/lineage-snapshot-persist.test.js test/cli/dataflow-diff.test.js test/cli/dataflow-watch.test.js test/cli/dataflow-recipients.test.js test/cli/dataflow-coverage.test.js test/security/provenance-safety.test.js test/security/provenance-injection.test.js test/posture/remediation-ledger.test.js test/posture/assurance-baseline.test.js test/posture/assurance-contracts.test.js test/posture/assurance-config.test.js test/evidence-issuer.test.js",
77
77
  "test:dataflow": "node --test test/privacy-catalog.test.js test/privacy-deep-walker.test.js test/privacy-deep-e2e.test.js test/container-taint.test.js test/stored-taint-families.test.js test/py-annotation-sources.test.js test/fn-reach.test.js test/deep-taint.test.js test/calibration.test.js test/holdout-eval.test.js test/cross-lang-meta.test.js test/cross-lang-queues.test.js test/phase5-xlang.test.js test/phase5-coverage.test.js test/phase6-taint.test.js test/llm-validator-consistency.test.js test/llm-validator-default-on.test.js test/llm-validator-preset.test.js test/parser-py-cst.test.js test/chrome-probe.test.js test/parser-cs-kt.test.js test/parser-cs-annotations.test.js test/parser-cs-control-flow.test.js test/parser-kt-control-flow.test.js test/parser-js-annotations.test.js test/parser-java-annotations.test.js test/parser-java-control-flow.test.js test/parser-java-calls.test.js test/parser-go.test.js test/parser-rust.test.js test/rust-taint-flow.test.js test/parser-php-rb.test.js test/parser-php-control-flow.test.js test/interproc-k2.test.js test/proven-clean.test.js test/backward-default.test.js test/incremental-cache.test.js test/string-regex-lattice.test.js test/closure-capture.test.js test/points-to.test.js test/type-stubs.test.js test/soft-taint.test.js test/ifds.test.js test/symbolic-exec-proof.test.js test/ifds-summary-edges.test.js test/stub-aware-filter.test.js test/cross-repo.test.js test/proof-gate.test.js test/layer-recall.test.js test/java-taint-flow.test.js test/kt-taint-flow.test.js test/sanitizer-typed-flow.test.js test/sanitizer-gate-unit.test.js test/proof-safe.test.js test/collection-taint.test.js test/kcfa-context.test.js test/kcfa-callstring.test.js test/flow-parity.test.js test/callgraph-resolve.test.js test/import-reachability.test.js test/ir-stats.test.js test/parser-cpp.test.js test/parser-js-decorators.test.js test/parser-js-if-else-cfg.test.js test/cpp-integration.test.js test/engine-reconnect.test.js test/phase2-scoping.test.js test/engine-recall.test.js test/catalog-annotation-source.test.js test/catalog-cs-p1.test.js test/catalog-expanded.test.js test/builtin-summaries.test.js test/interproc-mutation-crash.test.js test/mutated-param-taint.test.js test/return-sink-check.test.js test/higher-order-inline-callback.test.js test/interproc-findings-discard.test.js test/runscan-deep-option.test.js test/summary-cache-convergence.test.js test/parser-rb-calls.test.js test/parser-rb-module-level.test.js test/balanced-call.test.js test/destructuring-taint.test.js test/computed-member-taint.test.js test/string-interpolation-taint.test.js test/dataflow-deadcode-severity.test.js test/catalog-dotted-callee-lookup.test.js test/builtin-summary-family-blind-kill.test.js test/deep-mode-annotator-pipeline.test.js test/receiver-type-and-nested-calls.test.js test/member-write-and-loop-taint.test.js test/parser-py-module-level.test.js test/parser-php-module-level.test.js test/r14b-module-level-e2e.test.js test/annotation-taint-engine.test.js test/interproc-nested-call-taint.test.js test/catalog-xpath-injection.test.js test/catalog-ldap-injection.test.js test/catalog-xxe.test.js test/catalog-response-splitting.test.js test/catalog-code-injection.test.js test/parser-rb-control-flow.test.js test/catalog-ruby-p1.test.js test/callee-receiver-taint.test.js test/catalog-command-injection-p4.test.js test/catalog-xss-p4.test.js test/catalog-path-ssrf-p3.test.js test/catalog-ai-model-provider-precision.test.js test/catalog-orm-write.test.js test/dataflow/provenance-pipeline-integration.test.js test/dataflow-progress.test.js test/scan-progress.test.js test/sard-leakage-pipeline.test.js test/sard-split-scoring.test.js test/sard-llm-isolation.test.js test/sard-mutation-adversarial.test.js test/sard-baseline-adaptive-tolerance.test.js test/sard-independent-holdout.test.js test/sard-flow-aware-scoring.test.js test/sard-macro-score-support-floor.test.js test/sard-php-split.test.js test/parser-java-nary-concat.test.js test/sard-naive-baseline.test.js test/field-taint-cross-method.test.js test/field-taint-engine-unit.test.js test/coercion-sanitizer-kill.test.js test/constant-propagation-path-sensitivity.test.js test/parser-java-assignments.test.js test/catalog-java-sard.test.js test/parser-cs-juliet-shapes.test.js test/catalog-cs-sard-p2.test.js test/sard-cwe-key-merge.test.js test/parser-php-backtick.test.js test/parser-php-include.test.js test/php-include-merge.test.js test/parser-php-array-field-taint.test.js test/parser-php-cond-assign.test.js test/parser-rust-redos.test.js test/multi-sink-taint-chain.test.js test/static-field-cross-class-call-string.test.js test/regex-guard-narrowing.test.js test/object-instance-field-taint.test.js test/param-type-inference.test.js test/smt-path-feasibility.test.js test/cross-service-taint.test.js test/dataflow-expr-taint-scaling.test.js",
78
78
  "test:mcp": "node --test test/mcp.test.js test/mcp-apply-fix-multifile.test.js test/mcp-protocol-smoke.test.js test/mcp-audit.test.js test/audit-cli.test.js test/mcp-scratchpad.test.js test/mcp-offload.test.js test/sca-upgrade.test.js test/lsp-server.test.js test/mcp-dataflow-tools.test.js test/cli/governance-propose-edit.test.js test/cli/remediation-open-update.test.js test/cli/remediation-verify-reopen.test.js test/cli/dataflow-observations.test.js test/cli/federate-declare-list.test.js",
79
79
  "test:report": "node --test test/report-scan-invariants.test.js test/junit.test.js test/ci.test.js test/poc-generator.test.js test/verifier.test.js test/verifier-target.test.js test/annotator-errors.test.js test/scan-health.test.js test/finding-schema.test.js test/pipeline/finding-schema-provenance.test.js test/evidence-provenance.test.js test/report/provenance-output.test.js test/report/provenance-format-parity.test.js test/legacy-compat.test.js test/annotator-runner.test.js test/detector-runner.test.js test/detector-errors.test.js test/analyzer-supervisor.test.js test/cascade-worker-pool.test.js test/cascade-pool-wiring.test.js test/coverage-ledger.test.js test/coverage-ledger-completeness.test.js test/assurance-mode.test.js test/artifact-registry.test.js test/artifact-registry-completeness.test.js test/retention-policy.test.js test/state-lifecycle-report.test.js test/legal-hold.test.js test/production-feedback.test.js test/calibration-feedback.test.js test/encryption-provider.test.js test/grader-calibration.test.js test/pr-delta-gate.test.js test/vex.test.js test/report-render.test.js test/oscal-conformance.test.js test/cli-fix-apply.test.js test/apply-fix-service.test.js test/high-impact-approval-gate.test.js test/approver-registry.test.js test/suppression-exceptions.test.js test/fix-history.test.js test/producer-registry.test.js test/producer-collector.test.js test/enrichment-completion.test.js",
@@ -88,7 +88,11 @@
88
88
  "bench:language-support:check": "node ../bench/language-support/check.mjs",
89
89
  "bench:live-feed": "node ../bench/live-feed/run.mjs",
90
90
  "bench:language-support:perf": "node ../bench/language-support/perf.mjs",
91
- "test:lifecycle": "node --test test/ci-hang-protection.test.js test/ci-test-shards.test.js test/pre-bash-guard.test.js test/lsp-protocol-smoke.test.js test/ide-surfaces.test.js test/detector-liveness.test.js test/bench-watchdog.test.js test/ci-parity.test.js test/dead-code.test.js test/no-dead-modules.test.js test/no-orphan-scripts.test.js test/no-stray-state.test.js test/tree-integrity.test.js test/stop-hook.test.js test/plugin-self-check.test.js test/skills-registry.test.js test/bodyguard.test.js test/cache-invalidator-guard.test.js test/dispatch-pre-tool.test.js test/dispatch-user-prompt.test.js test/session-start-model-capture.test.js test/sandbox.test.js test/sandbox-escape.test.js test/ci-templates.test.js test/agent-mcp-tools.test.js test/triage-command.test.js test/no-orphan-tests.test.js test/supply-command.test.js test/posture-command.test.js test/labs-command.test.js test/compliance-command.test.js test/check-doc-drift.test.js test/dist-chunks-tracked.test.js && node ../scripts/lint-command-descriptions.mjs test/workflow-confinement.test.js test/check-bundle-source.test.js test/ship.test.js",
91
+ "test:verification": "node --test test/verification/verification-record.test.js test/verification/oracle-adapters.test.js test/verification/replay-manifest.test.js test/verification/patch-negative.test.js test/verification/non-taint-classes.test.js test/verification/interface-equivalence.test.js test/verification/advisory-gating.test.js test/verification/oracle-conformance.test.js",
92
+ "verification:conformance:check": "node ../scripts/verification-conformance-check.mjs",
93
+ "verification:conformance:static": "node ../scripts/verification-conformance-check.mjs --static",
94
+ "verification:replay-example": "node ../scripts/verification-replay-example.mjs",
95
+ "test:lifecycle": "node --test test/ci-hang-protection.test.js test/ci-test-shards.test.js test/pre-bash-guard.test.js test/lsp-protocol-smoke.test.js test/ide-surfaces.test.js test/detector-liveness.test.js test/bench-watchdog.test.js test/ci-parity.test.js test/dead-code.test.js test/no-dead-modules.test.js test/no-orphan-scripts.test.js test/no-stray-state.test.js test/tree-integrity.test.js test/stop-hook.test.js test/plugin-self-check.test.js test/skills-registry.test.js test/bodyguard.test.js test/cache-invalidator-guard.test.js test/dispatch-pre-tool.test.js test/dispatch-user-prompt.test.js test/session-start-model-capture.test.js test/sandbox.test.js test/sandbox-escape.test.js test/trust-boundary.test.js test/ci-templates.test.js test/agent-mcp-tools.test.js test/triage-command.test.js test/no-orphan-tests.test.js test/supply-command.test.js test/posture-command.test.js test/labs-command.test.js test/compliance-command.test.js test/check-doc-drift.test.js test/dist-chunks-tracked.test.js && node ../scripts/lint-command-descriptions.mjs test/workflow-confinement.test.js test/check-bundle-source.test.js test/ship.test.js",
92
96
  "test:eval": "node --test test/independent-eval.test.js test/realworld-recall.test.js",
93
97
  "test:discovery": "node --test test/discovery-partition.test.js test/discovery-lenses.test.js test/discovery-hunter.test.js test/discovery-confirm.test.js test/discovery-disprove.test.js test/discovery-judge.test.js test/discovery-run.test.js test/discovery-wiring.test.js test/discovery-llm-invoke.test.js test/local-only-mode.test.js",
94
98
  "eval:independent": "node ../bench/independent-eval/runner.mjs",
@@ -133,6 +137,7 @@
133
137
  "bench:self-scan:check": "node ../bench/self-scan/check.mjs",
134
138
  "bench:nist-800-171-labels:check": "node ../bench/nist-800-171-labels/check.mjs",
135
139
  "bench:mutation:check": "node ../bench/mutation/runner.mjs",
140
+ "baseline:capture": "node ../scripts/baseline-capture.mjs",
136
141
  "bench:protection-verdict:check": "node ../bench/protection-verdict/runner.mjs",
137
142
  "bench:fix-correctness": "node ../bench/fix-correctness/runner.mjs",
138
143
  "bench:iac-coverage": "node ../bench/iac-coverage/runner.mjs",
@@ -52,3 +52,12 @@ panel · `judge.js` shapes findings and dedupes against the prior scan ·
52
52
  `partition.js` `partitionNixFiles` groups `.nix` files by their import structure (a configuration and the modules it imports are
53
53
  one area), merged areas are capped, and hunter prompts for Haskell and Nix are redacted (split literals joined first) before they
54
54
  leave the process. `hunt` includes `.hs`, `.lhs` and `.nix`. With no endpoint every run is degraded and says so.
55
+
56
+ ## Advisory/gating separation (X-207)
57
+
58
+ Hunt output stays advisory in code, not just by convention. `memory.js` writes only through
59
+ `posture/verification/advisory-state.js` (`writeAdvisoryState`: one allowlisted file, atomic rename, never through a link), and
60
+ `report/index.js` excludes every hunt hypothesis from findings and exit codes. A hypothesis crosses into a finding only through
61
+ `promoteHypothesis` (`posture/verification/hypothesis-promotion.js`), reached from `runDiscovery` only with
62
+ `opts.promote = { policy, verify }`: a verifier receipt the promotion code verifies, an explicit policy, and an audit record. The
63
+ panel's votes, a confidence score and the confirmation tier are never inputs to that decision.
@@ -14,6 +14,8 @@ import { confirmAll } from './confirm.js';
14
14
  import { disprovePanel } from './disprove.js';
15
15
  import { judgeCandidates } from './judge.js';
16
16
  import { loadMemory, saveMemory, rememberRun, previouslyRefuted, nextWavePlan } from './memory.js';
17
+ import { emitVerification, headCommit } from '../posture/verification/emit.js';
18
+ import { promoteHypothesis } from '../posture/verification/hypothesis-promotion.js';
17
19
 
18
20
  // Bridge a candidate to the deterministic layer. A taint finding at or within
19
21
  // two lines of the candidate corroborates it; a modelled sink on the line
@@ -283,11 +285,37 @@ export async function runDiscovery(ctx = {}, opts = {}) {
283
285
  }));
284
286
  }
285
287
 
288
+ // X-201: one version-1 verification record per fresh hypothesis, ADDITIVE and advisory (hunt output never enters
289
+ // last-scan.json). A taint-corroborated candidate is `inconclusive` (a static probe is corroboration, not
290
+ // execution) and an uncorroborated one is `not-run`; a model refutation panel is recorded as inference only.
291
+ const hypothesisCommit = headCommit(opts.scanRoot);
292
+ const verificationRecords = [];
293
+ for (const f of fresh) {
294
+ const e = emitVerification('hunt', f, { finding: f, commit: hypothesisCommit });
295
+ if (e.ok) verificationRecords.push(e.record);
296
+ }
297
+
298
+ // X-207: opt-in promotion. A hypothesis crosses into a finding ONLY through `promoteHypothesis` (trusted verifier receipt,
299
+ // explicit policy, audit record). `fresh` is unchanged; promoted findings and every decision's audit record are reported
300
+ // separately, and nothing here writes `last-scan.json`. `opts.promote = { policy, verify(hypothesis) -> runOracle result }`.
301
+ let promotions = null;
302
+ if (opts.promote && typeof opts.promote.verify === 'function') {
303
+ promotions = [];
304
+ for (const f of fresh) {
305
+ let result = null;
306
+ try { result = await opts.promote.verify(f); } catch { result = null; }
307
+ const p = promoteHypothesis({ hypothesis: f, result, policy: opts.promote.policy });
308
+ promotions.push({ hypothesisId: p.audit.sourceHypothesis.id, decision: p.decision, audit: p.audit, finding: p.finding });
309
+ }
310
+ }
311
+
286
312
  return {
287
313
  schema: 'agentic-security/discovery@1',
288
314
  focusAreas: areas.map(a => ({ id: a.id, label: a.label, files: a.files.length, size: a.size })),
289
315
  runs,
290
316
  fresh,
317
+ verificationRecords,
318
+ ...(promotions ? { promotions } : {}),
291
319
  duplicates,
292
320
  suppressed,
293
321
  // `refutedCandidates` holds RAW candidates straight from `disprovePanel`,
@@ -40,6 +40,7 @@ import * as fs from 'node:fs';
40
40
  import * as path from 'node:path';
41
41
 
42
42
  import { stateWritesEnabled } from '../posture/state-dir.js';
43
+ import { writeAdvisoryState } from '../posture/verification/advisory-state.js';
43
44
  const MEMORY_SCHEMA = 'agentic-security/discovery-memory@1';
44
45
  export const MEMORY_FILE = path.join('.agentic-security', 'discovery-memory.json');
45
46
 
@@ -73,14 +74,15 @@ export function loadMemory(scanRoot) {
73
74
  }
74
75
  }
75
76
 
76
- /** Persist. Failure is non-fatal — the run still produced its report. */
77
+ /**
78
+ * Persist. Failure is non-fatal -- the run still produced its report. X-207: the write goes through the advisory-state writer
79
+ * (closed file-name allowlist, atomic rename, never through a link), so hunt output cannot reach `last-scan.json` or its
80
+ * signature even when a hostile tree planted a link at the memory file.
81
+ */
77
82
  export function saveMemory(scanRoot, memory) {
78
83
  try {
79
- const p = path.join(scanRoot, MEMORY_FILE);
80
84
  if (!stateWritesEnabled()) return;
81
- fs.mkdirSync(path.dirname(p), { recursive: true });
82
- fs.writeFileSync(p, JSON.stringify(memory, null, 2) + '\n');
83
- return true;
85
+ return writeAdvisoryState(scanRoot, path.basename(MEMORY_FILE), JSON.stringify(memory, null, 2) + '\n').ok;
84
86
  } catch {
85
87
  return false;
86
88
  }
@@ -42,6 +42,7 @@ import { verifyLastScan } from '../posture/integrity.js';
42
42
  import { stateDir } from '../posture/state-dir.js';
43
43
  import { applyFix as applyFixHistory, revertEntryById as revertFixEntry } from '../posture/fix-history.js';
44
44
  import { classifyFixMaterialRisk } from '../posture/material-change.js';
45
+ import { appendRepairRecord } from '../posture/verification/repair-records.js';
45
46
  import { loadApproverRegistry, verifyApprover, requiredRolesFor, checkSeparationOfDuties } from './approver-registry.js';
46
47
 
47
48
  const RESERVED_WRITE_PREFIXES = [
@@ -191,6 +192,12 @@ async function getVerifyFixCore() {
191
192
  * .agentic-security/authorized-approvers.json is configured, `approvedBy` is ALSO checked
192
193
  * against it (fix/approver-registry.js) — an approvedBy the registry doesn't recognize, or one
193
194
  * missing a role a touched category requires, is refused. A no-op with no registry configured.
195
+ * @param {object} [opts.patchNegative] - X-204: `{ original: {files, entry, oracleId, inputs}, functional: {inputs}, commit?, ... }`,
196
+ * the exploit scenario and functional cases the patch must satisfy. Honoured only when the `patch-negative-verification`
197
+ * feature (and `verification-oracles`) is enabled; otherwise ignored and this function behaves exactly as before. When it
198
+ * runs, a patch is written only if it is `verified-fix`, and the result carries `patchNegative` plus `repairRecords`: SEPARATE
199
+ * records for the proposal, its promotion or rejection, its application and any rollback.
200
+ * @param {object} [opts.assuranceConfig] - resolved assurance config (defaults to the process environment)
194
201
  * @param {boolean} [opts.dryRun]
195
202
  * @param {boolean} [opts.skipVerification] - escape hatch for callers that have ALREADY verified
196
203
  * (none currently use this — present so a future caller cannot be forced into double verification
@@ -202,7 +209,7 @@ async function getVerifyFixCore() {
202
209
  * configured, tests when a runner is detected — genuinely ran and passed, not silently skipped.
203
210
  * `verified:true, verifiedFull:false` is a real, honest state: applied, but on a degraded pass.
204
211
  */
205
- export async function applyVerifiedFix({ scanRoot, finding, files, fixMeta = null, dryRun = false, skipVerification = false } = {}) {
212
+ export async function applyVerifiedFix({ scanRoot, finding, files, fixMeta = null, dryRun = false, skipVerification = false, patchNegative = null, assuranceConfig } = {}) {
206
213
  if (!scanRoot) return { ok: false, applied: false, reason: 'scanRoot required' };
207
214
  if (!finding) return { ok: false, applied: false, reason: 'finding required' };
208
215
  if (!files || typeof files !== 'object' || !Object.keys(files).length) {
@@ -316,6 +323,9 @@ export async function applyVerifiedFix({ scanRoot, finding, files, fixMeta = nul
316
323
  }
317
324
 
318
325
  let verify = null;
326
+ let pn = null;
327
+ let repairRecords = [];
328
+ const pnView = (r) => ({ status: r.status, verifiedFix: r.verifiedFix, incompleteStep: r.incompleteStep, failureCode: r.failureCode, reason: r.reason, steps: r.steps, patchDigest: r.patchDigest, summary: r.summary, verificationRecord: r.verificationRecord });
319
329
  if (!skipVerification) {
320
330
  if (!finding.stableId) {
321
331
  return { ok: false, applied: false, reason: 'finding has no stableId — cannot verify a candidate fix against it' };
@@ -328,22 +338,31 @@ export async function applyVerifiedFix({ scanRoot, finding, files, fixMeta = nul
328
338
  originalFindingStableId: finding.stableId,
329
339
  files: Object.fromEntries(Object.entries(confined).map(([rel, v]) => [rel, v.content])),
330
340
  fixMeta,
341
+ ...(patchNegative ? { patchNegative, assuranceConfig } : {}),
331
342
  });
332
343
  } catch (e) {
333
344
  return { ok: false, applied: false, reason: `verification failed: ${e.message}` };
334
345
  }
335
346
  verify = verdict;
347
+ // X-204: present only when the caller asked and the feature is on. `pn` stays null on every legacy path.
348
+ const pnRun = verdict.patchNegative && verdict.patchNegative.status !== 'disabled' ? verdict.patchNegative : null;
349
+ if (pnRun) { pn = pnRun; repairRecords = pnRun.repairRecords || []; }
336
350
  if (!verdict.ok) {
351
+ // the patch-negative leg may have passed while another leg did not: the rejection is still its own record
352
+ if (pn && pn.verifiedFix) {
353
+ repairRecords = appendRepairRecord(repairRecords, { kind: 'rejected', hypothesisId: finding.stableId, revision: pn.promotion?.revision ?? null, diffDigest: pn.patchDigest, step: 'other-verification-legs', reason: `rejected by verifier: ${String(verdict.summary || 'did not verify').slice(0, 200)}` }).records;
354
+ }
337
355
  return {
338
356
  ok: false, applied: false,
339
357
  reason: `rejected by verifier: ${verdict.summary || verdict.rescan?.reason || 'did not verify'}`,
340
358
  verify: { rescan: verdict.rescan, lint: verdict.lint ? { runner: verdict.lint.runner, ok: verdict.lint.ok } : null, honesty: verdict.honesty || null },
359
+ ...(pn ? { patchNegative: pnView(pn), repairRecords } : {}),
341
360
  };
342
361
  }
343
362
  }
344
363
 
345
364
  if (dryRun) {
346
- return { ok: true, applied: false, dryRun: true, verified: !skipVerification, files: Object.keys(confined), verify, materialClassification };
365
+ return { ok: true, applied: false, dryRun: true, verified: !skipVerification, files: Object.keys(confined), verify, materialClassification, ...(pn ? { patchNegative: pnView(pn), repairRecords } : {}) };
347
366
  }
348
367
 
349
368
  // FR-304: re-check every file against its pre-verification baseline hash,
@@ -381,11 +400,21 @@ export async function applyVerifiedFix({ scanRoot, finding, files, fixMeta = nul
381
400
  for (const w of written) {
382
401
  try { await revertFixEntry(scanRoot, w.historyId); } catch { /* best-effort; original error still propagates below */ }
383
402
  }
403
+ // X-204: the rollback is its own record, after the promotion it undoes (a write that failed part-way)
404
+ if (pn) {
405
+ const rolled = appendRepairRecord(repairRecords, { kind: 'rolled-back', hypothesisId: finding.stableId, revision: pn.promotion?.revision ?? null, diffDigest: pn.patchDigest, reason: `the write failed and every file written in this batch was restored: ${String(e?.message || e).slice(0, 160)}` });
406
+ repairRecords = rolled.records;
407
+ }
384
408
  if (e && e.name === 'FixAttemptBudgetExceededError') {
385
- return { ok: false, applied: false, reason: `budget-exceeded: ${e.message}`, budgetExceeded: true, attempts: e.attempts, maxAttempts: e.max, key: e.key };
409
+ return { ok: false, applied: false, reason: `budget-exceeded: ${e.message}`, budgetExceeded: true, attempts: e.attempts, maxAttempts: e.max, key: e.key, ...(pn ? { patchNegative: pnView(pn), repairRecords } : {}) };
386
410
  }
411
+ if (pn && e && typeof e === 'object') e.repairRecords = repairRecords;
387
412
  throw e;
388
413
  }
414
+ // X-204: application is a separate record from the promotion that allowed it
415
+ if (pn) {
416
+ repairRecords = appendRepairRecord(repairRecords, { kind: 'applied', hypothesisId: finding.stableId, revision: pn.promotion?.revision ?? null, diffDigest: pn.patchDigest, reason: `written to ${written.length} file(s) after verified-fix promotion` }).records;
417
+ }
389
418
 
390
419
  // FR-305: `verified: true` only ever meant "verification was attempted",
391
420
  // not "every required leg genuinely ran and passed" — a caller that
@@ -400,5 +429,6 @@ export async function applyVerifiedFix({ scanRoot, finding, files, fixMeta = nul
400
429
  written,
401
430
  verify: verify ? { summary: verify.summary, verifiedFull: verify.verifiedFull, degradedLegs: verify.degradedLegs || [] } : null,
402
431
  materialClassification,
432
+ ...(pn ? { patchNegative: pnView(pn), repairRecords } : {}),
403
433
  };
404
434
  }
package/src/mcp/CLAUDE.md CHANGED
@@ -12,7 +12,7 @@ MCP server. JSON-RPC 2.0 over NDJSON on stdin/stdout. Bin entry `../../bin/agent
12
12
  | `find_rule_module` | ✓ | reads `scanner/src/{sast,posture}/` to answer "which file detects CWE-X / family Y" |
13
13
  | `lookup_cve` | ✓ | reads local OSV / KEV / EPSS cache; staleness-tiered |
14
14
  | `synthesize_fix` | ✓ | reads last-scan; returns the patch text |
15
- | `verify_fix` | ✗ | re-scans patched files in memory, runs lint + the project test suite + the fix-honesty gate + PoC re-check; does not touch the target project's own files, but appends a record to `.agentic-security/fix-metrics.jsonl` per attempt |
15
+ | `verify_fix` | ✗ | re-scans patched files in memory, runs lint + the project test suite + the fix-honesty gate + PoC re-check; does not touch the target project's own files, but appends a record to `.agentic-security/fix-metrics.jsonl` per attempt. Also returns `verificationRecord`, the shared version-1 verification record (X-201; additive, `apply_fix` returns it too): a static re-scan alone is `not-run`, never confirmed |
16
16
  | `apply_fix` | ✗ | writes via `posture/fix-history.js` (with backup). With `plan_digest` it applies a MULTI-FILE NixOS option fix (see "Multi-file apply_fix" below): all files or none, one backup per file, one history group |
17
17
  | `append_scratchpad` | ✗ | writes under `.agentic-security/agent-scratchpad/<agent>/<session>/` only |
18
18
  | `read_scratchpad` | ✓ | paginated read of scratchpad files |
@@ -103,3 +103,11 @@ the relock path is not a finding fix), no fix-attempt-budget enforcement (the la
103
103
  CLI, does not enforce a cap), and no `fixMeta` honesty gate (that gate judges a caller's residual/verdict claims, which this
104
104
  path does not take). The `preWrite` digest re-check is defence in depth over a deterministic planner and has no independent
105
105
  test that fails when it is removed (the earlier digest comparison is the pinned control).
106
+
107
+ ## Verification view (X-206)
108
+
109
+ `explain_finding` (when the finding in `last-scan.json` carries a `verificationRecord`), `verify_fix` and `apply_fix` add
110
+ `verificationView`, the shared projection from `posture/verification/projection.js`, beside the existing `verificationRecord`.
111
+ It is the same object the JSON report, the text reports and the autopilot response carry for the same finding. Additive: a finding
112
+ without a record returns exactly what it did. The key is `verificationView`, not `verification` (that name belongs to the
113
+ producer/verifier separation record). See `docs/guides/verification-schema-migration.md`.
package/src/mcp/tools.js CHANGED
@@ -26,6 +26,8 @@ import { withStateWritesDisabled } from '../posture/state-dir.js';
26
26
  import { analyzeTranscript, formatCacheReport, renderCacheStatusLine } from '../posture/cache-economics.js';
27
27
  import { redactString, redactFinding } from './redact.js';
28
28
  import { _remediationOf, normalizeFindings } from '../report/index.js';
29
+ // X-206: the one projection of the verification record, shared with the JSON report, the text report and the autopilot response.
30
+ import { verificationFields } from '../posture/verification/projection.js';
29
31
  // Git-origin provenance (Finding Provenance M0/M1). Distinct from
30
32
  // `finding.provenance` (AI-authorship) and from an SCA entry's `provenance`
31
33
  // (Sigstore/SLSA attestation) — see report/index.js's import comment.
@@ -599,6 +601,10 @@ export const explain_finding = {
599
601
  findingProvenance: f.findingProvenance ? redactFindingProvenance(f.findingProvenance, {
600
602
  pseudonymize: process.env.AGENTIC_SECURITY_PSEUDONYMIZE_AUTHORS === '1',
601
603
  }) : null,
604
+ // X-206: the same verification projection the JSON and text reports carry; absent when the finding has no record.
605
+ ...(f.verificationRecord && typeof f.verificationRecord === 'object'
606
+ ? { verificationRecord: f.verificationRecord, ...verificationFields(f.verificationRecord, { replay: f.verificationReplay }) }
607
+ : {}),
602
608
  };
603
609
  },
604
610
  };
@@ -875,7 +881,7 @@ export const apply_fix = {
875
881
  // default. Normalized to the scan+lint verdict shape used below.
876
882
  const { verifyFixWithTests } = await import('../posture/fix-verify-loop.js');
877
883
  const t = await verifyFixWithTests({ scanRoot: ctx.sessionRoot, originalFindingStableId: f.stableId, files: _files });
878
- verdict = { ok: t.ok, summary: t.summary, rescan: t.legs?.scan?.detail, lint: t.legs?.lint?.detail, tests: t.legs?.tests, testVerdict: t.verdict };
884
+ verdict = { ok: t.ok, summary: t.summary, rescan: t.legs?.scan?.detail, lint: t.legs?.lint?.detail, tests: t.legs?.tests, testVerdict: t.verdict, verificationRecord: t.verificationRecord ?? null };
879
885
  } else {
880
886
  const verifyFixCore = await getVerifyFixCore();
881
887
  verdict = await verifyFixCore({
@@ -893,6 +899,9 @@ export const apply_fix = {
893
899
  _meta: META, applied: false,
894
900
  reason: `patch rejected by verifier: ${verdict.summary || verdict.rescan?.reason || 'did not verify'}`,
895
901
  verify: { rescan: verdict.rescan, lint: { runner: verdict.lint?.runner, ok: verdict.lint?.ok }, honesty: verdict.honesty || null },
902
+ // X-201: the same version-1 verification record every surface emits (additive; null when none could be formed).
903
+ verificationRecord: verdict.verificationRecord ?? null,
904
+ ...verificationFields(verdict.verificationRecord),
896
905
  };
897
906
  }
898
907
  // FR-307/FR-1002/D-0024: this caller-supplied-patch branch writes via
@@ -913,7 +922,7 @@ export const apply_fix = {
913
922
  }
914
923
  const materialClassification = classifyFixMaterialRisk(filesForMaterialClassification);
915
924
  if (dry_run) {
916
- return { _meta: META, applied: false, dryRun: true, verified: true, files: Object.keys(confinedAbs), summary: verdict.summary, materialClassification };
925
+ return { _meta: META, applied: false, dryRun: true, verified: true, files: Object.keys(confinedAbs), summary: verdict.summary, verificationRecord: verdict.verificationRecord ?? null, ...verificationFields(verdict.verificationRecord), materialClassification };
917
926
  }
918
927
  const approvalRefusal = _highImpactApprovalRefusal(ctx, materialClassification, fixMeta);
919
928
  if (approvalRefusal) return approvalRefusal;
@@ -944,7 +953,7 @@ export const apply_fix = {
944
953
  }
945
954
  let acceptance = null;
946
955
  try { acceptance = fixAcceptanceRate(ctx.sessionRoot); } catch { /* best-effort */ }
947
- return { _meta: META, applied: true, verified: true, patched: written, integrity: status, verify: { summary: verdict.summary }, acceptance, materialClassification };
956
+ return { _meta: META, applied: true, verified: true, patched: written, integrity: status, verify: { summary: verdict.summary, verificationRecord: verdict.verificationRecord ?? null, ...verificationFields(verdict.verificationRecord) }, acceptance, materialClassification };
948
957
  }
949
958
 
950
959
  if (typeof f.fix?.replacement !== 'string') {
@@ -1033,7 +1042,7 @@ export const apply_fix = {
1033
1042
  // proceed with apply_fix.
1034
1043
  export const verify_fix = {
1035
1044
  name: 'verify_fix',
1036
- description: 'Verify a proposed patch before applying. Re-scans the patched files in memory, runs the project linter, runs the project test suite, checks fix honesty (FULL/MITIGATION/WORKAROUND) when fixMeta is supplied, and re-runs the PoC when one exists. Returns { ok, rescan, lint, tests, honesty, poc, summary }. Does not write to the target project’s own files, but DOES append one record per attempt to .agentic-security/fix-metrics.jsonl for the measured fix-loop.',
1045
+ description: 'Verify a proposed patch before applying. Re-scans the patched files in memory, runs the project linter, runs the project test suite, checks fix honesty (FULL/MITIGATION/WORKAROUND) when fixMeta is supplied, and re-runs the PoC when one exists. Returns { ok, rescan, lint, tests, honesty, poc, summary, verificationRecord } (verificationRecord is the shared version-1 record: outcome is one of not-run, unsupported, inconclusive, error, refuted, confirmed, and a static re-scan alone is never reported as confirmed; verificationView is the shared projection every interface shows for it, with state, scope, evidence ids, replay prerequisites and what was and was not verified). Does not write to the target project’s own files, but DOES append one record per attempt to .agentic-security/fix-metrics.jsonl for the measured fix-loop.',
1037
1046
  inputSchema: {
1038
1047
  type: 'object',
1039
1048
  additionalProperties: false,
@@ -1134,6 +1143,10 @@ export const verify_fix = {
1134
1143
  honesty: r.honesty,
1135
1144
  poc: r.poc ? { ...r.poc, reason: r.poc.reason ? redactString(r.poc.reason) : r.poc.reason } : r.poc,
1136
1145
  summary: r.summary,
1146
+ // X-201: the one version-1 verification record (additive). It states what was actually established, e.g.
1147
+ // `not-run` when no exploit oracle executed against the patch, never a bare pass/fail.
1148
+ verificationRecord: r.verificationRecord ?? null,
1149
+ ...verificationFields(r.verificationRecord),
1137
1150
  };
1138
1151
  } catch (e) {
1139
1152
  return { _meta: META, ok: false, reason: `verify_fix failed: ${e.message}` };