@clear-capabilities/agentic-security-scanner 0.167.0 → 0.169.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.
@@ -1 +1 @@
1
- 2e44c68c2c211c228f7a80226210e4c15a722a1fa450433222f42abb127f3b32 agentic-security.mjs
1
+ 84aea0a9b1b9d9655ff0b32a45fbf00b9f352101c4c0822cd3d10e0ea34d55bc agentic-security.mjs
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clear-capabilities/agentic-security-scanner",
3
- "version": "0.167.0",
3
+ "version": "0.169.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",
@@ -74,11 +74,11 @@
74
74
  "test:ollama-e2e": "AGENTIC_SECURITY_OLLAMA_E2E=1 node --test test/ollama-e2e.test.js",
75
75
  "test:glob": "node --test test/glob-compat.test.js",
76
76
  "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",
77
- "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 test/load-factor.test.js test/proof-fabric/core.test.js test/proof-fabric/fix-store-runner.test.js test/proof-fabric/fixtures.test.js test/cli/proof-fabric-cli.test.js test/rule-compiler/core.test.js test/cli/rule-compiler-cli.test.js",
77
+ "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 test/load-factor.test.js test/proof-fabric/core.test.js test/proof-fabric/fix-store-runner.test.js test/proof-fabric/fixtures.test.js test/cli/proof-fabric-cli.test.js test/rule-compiler/core.test.js test/cli/rule-compiler-cli.test.js test/fleet/fleet-graph.test.js test/fleet/fleet-manifest.test.js test/fleet/fleet-cli.test.js",
78
78
  "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 test/evaluation/guard-dominance.test.js test/evaluation/engine-mechanisms.test.js test/rx-exec-receiver.test.js",
79
79
  "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 test/capabilities/tools.test.js",
80
80
  "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",
81
- "test:lineage": "node --test test/lineage/schema.test.js test/lineage/ids.test.js test/lineage/protection.test.js test/lineage/classification.test.js test/lineage/validate.test.js test/lineage/json-schema-parity.test.js test/lineage/flagship-fixture.test.js test/lineage/flagship-fixture-semantics.test.js test/lineage/field-identity.test.js test/lineage/engine-expr-resolver.test.js test/lineage/engine-walker.test.js test/lineage/engine-integration.test.js test/lineage/engine-receiver-identity.test.js test/lineage/summaries.test.js test/lineage/driver.test.js test/lineage/engine-provenance.test.js test/lineage/engine-provenance-interprocedural.test.js test/lineage/path-store.test.js test/lineage/path-query.test.js test/lineage/flow-grade.test.js test/lineage/source-registry.test.js test/lineage/sink-registry.test.js test/lineage/transform-catalog.test.js test/lineage/handling-analyzer.test.js test/lineage/registry-real-code.test.js test/lineage/source-seeding.test.js test/lineage/graph-builder.test.js test/lineage/coverage.test.js test/lineage/resolve-destination.test.js test/lineage/queue-detail.test.js test/lineage/index.test.js test/lineage/obligation-mapping.test.js test/lineage/obligation-predicates.test.js test/lineage/obligation-predicates-walkthrough.test.js test/lineage/transit-protection.test.js test/lineage/at-rest-protection.test.js test/lineage/protection-summary.test.js test/lineage/ac01-multi-sink.test.js test/lineage/policy-verdict.test.js test/lineage/governance-refs.test.js test/lineage/export-privacy.test.js test/lineage/export-json.test.js test/lineage/export-csv.test.js test/lineage/decision-story.test.js test/lineage/export-briefing.test.js test/lineage/graph-snapshot.test.js test/lineage/graph-diff.test.js test/lineage/drift-policy.test.js test/lineage/recipient-profile.test.js test/lineage/recipient-registry.test.js test/lineage/recipient-wiring.test.js test/lineage/redact-graph.test.js test/lineage-scan-wiring.test.js test/lineage-artifact-write.test.js test/bench-data-lineage-runner.test.js test/bundle-frontend.test.js test/bundle-frontend-golden.test.js test/generate-html-report.test.js test/export-image.test.js test/lineage/scenario.test.js test/lineage/scenario-engine.test.js test/lineage/scenario-diff.test.js test/lineage/scenario-no-obligation-wiring.test.js test/lineage/impact-assessment.test.js test/lineage/impact-engine.test.js test/lineage/impact-no-obligation-wiring.test.js test/lineage/governance-edit.test.js test/lineage/remediation.test.js test/lineage/runtime-observation.test.js test/lineage/observation-correlation.test.js test/lineage/observation-adapters.test.js test/lineage/observation-store.test.js test/lineage/runtime-corroboration-wiring.test.js test/lineage/cross-repo-link.test.js test/lineage/federation-loader.test.js test/lineage/cross-repo-link-wiring.test.js",
81
+ "test:lineage": "node --test test/lineage/schema.test.js test/lineage/ids.test.js test/lineage/protection.test.js test/lineage/classification.test.js test/lineage/validate.test.js test/lineage/json-schema-parity.test.js test/lineage/flagship-fixture.test.js test/lineage/flagship-fixture-semantics.test.js test/lineage/field-identity.test.js test/lineage/engine-expr-resolver.test.js test/lineage/engine-walker.test.js test/lineage/engine-integration.test.js test/lineage/engine-receiver-identity.test.js test/lineage/summaries.test.js test/lineage/driver.test.js test/lineage/engine-provenance.test.js test/lineage/engine-provenance-interprocedural.test.js test/lineage/path-store.test.js test/lineage/path-query.test.js test/lineage/flow-grade.test.js test/lineage/source-registry.test.js test/lineage/sink-registry.test.js test/lineage/transform-catalog.test.js test/lineage/handling-analyzer.test.js test/lineage/registry-real-code.test.js test/lineage/source-seeding.test.js test/lineage/graph-builder.test.js test/lineage/coverage.test.js test/lineage/resolve-destination.test.js test/lineage/queue-detail.test.js test/lineage/index.test.js test/lineage/obligation-mapping.test.js test/lineage/obligation-predicates.test.js test/lineage/obligation-predicates-walkthrough.test.js test/lineage/transit-protection.test.js test/lineage/at-rest-protection.test.js test/lineage/protection-summary.test.js test/lineage/ac01-multi-sink.test.js test/lineage/policy-verdict.test.js test/lineage/governance-refs.test.js test/lineage/export-privacy.test.js test/lineage/export-json.test.js test/lineage/export-csv.test.js test/lineage/decision-story.test.js test/lineage/export-briefing.test.js test/lineage/graph-snapshot.test.js test/lineage/graph-diff.test.js test/lineage/drift-policy.test.js test/lineage/recipient-profile.test.js test/lineage/recipient-registry.test.js test/lineage/recipient-wiring.test.js test/lineage/redact-graph.test.js test/lineage-scan-wiring.test.js test/lineage-artifact-write.test.js test/bench-data-lineage-runner.test.js test/bundle-frontend.test.js test/bundle-frontend-golden.test.js test/generate-html-report.test.js test/export-image.test.js test/lineage/scenario.test.js test/lineage/scenario-engine.test.js test/lineage/scenario-diff.test.js test/lineage/scenario-no-obligation-wiring.test.js test/lineage/impact-assessment.test.js test/lineage/impact-engine.test.js test/lineage/impact-no-obligation-wiring.test.js test/lineage/governance-edit.test.js test/lineage/remediation.test.js test/lineage/runtime-observation.test.js test/lineage/observation-correlation.test.js test/lineage/observation-adapters.test.js test/lineage/observation-store.test.js test/lineage/runtime-corroboration-wiring.test.js test/lineage/cross-repo-link.test.js test/lineage/federation-loader.test.js test/lineage/cross-repo-link-wiring.test.js test/lineage/runtime-assurance.test.js test/cli/runtime-twin-cli.test.js",
82
82
  "test:bench-modules": "node --test test/phase4-harness.test.js test/pipeline.test.js test/proof-corpus-lib.test.js test/proof-corpus-runner.test.js",
