@haystackeditor/cli 0.15.25 → 0.15.26
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.
|
@@ -4,11 +4,13 @@ export interface HostedReproducibilityOptions {
|
|
|
4
4
|
head: string;
|
|
5
5
|
runs?: string;
|
|
6
6
|
seriesKey?: string;
|
|
7
|
+
freshPlans?: boolean;
|
|
7
8
|
account?: string;
|
|
8
9
|
interval?: string;
|
|
9
10
|
timeout?: string;
|
|
10
11
|
json?: boolean;
|
|
11
12
|
}
|
|
13
|
+
type HostedReproducibilityMode = 'exact_plan_replay' | 'fresh_plans';
|
|
12
14
|
interface HostedReproducibilityRun {
|
|
13
15
|
state: HostedRunState;
|
|
14
16
|
timedOut: boolean;
|
|
@@ -19,6 +21,7 @@ interface HostedReproducibilityIdentity {
|
|
|
19
21
|
baseCommit: string;
|
|
20
22
|
headCommit: string;
|
|
21
23
|
requestedRuns?: number;
|
|
24
|
+
mode?: HostedReproducibilityMode;
|
|
22
25
|
}
|
|
23
26
|
interface ParsedAssessment {
|
|
24
27
|
schemaVersion: 8 | 9;
|
|
@@ -60,6 +63,7 @@ export interface HostedReproducibilityReport {
|
|
|
60
63
|
repository: string;
|
|
61
64
|
base_commit: string;
|
|
62
65
|
head_commit: string;
|
|
66
|
+
reproducibility_mode: HostedReproducibilityMode;
|
|
63
67
|
requested_runs: number;
|
|
64
68
|
launched_runs: number;
|
|
65
69
|
unstarted_runs: number;
|
|
@@ -67,7 +71,7 @@ export interface HostedReproducibilityReport {
|
|
|
67
71
|
run_ids: string[];
|
|
68
72
|
replay_source_run_id: string | null;
|
|
69
73
|
plan_replay_verified: boolean;
|
|
70
|
-
planner_reproducibility_tested:
|
|
74
|
+
planner_reproducibility_tested: boolean;
|
|
71
75
|
planner_reproducible: boolean;
|
|
72
76
|
assessment_reproducible: boolean;
|
|
73
77
|
reproducible: boolean;
|
|
@@ -78,7 +82,7 @@ export interface HostedReproducibilityReport {
|
|
|
78
82
|
inconclusive_cell_ids: string[];
|
|
79
83
|
unrunnable_runs: RunFailure[];
|
|
80
84
|
}
|
|
81
|
-
export declare function hostedReproducibilityRunKey(seriesKey: string, repository: string, baseCommit: string, headCommit: string, ordinal: number): string;
|
|
85
|
+
export declare function hostedReproducibilityRunKey(seriesKey: string, repository: string, baseCommit: string, headCommit: string, ordinal: number, mode?: HostedReproducibilityMode): string;
|
|
82
86
|
export declare function analyzeHostedReproducibility(runs: HostedReproducibilityRun[], identity: HostedReproducibilityIdentity): HostedReproducibilityReport;
|
|
83
87
|
export declare function isHostedReproducibilityReplaySource(run: HostedReproducibilityRun, identity: HostedReproducibilityIdentity): boolean;
|
|
84
88
|
export declare function verifyHostedReproducibilityCommand(repositoryArg: string, options: HostedReproducibilityOptions): Promise<void>;
|
|
@@ -69,15 +69,24 @@ function requireSeriesKey(value) {
|
|
|
69
69
|
}
|
|
70
70
|
return key;
|
|
71
71
|
}
|
|
72
|
-
export function hostedReproducibilityRunKey(seriesKey, repository, baseCommit, headCommit, ordinal) {
|
|
72
|
+
export function hostedReproducibilityRunKey(seriesKey, repository, baseCommit, headCommit, ordinal, mode = 'exact_plan_replay') {
|
|
73
73
|
const digest = createHash('sha256')
|
|
74
|
-
.update(JSON.stringify(
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
74
|
+
.update(JSON.stringify(mode === 'exact_plan_replay'
|
|
75
|
+
? [
|
|
76
|
+
seriesKey,
|
|
77
|
+
repository,
|
|
78
|
+
baseCommit,
|
|
79
|
+
headCommit,
|
|
80
|
+
ordinal,
|
|
81
|
+
]
|
|
82
|
+
: [
|
|
83
|
+
mode,
|
|
84
|
+
seriesKey,
|
|
85
|
+
repository,
|
|
86
|
+
baseCommit,
|
|
87
|
+
headCommit,
|
|
88
|
+
ordinal,
|
|
89
|
+
]))
|
|
81
90
|
.digest('hex');
|
|
82
91
|
return `repro:${digest}`;
|
|
83
92
|
}
|
|
@@ -371,10 +380,17 @@ export function analyzeHostedReproducibility(runs, identity) {
|
|
|
371
380
|
});
|
|
372
381
|
}
|
|
373
382
|
}
|
|
383
|
+
const requestedRuns = identity.requestedRuns ?? runs.length;
|
|
384
|
+
const allRequestedRunsLaunched = runs.length === requestedRuns;
|
|
374
385
|
const planVariants = groupedPlanVariants(parsed);
|
|
375
386
|
const assessmentVariants = groupedOutcomeVariants(parsed);
|
|
376
|
-
const
|
|
377
|
-
const
|
|
387
|
+
const mode = identity.mode ?? 'exact_plan_replay';
|
|
388
|
+
const replaySourceRunId = mode === 'exact_plan_replay'
|
|
389
|
+
? parsed[0]?.runId ?? null
|
|
390
|
+
: null;
|
|
391
|
+
const planReplayVerified = mode === 'exact_plan_replay'
|
|
392
|
+
&& allRequestedRunsLaunched
|
|
393
|
+
&& parsed.length === runs.length
|
|
378
394
|
&& parsed.length > 1
|
|
379
395
|
&& parsed[0]?.assessment.schemaVersion === 8
|
|
380
396
|
&& parsed[0]?.assessment.replaySourceRunId === null
|
|
@@ -384,7 +400,17 @@ export function analyzeHostedReproducibility(runs, identity) {
|
|
|
384
400
|
=== parsed[0].assessment.schemaVersion
|
|
385
401
|
&& item.assessment.riskPlanSha256
|
|
386
402
|
=== parsed[0].assessment.riskPlanSha256);
|
|
387
|
-
const
|
|
403
|
+
const plannerReproducibilityTested = mode === 'fresh_plans' && parsed.length > 1;
|
|
404
|
+
const plannerReproducible = plannerReproducibilityTested
|
|
405
|
+
&& allRequestedRunsLaunched
|
|
406
|
+
&& parsed.length === runs.length
|
|
407
|
+
&& parsed.every(item => item.assessment.schemaVersion === 8
|
|
408
|
+
&& item.assessment.replaySourceRunId === null)
|
|
409
|
+
&& planVariants.length === 1;
|
|
410
|
+
const assessmentReproducible = (mode === 'exact_plan_replay'
|
|
411
|
+
? planReplayVerified
|
|
412
|
+
: plannerReproducible)
|
|
413
|
+
&& assessmentVariants.length === 1;
|
|
388
414
|
const planDriftCellIds = driftIds(parsed, assessment => new Map(assessment.planIdentities.map(plan => [
|
|
389
415
|
plan.cellId,
|
|
390
416
|
{ capabilityId: plan.capabilityId },
|
|
@@ -401,12 +427,12 @@ export function analyzeHostedReproducibility(runs, identity) {
|
|
|
401
427
|
const inconclusiveCellIds = [...new Set(parsed.flatMap(item => item.assessment.outcomes
|
|
402
428
|
.filter(outcome => outcome.status === 'inconclusive')
|
|
403
429
|
.map(outcome => outcome.cellId)))].sort();
|
|
404
|
-
const requestedRuns = identity.requestedRuns ?? runs.length;
|
|
405
430
|
return {
|
|
406
431
|
series_key: identity.seriesKey,
|
|
407
432
|
repository: identity.repository,
|
|
408
433
|
base_commit: identity.baseCommit,
|
|
409
434
|
head_commit: identity.headCommit,
|
|
435
|
+
reproducibility_mode: mode,
|
|
410
436
|
requested_runs: requestedRuns,
|
|
411
437
|
launched_runs: runs.length,
|
|
412
438
|
unstarted_runs: Math.max(0, requestedRuns - runs.length),
|
|
@@ -414,10 +440,12 @@ export function analyzeHostedReproducibility(runs, identity) {
|
|
|
414
440
|
run_ids: runs.map(run => run.state.runId),
|
|
415
441
|
replay_source_run_id: replaySourceRunId,
|
|
416
442
|
plan_replay_verified: planReplayVerified,
|
|
417
|
-
planner_reproducibility_tested:
|
|
418
|
-
planner_reproducible:
|
|
443
|
+
planner_reproducibility_tested: plannerReproducibilityTested,
|
|
444
|
+
planner_reproducible: plannerReproducible,
|
|
419
445
|
assessment_reproducible: assessmentReproducible,
|
|
420
|
-
reproducible:
|
|
446
|
+
reproducible: (mode === 'exact_plan_replay'
|
|
447
|
+
? planReplayVerified
|
|
448
|
+
: plannerReproducible)
|
|
421
449
|
&& assessmentReproducible
|
|
422
450
|
&& inconclusiveCellIds.length === 0,
|
|
423
451
|
plan_variants: planVariants,
|
|
@@ -444,9 +472,20 @@ function renderHostedReproducibility(report) {
|
|
|
444
472
|
const icon = report.reproducible ? chalk.green('✓') : chalk.yellow('!');
|
|
445
473
|
console.log(`${icon} Hosted reproducibility: ${report.reproducible ? 'reproducible' : 'not established'}`);
|
|
446
474
|
console.log(` ${chalk.dim('Runs:')} ${report.analyzable_runs}/${report.requested_runs} analyzable; ${report.launched_runs} launched`);
|
|
475
|
+
console.log(` ${chalk.dim('Mode:')} ${report.reproducibility_mode === 'fresh_plans'
|
|
476
|
+
? 'independent fresh plans'
|
|
477
|
+
: 'exact plan replay'}`);
|
|
447
478
|
console.log(` ${chalk.dim('Plan variants:')} ${report.plan_variants.length}`);
|
|
448
|
-
console.log(` ${chalk.dim('Exact plan replay:')} ${report.
|
|
449
|
-
|
|
479
|
+
console.log(` ${chalk.dim('Exact plan replay:')} ${report.reproducibility_mode === 'fresh_plans'
|
|
480
|
+
? 'not requested'
|
|
481
|
+
: report.plan_replay_verified
|
|
482
|
+
? 'verified'
|
|
483
|
+
: 'not verified'}`);
|
|
484
|
+
console.log(` ${chalk.dim('Independent planner reproducibility:')} ${report.planner_reproducibility_tested
|
|
485
|
+
? report.planner_reproducible
|
|
486
|
+
? 'verified'
|
|
487
|
+
: 'not reproducible'
|
|
488
|
+
: 'not tested'}`);
|
|
450
489
|
console.log(` ${chalk.dim('Assessment variants:')} ${report.assessment_variants.length}`);
|
|
451
490
|
if (report.plan_drift_cell_ids.length > 0) {
|
|
452
491
|
console.log(` ${chalk.dim('Plan drift:')} ${report.plan_drift_cell_ids.length} stable cell id(s)`);
|
|
@@ -465,6 +504,9 @@ export async function verifyHostedReproducibilityCommand(repositoryArg, options)
|
|
|
465
504
|
const repository = parseHostedRepository(repositoryArg);
|
|
466
505
|
const requestedRuns = boundedInteger(options.runs, DEFAULT_RUNS, '--runs', 2, MAX_RUNS);
|
|
467
506
|
const seriesKey = requireSeriesKey(options.seriesKey);
|
|
507
|
+
const mode = options.freshPlans
|
|
508
|
+
? 'fresh_plans'
|
|
509
|
+
: 'exact_plan_replay';
|
|
468
510
|
const auth = await resolveAuthContext({
|
|
469
511
|
preferredLogin: options.account,
|
|
470
512
|
owner: repository.owner,
|
|
@@ -479,8 +521,8 @@ export async function verifyHostedReproducibilityCommand(repositoryArg, options)
|
|
|
479
521
|
const started = await startHostedVerification(repositoryArg, {
|
|
480
522
|
base: options.base,
|
|
481
523
|
head: options.head,
|
|
482
|
-
idempotencyKey: hostedReproducibilityRunKey(seriesKey, repository.fullName, options.base, options.head, index + 1),
|
|
483
|
-
replayPlanFrom: index === 0
|
|
524
|
+
idempotencyKey: hostedReproducibilityRunKey(seriesKey, repository.fullName, options.base, options.head, index + 1, mode),
|
|
525
|
+
replayPlanFrom: options.freshPlans || index === 0
|
|
484
526
|
? undefined
|
|
485
527
|
: runs[0]?.state.runId,
|
|
486
528
|
wait: false,
|
|
@@ -497,7 +539,9 @@ export async function verifyHostedReproducibilityCommand(repositoryArg, options)
|
|
|
497
539
|
timedOut = waited.timedOut;
|
|
498
540
|
}
|
|
499
541
|
runs.push({ state, timedOut });
|
|
500
|
-
if (
|
|
542
|
+
if (!options.freshPlans
|
|
543
|
+
&&
|
|
544
|
+
index === 0
|
|
501
545
|
&& !isHostedReproducibilityReplaySource(runs[0], {
|
|
502
546
|
seriesKey,
|
|
503
547
|
repository: repository.fullName,
|
|
@@ -516,6 +560,7 @@ export async function verifyHostedReproducibilityCommand(repositoryArg, options)
|
|
|
516
560
|
baseCommit: options.base,
|
|
517
561
|
headCommit: options.head,
|
|
518
562
|
requestedRuns,
|
|
563
|
+
mode,
|
|
519
564
|
});
|
|
520
565
|
if (options.json) {
|
|
521
566
|
process.stdout.write(`${JSON.stringify(withSchema('cloud-verifier', {
|
package/dist/index.js
CHANGED
|
@@ -262,12 +262,13 @@ Example:
|
|
|
262
262
|
});
|
|
263
263
|
hostedVerify
|
|
264
264
|
.command('reproducibility')
|
|
265
|
-
.description('Replay one exact hosted risk plan
|
|
265
|
+
.description('Replay one exact hosted risk plan or run fresh plans to report drift')
|
|
266
266
|
.argument('<repository>', 'Exact GitHub owner/repository name')
|
|
267
267
|
.option('--base <sha>', 'Required: exact 40-character lowercase base commit SHA')
|
|
268
268
|
.option('--head <sha>', 'Required: exact 40-character lowercase head commit SHA')
|
|
269
269
|
.option('--runs <n>', 'Number of sequential production runs (default 3; min 2, max 10)')
|
|
270
270
|
.option('--series-key <key>', 'Stable series key for resumable idempotent retries')
|
|
271
|
+
.option('--fresh-plans', 'Run the planner independently for every run instead of replaying the first plan')
|
|
271
272
|
.option('--account <login>', 'Use a specific saved Haystack account')
|
|
272
273
|
.option('--interval <seconds>', 'Polling interval for each run (default 5)')
|
|
273
274
|
.option('--timeout <minutes>', 'Maximum wait per run (default 35)')
|
|
@@ -278,8 +279,14 @@ validated plan against the same repository and commit pair with a distinct,
|
|
|
278
279
|
series-derived idempotency key. A reproducible verdict requires schema-9 replay
|
|
279
280
|
provenance, stable assessment outcomes, and no inconclusive cells.
|
|
280
281
|
|
|
282
|
+
With --fresh-plans, every run plans independently. A reproducible verdict then
|
|
283
|
+
requires one stable-id plan variant, one assessment variant, no replay
|
|
284
|
+
provenance, and no inconclusive cells. Use this mode to detect planner drift;
|
|
285
|
+
use the default mode to isolate executor or assessment drift.
|
|
286
|
+
|
|
281
287
|
Example:
|
|
282
288
|
haystack verify hosted reproducibility owner/repo --base <sha> --head <sha> --runs 3
|
|
289
|
+
haystack verify hosted reproducibility owner/repo --base <sha> --head <sha> --runs 3 --fresh-plans
|
|
283
290
|
`)
|
|
284
291
|
.action(async (repository, _options, cmd) => {
|
|
285
292
|
const options = { ...cmd.optsWithGlobals(), ...cmd.opts() };
|