@delacour/warden-e2e 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/{chunk-empqr9n0.mjs → chunk-vs5npy59.mjs} +2010 -169
- package/index.mjs +214 -13
- package/package.json +2 -2
- package/preload.mjs +3002 -798
- package/types/core/src/a11y/a11y.config.d.ts +47 -0
- package/types/core/src/affected/affected.schema.d.ts +49 -1
- package/types/core/src/android-keyguard.d.ts +2 -0
- package/types/core/src/batch/preset.schema.d.ts +12 -1
- package/types/core/src/builds/app-option.schema.d.ts +1 -1
- package/types/core/src/builds/config.schema.d.ts +93 -2
- package/types/core/src/builds/define-config.d.ts +55 -0
- package/types/core/src/camera-mode.d.ts +17 -0
- package/types/core/src/camera-placeholder.d.ts +9 -0
- package/types/core/src/camera-profile.d.ts +12 -0
- package/types/core/src/claim-errors.d.ts +34 -0
- package/types/core/src/conditions/a11y-keys.d.ts +45 -0
- package/types/core/src/conditions/camera-media.d.ts +30 -0
- package/types/core/src/conditions/conditions.config.d.ts +24 -0
- package/types/core/src/conditions/conditions.types.d.ts +30 -0
- package/types/core/src/conditions/inject-state.d.ts +1 -1
- package/types/core/src/ios-runner/build.d.ts +28 -0
- package/types/core/src/ios-runner/cache.d.ts +44 -0
- package/types/core/src/ios-runner/pack.d.ts +17 -0
- package/types/core/src/ios-runner/signing.d.ts +40 -0
- package/types/core/src/ios-runner/sim-build.d.ts +36 -0
- package/types/core/src/ios-runner/stage.d.ts +26 -0
- package/types/core/src/lease-events.d.ts +126 -0
- package/types/core/src/ownership.d.ts +44 -0
- package/types/core/src/providers/android.d.ts +10 -1
- package/types/core/src/providers/provider.types.d.ts +9 -0
- package/types/core/src/queue.d.ts +43 -0
- package/types/core/src/store.d.ts +61 -5
- package/types/core/src/types.d.ts +10 -0
- package/types/core/src/xcode.d.ts +22 -0
- package/types/e2e/src/a11y/a11y-target.d.ts +23 -0
- package/types/e2e/src/a11y/a11y.types.d.ts +81 -0
- package/types/e2e/src/a11y/announcements.d.ts +24 -0
- package/types/e2e/src/a11y/audit.d.ts +22 -0
- package/types/e2e/src/a11y/native/atf.d.ts +51 -0
- package/types/e2e/src/a11y/native/xcuitest.d.ts +60 -0
- package/types/e2e/src/a11y/order.d.ts +5 -0
- package/types/e2e/src/a11y/rules/duplicate-name.d.ts +6 -0
- package/types/e2e/src/a11y/rules/hidden-focusable.d.ts +3 -0
- package/types/e2e/src/a11y/rules/image-no-name.d.ts +6 -0
- package/types/e2e/src/a11y/rules/label-in-name.d.ts +7 -0
- package/types/e2e/src/a11y/rules/missing-name.d.ts +3 -0
- package/types/e2e/src/a11y/rules/role-in-name.d.ts +3 -0
- package/types/e2e/src/a11y/rules/rule.context.d.ts +22 -0
- package/types/e2e/src/a11y/rules/target-size.d.ts +21 -0
- package/types/e2e/src/a11y/rules/text-clipped.d.ts +9 -0
- package/types/e2e/src/a11y/speech.d.ts +26 -0
- package/types/e2e/src/a11y/talkback-log.d.ts +13 -0
- package/types/e2e/src/app.d.ts +0 -1
- package/types/e2e/src/conditions/condition-events.d.ts +6 -1
- package/types/e2e/src/conditions/device-conditions.d.ts +30 -2
- package/types/e2e/src/device-a11y.d.ts +97 -0
- package/types/e2e/src/device.d.ts +11 -2
- package/types/e2e/src/driver/a11y-events.d.ts +30 -0
- package/types/e2e/src/driver/a11y-read.d.ts +12 -0
- package/types/e2e/src/driver/android/android-animations.d.ts +7 -0
- package/types/e2e/src/driver/android/android-argv.d.ts +2 -0
- package/types/e2e/src/driver/android/android-camera-host.d.ts +23 -0
- package/types/e2e/src/driver/android/android-camera.d.ts +28 -0
- package/types/e2e/src/driver/android/android-driver.d.ts +5 -0
- package/types/e2e/src/driver/android/android-screen-reader.d.ts +23 -0
- package/types/e2e/src/driver/android/android-settings.d.ts +9 -2
- package/types/e2e/src/driver/android/android-spoken.d.ts +18 -0
- package/types/e2e/src/driver/android/android-talkback.d.ts +57 -0
- package/types/e2e/src/driver/android/atf-device.d.ts +23 -0
- package/types/e2e/src/driver/android/atf-dex.generated.d.ts +7 -0
- package/types/e2e/src/driver/android/uiad-dex.generated.d.ts +5 -5
- package/types/e2e/src/driver/android/uiad-ops.d.ts +27 -1
- package/types/e2e/src/driver/android/uiad-protocol.d.ts +8 -0
- package/types/e2e/src/driver/android/uiad-screen-reader.d.ts +33 -0
- package/types/e2e/src/driver/driver.types.d.ts +71 -5
- package/types/e2e/src/driver/element-actions.d.ts +16 -0
- package/types/e2e/src/driver/ios/inject.d.ts +16 -2
- package/types/e2e/src/driver/ios/ios-a11y.d.ts +73 -0
- package/types/e2e/src/driver/ios/ios-audit.d.ts +36 -0
- package/types/e2e/src/driver/ios/ios-driver.d.ts +21 -1
- package/types/e2e/src/driver/ios/ios-network.d.ts +7 -2
- package/types/e2e/src/driver/ios/sim-runner.d.ts +41 -0
- package/types/e2e/src/driver/ios/simd-transport.d.ts +11 -2
- package/types/e2e/src/driver/native-audit.d.ts +26 -0
- package/types/e2e/src/driver/physical-conditions.d.ts +1 -1
- package/types/e2e/src/driver/screen-reader.d.ts +26 -0
- package/types/e2e/src/env.d.ts +16 -1
- package/types/e2e/src/expect/a11y-matchers.d.ts +33 -0
- package/types/e2e/src/expect/expect.d.ts +7 -2
- package/types/e2e/src/expect/matchers.d.ts +14 -0
- package/types/e2e/src/index.d.ts +5 -1
- package/types/e2e/src/runner/callsite.d.ts +3 -1
- package/types/e2e/src/screen/render.d.ts +8 -0
|
@@ -104,6 +104,22 @@ export type A11yCondition = {
|
|
|
104
104
|
reduceMotion?: boolean;
|
|
105
105
|
boldText?: boolean;
|
|
106
106
|
contrast?: "increased" | "normal";
|
|
107
|
+
reduceTransparency?: boolean;
|
|
108
|
+
buttonShapes?: boolean;
|
|
109
|
+
differentiateWithoutColor?: boolean;
|
|
110
|
+
onOffLabels?: boolean;
|
|
111
|
+
highContrastText?: boolean;
|
|
112
|
+
colorInversion?: boolean;
|
|
113
|
+
screenReader?: boolean;
|
|
114
|
+
/**
|
|
115
|
+
* Android TalkBack: the `enabled_accessibility_services` and `accessibility_enabled` values (`null` = unset) the
|
|
116
|
+
* device had before warden turned TalkBack on, so undo puts back exactly those and never drops a service warden
|
|
117
|
+
* did not add. Present only while `screenReader` is true on Android.
|
|
118
|
+
*/
|
|
119
|
+
screenReaderPrevious?: {
|
|
120
|
+
services: string | null;
|
|
121
|
+
enabled: string | null;
|
|
122
|
+
};
|
|
107
123
|
};
|
|
108
124
|
/** `cosmetic`: the status bar says so, the OS and the app still see the real battery (iOS simulators) */
|
|
109
125
|
export type BatteryCondition = {
|
|
@@ -111,6 +127,19 @@ export type BatteryCondition = {
|
|
|
111
127
|
charging?: boolean;
|
|
112
128
|
cosmetic?: boolean;
|
|
113
129
|
};
|
|
130
|
+
export type CameraFacing = "back" | "front";
|
|
131
|
+
export declare const CAMERA_FACINGS: readonly CameraFacing[];
|
|
132
|
+
/** A staged asset the simulated camera plays; `path` is the staged copy under the device's camera dir. */
|
|
133
|
+
export type CameraFeed = {
|
|
134
|
+
kind: "image";
|
|
135
|
+
path: string;
|
|
136
|
+
} | {
|
|
137
|
+
kind: "video";
|
|
138
|
+
path: string;
|
|
139
|
+
loop: boolean;
|
|
140
|
+
};
|
|
141
|
+
/** The simulated camera per facing; a facing left out has no camera (iOS) or the emulator's default (Android). */
|
|
142
|
+
export type CameraCondition = Partial<Record<CameraFacing, CameraFeed>>;
|
|
114
143
|
/** Everything a lease can have set on its device, one slot per area. */
|
|
115
144
|
export type ConditionValues = {
|
|
116
145
|
network: NetworkCondition;
|
|
@@ -120,6 +149,7 @@ export type ConditionValues = {
|
|
|
120
149
|
locale: LocaleCondition;
|
|
121
150
|
a11y: A11yCondition;
|
|
122
151
|
battery: BatteryCondition;
|
|
152
|
+
camera: CameraCondition;
|
|
123
153
|
};
|
|
124
154
|
export type ConditionArea = keyof ConditionValues;
|
|
125
155
|
/** Display/iteration order; also the order undo resets areas in. */
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
export declare function injectStatePath(wardenHome: string, udid: string): string;
|
|
8
8
|
/**
|
|
9
|
-
* Write `{ seq: previous + 1, offline: false, clockOffsetMs: 0 }` (no freeze) when a state file exists; the dylib
|
|
9
|
+
* Write `{ seq: previous + 1, offline: false, clockOffsetMs: 0, camera: {} }` (no freeze, no camera feeds) when a state file exists; the dylib
|
|
10
10
|
* applies any higher `seq`. No file = no injected app was ever launched on the device: nothing to do.
|
|
11
11
|
* Returns an error message, or undefined.
|
|
12
12
|
*/
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type AsyncResult } from "../../../types/src/result";
|
|
2
|
+
import type { EnsureRunnerBuildInput, RunnerBuild, RunnerBuildError } from "./runner-build.types";
|
|
3
|
+
export declare const RUNNER_SCHEME = "WardenRunner";
|
|
4
|
+
/**
|
|
5
|
+
* The `xcodebuild` argv that builds the runner for one phone. `-destination id=` is what registers the UDID in the
|
|
6
|
+
* provisioning profile; the bundle id goes through `WARDEN_RUNNER_BUNDLE_ID` (the project derives the host app's,
|
|
7
|
+
* the UI-test bundle's and the runner app's ids from it, which `PRODUCT_BUNDLE_IDENTIFIER` would collapse into one).
|
|
8
|
+
*/
|
|
9
|
+
export declare function buildRunnerArgv(o: {
|
|
10
|
+
project: string;
|
|
11
|
+
derivedData: string;
|
|
12
|
+
udid: string;
|
|
13
|
+
team: string;
|
|
14
|
+
bundleId: string;
|
|
15
|
+
}): string[];
|
|
16
|
+
/**
|
|
17
|
+
* The derived-data directory `ensureRunnerBuild` would use for the current team, Xcode build and runner sources,
|
|
18
|
+
* without staging, building or taking the build lease: one Xcode version probe, the team lookup and a hash of the
|
|
19
|
+
* sources. It is the identity of a runner build; a resident host that serves another directory serves an old runner.
|
|
20
|
+
*/
|
|
21
|
+
export declare function expectedRunnerDerivedData(input: Pick<EnsureRunnerBuildInput, "team" | "configTeamId" | "env" | "home" | "exec" | "loadSources">): AsyncResult<string, RunnerBuildError>;
|
|
22
|
+
/**
|
|
23
|
+
* A runner build signed for `udid`, ready for `xcodebuild test-without-building -xctestrun`. Cached per
|
|
24
|
+
* (team, Xcode build, runner sources) and rebuilt only for a phone the profile does not list yet, or when the
|
|
25
|
+
* profile ends within 24 h. Builds are serialised by a build lease; a waiting warden re-reads the cache and
|
|
26
|
+
* reuses what the holder built. Never installs or launches anything itself.
|
|
27
|
+
*/
|
|
28
|
+
export declare function ensureRunnerBuild(input: EnsureRunnerBuildInput): AsyncResult<RunnerBuild, RunnerBuildError>;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { Exec } from "../exec";
|
|
2
|
+
/** Rebuild when the provisioning profile ends within this long (free teams get 7-day profiles). */
|
|
3
|
+
export declare const PROFILE_MARGIN_MS: number;
|
|
4
|
+
/** When the profile's expiry cannot be read, assume a free team's 7 days minus a day of margin from the build. */
|
|
5
|
+
export declare const UNKNOWN_PROFILE_LIFETIME_MS: number;
|
|
6
|
+
/** `meta.json` next to `dd/` in a cache directory. */
|
|
7
|
+
export type RunnerMeta = {
|
|
8
|
+
/** every phone the profile was built for (a new UDID needs a build: `-destination id=` registers it) */
|
|
9
|
+
udids: string[];
|
|
10
|
+
/** earliest expiry (ms since epoch) of the profiles embedded in the products; null = unreadable */
|
|
11
|
+
profileExpires: number | null;
|
|
12
|
+
builtAt: number;
|
|
13
|
+
bundleId: string;
|
|
14
|
+
};
|
|
15
|
+
export type RebuildReason = "none" | "new_udid" | "profile_expiring" | "bundle_id" | "missing";
|
|
16
|
+
/** `<home>/ios-runner/<team>/<xcodeBuild>-<srcHash>`: holds `dd/` (derived data) and `meta.json`. */
|
|
17
|
+
export declare function runnerCacheDir(home: string, team: string, xcodeBuild: string, srcHash: string): string;
|
|
18
|
+
export declare function derivedDataDir(cacheDir: string): string;
|
|
19
|
+
/** The cache's meta, or undefined when absent or not ours (a half-written file never reads as a hit). */
|
|
20
|
+
export declare function readRunnerMeta(cacheDir: string): RunnerMeta | undefined;
|
|
21
|
+
/** Written last, after the build, through a rename: the meta is what makes a build count. */
|
|
22
|
+
export declare function writeRunnerMeta(cacheDir: string, meta: RunnerMeta): void;
|
|
23
|
+
/** The `.xctestrun` `build-for-testing` wrote under `Build/Products`. */
|
|
24
|
+
export declare function findXctestrun(derivedData: string): string | undefined;
|
|
25
|
+
export type ReuseDecision = {
|
|
26
|
+
reuse: true;
|
|
27
|
+
} | {
|
|
28
|
+
reuse: false;
|
|
29
|
+
because: RebuildReason;
|
|
30
|
+
};
|
|
31
|
+
/** Can the cached build serve `udid` now? */
|
|
32
|
+
export declare function decideReuse(o: {
|
|
33
|
+
meta: RunnerMeta | undefined;
|
|
34
|
+
xctestrun: string | undefined;
|
|
35
|
+
udid: string;
|
|
36
|
+
bundleId: string;
|
|
37
|
+
now: number;
|
|
38
|
+
}): ReuseDecision;
|
|
39
|
+
/** `<key>ExpirationDate</key><date>2026-11-05T01:02:03Z</date>` of a decoded provisioning profile. */
|
|
40
|
+
export declare function parseProfileExpiry(plist: string): number | undefined;
|
|
41
|
+
/** Paths of the `embedded.mobileprovision` of every app `build-for-testing` produced (host app and test runner app). */
|
|
42
|
+
export declare function embeddedProfiles(derivedData: string): string[];
|
|
43
|
+
/** The earliest expiry among the built apps' profiles (`security cms -D`), null when none can be read. */
|
|
44
|
+
export declare function readProfileExpiry(exec: Exec, derivedData: string): Promise<number | null>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type AsyncResult, type Result } from "../../../types/src/result";
|
|
2
|
+
import type { RunnerSourceFiles } from "./runner-build.types";
|
|
3
|
+
/**
|
|
4
|
+
* The iPhone runner's Xcode project, as the text files the binary embeds (`ios-runner.bin`: a JSON map of
|
|
5
|
+
* path -> text), the way the `@delacour/warden-e2e` runtime is. Paths are relative to `packages/e2e/native`
|
|
6
|
+
* and keep that layout, because the project reaches the shared `warden-simd` files through `../ios/…`.
|
|
7
|
+
*/
|
|
8
|
+
/** Name of the embedded blob (`Bun.embeddedFiles` reports it with a content hash: `ios-runner-<hash>.bin`). */
|
|
9
|
+
export declare const RUNNER_BLOB = "ios-runner.bin";
|
|
10
|
+
/** Read the runner sources from a checkout's `packages/e2e/native` directory. */
|
|
11
|
+
export declare function collectRunnerSources(nativeDir: string): Result<RunnerSourceFiles>;
|
|
12
|
+
export declare function writeRunnerBlob(nativeDir: string, outFile: string): Result<void>;
|
|
13
|
+
/** A path inside the staged root: relative, `/`-separated, no `..`. */
|
|
14
|
+
export declare function isSafeSourcePath(path: string): boolean;
|
|
15
|
+
export declare function parseRunnerBlob(text: string): Result<RunnerSourceFiles>;
|
|
16
|
+
/** The blob embedded in the compiled binary (`Bun.embeddedFiles`, name `ios-runner-<hash>.bin`). */
|
|
17
|
+
export declare function loadEmbeddedRunnerSources(embedded?: readonly Blob[]): AsyncResult<RunnerSourceFiles>;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { type AsyncResult } from "../../../types/src/result";
|
|
2
|
+
import type { Exec } from "../exec";
|
|
3
|
+
import type { RunnerBuildError, SigningTeam, TeamSource } from "./runner-build.types";
|
|
4
|
+
/** Env var that names the signing team; wins over `ios.teamId`. */
|
|
5
|
+
export declare const IOS_TEAM_ENV = "WARDEN_IOS_TEAM_ID";
|
|
6
|
+
/** `nz.co.delacour.warden.runner.<team>` (what the Xcode project defaults to too). */
|
|
7
|
+
export declare function defaultRunnerBundleId(team: string): string;
|
|
8
|
+
export type SigningIdentity = {
|
|
9
|
+
sha1: string;
|
|
10
|
+
name: string;
|
|
11
|
+
};
|
|
12
|
+
/** ` 3) 1111…11 "Apple Development: Test User (AB12CD34EF)"` lines of `security find-identity -v -p codesigning`. */
|
|
13
|
+
export declare function parseIdentities(stdout: string): SigningIdentity[];
|
|
14
|
+
/** The DER of every `-----BEGIN CERTIFICATE-----` block in `text`. */
|
|
15
|
+
export declare function certificatesFromPem(text: string): Uint8Array[];
|
|
16
|
+
export declare function sha1Hex(der: Uint8Array): string;
|
|
17
|
+
/**
|
|
18
|
+
* The `OU` of the certificate's subject: for Apple Development certificates the team id. Walks
|
|
19
|
+
* Certificate > TBSCertificate > subject (the issuer's own `OU` is a different element and never read).
|
|
20
|
+
*/
|
|
21
|
+
export declare function teamOfCertificate(der: Uint8Array): string | undefined;
|
|
22
|
+
/** The teams the keychain holds a valid `Apple Development` identity for. Read-only: two `security` calls. */
|
|
23
|
+
export declare function listSigningTeams(exec: Exec): AsyncResult<SigningTeam[]>;
|
|
24
|
+
export type ResolvedTeam = {
|
|
25
|
+
team: string;
|
|
26
|
+
source: TeamSource;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* The signing team, first hit wins: the explicit one, `WARDEN_IOS_TEAM_ID`, `ios.teamId`, then the keychain when
|
|
30
|
+
* it holds `Apple Development` identities of exactly one team. Several teams is `ios_team_ambiguous` (never a
|
|
31
|
+
* guess: a wrong team signs with the wrong account); none is `no_team`.
|
|
32
|
+
*/
|
|
33
|
+
export declare function resolveTeam(input: {
|
|
34
|
+
explicit?: string | undefined;
|
|
35
|
+
configTeamId?: string | undefined;
|
|
36
|
+
env: Record<string, string | undefined>;
|
|
37
|
+
exec: Exec;
|
|
38
|
+
}): AsyncResult<ResolvedTeam, RunnerBuildError>;
|
|
39
|
+
/** The runner's bundle id: the override (checked), else `nz.co.delacour.warden.runner.<team>`. */
|
|
40
|
+
export declare function resolveRunnerBundleId(team: string, configured: string | undefined): string | RunnerBuildError;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { type AsyncResult } from "../../../types/src/result";
|
|
2
|
+
import { type BuildLockDeps } from "../builds/lock";
|
|
3
|
+
import { type Exec } from "../exec";
|
|
4
|
+
import type { RunnerSourceFiles } from "./runner-build.types";
|
|
5
|
+
/**
|
|
6
|
+
* The runner (XCUITest server) built for the iOS Simulator: no signing, no team, no device registration, so it is one
|
|
7
|
+
* `xcodebuild build-for-testing -destination 'generic/platform=iOS Simulator'` (about 4-10 s) per Xcode build and runner
|
|
8
|
+
* source hash, cached under `<home>/ios-runner/simulator/<xcodeBuild>-<hash>`. Any simulator can then run it
|
|
9
|
+
* (`test-without-building -destination id=<udid>`). Used by the XCUITest accessibility audit (A11Y-4).
|
|
10
|
+
*/
|
|
11
|
+
export type SimRunnerBuild = {
|
|
12
|
+
/** the `.xctestrun` inside `derivedData` */
|
|
13
|
+
xctestrun: string;
|
|
14
|
+
derivedData: string;
|
|
15
|
+
/** false: built just now */
|
|
16
|
+
cached: boolean;
|
|
17
|
+
};
|
|
18
|
+
export type EnsureSimRunnerInput = {
|
|
19
|
+
env: Record<string, string | undefined>;
|
|
20
|
+
exec: Exec;
|
|
21
|
+
/** warden's own version (part of the staged-source directory name) */
|
|
22
|
+
version: string;
|
|
23
|
+
loadSources: () => AsyncResult<RunnerSourceFiles>;
|
|
24
|
+
/** serialises builds of one (Xcode, sources); without it two concurrent callers may both build */
|
|
25
|
+
lock?: BuildLockDeps;
|
|
26
|
+
home?: string;
|
|
27
|
+
log?: (line: string) => void;
|
|
28
|
+
timeoutMs?: number;
|
|
29
|
+
};
|
|
30
|
+
export declare function simRunnerCacheDir(home: string, xcodeBuild: string, srcHash: string): string;
|
|
31
|
+
export declare function buildSimRunnerArgv(o: {
|
|
32
|
+
project: string;
|
|
33
|
+
derivedData: string;
|
|
34
|
+
}): string[];
|
|
35
|
+
/** A simulator runner build for the current Xcode and runner sources; built now when the cache has none. */
|
|
36
|
+
export declare function ensureSimRunnerBuild(input: EnsureSimRunnerInput): AsyncResult<SimRunnerBuild>;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type AsyncResult } from "../../../types/src/result";
|
|
2
|
+
import type { RunnerSourceFiles } from "./runner-build.types";
|
|
3
|
+
export type StagedRunner = {
|
|
4
|
+
/** `<home>/ios-runner/src/<version>-<hash>` */
|
|
5
|
+
dir: string;
|
|
6
|
+
/** the Xcode project inside it */
|
|
7
|
+
project: string;
|
|
8
|
+
/** `<version>-<hash>`: part of the build cache key */
|
|
9
|
+
key: string;
|
|
10
|
+
/** first 10 hex digits of the sources' hash */
|
|
11
|
+
hash: string;
|
|
12
|
+
};
|
|
13
|
+
/** Where the runner builds live: `<home>/ios-runner`. */
|
|
14
|
+
export declare function runnerRoot(home: string): string;
|
|
15
|
+
/** Content hash of the sources: names and texts, in name order. */
|
|
16
|
+
export declare function runnerSourceHash(files: RunnerSourceFiles): string;
|
|
17
|
+
/**
|
|
18
|
+
* Write the runner sources to `<home>/ios-runner/src/<version>-<hash>/` (the layout of `packages/e2e/native`).
|
|
19
|
+
* Content-keyed and never rewritten in place, so a build still running from an older copy is safe. Written to a
|
|
20
|
+
* sibling temp directory and renamed, so a concurrent warden never sees half a copy; `.ready` marks a complete one.
|
|
21
|
+
*/
|
|
22
|
+
export declare function stageRunnerSources(o: {
|
|
23
|
+
home: string;
|
|
24
|
+
version: string;
|
|
25
|
+
loadSources: () => AsyncResult<RunnerSourceFiles>;
|
|
26
|
+
}): AsyncResult<StagedRunner>;
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import type { Database } from "bun:sqlite";
|
|
2
|
+
import type { Result } from "../../types/src/result";
|
|
3
|
+
import type { WaitReason } from "./queue";
|
|
4
|
+
import type { Platform } from "./types";
|
|
5
|
+
/**
|
|
6
|
+
* The lease event log: an append-only `lease_events` table, one row per thing that happened to a device
|
|
7
|
+
* lease or device. Event type names are `snake_case` throughout (`heartbeat_lost`, `device_unhealthy`).
|
|
8
|
+
* Rows are written inside the same store transaction as the change they record (see `Store`), pruned by
|
|
9
|
+
* `warden gc` after `WARDEN_EVENTS_DAYS` (default 7).
|
|
10
|
+
*/
|
|
11
|
+
/** How a granted lease came to be: `leased` is a caller-side lease on a named device (`warden ui`). */
|
|
12
|
+
export type GrantAction = "reuse" | "boot" | "create" | "leased";
|
|
13
|
+
/** Per-type payloads (the JSON `data` column). */
|
|
14
|
+
export type LeaseEventPayloads = {
|
|
15
|
+
/** a `claimDevices` call started */
|
|
16
|
+
requested: {
|
|
17
|
+
platform: Platform;
|
|
18
|
+
profile: string;
|
|
19
|
+
count: number;
|
|
20
|
+
};
|
|
21
|
+
/** a claim started waiting (boot queue or polling for a device): once per claim call, with why */
|
|
22
|
+
queued: {
|
|
23
|
+
platform: Platform;
|
|
24
|
+
profile: string;
|
|
25
|
+
queueId: string;
|
|
26
|
+
reason?: WaitReason;
|
|
27
|
+
};
|
|
28
|
+
/** a device lease was handed to the caller; `waitMs` = time since the claim was requested */
|
|
29
|
+
granted: {
|
|
30
|
+
platform: Platform;
|
|
31
|
+
profile?: string;
|
|
32
|
+
deviceId: string;
|
|
33
|
+
deviceName: string;
|
|
34
|
+
action: GrantAction;
|
|
35
|
+
waitMs: number;
|
|
36
|
+
};
|
|
37
|
+
/** a holder's heartbeat found its lease gone (released or reclaimed by someone else) */
|
|
38
|
+
heartbeat_lost: Record<string, never>;
|
|
39
|
+
/** the lease was deleted by its owner / a release; `heldMs` = time since it was granted */
|
|
40
|
+
released: {
|
|
41
|
+
heldMs: number;
|
|
42
|
+
};
|
|
43
|
+
/** the lease went stale (owner process gone, heartbeat expired) and was reclaimed */
|
|
44
|
+
expired: {
|
|
45
|
+
heldMs: number;
|
|
46
|
+
};
|
|
47
|
+
/** an idle warden device was shut down to make room for the queue head */
|
|
48
|
+
evicted: {
|
|
49
|
+
platform: Platform;
|
|
50
|
+
deviceId: string;
|
|
51
|
+
deviceName: string;
|
|
52
|
+
};
|
|
53
|
+
quarantined: {
|
|
54
|
+
platform: Platform;
|
|
55
|
+
deviceId: string;
|
|
56
|
+
deviceName: string;
|
|
57
|
+
reason?: string;
|
|
58
|
+
};
|
|
59
|
+
unquarantined: {
|
|
60
|
+
platform: Platform;
|
|
61
|
+
deviceId: string;
|
|
62
|
+
};
|
|
63
|
+
/** warden shut a device down: `idle` (gc idle pass) or `release` (a lease ended with the device) */
|
|
64
|
+
shutdown: {
|
|
65
|
+
platform: Platform;
|
|
66
|
+
deviceId: string;
|
|
67
|
+
deviceName?: string;
|
|
68
|
+
cause: "idle" | "release";
|
|
69
|
+
};
|
|
70
|
+
/** the health monitor under a lease read a device bad `consecutive` times in a row (once per episode) */
|
|
71
|
+
device_unhealthy: {
|
|
72
|
+
platform: Platform;
|
|
73
|
+
deviceId: string;
|
|
74
|
+
reason?: string;
|
|
75
|
+
consecutive?: number;
|
|
76
|
+
};
|
|
77
|
+
/** the monitor rebooted the device under the same lease after `attempts` tries, `durationMs` in all */
|
|
78
|
+
device_recovered: {
|
|
79
|
+
platform: Platform;
|
|
80
|
+
deviceId: string;
|
|
81
|
+
attempts?: number;
|
|
82
|
+
durationMs?: number;
|
|
83
|
+
};
|
|
84
|
+
};
|
|
85
|
+
export type LeaseEventType = keyof LeaseEventPayloads;
|
|
86
|
+
export declare const LEASE_EVENT_TYPES: readonly LeaseEventType[];
|
|
87
|
+
type EventBase = {
|
|
88
|
+
at: number;
|
|
89
|
+
leaseId?: string;
|
|
90
|
+
resourceKey?: string;
|
|
91
|
+
ownerKey?: string;
|
|
92
|
+
label?: string;
|
|
93
|
+
claimId?: string;
|
|
94
|
+
};
|
|
95
|
+
/** An event to write; `data` is typed by `type`. */
|
|
96
|
+
export type NewLeaseEvent = {
|
|
97
|
+
[T in LeaseEventType]: EventBase & {
|
|
98
|
+
type: T;
|
|
99
|
+
data: LeaseEventPayloads[T];
|
|
100
|
+
};
|
|
101
|
+
}[LeaseEventType];
|
|
102
|
+
/** A stored event. */
|
|
103
|
+
export type LeaseEvent = NewLeaseEvent & {
|
|
104
|
+
id: number;
|
|
105
|
+
};
|
|
106
|
+
export declare function insertLeaseEvent(db: Database, event: NewLeaseEvent): void;
|
|
107
|
+
export type EventFilter = {
|
|
108
|
+
/** only events at or after this time (ms) */
|
|
109
|
+
since?: number;
|
|
110
|
+
/** exact lease id */
|
|
111
|
+
leaseId?: string;
|
|
112
|
+
/** the owner key (`agent:<sessionId>`, `user:<pid>`, `ci:<runId>`) or any substring of it */
|
|
113
|
+
owner?: string;
|
|
114
|
+
/** only events with an id above this (the `--follow` cursor) */
|
|
115
|
+
afterId?: number;
|
|
116
|
+
};
|
|
117
|
+
/** Events oldest first (by id), matching every given filter. */
|
|
118
|
+
export declare function queryLeaseEvents(db: Database, filter?: EventFilter): LeaseEvent[];
|
|
119
|
+
/** Delete events older than `before` (ms); returns how many. */
|
|
120
|
+
export declare function pruneLeaseEvents(db: Database, before: number): number;
|
|
121
|
+
export declare const DEFAULT_EVENTS_DAYS = 7;
|
|
122
|
+
/** How long events are kept: `WARDEN_EVENTS_DAYS` (a positive integer) else 7 days. */
|
|
123
|
+
export declare function eventsRetentionMs(env: Record<string, string | undefined>): Result<number>;
|
|
124
|
+
/** Labels of leases warden holds for its own housekeeping: never user-visible grants. */
|
|
125
|
+
export declare const INTERNAL_LEASE_LABELS: readonly string[];
|
|
126
|
+
export {};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { Store } from "./store";
|
|
2
|
+
/**
|
|
3
|
+
* Ownership marker: warden writes `.warden-owned.json` into the device dir of every iOS simulator it creates,
|
|
4
|
+
* naming the host instance (a random id kept in the store `meta`) that made it. Destructive paths (shutdown on
|
|
5
|
+
* release, erase, delete) refuse a sim whose marker names another instance: a second `WARDEN_HOME` or user
|
|
6
|
+
* account on the same machine owns it. A sim with no marker keeps today's rules. Android emulators are AVDs
|
|
7
|
+
* launched from a shared definition, not created per claim, so they carry no marker.
|
|
8
|
+
*/
|
|
9
|
+
export declare const MARKER_FILE = ".warden-owned.json";
|
|
10
|
+
export declare const INSTANCE_META_KEY = "instance_id";
|
|
11
|
+
export type OwnershipMarker = {
|
|
12
|
+
instanceId: string;
|
|
13
|
+
name: string;
|
|
14
|
+
createdAt: number;
|
|
15
|
+
};
|
|
16
|
+
/** The filesystem the marker lives on, injectable for tests. */
|
|
17
|
+
export type MarkerFs = {
|
|
18
|
+
readText: (path: string) => string | undefined;
|
|
19
|
+
writeText: (path: string, text: string) => void;
|
|
20
|
+
isDir: (path: string) => boolean;
|
|
21
|
+
};
|
|
22
|
+
export declare const realMarkerFs: MarkerFs;
|
|
23
|
+
type Env = Record<string, string | undefined>;
|
|
24
|
+
/** This host's stable instance id: created on first use inside a transaction, then read from `meta`. */
|
|
25
|
+
export declare function instanceId(store: Store): string;
|
|
26
|
+
/** `~/Library/Developer/CoreSimulator/Devices/<udid>` (the default device set); undefined without `HOME`. */
|
|
27
|
+
export declare function simDeviceDir(env: Env, udid: string): string | undefined;
|
|
28
|
+
export type MarkerRead = {
|
|
29
|
+
kind: "absent";
|
|
30
|
+
} | {
|
|
31
|
+
kind: "present";
|
|
32
|
+
marker: OwnershipMarker;
|
|
33
|
+
} | {
|
|
34
|
+
kind: "unreadable";
|
|
35
|
+
};
|
|
36
|
+
export declare function readMarker(fs: MarkerFs, env: Env, udid: string): MarkerRead;
|
|
37
|
+
/** Best-effort: only into a device dir that exists, and a failure never fails the create. */
|
|
38
|
+
export declare function writeMarker(fs: MarkerFs, env: Env, store: Store, udid: string, name: string, now: number): boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Why a destructive step must leave this sim alone, else undefined: its marker names another warden instance.
|
|
41
|
+
* A missing or unreadable marker blocks nothing (sims made before markers existed).
|
|
42
|
+
*/
|
|
43
|
+
export declare function markerBlock(fs: MarkerFs, env: Env, store: Store, udid: string): string | undefined;
|
|
44
|
+
export {};
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { type AsyncResult, type Result } from "../../../types/src/result";
|
|
2
|
+
import { type CameraMode } from "../camera-mode";
|
|
2
3
|
import { type PidAlive } from "../liveness";
|
|
4
|
+
import { type Store } from "../store";
|
|
3
5
|
import type { Lease } from "../types";
|
|
4
6
|
import type { DeviceProvider, ProviderDeps } from "./provider.types";
|
|
5
7
|
/** Emulator console ports: even, 5554–5584; the adb port is console + 1. */
|
|
@@ -115,7 +117,12 @@ export declare function rejectedOption(error: string): boolean;
|
|
|
115
117
|
/** A claim's emulator: headless, `-read-only` (one AVD backs many), from `snapshot` when given, never saving on exit. */
|
|
116
118
|
export declare function claimEmulatorArgv(tools: AndroidTools, env: Record<string, string | undefined>, avd: string, port: number, snapshot?: string,
|
|
117
119
|
/** `false` leaves out `headlessEmulatorFlags` (an emulator that rejected them) */
|
|
118
|
-
headless?: boolean
|
|
120
|
+
headless?: boolean,
|
|
121
|
+
/** a `+cam` device's feeds: each camera plays `mode`'s video, else reads `<facing>.png` from `dir` */
|
|
122
|
+
camera?: {
|
|
123
|
+
dir: string;
|
|
124
|
+
mode: CameraMode;
|
|
125
|
+
}): string[];
|
|
119
126
|
export type BootWaitDeps = ExecDeps & {
|
|
120
127
|
now: () => number;
|
|
121
128
|
sleep: (ms: number) => Promise<void>;
|
|
@@ -150,6 +157,8 @@ export declare const GOLDEN_PORT_LABEL = "emulator golden ";
|
|
|
150
157
|
* and unverified on a device; a successful load logs `Successfully loaded snapshot …`, which does not match.
|
|
151
158
|
*/
|
|
152
159
|
export declare function snapshotLoadFailed(log: string): boolean;
|
|
160
|
+
/** The bookkeeping of an emulator that is gone: its console-port lease and its `devices` record. */
|
|
161
|
+
export declare function forgetAndroidEmulator(store: Store, serial: string): void;
|
|
153
162
|
/**
|
|
154
163
|
* Global settings applied after every boot. On a fresh playstore image the first `adb install` can pop a Play
|
|
155
164
|
* Protect consent dialog (a GMS activity the e2e alert detector cannot classify), and every adb install otherwise
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { AsyncResult } from "../../../types/src/result";
|
|
2
2
|
import type { Exec } from "../exec";
|
|
3
|
+
import type { MarkerFs } from "../ownership";
|
|
3
4
|
import type { Store } from "../store";
|
|
4
5
|
import type { InventoryDevice, Owner, Platform } from "../types";
|
|
5
6
|
import type { Timer } from "./health";
|
|
@@ -36,6 +37,12 @@ export interface DeviceProvider {
|
|
|
36
37
|
* shutdown reuse), never for an already-booted one. A failure is logged by the provider, never fatal.
|
|
37
38
|
*/
|
|
38
39
|
prepareBoot?(id: string): Promise<void>;
|
|
40
|
+
/**
|
|
41
|
+
* Restart a running warden device in place (same id, same console port, device record and port lease kept) so
|
|
42
|
+
* launch-time settings apply again; the caller then waits with `waitReady`. Android camera emulators only
|
|
43
|
+
* (the camera source is fixed at launch). Absent = the platform cannot.
|
|
44
|
+
*/
|
|
45
|
+
relaunch?(id: string): AsyncResult<void>;
|
|
39
46
|
/** Called once the device is ready, before the health probe. Best effort. */
|
|
40
47
|
afterReady?(id: string): Promise<void>;
|
|
41
48
|
}
|
|
@@ -60,4 +67,6 @@ export type ProviderDeps = {
|
|
|
60
67
|
timer?: (ms: number) => Timer;
|
|
61
68
|
/** progress notes (e.g. "cloning from golden…") — stderr in the CLI */
|
|
62
69
|
log?: (line: string) => void;
|
|
70
|
+
/** where the iOS ownership marker is written (default: the real filesystem) */
|
|
71
|
+
markerFs?: MarkerFs;
|
|
63
72
|
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { type Result } from "../../types/src/result";
|
|
2
|
+
import type { CapacityReason } from "./claim-errors";
|
|
3
|
+
import type { QueueEntry } from "./types";
|
|
4
|
+
export declare const PRIORITY: {
|
|
5
|
+
readonly interactive: 30;
|
|
6
|
+
readonly high: 20;
|
|
7
|
+
readonly normal: 10;
|
|
8
|
+
readonly low: 0;
|
|
9
|
+
};
|
|
10
|
+
export type PriorityName = keyof typeof PRIORITY;
|
|
11
|
+
/** `high` for 20, the number itself for a custom priority. */
|
|
12
|
+
export declare const priorityName: (priority: number) => string;
|
|
13
|
+
/** Gap between adjacent named priorities (`low` → `normal` → `high` → `interactive`). */
|
|
14
|
+
export declare const PRIORITY_STEP = 10;
|
|
15
|
+
/**
|
|
16
|
+
* One priority level (`PRIORITY_STEP` points) is earned per this much waiting, so a `low` row overtakes
|
|
17
|
+
* a fresh `high` one after 20 min, inside the default 30 min `--wait`.
|
|
18
|
+
*/
|
|
19
|
+
export declare const AGING_MS: number;
|
|
20
|
+
export declare function parsePriority(input: string): Result<number>;
|
|
21
|
+
/** `priority + PRIORITY_STEP × floor(age / 10 min)`; clock skew (now < enqueuedAt) earns nothing. */
|
|
22
|
+
export declare function effectivePriority(entry: QueueEntry, now: number): number;
|
|
23
|
+
/** Head first: effective priority desc, then enqueued_at asc, then id. */
|
|
24
|
+
export declare function queueOrder(entries: readonly QueueEntry[], now: number): QueueEntry[];
|
|
25
|
+
/** 1-based position, or undefined when `id` is not queued. */
|
|
26
|
+
export declare function queuePosition(entries: readonly QueueEntry[], id: string, now: number): number | undefined;
|
|
27
|
+
export declare function isHead(entries: readonly QueueEntry[], id: string, now: number): boolean;
|
|
28
|
+
/** Why a polling claim has no device yet although the host has room: nothing to reserve. */
|
|
29
|
+
export declare const DEVICE_WAIT_REASONS: readonly ["device-limit", "requester-limit", "waiting-for-device"];
|
|
30
|
+
export type DeviceWaitReason = (typeof DEVICE_WAIT_REASONS)[number];
|
|
31
|
+
/**
|
|
32
|
+
* Why a claim is not running yet. Queued for host capacity: behind an earlier claim, or the resource the
|
|
33
|
+
* host is out of. Polling for a device: the pool is at `--max`, the owner is at its limit, or a physical
|
|
34
|
+
* device is not free / ready.
|
|
35
|
+
*/
|
|
36
|
+
export type WaitReason = "behind-earlier" | CapacityReason | DeviceWaitReason;
|
|
37
|
+
export declare const isWaitReason: (s: string) => s is WaitReason;
|
|
38
|
+
export declare const isDeviceWaitReason: (reason: WaitReason | undefined) => reason is DeviceWaitReason;
|
|
39
|
+
/**
|
|
40
|
+
* Rows that take part in admission order. A claim polling for a device holds a row so `warden queue` can
|
|
41
|
+
* show why it waits, but it needs no boot slot: it is never ahead of, or counted against, a claim that does.
|
|
42
|
+
*/
|
|
43
|
+
export declare const admissionQueue: (entries: readonly QueueEntry[]) => QueueEntry[];
|
|
@@ -1,9 +1,21 @@
|
|
|
1
1
|
import { Database } from "bun:sqlite";
|
|
2
|
+
import { type EventFilter, type LeaseEvent, type NewLeaseEvent } from "./lease-events";
|
|
2
3
|
import { type PidAlive } from "./liveness";
|
|
4
|
+
import { type WaitReason } from "./queue";
|
|
3
5
|
import type { Lease, Owner, Platform, QueueEntry, Resource } from "./types";
|
|
4
6
|
/** `$WARDEN_HOME`, else `~/.warden`. */
|
|
5
7
|
export declare function wardenHome(env?: Record<string, string | undefined>): string;
|
|
6
8
|
export declare function defaultDbPath(env?: Record<string, string | undefined>): string;
|
|
9
|
+
/** An owner's idempotency key and the claim it created (one row per key, however many leases the claim holds). */
|
|
10
|
+
export type ClaimKey = {
|
|
11
|
+
ownerKey: string;
|
|
12
|
+
key: string;
|
|
13
|
+
claimId: string;
|
|
14
|
+
platform: string;
|
|
15
|
+
profile: string;
|
|
16
|
+
count: number;
|
|
17
|
+
ready: boolean;
|
|
18
|
+
};
|
|
7
19
|
/**
|
|
8
20
|
* Ordered schema migrations; index + 1 = `PRAGMA user_version` after applying. Append only — never
|
|
9
21
|
* edit an entry that has shipped.
|
|
@@ -12,6 +24,11 @@ export declare const MIGRATIONS: readonly string[];
|
|
|
12
24
|
/** A queued claim that has not refreshed within this long is dead (its waiter polls every few seconds). */
|
|
13
25
|
export declare const QUEUE_TTL_MS = 60000;
|
|
14
26
|
export declare function ownerKey(owner: Owner): string;
|
|
27
|
+
/** Why leases are being deleted when it is history worth logging (see `Store.deleteLeases`). */
|
|
28
|
+
export type LeaseEnd = {
|
|
29
|
+
type: "released" | "expired";
|
|
30
|
+
now: number;
|
|
31
|
+
};
|
|
15
32
|
export type NewLease = {
|
|
16
33
|
resource: Resource;
|
|
17
34
|
owner: Owner;
|
|
@@ -20,6 +37,7 @@ export type NewLease = {
|
|
|
20
37
|
pid?: number;
|
|
21
38
|
bootedByOwner?: boolean;
|
|
22
39
|
shared?: boolean;
|
|
40
|
+
claimId?: string;
|
|
23
41
|
};
|
|
24
42
|
/** A device warden created (sim) or launched (emulator). */
|
|
25
43
|
export type DeviceRecord = {
|
|
@@ -35,6 +53,8 @@ export type DeviceRecord = {
|
|
|
35
53
|
quarantineReason?: string;
|
|
36
54
|
/** set while the simulator's daemons are switched off (`sim slim`); cleared by `--restore` */
|
|
37
55
|
slimmedAt?: number;
|
|
56
|
+
/** set when `warden dev` took the sim: kept on shutdown (not ephemeral) so installs and logins survive */
|
|
57
|
+
persistentAt?: number;
|
|
38
58
|
};
|
|
39
59
|
export declare function newLeaseId(): string;
|
|
40
60
|
/**
|
|
@@ -61,7 +81,29 @@ export declare class Store {
|
|
|
61
81
|
listLeases(): Lease[];
|
|
62
82
|
listLeasesByOwner(owner: Owner): Lease[];
|
|
63
83
|
findLeaseByResource(resource: Resource): Lease | undefined;
|
|
64
|
-
|
|
84
|
+
/**
|
|
85
|
+
* Delete leases. With `end`, a device lease that was granted (has a `granted` event) also logs `end.type`
|
|
86
|
+
* in the same transaction. Without it nothing is logged: rollbacks, create placeholders, eviction holds,
|
|
87
|
+
* ports and build locks are not lease history.
|
|
88
|
+
*/
|
|
89
|
+
deleteLeases(ids: readonly string[], end?: LeaseEnd): number;
|
|
90
|
+
private recordLeaseEnd;
|
|
91
|
+
/** Append one lease event (join the caller's transaction when there is one). */
|
|
92
|
+
recordEvent(event: NewLeaseEvent): void;
|
|
93
|
+
/** `queued` is logged once per claim call (its queue row is refreshed every round): false when already there. */
|
|
94
|
+
recordQueuedOnce(event: Extract<NewLeaseEvent, {
|
|
95
|
+
type: "queued";
|
|
96
|
+
}>): boolean;
|
|
97
|
+
/**
|
|
98
|
+
* A holder's heartbeat found these leases gone: log `heartbeat_lost` for those no longer held, copying
|
|
99
|
+
* who/what from their `granted` event. Returns how many were logged.
|
|
100
|
+
*/
|
|
101
|
+
recordHeartbeatLost(ids: readonly string[], now: number): number;
|
|
102
|
+
/** The `heartbeat_lost` event of a granted lease (who/what copied from its grant); undefined for one never granted (ports, builds). */
|
|
103
|
+
private heartbeatLostEvent;
|
|
104
|
+
listEvents(filter?: EventFilter): LeaseEvent[];
|
|
105
|
+
/** Delete events older than `before` (ms). */
|
|
106
|
+
pruneEvents(before: number): number;
|
|
65
107
|
/**
|
|
66
108
|
* Stamp `heartbeat_at = now` on `ids`; returns how many leases were updated (fewer than `ids` = some
|
|
67
109
|
* were released). `periodMs` (the holder's heartbeat interval) marks them supervised: with a pid,
|
|
@@ -72,8 +114,17 @@ export declare class Store {
|
|
|
72
114
|
reclaimStale(now: number, pidAlive: PidAlive): string[];
|
|
73
115
|
/** Insert a queue row; an existing id keeps its `enqueuedAt` (queue age) and updates the rest. */
|
|
74
116
|
enqueue(entry: QueueEntry): QueueEntry;
|
|
75
|
-
/** Stamp `heartbeat_at`; false when the row is gone (reclaimed or dequeued). */
|
|
76
|
-
refreshQueue(id: string, now: number): boolean;
|
|
117
|
+
/** Stamp `heartbeat_at` and the live wait `reason`; false when the row is gone (reclaimed or dequeued). */
|
|
118
|
+
refreshQueue(id: string, now: number, reason?: WaitReason): boolean;
|
|
119
|
+
/** The claim an owner's idempotency key points at (caller holds the transaction), if any. */
|
|
120
|
+
getClaimKey(ownerKey: string, key: string): ClaimKey | undefined;
|
|
121
|
+
/** Point (owner, key) at a claim, replacing a stale mapping; `ready` stays false until the claim is granted. */
|
|
122
|
+
setClaimKey(entry: Omit<ClaimKey, "ready">, now: number): void;
|
|
123
|
+
/** The claim's leases are all granted: a repeat of its key now returns them. */
|
|
124
|
+
markClaimKeyReady(claimId: string): void;
|
|
125
|
+
deleteClaimKey(ownerKey: string, key: string): void;
|
|
126
|
+
/** Forget the keys of a claim that failed (its leases are gone). */
|
|
127
|
+
deleteClaimKeysOf(claimId: string): void;
|
|
77
128
|
dequeue(id: string): boolean;
|
|
78
129
|
/** Raw insertion order (`enqueued_at`, id); use `queueOrder` for admission order. */
|
|
79
130
|
listQueue(): QueueEntry[];
|
|
@@ -83,11 +134,16 @@ export declare class Store {
|
|
|
83
134
|
listDevices(platform?: Platform): DeviceRecord[];
|
|
84
135
|
touchDevice(platform: Platform, id: string, now: number): void;
|
|
85
136
|
/** Quarantine a warden device (recording it first when only its name marked it ours). */
|
|
86
|
-
quarantineDevice(device: Omit<DeviceRecord, "createdAt" | "lastUsedAt" | "quarantinedAt" | "quarantineReason" | "slimmedAt">, reason: string | undefined, now: number): void;
|
|
137
|
+
quarantineDevice(device: Omit<DeviceRecord, "createdAt" | "lastUsedAt" | "quarantinedAt" | "quarantineReason" | "slimmedAt" | "persistentAt">, reason: string | undefined, now: number): void;
|
|
87
138
|
/** Clear quarantine on every recorded device with this id (any platform); returns how many were cleared. */
|
|
88
|
-
unquarantineDevice(id: string): number;
|
|
139
|
+
unquarantineDevice(id: string, now: number): number;
|
|
89
140
|
/** Record (`at` set) or clear (`at` undefined) that a recorded device's daemons are slimmed. */
|
|
90
141
|
setSlimmed(platform: Platform, id: string, at: number | undefined): void;
|
|
142
|
+
/**
|
|
143
|
+
* Mark a recorded device persistent (never deleted as ephemeral). Update-only: a record is what makes a device
|
|
144
|
+
* warden-created (`shouldShutdown`), so a sim that was already running when leased must not gain one here.
|
|
145
|
+
*/
|
|
146
|
+
markPersistent(platform: Platform, id: string, now: number): void;
|
|
91
147
|
forgetDevice(platform: Platform, id: string): void;
|
|
92
148
|
/**
|
|
93
149
|
* What a `warden gc` pass could find to do, from the store alone (no simulator is asked):
|