Skip to content

Commit 3739272

Browse files
autopilot: address PR #19 review feedback
Why: - Resolve actionable GitHub review feedback for PR #19. Changed: - Addressed all six Codex review threads: fail closed on schema-invalid lease/pin JSON, make fallback device-type selection order-stable, keep automatic finishing off the setup sheet, treat doctor-record deletions as snapshot finishing work, preserve selected path overrides in recovery commands, and include rollback inventories in the Stop/timeout grace window. Verification: - npm run agent:complete -- --session-dir [controller artifact: jobs/pr-19/20260825-162015-12a4bca4-3c8e-47d3-ad4f-31b5204c837c/task-session] - npm run agent:context -- --paths-file [controller artifact: jobs/pr-19/20260825-162015-12a4bca4-3c8e-47d3-ad4f-31b5204c837c/actual-paths.txt] --session-dir [controller artifact: jobs/pr-19/20260825-162015-12a4bca4-3c8e-47d3-ad4f-31b5204c837c/task-session] - npm run agent:verify -- --profile implementation --paths-file [controller artifact: jobs/pr-19/20260825-162015-12a4bca4-3c8e-47d3-ad4f-31b5204c837c/task-session/verify/implementation/paths.txt] --session-dir [controller artifact: jobs/pr-19/20260825-162015-12a4bca4-3c8e-47d3-ad4f-31b5204c837c/task-session] - npm run agent:verify -- --profile spec-only --paths-file [controller artifact: jobs/pr-19/20260825-162015-12a4bca4-3c8e-47d3-ad4f-31b5204c837c/task-session/verify/spec-only/paths.txt] --session-dir [controller artifact: jobs/pr-19/20260825-162015-12a4bca4-3c8e-47d3-ad4f-31b5204c837c/task-session] Affected: - .github/workflows/ci.yml - CHANGELOG.md - CONTRIBUTING.md - Casks/simulator-broker.rb - Formula/simbroker.rb - README.md - agent-harness/package-lock.json - agent-harness/package.json - app/README.md - app/Sources/BrokerDashboardStore.swift - app/Sources/BrokerLocalCommandClient.swift - app/Sources/BrokerOnboardingCommands.swift - app/Sources/BrokerServiceClient.swift - app/Sources/BrokerSetupModels.swift - app/Sources/BrokerSnapshotLoader.swift - app/Sources/RootView.swift - app/Sources/SetupPlanDevicesView.swift - app/Sources/SetupPlanPrerequisitesView.swift - app/Sources/SetupPlanSheet.swift - app/Sources/SharedViews.swift - app/Tests/BrokerDashboardStoreTests.swift - app/Tests/BrokerLocalCommandClientTests.swift - app/Tests/BrokerOnboardingCommandsTests.swift - app/Tests/BrokerRuntimePathsTests.swift - app/Tests/BrokerServiceClientTests.swift - app/Tests/BrokerSnapshotLoaderTests.swift - broker-core/containment.mjs - broker-core/error-contract.mjs - broker-core/index.mjs - broker-core/simctl.mjs - broker-core/test/broker-core.test.mjs - client/bin/simbroker.mjs - client/command-dispatch.mjs - client/public-surface.mjs - client/service/service-client.mjs - client/setup-preflight.mjs - client/setup-provisioning-worker.mjs - client/setup-provisioning.mjs - client/test/brokerd.test.mjs - client/test/install-distribution.test.mjs - client/test/public-surface.test.mjs - client/test/setup-preflight.test.mjs - client/test/simbroker.test.mjs - docs/concepts.md - docs/getting-started.md - docs/status.md - docs/test/front-door.test.mjs - examples/harness-adoption/README.md - examples/harness-adoption/sample-consumer-repo/.simulator-broker/project.json - examples/harness-adoption/sample-consumer-repo/README.md - packages/simbroker/README.md - scripts/install_smoke.sh - scripts/package_distribution.sh - spec/README.md - spec/architecture.md - spec/build-and-test.md - spec/global-simulator-broker.md - spec/project-structure.md - spec/tasks/README.md - spec/tasks/guided-simbroker-setup.md Refs: - #19 - #19 (comment) - #19 (comment) - #19 (comment) - #19 (comment) - #19 (comment) - #19 (comment) Session: - task-session: [controller artifact: jobs/pr-19/20260825-162015-12a4bca4-3c8e-47d3-ad4f-31b5204c837c/task-session] - report: [controller artifact: jobs/pr-19/20260825-162015-12a4bca4-3c8e-47d3-ad4f-31b5204c837c/report.md]
1 parent 6b3dec1 commit 3739272

