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 +15 -0
- package/bin/terrain-installer.js +117 -0
- package/package.json +2 -2
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
|
|
package/bin/terrain-installer.js
CHANGED
|
@@ -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