Skip to content

fix(scan): close remaining check/remediation intent mismatches - #204

Merged
alimobrem merged 2 commits into
mainfrom
fix/intent-audit-mismatches
Jul 23, 2026
Merged

fix(scan): close remaining check/remediation intent mismatches#204
alimobrem merged 2 commits into
mainfrom
fix/intent-audit-mismatches

Conversation

@alimobrem

Copy link
Copy Markdown
Owner

Summary

  • Closes all remaining gaps from docs/checks-remediations-intent-audit.md (audit doc included; supersedes/absorbs docs: checks & remediations intent audit #202 content)
  • Container subtypes: path-bound USER / HEALTHCHECK / UBI FROM harden (pin alone still refuses mismatch)
  • replicas vs PDB: analyzer emits replicasworkload-replicas source; availability stays PDB-only
  • health: source workload-health-probes + workload_probes; Kyverno companion refused
  • pipeline: detect skill sees GHA/GitLab/Jenkins/Tekton; non-Tekton → tekton_migration detect-only
  • tracing: detect-only (align instrumentation); otel-collector does not clear
  • policy: honest labels-only clear_evidence text
  • rbac: SecurityAnalyzer emits SA/Role/RoleBinding finding
  • Detect rename: secrets-scanning-in-civulnerability-scanning-in-ci
  • SBOM→CI already on tip via fix(compliance): clear sbom via CI generation, not static file #203

Test plan

  • tests/test_workload_patches.py, test_clear_evidence.py, test_skill_registry_agreement.py, phase4 detect parity
  • CI green + scripts/ci-merge-gate.sh
  • Close docs: checks & remediations intent audit #202 as absorbed / duplicate after merge
  • Comment pulse-agent#4 that next Scan opens CI SBOM (not static file)

Made with Cursor

Ship the audit After for SBOM→CI companions plus container harden,
replicas vs PDB, health source probes, GHA-aware pipeline, tracing
detect-only, honest policy text, and rbac analyzer emit.

Co-authored-by: Cursor <cursoragent@cursor.com>
@alimobrem

Copy link
Copy Markdown
Owner Author

Includes the intent-audit doc from #202 (updated statuses). After this merges, #202 can close as absorbed.

Co-authored-by: Cursor <cursoragent@cursor.com>
@alimobrem
alimobrem merged commit 7c30cf3 into main Jul 23, 2026
4 checks passed
alimobrem pushed a commit that referenced this pull request Jul 23, 2026
…ive findings

Every existing test proves staged content / routing / refuse reasons in
isolation -- none of them run the real loop this product's own contract
depends on ("a good AgentIT PR clears a real finding on next re-Assess").
That gap is exactly why the #199-#204 class of bug (a verifier too weak
to notice a "fix" that would not actually clear the finding) was only
ever caught by live dogfooding, one incident at a time, not CI.

tests/test_e2e_finding_clears.py: real temp-directory "repo" + the real
analyzer + skill-generation + enrichment functions; only the GitHub API
boundary (read_file/tree_paths) is backed by that same temp directory
instead of a live network call, so the whole loop runs hermetically and
fast.

- TestReplicasFindingActuallyClearsAfterMerge: pulse-agent's real Helm-
  templated replicas shape -- proves this session's workload-Helm-
  awareness fix closes the loop end-to-end (generate -> enrich -> merge
  -> re-Assess -> finding gone), not just that the enrichment function
  returns the right dict in isolation.
- TestContainerFindingActuallyClearsAfterMerge: pulse-agent's real
  Dockerfile :latest finding, proving the already-working containerfile-
  pin mechanism closes the same loop.
- A genuine, real gap this test-writing effort surfaced and documents
  rather than silently missing: HADRAnalyzer's replicas check is
  repo-wide (any YAML file with replicas>=2 anywhere in the tree), not
  scoped to the real deployed workload -- so even the OLD, un-enriched,
  fully disconnected stub also "clears" the finding dishonestly. Flagged
  as a known limitation (TestReplicasFindingActuallyClearsAfterMerge::
  test_known_gap_the_disconnected_stub_alone_also_fools_the_analyzer),
  not fixed in this pass -- the detection side has the same "is this the
  real workload" blind spot the generation side just had.

sbom (enrich_sbom_from_repo) is a natural next fixture to add here, not
yet built out. Full non-browser suite: 3426 passed, 452 skipped, 0
failed.

