mapterrain 0.1.1 → 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
@@ -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.1",
3
+ "version": "0.1.2",
4
4
  "description": "Terrain test intelligence CLI.",
5
5
  "type": "module",
6
6
  "bin": {