12 files changed

Lines changed: 531 additions & 58 deletions

CHANGELOG.md

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1515
health verification. Setup blocks file-shaped destination ancestors before
1616
mutation, rechecks the post-doctor snapshot for unavailable or
1717
`repair-needed` aliases, and gives the macOS app a lock/rollback/finishing
18-
timeout with the same cooperative escalation window used on Stop.
18+
timeout with a cooperative Stop/timeout window that also covers rollback
19+
and indeterminate-create inventory commands.
1920
- A macOS setup-plan sheet with all six aliases, runtime/build and disk details,
2021
remediation commands, exact plan confirmation, cooperative Stop, and
2122
cancellation-aware task ownership.
@@ -25,11 +26,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2526
- Guided setup binds existing-host confirmation to the requested `--host-id`,
2627
uses the separate device-type inventory when a runtime omits
2728
`supportedDeviceTypes`, and fails closed when known-projects or lease/pin
28-
JSON cannot be loaded instead of reporting `ready`. Preview probes service
29-
identity even when no host is configured yet, so a socket occupied by a
30-
different broker is blocked before device creation. `ready` also requires
31-
the app snapshot to be at least as new as known-projects and lease/pin
32-
records, not only host-config and registry.
29+
JSON cannot be loaded or is not a valid identity record instead of reporting
30+
`ready`. Preview probes service identity even when no host is configured yet,
31+
so a socket occupied by a different broker is blocked before device creation.
32+
`ready` also requires the app snapshot to be at least as new as
33+
known-projects, lease/pin records, and the lease/pin directories, and to
34+
match the current doctor-record set, so deletions are finishing work rather
35+
than a stale `ready`. Fallback device-type selection is identifier-stable
36+
when preferred names are absent. Setup failure recovery commands keep the
37+
selected `--host-config`, `--state-root`, and `--service-socket` paths.
38+
The macOS app keeps automatic service/snapshot finishing off the first-time
39+
review sheet. Setup apply Stop/timeout waits for rollback inventory calls
40+
before escalating to SIGKILL.
3341
- Public CI no longer runs the full-repo home-path leak scan on
3442
GitHub-hosted runners. Ubuntu (10 minutes) runs skill ownership,
3543
`test:broker-core`, and `test:harness-adoption`. macOS (15 minutes)

