infraweaver 0.3.7 → 0.3.9
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.
- package/README.md +2 -2
- package/dist/agents/claude.d.ts +1 -1
- package/dist/agents/claudePretoolGate.d.ts +0 -36
- package/dist/agents/toolPlaneWatch.d.ts +31 -0
- package/dist/cli.mjs +31908 -18736
- package/dist/external.d.ts +2 -1
- package/dist/index.js +31992 -18749
- package/dist/internal.js +133 -52
- package/dist/mcp/baseBranch.d.ts +41 -0
- package/dist/mcp/changeSummary.d.ts +1 -1
- package/dist/mcp/findingAudit.d.ts +2 -0
- package/dist/mcp/guardrails.d.ts +38 -0
- package/dist/mcp/localContext.d.ts +1 -1
- package/dist/mcp/moduleExtraction.d.ts +5 -0
- package/dist/mcp/pr.d.ts +0 -31
- package/dist/mcp/reproveSuggestions.d.ts +27 -0
- package/dist/mcp/review.d.ts +126 -2
- package/dist/mcp/reviewCommentLimit.d.ts +17 -0
- package/dist/mcp/reviewComments.d.ts +9 -5
- package/dist/mcp/reviewDedup.d.ts +1 -0
- package/dist/mcp/reviewMarkers.d.ts +10 -0
- package/dist/mcp/reviewProvenance.d.ts +20 -6
- package/dist/mcp/shared.d.ts +15 -0
- package/dist/mcp/suggestionProver.d.ts +3 -2
- package/dist/mcp/terraform/checkovSeverities.d.ts +7 -0
- package/dist/mcp/terraform/consolidation.d.ts +26 -0
- package/dist/mcp/terraform/cost.d.ts +2 -2
- package/dist/mcp/terraform/currency.d.ts +11 -0
- package/dist/mcp/terraform/deltaSummary.d.ts +11 -1
- package/dist/mcp/terraform/emittedArtefacts.d.ts +11 -0
- package/dist/mcp/terraform/evidence.d.ts +10 -0
- package/dist/mcp/terraform/gateScan.d.ts +2 -16
- package/dist/mcp/terraform/hcl.d.ts +15 -0
- package/dist/mcp/terraform/initArtefacts.d.ts +21 -0
- package/dist/mcp/terraform/localModules.d.ts +29 -0
- package/dist/mcp/terraform/moduleFindings.d.ts +38 -0
- package/dist/mcp/terraform/refactor/attribution.d.ts +1 -1
- package/dist/mcp/terraform/refactor/equivalence.d.ts +7 -0
- package/dist/mcp/terraform/refactor/expressions.d.ts +62 -0
- package/dist/mcp/terraform/refactor/references.d.ts +15 -0
- package/dist/mcp/terraform/refactor/resources.d.ts +11 -0
- package/dist/mcp/terraform/refactor/substitution.d.ts +55 -37
- package/dist/mcp/terraform/scanDelta.d.ts +28 -0
- package/dist/mcp/terraform/scanSession.d.ts +13 -0
- package/dist/mcp/terraform/scanners.d.ts +67 -18
- package/dist/mcp/terraform/secretScan.d.ts +19 -0
- package/dist/mcp/terraform/standardsReport.d.ts +1 -1
- package/dist/mcp/terraform/tools/plan.d.ts +1 -1
- package/dist/mcp/terraform/tools/scan.d.ts +7 -7
- package/dist/mcp/terraform/tools/validate.d.ts +7 -2
- package/dist/mcp/terraform/tools/verifyRemediation.d.ts +1 -1
- package/dist/mcp/terraform/treeLayout.d.ts +57 -0
- package/dist/mcp/terraform/types.d.ts +1 -1
- package/dist/phases/applyPlan.d.ts +0 -2
- package/dist/toolState.d.ts +3 -0
- package/dist/utils/agent/codexOAuth.d.ts +1 -1
- package/dist/utils/cli.d.ts +1 -7
- package/dist/utils/cloud/cloudReport.d.ts +1 -1
- package/dist/utils/config/baseRefConfig.d.ts +1 -1
- package/dist/utils/config/infraweaverConfig.d.ts +1 -1
- package/dist/utils/config/payload.d.ts +5 -0
- package/dist/utils/markdownTable.d.ts +6 -0
- package/dist/utils/setup/toolLicensing.d.ts +1 -1
- package/dist/utils/unifiedDiff.d.ts +69 -0
- package/package.json +34 -28
- package/src/agents/claude.ts +48 -13
- package/src/agents/claudePretoolGate.ts +9 -17
- package/src/agents/sessionLabeler.ts +3 -3
- package/src/agents/toolPlaneWatch.ts +71 -0
- package/src/external.ts +3 -1
- package/src/mcp/assess.ts +8 -8
- package/src/mcp/baseBranch.ts +96 -0
- package/src/mcp/changeSummary.ts +10 -11
- package/src/mcp/checkSuite.ts +7 -10
- package/src/mcp/checkout.ts +13 -40
- package/src/mcp/crosswalk.ts +5 -0
- package/src/mcp/findingAudit.ts +1 -1
- package/src/mcp/git.ts +44 -2
- package/src/mcp/guardrails.ts +188 -36
- package/src/mcp/localContext.ts +2 -0
- package/src/mcp/localServer.ts +1 -0
- package/src/mcp/moduleExtraction.ts +17 -5
- package/src/mcp/moduleTests.ts +9 -2
- package/src/mcp/modules.ts +10 -7
- package/src/mcp/pr.ts +17 -65
- package/src/mcp/reproveSuggestions.ts +86 -0
- package/src/mcp/review.ts +249 -75
- package/src/mcp/reviewAnchor.ts +11 -23
- package/src/mcp/reviewCommentLimit.ts +79 -0
- package/src/mcp/reviewComments.ts +34 -81
- package/src/mcp/reviewDedup.ts +1 -1
- package/src/mcp/reviewMarkers.ts +25 -2
- package/src/mcp/reviewProvenance.ts +56 -21
- package/src/mcp/roots.ts +3 -2
- package/src/mcp/server.ts +2 -1
- package/src/mcp/shared.ts +40 -1
- package/src/mcp/shell.ts +27 -28
- package/src/mcp/suggestionProver.ts +8 -4
- package/src/mcp/terraform/checkovSeverities.ts +1156 -0
- package/src/mcp/terraform/concernResult.ts +2 -1
- package/src/mcp/terraform/consolidation.ts +232 -19
- package/src/mcp/terraform/cost.ts +16 -7
- package/src/mcp/terraform/cspm.ts +8 -4
- package/src/mcp/terraform/currency.ts +28 -12
- package/src/mcp/terraform/decisions.ts +1 -1
- package/src/mcp/terraform/deltaSummary.ts +50 -1
- package/src/mcp/terraform/emittedArtefacts.ts +63 -0
- package/src/mcp/terraform/evidence.ts +23 -3
- package/src/mcp/terraform/gateScan.ts +191 -128
- package/src/mcp/terraform/hcl.ts +27 -2
- package/src/mcp/terraform/idleFloor.ts +2 -1
- package/src/mcp/terraform/initArtefacts.ts +76 -0
- package/src/mcp/terraform/localModules.ts +112 -0
- package/src/mcp/terraform/moduleDocs.ts +2 -5
- package/src/mcp/terraform/moduleFindings.ts +102 -0
- package/src/mcp/terraform/moduleVersionConstraints.ts +1 -1
- package/src/mcp/terraform/planPairs.ts +2 -20
- package/src/mcp/terraform/policyGate.ts +0 -15
- package/src/mcp/terraform/refactor/attribution.ts +39 -5
- package/src/mcp/terraform/refactor/equivalence.ts +49 -10
- package/src/mcp/terraform/refactor/expressions.ts +323 -0
- package/src/mcp/terraform/refactor/references.ts +101 -0
- package/src/mcp/terraform/refactor/resources.ts +79 -7
- package/src/mcp/terraform/refactor/substitution.ts +852 -256
- package/src/mcp/terraform/scanDelta.ts +41 -0
- package/src/mcp/terraform/scanSession.ts +30 -0
- package/src/mcp/terraform/scannerCache.ts +4 -0
- package/src/mcp/terraform/scanners.ts +553 -233
- package/src/mcp/terraform/secretScan.ts +131 -0
- package/src/mcp/terraform/standardsReport.ts +14 -13
- package/src/mcp/terraform/tools/consolidationCandidates.ts +3 -0
- package/src/mcp/terraform/tools/emitEvidence.ts +13 -5
- package/src/mcp/terraform/tools/emitOscal.ts +3 -2
- package/src/mcp/terraform/tools/emitVex.ts +10 -4
- package/src/mcp/terraform/tools/equivalenceCheck.ts +139 -21
- package/src/mcp/terraform/tools/infracostDiff.ts +2 -2
- package/src/mcp/terraform/tools/plan.ts +30 -17
- package/src/mcp/terraform/tools/reviewScanDelta.ts +3 -0
- package/src/mcp/terraform/tools/scan.ts +5 -3
- package/src/mcp/terraform/tools/standardsReport.ts +1 -1
- package/src/mcp/terraform/tools/validate.ts +20 -5
- package/src/mcp/terraform/tools/verifyRemediation.ts +14 -7
- package/src/mcp/terraform/tools/versionCurrency.ts +3 -1
- package/src/mcp/terraform/treeLayout.ts +378 -0
- package/src/mcp/terraform/types.ts +4 -1
- package/src/mcp/terraform/versionRequirements.ts +11 -2
- package/src/modes/__snapshots__/assemblePrompt.test.ts.snap +2 -0
- package/src/modes/modernize-deprecated.ts +2 -2
- package/src/modes/refactor.ts +9 -9
- package/src/modes/remediate.ts +1 -1
- package/src/modes/terraform-code-review.ts +7 -3
- package/src/modes/update-dependencies.ts +6 -6
- package/src/phases/applyPlan.ts +33 -14
- package/src/phases/applySuggestions.ts +2 -2
- package/src/phases/runAgentWithWatchdogs.ts +2 -1
- package/src/toolState.ts +7 -0
- package/src/utils/agent/codexOAuth.ts +7 -7
- package/src/utils/agent/openCodeModels.ts +4 -6
- package/src/utils/cli.ts +1 -31
- package/src/utils/cloud/runContext.ts +1 -8
- package/src/utils/config/baseRefConfig.ts +6 -0
- package/src/utils/config/infraweaverConfig.ts +2 -0
- package/src/utils/config/payload.ts +41 -0
- package/src/utils/github/assets.ts +1 -6
- package/src/utils/github/github.ts +42 -150
- package/src/utils/github/token.ts +6 -11
- package/src/utils/log.ts +4 -5
- package/src/utils/markdownTable.ts +11 -0
- package/src/utils/prompt/changeImpact.ts +18 -26
- package/src/utils/prompt/instructions.ts +1 -1
- package/src/utils/prompt/promptDirectives.ts +1 -1
- package/src/utils/prompt/toolSelection.ts +11 -2
- package/src/utils/setup/toolLicensing.ts +7 -0
- package/src/utils/telemetry.ts +2 -1
- package/src/utils/unifiedDiff.ts +120 -0
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
import { analysePlanPairs, type PairAnalysis } from "#app/mcp/terraform/planPairs";
|
|
20
20
|
import { readWorkingTree, workingTreeFingerprint } from "#app/mcp/terraform/refactor/workspace";
|
|
21
21
|
import { scanSessionOf } from "#app/mcp/terraform/scanSession";
|
|
22
|
-
import {
|
|
22
|
+
import { runTfAsync } from "#app/mcp/terraform/subprocess";
|
|
23
23
|
import { skipResult } from "#app/mcp/terraform/types";
|
|
24
24
|
import { log } from "#app/utils/cli";
|
|
25
25
|
import { resolveModuleCredentialEnv } from "#app/utils/setup/registryAuth";
|
|
@@ -66,13 +66,13 @@ interface RootPlanOutcome {
|
|
|
66
66
|
* throws on that deliberately) yields no pairs, and the proof degrades to the
|
|
67
67
|
* keys-free floor it always had.
|
|
68
68
|
*/
|
|
69
|
-
function capturePlanPairs(
|
|
69
|
+
async function capturePlanPairs(
|
|
70
70
|
absDir: string,
|
|
71
71
|
planFile: string,
|
|
72
72
|
creds: Record<string, string>,
|
|
73
|
-
): PairAnalysis | undefined {
|
|
73
|
+
): Promise<PairAnalysis | undefined> {
|
|
74
74
|
try {
|
|
75
|
-
const shown =
|
|
75
|
+
const shown = await runTfAsync(["show", "-json", planFile], absDir, creds);
|
|
76
76
|
if (shown.status !== 0 || !shown.stdout.trim()) return undefined;
|
|
77
77
|
return analysePlanPairs(parseTerraformPlanFile(shown.stdout).resourceChanges);
|
|
78
78
|
} catch {
|
|
@@ -85,14 +85,14 @@ function capturePlanPairs(
|
|
|
85
85
|
|
|
86
86
|
/** init + plan (+ a stability re-plan + a human-readable plan) in a SINGLE root.
|
|
87
87
|
* Used by terraform_plan once per discovered root. */
|
|
88
|
-
function planOneRoot(
|
|
88
|
+
async function planOneRoot(
|
|
89
89
|
absDir: string,
|
|
90
90
|
creds: Record<string, string>,
|
|
91
91
|
tmpdir?: string,
|
|
92
|
-
): RootPlanOutcome {
|
|
92
|
+
): Promise<RootPlanOutcome> {
|
|
93
93
|
// `terraform` when installed, else `tofu` (identical plan CLI, resolved by
|
|
94
|
-
//
|
|
95
|
-
const init =
|
|
94
|
+
// runTfAsync) — an OpenTofu-only runner keeps the plan gate instead of skipping it.
|
|
95
|
+
const init = await runTfAsync(["init", "-input=false", "-no-color"], absDir, creds);
|
|
96
96
|
if (init.missing) return { ran: false, skipReason: "terraform (or tofu) not installed" };
|
|
97
97
|
if (init.status !== 0) {
|
|
98
98
|
return {
|
|
@@ -106,7 +106,7 @@ function planOneRoot(
|
|
|
106
106
|
// and a stray one left by a crash would land in the customer's diff. Absent a
|
|
107
107
|
// tmpdir (a caller that did not supply one) the pairing is simply skipped.
|
|
108
108
|
const planFile = tmpdir ? join(tmpdir, `plan-${process.pid}-${planFileSeq++}.tfplan`) : undefined;
|
|
109
|
-
const plan =
|
|
109
|
+
const plan = await runTfAsync(
|
|
110
110
|
planFile ? [...PLAN_JSON_ARGS, `-out=${planFile}`] : PLAN_JSON_ARGS,
|
|
111
111
|
absDir,
|
|
112
112
|
creds,
|
|
@@ -118,7 +118,7 @@ function planOneRoot(
|
|
|
118
118
|
skipReason: `terraform plan failed: ${plan.stderr.trim().slice(0, 200) || "unknown error"}`,
|
|
119
119
|
};
|
|
120
120
|
}
|
|
121
|
-
const pairs = planFile ? capturePlanPairs(absDir, planFile, creds) : undefined;
|
|
121
|
+
const pairs = planFile ? await capturePlanPairs(absDir, planFile, creds) : undefined;
|
|
122
122
|
const summary = parseTerraformPlanJson(plan.stdout);
|
|
123
123
|
const hasChanges =
|
|
124
124
|
summary.add + summary.change + summary.destroy > 0 || summary.changed.length > 0;
|
|
@@ -127,7 +127,7 @@ function planOneRoot(
|
|
|
127
127
|
let stable = true;
|
|
128
128
|
let stabilityReason: string | undefined;
|
|
129
129
|
if (hasChanges) {
|
|
130
|
-
const plan2 =
|
|
130
|
+
const plan2 = await runTfAsync(PLAN_JSON_ARGS, absDir, creds);
|
|
131
131
|
if (plan2.status === 0) {
|
|
132
132
|
const s = comparePlanStability(summary, parseTerraformPlanJson(plan2.stdout));
|
|
133
133
|
stable = s.stable;
|
|
@@ -137,7 +137,11 @@ function planOneRoot(
|
|
|
137
137
|
// human-readable plan for the PR <details> block (separate non-json run).
|
|
138
138
|
let planText: string | undefined;
|
|
139
139
|
if (hasChanges) {
|
|
140
|
-
const readable =
|
|
140
|
+
const readable = await runTfAsync(
|
|
141
|
+
["plan", "-input=false", "-no-color", "-lock=false"],
|
|
142
|
+
absDir,
|
|
143
|
+
creds,
|
|
144
|
+
);
|
|
141
145
|
if (readable.status === 0 && readable.stdout.trim())
|
|
142
146
|
planText = readable.stdout.trim().slice(0, 12_000);
|
|
143
147
|
}
|
|
@@ -220,7 +224,11 @@ const TerraformPlanParams = type({});
|
|
|
220
224
|
export interface PlanRunners {
|
|
221
225
|
/** `tmpdir` is where the saved plan file goes — outside the repository,
|
|
222
226
|
* because it holds attribute values. */
|
|
223
|
-
planOneRoot: (
|
|
227
|
+
planOneRoot: (
|
|
228
|
+
absDir: string,
|
|
229
|
+
creds: Record<string, string>,
|
|
230
|
+
tmpdir?: string,
|
|
231
|
+
) => Promise<RootPlanOutcome>;
|
|
224
232
|
hasCloudCredentials: () => boolean;
|
|
225
233
|
}
|
|
226
234
|
|
|
@@ -250,10 +258,15 @@ export async function runPlan(ctx: LocalToolContext, runners: PlanRunners = real
|
|
|
250
258
|
// aggregate. resolveRoots falls back to [cwd] for a single-root repo, so
|
|
251
259
|
// behaviour there is identical to before.
|
|
252
260
|
const roots = resolveRoots(cwd);
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
261
|
+
// one root at a time: each init writes its root's `.terraform`, and the
|
|
262
|
+
// providers they download share the same plugin directories.
|
|
263
|
+
const perRoot: { dir: string; outcome: RootPlanOutcome }[] = [];
|
|
264
|
+
for (const r of roots) {
|
|
265
|
+
perRoot.push({
|
|
266
|
+
dir: r.relDir || ".",
|
|
267
|
+
outcome: await runners.planOneRoot(r.absDir, creds, ctx.tmpdir),
|
|
268
|
+
});
|
|
269
|
+
}
|
|
257
270
|
const ran = perRoot.filter((p) => p.outcome.ran);
|
|
258
271
|
if (ran.length === 0) {
|
|
259
272
|
// every root skipped — surface the first reason (e.g. not installed / init failed).
|
|
@@ -69,6 +69,9 @@ function describe(f: DeltaFinding) {
|
|
|
69
69
|
? {
|
|
70
70
|
possibly_preexisting:
|
|
71
71
|
"this resource appeared as another with the same finding vanished — likely a rename without a `moved` block",
|
|
72
|
+
...(f.renamedFrom
|
|
73
|
+
? { renamed_from: { resource: f.renamedFrom.address, file: f.renamedFrom.file } }
|
|
74
|
+
: {}),
|
|
72
75
|
}
|
|
73
76
|
: {}),
|
|
74
77
|
evidence: c.evidence,
|
|
@@ -19,7 +19,7 @@ import { EMPTY_NOISE_PROFILE, explainNoiseAdjustment } from "#app/mcp/terraform/
|
|
|
19
19
|
import { scoreConcern, topRisks } from "#app/mcp/terraform/risk";
|
|
20
20
|
import { runScannersCached } from "#app/mcp/terraform/scannerCache";
|
|
21
21
|
import { changedTerraformFiles } from "#app/mcp/terraform/scanners";
|
|
22
|
-
import { scanSessionOf } from "#app/mcp/terraform/scanSession";
|
|
22
|
+
import { recordScanConcerns, scanSessionOf } from "#app/mcp/terraform/scanSession";
|
|
23
23
|
import { auditNativeSkips } from "#app/mcp/terraform/suppressions";
|
|
24
24
|
import { type Concern, SEVERITY_RANK, type Severity } from "#app/mcp/terraform/types";
|
|
25
25
|
import { UNTRUSTED_DATA_NOTICE } from "#app/mcp/untrustedContent";
|
|
@@ -38,7 +38,7 @@ const TerraformScanParams = type({
|
|
|
38
38
|
"'file' (default) makes one group per file (smaller blast radius per PR). 'rule' groups a single rule's concerns across ALL files into one group — use for sweeping, low-risk rules (e.g. 'add tags everywhere') so they become one PR instead of many.",
|
|
39
39
|
),
|
|
40
40
|
"download_external_modules?": type("boolean").describe(
|
|
41
|
-
"default false. When true, checkov resolves and scans INSIDE registry/git modules — REQUIRED for the refactor b.4 path (adopting a third-party module): scanning with it OFF would stop looking inside the adopted module and silently drop its findings (a false ✗→✓ / 'finding-laundering'). Leave off for normal Terraform-only scans (faster, offline).",
|
|
41
|
+
"default false. When true, checkov resolves and scans INSIDE registry/git modules — REQUIRED for the refactor b.4 path (adopting a third-party module): scanning with it OFF would stop looking inside the adopted module and silently drop its findings (a false ✗→✓ / 'finding-laundering'). A finding inside a module is reported on the line of the `module` block that calls it, naming the module resource. Leave off for normal Terraform-only scans (faster, offline).",
|
|
42
42
|
),
|
|
43
43
|
});
|
|
44
44
|
|
|
@@ -99,6 +99,8 @@ export async function runScan(
|
|
|
99
99
|
// the SAME threshold everywhere a scan happens. A verifier running on a
|
|
100
100
|
// different one would report a finding resolved because the knob moved.
|
|
101
101
|
repeatedLiteralThreshold: ctx.payload.repeatedLiteralThreshold,
|
|
102
|
+
// and the same variable files, which decide what trivy and checkov see
|
|
103
|
+
varFiles: ctx.payload.varFiles,
|
|
102
104
|
});
|
|
103
105
|
if (ctx.payload.toolsNetwork?.unknown.length) {
|
|
104
106
|
log.warning(
|
|
@@ -152,7 +154,7 @@ export async function runScan(
|
|
|
152
154
|
const reintroduced = findReintroduced(active, resolved);
|
|
153
155
|
// the reported (post-filter) set — read at end-of-run by finalizeSuccessRun
|
|
154
156
|
// to emit the SARIF artifact + findings outputs.
|
|
155
|
-
scanSessionOf(ctx.toolState)
|
|
157
|
+
recordScanConcerns(scanSessionOf(ctx.toolState), result.annotated);
|
|
156
158
|
// G7: the org's fleet-wide dismissal record, seeded at run start. Affects the
|
|
157
159
|
// ORDER of `top_risks` / `risk_score` only — every concern above is already
|
|
158
160
|
// final by this point, and nothing here can remove one.
|
|
@@ -55,7 +55,7 @@ export function TerraformStandardsReportTool(ctx: LocalToolContext) {
|
|
|
55
55
|
note: "no `.tf` files found in the workspace — nothing to report on",
|
|
56
56
|
});
|
|
57
57
|
}
|
|
58
|
-
const report = standardsReport(cwd);
|
|
58
|
+
const report = await standardsReport(cwd);
|
|
59
59
|
scanSessionOf(ctx.toolState).standardsReport = report;
|
|
60
60
|
log.info(
|
|
61
61
|
`» terraform_standards_report: ${report.summary.violations} violation(s), ` +
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { type } from "arktype";
|
|
2
2
|
import type { LocalToolContext } from "#app/mcp/localContext";
|
|
3
3
|
import { execute, tool, toolOk } from "#app/mcp/shared";
|
|
4
|
+
import { initArtefacts, removeNewInitArtefacts } from "#app/mcp/terraform/initArtefacts";
|
|
4
5
|
import { resolveRoots } from "#app/mcp/terraform/paths";
|
|
5
6
|
import {
|
|
6
7
|
checkArgumentsAgainstSchema,
|
|
7
8
|
collectProviderRequirements,
|
|
9
|
+
initFailures,
|
|
8
10
|
scanFmt,
|
|
9
11
|
scanTflint,
|
|
10
12
|
scanValidate,
|
|
@@ -29,6 +31,7 @@ export interface ValidateScanners {
|
|
|
29
31
|
scanTflint: typeof scanTflint;
|
|
30
32
|
collectProviderRequirements: typeof collectProviderRequirements;
|
|
31
33
|
checkArgumentsAgainstSchema: typeof checkArgumentsAgainstSchema;
|
|
34
|
+
initFailures: typeof initFailures;
|
|
32
35
|
}
|
|
33
36
|
|
|
34
37
|
const realScanners: ValidateScanners = {
|
|
@@ -37,6 +40,7 @@ const realScanners: ValidateScanners = {
|
|
|
37
40
|
scanTflint,
|
|
38
41
|
collectProviderRequirements,
|
|
39
42
|
checkArgumentsAgainstSchema,
|
|
43
|
+
initFailures,
|
|
40
44
|
};
|
|
41
45
|
|
|
42
46
|
export async function runValidate(
|
|
@@ -53,14 +57,15 @@ export async function runValidate(
|
|
|
53
57
|
});
|
|
54
58
|
// `terraform validate` runs per-root (multi-root aware); fmt + tflint are
|
|
55
59
|
// recursive over the whole tree.
|
|
56
|
-
const
|
|
57
|
-
const
|
|
60
|
+
const artefactsBefore = initArtefacts(cwd);
|
|
61
|
+
const fmtOutcome = await scanners.scanFmt(cwd);
|
|
62
|
+
const validateOutcome = await scanners.scanValidate(cwd, terraformEnv);
|
|
58
63
|
// the SAME egress directive as terraform_scan: without it, a validate call
|
|
59
64
|
// would spawn tflint (and its network-fetching `--init`, memoised per dir)
|
|
60
65
|
// with open egress under `tools_network: deny` — and whichever call inits
|
|
61
66
|
// first wins for the rest of the process.
|
|
62
67
|
const tflintOutcome = selection.enabled("tflint")
|
|
63
|
-
? scanners.scanTflint(cwd, ctx.payload.toolsNetwork)
|
|
68
|
+
? await scanners.scanTflint(cwd, ctx.payload.toolsNetwork)
|
|
64
69
|
: skipped("tflint", selection.offReason("tflint") ?? "disabled by tools_enabled");
|
|
65
70
|
|
|
66
71
|
// PRE-EXISTING `terraform fmt` drift (captured by the pre-fix scan, before
|
|
@@ -89,6 +94,10 @@ export async function runValidate(
|
|
|
89
94
|
// those roots are valid, so `passed` must fail closed below rather than
|
|
90
95
|
// silently treating an un-validated root as passing.
|
|
91
96
|
const unvalidatedRoots = validateOutcome.unvalidated ?? 0;
|
|
97
|
+
// roots whose init failed validated without their modules and providers, so
|
|
98
|
+
// a clean list there checked almost nothing — a module bump the provider pin
|
|
99
|
+
// cannot satisfy reads exactly like a pass unless this is said
|
|
100
|
+
const initFailed = scanners.initFailures(cwd);
|
|
92
101
|
// surface the pinned provider majors so the fix targets the right
|
|
93
102
|
// argument schema (deterministic, read straight from required_providers).
|
|
94
103
|
const providers = scanners.collectProviderRequirements(cwd);
|
|
@@ -100,6 +109,8 @@ export async function runValidate(
|
|
|
100
109
|
// Deterministic and degrades green (omitted) when the schema isn't
|
|
101
110
|
// available (terraform not installed / dir not init-ed).
|
|
102
111
|
const schemaCheck = scanners.checkArgumentsAgainstSchema(cwd);
|
|
112
|
+
// the schema was read from what init installed; left behind, it blocks the push
|
|
113
|
+
removeNewInitArtefacts(cwd, artefactsBefore, "terraform_validate");
|
|
103
114
|
return toolOk({
|
|
104
115
|
// `passed` stays gated on fmt + validate + tflint only — those are
|
|
105
116
|
// authoritative (fmt counts only NEW drift; see preexisting_fmt_drift).
|
|
@@ -107,13 +118,17 @@ export async function runValidate(
|
|
|
107
118
|
// parse), surfaced separately so a parser edge case can never wrongly
|
|
108
119
|
// block a valid fix. Fails closed when a root ran but couldn't be
|
|
109
120
|
// validated: an un-validated root is "unknown", not "clean".
|
|
110
|
-
passed: remaining.length === 0 && unvalidatedRoots === 0,
|
|
121
|
+
passed: remaining.length === 0 && unvalidatedRoots === 0 && initFailed.length === 0,
|
|
111
122
|
checks_ran: ran,
|
|
112
123
|
// true when ≥1 Terraform root could not be validated (terraform ran but
|
|
113
124
|
// its `-json` output was unparseable). When set, `passed` is false even
|
|
114
125
|
// with no remaining_issues — re-run after fixing the root's init/state,
|
|
115
126
|
// or inspect it by hand; do NOT treat the clean issue list as a pass.
|
|
116
|
-
validate_incomplete: unvalidatedRoots > 0,
|
|
127
|
+
validate_incomplete: unvalidatedRoots > 0 || initFailed.length > 0,
|
|
128
|
+
// the roots whose `terraform init` failed, with init's error — fix what it
|
|
129
|
+
// names (often a version constraint the change made unsatisfiable) before
|
|
130
|
+
// anything else; their validate result means nothing until then.
|
|
131
|
+
init_failed: initFailed,
|
|
117
132
|
remaining_issues: remaining,
|
|
118
133
|
// ADVISORY (does not gate `passed`): files that already failed
|
|
119
134
|
// `terraform fmt` at the pre-fix baseline. Reformatting them would pull
|
|
@@ -55,6 +55,8 @@ export async function runVerifyRemediation(
|
|
|
55
55
|
// the SAME threshold everywhere a scan happens. A verifier running on a
|
|
56
56
|
// different one would report a finding resolved because the knob moved.
|
|
57
57
|
repeatedLiteralThreshold: ctx.payload.repeatedLiteralThreshold,
|
|
58
|
+
// and the same variable files, which decide what trivy and checkov see
|
|
59
|
+
varFiles: ctx.payload.varFiles,
|
|
58
60
|
});
|
|
59
61
|
const currentConcerns = dedupe(outcomes.flatMap((o) => o.concerns));
|
|
60
62
|
const currentIds = currentConcerns.map((c) => c.id);
|
|
@@ -63,12 +65,16 @@ export async function runVerifyRemediation(
|
|
|
63
65
|
// concern look resolved nor a pre-existing one look like a regression.
|
|
64
66
|
const currentKeys = new Set(currentConcerns.map((c) => concernKeyOf(c)));
|
|
65
67
|
|
|
66
|
-
// Map each requested id → its key via
|
|
67
|
-
//
|
|
68
|
-
//
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
68
|
+
// Map each requested id → its key via EVERY scan this run, not only the
|
|
69
|
+
// latest: an agent that re-scans after its edit still verifies the ids it
|
|
70
|
+
// took from the first scan, and those are line-pinned. Ids no scan this run
|
|
71
|
+
// reported (verify called without one — e.g. a bare findings.json) fall back
|
|
72
|
+
// to exact-id matching.
|
|
73
|
+
const reported = new Map([
|
|
74
|
+
...(session.concernsById ?? []),
|
|
75
|
+
...(session.lastScanConcerns ?? []).map((c) => [c.id, c] as const),
|
|
76
|
+
]);
|
|
77
|
+
const keyById = new Map([...reported].map(([id, c]) => [id, concernKeyOf(c)] as const));
|
|
72
78
|
const keyed: { id: string; key: string }[] = [];
|
|
73
79
|
const unkeyed: string[] = [];
|
|
74
80
|
for (const id of concern_ids) {
|
|
@@ -116,7 +122,8 @@ export async function runVerifyRemediation(
|
|
|
116
122
|
resolved.length > 0
|
|
117
123
|
) {
|
|
118
124
|
const resolvedSet = new Set(resolved);
|
|
119
|
-
|
|
125
|
+
// from every scan: after a re-scan the resolved ids are the FIRST scan's
|
|
126
|
+
const captures = [...reported.values()]
|
|
120
127
|
.filter((c) => resolvedSet.has(c.id))
|
|
121
128
|
.map(captureProvenFix);
|
|
122
129
|
if (captures.length > 0) {
|
|
@@ -15,7 +15,9 @@ export function TerraformVersionCurrencyTool(ctx: LocalToolContext) {
|
|
|
15
15
|
"Check the workspace's pinned providers and registry modules against the Terraform Registry's " +
|
|
16
16
|
"published versions — the upgrade intelligence no scanner provides (tflint checks pins EXIST, not " +
|
|
17
17
|
"that they're current). Reports per provider/module the written constraint, the `latest` stable " +
|
|
18
|
-
"version, the newest version the constraint admits,
|
|
18
|
+
"version, the newest version the constraint admits, `latest_in_major` (the newest release in the " +
|
|
19
|
+
"major the constraint already sits on — the bump that crosses no major, offered even when `latest` " +
|
|
20
|
+
"is majors ahead), and `outdated`/`majors_behind`; registry " +
|
|
19
21
|
"modules with no version pin are flagged `unpinned` (pin them to `latest`). Remediation contract: " +
|
|
20
22
|
"one `chore(deps)` PR per upgrade group; minor/patch bumps may proceed autonomously; a MAJOR " +
|
|
21
23
|
"bump means the module/provider interface may have changed — apply it only with a `needs-human` " +
|