mapterrain 0.1.0 → 0.1.2

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 CHANGED
@@ -58,6 +58,8 @@ terrain impact "What validations matter for this change?"
58
58
  terrain explain "Why did Terrain make this decision?"
59
59
  ```
60
60
 
61
+ > **About the example outputs below.** The CLI dumps in this section illustrate the *shape* of Terrain's reports on a large pandas-style repository — they are not literal output from a single live run. A few specific signals shown (`xfailAccumulation` age, statistical flaky-test failure rates, the `0.91+` duplicate similarity threshold) are marked `[experimental]` or `[planned]` in 0.1.2; see [docs/release/feature-status.md](docs/release/feature-status.md) for what's stable, what's experimental, and what's planned. The headline "30 seconds" promise refers to small-to-medium repos (≤ 1,000 test files) on commodity hardware; expect 5–15 seconds on a typical service repo and longer on monorepos.
62
+
61
63
  ### 1. Analyze — understand the test system
62
64
 
63
65
  ```bash
@@ -215,6 +217,18 @@ Terrain is framework-agnostic and language-aware. The same analysis model applie
215
217
 
216
218
  The structural model is the same. The signals and recommendations adapt to the framework and test patterns detected.
217
219
 
220
+ ## AI Testing in CI
221
+
222
+ Terrain gives AI components the same CI safety net as regular tests:
223
+
224
+ - **Surface discovery** — automatically detects prompts, contexts, datasets, tool definitions, RAG pipelines, and eval scenarios in your code
225
+ - **Impact-scoped selection** — `terrain ai run --base main` runs only the eval scenarios affected by your change
226
+ - **Protection gaps** — `terrain pr` flags changed AI surfaces that have no eval scenario covering them
227
+ - **Policy enforcement** — block PRs that modify uncovered AI surfaces, regress accuracy, or trigger safety failures
228
+ - **GitHub Action** — drop-in `terrain-ai.yml` workflow template for AI CI gates
229
+
230
+ The same structural graph that powers test selection for regular code also traces AI surface dependencies, so a change to a prompt template triggers the right eval scenarios automatically.
231
+
218
232
  ## How CI Optimization Emerges
219
233
 
220
234
  Terrain does not start with CI optimization. It starts with understanding.
@@ -225,6 +239,7 @@ When you run `terrain analyze`, Terrain builds a structural model: which tests e
225
239
  - **Redundancy reduction** — `terrain insights` surfaces duplicate test clusters. Removing or consolidating them directly reduces CI time without reducing coverage.
226
240
  - **Fanout control** — High-fanout fixtures that trigger thousands of tests on any change are identified and prioritized for splitting.
227
241
  - **Confidence-based runs** — Impact analysis assigns confidence scores. CI pipelines can run high-confidence tests immediately and defer low-confidence tests to nightly runs.
242
+ - **What to test next** — `terrain insights` ranks untested source files by dependency count, telling you which test to write first for maximum impact.
228
243
 
229
244
  The result is faster CI that comes from *understanding the test system*, not from skipping tests and hoping for the best.
230
245
 
@@ -78,6 +78,116 @@ function archiveDownloadUrl(version) {
78
78
  return `${baseUrl}/v${version}/${archiveFileName(version)}`;
79
79
  }
80
80
 
