agent-scenario-loop 0.1.4 → 0.1.6
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/dist/core/artifact-layout.d.ts +2 -0
- package/dist/core/artifact-layout.js +2 -0
- package/dist/core/planner.js +4 -3
- package/dist/runner/profile-android.js +36 -9
- package/dist/runner/profile-ios.js +29 -2
- package/dist/runner/profile-mobile.d.ts +32 -1
- package/dist/runner/profile-mobile.js +317 -13
- package/dist/scripts/consumer-rehearsal.d.ts +9 -1
- package/dist/scripts/consumer-rehearsal.js +18 -1
- package/dist/scripts/package-smoke.d.ts +9 -1
- package/dist/scripts/package-smoke.js +31 -9
- package/dist/scripts/release-check.d.ts +47 -0
- package/dist/scripts/release-check.js +117 -0
- package/dist/scripts/release-readiness.js +20 -1
- package/docs/consumer-rehearsal.md +6 -0
- package/docs/live-proofs.md +7 -1
- package/package.json +2 -2
|
@@ -7,6 +7,7 @@ declare const ARTIFACT_FILENAMES: {
|
|
|
7
7
|
liveProofSet: string;
|
|
8
8
|
plannerCompatibility: string;
|
|
9
9
|
projectValidation: string;
|
|
10
|
+
runPlan: string;
|
|
10
11
|
verdict: string;
|
|
11
12
|
};
|
|
12
13
|
declare const PROFILE_ARTIFACT_FILENAMES: {
|
|
@@ -34,6 +35,7 @@ type ArtifactLayout = {
|
|
|
34
35
|
liveProofSet: string;
|
|
35
36
|
plannerCompatibility: string;
|
|
36
37
|
projectValidation: string;
|
|
38
|
+
runPlan: string;
|
|
37
39
|
raw: string;
|
|
38
40
|
captures: string;
|
|
39
41
|
signals: {
|
|
@@ -13,6 +13,7 @@ const ARTIFACT_FILENAMES = {
|
|
|
13
13
|
liveProofSet: 'live-proof-set.json',
|
|
14
14
|
plannerCompatibility: 'planner-compatibility.json',
|
|
15
15
|
projectValidation: 'project-validation.json',
|
|
16
|
+
runPlan: 'run-plan.json',
|
|
16
17
|
verdict: 'verdict.json',
|
|
17
18
|
};
|
|
18
19
|
exports.ARTIFACT_FILENAMES = ARTIFACT_FILENAMES;
|
|
@@ -49,6 +50,7 @@ function createArtifactLayout({ outputDir }) {
|
|
|
49
50
|
liveProofSet: path.join(outputDir, ARTIFACT_FILENAMES.liveProofSet),
|
|
50
51
|
plannerCompatibility: path.join(outputDir, ARTIFACT_FILENAMES.plannerCompatibility),
|
|
51
52
|
projectValidation: path.join(outputDir, ARTIFACT_FILENAMES.projectValidation),
|
|
53
|
+
runPlan: path.join(outputDir, ARTIFACT_FILENAMES.runPlan),
|
|
52
54
|
raw: path.join(outputDir, 'raw'),
|
|
53
55
|
captures: path.join(outputDir, 'captures'),
|
|
54
56
|
signals: {
|
package/dist/core/planner.js
CHANGED
|
@@ -724,14 +724,15 @@ function validatePrimaryRunner({ runner, errors, }) {
|
|
|
724
724
|
* @returns {string[]}
|
|
725
725
|
*/
|
|
726
726
|
function resolveEffectivePlatforms({ scenario, runner, platform, errors, }) {
|
|
727
|
-
const
|
|
727
|
+
const declaredScenarioPlatforms = asArray(scenario?.platforms);
|
|
728
728
|
const runnerPlatforms = asArray(runner?.platforms);
|
|
729
|
+
const scenarioPlatforms = declaredScenarioPlatforms.length > 0 ? declaredScenarioPlatforms : runnerPlatforms;
|
|
729
730
|
if (platform) {
|
|
730
|
-
if (!
|
|
731
|
+
if (declaredScenarioPlatforms.length > 0 && !declaredScenarioPlatforms.includes(platform)) {
|
|
731
732
|
errors.push(createIssue('platform_not_supported_by_scenario', 'The scenario does not support the selected platform.', {
|
|
732
733
|
scenarioId: getScenarioId(scenario),
|
|
733
734
|
platform,
|
|
734
|
-
supportedPlatforms: uniqueSorted(
|
|
735
|
+
supportedPlatforms: uniqueSorted(declaredScenarioPlatforms),
|
|
735
736
|
}));
|
|
736
737
|
}
|
|
737
738
|
if (!runnerPlatforms.includes(platform)) {
|
|
@@ -17,7 +17,7 @@ const fs = require('node:fs');
|
|
|
17
17
|
const path = require('node:path');
|
|
18
18
|
const { hasHelpFlag } = require('./cli');
|
|
19
19
|
const { ANDROID_DEVICE_EPOCH_MS_PLACEHOLDER, parsePositiveInteger, runAndroidAdbPreflight, } = require('./android-adb');
|
|
20
|
-
const { parseArgs, readScalarArg, runProfileMobile, usage, } = require('./profile-mobile');
|
|
20
|
+
const { parseArgs, readScalarArg, resolveArtifactRoot, resolveProfileScenarioName, runProfileCompatibilityPreflight, runProfileMobile, usage, } = require('./profile-mobile');
|
|
21
21
|
exports.parseArgs = parseArgs;
|
|
22
22
|
exports.usage = usage;
|
|
23
23
|
const { buildScenarioExecutionPlan } = require('../core/execution-plan');
|
|
@@ -46,6 +46,17 @@ const MANIFEST_LIFECYCLE_PHASES = new Set([
|
|
|
46
46
|
'reboot',
|
|
47
47
|
'relaunch',
|
|
48
48
|
]);
|
|
49
|
+
const ANDROID_PROFILE_RUNNER_CAPABILITIES = {
|
|
50
|
+
schemaVersion: '1.0.0',
|
|
51
|
+
runnerId: 'android-adb-profile-runner',
|
|
52
|
+
kind: 'primary',
|
|
53
|
+
platforms: ['android'],
|
|
54
|
+
capabilities: ['launch', 'sessionControl', 'command', 'logCapture', 'artifactWrite'],
|
|
55
|
+
driverActions: ['tap', 'scroll', 'assertVisible', 'inspectTree', 'screenshot', 'record', 'readLogs'],
|
|
56
|
+
artifactOutputs: ['logs', 'signals', 'screenshot', 'video', 'uiTree'],
|
|
57
|
+
uiContexts: ['app'],
|
|
58
|
+
lifecycle: ['prepare', 'launch', 'startSession', 'executeStep', 'waitForTruthEvent', 'captureEvidence', 'stopSession', 'finalize'],
|
|
59
|
+
};
|
|
49
60
|
/**
|
|
50
61
|
* Reads and parses a JSON object from disk.
|
|
51
62
|
*
|
|
@@ -753,8 +764,32 @@ async function runProfileAndroid(args, options = {}) {
|
|
|
753
764
|
const config = readJson(path.resolve(args.config));
|
|
754
765
|
const scenario = readJson(path.resolve(args.scenario));
|
|
755
766
|
const runId = typeof args['run-id'] === 'string' ? args['run-id'] : createRunId();
|
|
767
|
+
const scenarioName = resolveProfileScenarioName({ scenario, scenarioPath: path.resolve(args.scenario) });
|
|
768
|
+
const artifactRoot = resolveArtifactRoot({
|
|
769
|
+
args,
|
|
770
|
+
config,
|
|
771
|
+
configPath: path.resolve(args.config),
|
|
772
|
+
platform: 'android',
|
|
773
|
+
});
|
|
756
774
|
const adbCaptureEnabled = isEnabled(args['adb-capture']);
|
|
757
775
|
const agentDeviceCaptureEnabled = isEnabled(args['agent-device-capture']);
|
|
776
|
+
const driverSteps = adbCaptureEnabled ? resolveAndroidAdbDriverSteps(scenario) : [];
|
|
777
|
+
if (adbCaptureEnabled) {
|
|
778
|
+
const driverStepErrors = validateAndroidAdbDriverSteps(driverSteps);
|
|
779
|
+
if (driverStepErrors.length > 0) {
|
|
780
|
+
throw new Error(`Invalid Android adb driver step metadata: ${driverStepErrors.join(' ')}`);
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
await runProfileCompatibilityPreflight({
|
|
784
|
+
args,
|
|
785
|
+
artifactRoot,
|
|
786
|
+
platform: 'android',
|
|
787
|
+
primaryRunner: ANDROID_PROFILE_RUNNER_CAPABILITIES,
|
|
788
|
+
runDir: path.join(artifactRoot, scenarioName, runId),
|
|
789
|
+
runId,
|
|
790
|
+
scenario,
|
|
791
|
+
scenarioName,
|
|
792
|
+
});
|
|
758
793
|
const profileSessionEnabled = isEnabled(args['profile-session']);
|
|
759
794
|
const profileSessionStorageEnabled = isEnabled(args['android-profile-session-storage']);
|
|
760
795
|
const profileSessionStorageKey = readStringArgOrEnv(args['android-profile-session-storage-key'], [
|
|
@@ -793,14 +828,6 @@ async function runProfileAndroid(args, options = {}) {
|
|
|
793
828
|
'ASL_ANDROID_ADB_COMMAND_TIMEOUT_MS',
|
|
794
829
|
'ASL_EXAMPLE_ANDROID_ADB_COMMAND_TIMEOUT_MS',
|
|
795
830
|
]), 30000);
|
|
796
|
-
const scenarioName = typeof scenario.name === 'string' ? scenario.name : path.basename(args.scenario, '.json');
|
|
797
|
-
const driverSteps = adbCaptureEnabled ? resolveAndroidAdbDriverSteps(scenario) : [];
|
|
798
|
-
if (adbCaptureEnabled) {
|
|
799
|
-
const driverStepErrors = validateAndroidAdbDriverSteps(driverSteps);
|
|
800
|
-
if (driverStepErrors.length > 0) {
|
|
801
|
-
throw new Error(`Invalid Android adb driver step metadata: ${driverStepErrors.join(' ')}`);
|
|
802
|
-
}
|
|
803
|
-
}
|
|
804
831
|
const profileSessionCommands = profileSessionEnabled ? resolveAndroidAdbProfileCommands(scenario) : [];
|
|
805
832
|
const commandWaitMs = parsePositiveInteger(readScalarArg(args['command-wait-ms']), 250);
|
|
806
833
|
const profileSessionDeepLinks = profileSessionEnabled && !profileSessionStorageEnabled
|
|
@@ -18,7 +18,7 @@ const fs = require('node:fs');
|
|
|
18
18
|
const path = require('node:path');
|
|
19
19
|
const { hasHelpFlag } = require('./cli');
|
|
20
20
|
const { buildScenarioExecutionPlan } = require('../core/execution-plan');
|
|
21
|
-
const { buildProfileHealth, buildProfileVerdict, buildVerdictBudgetChecks, parseArgs, readScalarArg, runProfileCli, runProfileMobile, usage, } = require('./profile-mobile');
|
|
21
|
+
const { buildProfileHealth, buildProfileVerdict, buildVerdictBudgetChecks, parseArgs, readScalarArg, resolveArtifactRoot, resolveProfileScenarioName, runProfileCompatibilityPreflight, runProfileCli, runProfileMobile, usage, } = require('./profile-mobile');
|
|
22
22
|
exports.buildProfileHealth = buildProfileHealth;
|
|
23
23
|
exports.buildProfileVerdict = buildProfileVerdict;
|
|
24
24
|
exports.buildVerdictBudgetChecks = buildVerdictBudgetChecks;
|
|
@@ -52,6 +52,17 @@ const MANIFEST_LIFECYCLE_PHASES = new Set([
|
|
|
52
52
|
'reboot',
|
|
53
53
|
'relaunch',
|
|
54
54
|
]);
|
|
55
|
+
const IOS_PROFILE_RUNNER_CAPABILITIES = {
|
|
56
|
+
schemaVersion: '1.0.0',
|
|
57
|
+
runnerId: 'ios-simctl-profile-runner',
|
|
58
|
+
kind: 'primary',
|
|
59
|
+
platforms: ['ios'],
|
|
60
|
+
capabilities: ['launch', 'sessionControl', 'command', 'logCapture', 'artifactWrite'],
|
|
61
|
+
driverActions: ['tap', 'scroll', 'assertVisible', 'inspectTree', 'screenshot', 'readLogs'],
|
|
62
|
+
artifactOutputs: ['logs', 'signals', 'screenshot', 'uiTree'],
|
|
63
|
+
uiContexts: ['app'],
|
|
64
|
+
lifecycle: ['prepare', 'launch', 'startSession', 'executeStep', 'waitForTruthEvent', 'captureEvidence', 'stopSession', 'finalize'],
|
|
65
|
+
};
|
|
55
66
|
/**
|
|
56
67
|
* Reads and parses a JSON object from disk.
|
|
57
68
|
*
|
|
@@ -608,6 +619,23 @@ async function runProfileIos(args, options = {}) {
|
|
|
608
619
|
const config = readJson(path.resolve(args.config));
|
|
609
620
|
const scenario = readJson(path.resolve(args.scenario));
|
|
610
621
|
const runId = typeof args['run-id'] === 'string' ? args['run-id'] : createRunId();
|
|
622
|
+
const scenarioName = resolveProfileScenarioName({ scenario, scenarioPath: path.resolve(args.scenario) });
|
|
623
|
+
const artifactRoot = resolveArtifactRoot({
|
|
624
|
+
args,
|
|
625
|
+
config,
|
|
626
|
+
configPath: path.resolve(args.config),
|
|
627
|
+
platform: 'ios',
|
|
628
|
+
});
|
|
629
|
+
await runProfileCompatibilityPreflight({
|
|
630
|
+
args,
|
|
631
|
+
artifactRoot,
|
|
632
|
+
platform: 'ios',
|
|
633
|
+
primaryRunner: IOS_PROFILE_RUNNER_CAPABILITIES,
|
|
634
|
+
runDir: path.join(artifactRoot, scenarioName, runId),
|
|
635
|
+
runId,
|
|
636
|
+
scenario,
|
|
637
|
+
scenarioName,
|
|
638
|
+
});
|
|
611
639
|
const profileSessionEnabled = isEnabled(args['profile-session']);
|
|
612
640
|
const profileSessionStorageEnabled = isEnabled(args['profile-session-storage']);
|
|
613
641
|
const profileSessionStorageKey = readStringArgOrEnv(args['ios-profile-session-storage-key'], [
|
|
@@ -638,7 +666,6 @@ async function runProfileIos(args, options = {}) {
|
|
|
638
666
|
'ASL_IOS_DEV_CLIENT_WAIT_MS',
|
|
639
667
|
'ASL_EXAMPLE_IOS_DEV_CLIENT_WAIT_MS',
|
|
640
668
|
]), 1000);
|
|
641
|
-
const scenarioName = typeof scenario.name === 'string' ? scenario.name : path.basename(args.scenario, '.json');
|
|
642
669
|
const profileSessionCommands = profileSessionEnabled ? resolveIosSimctlProfileCommands(scenario) : [];
|
|
643
670
|
const iosDevClientDeepLinks = iosDevClientUrl
|
|
644
671
|
? [
|
|
@@ -10,6 +10,7 @@ type CliArgs = {
|
|
|
10
10
|
events?: string | boolean;
|
|
11
11
|
out?: string | boolean;
|
|
12
12
|
provider?: CliArgValue;
|
|
13
|
+
'profile-session-entries'?: string | boolean;
|
|
13
14
|
'run-id'?: string | boolean;
|
|
14
15
|
signal?: CliArgValue;
|
|
15
16
|
[key: string]: CliArgValue | undefined;
|
|
@@ -19,6 +20,16 @@ type ProfileRunResult = {
|
|
|
19
20
|
health: Record<string, unknown>;
|
|
20
21
|
verdict: Record<string, unknown>;
|
|
21
22
|
};
|
|
23
|
+
type CompatibilityPreflightOptions = {
|
|
24
|
+
args: CliArgs;
|
|
25
|
+
artifactRoot: string;
|
|
26
|
+
platform: ProfilePlatform;
|
|
27
|
+
primaryRunner: Record<string, unknown>;
|
|
28
|
+
runDir: string;
|
|
29
|
+
runId: string;
|
|
30
|
+
scenario: Record<string, unknown>;
|
|
31
|
+
scenarioName: string;
|
|
32
|
+
};
|
|
22
33
|
type ProfilePlatform = 'android' | 'ios';
|
|
23
34
|
type ProfileMobileOptions = {
|
|
24
35
|
commandTransport?: string;
|
|
@@ -259,6 +270,26 @@ declare function resolveEventLogPath({ args, platform }: {
|
|
|
259
270
|
args: CliArgs;
|
|
260
271
|
platform: ProfilePlatform;
|
|
261
272
|
}): string | null;
|
|
273
|
+
/**
|
|
274
|
+
* Resolves the profile scenario name from modern or legacy scenario identity fields.
|
|
275
|
+
*
|
|
276
|
+
* @param {{scenario: Record<string, unknown>, scenarioPath: string}} options
|
|
277
|
+
* @returns {string}
|
|
278
|
+
*/
|
|
279
|
+
declare function resolveProfileScenarioName({ scenario, scenarioPath, }: {
|
|
280
|
+
scenario: Record<string, unknown>;
|
|
281
|
+
scenarioPath: string;
|
|
282
|
+
}): string;
|
|
283
|
+
/**
|
|
284
|
+
* Runs planner compatibility before a live profile capture starts.
|
|
285
|
+
*
|
|
286
|
+
* Failed compatibility writes classified artifacts in the profile run folder so
|
|
287
|
+
* agents can stop before adb, simctl, or provider work consumes runtime time.
|
|
288
|
+
*
|
|
289
|
+
* @param {CompatibilityPreflightOptions} options
|
|
290
|
+
* @returns {Promise<void>}
|
|
291
|
+
*/
|
|
292
|
+
declare function runProfileCompatibilityPreflight({ args, artifactRoot, platform, primaryRunner, runDir, runId, scenario, scenarioName, }: CompatibilityPreflightOptions): Promise<void>;
|
|
262
293
|
/**
|
|
263
294
|
* Creates a stable fingerprint for the scenario contract used by one run.
|
|
264
295
|
*
|
|
@@ -286,5 +317,5 @@ declare function runProfileCli({ argv, binaryName, defaultDriver, platform, }: {
|
|
|
286
317
|
defaultDriver: string;
|
|
287
318
|
platform: ProfilePlatform;
|
|
288
319
|
}): Promise<void>;
|
|
289
|
-
export { buildProfileHealth, buildProviderCommandFailureHealth, buildProfileVerdict, buildVerdictBudgetChecks, parseArgs, buildEvidenceAttachmentManifest, readScalarArg, resolveAppId, resolveArtifactRoot, resolveAttachedEvidence, resolveComparisonLane, resolveEventLogPath, resolveInteractionDriver, runProfileCli, runProfileMobile, hashScenarioContract, usage, };
|
|
320
|
+
export { buildProfileHealth, buildProviderCommandFailureHealth, buildProfileVerdict, buildVerdictBudgetChecks, parseArgs, buildEvidenceAttachmentManifest, readScalarArg, resolveAppId, resolveArtifactRoot, resolveAttachedEvidence, resolveComparisonLane, resolveEventLogPath, resolveInteractionDriver, resolveProfileScenarioName, runProfileCompatibilityPreflight, runProfileCli, runProfileMobile, hashScenarioContract, usage, };
|
|
290
321
|
export type { CliArgs, ProfileMobileOptions, ProfilePlatform, ProfileRunResult, };
|
|
@@ -14,11 +14,13 @@ exports.resolveAttachedEvidence = resolveAttachedEvidence;
|
|
|
14
14
|
exports.resolveComparisonLane = resolveComparisonLane;
|
|
15
15
|
exports.resolveEventLogPath = resolveEventLogPath;
|
|
16
16
|
exports.resolveInteractionDriver = resolveInteractionDriver;
|
|
17
|
+
exports.resolveProfileScenarioName = resolveProfileScenarioName;
|
|
18
|
+
exports.runProfileCompatibilityPreflight = runProfileCompatibilityPreflight;
|
|
17
19
|
exports.runProfileCli = runProfileCli;
|
|
18
20
|
exports.runProfileMobile = runProfileMobile;
|
|
19
21
|
exports.hashScenarioContract = hashScenarioContract;
|
|
20
22
|
exports.usage = usage;
|
|
21
|
-
const {
|
|
23
|
+
const { spawn } = require('node:child_process');
|
|
22
24
|
const fs = require('node:fs');
|
|
23
25
|
const fsp = require('node:fs/promises');
|
|
24
26
|
const path = require('node:path');
|
|
@@ -26,12 +28,14 @@ const crypto = require('node:crypto');
|
|
|
26
28
|
const { buildAgentSummaryMarkdown } = require('../core/agent-summary');
|
|
27
29
|
const { createArtifactLayout } = require('../core/artifact-layout');
|
|
28
30
|
const { writeJsonArtifact, writeTextArtifact } = require('../core/artifact-writer');
|
|
31
|
+
const { buildCompatibilityHealth, buildUnevaluatedVerdict, evaluateRunnerCompatibility, } = require('../core/planner');
|
|
29
32
|
const { buildBudgetVerdict, buildCausalRun, buildCausalTimeline, buildManifest, buildMetricsFromProfileEvents, buildSummaryMarkdown, extractProfileEvents, extractProfileSessionEntries, } = require('../core/artifact-contract');
|
|
30
33
|
const { SCHEMAS, assertValidJson } = require('../core/schema-validator');
|
|
31
34
|
const { writeUsage } = require('./cli');
|
|
32
35
|
const CAPTURE_EVIDENCE_KINDS = new Set(['screenshot', 'uiTree', 'video']);
|
|
33
36
|
const PROVIDER_EVIDENCE_KINDS = new Set(['accessibility', 'logs', 'profiler']);
|
|
34
37
|
const SIGNAL_EVIDENCE_KINDS = new Set(['js', 'memory', 'network']);
|
|
38
|
+
const DEFAULT_PROVIDER_COMMAND_TIMEOUT_MS = 180_000;
|
|
35
39
|
/**
|
|
36
40
|
* Prints CLI usage to stderr.
|
|
37
41
|
*
|
|
@@ -145,6 +149,15 @@ function readRepeatableArgValues(args, key) {
|
|
|
145
149
|
return entry;
|
|
146
150
|
});
|
|
147
151
|
}
|
|
152
|
+
/**
|
|
153
|
+
* Reads whether a boolean-style CLI flag was supplied.
|
|
154
|
+
*
|
|
155
|
+
* @param {CliArgValue | undefined} value
|
|
156
|
+
* @returns {boolean}
|
|
157
|
+
*/
|
|
158
|
+
function isEnabled(value) {
|
|
159
|
+
return value === true || value === 'true';
|
|
160
|
+
}
|
|
148
161
|
/**
|
|
149
162
|
* Parses a `kind:path` evidence attachment value.
|
|
150
163
|
*
|
|
@@ -176,23 +189,83 @@ async function hashFileSha256(filePath) {
|
|
|
176
189
|
return crypto.createHash('sha256').update(content).digest('hex');
|
|
177
190
|
}
|
|
178
191
|
/**
|
|
179
|
-
*
|
|
192
|
+
* Resolves the timeout applied to provider commands.
|
|
180
193
|
*
|
|
181
|
-
* @
|
|
194
|
+
* @returns {number}
|
|
195
|
+
*/
|
|
196
|
+
function resolveProviderCommandTimeoutMs() {
|
|
197
|
+
return readPositiveInteger(process.env.ASL_PROVIDER_COMMAND_TIMEOUT_MS, DEFAULT_PROVIDER_COMMAND_TIMEOUT_MS);
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Runs one provider command without a shell, streaming output to raw files.
|
|
201
|
+
*
|
|
202
|
+
* @param {{command: string, args: string[], cwd?: string, env?: Record<string, string>, stderrPath: string, stdoutPath: string, timeoutMs: number}} options
|
|
182
203
|
* @returns {Promise<ProviderCommandResult>}
|
|
183
204
|
*/
|
|
184
|
-
function execProviderCommand({ args, command, cwd, env, }) {
|
|
205
|
+
function execProviderCommand({ args, command, cwd, env, stderrPath, stdoutPath, timeoutMs, }) {
|
|
185
206
|
return new Promise((resolve) => {
|
|
186
|
-
|
|
207
|
+
const child = spawn(command, args, {
|
|
187
208
|
...(cwd ? { cwd } : {}),
|
|
188
209
|
env: env ? { ...process.env, ...env } : process.env,
|
|
189
|
-
|
|
210
|
+
shell: false,
|
|
211
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
212
|
+
});
|
|
213
|
+
const stdoutChunks = [];
|
|
214
|
+
const stderrChunks = [];
|
|
215
|
+
let timedOut = false;
|
|
216
|
+
let settled = false;
|
|
217
|
+
const timeout = setTimeout(() => {
|
|
218
|
+
timedOut = true;
|
|
219
|
+
child.kill('SIGTERM');
|
|
220
|
+
setTimeout(() => {
|
|
221
|
+
if (!settled) {
|
|
222
|
+
child.kill('SIGKILL');
|
|
223
|
+
}
|
|
224
|
+
}, 1000).unref();
|
|
225
|
+
}, timeoutMs);
|
|
226
|
+
timeout.unref();
|
|
227
|
+
child.stdout.on('data', (chunk) => {
|
|
228
|
+
stdoutChunks.push(chunk);
|
|
229
|
+
fs.appendFileSync(stdoutPath, chunk);
|
|
230
|
+
});
|
|
231
|
+
child.stderr.on('data', (chunk) => {
|
|
232
|
+
stderrChunks.push(chunk);
|
|
233
|
+
fs.appendFileSync(stderrPath, chunk);
|
|
234
|
+
});
|
|
235
|
+
child.on('error', (error) => {
|
|
236
|
+
if (settled) {
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
clearTimeout(timeout);
|
|
240
|
+
settled = true;
|
|
241
|
+
const stderr = error.message;
|
|
242
|
+
fs.appendFileSync(stderrPath, `${stderr}\n`, 'utf8');
|
|
243
|
+
resolve({
|
|
244
|
+
args,
|
|
245
|
+
command,
|
|
246
|
+
exitCode: 1,
|
|
247
|
+
signal: null,
|
|
248
|
+
stderr,
|
|
249
|
+
stdout: Buffer.concat(stdoutChunks).toString('utf8'),
|
|
250
|
+
timedOut,
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
child.on('close', (exitCode, signal) => {
|
|
254
|
+
if (settled) {
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
clearTimeout(timeout);
|
|
258
|
+
settled = true;
|
|
259
|
+
const stdout = Buffer.concat(stdoutChunks).toString('utf8');
|
|
260
|
+
const stderr = Buffer.concat(stderrChunks).toString('utf8');
|
|
190
261
|
resolve({
|
|
191
262
|
args,
|
|
192
263
|
command,
|
|
193
|
-
exitCode:
|
|
264
|
+
exitCode: typeof exitCode === 'number' ? exitCode : timedOut ? 124 : 1,
|
|
265
|
+
signal,
|
|
194
266
|
stderr,
|
|
195
267
|
stdout,
|
|
268
|
+
timedOut,
|
|
196
269
|
});
|
|
197
270
|
});
|
|
198
271
|
});
|
|
@@ -384,32 +457,66 @@ async function executeProviderCommands({ args, layout, platform, runDir, runId,
|
|
|
384
457
|
? resolveProviderPath({ context, manifestDir, value: providerCommand.cwd })
|
|
385
458
|
: manifestDir;
|
|
386
459
|
const resolvedEnv = Object.fromEntries(Object.entries(providerCommand.env ?? {}).map(([key, value]) => [key, applyProviderPlaceholders(value, context)]));
|
|
460
|
+
const commandRecordFileName = `${providerId}-${providerCommand.id}.json`;
|
|
461
|
+
const stdoutFileName = `${providerId}-${providerCommand.id}.stdout.txt`;
|
|
462
|
+
const stderrFileName = `${providerId}-${providerCommand.id}.stderr.txt`;
|
|
463
|
+
const commandRecordPath = path.join(commandRecordDir, commandRecordFileName);
|
|
464
|
+
const stdoutPath = path.join(commandRecordDir, stdoutFileName);
|
|
465
|
+
const stderrPath = path.join(commandRecordDir, stderrFileName);
|
|
466
|
+
const timeoutMs = resolveProviderCommandTimeoutMs();
|
|
467
|
+
const startedAt = new Date().toISOString();
|
|
468
|
+
await fsp.writeFile(stdoutPath, '', 'utf8');
|
|
469
|
+
await fsp.writeFile(stderrPath, '', 'utf8');
|
|
470
|
+
await fsp.writeFile(commandRecordPath, `${JSON.stringify({
|
|
471
|
+
args: resolvedArgs,
|
|
472
|
+
command: resolvedCommand,
|
|
473
|
+
phase: providerCommand.phase,
|
|
474
|
+
providerId,
|
|
475
|
+
startedAt,
|
|
476
|
+
status: 'started',
|
|
477
|
+
stderrPath: `raw/provider-commands/${stderrFileName}`,
|
|
478
|
+
stdoutPath: `raw/provider-commands/${stdoutFileName}`,
|
|
479
|
+
timeoutMs,
|
|
480
|
+
}, null, 2)}\n`, 'utf8');
|
|
387
481
|
const commandResult = await execProviderCommand({
|
|
388
482
|
args: resolvedArgs,
|
|
389
483
|
command: resolvedCommand,
|
|
390
484
|
cwd: resolvedCwd,
|
|
391
485
|
env: resolvedEnv,
|
|
486
|
+
stderrPath,
|
|
487
|
+
stdoutPath,
|
|
488
|
+
timeoutMs,
|
|
392
489
|
});
|
|
393
|
-
const commandRecordFileName = `${providerId}-${providerCommand.id}.json`;
|
|
394
|
-
const commandRecordPath = path.join(commandRecordDir, commandRecordFileName);
|
|
395
490
|
await fsp.writeFile(commandRecordPath, `${JSON.stringify({
|
|
396
491
|
args: commandResult.args,
|
|
397
492
|
command: commandResult.command,
|
|
493
|
+
endedAt: new Date().toISOString(),
|
|
398
494
|
exitCode: commandResult.exitCode,
|
|
399
495
|
phase: providerCommand.phase,
|
|
400
496
|
providerId,
|
|
497
|
+
signal: commandResult.signal,
|
|
401
498
|
stderr: commandResult.stderr,
|
|
499
|
+
stderrPath: `raw/provider-commands/${stderrFileName}`,
|
|
500
|
+
status: commandResult.timedOut ? 'timed_out' : commandResult.exitCode === 0 ? 'completed' : 'failed',
|
|
402
501
|
stdout: commandResult.stdout,
|
|
502
|
+
stdoutPath: `raw/provider-commands/${stdoutFileName}`,
|
|
503
|
+
timedOut: commandResult.timedOut,
|
|
504
|
+
timeoutMs,
|
|
403
505
|
}, null, 2)}\n`, 'utf8');
|
|
404
506
|
if (commandResult.exitCode !== 0) {
|
|
507
|
+
const timedOut = commandResult.timedOut;
|
|
405
508
|
failures.push({
|
|
406
509
|
commandId: providerCommand.id,
|
|
407
|
-
code: 'provider_command_failed',
|
|
510
|
+
code: timedOut ? 'provider_liveness_timeout' : 'provider_command_failed',
|
|
408
511
|
exitCode: commandResult.exitCode,
|
|
409
|
-
message:
|
|
512
|
+
message: timedOut
|
|
513
|
+
? `Evidence provider command ${providerId}/${providerCommand.id} did not finish before the ${timeoutMs}ms timeout.`
|
|
514
|
+
: `Evidence provider command ${providerId}/${providerCommand.id} failed with exit code ${commandResult.exitCode}.`,
|
|
410
515
|
name: 'evidence_provider_command_completed',
|
|
411
|
-
nextAction:
|
|
412
|
-
|
|
516
|
+
nextAction: timedOut
|
|
517
|
+
? `Inspect raw/provider-commands/${commandRecordFileName}, raw/provider-commands/${stdoutFileName}, and raw/provider-commands/${stderrFileName}; fix the provider liveness issue or increase ASL_PROVIDER_COMMAND_TIMEOUT_MS only if the provider is making progress.`
|
|
518
|
+
: `Inspect raw/provider-commands/${commandRecordFileName}, fix the provider command or its environment, then rerun the profile.`,
|
|
519
|
+
nextActionCode: timedOut ? 'fix_provider_liveness' : 'fix_provider_command',
|
|
413
520
|
phase: providerCommand.phase,
|
|
414
521
|
providerId,
|
|
415
522
|
rawPath: `raw/provider-commands/${commandRecordFileName}`,
|
|
@@ -608,6 +715,127 @@ function toPortablePathReference(targetPath) {
|
|
|
608
715
|
}
|
|
609
716
|
return path.basename(targetPath);
|
|
610
717
|
}
|
|
718
|
+
/**
|
|
719
|
+
* Resolves the evidence input mode before profile parsing starts.
|
|
720
|
+
*
|
|
721
|
+
* @param {{args: CliArgs, platform: ProfilePlatform}} options
|
|
722
|
+
* @returns {string}
|
|
723
|
+
*/
|
|
724
|
+
function resolveProfileInputMode({ args, platform }) {
|
|
725
|
+
if (typeof args.events === 'string') {
|
|
726
|
+
return 'fixture-event-log';
|
|
727
|
+
}
|
|
728
|
+
if (platform === 'android') {
|
|
729
|
+
if (typeof args['adb-artifacts'] === 'string') {
|
|
730
|
+
return 'adb-sidecar';
|
|
731
|
+
}
|
|
732
|
+
if (isEnabled(args['adb-capture'])) {
|
|
733
|
+
return 'adb-live-capture';
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
if (typeof args['simctl-artifacts'] === 'string') {
|
|
737
|
+
return 'simctl-sidecar';
|
|
738
|
+
}
|
|
739
|
+
if (isEnabled(args['simctl-capture'])) {
|
|
740
|
+
return 'simctl-live-capture';
|
|
741
|
+
}
|
|
742
|
+
return 'no-profile-evidence';
|
|
743
|
+
}
|
|
744
|
+
/**
|
|
745
|
+
* Reads unique scenario step kinds for early operator visibility.
|
|
746
|
+
*
|
|
747
|
+
* @param {Record<string, unknown>} scenario
|
|
748
|
+
* @returns {string[]}
|
|
749
|
+
*/
|
|
750
|
+
function readScenarioStepKinds(scenario) {
|
|
751
|
+
if (!Array.isArray(scenario.steps)) {
|
|
752
|
+
return [];
|
|
753
|
+
}
|
|
754
|
+
return Array.from(new Set(scenario.steps
|
|
755
|
+
.filter(isRecord)
|
|
756
|
+
.map((step) => step.kind)
|
|
757
|
+
.filter((kind) => typeof kind === 'string')))
|
|
758
|
+
.sort();
|
|
759
|
+
}
|
|
760
|
+
/**
|
|
761
|
+
* Reads wait milestone ids from scenario steps.
|
|
762
|
+
*
|
|
763
|
+
* @param {Record<string, unknown>} scenario
|
|
764
|
+
* @returns {string[]}
|
|
765
|
+
*/
|
|
766
|
+
function readScenarioWaitMilestones(scenario) {
|
|
767
|
+
if (!Array.isArray(scenario.steps)) {
|
|
768
|
+
return [];
|
|
769
|
+
}
|
|
770
|
+
return Array.from(new Set(scenario.steps
|
|
771
|
+
.filter(isRecord)
|
|
772
|
+
.filter((step) => step.kind === 'waitForMilestone')
|
|
773
|
+
.map((step) => step.milestone)
|
|
774
|
+
.filter((milestone) => typeof milestone === 'string')))
|
|
775
|
+
.sort();
|
|
776
|
+
}
|
|
777
|
+
/**
|
|
778
|
+
* Builds the early run plan artifact before provider commands or event parsing.
|
|
779
|
+
*
|
|
780
|
+
* @param {{args: CliArgs, artifactRoot: string, comparisonLane?: string | undefined, expectedIterations: number, interactionDriver: string, layout: ReturnType<typeof createArtifactLayout>, milestoneEventsPerIteration: number, options: ProfileMobileOptions, profileScenario: Record<string, unknown>, runDir: string, runId: string, scenarioHash: string, scenarioPath: string}} options
|
|
781
|
+
* @returns {ProfileRunPlan}
|
|
782
|
+
*/
|
|
783
|
+
function buildProfileRunPlan({ args, artifactRoot, comparisonLane, expectedIterations, interactionDriver, layout, milestoneEventsPerIteration, options, profileScenario, runDir, runId, scenarioHash, scenarioPath, }) {
|
|
784
|
+
return {
|
|
785
|
+
artifactVersion: '1.0.0',
|
|
786
|
+
runId,
|
|
787
|
+
scenarioId: resolveProfileScenarioName({ scenario: profileScenario, scenarioPath }),
|
|
788
|
+
scenarioHash,
|
|
789
|
+
platform: options.platform,
|
|
790
|
+
inputMode: resolveProfileInputMode({ args, platform: options.platform }),
|
|
791
|
+
artifactRoot,
|
|
792
|
+
runDir,
|
|
793
|
+
interactionDriver,
|
|
794
|
+
...(comparisonLane ? { comparisonLane } : {}),
|
|
795
|
+
expectedIterations,
|
|
796
|
+
milestoneEventsPerIteration,
|
|
797
|
+
commandTransport: resolveCommandTransport({ args, interactionDriver, options }),
|
|
798
|
+
providers: readRepeatableArgValues(args, 'provider').map((providerPath) => ({
|
|
799
|
+
path: toPortablePathReference(path.resolve(providerPath)),
|
|
800
|
+
})),
|
|
801
|
+
requestedDiagnostics: {
|
|
802
|
+
required: Array.from(readScenarioStringSet(profileScenario, ['artifacts', 'required'])).sort(),
|
|
803
|
+
optional: Array.from(readScenarioStringSet(profileScenario, ['artifacts', 'optional'])).sort(),
|
|
804
|
+
},
|
|
805
|
+
scenarioShape: {
|
|
806
|
+
budgets: Array.isArray(profileScenario.budgets) ? profileScenario.budgets.length : 0,
|
|
807
|
+
steps: Array.isArray(profileScenario.steps) ? profileScenario.steps.length : 0,
|
|
808
|
+
stepKinds: readScenarioStepKinds(profileScenario),
|
|
809
|
+
waitForMilestones: readScenarioWaitMilestones(profileScenario),
|
|
810
|
+
},
|
|
811
|
+
evidenceSources: {
|
|
812
|
+
...(typeof args.events === 'string' ? { events: toPortablePathReference(path.resolve(args.events)) } : {}),
|
|
813
|
+
...(typeof args['profile-session-entries'] === 'string'
|
|
814
|
+
? { profileSessionEntries: toPortablePathReference(path.resolve(args['profile-session-entries'])) }
|
|
815
|
+
: {}),
|
|
816
|
+
...(typeof args['adb-artifacts'] === 'string'
|
|
817
|
+
? { adbArtifacts: toPortablePathReference(path.resolve(args['adb-artifacts'])) }
|
|
818
|
+
: {}),
|
|
819
|
+
...(typeof args['simctl-artifacts'] === 'string'
|
|
820
|
+
? { simctlArtifacts: toPortablePathReference(path.resolve(args['simctl-artifacts'])) }
|
|
821
|
+
: {}),
|
|
822
|
+
adbCapture: isEnabled(args['adb-capture']),
|
|
823
|
+
simctlCapture: isEnabled(args['simctl-capture']),
|
|
824
|
+
signals: readRepeatableArgValues(args, 'signal').length,
|
|
825
|
+
captures: readRepeatableArgValues(args, 'capture').length,
|
|
826
|
+
},
|
|
827
|
+
};
|
|
828
|
+
}
|
|
829
|
+
/**
|
|
830
|
+
* Writes the early profile run plan artifact and a compact status heartbeat.
|
|
831
|
+
*
|
|
832
|
+
* @param {{layout: ReturnType<typeof createArtifactLayout>, plan: ProfileRunPlan}} options
|
|
833
|
+
* @returns {Promise<void>}
|
|
834
|
+
*/
|
|
835
|
+
async function writeProfileRunPlan({ layout, plan, }) {
|
|
836
|
+
await fsp.writeFile(layout.runPlan, `${JSON.stringify(plan, null, 2)}\n`, 'utf8');
|
|
837
|
+
process.stderr.write(`profile run plan: ${plan.platform}/${plan.scenarioId} mode=${plan.inputMode} providers=${plan.providers.length} requiredDiagnostics=${plan.requestedDiagnostics.required.length} runPlan=${path.relative(process.cwd(), layout.runPlan)}\n`);
|
|
838
|
+
}
|
|
611
839
|
/**
|
|
612
840
|
* Returns a path reference from one run folder to an external sidecar.
|
|
613
841
|
*
|
|
@@ -1535,6 +1763,66 @@ function resolveProfileScenarioName({ scenario, scenarioPath, }) {
|
|
|
1535
1763
|
}
|
|
1536
1764
|
return path.basename(scenarioPath, '.json');
|
|
1537
1765
|
}
|
|
1766
|
+
/**
|
|
1767
|
+
* Reads evidence-provider manifests for profile compatibility preflight.
|
|
1768
|
+
*
|
|
1769
|
+
* @param {CliArgs} args
|
|
1770
|
+
* @returns {Record<string, unknown>[]}
|
|
1771
|
+
*/
|
|
1772
|
+
function readEvidenceProviderManifests(args) {
|
|
1773
|
+
return readRepeatableArgValues(args, 'provider').map((providerPath, index) => assertValidJson(readJson(path.resolve(providerPath)), SCHEMAS.runnerCapabilities, `Evidence provider manifest ${index + 1}`));
|
|
1774
|
+
}
|
|
1775
|
+
/**
|
|
1776
|
+
* Runs planner compatibility before a live profile capture starts.
|
|
1777
|
+
*
|
|
1778
|
+
* Failed compatibility writes classified artifacts in the profile run folder so
|
|
1779
|
+
* agents can stop before adb, simctl, or provider work consumes runtime time.
|
|
1780
|
+
*
|
|
1781
|
+
* @param {CompatibilityPreflightOptions} options
|
|
1782
|
+
* @returns {Promise<void>}
|
|
1783
|
+
*/
|
|
1784
|
+
async function runProfileCompatibilityPreflight({ args, artifactRoot, platform, primaryRunner, runDir, runId, scenario, scenarioName, }) {
|
|
1785
|
+
const layout = createArtifactLayout({ outputDir: runDir });
|
|
1786
|
+
const compatibility = evaluateRunnerCompatibility({
|
|
1787
|
+
scenario,
|
|
1788
|
+
runner: primaryRunner,
|
|
1789
|
+
evidenceProviders: readEvidenceProviderManifests(args),
|
|
1790
|
+
platform,
|
|
1791
|
+
});
|
|
1792
|
+
await writeJsonArtifact({
|
|
1793
|
+
filePath: layout.plannerCompatibility,
|
|
1794
|
+
value: compatibility,
|
|
1795
|
+
schema: {
|
|
1796
|
+
type: 'object',
|
|
1797
|
+
additionalProperties: true,
|
|
1798
|
+
},
|
|
1799
|
+
label: 'Planner compatibility artifact',
|
|
1800
|
+
});
|
|
1801
|
+
if (compatibility.compatible) {
|
|
1802
|
+
process.stderr.write(`profile preflight passed: ${platform}/${scenarioName} artifactRoot=${artifactRoot} planner=${path.relative(process.cwd(), layout.plannerCompatibility)}\n`);
|
|
1803
|
+
return;
|
|
1804
|
+
}
|
|
1805
|
+
const health = buildCompatibilityHealth({ scenario, runId, compatibility });
|
|
1806
|
+
const verdict = buildUnevaluatedVerdict({ scenario, runId, health });
|
|
1807
|
+
const agentSummary = buildAgentSummaryMarkdown({ health, verdict });
|
|
1808
|
+
await writeJsonArtifact({
|
|
1809
|
+
filePath: layout.health,
|
|
1810
|
+
value: health,
|
|
1811
|
+
schema: SCHEMAS.health,
|
|
1812
|
+
label: 'Health artifact',
|
|
1813
|
+
});
|
|
1814
|
+
await writeJsonArtifact({
|
|
1815
|
+
filePath: layout.verdict,
|
|
1816
|
+
value: verdict,
|
|
1817
|
+
schema: SCHEMAS.verdict,
|
|
1818
|
+
label: 'Verdict artifact',
|
|
1819
|
+
});
|
|
1820
|
+
await writeTextArtifact({
|
|
1821
|
+
filePath: layout.agentSummary,
|
|
1822
|
+
content: agentSummary,
|
|
1823
|
+
});
|
|
1824
|
+
throw new Error(`Profile compatibility preflight failed; inspect ${runDir}/agent-summary.md.`);
|
|
1825
|
+
}
|
|
1538
1826
|
/**
|
|
1539
1827
|
* Serializes JSON with stable object key ordering for reproducible hashes.
|
|
1540
1828
|
*
|
|
@@ -1992,6 +2280,22 @@ async function runProfileMobile(args, options) {
|
|
|
1992
2280
|
await ensureDir(layout.signals.js);
|
|
1993
2281
|
await ensureDir(layout.signals.memory);
|
|
1994
2282
|
await ensureDir(layout.signals.network);
|
|
2283
|
+
const runPlan = buildProfileRunPlan({
|
|
2284
|
+
args,
|
|
2285
|
+
artifactRoot,
|
|
2286
|
+
comparisonLane,
|
|
2287
|
+
expectedIterations,
|
|
2288
|
+
interactionDriver,
|
|
2289
|
+
layout,
|
|
2290
|
+
milestoneEventsPerIteration,
|
|
2291
|
+
options,
|
|
2292
|
+
profileScenario,
|
|
2293
|
+
runDir,
|
|
2294
|
+
runId,
|
|
2295
|
+
scenarioHash,
|
|
2296
|
+
scenarioPath,
|
|
2297
|
+
});
|
|
2298
|
+
await writeProfileRunPlan({ layout, plan: runPlan });
|
|
1995
2299
|
const providerExecution = await executeProviderCommands({
|
|
1996
2300
|
args,
|
|
1997
2301
|
layout,
|
|
@@ -15,6 +15,14 @@ type FailedRunOutput = {
|
|
|
15
15
|
* @returns {NodeJS.ProcessEnv}
|
|
16
16
|
*/
|
|
17
17
|
declare function createRehearsalEnv(tempRoot: string): NodeJS.ProcessEnv;
|
|
18
|
+
/**
|
|
19
|
+
* Resolves an existing tarball from the environment, when a parent release gate
|
|
20
|
+
* has already packed the current package.
|
|
21
|
+
*
|
|
22
|
+
* @param {NodeJS.ProcessEnv} env
|
|
23
|
+
* @returns {string | null}
|
|
24
|
+
*/
|
|
25
|
+
declare function resolveProvidedTarball(env: NodeJS.ProcessEnv): string | null;
|
|
18
26
|
/**
|
|
19
27
|
* Runs a command and returns stdout while preserving child stderr on failure.
|
|
20
28
|
*
|
|
@@ -116,4 +124,4 @@ declare function rehearseConsumerInstall({ appRoot, env, tarballPath, }: {
|
|
|
116
124
|
env: NodeJS.ProcessEnv;
|
|
117
125
|
tarballPath: string;
|
|
118
126
|
}): void;
|
|
119
|
-
export { createRehearsalEnv, mergeGeneratedScripts, packageBinPath, packPackage, readJson, rehearseConsumerInstall, replaceConfigPlaceholders, run, runExpectFailure, writeFakeArgent, writeExistingAppFixture, writeProfileEventFixtures, writeJson, };
|
|
127
|
+
export { createRehearsalEnv, mergeGeneratedScripts, packageBinPath, packPackage, readJson, rehearseConsumerInstall, replaceConfigPlaceholders, resolveProvidedTarball, run, runExpectFailure, writeFakeArgent, writeExistingAppFixture, writeProfileEventFixtures, writeJson, };
|
|
@@ -8,6 +8,7 @@ exports.packPackage = packPackage;
|
|
|
8
8
|
exports.readJson = readJson;
|
|
9
9
|
exports.rehearseConsumerInstall = rehearseConsumerInstall;
|
|
10
10
|
exports.replaceConfigPlaceholders = replaceConfigPlaceholders;
|
|
11
|
+
exports.resolveProvidedTarball = resolveProvidedTarball;
|
|
11
12
|
exports.run = run;
|
|
12
13
|
exports.runExpectFailure = runExpectFailure;
|
|
13
14
|
exports.writeFakeArgent = writeFakeArgent;
|
|
@@ -39,6 +40,22 @@ function createRehearsalEnv(tempRoot) {
|
|
|
39
40
|
env.npm_config_update_notifier = 'false';
|
|
40
41
|
return env;
|
|
41
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* Resolves an existing tarball from the environment, when a parent release gate
|
|
45
|
+
* has already packed the current package.
|
|
46
|
+
*
|
|
47
|
+
* @param {NodeJS.ProcessEnv} env
|
|
48
|
+
* @returns {string | null}
|
|
49
|
+
*/
|
|
50
|
+
function resolveProvidedTarball(env) {
|
|
51
|
+
const tarballPath = env.ASL_PACKAGE_TARBALL ? path.resolve(env.ASL_PACKAGE_TARBALL) : '';
|
|
52
|
+
if (!tarballPath) {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
assert.equal(fs.existsSync(tarballPath), true, `ASL_PACKAGE_TARBALL does not exist: ${tarballPath}`);
|
|
56
|
+
assert.match(path.basename(tarballPath), /^agent-scenario-loop-.+\.tgz$/u, 'ASL_PACKAGE_TARBALL must point to an agent-scenario-loop tarball');
|
|
57
|
+
return tarballPath;
|
|
58
|
+
}
|
|
42
59
|
/**
|
|
43
60
|
* Resolves the per-command timeout for package gate child processes.
|
|
44
61
|
*
|
|
@@ -729,7 +746,7 @@ function main() {
|
|
|
729
746
|
const appRoot = path.join(tempRoot, 'existing-mobile-app');
|
|
730
747
|
fs.mkdirSync(appRoot, { recursive: true });
|
|
731
748
|
try {
|
|
732
|
-
const tarballPath = packPackage({
|
|
749
|
+
const tarballPath = resolveProvidedTarball(env) ?? packPackage({
|
|
733
750
|
env,
|
|
734
751
|
packageRoot,
|
|
735
752
|
packDir: path.join(tempRoot, 'pack'),
|
|
@@ -36,6 +36,14 @@ declare function isAllowedPackedFile(filePath: string): boolean;
|
|
|
36
36
|
* @returns {NodeJS.ProcessEnv}
|
|
37
37
|
*/
|
|
38
38
|
declare function createSmokeEnv(tempRoot: string): NodeJS.ProcessEnv;
|
|
39
|
+
/**
|
|
40
|
+
* Resolves an existing tarball from the environment, when a parent release gate
|
|
41
|
+
* has already packed the current package.
|
|
42
|
+
*
|
|
43
|
+
* @param {NodeJS.ProcessEnv} env
|
|
44
|
+
* @returns {string | null}
|
|
45
|
+
*/
|
|
46
|
+
declare function resolveProvidedTarball(env: NodeJS.ProcessEnv): string | null;
|
|
39
47
|
/**
|
|
40
48
|
* Runs a command and returns stdout while preserving stderr for failures.
|
|
41
49
|
*
|
|
@@ -93,4 +101,4 @@ declare function typescriptBinPath(repoRoot: string): string;
|
|
|
93
101
|
* @returns {void}
|
|
94
102
|
*/
|
|
95
103
|
declare function main(): void;
|
|
96
|
-
export { createSmokeEnv, listFiles, main, isAllowedPackedFile, packageBinPath, run, runExpectFailure, typescriptBinPath, writeFakeAdb, writeSmokeRun, };
|
|
104
|
+
export { createSmokeEnv, listFiles, main, isAllowedPackedFile, packageBinPath, resolveProvidedTarball, run, runExpectFailure, typescriptBinPath, writeFakeAdb, writeSmokeRun, };
|
|
@@ -6,6 +6,7 @@ exports.listFiles = listFiles;
|
|
|
6
6
|
exports.main = main;
|
|
7
7
|
exports.isAllowedPackedFile = isAllowedPackedFile;
|
|
8
8
|
exports.packageBinPath = packageBinPath;
|
|
9
|
+
exports.resolveProvidedTarball = resolveProvidedTarball;
|
|
9
10
|
exports.run = run;
|
|
10
11
|
exports.runExpectFailure = runExpectFailure;
|
|
11
12
|
exports.typescriptBinPath = typescriptBinPath;
|
|
@@ -137,6 +138,22 @@ function createSmokeEnv(tempRoot) {
|
|
|
137
138
|
env.npm_config_update_notifier = 'false';
|
|
138
139
|
return env;
|
|
139
140
|
}
|
|
141
|
+
/**
|
|
142
|
+
* Resolves an existing tarball from the environment, when a parent release gate
|
|
143
|
+
* has already packed the current package.
|
|
144
|
+
*
|
|
145
|
+
* @param {NodeJS.ProcessEnv} env
|
|
146
|
+
* @returns {string | null}
|
|
147
|
+
*/
|
|
148
|
+
function resolveProvidedTarball(env) {
|
|
149
|
+
const tarballPath = env.ASL_PACKAGE_TARBALL ? path.resolve(env.ASL_PACKAGE_TARBALL) : '';
|
|
150
|
+
if (!tarballPath) {
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
assert.equal(fs.existsSync(tarballPath), true, `ASL_PACKAGE_TARBALL does not exist: ${tarballPath}`);
|
|
154
|
+
assert.match(path.basename(tarballPath), /^agent-scenario-loop-.+\.tgz$/u, 'ASL_PACKAGE_TARBALL must point to an agent-scenario-loop tarball');
|
|
155
|
+
return tarballPath;
|
|
156
|
+
}
|
|
140
157
|
/**
|
|
141
158
|
* Resolves the per-command timeout for package gate child processes.
|
|
142
159
|
*
|
|
@@ -556,14 +573,18 @@ function main() {
|
|
|
556
573
|
try {
|
|
557
574
|
assert.equal(fs.existsSync(path.join(repoRoot, '.npmignore')), true, 'root .npmignore must exist so npm pack never falls back to .gitignore');
|
|
558
575
|
assert.equal(fs.existsSync(path.join(repoRoot, 'examples', 'mobile-app', '.npmignore')), true, 'example app .npmignore must exist so npm pack never falls back to the app-local .gitignore');
|
|
559
|
-
const
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
576
|
+
const providedTarballPath = resolveProvidedTarball(env);
|
|
577
|
+
const tarballPath = providedTarballPath ?? (() => {
|
|
578
|
+
const packOutput = run('npm', ['pack', '--pack-destination', packDir], {
|
|
579
|
+
cwd: repoRoot,
|
|
580
|
+
env,
|
|
581
|
+
});
|
|
582
|
+
const tarballName = packOutput.trim().split(/\n/u).pop();
|
|
583
|
+
assert.ok(tarballName, 'npm pack did not print a tarball name');
|
|
584
|
+
const packedTarballPath = path.join(packDir, tarballName);
|
|
585
|
+
assert.equal(fs.existsSync(packedTarballPath), true, `missing packed tarball: ${packedTarballPath}`);
|
|
586
|
+
return packedTarballPath;
|
|
587
|
+
})();
|
|
567
588
|
run('npm', ['init', '-y'], {
|
|
568
589
|
cwd: installDir,
|
|
569
590
|
env,
|
|
@@ -1888,7 +1909,7 @@ function main() {
|
|
|
1888
1909
|
"assert.deepEqual(documentedRunnerSubpaths(), exportedRunnerSubpaths(), 'installed docs/api.md runner subpaths must match package exports');",
|
|
1889
1910
|
"assert.equal(asl.ARTIFACT_LAYOUT_VERSION, '1.0.0');",
|
|
1890
1911
|
"assert.equal(asl.ARTIFACT_FILENAMES.health, 'health.json');",
|
|
1891
|
-
"assert.deepEqual(asl.ARTIFACT_FILENAMES, { agentSummary: 'agent-summary.md', comparison: 'comparison.json', health: 'health.json', liveProof: 'live-proof.json', liveProofSet: 'live-proof-set.json', plannerCompatibility: 'planner-compatibility.json', projectValidation: 'project-validation.json', verdict: 'verdict.json' });",
|
|
1912
|
+
"assert.deepEqual(asl.ARTIFACT_FILENAMES, { agentSummary: 'agent-summary.md', comparison: 'comparison.json', health: 'health.json', liveProof: 'live-proof.json', liveProofSet: 'live-proof-set.json', plannerCompatibility: 'planner-compatibility.json', projectValidation: 'project-validation.json', runPlan: 'run-plan.json', verdict: 'verdict.json' });",
|
|
1892
1913
|
"assert.equal(asl.PROFILE_ARTIFACT_FILENAMES.metrics, 'metrics.json');",
|
|
1893
1914
|
"assert.deepEqual(asl.PROFILE_ARTIFACT_FILENAMES, { budgetVerdict: 'budget-verdict.json', causalRun: 'causal-run.json', manifest: 'manifest.json', metrics: 'metrics.json', summary: 'summary.md' });",
|
|
1894
1915
|
"assert.equal(typeof asl.createArtifactLayout, 'function');",
|
|
@@ -1901,6 +1922,7 @@ function main() {
|
|
|
1901
1922
|
"assert.equal(layout.liveProofSet, 'run/live-proof-set.json');",
|
|
1902
1923
|
"assert.equal(layout.plannerCompatibility, 'run/planner-compatibility.json');",
|
|
1903
1924
|
"assert.equal(layout.projectValidation, 'run/project-validation.json');",
|
|
1925
|
+
"assert.equal(layout.runPlan, 'run/run-plan.json');",
|
|
1904
1926
|
"assert.equal(layout.raw, 'run/raw');",
|
|
1905
1927
|
"assert.equal(layout.captures, 'run/captures');",
|
|
1906
1928
|
"assert.equal(layout.signals.js, 'run/signals/js');",
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
type RunOptions = {
|
|
3
|
+
cwd: string;
|
|
4
|
+
env: NodeJS.ProcessEnv;
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* Creates a clean npm environment for repeatable release gates.
|
|
8
|
+
*
|
|
9
|
+
* @param {string} tempRoot
|
|
10
|
+
* @returns {NodeJS.ProcessEnv}
|
|
11
|
+
*/
|
|
12
|
+
declare function createReleaseEnv(tempRoot: string): NodeJS.ProcessEnv;
|
|
13
|
+
/**
|
|
14
|
+
* Resolves the per-command timeout for release gate child processes.
|
|
15
|
+
*
|
|
16
|
+
* @param {NodeJS.ProcessEnv} env
|
|
17
|
+
* @returns {number}
|
|
18
|
+
*/
|
|
19
|
+
declare function resolveCommandTimeoutMs(env: NodeJS.ProcessEnv): number;
|
|
20
|
+
/**
|
|
21
|
+
* Runs a release command with inherited output.
|
|
22
|
+
*
|
|
23
|
+
* @param {string} command
|
|
24
|
+
* @param {string[]} args
|
|
25
|
+
* @param {RunOptions} options
|
|
26
|
+
* @returns {void}
|
|
27
|
+
*/
|
|
28
|
+
declare function run(command: string, args: string[], options: RunOptions): void;
|
|
29
|
+
/**
|
|
30
|
+
* Packs the package once for downstream release gates.
|
|
31
|
+
*
|
|
32
|
+
* @param {{env: NodeJS.ProcessEnv, packageRoot: string, packDir: string}} options
|
|
33
|
+
* @returns {string}
|
|
34
|
+
*/
|
|
35
|
+
declare function packReleasePackage({ env, packageRoot, packDir, }: {
|
|
36
|
+
env: NodeJS.ProcessEnv;
|
|
37
|
+
packageRoot: string;
|
|
38
|
+
packDir: string;
|
|
39
|
+
}): string;
|
|
40
|
+
/**
|
|
41
|
+
* Runs the full release check while reusing one package tarball for install
|
|
42
|
+
* smoke and consumer rehearsal.
|
|
43
|
+
*
|
|
44
|
+
* @returns {void}
|
|
45
|
+
*/
|
|
46
|
+
declare function main(): void;
|
|
47
|
+
export { createReleaseEnv, main, packReleasePackage, resolveCommandTimeoutMs, run, };
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.createReleaseEnv = createReleaseEnv;
|
|
5
|
+
exports.main = main;
|
|
6
|
+
exports.packReleasePackage = packReleasePackage;
|
|
7
|
+
exports.resolveCommandTimeoutMs = resolveCommandTimeoutMs;
|
|
8
|
+
exports.run = run;
|
|
9
|
+
const assert = require('node:assert/strict');
|
|
10
|
+
const fs = require('node:fs');
|
|
11
|
+
const os = require('node:os');
|
|
12
|
+
const path = require('node:path');
|
|
13
|
+
const { execFileSync } = require('node:child_process');
|
|
14
|
+
const DEFAULT_COMMAND_TIMEOUT_MS = 180_000;
|
|
15
|
+
/**
|
|
16
|
+
* Creates a clean npm environment for repeatable release gates.
|
|
17
|
+
*
|
|
18
|
+
* @param {string} tempRoot
|
|
19
|
+
* @returns {NodeJS.ProcessEnv}
|
|
20
|
+
*/
|
|
21
|
+
function createReleaseEnv(tempRoot) {
|
|
22
|
+
const env = { ...process.env };
|
|
23
|
+
for (const key of Object.keys(env)) {
|
|
24
|
+
if (key.toLowerCase().startsWith('npm_config_')) {
|
|
25
|
+
delete env[key];
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
env.npm_config_audit = 'false';
|
|
29
|
+
env.npm_config_cache = path.join(tempRoot, 'npm-cache');
|
|
30
|
+
env.npm_config_fund = 'false';
|
|
31
|
+
env.npm_config_update_notifier = 'false';
|
|
32
|
+
return env;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Resolves the per-command timeout for release gate child processes.
|
|
36
|
+
*
|
|
37
|
+
* @param {NodeJS.ProcessEnv} env
|
|
38
|
+
* @returns {number}
|
|
39
|
+
*/
|
|
40
|
+
function resolveCommandTimeoutMs(env) {
|
|
41
|
+
const timeoutMs = Number.parseInt(env.ASL_PACKAGE_GATE_TIMEOUT_MS ?? '', 10);
|
|
42
|
+
return Number.isFinite(timeoutMs) && timeoutMs > 0
|
|
43
|
+
? timeoutMs
|
|
44
|
+
: DEFAULT_COMMAND_TIMEOUT_MS;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Runs a release command with inherited output.
|
|
48
|
+
*
|
|
49
|
+
* @param {string} command
|
|
50
|
+
* @param {string[]} args
|
|
51
|
+
* @param {RunOptions} options
|
|
52
|
+
* @returns {void}
|
|
53
|
+
*/
|
|
54
|
+
function run(command, args, options) {
|
|
55
|
+
execFileSync(command, args, {
|
|
56
|
+
cwd: options.cwd,
|
|
57
|
+
env: options.env,
|
|
58
|
+
stdio: 'inherit',
|
|
59
|
+
timeout: resolveCommandTimeoutMs(options.env),
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Packs the package once for downstream release gates.
|
|
64
|
+
*
|
|
65
|
+
* @param {{env: NodeJS.ProcessEnv, packageRoot: string, packDir: string}} options
|
|
66
|
+
* @returns {string}
|
|
67
|
+
*/
|
|
68
|
+
function packReleasePackage({ env, packageRoot, packDir, }) {
|
|
69
|
+
fs.mkdirSync(packDir, { recursive: true });
|
|
70
|
+
const packOutput = execFileSync('npm', ['pack', '--pack-destination', packDir], {
|
|
71
|
+
cwd: packageRoot,
|
|
72
|
+
encoding: 'utf8',
|
|
73
|
+
env,
|
|
74
|
+
stdio: ['ignore', 'pipe', 'inherit'],
|
|
75
|
+
timeout: resolveCommandTimeoutMs(env),
|
|
76
|
+
});
|
|
77
|
+
const tarballName = packOutput.trim().split(/\n/u).pop();
|
|
78
|
+
assert.ok(tarballName, 'npm pack did not print a tarball name');
|
|
79
|
+
const tarballPath = path.join(packDir, tarballName);
|
|
80
|
+
assert.equal(fs.existsSync(tarballPath), true, `missing packed tarball: ${tarballPath}`);
|
|
81
|
+
return tarballPath;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Runs the full release check while reusing one package tarball for install
|
|
85
|
+
* smoke and consumer rehearsal.
|
|
86
|
+
*
|
|
87
|
+
* @returns {void}
|
|
88
|
+
*/
|
|
89
|
+
function main() {
|
|
90
|
+
const repoRoot = path.resolve(__dirname, '..', '..');
|
|
91
|
+
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'asl-release-check-'));
|
|
92
|
+
const env = createReleaseEnv(tempRoot);
|
|
93
|
+
try {
|
|
94
|
+
run('pnpm', ['test'], { cwd: repoRoot, env });
|
|
95
|
+
run(process.execPath, [path.join(repoRoot, 'dist', 'scripts', 'release-readiness.js')], { cwd: repoRoot, env });
|
|
96
|
+
const tarballPath = packReleasePackage({
|
|
97
|
+
env,
|
|
98
|
+
packageRoot: repoRoot,
|
|
99
|
+
packDir: path.join(tempRoot, 'pack'),
|
|
100
|
+
});
|
|
101
|
+
const gateEnv = {
|
|
102
|
+
...env,
|
|
103
|
+
ASL_PACKAGE_TARBALL: tarballPath,
|
|
104
|
+
};
|
|
105
|
+
run(process.execPath, [path.join(repoRoot, 'dist', 'scripts', 'package-smoke.js')], { cwd: repoRoot, env: gateEnv });
|
|
106
|
+
run(process.execPath, [path.join(repoRoot, 'dist', 'scripts', 'consumer-rehearsal.js')], { cwd: repoRoot, env: gateEnv });
|
|
107
|
+
process.stdout.write(`release check passed: ${tarballPath}\n`);
|
|
108
|
+
fs.rmSync(tempRoot, { recursive: true, force: true });
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
console.error(`release check temp kept at: ${tempRoot}`);
|
|
112
|
+
throw error;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
if (require.main === module) {
|
|
116
|
+
main();
|
|
117
|
+
}
|
|
@@ -367,7 +367,7 @@ function assertReleaseScripts(packageJson) {
|
|
|
367
367
|
const scripts = getStringMap(packageJson, 'scripts');
|
|
368
368
|
assert.equal(scripts.prepublishOnly, 'pnpm release:check');
|
|
369
369
|
assert.equal(scripts['release:readiness'], 'pnpm build && node dist/scripts/release-readiness.js');
|
|
370
|
-
assert.equal(scripts['release:check'], 'pnpm
|
|
370
|
+
assert.equal(scripts['release:check'], 'pnpm build && node dist/scripts/release-check.js');
|
|
371
371
|
assert.equal(scripts['package:smoke'], 'pnpm build && node dist/scripts/package-smoke.js');
|
|
372
372
|
assert.equal(scripts['consumer:rehearse'], 'pnpm build && node dist/scripts/consumer-rehearsal.js');
|
|
373
373
|
assert.equal(scripts['downstream:local-package'], 'pnpm build && node dist/scripts/downstream-local-package-gate.js');
|
|
@@ -380,6 +380,24 @@ function assertReleaseScripts(packageJson) {
|
|
|
380
380
|
assert.match(scripts['example:mobile:live-proof'], /--fail-on-regression/u);
|
|
381
381
|
assert.match(scripts['example:app:start:isolated'], /--port 8097 --host localhost --clear/u);
|
|
382
382
|
}
|
|
383
|
+
/**
|
|
384
|
+
* Asserts that pushed semver tags synchronize GitHub Releases from npm package truth.
|
|
385
|
+
*
|
|
386
|
+
* @param {string} repoRoot
|
|
387
|
+
* @returns {void}
|
|
388
|
+
*/
|
|
389
|
+
function assertGithubReleaseWorkflow(repoRoot) {
|
|
390
|
+
const workflowPath = path.join(repoRoot, '.github', 'workflows', 'github-release.yml');
|
|
391
|
+
const workflow = fs.readFileSync(workflowPath, 'utf8');
|
|
392
|
+
assert.match(workflow, /tags:\n\s+- 'v\*\.\*\.\*'/u);
|
|
393
|
+
assert.match(workflow, /contents: write/u);
|
|
394
|
+
assert.match(workflow, /VERSION="\$\{GITHUB_REF_NAME#v\}"/u);
|
|
395
|
+
assert.match(workflow, /PACKAGE_VERSION="\$\(node -p "require\('\.\/package\.json'\)\.version"\)"/u);
|
|
396
|
+
assert.match(workflow, /npm view agent-scenario-loop@"\$VERSION" version/u);
|
|
397
|
+
assert.match(workflow, /gh release create "\$GITHUB_REF_NAME"/u);
|
|
398
|
+
assert.match(workflow, /--generate-notes/u);
|
|
399
|
+
assert.match(workflow, /--verify-tag/u);
|
|
400
|
+
}
|
|
383
401
|
/**
|
|
384
402
|
* Asserts that public binaries and package subpath exports map to built files.
|
|
385
403
|
*
|
|
@@ -508,6 +526,7 @@ function main() {
|
|
|
508
526
|
const packageJson = readJsonObject(path.join(repoRoot, 'package.json'));
|
|
509
527
|
assertPublicPackageMetadata(packageJson);
|
|
510
528
|
assertReleaseScripts(packageJson);
|
|
529
|
+
assertGithubReleaseWorkflow(repoRoot);
|
|
511
530
|
assertPublicEntrypoints(packageJson);
|
|
512
531
|
assertPublicApiDocs(packageJson, repoRoot);
|
|
513
532
|
assertPackageFileList(packageJson);
|
|
@@ -16,6 +16,12 @@ Package gates run child package-manager and CLI commands with a bounded timeout.
|
|
|
16
16
|
ASL_PACKAGE_GATE_TIMEOUT_MS=300000 pnpm consumer:rehearse
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
+
When a parent release gate has already packed the current package, pass that tarball through `ASL_PACKAGE_TARBALL` to reuse it instead of packing again:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
ASL_PACKAGE_TARBALL=/path/to/agent-scenario-loop-0.1.4.tgz pnpm consumer:rehearse
|
|
23
|
+
```
|
|
24
|
+
|
|
19
25
|
## Downstream Local-Package Gate
|
|
20
26
|
|
|
21
27
|
Before publishing a release candidate, validate the packed local package inside at least one real downstream app when that app has already adopted durable ASL scenarios. This catches package, runner, schema, and helper regressions before npm distribution.
|
package/docs/live-proofs.md
CHANGED
|
@@ -35,6 +35,8 @@ pnpm check-plan -- --scenario examples/scenarios/mobile/app-startup.json --runne
|
|
|
35
35
|
|
|
36
36
|
This validates the input manifests, writes schema-checked `health.json` and `verdict.json`, writes `agent-summary.md`, and includes the raw planner match in `planner-compatibility.json`.
|
|
37
37
|
|
|
38
|
+
Live profile wrappers also run this compatibility check before adb, simctl, agent-device, or provider capture starts. A compatible run writes `planner-compatibility.json` as the first profile artifact, then continues into the platform capture. An incompatible run writes failed `health.json`, inconclusive `verdict.json`, `agent-summary.md`, and the planner artifact in the profile run folder, then exits before touching the device runtime. This keeps missing required diagnostics, unsupported platforms, and impossible runner/provider plans out of the long capture loop.
|
|
39
|
+
|
|
38
40
|
## Host/Device Access
|
|
39
41
|
|
|
40
42
|
Keep deterministic validation and live device proof as separate execution lanes.
|
|
@@ -338,7 +340,7 @@ Before publishing, run:
|
|
|
338
340
|
pnpm release:check
|
|
339
341
|
```
|
|
340
342
|
|
|
341
|
-
That gate runs tests
|
|
343
|
+
That gate builds the release scripts, runs tests and readiness checks, packs the package once, then reuses that tarball for package smoke, installed-binary checks, fake-device example proofs, schema/example/template/doc packaging checks, and the packed-package consumer rehearsal. Reusing one tarball keeps the release path closer to npm publish behavior and avoids repeated clean/build/pack cycles.
|
|
342
344
|
|
|
343
345
|
Package smoke and consumer rehearsal keep child commands bounded so package-manager stalls fail with the temporary rehearsal directory preserved. Set `ASL_PACKAGE_GATE_TIMEOUT_MS` to raise the per-command timeout when a local registry, proxy, or cold package cache is slow:
|
|
344
346
|
|
|
@@ -346,6 +348,10 @@ Package smoke and consumer rehearsal keep child commands bounded so package-mana
|
|
|
346
348
|
ASL_PACKAGE_GATE_TIMEOUT_MS=300000 pnpm release:check
|
|
347
349
|
```
|
|
348
350
|
|
|
351
|
+
## Run Plan First
|
|
352
|
+
|
|
353
|
+
Profile runs write `run-plan.json` before provider commands, evidence ingest, and final health classification. Inspect it first when a live loop stalls or fails early: it records the scenario id, scenario hash, input mode (`fixture-event-log`, `adb-sidecar`, `simctl-sidecar`, or live capture), expected iterations, command transport, provider manifests, requested diagnostics, and evidence source paths. The profile CLIs also print a compact run-plan heartbeat to stderr while keeping stdout reserved for the run directory.
|
|
354
|
+
|
|
349
355
|
## Side References
|
|
350
356
|
|
|
351
357
|
- [Consumer App Rehearsal](consumer-rehearsal.md) for adoption inside an existing app
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-scenario-loop",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Scenario orchestration and evidence collection for agent-driven software development. Bring your own runner. Keep your scenarios. Keep your evidence.",
|
|
6
6
|
"license": "MIT",
|
|
@@ -232,7 +232,7 @@
|
|
|
232
232
|
"downstream:local-package": "pnpm build && node dist/scripts/downstream-local-package-gate.js",
|
|
233
233
|
"package:smoke": "pnpm build && node dist/scripts/package-smoke.js",
|
|
234
234
|
"prepublishOnly": "pnpm release:check",
|
|
235
|
-
"release:check": "pnpm
|
|
235
|
+
"release:check": "pnpm build && node dist/scripts/release-check.js",
|
|
236
236
|
"release:readiness": "pnpm build && node dist/scripts/release-readiness.js",
|
|
237
237
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
238
238
|
"test": "pnpm clean && pnpm typecheck && pnpm exec tsc -p tsconfig.json && node --test dist/core/__tests__/*.test.js dist/runner/__tests__/*.test.js",
|