app/Sources/BrokerDashboardStore.swift

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,11 @@ final class BrokerDashboardStore {
8888
var pendingSetupConfirmation: String?
8989
var setupPhase: BrokerSetupPhase = .idle
9090
var setupPlan: BrokerSetupPlan?
91+
92+
var isAutomaticSetupInProgress: Bool {
93+
isApplyingAction && setupPlan == nil && setupPhase != .idle && setupPhase != .awaitingConfirmation
94+
}
95+
9196
var selectedPane: BrokerNavigationPane = .overview {
9297
didSet {
9398
if selectedPane != .overview {
@@ -546,19 +551,21 @@ final class BrokerDashboardStore {
546551
do {
547552
let plan = try await loadGuidedSetupPlan()
548553
try Task.checkCancellation()
549-
setupPlan = plan
550554
if plan.status == .blocked {
555+
setupPlan = plan
551556
pendingSetupConfirmation = nil
552557
setupPhase = .awaitingConfirmation
553558
return
554559
}
555560
if plan.confirmation.required {
561+
setupPlan = plan
556562
pendingSetupConfirmation = plan.planId
557563
setupPhase = .awaitingConfirmation
558564
return
559565
}
566+
setupPlan = nil
567+
pendingSetupConfirmation = nil
560568
if plan.status == .ready {
561-
setupPlan = nil
562569
setupPhase = .idle
563570
setActionMessage("Setup complete — brokerd is running and all managed simulators are healthy.")
564571
return

app/Sources/BrokerLocalCommandClient.swift

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -556,11 +556,17 @@ private enum BrokerLocalCommandTimeouts {
556556
private static let serviceStartLockProcessSamplerInvocations = 2
557557
private static let serviceStartStateLoads = 2
558558
private static let simctlCommandTimeoutSeconds = 120
559-
private static let setupCancellationOperationCount = serviceStartHostAliasCount + 1
559+
private static let simctlInventoryCommandsPerStateLoad = 3
560+
// One in-flight simctl command, plus two 3-command inventories
561+
// (indeterminate create recovery and rollback attribution) and six deletes.
562+
private static let setupCancellationActiveOperationCount = 1
563+
private static let setupCancellationOperationCount = setupCancellationActiveOperationCount
564+
+ simctlInventoryCommandsPerStateLoad
565+
+ simctlInventoryCommandsPerStateLoad
566+
+ serviceStartHostAliasCount
560567
private static let setupCancellationOverheadSeconds = 60
561568
private static let setupMaxSimctlCommandsUnderCapacityLock = 30
562569
private static let setupPreviewTimeoutSeconds = 600
563-
private static let simctlInventoryCommandsPerStateLoad = 3
564570
private static let staleContainmentProcessSamplerInvocations = 8
565571
private static let stateLoadBudgetSeconds = (simctlInventoryCommandsPerStateLoad * simctlCommandTimeoutSeconds)
566572
+ (processSamplerInvocationsPerStateLoad * processSamplerTimeoutSeconds)

app/Sources/RootView.swift

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ struct RootView: View {
5858
store.lastActionMessage != nil
5959
|| (store.lastErrorMessage != nil && store.snapshot != nil)
6060
|| store.startupState == .readOnlySnapshot
61+
|| store.isAutomaticSetupInProgress
6162
}
6263

6364
@ViewBuilder
@@ -142,11 +143,25 @@ struct RootView: View {
142143

143144
@ViewBuilder
144145
private var statusMessages: some View {
146+
automaticSetupProgressCard
145147
actionMessageCard
146148
errorMessageCard
147149
readOnlySnapshotMessageCard
148150
}
149151

152+
@ViewBuilder
153+
private var automaticSetupProgressCard: some View {
154+
if store.isAutomaticSetupInProgress {
155+
StatusMessageCard(
156+
color: .blue,
157+
message: "Finishing setup: starting brokerd, refreshing the snapshot, and verifying Simulator health.",
158+
symbolName: "gearshape.2",
159+
actionTitle: store.setupPhase == .applying ? "Stop" : nil,
160+
onAction: store.setupPhase == .applying ? { store.stopGuidedSetup() } : nil
161+
)
162+
}
163+
}
164+
150165
@ViewBuilder
151166
private var actionMessageCard: some View {
152167
if let message = store.lastActionMessage {

app/Sources/SharedViews.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,14 @@ struct BrokerSetupView: View {
347347
}
348348
.buttonStyle(.bordered)
349349
.disabled(store.isApplyingAction)
350+
351+
if store.isAutomaticSetupInProgress {
352+
ProgressView()
353+
.controlSize(.small)
354+
if store.setupPhase == .applying {
355+
Button("Stop", role: .destructive, action: store.stopGuidedSetup)
356+
}
357+
}
350358
}
351359

352360
GroupBox("Setup status") {

app/Tests/BrokerDashboardStoreTests.swift

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -777,6 +777,71 @@ final class BrokerDashboardStoreTests: XCTestCase {
777777
XCTAssertTrue(invocations[1].arguments.contains("--host-id"))
778778
XCTAssertTrue(invocations[1].arguments.contains("guided-app-host"))
779779
XCTAssertNil(store.setupPlan)
780+
XCTAssertEqual(store.setupPhase, .idle)
781+
}
782+
783+
func testGuidedSetupAutomaticFinishingDoesNotPresentSetupSheet() async throws {
784+
let runtimePaths = BrokerRuntimePaths(
785+
stateRoot: URL(fileURLWithPath: "/tmp/simbroker-auto-finish-state"),
786+
hostConfigURL: URL(fileURLWithPath: "/tmp/simbroker-auto-finish-host.json"),
787+
configuredCLIURL: URL(fileURLWithPath: "/tmp/fake-simbroker")
788+
)
789+
let setupPlan = try makeSetupPlan(confirmationRequired: false, hostConfigured: true, includeRuntime: false, createCount: 0)
790+
let localRunner = CancellableApplyLocalCommandRunner(plan: setupPlan)
791+
let loadedState = BrokerLoadedState(
792+
paths: runtimePaths,
793+
tooling: BrokerToolingState(cliPath: runtimePaths.configuredCLIURL, hostConfigExists: true, installMetadata: nil),
794+
service: nil,
795+
snapshot: nil
796+
)
797+
let store = BrokerDashboardStore(
798+
loader: StubSnapshotLoader(state: loadedState),
799+
commandClient: RecordingCommandClient(),
800+
localCommandRunner: localRunner,
801+
runtimePaths: runtimePaths
802+
)
803+
804+
store.requestGuidedSetup()
805+
try await waitUntil { await localRunner.applyStarted() }
806+
try await waitUntil { await MainActor.run { store.setupPhase == .applying } }
807+
808+
XCTAssertNil(store.setupPlan)
809+
XCTAssertNil(store.pendingSetupConfirmation)
810+
XCTAssertTrue(store.isAutomaticSetupInProgress)
811+
812+
store.stopGuidedSetup()
813+
try await waitUntil { await MainActor.run { store.setupPhase == .idle && store.isApplyingAction == false } }
814+
XCTAssertNil(store.setupPlan)
815+
XCTAssertFalse(store.isAutomaticSetupInProgress)
816+
}
817+
818+
func testGuidedSetupAutomaticFinishingFailureDoesNotPresentSetupSheet() async throws {
819+
let runtimePaths = BrokerRuntimePaths(
820+
stateRoot: URL(fileURLWithPath: "/tmp/simbroker-auto-fail-state"),
821+
hostConfigURL: URL(fileURLWithPath: "/tmp/simbroker-auto-fail-host.json"),
822+
configuredCLIURL: URL(fileURLWithPath: "/tmp/fake-simbroker")
823+
)
824+
let setupPlan = try makeSetupPlan(confirmationRequired: false, hostConfigured: true, includeRuntime: false, createCount: 0)
825+
let localRunner = FailingApplyLocalCommandRunner(plan: setupPlan)
826+
let loadedState = BrokerLoadedState(
827+
paths: runtimePaths,
828+
tooling: BrokerToolingState(cliPath: runtimePaths.configuredCLIURL, hostConfigExists: true, installMetadata: nil),
829+
service: nil,
830+
snapshot: nil
831+
)
832+
let store = BrokerDashboardStore(
833+
loader: StubSnapshotLoader(state: loadedState),
834+
commandClient: RecordingCommandClient(),
835+
localCommandRunner: localRunner,
836+
runtimePaths: runtimePaths
837+
)
838+
839+
store.requestGuidedSetup()
840+
try await waitUntil { await MainActor.run { store.lastErrorMessage != nil && store.isApplyingAction == false } }
841+
842+
XCTAssertEqual(store.setupPhase, .idle)
843+
XCTAssertNil(store.setupPlan)
844+
XCTAssertEqual(store.lastErrorMessage, "Setup committed; rerun simbroker setup.")
780845
}
781846

782847
func testStoppingGuidedSetupClearsStalePlanWithoutPresentingCancellationAsFailure() async throws {

app/Tests/BrokerLocalCommandClientTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ final class BrokerLocalCommandClientTests: XCTestCase {
217217
)
218218
XCTAssertEqual(
219219
runner.resolvedCancellationEscalationNanoseconds(for: ["setup", "--apply", "--confirm", "sha256:test", "--json"]),
220-
900 * 1_000_000_000
220+
1_620 * 1_000_000_000
221221
)
222222
let tempRoot = try makeTempRoot()
223223
let stateRoot = tempRoot.appending(path: "state")

broker-core/index.mjs

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1101,7 +1101,19 @@ function selectPreferredDeviceType(runtime, deviceFamily) {
11011101
.filter((deviceType) => exactNamePattern.test(deviceType.name))
11021102
.sort((left, right) => compareVersions(right.name, left.name));
11031103

1104-
return preferred[0] ?? supportedDeviceTypes[0];
1104+
if (preferred[0]) {
1105+
return preferred[0];
1106+
}
1107+
1108+
return [...supportedDeviceTypes].sort(compareSetupDeviceTypes)[0];
1109+
}
1110+
1111+
function compareSetupDeviceTypes(left, right) {
1112+
const identifierComparison = String(left?.identifier ?? "").localeCompare(String(right?.identifier ?? ""));
1113+
if (identifierComparison !== 0) {
1114+
return identifierComparison;
1115+
}
1116+
return String(left?.name ?? "").localeCompare(String(right?.name ?? ""));
11051117
}
11061118

11071119
function deleteAliasSimulator(simctl, simulatorId) {
@@ -1645,14 +1657,14 @@ function setupExistingHostState(paths, inventory, options = {}) {
16451657
});
16461658
}
16471659
try {
1648-
listJsonFiles(paths.leasesDir);
1649-
listJsonFiles(paths.pinsDir);
1660+
listJsonFiles(paths.leasesDir).forEach(assertValidLeaseRecord);
1661+
listJsonFiles(paths.pinsDir).forEach(assertValidPinRecord);
16501662
} catch (error) {
16511663
issues.push({
16521664
id: "leases",
16531665
status: "blocked",
16541666
summary: "Existing lease or pin records are invalid and setup will not replace them automatically.",
1655-
details: { message: error.message },
1667+
details: error instanceof BrokerError ? error.payload : { message: error.message },
16561668
remediationCommands: ["simbroker doctor"],
16571669
});
16581670
}
@@ -2331,6 +2343,20 @@ function listJsonFiles(dirPath) {
23312343
.map(readJson);
23322344
}
23332345

2346+
function assertValidLeaseRecord(record) {
2347+
requireObject(record, "lease");
2348+
requireString(record.leaseId, "lease.leaseId");
2349+
requireString(record.alias, "lease.alias");
2350+
return record;
2351+
}
2352+
2353+
function assertValidPinRecord(record) {
2354+
requireObject(record, "pin");
2355+
requireString(record.pinId, "pin.pinId");
2356+
requireString(record.alias, "pin.alias");
2357+
return record;
2358+
}
2359+
23342360
function leaseMatchesPin(leaseOrRequest, pin) {
23352361
return leaseOrRequest.projectId === pin.projectId
23362362
&& (pin.purposeId === null || leaseOrRequest.purposeId === pin.purposeId);

broker-core/test/broker-core.test.mjs

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -967,6 +967,80 @@ test("setup uses the device-type inventory when a runtime omits supportedDeviceT
967967
assert.equal(emptyArrayPreview.devices.length, 6);
968968
});
969969

970+
test("setup fallback device types stay stable when preferred names are absent", () => {
971+
const root = makeTempDir();
972+
const deviceTypes = [
973+
{
974+
identifier: "com.apple.CoreSimulator.SimDeviceType.iPhone-SE",
975+
name: "iPhone SE",
976+
productFamily: "iPhone",
977+
},
978+
{
979+
identifier: "com.apple.CoreSimulator.SimDeviceType.iPhone-16-Pro",
980+
name: "iPhone 16 Pro",
981+
productFamily: "iPhone",
982+
},
983+
{
984+
identifier: "com.apple.CoreSimulator.SimDeviceType.iPad-Pro-11",
985+
name: "iPad Pro 11-inch",
986+
productFamily: "iPad",
987+
},
988+
{
989+
identifier: "com.apple.CoreSimulator.SimDeviceType.iPad-Air",
990+
name: "iPad Air",
991+
productFamily: "iPad",
992+
},
993+
];
994+
const runtime = {
995+
buildversion: "23F76",
996+
identifier: "com.apple.CoreSimulator.SimRuntime.iOS-26-5",
997+
isAvailable: true,
998+
version: "26.5",
999+
};
1000+
const forwardSimctl = createSimctlFixture(root, {
1001+
runtimes: [{ ...runtime, supportedDeviceTypes: deviceTypes }],
1002+
});
1003+
const reversedRoot = makeTempDir();
1004+
const reversedSimctl = createSimctlFixture(reversedRoot, {
1005+
runtimes: [{ ...runtime, supportedDeviceTypes: [...deviceTypes].reverse() }],
1006+
});
1007+
const forwardPaths = resolveBrokerPaths({
1008+
hostConfigPath: path.join(root, "host-config.json"),
1009+
stateRoot: path.join(root, "state"),
1010+
});
1011+
const reversedPaths = resolveBrokerPaths({
1012+
hostConfigPath: path.join(reversedRoot, "host-config.json"),
1013+
stateRoot: path.join(reversedRoot, "state"),
1014+
});
1015+
1016+
const forward = previewSetupBroker(forwardPaths, {
1017+
hostId: "fallback-device-types",
1018+
simctlAdapter: forwardSimctl.adapter,
1019+
});
1020+
const reversed = previewSetupBroker(reversedPaths, {
1021+
hostId: "fallback-device-types",
1022+
simctlAdapter: reversedSimctl.adapter,
1023+
});
1024+
1025+
assert.equal(forward.planId, reversed.planId);
1026+
assert.equal(
1027+
forward.devices.find((device) => device.deviceFamily === "iPhone").deviceTypeIdentifier,
1028+
"com.apple.CoreSimulator.SimDeviceType.iPhone-16-Pro",
1029+
);
1030+
assert.equal(
1031+
reversed.devices.find((device) => device.deviceFamily === "iPhone").deviceTypeIdentifier,
1032+
"com.apple.CoreSimulator.SimDeviceType.iPhone-16-Pro",
1033+
);
1034+
assert.equal(
1035+
forward.devices.find((device) => device.deviceFamily === "iPad").deviceTypeIdentifier,
1036+
"com.apple.CoreSimulator.SimDeviceType.iPad-Air",
1037+
);
1038+
assert.equal(
1039+
reversed.devices.find((device) => device.deviceFamily === "iPad").deviceTypeIdentifier,
1040+
"com.apple.CoreSimulator.SimDeviceType.iPad-Air",
1041+
);
1042+
});
1043+
9701044
test("setup preview is an exact stable six-device plan with strict reuse matching", () => {
9711045
const root = makeTempDir();
9721046
const matchingName = "Simulator Broker setup-plan ui-1";
@@ -1123,6 +1197,24 @@ test("setup blocks an existing host when doctor-relevant state files are malform
11231197
assert.equal(leasesBlocked.status, "blocked");
11241198
assert.ok(leasesBlocked.prerequisites.some((issue) =>
11251199
issue.id === "leases" && issue.status === "blocked"));
1200+
1201+
fs.writeFileSync(path.join(resolvedPaths.leasesDir, "broken.json"), "{}\n");
1202+
const emptyLeaseBlocked = previewSetupBroker(resolvedPaths, {
1203+
simctlAdapter: paths.simctl.adapter,
1204+
});
1205+
assert.equal(emptyLeaseBlocked.status, "blocked");
1206+
assert.ok(emptyLeaseBlocked.prerequisites.some((issue) =>
1207+
issue.id === "leases" && issue.status === "blocked"));
1208+
1209+
fs.rmSync(path.join(resolvedPaths.leasesDir, "broken.json"));
1210+
fs.mkdirSync(resolvedPaths.pinsDir, { recursive: true });
1211+
writeJson(path.join(resolvedPaths.pinsDir, "empty-pin.json"), {});
1212+
const emptyPinBlocked = previewSetupBroker(resolvedPaths, {
1213+
simctlAdapter: paths.simctl.adapter,
1214+
});
1215+
assert.equal(emptyPinBlocked.status, "blocked");
1216+
assert.ok(emptyPinBlocked.prerequisites.some((issue) =>
1217+
issue.id === "leases" && issue.status === "blocked"));
11261218
});
11271219

11281220
test("setup blocks an existing host whose registry marks an alias for repair", () => {

0 commit comments

Comments
 (0)