83
83
  "test:server": "node --test test/server/graph-loader.test.js test/server/security.test.js test/server/routes.test.js test/server/http-server.test.js test/server/cmd-explore.test.js test/server/cmd-dataflow-export.test.js test/server/static-assets.test.js test/server/dataflow-scenario-cli.test.js test/server/dataflow-impact-cli.test.js",
84
84
  "test:haskell": "node --test test/haskell/haskell-injection.test.js test/haskell/haskell-ir.test.js test/haskell/haskell-manifests.test.js test/haskell/haskell-parser.test.js test/haskell/haskell-remediation.test.js test/haskell/haskell-resolved-graph.test.js test/haskell/haskell-sca.test.js test/haskell/haskell-sca-severity.test.js test/haskell/haskell-web-guards.test.js test/haskell/haskell-advisory-feed.test.js test/haskell/haskell-security-rules.test.js test/haskell/haskell-taint.test.js test/haskell/haskell-boundaries.test.js test/haskell/haskell-live-feed-scan.test.js test/haskell/haskell-supply-bounds.test.js test/haskell/haskell-ir-scaling.test.js",
@@ -946,6 +946,25 @@ layer, without collecting a single payload value.
946
946
  | `coverage.js` / `index.js` (extended, additively) | `buildGraphWithCoverage` composes a default `correlateObservations` closure over a **pre-loaded** `opts.runtimeObservations` array (never a raw path); `index.js` performs the ONE read per `buildLineageGraph` call (`fs.existsSync(dir) ? loadObservations(scanRoot) : null` — the `null` is deliberate, see below). |
947
947
  | `bin/agentic-security.js` (`dataflow observations import\|list`, `dataflow twin`) | The AC-29 proof surface — see `commands/dataflow.md`'s own `## Runtime observations (FR-505)` / `## Runtime Digital Twin layers (AC-29)` sections for the full CLI reference. `observations import` is dry-run-by-default and refuses the WHOLE import on any rejected record; `observations list` never prints an attribute key or value; `dataflow twin` is read-only and never writes into `.agentic-security/`. |