Co-authored-by: Cursor <cursoragent@cursor.com>
alimobrem added a commit that referenced this pull request Jul 24, 2026
…manual/auto delivery parity, collision + placeholder gates (#205)

* fix(remediation): Helm-aware replicas/health-probes patches, not deploy/deployment.yaml theater

pulse-agent#5/#6 (gitops): the workload-replicas/workload-health-probes
skills fabricate a disconnected deploy/deployment.yaml stub for every app,
even one with a perfectly real Helm chart -- root cause is
_workload_yaml_paths()/_snapshot_file_text() depending on an
analyzers.snapshot ContextVar that is only ever set during
run_assessment()'s own analyzer pass and is always gone by the time a
separate, later onboarding job calls these generators.

- source_patches.enrich_workload_files_from_repo(): the same GitHub-REST
  read_file/tree_paths mechanism apply_containerfile_pin_only() /
  enrich_sbom_from_repo() already use to find the app's real workload
  manifest instead of guessing. When the workload templates `replicas:` via
  `{{ .Values.<key> }}` (Helm), patches the chart's own values.yaml numeric
  key instead of rewriting the templated line -- avoids inserting a second,
  conflicting literal replicas: key that would corrupt the chart. Drops
  (fail-closed) rather than opens a PR when no real workload can be found or
  the chart key is ambiguous/nested, same posture as self_managed_hpa.py /
  fleet_hpa.py's scaleTargetRef gates.
- Wired into auto_delivery.py's existing pre-clear-evidence enrichment step
  and routes/assessments.py's manual Deliver path (parity).
- workload_patches.py: helm_templated_replicas_key/chart_root_for_template_path/
  values_yaml_path_for_chart/patch_values_numeric_key/values_yaml_replicas_at_least;
  verify_workload_replicas() now also accepts a values.yaml-shaped clear-evidence.
- analyzers/ha_dr.py: the `replicas` finding was also Helm-blind at
  detection time (raw literal-digit regex only) -- a chart correctly setting
  replicaCount>=2 in values.yaml would never clear the finding even after a
  perfect fix merged. Resolves a templated replicas: line against any
  values.yaml in the repo before flagging.

Tests: tests/test_workload_patches.py (new TestHelmChartAwareness,
TestEnrichWorkloadFilesFromRepo), tests/test_ha_dr.py (Helm values.yaml
resolution, still-flagged-below-minimum). Full non-browser suite: 3397
passed, 452 skipped, 0 failed.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(portal): manual Deliver routes through auto_validate_and_deliver, closing the gate gap

Hello-World gitops#31 class of bug: this route's own hand-maintained
subset of quality gates (Phase A finding gate, file filter, wrong-layer
strip, per-cluster cap, clear-evidence) had already drifted once from
auto_validate_and_deliver()'s full bar (missing Phase C SSA/property
checks, the fleet HPA scaleTargetRef gate, the capability filter, and the
bounded validate/fix converge loop) -- confirmed via a real entry-point x
gate audit.

POST /assessments/{id}/deliver's dry_run=False path now calls
auto_validate_and_deliver() directly instead of re-implementing a subset
of its own gates -- one pipeline every real-delivery entry point shares,
so this class of gap cannot reopen in a new shape. dry_run=True stays a
lightweight route_and_deliver(dry_run=True) preview, unchanged. The
narrow octocat/Hello-World probe-repo hard-refuse stays in the route
(not a general delivery gate, doesn't belong inside a function that must
stay reusable for any real app).

Test updates reflect real, expected behavior differences now that manual
Deliver shares the automatic pipeline's own validate/fix loop (which can
leave its own dry-run validation-probe delivery rows behind, and surfaces
"could not converge"-shaped refusal reasons for batches that previously
only failed a raw file-count cap) -- not weakened assertions; every test
still proves no PR/commit happens when it shouldn't. Also fixed a fixture
gap surfaced by the new Phase C property check now actually running for
this path: test_pr_repo_labeling.py's NetworkPolicy stub was missing a
real Ingress policyType, which the auto path's own validation would
always have refused.

Full non-browser suite: 3397 passed, 452 skipped, 0 failed.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(delivery): broaden placeholder detection beyond one hardcoded image token

