@haystackeditor/cli 0.15.30 → 0.16.0
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 +111 -60
- package/dist/assets/hooks/scripts/commit-msg.sh +3 -0
- package/dist/assets/hooks/scripts/post-commit.sh +3 -0
- package/dist/assets/hooks/scripts/pre-commit.sh +11 -6
- package/dist/assets/hooks/scripts/pre-push.sh +3 -0
- package/dist/assets/hooks/scripts/prepare-commit-msg.sh +3 -0
- package/dist/commands/case-batch-contract.js +608 -0
- package/dist/commands/case-batch.js +713 -0
- package/dist/commands/cloud-verifier-identity-census.js +5 -2
- package/dist/commands/combination-search-hook.js +139 -0
- package/dist/commands/design-verify.js +27 -1
- package/dist/commands/dismiss.js +2 -1
- package/dist/commands/hooks.js +66 -7
- package/dist/commands/install-session-hooks.js +131 -44
- package/dist/commands/policy.js +32 -45
- package/dist/commands/precompute-delivery.js +6 -1
- package/dist/commands/scaffold-provisional-universe.js +8 -10
- package/dist/commands/setup.js +32 -10
- package/dist/commands/submit.js +7 -2
- package/dist/commands/telemetry.js +17 -2
- package/dist/commands/triage.js +2 -1
- package/dist/commands/verify-explore.js +1 -0
- package/dist/commands/verify-history.js +152 -0
- package/dist/commands/verify-hosted-mcp.js +3 -12
- package/dist/commands/verify-hosted-reproducibility.js +49 -547
- package/dist/commands/verify-hosted.js +180 -163
- package/dist/commands/verify-precompute.js +57 -10
- package/dist/commands/verify.js +51 -139
- package/dist/index.js +264 -348
- package/dist/lazy.js +8 -0
- package/dist/schema.js +1 -0
- package/dist/tools/detect.js +3 -24
- package/dist/triage/prompts.js +46 -45
- package/dist/triage/runner.js +119 -33
- package/dist/types/verify-history.js +1 -0
- package/dist/types.js +3 -3
- package/dist/utils/auth.js +14 -2
- package/dist/utils/design-verifier-api.js +23 -0
- package/dist/utils/design-verifier-history.js +79 -0
- package/dist/utils/git.js +60 -29
- package/dist/utils/github-api.js +14 -1
- package/dist/utils/haystack-api.js +26 -7
- package/dist/utils/hooks.js +43 -6
- package/dist/utils/prompter.js +25 -10
- package/dist/utils/safe-write.js +31 -0
- package/dist/utils/secret-paths.js +115 -0
- package/dist/utils/secrets.js +0 -1
- package/dist/utils/telemetry.js +34 -14
- package/dist/utils/update-check.js +151 -0
- package/package.json +18 -14
- package/schemas/case-batch.v1.json +184 -0
- package/schemas/cloud-verifier.v1.json +55 -100
- package/dist/commands/ask.d.ts +0 -14
- package/dist/commands/cloud-verifier-behaviors.d.ts +0 -27
- package/dist/commands/cloud-verifier-data-store-census.d.ts +0 -47
- package/dist/commands/cloud-verifier-data-store-drift.d.ts +0 -42
- package/dist/commands/cloud-verifier-identity-census.d.ts +0 -88
- package/dist/commands/cloud-verifier-materialization.d.ts +0 -16
- package/dist/commands/cloud-verifier-pascal-selector-census.d.ts +0 -29
- package/dist/commands/cloud-verifier-python-manifest-selector-census.d.ts +0 -27
- package/dist/commands/cloud-verifier-specialized-operational-census.d.ts +0 -51
- package/dist/commands/cloud-verifier-universe.d.ts +0 -31
- package/dist/commands/config.d.ts +0 -46
- package/dist/commands/design-verify.d.ts +0 -31
- package/dist/commands/dismiss.d.ts +0 -29
- package/dist/commands/hooks.d.ts +0 -13
- package/dist/commands/inbox.d.ts +0 -65
- package/dist/commands/init.d.ts +0 -10
- package/dist/commands/install-session-hooks.d.ts +0 -17
- package/dist/commands/login.d.ts +0 -8
- package/dist/commands/mcp.d.ts +0 -1
- package/dist/commands/policy.d.ts +0 -31
- package/dist/commands/pr-status.d.ts +0 -144
- package/dist/commands/pr.d.ts +0 -40
- package/dist/commands/precompute-delivery-contract.d.ts +0 -68
- package/dist/commands/precompute-delivery.d.ts +0 -20
- package/dist/commands/prepare-universe-review.d.ts +0 -115
- package/dist/commands/production-source-deny-policy.d.ts +0 -15
- package/dist/commands/request-review.d.ts +0 -26
- package/dist/commands/review.d.ts +0 -25
- package/dist/commands/rules.d.ts +0 -4
- package/dist/commands/scaffold-provisional-universe.d.ts +0 -468
- package/dist/commands/schema-cmd.d.ts +0 -2
- package/dist/commands/setup.d.ts +0 -28
- package/dist/commands/skills.d.ts +0 -8
- package/dist/commands/status.d.ts +0 -4
- package/dist/commands/submit.d.ts +0 -30
- package/dist/commands/system-map.d.ts +0 -42
- package/dist/commands/telemetry.d.ts +0 -53
- package/dist/commands/tokens.d.ts +0 -14
- package/dist/commands/triage.d.ts +0 -35
- package/dist/commands/verify-core.d.ts +0 -449
- package/dist/commands/verify-core.js +0 -789
- package/dist/commands/verify-explore.d.ts +0 -14
- package/dist/commands/verify-hosted-mcp.d.ts +0 -12
- package/dist/commands/verify-hosted-reproducibility.d.ts +0 -90
- package/dist/commands/verify-hosted.d.ts +0 -92
- package/dist/commands/verify-mcp.d.ts +0 -8
- package/dist/commands/verify-mcp.js +0 -517
- package/dist/commands/verify-ops.d.ts +0 -158
- package/dist/commands/verify-ops.js +0 -1148
- package/dist/commands/verify-precompute.d.ts +0 -30
- package/dist/commands/verify-reproducibility.d.ts +0 -85
- package/dist/commands/verify-reproducibility.js +0 -494
- package/dist/commands/verify-reseal.d.ts +0 -9
- package/dist/commands/verify-reseal.js +0 -148
- package/dist/commands/verify-sandboxes.d.ts +0 -95
- package/dist/commands/verify-sandboxes.js +0 -352
- package/dist/commands/verify.d.ts +0 -28
- package/dist/commands/webhooks.d.ts +0 -30
- package/dist/index.d.ts +0 -22
- package/dist/schema.d.ts +0 -28
- package/dist/states.d.ts +0 -29
- package/dist/tools/detect.d.ts +0 -50
- package/dist/triage/prompts.d.ts +0 -24
- package/dist/triage/runner.d.ts +0 -34
- package/dist/triage/types.d.ts +0 -42
- package/dist/types.d.ts +0 -1684
- package/dist/utils/action-output.d.ts +0 -24
- package/dist/utils/analysis-api.d.ts +0 -187
- package/dist/utils/auth.d.ts +0 -79
- package/dist/utils/config.d.ts +0 -24
- package/dist/utils/design-verifier-api.d.ts +0 -200
- package/dist/utils/design-verifier-result.d.ts +0 -74
- package/dist/utils/detect.d.ts +0 -43
- package/dist/utils/git.d.ts +0 -135
- package/dist/utils/github-api.d.ts +0 -104
- package/dist/utils/haystack-api.d.ts +0 -37
- package/dist/utils/hooks.d.ts +0 -12
- package/dist/utils/pending-state.d.ts +0 -40
- package/dist/utils/pr-ref.d.ts +0 -27
- package/dist/utils/prompter.d.ts +0 -85
- package/dist/utils/secrets.d.ts +0 -47
- package/dist/utils/telemetry.d.ts +0 -19
- /package/dist/commands/{precompute-delivery-worker.d.ts → combination-search-hook-contract.js} +0 -0
|
@@ -76,95 +76,64 @@ export function parseHostedRunState(value, expectedRunId) {
|
|
|
76
76
|
if (headCommit && !COMMIT_SHA.test(headCommit)) {
|
|
77
77
|
throw new Error('Hosted verifier response has an invalid headCommit.');
|
|
78
78
|
}
|
|
79
|
-
const outputValue = response.output;
|
|
80
|
-
const output = outputValue === undefined
|
|
81
|
-
? undefined
|
|
82
|
-
: requireRecord(outputValue, 'Hosted verifier output');
|
|
83
79
|
const resultValue = response.result;
|
|
84
|
-
|
|
85
|
-
if (response.provider !== undefined && !fleet) {
|
|
80
|
+
if (response.provider !== 'haystack') {
|
|
86
81
|
throw new Error('Hosted verifier returned an unsupported provider.');
|
|
87
82
|
}
|
|
88
|
-
if (
|
|
83
|
+
if (response.version !== FLEET_PROJECTION_VERSION) {
|
|
89
84
|
throw new Error('Hosted verifier returned an unsupported fleet projection version.');
|
|
90
85
|
}
|
|
91
|
-
const result = fleet || resultValue === undefined
|
|
92
|
-
? undefined
|
|
93
|
-
: parseHostedRunnerResult(resultValue);
|
|
94
86
|
const createdAt = optionalTimestamp(response, 'createdAt');
|
|
95
87
|
const completedAt = optionalTimestamp(response, 'completedAt');
|
|
96
88
|
let progress;
|
|
97
89
|
let fleetResult;
|
|
98
|
-
if (
|
|
99
|
-
|
|
100
|
-
|
|
90
|
+
if (response.publicRunId !== runId || !repository || !baseCommit || !headCommit || !createdAt) {
|
|
91
|
+
throw new Error('Hosted fleet projection is missing its immutable run identity.');
|
|
92
|
+
}
|
|
93
|
+
if (response.progress !== undefined && response.progress !== null) {
|
|
94
|
+
progress = requireRecord(response.progress, 'Hosted fleet progress');
|
|
95
|
+
if (progress.version !== 'dv-verify-progress-v1' || progress.publicRunId !== runId) {
|
|
96
|
+
throw new Error('Hosted fleet progress does not match the requested run.');
|
|
101
97
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
98
|
+
requireString(progress, 'stage');
|
|
99
|
+
requireString(progress, 'status');
|
|
100
|
+
}
|
|
101
|
+
if (resultValue !== undefined && resultValue !== null) {
|
|
102
|
+
fleetResult = requireRecord(resultValue, 'Hosted fleet result');
|
|
103
|
+
const mapping = requireRecord(fleetResult.mapping, 'Hosted fleet result mapping');
|
|
104
|
+
const provenance = requireRecord(fleetResult.provenance, 'Hosted fleet provenance');
|
|
105
|
+
if (fleetResult.version !== 'dv-verify-result-v1' || fleetResult.provider !== 'haystack'
|
|
106
|
+
|| mapping.publicRunId !== runId || provenance.baseSha !== baseCommit || provenance.headSha !== headCommit) {
|
|
107
|
+
throw new Error('Hosted fleet result does not match the requested run and commits.');
|
|
109
108
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
// P0 result; the CLI only checks the fields it presents, retaining the
|
|
121
|
-
// original document instead of defining a second P0 wire contract.
|
|
122
|
-
requireString(fleetResult, 'lifecycle', /^(complete|failed|cancelled|budget-stopped|account-action)$/);
|
|
123
|
-
requireString(fleetResult, 'runHealth', /^(healthy|degraded|infrastructure-error|not-executed)$/);
|
|
124
|
-
const cleanup = requireRecord(fleetResult.cleanup, 'Hosted fleet cleanup');
|
|
125
|
-
requireString(cleanup, 'status', /^(complete|pending)$/);
|
|
126
|
-
if (!Array.isArray(cleanup.pending) || !Array.isArray(fleetResult.findings)) {
|
|
127
|
-
throw new Error('Hosted fleet result has invalid findings or cleanup.');
|
|
128
|
-
}
|
|
129
|
-
for (const value of fleetResult.findings) {
|
|
130
|
-
const finding = requireRecord(value, 'Hosted fleet finding');
|
|
131
|
-
requireString(finding, 'findingId');
|
|
132
|
-
requireString(finding, 'caseId');
|
|
133
|
-
requireString(finding, 'judgment');
|
|
134
|
-
}
|
|
135
|
-
requireString(provenance, 'intent', /^(user|inferred)$/);
|
|
136
|
-
if (status === 'complete' && fleetResult.lifecycle !== 'complete') {
|
|
137
|
-
throw new Error('Hosted fleet lifecycle disagrees with its completed job.');
|
|
138
|
-
}
|
|
109
|
+
// CONTRACT-GAP: P0 currently couples its canonical parsers to runner-only
|
|
110
|
+
// modules outside this published CLI. The server validates the complete
|
|
111
|
+
// P0 result; the CLI only checks the fields it presents, retaining the
|
|
112
|
+
// original document instead of defining a second P0 wire contract.
|
|
113
|
+
requireString(fleetResult, 'lifecycle', /^(complete|failed|cancelled|budget-stopped|account-action)$/);
|
|
114
|
+
requireString(fleetResult, 'runHealth', /^(healthy|degraded|infrastructure-error|not-executed)$/);
|
|
115
|
+
const cleanup = requireRecord(fleetResult.cleanup, 'Hosted fleet cleanup');
|
|
116
|
+
requireString(cleanup, 'status', /^(complete|pending)$/);
|
|
117
|
+
if (!Array.isArray(cleanup.pending) || !Array.isArray(fleetResult.findings)) {
|
|
118
|
+
throw new Error('Hosted fleet result has invalid findings or cleanup.');
|
|
139
119
|
}
|
|
140
|
-
|
|
141
|
-
|
|
120
|
+
for (const value of fleetResult.findings) {
|
|
121
|
+
const finding = requireRecord(value, 'Hosted fleet finding');
|
|
122
|
+
requireString(finding, 'findingId');
|
|
123
|
+
requireString(finding, 'caseId');
|
|
124
|
+
requireString(finding, 'judgment');
|
|
142
125
|
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
// Two terminal shapes exist: the Workflow receipt envelope (`output`)
|
|
146
|
-
// and the runner-queue result envelope (`result`). Exactly one must
|
|
147
|
-
// validate.
|
|
148
|
-
if (result) {
|
|
149
|
-
if (!repository || !baseCommit || !headCommit || !createdAt) {
|
|
150
|
-
throw new Error('Hosted verifier returned a complete run without a matching terminal receipt.');
|
|
151
|
-
}
|
|
126
|
+
if (fleetResult.savedChecks !== undefined) {
|
|
127
|
+
parseHostedSavedChecks(fleetResult.savedChecks, runId, headCommit);
|
|
152
128
|
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|| !createdAt
|
|
157
|
-
|| !completedAt
|
|
158
|
-
|| !output
|
|
159
|
-
|| output.runId !== runId
|
|
160
|
-
|| output.repository !== repository
|
|
161
|
-
|| output.baseCommit !== baseCommit
|
|
162
|
-
|| output.headCommit !== headCommit
|
|
163
|
-
|| output.sourceOfflineReady !== true
|
|
164
|
-
|| output.sandboxDeleted !== true) {
|
|
165
|
-
throw new Error('Hosted verifier returned a complete run without a matching terminal receipt.');
|
|
129
|
+
requireString(provenance, 'intent', /^(user|inferred)$/);
|
|
130
|
+
if (status === 'complete' && fleetResult.lifecycle !== 'complete') {
|
|
131
|
+
throw new Error('Hosted fleet lifecycle disagrees with its completed job.');
|
|
166
132
|
}
|
|
167
133
|
}
|
|
134
|
+
if (status === 'complete' && !fleetResult) {
|
|
135
|
+
throw new Error('Hosted fleet run completed without a result receipt.');
|
|
136
|
+
}
|
|
168
137
|
return {
|
|
169
138
|
runId,
|
|
170
139
|
status,
|
|
@@ -173,37 +142,13 @@ export function parseHostedRunState(value, expectedRunId) {
|
|
|
173
142
|
headCommit,
|
|
174
143
|
createdAt,
|
|
175
144
|
completedAt,
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
145
|
+
provider: 'haystack',
|
|
146
|
+
version: FLEET_PROJECTION_VERSION,
|
|
147
|
+
progress,
|
|
148
|
+
fleetResult,
|
|
179
149
|
error: optionalString(response, 'error'),
|
|
180
150
|
};
|
|
181
151
|
}
|
|
182
|
-
function parseHostedRunnerResult(value) {
|
|
183
|
-
const record = requireRecord(value, 'Hosted verifier runner result');
|
|
184
|
-
const regressionsValue = record.regressions;
|
|
185
|
-
if (typeof record.verdict !== 'string'
|
|
186
|
-
|| typeof record.summary !== 'string'
|
|
187
|
-
|| !Number.isInteger(record.casesTotal)
|
|
188
|
-
|| !Number.isInteger(record.casesFailed)
|
|
189
|
-
|| typeof record.outputLocation !== 'string'
|
|
190
|
-
|| typeof record.wallSeconds !== 'number'
|
|
191
|
-
|| !Array.isArray(regressionsValue)
|
|
192
|
-
|| !regressionsValue.every(item => !!item && typeof item === 'object' && !Array.isArray(item)
|
|
193
|
-
&& typeof item.caseId === 'string'
|
|
194
|
-
&& typeof item.title === 'string')) {
|
|
195
|
-
throw new Error('Hosted verifier runner result is malformed.');
|
|
196
|
-
}
|
|
197
|
-
return {
|
|
198
|
-
verdict: record.verdict,
|
|
199
|
-
summary: record.summary,
|
|
200
|
-
casesTotal: record.casesTotal,
|
|
201
|
-
casesFailed: record.casesFailed,
|
|
202
|
-
regressions: regressionsValue,
|
|
203
|
-
outputLocation: record.outputLocation,
|
|
204
|
-
wallSeconds: record.wallSeconds,
|
|
205
|
-
};
|
|
206
|
-
}
|
|
207
152
|
export function parseHostedRepository(value) {
|
|
208
153
|
const parts = value.split('/');
|
|
209
154
|
if (parts.length !== 2
|
|
@@ -216,13 +161,95 @@ export function parseHostedRepository(value) {
|
|
|
216
161
|
fullName: value,
|
|
217
162
|
};
|
|
218
163
|
}
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
164
|
+
/** The service validates the shared full contract. Check every additional
|
|
165
|
+
* field used by this published CLI before displaying or accepting a clean run. */
|
|
166
|
+
function parseHostedSavedChecks(value, runId, headSha) {
|
|
167
|
+
const saved = requireRecord(value, 'Hosted saved checks');
|
|
168
|
+
if (saved.version !== 'dv-fleet-saved-check-result-v1'
|
|
169
|
+
|| !Array.isArray(saved.descriptors) || !Array.isArray(saved.outcomes)
|
|
170
|
+
|| !Array.isArray(saved.unresolvedFindings) || !Array.isArray(saved.conflicts)) {
|
|
171
|
+
throw new Error('Hosted saved checks have an invalid version or records.');
|
|
172
|
+
}
|
|
173
|
+
const completeness = requireRecord(saved.completeness, 'Hosted saved check completeness');
|
|
174
|
+
requireString(completeness, 'status', /^(complete|incomplete)$/);
|
|
175
|
+
if (completeness.status === 'incomplete' && (!Array.isArray(completeness.reasons) || completeness.reasons.length === 0)) {
|
|
176
|
+
throw new Error('Hosted saved check history has no explanation for missing evidence.');
|
|
177
|
+
}
|
|
178
|
+
const ref = (record) => {
|
|
179
|
+
requireString(record, 'checkId', /^[0-9a-f]{64}$/);
|
|
180
|
+
requireString(record, 'planRevisionId', /^[0-9a-f]{64}$/);
|
|
181
|
+
return `${record.checkId}:${record.planRevisionId}`;
|
|
182
|
+
};
|
|
183
|
+
const descriptors = new Map();
|
|
184
|
+
for (const value of saved.descriptors) {
|
|
185
|
+
const descriptor = requireRecord(value, 'Hosted saved check descriptor');
|
|
186
|
+
const key = ref(descriptor);
|
|
187
|
+
if (descriptor.version !== 'dv-fleet-saved-check-v1' || descriptors.has(key)) {
|
|
188
|
+
throw new Error('Hosted saved check descriptor identity is invalid or duplicated.');
|
|
189
|
+
}
|
|
190
|
+
requireString(descriptor, 'captureStatus', /^(captured|unavailable)$/);
|
|
191
|
+
descriptors.set(key, descriptor);
|
|
192
|
+
}
|
|
193
|
+
const outcomes = new Map();
|
|
194
|
+
for (const value of saved.outcomes) {
|
|
195
|
+
const outcome = requireRecord(value, 'Hosted saved check outcome');
|
|
196
|
+
const key = ref(outcome);
|
|
197
|
+
requireString(outcome, 'status', /^(passed|reproduced|blocked|inconclusive)$/);
|
|
198
|
+
if (outcome.version !== 'dv-fleet-saved-check-outcome-v1'
|
|
199
|
+
|| (!descriptors.has(key) && completeness.status !== 'incomplete') || outcomes.has(key)
|
|
200
|
+
|| outcome.publicRunId !== runId || outcome.headSha !== headSha
|
|
201
|
+
|| (outcome.currentCaseId !== null && typeof outcome.currentCaseId !== 'string')
|
|
202
|
+
|| (outcome.blockedCode !== null && typeof outcome.blockedCode !== 'string')) {
|
|
203
|
+
throw new Error('Hosted saved check outcome does not match its current run or original check.');
|
|
204
|
+
}
|
|
205
|
+
if ((outcome.status === 'blocked') !== (outcome.blockedCode !== null)
|
|
206
|
+
|| (outcome.status === 'passed' && (descriptors.get(key)?.captureStatus !== 'captured'
|
|
207
|
+
|| outcome.currentCaseId === null || !outcome.receipt || completeness.status !== 'complete'))) {
|
|
208
|
+
throw new Error('Hosted saved check outcome claims a pass without original execution evidence.');
|
|
209
|
+
}
|
|
210
|
+
outcomes.set(key, outcome);
|
|
211
|
+
}
|
|
212
|
+
const findings = new Set();
|
|
213
|
+
for (const value of saved.unresolvedFindings) {
|
|
214
|
+
const finding = requireRecord(value, 'Hosted unresolved finding');
|
|
215
|
+
const key = ref(finding);
|
|
216
|
+
requireString(finding, 'findingId');
|
|
217
|
+
const source = requireRecord(finding.firstObserved, 'Hosted original finding evidence');
|
|
218
|
+
requireString(source, 'publicRunId', RUN_ID);
|
|
219
|
+
requireString(source, 'caseId');
|
|
220
|
+
requireString(source, 'headSha', COMMIT_SHA);
|
|
221
|
+
if (finding.version !== 'dv-fleet-unresolved-finding-v1'
|
|
222
|
+
|| (!descriptors.has(key) && completeness.status !== 'incomplete')
|
|
223
|
+
|| findings.has(finding.findingId) || !Array.isArray(source.evidence) || source.evidence.length === 0
|
|
224
|
+
|| outcomes.get(key)?.status === 'passed') {
|
|
225
|
+
throw new Error('Hosted unresolved finding has invalid original evidence or check identity.');
|
|
226
|
+
}
|
|
227
|
+
findings.add(finding.findingId);
|
|
228
|
+
if (finding.lastOutcome !== null) {
|
|
229
|
+
const last = requireRecord(finding.lastOutcome, 'Hosted unresolved finding outcome');
|
|
230
|
+
if (ref(last) !== key || last.status === 'passed')
|
|
231
|
+
throw new Error('Hosted unresolved finding lost its exact original check.');
|
|
232
|
+
requireString(last, 'status', /^(reproduced|blocked|inconclusive)$/);
|
|
233
|
+
if (last.blockedCode !== null)
|
|
234
|
+
requireString(last, 'blockedCode');
|
|
235
|
+
}
|
|
222
236
|
}
|
|
223
|
-
|
|
237
|
+
for (const value of saved.conflicts) {
|
|
238
|
+
const conflict = requireRecord(value, 'Hosted saved check conflict');
|
|
239
|
+
requireString(conflict, 'checkId', /^[0-9a-f]{64}$/);
|
|
240
|
+
if (!Array.isArray(conflict.planRevisionIds) || conflict.planRevisionIds.length < 2
|
|
241
|
+
|| conflict.planRevisionIds.some(id => typeof id !== 'string' || !/^[0-9a-f]{64}$/.test(id))) {
|
|
242
|
+
throw new Error('Hosted saved check conflict is invalid.');
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
return saved;
|
|
246
|
+
}
|
|
247
|
+
function savedCheckDisplay(value) {
|
|
248
|
+
// eslint-disable-next-line no-control-regex -- rejecting/stripping control characters is the point
|
|
249
|
+
return String(value).replace(/[\u0000-\u001f\u007f-\u009f]/g, '');
|
|
224
250
|
}
|
|
225
251
|
function requireRevision(value, flag) {
|
|
252
|
+
// eslint-disable-next-line no-control-regex -- rejecting/stripping control characters is the point
|
|
226
253
|
if (typeof value !== 'string' || value.length === 0 || value.length > 1_024 || /[\u0000-\u0020\u007f]/.test(value)) {
|
|
227
254
|
throw new Error(`${flag} must be a non-empty Git revision of at most 1024 characters without whitespace or control characters.`);
|
|
228
255
|
}
|
|
@@ -276,13 +303,9 @@ export function readHostedIntentFile(intentFile) {
|
|
|
276
303
|
intended_outcomes: record.intended_outcomes,
|
|
277
304
|
};
|
|
278
305
|
}
|
|
279
|
-
export function defaultHostedIdempotencyKey(repository, baseCommit, headCommit,
|
|
306
|
+
export function defaultHostedIdempotencyKey(repository, baseCommit, headCommit, intent) {
|
|
280
307
|
const digest = createHash('sha256')
|
|
281
|
-
.update(JSON.stringify(
|
|
282
|
-
? [fleetRequest.provider, repository, baseCommit, headCommit, fleetRequest.intent ?? null]
|
|
283
|
-
: replayPlanFrom
|
|
284
|
-
? [repository, baseCommit, headCommit, replayPlanFrom]
|
|
285
|
-
: [repository, baseCommit, headCommit]))
|
|
308
|
+
.update(JSON.stringify(['haystack', repository, baseCommit, headCommit, intent ?? null]))
|
|
286
309
|
.digest('hex');
|
|
287
310
|
return `cli:${digest}`;
|
|
288
311
|
}
|
|
@@ -309,7 +332,7 @@ export function hostedPollingOptions(interval, timeout, onStatus) {
|
|
|
309
332
|
};
|
|
310
333
|
}
|
|
311
334
|
export async function startHostedVerification(repositoryArg, options, token) {
|
|
312
|
-
const {
|
|
335
|
+
const { launchBody, idempotencyKey } = prepareHostedLaunch(repositoryArg, options);
|
|
313
336
|
const path = '/api/agent/cloud-verifier/runs';
|
|
314
337
|
const response = await fetch(haystackApiUrl(path), {
|
|
315
338
|
method: 'POST',
|
|
@@ -335,49 +358,27 @@ export async function startHostedVerification(repositoryArg, options, token) {
|
|
|
335
358
|
throw error;
|
|
336
359
|
}
|
|
337
360
|
const state = parseHostedRunState(await response.json());
|
|
338
|
-
|
|
339
|
-
throw new Error('Hosted verifier did not acknowledge the fleet-pinned request.');
|
|
340
|
-
}
|
|
341
|
-
return {
|
|
342
|
-
state: {
|
|
343
|
-
...state,
|
|
344
|
-
repository: state.repository ?? repository.fullName,
|
|
345
|
-
baseCommit: state.baseCommit ?? launchBody.baseCommit,
|
|
346
|
-
headCommit: state.headCommit ?? launchBody.headCommit,
|
|
347
|
-
},
|
|
348
|
-
idempotencyKey,
|
|
349
|
-
};
|
|
361
|
+
return { state, idempotencyKey };
|
|
350
362
|
}
|
|
351
363
|
function prepareHostedLaunch(repositoryArg, options) {
|
|
352
364
|
const repository = parseHostedRepository(repositoryArg);
|
|
353
|
-
const
|
|
354
|
-
const
|
|
355
|
-
const
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
if (fleet && intent && [intent.problem, intent.goal, ...intent.intended_outcomes].some(value => /[\u0000-\u001f\u007f]/.test(value))) {
|
|
365
|
+
const baseCommit = requireRevision(options.base, '--base');
|
|
366
|
+
const headCommit = requireRevision(options.head, '--head');
|
|
367
|
+
const intent = options.intentFile === undefined ? undefined : readHostedIntentFile(options.intentFile);
|
|
368
|
+
// eslint-disable-next-line no-control-regex -- rejecting/stripping control characters is the point
|
|
369
|
+
if (intent && [intent.problem, intent.goal, ...intent.intended_outcomes].some(value => /[\u0000-\u001f\u007f]/.test(value))) {
|
|
359
370
|
throw new Error('Fleet --intent-file fields must be single-line text without control characters.');
|
|
360
371
|
}
|
|
361
|
-
const replayPlanFrom = options.replayPlanFrom;
|
|
362
|
-
if (fleet && replayPlanFrom !== undefined) {
|
|
363
|
-
throw new Error('--replay-plan-from is available through the explicit hosted start alias.');
|
|
364
|
-
}
|
|
365
|
-
if (replayPlanFrom !== undefined && !RUN_ID.test(replayPlanFrom)) {
|
|
366
|
-
throw new Error('--replay-plan-from must match cv_ followed by 48 lowercase hex characters.');
|
|
367
|
-
}
|
|
368
372
|
const idempotencyKey = requireIdempotencyKey(options.idempotencyKey
|
|
369
|
-
?? defaultHostedIdempotencyKey(repository.fullName, baseCommit, headCommit,
|
|
373
|
+
?? defaultHostedIdempotencyKey(repository.fullName, baseCommit, headCommit, intent));
|
|
370
374
|
const launchBody = {
|
|
371
|
-
|
|
375
|
+
provider: 'haystack',
|
|
372
376
|
owner: repository.owner,
|
|
373
377
|
repository: repository.repository,
|
|
374
378
|
baseCommit,
|
|
375
379
|
headCommit,
|
|
376
380
|
idempotencyKey,
|
|
377
381
|
...(intent ? { intent } : {}),
|
|
378
|
-
...(replayPlanFrom
|
|
379
|
-
? { replayPlanFromRunId: replayPlanFrom }
|
|
380
|
-
: {}),
|
|
381
382
|
};
|
|
382
383
|
return { repository, launchBody, idempotencyKey };
|
|
383
384
|
}
|
|
@@ -437,9 +438,8 @@ export function buildHostedJson(operation, state, extras = {}) {
|
|
|
437
438
|
: {}),
|
|
438
439
|
...(extras.nextCommand ? { next_command: extras.nextCommand } : {}),
|
|
439
440
|
...(extras.timedOut !== undefined ? { timed_out: extras.timedOut } : {}),
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
...(state.provider ? { provider: state.provider, version: state.version } : {}),
|
|
441
|
+
provider: state.provider,
|
|
442
|
+
version: state.version,
|
|
443
443
|
...(state.progress ? { progress: state.progress } : {}),
|
|
444
444
|
...(state.fleetResult ? { result: state.fleetResult } : {}),
|
|
445
445
|
...(state.error ? { error: state.error } : {}),
|
|
@@ -461,13 +461,11 @@ function printHostedState(operation, state, options) {
|
|
|
461
461
|
}), null, 2)}\n`);
|
|
462
462
|
return;
|
|
463
463
|
}
|
|
464
|
-
const icon = state.
|
|
464
|
+
const icon = state.status === 'complete'
|
|
465
465
|
? chalk.cyan('•')
|
|
466
|
-
: state.status
|
|
467
|
-
? chalk.
|
|
468
|
-
:
|
|
469
|
-
? chalk.red('✗')
|
|
470
|
-
: chalk.cyan('•');
|
|
466
|
+
: TERMINAL_STATUSES.has(state.status)
|
|
467
|
+
? chalk.red('✗')
|
|
468
|
+
: chalk.cyan('•');
|
|
471
469
|
console.log(`${icon} Hosted verification ${chalk.bold(state.runId)}: ${state.status}`);
|
|
472
470
|
if (state.repository) {
|
|
473
471
|
console.log(` ${chalk.dim('Repository:')} ${state.repository}`);
|
|
@@ -475,15 +473,6 @@ function printHostedState(operation, state, options) {
|
|
|
475
473
|
if (state.baseCommit && state.headCommit) {
|
|
476
474
|
console.log(` ${chalk.dim('Commits:')} ${state.baseCommit.slice(0, 12)} → ${state.headCommit.slice(0, 12)}`);
|
|
477
475
|
}
|
|
478
|
-
if (state.result) {
|
|
479
|
-
const passed = state.result.casesTotal - state.result.casesFailed;
|
|
480
|
-
console.log(` ${chalk.dim('Verdict:')} ${state.result.verdict} `
|
|
481
|
-
+ `(${passed}/${state.result.casesTotal} cases clean, ${Math.round(state.result.wallSeconds)}s)`);
|
|
482
|
-
for (const regression of state.result.regressions) {
|
|
483
|
-
console.log(` ${chalk.red('✗')} ${regression.caseId}: ${regression.title}`);
|
|
484
|
-
}
|
|
485
|
-
console.log(` ${chalk.dim('Artifacts:')} ${state.result.outputLocation}`);
|
|
486
|
-
}
|
|
487
476
|
if (state.progress) {
|
|
488
477
|
console.log(` ${chalk.dim('Progress:')} ${state.progress.stage} (${state.progress.status})`);
|
|
489
478
|
}
|
|
@@ -499,6 +488,26 @@ function printHostedState(operation, state, options) {
|
|
|
499
488
|
const finding = requireRecord(value, 'Hosted fleet finding');
|
|
500
489
|
console.log(` ${chalk.dim('Finding:')} ${finding.findingId} / ${finding.caseId}: ${finding.judgment}`);
|
|
501
490
|
}
|
|
491
|
+
if (result.savedChecks !== undefined) {
|
|
492
|
+
const saved = requireRecord(result.savedChecks, 'Hosted saved checks');
|
|
493
|
+
const completeness = requireRecord(saved.completeness, 'Hosted saved check completeness');
|
|
494
|
+
const unresolved = saved.unresolvedFindings;
|
|
495
|
+
console.log(` ${chalk.dim('Saved checks:')} ${completeness.status}; ${saved.conflicts.length} conflicts; ${unresolved.length} unresolved bugs`);
|
|
496
|
+
for (const value of unresolved) {
|
|
497
|
+
const finding = requireRecord(value, 'Hosted unresolved finding');
|
|
498
|
+
const source = requireRecord(finding.firstObserved, 'Hosted original finding evidence');
|
|
499
|
+
const last = finding.lastOutcome === null ? null : requireRecord(finding.lastOutcome, 'Hosted unresolved finding outcome');
|
|
500
|
+
console.log(` ${chalk.yellow('Unresolved:')} ${savedCheckDisplay(finding.findingId)} / ${savedCheckDisplay(source.caseId)}`);
|
|
501
|
+
console.log(` First reproduced at ${String(source.headSha).slice(0, 12)}; latest check: ${savedCheckDisplay(last?.status ?? 'not executed')}${last?.blockedCode ? ` (${savedCheckDisplay(last.blockedCode)})` : ''}`);
|
|
502
|
+
}
|
|
503
|
+
if (unresolved.length)
|
|
504
|
+
console.log(' Earlier bug evidence remains open until the exact saved check passes.');
|
|
505
|
+
if (completeness.status === 'incomplete')
|
|
506
|
+
console.log(' Saved check history is incomplete; this run cannot confirm earlier bugs are fixed.');
|
|
507
|
+
}
|
|
508
|
+
else {
|
|
509
|
+
console.log(` ${chalk.dim('Saved checks:')} unavailable for this run`);
|
|
510
|
+
}
|
|
502
511
|
}
|
|
503
512
|
if (state.error)
|
|
504
513
|
console.log(` ${chalk.dim('Error:')} ${state.error}`);
|
|
@@ -516,6 +525,16 @@ function markNonSuccess(state, timedOut = false) {
|
|
|
516
525
|
process.exitCode = 2;
|
|
517
526
|
return;
|
|
518
527
|
}
|
|
528
|
+
if (result.savedChecks !== undefined) {
|
|
529
|
+
const saved = requireRecord(result.savedChecks, 'Hosted saved checks');
|
|
530
|
+
const completeness = requireRecord(saved.completeness, 'Hosted saved check completeness');
|
|
531
|
+
if (completeness.status !== 'complete' || saved.conflicts.length > 0) {
|
|
532
|
+
process.exitCode = 2;
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
if (saved.unresolvedFindings.length > 0)
|
|
536
|
+
process.exitCode = 1;
|
|
537
|
+
}
|
|
519
538
|
if (result.findings.some(value => requireRecord(value, 'Hosted fleet finding').judgment === 'regression')) {
|
|
520
539
|
process.exitCode = 1;
|
|
521
540
|
}
|
|
@@ -538,9 +557,7 @@ export async function verifyHostedStartCommand(repositoryArg, options) {
|
|
|
538
557
|
repo: repository.repository,
|
|
539
558
|
});
|
|
540
559
|
if (!options.json) {
|
|
541
|
-
console.error(chalk.dim(
|
|
542
|
-
? 'Submitting repository revisions to the hosted fleet verifier…'
|
|
543
|
-
: 'Submitting the exact commit pair to the hosted verifier…'));
|
|
560
|
+
console.error(chalk.dim('Submitting repository revisions to the hosted fleet verifier…'));
|
|
544
561
|
}
|
|
545
562
|
const started = await startHostedVerification(repositoryArg, options, auth.token);
|
|
546
563
|
const selectedAccount = auth.token.startsWith('hsk_live_')
|
|
@@ -9,7 +9,9 @@ import { NotLoggedInError, resolveAuthContext } from '../utils/auth.js';
|
|
|
9
9
|
import { findGitRoot, parseRemoteUrl, resolveDiffBaseRef, } from '../utils/git.js';
|
|
10
10
|
import { HaystackApiError, haystackJson } from '../utils/haystack-api.js';
|
|
11
11
|
import { trackPrecomputeEvent } from '../utils/telemetry.js';
|
|
12
|
+
import { findSecretsInPatch, isSecretBearingPath } from '../utils/secret-paths.js';
|
|
12
13
|
import { startPrecomputeDelivery } from './precompute-delivery.js';
|
|
14
|
+
import { captureCombinationSearchHook, readCombinationSearchHookStatus } from './combination-search-hook.js';
|
|
13
15
|
// --hook has five seconds to capture the exact source and publish local custody.
|
|
14
16
|
// Its detached sender owns the bounded network delivery and acknowledgement.
|
|
15
17
|
// An explicit invocation still waits synchronously for the server response.
|
|
@@ -48,13 +50,16 @@ function remainingMs(deadline) {
|
|
|
48
50
|
function gitOutput(args, gitRoot, deadline, options = {}) {
|
|
49
51
|
const timeout = Math.max(1, Math.min(remainingMs(deadline), options.timeoutCapMs ?? HOOK_WALL_MS));
|
|
50
52
|
try {
|
|
51
|
-
|
|
53
|
+
const output = execFileSync('git', args, {
|
|
52
54
|
cwd: gitRoot,
|
|
53
55
|
encoding: 'utf8',
|
|
54
56
|
env: options.env ?? process.env,
|
|
55
57
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
56
58
|
timeout,
|
|
57
|
-
|
|
59
|
+
...(options.input === undefined ? {} : { input: options.input }),
|
|
60
|
+
...(options.maxBuffer === undefined ? {} : { maxBuffer: options.maxBuffer }),
|
|
61
|
+
});
|
|
62
|
+
return options.raw ? output : output.trim();
|
|
58
63
|
}
|
|
59
64
|
catch {
|
|
60
65
|
throw new PrecomputeFailure('Could not derive the repository snapshot.');
|
|
@@ -110,7 +115,41 @@ function deriveBaseSha(gitRoot, deadline) {
|
|
|
110
115
|
}
|
|
111
116
|
return baseSha;
|
|
112
117
|
}
|
|
113
|
-
|
|
118
|
+
// The -U0 scan text omits binary content and context, so it stays far below
|
|
119
|
+
// the uploaded patch; this only guards against git's 1 MiB default.
|
|
120
|
+
const SCAN_PATCH_MAX_BYTES = 256 * 1024 * 1024;
|
|
121
|
+
/**
|
|
122
|
+
* Withholds secret-bearing changes from the snapshot index before the tree is
|
|
123
|
+
* written, so the tree, work commit, uploaded patch and combination-search
|
|
124
|
+
* patches all agree. A withheld path keeps its base version (or is absent when
|
|
125
|
+
* base has none). Every withheld path is named on stderr; secret values never are.
|
|
126
|
+
*/
|
|
127
|
+
function withholdSecretBearingChanges(gitRoot, baseSha, indexEnv, deadline) {
|
|
128
|
+
const changed = gitOutput(['diff', '--cached', '--no-renames', '--diff-filter=d', '--name-only', '-z', baseSha, '--'], gitRoot, deadline, { env: indexEnv, maxBuffer: SCAN_PATCH_MAX_BYTES, raw: true }).split('\0').filter(path => path.length > 0);
|
|
129
|
+
const withheld = new Map();
|
|
130
|
+
for (const path of changed) {
|
|
131
|
+
if (isSecretBearingPath(path))
|
|
132
|
+
withheld.set(path, 'secret-bearing file name');
|
|
133
|
+
}
|
|
134
|
+
if (changed.length > withheld.size) {
|
|
135
|
+
const patch = gitOutput(['-c', 'core.quotePath=false', 'diff', '--cached', '--no-renames', '--diff-filter=d', '-U0',
|
|
136
|
+
'--no-color', '--no-ext-diff', '--no-textconv',
|
|
137
|
+
// pathFromDiffHeader expects `a/` and `b/`; pin them so diff.noprefix
|
|
138
|
+
// or diff.mnemonicPrefix in the user's git config cannot break parsing.
|
|
139
|
+
'--src-prefix=a/', '--dst-prefix=b/', baseSha, '--'], gitRoot, deadline, { env: indexEnv, maxBuffer: SCAN_PATCH_MAX_BYTES });
|
|
140
|
+
for (const [path, pattern] of findSecretsInPatch(patch)) {
|
|
141
|
+
if (!withheld.has(path))
|
|
142
|
+
withheld.set(path, `possible secret: ${pattern}`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
if (withheld.size === 0)
|
|
146
|
+
return;
|
|
147
|
+
gitOutput(['reset', '-q', baseSha, '--pathspec-from-file=-', '--pathspec-file-nul'], gitRoot, deadline, { env: { ...indexEnv, GIT_LITERAL_PATHSPECS: '1' }, input: `${[...withheld.keys()].join('\0')}\0` });
|
|
148
|
+
for (const [path, reason] of withheld) {
|
|
149
|
+
process.stderr.write(`haystack: withheld ${JSON.stringify(path)} from the precompute upload (${reason}).\n`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
function deriveWorktreeTreeSha(gitRoot, baseSha, deadline) {
|
|
114
153
|
const scratchDirectory = mkdtempSync(join(tmpdir(), 'haystack-precompute-index-'));
|
|
115
154
|
const temporaryIndex = join(scratchDirectory, 'index');
|
|
116
155
|
try {
|
|
@@ -128,6 +167,7 @@ function deriveWorktreeTreeSha(gitRoot, deadline) {
|
|
|
128
167
|
gitOutput(['read-tree', 'HEAD'], gitRoot, deadline, { env: indexEnv });
|
|
129
168
|
}
|
|
130
169
|
gitOutput(['add', '-A'], gitRoot, deadline, { env: indexEnv });
|
|
170
|
+
withholdSecretBearingChanges(gitRoot, baseSha, indexEnv, deadline);
|
|
131
171
|
const treeSha = gitOutput(['write-tree'], gitRoot, deadline, { env: indexEnv });
|
|
132
172
|
if (!isCommitSha(treeSha)) {
|
|
133
173
|
throw new PrecomputeFailure('Could not derive the working-tree snapshot.');
|
|
@@ -221,7 +261,7 @@ export async function derivePrecomputeRequest(gitRoot, owner, repository, deadli
|
|
|
221
261
|
// make an older stop look newer than a later one.
|
|
222
262
|
const observedAt = Date.now();
|
|
223
263
|
const baseSha = deriveBaseSha(gitRoot, deadline);
|
|
224
|
-
const treeSha = deriveWorktreeTreeSha(gitRoot, deadline);
|
|
264
|
+
const treeSha = deriveWorktreeTreeSha(gitRoot, baseSha, deadline);
|
|
225
265
|
const workCommit = deriveWorkCommit(gitRoot, baseSha, treeSha, deadline);
|
|
226
266
|
const patchGzip = await buildCompressedPatch(gitRoot, baseSha, workCommit, deadline);
|
|
227
267
|
const cacheKey = [
|
|
@@ -231,6 +271,7 @@ export async function derivePrecomputeRequest(gitRoot, owner, repository, deadli
|
|
|
231
271
|
'slim',
|
|
232
272
|
slimInputHash(),
|
|
233
273
|
].join('-');
|
|
274
|
+
const combinationSearch = captureCombinationSearchHook(gitRoot, `${owner}/${repository}`, baseSha, workCommit, deadline);
|
|
234
275
|
return {
|
|
235
276
|
treeSha,
|
|
236
277
|
request: {
|
|
@@ -242,6 +283,7 @@ export async function derivePrecomputeRequest(gitRoot, owner, repository, deadli
|
|
|
242
283
|
cacheKey,
|
|
243
284
|
patchGzBase64: patchGzip.toString('base64'),
|
|
244
285
|
patchSha256: createHash('sha256').update(patchGzip).digest('hex'),
|
|
286
|
+
...(combinationSearch ? { combinationSearch } : {}),
|
|
245
287
|
},
|
|
246
288
|
};
|
|
247
289
|
}
|
|
@@ -249,15 +291,16 @@ function precomputeResult(response) {
|
|
|
249
291
|
if (!response || typeof response !== 'object' || Array.isArray(response)) {
|
|
250
292
|
throw new PrecomputeFailure('The precompute service returned an invalid response.');
|
|
251
293
|
}
|
|
252
|
-
const { status, runId, walker: rawWalker } = response;
|
|
294
|
+
const { status, runId, walker: rawWalker, search: rawSearch } = response;
|
|
253
295
|
const walker = rawWalker === undefined ? undefined : readWalkerStatus(rawWalker);
|
|
296
|
+
const search = rawSearch === undefined ? undefined : readCombinationSearchHookStatus(rawSearch);
|
|
254
297
|
if (status === 'queued' && typeof runId === 'string' && isRunId(runId)) {
|
|
255
|
-
return { outcome: 'enqueued', runId, ...(walker === undefined ? {} : { walker }) };
|
|
298
|
+
return { outcome: 'enqueued', runId, ...(walker === undefined ? {} : { walker }), ...(search === undefined ? {} : { search }) };
|
|
256
299
|
}
|
|
257
300
|
if (status === 'cached')
|
|
258
|
-
return { outcome: 'cached', ...(walker === undefined ? {} : { walker }) };
|
|
301
|
+
return { outcome: 'cached', ...(walker === undefined ? {} : { walker }), ...(search === undefined ? {} : { search }) };
|
|
259
302
|
if (status === 'existing')
|
|
260
|
-
return { outcome: 'existing', ...(walker === undefined ? {} : { walker }) };
|
|
303
|
+
return { outcome: 'existing', ...(walker === undefined ? {} : { walker }), ...(search === undefined ? {} : { search }) };
|
|
261
304
|
throw new PrecomputeFailure('The precompute service returned an invalid response.');
|
|
262
305
|
}
|
|
263
306
|
/** One authenticated submission of the already captured request. No source reads or retries. */
|
|
@@ -340,7 +383,7 @@ function normalModeFailure(error) {
|
|
|
340
383
|
}
|
|
341
384
|
return new PrecomputeFailure('The precompute request failed.');
|
|
342
385
|
}
|
|
343
|
-
function printResult(outcome, runId, walker) {
|
|
386
|
+
function printResult(outcome, runId, walker, search) {
|
|
344
387
|
if (outcome === 'enqueued') {
|
|
345
388
|
console.log(chalk.green(`Precompute request queued${runId ? ` (${runId})` : ''}.`));
|
|
346
389
|
}
|
|
@@ -360,6 +403,10 @@ function printResult(outcome, runId, walker) {
|
|
|
360
403
|
: 'Walker unavailable: the enqueue request failed.');
|
|
361
404
|
else
|
|
362
405
|
console.log('Walker status was not reported by the service.');
|
|
406
|
+
if (search?.status === 'queued' || search?.status === 'existing')
|
|
407
|
+
console.log(`Browser search ${search.status} (${search.runId}).`);
|
|
408
|
+
else if (search?.status === 'unavailable' && search.reason !== 'not-configured')
|
|
409
|
+
console.log(`Browser search unavailable: ${search.reason}.`);
|
|
363
410
|
}
|
|
364
411
|
export async function verifyPrecomputeCommand(options) {
|
|
365
412
|
const startedAt = Date.now();
|
|
@@ -421,7 +468,7 @@ export async function verifyPrecomputeCommand(options) {
|
|
|
421
468
|
}
|
|
422
469
|
}
|
|
423
470
|
if (!options.hook)
|
|
424
|
-
printResult(outcome, result.runId, result.walker);
|
|
471
|
+
printResult(outcome, result.runId, result.walker, result.search);
|
|
425
472
|
}
|
|
426
473
|
catch (error) {
|
|
427
474
|
if (!options.hook)
|