948
948
 
949
+ ### PRD "Runtime-Corroborated Security Twin" addendum (2026-10-13) — closes the real telemetry-input gap
950
+
951
+ `observation-adapters.js` gained four more adapters alongside `native-jsonl`: `otel-json`
952
+ (OpenTelemetry JSON trace export, one draft per span), `envoy-access-log` (Envoy's default JSON
953
+ access log, `response_flags` → the new `network.securityDecision` attribute), `k8s-audit-log`
954
+ (Kubernetes `audit.k8s.io` JSONL — `objectRef`/`responseStatus.code`, deliberately never
955
+ `user.username`), `http-access-log` (generic JSON-lines access log). All four are pure (zero
956
+ imports) and follow the exact same `(text, context) -> {drafts, errors}` contract, dropping every
957
+ field with no safe, approved destination. `runtime-observation.js` gained exactly one new attribute
958
+ key, `network.securityDecision` (a closed `allowed|denied` enum, deliberately narrower than the
959
+ generic identifier-value grammar every other key gets) — no identity field was added; that remains
960
+ a deliberate, disclosed omission. `runtime-assurance.js` (new) reports `runtimeStatus:
961
+ complete|partial|unavailable`, read by the new top-level `runtime` CLI (`runtime ingest/status/
962
+ reconcile/query`, `commands/runtime.md`) — `ingest` shims the existing `dataflow observations
963
+ import`, the rest are new but read-only over the existing correlation/store functions. The 3-rung
964
+ match ladder, the 3-valued `OBSERVATION_LAYERS`, and the server/UI deferral above are ALL
965
+ unchanged — see `docs/implementation-status/runtime-corroborated-twin.md` for the full scope
966
+ decision and what was deliberately left out.
967
+
949
968
  ### The five AC-29 properties a future UI increment inherits, and must not break
950
969
 
951
970
  A future UI increment (deferred here — see the out-of-scope list below) sits
@@ -262,6 +262,370 @@ export function parseNativeJsonlObservations(text, context) {
262
262
  return { drafts, errors };
263
263
  }
264
264
 