Two real incidents reached merged content before being caught live:
an Argo Application repoURL of "your-org/hello-world.git" (gitops#32)
and a container image of "quay.io/org/pinky" -- neither is
REPLACE_WITH_AGENTIT_IMAGE, the only string
has_unresolved_placeholders() ever checked.

- delivery.py::has_unresolved_placeholders(): adds field-scoped regexes
  for repoURL:/image: lines matching your-org/your_org, example.com, or
  quay.io/org/ -- deliberately field-scoped, not a blanket content scan,
  so a legitimate placeholder-shaped value elsewhere in a generated file
  (skills/incident/alertmanager-config.md's own sample
  "team@example.com" alerting address) is never mistaken for a
  fabricated deploy target. Deliberately does NOT flag generic
  "github.com/org/..." repo URLs -- this codebase's own test fixtures
  already rely on that exact shape for a fine, real-looking repo, so a
  broader match would have been a false-positive regression, not a fix.
- clear_evidence.py::verify_argocd_application(): the same "your-org"/
  "example.com" repoURL patterns were previously explicitly *accepted*
  ("Template placeholder still ok" pass-through) whenever a path/chart
  was also present -- now hard-refused outright, matching the general
  placeholder gate.

Tests: tests/test_delivery_router.py (new placeholder-catch and
real-looking-URL-not-flagged cases), tests/test_clear_evidence.py (new
refuse-your-org, refuse-example.com, generic-org-still-allowed cases).
Full non-browser suite: 3401 passed, 452 skipped, 1 pre-existing flaky
failure unrelated to this change (test_finding_escalation.py, passes
standalone and within its own file -- known test-isolation flakiness).

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(quality): refuse a batch that would create colliding (kind,name,namespace) resources

Confirmed root cause of a real incident: duplicate GVK/name resources
across generated files blocked managed-pinky's Argo sync after merge --
nothing in this pipeline ever checked for this pre-open. SSA dry-run
validates each file/resource independently; Argo's own "who owns this"
ownership conflict only ever surfaced live, after merge.

- quality_prs.find_resource_collisions(): parses every generated file's
  YAML (multi-doc `---` aware) and flags any exact (apiVersion, kind,
  namespace, name) repeat -- within one file or across the whole batch.
  Different API groups sharing a bare kind name (two CRDs both calling
  themselves "Policy") are correctly not a collision; only an exact GVK
  match is.
- cluster_validation_ok() gains an optional collisions= param -- a
  collision is a hard Phase C refusal, same posture as an SSA/property
  failure, not a silent drop-one-keep-one guess.
- Wired into auto_delivery.py's one cluster_validation_ok() call site --
  since manual Deliver now shares this same pipeline, both entry points
  get this check for free, no separate wiring needed.

Tests: tests/test_quality_prs.py (new TestResourceCollisions -- distinct
resources, same-name-different-kind, same-name-different-namespace,
cross-file and within-file collisions, different-API-group same-kind-name
non-collision, cluster_validation_ok wiring). Full non-browser suite:
3410 passed, 452 skipped, 0 failed.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(cluster-apply): distinguish capability-limited from cluster-incompatible soft dry-run skips

Both an RBAC Forbidden and a genuinely-missing optional CRD classify as
"soft" (non-blocking, unchanged) -- but they are not the same kind of gap,
and classify_dry_run_error() folded them into one flat string. A human
reading a PR's dry-run notes could not tell "grant AgentIT's SA more
rights and this would apply" from "this cluster does not run that
operator at all, no RBAC change will ever make this sync" -- the real
ambiguity behind a permanently-unsyncable resource reaching a merged
GitOps PR (managed-pinky's real incident: missing Kyverno/Litmus/audit
CRDs needed manual cleanup after merge).

- classify_soft_dry_run_reason(): additive, does not change
  classify_dry_run_error()'s existing "hard"/"soft" contract (many
  callers/tests already assert on those exact two strings) -- further
  splits an already-soft message into capability_limited / cluster_
  incompatible / other.
- label_soft_dry_run_warning(): tags a dry-run warning string with which
  bucket it's in before it reaches skipped/warnings -- wired into both
  places dry_run_manifests_against_cluster() builds a soft-skip warning
  (the exception-catch path and the per-document errors path). Purely a
  labeling change: which files get soft-skipped (dropped from the
  delivery pack) is unchanged.

Tests: tests/test_cluster_dry_run.py (new TestClassifySoftDryRunReason --
capability_limited/cluster_incompatible/other cases, label wording,
original message preserved). Full non-browser suite: 3418 passed, 452
skipped, 0 failed.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(registry): dedupe expected_clear_lines() by category, not per finding

pulse-agent#2, confirmed live: a PR targeting seven separate "container"
findings (:latest across three Dockerfiles, missing HEALTHCHECK, non-UBI
base) printed the identical "Clears \`container\` by pinning..." sentence
seven times in a row. expected_clear_lines() looped once per target
finding, but the emitted line's content depends only on the finding's
category (via contract_for(cat)), never its own description -- every
finding in the same category was always going to produce the exact same
sentence.

Now emits one line per distinct category (order of first appearance
preserved), not one per finding. Purely a PR-body readability fix --
does not change which categories/files are targeted or any gate
decision.

Tests: tests/test_skill_registry_agreement.py (new
TestExpectedClearLinesDeduped), tests/test_quality_prs.py (new
build_helpful_pr_body regression case using pulse-agent#2's real 7
container findings). Full non-browser suite: 3423 passed, 452 skipped,
0 failed.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(copy): stop describing review_final_manifests() as a safety gate