81
+ function signatureDownloadUrl(version) {
82
+ return `${archiveDownloadUrl(version)}.sig`;
83
+ }
84
+
85
+ function certificateDownloadUrl(version) {
86
+ return `${archiveDownloadUrl(version)}.pem`;
87
+ }
88
+
89
+ function expectedSignerIdentity(version) {
90
+ // The keyless Sigstore signature is anchored to the GitHub Actions workflow
91
+ // that ran goreleaser at release time. The workflow runs on the v<version>
92
+ // tag, so the OIDC subject identity is deterministic.
93
+ return (
94
+ `https://github.com/${GITHUB_OWNER}/${GITHUB_REPO}` +
95
+ `/.github/workflows/release.yml@refs/tags/v${version}`
96
+ );
97
+ }
98
+
99
+ const SIGSTORE_OIDC_ISSUER = 'https://token.actions.githubusercontent.com';
100
+
101
+ function isCosignAvailable() {
102
+ try {
103
+ execFileSync('cosign', ['version'], { stdio: 'pipe' });
104
+ return true;
105
+ } catch {
106
+ return false;
107
+ }
108
+ }
109
+
110
+ // Best-effort signature verification. In 0.1.2 this is warn-only: a missing
111
+ // cosign, missing signature artifact, or verification failure logs to stderr
112
+ // and does NOT block install. The signing pipeline is still maturing and we
113
+ // don't want to break npm installs while it stabilises.
114
+ //
115
+ // In 0.2 this becomes hard-fail unless TERRAIN_INSTALLER_SKIP_VERIFY=1 is set,
116
+ // at which point the warning escalates to an error.
117
+ async function verifySignatureBestEffort({
118
+ archivePath,
119
+ version,
120
+ tempDir,
121
+ quiet,
122
+ env,
123
+ }) {
124
+ if (env.TERRAIN_INSTALLER_SKIP_VERIFY === '1') {
125
+ log(
126
+ 'Skipping signature verification (TERRAIN_INSTALLER_SKIP_VERIFY=1).',
127
+ quiet
128
+ );
129
+ return { verified: false, reason: 'skipped-by-env' };
130
+ }
131
+
132
+ if (!isCosignAvailable()) {
133
+ log(
134
+ 'cosign not found on PATH; skipping signature verification. ' +
135
+ 'Install cosign (https://github.com/sigstore/cosign) for stronger ' +
136
+ 'integrity guarantees in future releases.',
137
+ quiet
138
+ );
139
+ return { verified: false, reason: 'cosign-missing' };
140
+ }
141
+
142
+ const sigPath = path.join(tempDir, `${path.basename(archivePath)}.sig`);
143
+ const certPath = path.join(tempDir, `${path.basename(archivePath)}.pem`);
144
+
145
+ try {
146
+ await downloadFile(signatureDownloadUrl(version), sigPath);
147
+ await downloadFile(certificateDownloadUrl(version), certPath);
148
+ } catch (error) {
149
+ log(
150
+ `Could not fetch signature artifacts (${error.message}); ` +
151
+ 'skipping verification.',
152
+ quiet
153
+ );
154
+ return { verified: false, reason: 'sig-download-failed' };
155
+ }
156
+
157
+ try {
158
+ execFileSync(
159
+ 'cosign',
160
+ [
161
+ 'verify-blob',
162
+ '--certificate',
163
+ certPath,
164
+ '--signature',
165
+ sigPath,
166
+ '--certificate-identity',
167
+ expectedSignerIdentity(version),
168
+ '--certificate-oidc-issuer',
169
+ SIGSTORE_OIDC_ISSUER,
170
+ archivePath,
171
+ ],
172
+ { stdio: 'pipe' }
173
+ );
174
+ log(
175
+ `Verified Sigstore signature for ${path.basename(archivePath)}.`,
176
+ quiet
177
+ );
178
+ return { verified: true, reason: 'ok' };
179
+ } catch (error) {
180
+ log(
181
+ `WARNING: cosign verify-blob failed for ${path.basename(archivePath)}. ` +
182
+ 'The downloaded archive may be tampered with. Continuing install ' +
183
+ '(verification will become mandatory in 0.2). Error: ' +
184
+ (error.stderr ? error.stderr.toString().trim() : error.message),
185
+ quiet
186
+ );
187
+ return { verified: false, reason: 'verify-failed' };
188
+ }
189
+ }
190
+
81
191
  async function ensureDirectory(dir) {
82
192
  await fs.mkdir(dir, { recursive: true });
83
193
  }
@@ -233,6 +343,13 @@ export async function ensureTerrainBinary({
233
343
  quiet
234
344
  );
235
345
  await downloadFile(archiveDownloadUrl(version), archivePath);
346
+ await verifySignatureBestEffort({
347
+ archivePath,
348
+ version,
349
+ tempDir,
350
+ quiet,
351
+ env,
352
+ });
236
353
  await ensureDirectory(extractDir);
237
354
  extractArchive(archivePath, extractDir);
238
355
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mapterrain",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Terrain test intelligence CLI.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -81,4 +81,4 @@
81
81
  "node": ">=22.0.0"
82
82
  },
83
83
  "sideEffects": false
84
- }
84
+ }