265
+ // ── The four PRD "Runtime-Corroborated Security Twin" adapters ─────────
266
+ //
267
+ // Each of the four parsers below follows the identical contract the
268
+ // native-jsonl adapter above already established: pure (zero imports, no
269
+ // fs, never throws), `(text, context) -> {drafts, errors}`, one
270
+ // `{line, message}` error per malformed line (never a value echo), every
271
+ // OTHER line still parses. Each produces drafts carrying ONLY attribute
272
+ // keys already on `RUNTIME_ATTRIBUTE_KEYS` (`runtime-observation.js`) —
273
+ // none of these parsers imports that file (this module stays zero-import
274
+ // by contract), so an attribute key typo here is caught one layer up, at
275
+ // import time, exactly like the native-jsonl adapter's own documented
276
+ // two-layer split. Each adapter deliberately DROPS every field of its
277
+ // source format with no safe, approved destination — a raw URL path/query
278
+ // string, a user agent, a client IP, a trace/span id, a username/identity
279
+ // — rather than inventing a new attribute key to carry it (PRD "Privacy
280
+ // Protection" P0-7: schema/field classification only, never a captured
281
+ // value). What each adapter keeps and drops is documented at its own
282
+ // definition.
283
+
284
+ const _IDENTIFIER_ATTR_RE = /^[A-Za-z0-9._:-]{1,128}$/;
285
+
286
+ // Only ever WRITES a value that already looks like an approved identifier
287
+ // — never force-sanitizes an unsafe value into a misleadingly different
288
+ // one. Returns `null` (meaning "drop this attribute") for anything that
289
+ // doesn't already look safe, rather than guessing.
290
+ function _safeIdentifierOrNull(v) {
291
+ if (typeof v !== 'string' || v.length === 0) return null;
292
+ return _IDENTIFIER_ATTR_RE.test(v) ? v : null;
293
+ }
294
+
295
+ // `"host:port"` -> `{host, port}` (port `null` when absent/unparseable).
296
+ // Never a full URL parse — this shape (Envoy's `upstream_host`, a bare
297
+ // `host:port` pair) has no scheme/path to confuse it with.
298
+ function _splitHostPort(v) {
299
+ if (typeof v !== 'string' || v.length === 0) return { host: null, port: null };
300
+ const i = v.lastIndexOf(':');
301
+ if (i === -1) return { host: _safeIdentifierOrNull(v), port: null };
302
+ const host = v.slice(0, i);
303
+ const portStr = v.slice(i + 1);
304
+ const port = /^[0-9]{1,5}$/.test(portStr) ? Number(portStr) : null;
305
+ return { host: _safeIdentifierOrNull(host), port };
306
+ }
307
+
308
+ function _draftBase(context, environmentOverride) {
309
+ const { version, adapter, source, environment, windowStart, windowEnd, importedAt, retention } = context;
310
+ return {
311
+ version, adapter, source,
312
+ environment: environmentOverride !== undefined ? environmentOverride : environment,
313
+ windowStart, windowEnd, retention, importedAt,
314
+ };
315
+ }
316
+
317
+ /**
318
+ * OpenTelemetry JSON trace export (the standard OTLP/JSON `resourceSpans`
319
+ * document — a SINGLE JSON object, never JSONL; this is the format
320
+ * `otlptracehttp` with `OTEL_EXPORTER_OTLP_TRACES_PROTOCOL=http/json`
321
+ * writes). One draft per span. Reads only well-known OpenTelemetry
322
+ * semantic-convention attribute keys — `server.address`/`net.peer.name`,
323
+ * `server.port`/`net.peer.port`, `url.scheme`/`http.scheme`,
324
+ * `network.protocol.name`, `db.system`/`db.name`,
325
+ * `messaging.destination.name` — mapped onto the approved
326
+ * `destination.*`/`network.protocol`/`schema.name` keys; `resource`-level
327
+ * `service.name` is carried as `service.name`. Every other span
328
+ * attribute (`http.url`'s full query string, `http.request.header.*`,
329
+ * `gen_ai.*` prompt/completion content, trace/span ids themselves) is
330
+ * dropped — none has a safe, approved destination. `eventCountBand` is
331
+ * always `'1'`: a single exported span is a single observed event, never
332
+ * an aggregate count OTel itself does not report.
333
+ */
334
+ export function parseOtelJsonObservations(text, context) {
335
+ if (typeof text !== 'string' || !_isPlainObject(context)) {
336
+ return { drafts: [], errors: [{ line: 0, message: 'parseOtelJsonObservations requires a string text and a plain-object context' }] };
337
+ }
338
+
339
+ let doc;
340
+ try {
341
+ doc = JSON.parse(text);
342
+ } catch {
343
+ return { drafts: [], errors: [{ line: 0, message: 'invalid JSON: expected one OTLP/JSON resourceSpans document' }] };
344
+ }
345
+ if (!_isPlainObject(doc) || !Array.isArray(doc.resourceSpans)) {
346
+ return { drafts: [], errors: [{ line: 0, message: 'expected a JSON object with a top-level "resourceSpans" array (the OTLP/JSON export shape)' }] };
347
+ }
348
+
349
+ const _attrMap = (attrs) => {
350
+ const m = {};
351
+ if (!Array.isArray(attrs)) return m;
352
+ for (const a of attrs) {
353
+ if (!_isPlainObject(a) || typeof a.key !== 'string' || !_isPlainObject(a.value)) continue;
354
+ const v = a.value;
355
+ if (typeof v.stringValue === 'string') m[a.key] = v.stringValue;
356
+ else if (typeof v.intValue === 'number' || typeof v.intValue === 'string') m[a.key] = String(v.intValue);
357
+ else if (typeof v.boolValue === 'boolean') m[a.key] = v.boolValue;
358
+ }
359
+ return m;
360
+ };
361
+
362
+ // Nanoseconds-since-epoch (OTLP's own wire unit) as a string or number
363
+ // -> ISO-8601. Returns null (never a fabricated "now") when absent or
364
+ // unparseable, letting the caller fall back to the import window.
365
+ const _isoFromUnixNano = (v) => {
366
+ if (v === undefined || v === null) return null;
367
+ const n = typeof v === 'string' ? Number(v) : v;
368
+ if (!Number.isFinite(n) || n <= 0) return null;
369
+ const ms = Math.round(n / 1e6);
370
+ const d = new Date(ms);
371
+ return Number.isFinite(d.getTime()) ? d.toISOString() : null;
372
+ };
373
+
374
+ const drafts = [];
375
+ for (const rs of doc.resourceSpans) {
376
+ if (!_isPlainObject(rs)) continue;
377
+ const resourceAttrs = _attrMap(rs.resource?.attributes);
378
+ const serviceName = _safeIdentifierOrNull(resourceAttrs['service.name']);
379
+
380
+ for (const ss of Array.isArray(rs.scopeSpans) ? rs.scopeSpans : []) {
381
+ if (!_isPlainObject(ss)) continue;
382
+ for (const span of Array.isArray(ss.spans) ? ss.spans : []) {
383
+ if (!_isPlainObject(span)) continue;
384
+ const a = _attrMap(span.attributes);
385
+
386
+ const attributes = {};
387
+ if (serviceName) attributes['service.name'] = serviceName;
388
+ const host = _safeIdentifierOrNull(a['server.address'] ?? a['net.peer.name']);
389
+ if (host) attributes['destination.host'] = host;
390
+ const portRaw = a['server.port'] ?? a['net.peer.port'];
391
+ const port = /^[0-9]{1,5}$/.test(String(portRaw ?? '')) ? Number(portRaw) : null;
392
+ if (port !== null) attributes['destination.port'] = port;
393
+ const scheme = _safeIdentifierOrNull(a['url.scheme'] ?? a['http.scheme']);
394
+ if (scheme) attributes['destination.scheme'] = scheme;
395
+ const protocol = _safeIdentifierOrNull(a['network.protocol.name']);
396
+ if (protocol) attributes['network.protocol'] = protocol;
397
+ const storeService = _safeIdentifierOrNull(a['db.system'] ?? a['db.name'] ?? a['messaging.destination.name']);
398
+ if (storeService) attributes['destination.service'] = storeService;
399
+
400
+ if (Object.keys(attributes).length === 0) continue; // nothing safe to report for this span
401
+
402
+ const at = _isoFromUnixNano(span.startTimeUnixNano) ?? context.windowStart;
403
+ drafts.push({
404
+ ..._draftBase(context),
405
+ attributes,
406
+ eventCountBand: '1',
407
+ firstObservedAt: at,
408
+ lastObservedAt: _isoFromUnixNano(span.endTimeUnixNano) ?? at,
409
+ });
410
+ }
411
+ }
412
+ }
413
+
414
+ return { drafts, errors: [] };
415
+ }
416
+
417
+ /**
418
+ * Envoy's default JSON access log (`json_format` logging, one JSON
419
+ * object per line). Reads `upstream_host` (split into
420
+ * `destination.host`/`destination.port`), `upstream_cluster` (as
421
+ * `destination.service`), `protocol` (normalized — Envoy writes
422
+ * `"HTTP/1.1"`, which contains `/` and is not an approved identifier
423
+ * shape; lowercased and `/` stripped, e.g. `"http1.1"`), and
424
+ * `response_flags` (`'-'`/empty means no flag, i.e. allowed; a non-empty
425
+ * flags string, e.g. `"UAEX"` for unauthorized, is treated as `'denied'`
426
+ * via `network.securityDecision`). `start_time`/`end_time` (or
427
+ * `start_time` alone, with `duration` in milliseconds added) feed
428
+ * `firstObservedAt`/`lastObservedAt`. Everything else — client address,
429
+ * request path/query, user agent, request id, response code's own exact
430
+ * value — is dropped.
431
+ */
432
+ export function parseEnvoyAccessLogObservations(text, context) {
433
+ if (typeof text !== 'string' || !_isPlainObject(context)) {
434
+ return { drafts: [], errors: [{ line: 0, message: 'parseEnvoyAccessLogObservations requires a string text and a plain-object context' }] };
435
+ }
436
+
437
+ const drafts = [];
438
+ const errors = [];
439
+ const lines = text.split(/\r?\n/);
440
+ for (let i = 0; i < lines.length; i++) {
441
+ const rawLine = lines[i];
442
+ if (rawLine.trim().length === 0) continue;
443
+ const line = i + 1;
444
+
445
+ let record;
446
+ try {
447
+ record = JSON.parse(rawLine);
448
+ } catch {
449
+ errors.push({ line, message: `invalid JSON on line ${line}` });
450
+ continue;
451
+ }
452
+ if (!_isPlainObject(record)) {
453
+ errors.push({ line, message: `record on line ${line} must be a JSON object` });
454
+ continue;
455
+ }
456
+
457
+ const { host, port } = _splitHostPort(record.upstream_host);
458
+ const cluster = _safeIdentifierOrNull(record.upstream_cluster);
459
+ const protocol = typeof record.protocol === 'string'
460
+ ? _safeIdentifierOrNull(record.protocol.toLowerCase().replace(/\//g, ''))
461
+ : null;
462
+
463
+ const attributes = {};
464
+ if (host) attributes['destination.host'] = host;
465
+ if (port !== null) attributes['destination.port'] = port;
466
+ if (cluster) attributes['destination.service'] = cluster;
467
+ if (protocol) attributes['network.protocol'] = protocol;
468
+ if (typeof record.response_flags === 'string') {
469
+ attributes['network.securityDecision'] = record.response_flags.trim().length === 0 || record.response_flags.trim() === '-' ? 'allowed' : 'denied';
470
+ }
471
+
472
+ if (Object.keys(attributes).length === 0) continue; // nothing safe/present to report for this line
473
+
474
+ const startIso = typeof record.start_time === 'string' && Number.isFinite(Date.parse(record.start_time))
475
+ ? record.start_time : context.windowStart;
476
+ let endIso = typeof record.end_time === 'string' && Number.isFinite(Date.parse(record.end_time))
477
+ ? record.end_time : null;
478
+ if (endIso === null && typeof record.duration === 'number' && Number.isFinite(record.duration)) {
479
+ endIso = new Date(Date.parse(startIso) + record.duration).toISOString();
480
+ }
481
+
482
+ drafts.push({
483
+ ..._draftBase(context),
484
+ attributes,
485
+ eventCountBand: '1',
486
+ firstObservedAt: startIso,
487
+ lastObservedAt: endIso ?? startIso,
488
+ });
489
+ }
490
+
491
+ return { drafts, errors };
492
+ }
493
+
494
+ /**
495
+ * Kubernetes `audit.k8s.io` audit-event JSONL (one event object per
496
+ * line — the default `webhook`/file backend format). Reads `objectRef`
497
+ * (`namespace` + `resource`, joined as `"<namespace>:<resource>"` into
498
+ * `destination.service` — never `objectRef.name`, which is frequently a
499
+ * user-chosen, potentially sensitive resource name) and
500
+ * `responseStatus.code` (mapped to `network.securityDecision`: `< 400`
501
+ * is `'allowed'`, `>= 400` is `'denied'` — a client error is treated as a
502
+ * decision here because the audit log's own admission/authorization
503
+ * failures surface as 401/403/409, which is exactly the "security
504
+ * decision" fact this adapter exists to carry). `requestReceivedTimestamp`/
505
+ * `stageTimestamp` feed the observed-at window. **`user.username` and
506
+ * every request/response body field are deliberately never read** — a
507
+ * K8s audit event's `user` block is exactly the "authenticated identity"
508
+ * PRD 2 names, and `runtime-observation.js`'s own contract has no
509
+ * approved field for it (a deliberate, disclosed scope boundary — see
510
+ * this PRD's implementation-status doc).
511
+ */
512
+ export function parseK8sAuditLogObservations(text, context) {
513
+ if (typeof text !== 'string' || !_isPlainObject(context)) {
514
+ return { drafts: [], errors: [{ line: 0, message: 'parseK8sAuditLogObservations requires a string text and a plain-object context' }] };
515
+ }
516
+
517
+ const drafts = [];
518
+ const errors = [];
519
+ const lines = text.split(/\r?\n/);
520
+ for (let i = 0; i < lines.length; i++) {
521
+ const rawLine = lines[i];
522
+ if (rawLine.trim().length === 0) continue;
523
+ const line = i + 1;
524
+
525
+ let record;
526
+ try {
527
+ record = JSON.parse(rawLine);
528
+ } catch {
529
+ errors.push({ line, message: `invalid JSON on line ${line}` });
530
+ continue;
531
+ }
532
+ if (!_isPlainObject(record)) {
533
+ errors.push({ line, message: `record on line ${line} must be a JSON object` });
534
+ continue;
535
+ }
536
+
537
+ const objectRef = _isPlainObject(record.objectRef) ? record.objectRef : {};
538
+ const namespace = _safeIdentifierOrNull(objectRef.namespace);
539
+ const resource = _safeIdentifierOrNull(objectRef.resource);
540
+ const code = record.responseStatus && typeof record.responseStatus === 'object' ? record.responseStatus.code : undefined;
541
+
542
+ const attributes = {};
543
+ if (namespace && resource) attributes['destination.service'] = `${namespace}:${resource}`;
544
+ else if (resource) attributes['destination.service'] = resource;
545
+ if (typeof code === 'number' && Number.isFinite(code)) {
546
+ attributes['network.securityDecision'] = code < 400 ? 'allowed' : 'denied';
547
+ }
548
+
549
+ if (Object.keys(attributes).length === 0) continue;
550
+
551
+ const observedAt = typeof record.requestReceivedTimestamp === 'string' && Number.isFinite(Date.parse(record.requestReceivedTimestamp))
552
+ ? record.requestReceivedTimestamp
553
+ : (typeof record.stageTimestamp === 'string' && Number.isFinite(Date.parse(record.stageTimestamp)) ? record.stageTimestamp : context.windowStart);
554
+
555
+ drafts.push({
556
+ ..._draftBase(context),
557
+ attributes,
558
+ eventCountBand: '1',
559
+ firstObservedAt: observedAt,
560
+ lastObservedAt: observedAt,
561
+ });
562
+ }
563
+
564
+ return { drafts, errors };
565
+ }
566
+
567
+ /**
568
+ * A generic JSON-lines HTTP access log (one JSON object per line — the
569
+ * shape a structured reverse proxy or load-balancer access log normalizes
570
+ * to; deliberately not an Apache/Nginx TEXT combined-log parser, which
571
+ * would need its own separate, much larger format-string handling).
572
+ * Reads `host`/`destination_host` -> `destination.host`,
573
+ * `upstream`/`upstream_service` -> `destination.service`, `scheme` ->
574
+ * `destination.scheme`. `timestamp` (or `time`) feeds the observed-at
575
+ * window. Request path/query, client IP, user agent, status code's own
576
+ * value, and every header are dropped.
577
+ */
578
+ export function parseHttpAccessLogObservations(text, context) {
579
+ if (typeof text !== 'string' || !_isPlainObject(context)) {
580
+ return { drafts: [], errors: [{ line: 0, message: 'parseHttpAccessLogObservations requires a string text and a plain-object context' }] };
581
+ }
582
+
583
+ const drafts = [];
584
+ const errors = [];
585
+ const lines = text.split(/\r?\n/);
586
+ for (let i = 0; i < lines.length; i++) {
587
+ const rawLine = lines[i];
588
+ if (rawLine.trim().length === 0) continue;
589
+ const line = i + 1;
590
+
591
+ let record;
592
+ try {
593
+ record = JSON.parse(rawLine);
594
+ } catch {
595
+ errors.push({ line, message: `invalid JSON on line ${line}` });
596
+ continue;
597
+ }
598
+ if (!_isPlainObject(record)) {
599
+ errors.push({ line, message: `record on line ${line} must be a JSON object` });
600
+ continue;
601
+ }
602
+
603
+ const host = _safeIdentifierOrNull(record.host ?? record.destination_host);
604
+ const upstream = _safeIdentifierOrNull(record.upstream ?? record.upstream_service);
605
+ const scheme = _safeIdentifierOrNull(record.scheme);
606
+
607
+ const attributes = {};
608
+ if (host) attributes['destination.host'] = host;
609
+ if (upstream) attributes['destination.service'] = upstream;
610
+ if (scheme) attributes['destination.scheme'] = scheme;
611
+
612
+ if (Object.keys(attributes).length === 0) continue;
613
+
614
+ const ts = record.timestamp ?? record.time;
615
+ const observedAt = typeof ts === 'string' && Number.isFinite(Date.parse(ts)) ? ts : context.windowStart;
616
+
617
+ drafts.push({
618
+ ..._draftBase(context),
619
+ attributes,
620
+ eventCountBand: '1',
621
+ firstObservedAt: observedAt,
622
+ lastObservedAt: observedAt,
623
+ });
624
+ }
625
+
626
+ return { drafts, errors };
627
+ }
628
+
265
629
  // The adapter registry — a frozen, module-level lookup. `adapterFor`
266
630
  // returns `null` for anything unknown, NEVER a default: an unimplemented
267
631
  // adapter name (e.g. `'otlp'`, deferred per the scoping doc's §4.3 ruling)
@@ -269,6 +633,10 @@ export function parseNativeJsonlObservations(text, context) {
269
633
  // someday exist.
270
634
  const _ADAPTERS = Object.freeze({
271
635
  'native-jsonl': Object.freeze({ name: 'native-jsonl', parse: parseNativeJsonlObservations }),
636
+ 'otel-json': Object.freeze({ name: 'otel-json', parse: parseOtelJsonObservations }),
637
+ 'envoy-access-log': Object.freeze({ name: 'envoy-access-log', parse: parseEnvoyAccessLogObservations }),
638
+ 'k8s-audit-log': Object.freeze({ name: 'k8s-audit-log', parse: parseK8sAuditLogObservations }),
639
+ 'http-access-log': Object.freeze({ name: 'http-access-log', parse: parseHttpAccessLogObservations }),
272
640
  });
273
641
 
274
642
  /**
@@ -0,0 +1,135 @@
1
+ // runtime-assurance.js — PRD "Runtime-Corroborated Security Twin" P0-8
2
+ // (Runtime Assurance). A pure aggregation over already-loaded observation
3
+ // imports and an already-computed `correlateObservations` result — never
4
+ // re-reads a file, never re-runs the match ladder. Honest by construction:
5
+ // the one rule this module exists to enforce is that a MISSING or STALE
6
+ // telemetry source can never collapse into "no runtime risk" — the
7
+ // `runtimeStatus` field is always exactly one of `'complete'`, `'partial'`,
8
+ // or `'unavailable'`, and `'unavailable'` is the answer whenever no
9
+ // observation store was consulted at all, never a silent empty report.
10
+ //
11
+ // `parseFailures` is always reported as 0, with an explicit note why:
12
+ // `dataflow observations import` REFUSES the whole import the moment any
13
+ // record fails to parse or validate (AC-29 clause 5) — a failed import is
14
+ // never persisted, so there is no on-disk history of parse failures for
15
+ // this module to read. This is a real, disclosed limitation, not a false
16
+ // "zero failures ever happened" claim.
17
+
18
+ export const RUNTIME_STATUS_VALUES = Object.freeze(['complete', 'partial', 'unavailable']);
19
+
20
+ function _isPlainObject(v) {
21
+ return typeof v === 'object' && v !== null && !Array.isArray(v);
22
+ }
23
+
24
+ function _maxIso(list) {
25
+ let best = null;
26
+ let bestT = -Infinity;
27
+ for (const s of list) {
28
+ const t = Date.parse(s);
29
+ if (Number.isFinite(t) && t > bestT) { bestT = t; best = s; }
30
+ }
31
+ return best;
32
+ }
33
+
34
+ function _minIso(list) {
35
+ let best = null;
36
+ let bestT = Infinity;
37
+ for (const s of list) {
38
+ const t = Date.parse(s);
39
+ if (Number.isFinite(t) && t < bestT) { bestT = t; best = s; }
40
+ }
41
+ return best;
42
+ }
43
+
44
+ /**
45
+ * `computeRuntimeAssurance(opts) -> RuntimeAssurance`
46
+ *
47
+ * `opts`:
48
+ * - `importRecords`: the `ObservationImport[]` read back from the store
49
+ * (`loadObservationImports`), or `null`/`undefined` when the store was
50
+ * never consulted at all (mirrors `correlateObservations`'s own
51
+ * `observations === null` convention).
52
+ * - `correlation`: the `CorrelationResult` from `correlateObservations`
53
+ * for the SAME graph/observations/window this call is reporting on.
54
+ * - `now`: an ISO-8601 string (defaults to `new Date().toISOString()`,
55
+ * injectable for deterministic tests).
56
+ * - `staleAfterHours`: how old the most recent observed event can be
57
+ * before freshness is reported stale (default 24 * 30 = 720, i.e. 30
58
+ * days — telemetry windows in this package's own fixtures/CLI default
59
+ * to month-long exports, so a 30-day staleness bar is the honest
60
+ * "this is still describing roughly the current system" line, not an
61
+ * arbitrary short window).
62
+ *
63
+ * Never throws.
64
+ */
65
+ export function computeRuntimeAssurance(opts) {
66
+ const o = opts && typeof opts === 'object' ? opts : {};
67
+ const now = typeof o.now === 'string' && Number.isFinite(Date.parse(o.now)) ? o.now : new Date().toISOString();
68
+ const staleAfterHours = Number.isFinite(o.staleAfterHours) ? o.staleAfterHours : 720;
69
+
70
+ const importRecords = Array.isArray(o.importRecords) ? o.importRecords : null;
71
+ const correlation = _isPlainObject(o.correlation) ? o.correlation : null;
72
+
73
+ const base = {
74
+ runtimeStatus: 'unavailable',
75
+ telemetryWindow: { start: null, end: null },
76
+ freshness: { lastObservedAt: null, lastImportedAt: null, staleHours: null, stale: true },
77
+ parseFailures: 0,
78
+ parseFailuresNote: 'A failed import is refused in full and never persisted (AC-29 clause 5) — '
79
+ + 'this count reflects the import store, not the history of every attempt. Check `runtime ingest`\'s '
80
+ + 'own exit code/stderr at import time for parse failures.',
81
+ mappingPercentage: null,
82
+ unmatchedObservationCount: 0,
83
+ totalObservationCount: 0,
84
+ missingTelemetry: true,
85
+ limitations: [],
86
+ };
87
+
88
+ // No store was ever consulted — the honest 'unavailable' case, never a
89
+ // fabricated empty-but-fine report.
90
+ if (importRecords === null || importRecords.length === 0 || !correlation || correlation.evaluated !== true) {
91
+ base.limitations.push(
92
+ 'No runtime observation import exists for this scan — runtimeStatus is \'unavailable\', never \'complete\' '
93
+ + 'or a silent absence of risk. Import telemetry with `runtime ingest` before relying on this report.',
94
+ );
95
+ return base;
96
+ }
97
+
98
+ const windowStart = _minIso(importRecords.map((r) => r?.windowStart));
99
+ const windowEnd = _maxIso(importRecords.map((r) => r?.windowEnd));
100
+ base.telemetryWindow = { start: windowStart, end: windowEnd };
101
+ base.missingTelemetry = false;
102
+
103
+ const allObservations = importRecords.flatMap((r) => (Array.isArray(r?.observations) ? r.observations : []));
104
+ const total = allObservations.length;
105
+ const unmatched = allObservations.filter((obs) => obs && obs.matchMethod === 'unmatched').length;
106
+ base.totalObservationCount = total;
107
+ base.unmatchedObservationCount = unmatched;
108
+ base.mappingPercentage = total > 0 ? Math.round(((total - unmatched) / total) * 10000) / 100 : null;
109
+
110
+ const lastObservedAt = _maxIso(allObservations.map((obs) => obs?.lastObservedAt));
111
+ const lastImportedAt = _maxIso(importRecords.map((r) => r?.importedAt));
112
+ base.freshness.lastObservedAt = lastObservedAt;
113
+ base.freshness.lastImportedAt = lastImportedAt;
114
+ if (lastObservedAt !== null) {
115
+ const ageMs = Date.parse(now) - Date.parse(lastObservedAt);
116
+ const ageHours = Math.round((ageMs / 3600000) * 100) / 100;
117
+ base.freshness.staleHours = ageHours;
118
+ base.freshness.stale = ageHours > staleAfterHours;
119
+ }
120
+
121
+ if (total === 0) {
122
+ base.limitations.push('The import store exists but carries zero observation records.');
123
+ }
124
+ if (unmatched > 0) {
125
+ base.limitations.push(`${unmatched} of ${total} imported observation(s) never matched any graph node (matchMethod 'unmatched') — these contribute no corroboration to any flow.`);
126
+ }
127
+ if (base.freshness.stale) {
128
+ base.limitations.push(`The most recent observed event is ${base.freshness.staleHours} hour(s) old (beyond the ${staleAfterHours}-hour staleness bar) — this twin may no longer describe current production behavior.`);
129
+ }
130
+
131
+ const complete = total > 0 && unmatched === 0 && !base.freshness.stale;
132
+ base.runtimeStatus = complete ? 'complete' : 'partial';
133
+
134
+ return base;
135
+ }