Audited every "LLM" + "safety"/"gate" reference across live code, live
templates, and current (non-historical) docs for the class of
overstatement the underlying function's own docstring already warns
against: "Callers should therefore treat a 'not approved' result as
something to surface clearly... not as a reason to block PR creation."

Two real, live instances found and fixed; everything else already
correctly says "advisory"/"non-blocking"/"human gate remains merge":

- llm.py::review_final_manifests()'s own docstring opened with "Final
  pre-PR quality/safety gate..." -- directly contradicting its own next
  paragraph ("never a reason to block PR creation"). Reworded to
  "quality/completeness opinion... advisory only, never a gate".
- templates/settings.html's "LLM Safety Gate" stat card is a stale
  AutoMode-era label: AutoMode's own LLM classify_action (a genuine
  blocking safety gate) was removed entirely (2026-07-18); the stat it
  labels (llm_available) is just "is an LLM client configured" -- used by
  several mostly-advisory features (secret classification, skill
  generation, the final quality review, capability-scout's evidence
  gate), none of which is "the" safety gate. Relabeled "LLM Availability".

Deliberately left unchanged (already accurate, not the class of bug
this audit was for): quality_prs.py's PR-body "non-blocking — human gate
remains merge" copy; cli.py's "LLM review — first approver gate" for
CLI self-fix's review_fix() specifically, which really is fail-closed
(rejects on low confidence / LLM unavailable) -- an accurate label for a
genuinely different, blocking function, not the portal's advisory
review_final_manifests(); docs/history/*.md's historical/superseded
proposal docs, which document what shipped or was proposed at the time,
not current behavior.

Tests: tests/test_portal.py updated for the corrected settings.html
label. Full non-browser suite: 3422 passed, 452 skipped, 1 pre-existing
flaky error unrelated to this change (test_finding_scoped_verification.py,
passes standalone -- known test-isolation flakiness).

Co-authored-by: Cursor <cursoragent@cursor.com>

* test: real generate-deliver-reassess e2e proof, using pulse-agent's live findings

Every existing test proves staged content / routing / refuse reasons in
isolation -- none of them run the real loop this product's own contract
depends on ("a good AgentIT PR clears a real finding on next re-Assess").
That gap is exactly why the #199-#204 class of bug (a verifier too weak
to notice a "fix" that would not actually clear the finding) was only
ever caught by live dogfooding, one incident at a time, not CI.

tests/test_e2e_finding_clears.py: real temp-directory "repo" + the real
analyzer + skill-generation + enrichment functions; only the GitHub API
boundary (read_file/tree_paths) is backed by that same temp directory
instead of a live network call, so the whole loop runs hermetically and
fast.

- TestReplicasFindingActuallyClearsAfterMerge: pulse-agent's real Helm-
  templated replicas shape -- proves this session's workload-Helm-
  awareness fix closes the loop end-to-end (generate -> enrich -> merge
  -> re-Assess -> finding gone), not just that the enrichment function
  returns the right dict in isolation.
- TestContainerFindingActuallyClearsAfterMerge: pulse-agent's real
  Dockerfile :latest finding, proving the already-working containerfile-
  pin mechanism closes the same loop.
- A genuine, real gap this test-writing effort surfaced and documents
  rather than silently missing: HADRAnalyzer's replicas check is
  repo-wide (any YAML file with replicas>=2 anywhere in the tree), not
  scoped to the real deployed workload -- so even the OLD, un-enriched,
  fully disconnected stub also "clears" the finding dishonestly. Flagged
  as a known limitation (TestReplicasFindingActuallyClearsAfterMerge::
  test_known_gap_the_disconnected_stub_alone_also_fools_the_analyzer),
  not fixed in this pass -- the detection side has the same "is this the
  real workload" blind spot the generation side just had.

sbom (enrich_sbom_from_repo) is a natural next fixture to add here, not
yet built out. Full non-browser suite: 3426 passed, 452 skipped, 0
failed.

Co-authored-by: Cursor <cursoragent@cursor.com>

* docs: record PR-quality root-cause pass in changelog + release-notes

Per this repo's own established convention (README stays a short pitch;
dated writeups go to docs/history/changelog-dogfood-notes.md, which
docs/release-notes.md's current-state summary points readers to) --
records this branch's 8 fixes (Helm-aware workload patches, manual-
Deliver/auto-delivery gate parity, broadened placeholder detection,
resource-collision check, soft dry-run reason labeling, PR-body dedup,
LLM-review copy audit, real generate-deliver-reassess e2e test) and the
live incident that prompted them.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Ali Mobrem <amobrem@example.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant