@mpgd/cli 0.34.0 → 0.36.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.
Files changed (49) hide show
  1. package/dist/android-bundle-signer.d.ts +2 -0
  2. package/dist/android-bundle-signer.js +38 -0
  3. package/dist/android-signing-session.d.ts +22 -0
  4. package/dist/android-signing-session.js +198 -0
  5. package/dist/artifact-inspection-environment.d.ts +2 -0
  6. package/dist/artifact-inspection-environment.js +31 -0
  7. package/dist/asc-pin.json +26 -0
  8. package/dist/capacitor-shell-starter.d.ts +1 -0
  9. package/dist/capacitor-shell-starter.js +9 -6
  10. package/dist/deploy-planning.d.ts +64 -0
  11. package/dist/deploy-planning.js +471 -0
  12. package/dist/deploy-process.d.ts +28 -0
  13. package/dist/deploy-process.js +197 -0
  14. package/dist/index.d.ts +8 -0
  15. package/dist/index.js +483 -13
  16. package/dist/ios-ipa-inspection.js +151 -0
  17. package/dist/ios-keychain-import.swift +92 -0
  18. package/dist/ios-plist.d.ts +2 -0
  19. package/dist/ios-plist.js +75 -0
  20. package/dist/ios-signing-profile.d.ts +12 -0
  21. package/dist/ios-signing-profile.js +60 -0
  22. package/dist/ios-signing-session.d.ts +22 -0
  23. package/dist/ios-signing-session.js +256 -0
  24. package/dist/kit-upgrade-target-readiness.d.ts +8 -0
  25. package/dist/kit-upgrade-target-readiness.js +119 -0
  26. package/dist/kit-upgrade.d.ts +41 -0
  27. package/dist/kit-upgrade.js +489 -0
  28. package/dist/native-build-info.json +5 -0
  29. package/dist/native-build-target.js +12032 -0
  30. package/dist/native-deploy-run.d.ts +35 -0
  31. package/dist/native-deploy-run.js +462 -0
  32. package/dist/native-deploy-submission.d.ts +40 -0
  33. package/dist/native-deploy-submission.js +244 -0
  34. package/dist/native-deploy-workflow.d.ts +11 -0
  35. package/dist/native-deploy-workflow.js +328 -0
  36. package/dist/play-internal-submission.d.ts +32 -0
  37. package/dist/play-internal-submission.js +247 -0
  38. package/dist/play-publisher-port.d.ts +30 -0
  39. package/dist/play-publisher-port.js +1 -0
  40. package/dist/play-sdk-adapter.js +78 -0
  41. package/dist/release-state.d.ts +140 -0
  42. package/dist/release-state.js +1046 -0
  43. package/dist/release-workspace.d.ts +61 -0
  44. package/dist/release-workspace.js +504 -0
  45. package/dist/testflight-submission.d.ts +49 -0
  46. package/dist/testflight-submission.js +462 -0
  47. package/package.json +20 -13
  48. package/templates/phaser-game/README.md +23 -8
  49. package/templates/phaser-game/src/main.ts +34 -33
@@ -0,0 +1,247 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { createReadStream, existsSync, statSync } from 'node:fs';
3
+ import { inspectAndroidBundleSigner } from './android-bundle-signer.js';
4
+ const sha256Pattern = /^[a-f0-9]{64}$/u;
5
+ const editIdPattern = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/u;
6
+ const trackName = 'internal';
7
+ const bundleUploadTimeoutMs = 120_000;
8
+ export class PlaySubmissionUncertainError extends Error {
9
+ stage;
10
+ editId;
11
+ constructor(stage, editId) {
12
+ super(`Google Play ${stage} result is uncertain for edit ${editId}; inspect remote state before retry.`);
13
+ this.stage = stage;
14
+ this.editId = editId;
15
+ this.name = 'PlaySubmissionUncertainError';
16
+ }
17
+ }
18
+ /** Submit only a previously inspected, immutable Android build record. */
19
+ export async function submitVerifiedAndroidBundle(input) {
20
+ if (!existsSync(input.serviceAccountFile) || !statSync(input.serviceAccountFile).isFile()) {
21
+ throw new Error('Google Play service account file is missing.');
22
+ }
23
+ const { createServiceAccountPublisher } = await import(new URL('./play-sdk-adapter.js', import.meta.url).href);
24
+ const publisher = createServiceAccountPublisher(input.serviceAccountFile);
25
+ return submitVerifiedAndroidBundleWithPublisher(input, publisher);
26
+ }
27
+ /** Internal injection point for credential-free SDK contract tests. */
28
+ export async function submitVerifiedAndroidBundleWithPublisher(input, publisher, requestRootUrl) {
29
+ const versionCode = await preflight(input);
30
+ const expectedSha256 = input.record.artifactSha256;
31
+ const packageName = input.packageName;
32
+ if (input.resumeEditId !== undefined && !editIdPattern.test(input.resumeEditId)) {
33
+ throw new Error('Google Play resume edit ID is malformed.');
34
+ }
35
+ input.signal?.throwIfAborted();
36
+ let editId = input.resumeEditId ?? await publisher.insertEdit(packageName, input.signal);
37
+ let freshEdit = input.resumeEditId === undefined;
38
+ if (input.resumeEditId === undefined) {
39
+ try {
40
+ await input.onEditCreated?.(editId);
41
+ }
42
+ catch {
43
+ throw new PlaySubmissionUncertainError('checkpoint', editId);
44
+ }
45
+ }
46
+ let bundles;
47
+ try {
48
+ bundles = await publisher.listBundles(packageName, editId, input.signal);
49
+ }
50
+ catch (error) {
51
+ if (input.resumeEditId === undefined || !isHttpStatus(error, 404)) {
52
+ throw error;
53
+ }
54
+ // A successful commit may have invalidated the old edit while its response
55
+ // was lost. Open a new snapshot solely to inspect the live internal track.
56
+ editId = await publisher.insertEdit(packageName, input.signal);
57
+ freshEdit = true;
58
+ try {
59
+ await input.onEditCreated?.(editId);
60
+ }
61
+ catch {
62
+ throw new PlaySubmissionUncertainError('checkpoint', editId);
63
+ }
64
+ bundles = await publisher.listBundles(packageName, editId, input.signal);
65
+ }
66
+ let existing = findVersionBundle(bundles, versionCode, expectedSha256);
67
+ const currentTrack = await getInternalTrack(publisher, packageName, editId, input.signal);
68
+ if (hasVersion(currentTrack.releases, versionCode)) {
69
+ if (existing === undefined) {
70
+ throw new Error('Google Play internal track has this version without matching bundle bytes.');
71
+ }
72
+ if (!hasCompletedVersion(currentTrack.releases, versionCode)) {
73
+ throw new Error('Google Play internal track version is not completed; inspect it before resubmitting.');
74
+ }
75
+ if (freshEdit) {
76
+ return result(input, editId, versionCode, true);
77
+ }
78
+ // A resumed edit sees its own uncommitted track. Publish it before success.
79
+ await publisher.validateEdit(packageName, editId, input.signal);
80
+ try {
81
+ await publisher.commitEdit(packageName, editId, input.signal);
82
+ }
83
+ catch {
84
+ throw new PlaySubmissionUncertainError('commit', editId);
85
+ }
86
+ return result(input, editId, versionCode, false);
87
+ }
88
+ if (existing === undefined) {
89
+ try {
90
+ const uploaded = await publisher.uploadBundle({
91
+ packageName,
92
+ editId,
93
+ aabFile: input.aabFile,
94
+ timeoutMs: bundleUploadTimeoutMs,
95
+ ...(input.signal === undefined ? {} : { signal: input.signal }),
96
+ ...(requestRootUrl === undefined ? {} : { requestRootUrl }),
97
+ });
98
+ assertBundle(uploaded, versionCode, expectedSha256);
99
+ }
100
+ catch (error) {
101
+ if (isDefinitiveHttpError(error)) {
102
+ throw error;
103
+ }
104
+ try {
105
+ bundles = await publisher.listBundles(packageName, editId, input.signal);
106
+ existing = findVersionBundle(bundles, versionCode, expectedSha256);
107
+ }
108
+ catch {
109
+ throw new PlaySubmissionUncertainError('upload', editId);
110
+ }
111
+ if (existing === undefined) {
112
+ throw new PlaySubmissionUncertainError('upload', editId);
113
+ }
114
+ }
115
+ }
116
+ const release = {
117
+ name: input.record.releaseKey,
118
+ status: 'completed',
119
+ versionCodes: [String(versionCode)],
120
+ };
121
+ const desired = {
122
+ track: trackName,
123
+ releases: [...(currentTrack.releases ?? []), release],
124
+ };
125
+ try {
126
+ await publisher.updateTrack(packageName, editId, desired, input.signal);
127
+ }
128
+ catch (error) {
129
+ let applied;
130
+ try {
131
+ const observed = await getInternalTrack(publisher, packageName, editId, input.signal);
132
+ applied = hasCompletedVersion(observed.releases, versionCode);
133
+ }
134
+ catch {
135
+ throw new PlaySubmissionUncertainError('track-update', editId);
136
+ }
137
+ if (!applied) {
138
+ throw error;
139
+ }
140
+ }
141
+ try {
142
+ await publisher.validateEdit(packageName, editId, input.signal);
143
+ }
144
+ catch (error) {
145
+ if (isDefinitiveHttpError(error)) {
146
+ throw error;
147
+ }
148
+ throw new PlaySubmissionUncertainError('validate', editId);
149
+ }
150
+ try {
151
+ await publisher.commitEdit(packageName, editId, input.signal);
152
+ return result(input, editId, versionCode, false);
153
+ }
154
+ catch {
155
+ // Inserting a new edit invalidates the first edit for this API user. A
156
+ // failed commit response cannot safely be reconciled by creating one.
157
+ throw new PlaySubmissionUncertainError('commit', editId);
158
+ }
159
+ }
160
+ async function preflight(input) {
161
+ input.signal?.throwIfAborted();
162
+ const record = input.record;
163
+ const versionCode = record.platformVersion.versionCode;
164
+ if (record.target !== 'android' || input.packageName !== record.inspectedAppId
165
+ || !Number.isSafeInteger(versionCode) || Number(versionCode) < 1
166
+ || !sha256Pattern.test(record.artifactSha256)
167
+ || !sha256Pattern.test(record.inspectedSignerSha256 ?? '')
168
+ || !input.aabFile.endsWith('.aab')
169
+ || !existsSync(input.aabFile) || !statSync(input.aabFile).isFile()) {
170
+ throw new Error('Google Play submission requires a matching verified Android AAB record.');
171
+ }
172
+ const hash = createHash('sha256');
173
+ for await (const chunk of createReadStream(input.aabFile)) {
174
+ hash.update(chunk);
175
+ }
176
+ input.signal?.throwIfAborted();
177
+ if (hash.digest('hex') !== record.artifactSha256) {
178
+ throw new Error('Google Play AAB bytes differ from the immutable build record.');
179
+ }
180
+ if (await inspectAndroidBundleSigner(input.aabFile)
181
+ !== record.inspectedSignerSha256?.replaceAll(':', '').toLowerCase()) {
182
+ throw new Error('Google Play AAB signer differs from the immutable build record.');
183
+ }
184
+ return Number(versionCode);
185
+ }
186
+ function assertBundle(bundle, versionCode, sha256) {
187
+ if (bundle.versionCode !== versionCode || bundle.sha256?.toLowerCase() !== sha256) {
188
+ throw new Error('Google Play returned a different bundle version or SHA-256.');
189
+ }
190
+ }
191
+ function findVersionBundle(bundles, versionCode, sha256) {
192
+ const sameVersion = bundles.find((bundle) => bundle.versionCode === versionCode);
193
+ if (sameVersion !== undefined) {
194
+ assertBundle(sameVersion, versionCode, sha256);
195
+ }
196
+ return sameVersion;
197
+ }
198
+ async function getInternalTrack(publisher, packageName, editId, signal) {
199
+ try {
200
+ return await publisher.getTrack(packageName, editId, signal);
201
+ }
202
+ catch (error) {
203
+ if (isHttpStatus(error, 404)) {
204
+ return { track: trackName, releases: [] };
205
+ }
206
+ throw error;
207
+ }
208
+ }
209
+ function isHttpStatus(error, status) {
210
+ if (typeof error !== 'object' || error === null) {
211
+ return false;
212
+ }
213
+ const value = error;
214
+ return value.code === status || value.response?.status === status;
215
+ }
216
+ function isDefinitiveHttpError(error) {
217
+ if (typeof error !== 'object' || error === null) {
218
+ return false;
219
+ }
220
+ const value = error;
221
+ const raw = value.response?.status ?? value.code;
222
+ const status = typeof raw === 'number'
223
+ ? raw
224
+ : typeof raw === 'string' && /^\d{3}$/u.test(raw)
225
+ ? Number(raw)
226
+ : undefined;
227
+ return status !== undefined && status >= 400 && status < 500
228
+ && status !== 408 && status !== 429;
229
+ }
230
+ function hasVersion(releases, versionCode) {
231
+ return releases?.some((release) => release.versionCodes?.includes(String(versionCode))) ?? false;
232
+ }
233
+ function hasCompletedVersion(releases, versionCode) {
234
+ return releases?.some((release) => release.status === 'completed'
235
+ && release.versionCodes?.includes(String(versionCode))) ?? false;
236
+ }
237
+ function result(input, editId, versionCode, alreadyCommitted) {
238
+ return {
239
+ status: 'committed',
240
+ packageName: input.packageName,
241
+ editId,
242
+ track: trackName,
243
+ versionCode,
244
+ bundleSha256: input.record.artifactSha256,
245
+ alreadyCommitted,
246
+ };
247
+ }
@@ -0,0 +1,30 @@
1
+ /** Minimal store command port. The Google SDK lives under adapters/, not in the CLI. */
2
+ export interface PlayBundle {
3
+ readonly versionCode?: number | null;
4
+ readonly sha256?: string | null;
5
+ }
6
+ export interface PlayTrackRelease {
7
+ readonly name?: string | null | undefined;
8
+ readonly status?: string | null | undefined;
9
+ readonly versionCodes?: readonly string[] | null | undefined;
10
+ }
11
+ export interface PlayTrack {
12
+ readonly track?: string | null | undefined;
13
+ readonly releases?: readonly PlayTrackRelease[] | null | undefined;
14
+ }
15
+ export interface PlayPublisher {
16
+ insertEdit(packageName: string, editIdSignal?: AbortSignal): Promise<string>;
17
+ listBundles(packageName: string, editId: string, signal?: AbortSignal): Promise<PlayBundle[]>;
18
+ uploadBundle(input: {
19
+ readonly packageName: string;
20
+ readonly editId: string;
21
+ readonly aabFile: string;
22
+ readonly timeoutMs: number;
23
+ readonly requestRootUrl?: string;
24
+ readonly signal?: AbortSignal;
25
+ }): Promise<PlayBundle>;
26
+ getTrack(packageName: string, editId: string, signal?: AbortSignal): Promise<PlayTrack>;
27
+ updateTrack(packageName: string, editId: string, track: PlayTrack, signal?: AbortSignal): Promise<void>;
28
+ validateEdit(packageName: string, editId: string, signal?: AbortSignal): Promise<void>;
29
+ commitEdit(packageName: string, editId: string, signal?: AbortSignal): Promise<void>;
30
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,78 @@
1
+ // adapters/play-publisher/src/index.ts
2
+ import { createReadStream } from "node:fs";
3
+ import { androidpublisher, auth } from "@googleapis/androidpublisher";
4
+ var scope = "https://www.googleapis.com/auth/androidpublisher";
5
+ function createServiceAccountPublisher(serviceAccountFile) {
6
+ const googleAuth = new auth.GoogleAuth({ keyFile: serviceAccountFile, scopes: [scope] });
7
+ return wrapPublisher(androidpublisher({ version: "v3", auth: googleAuth }));
8
+ }
9
+ function createMockPublisher(accessToken, rootUrl) {
10
+ const oauth = new auth.OAuth2();
11
+ oauth.setCredentials({ access_token: accessToken });
12
+ return wrapPublisher(androidpublisher({ version: "v3", auth: oauth, rootUrl }));
13
+ }
14
+ function wrapPublisher(publisher) {
15
+ return {
16
+ async insertEdit(packageName, signal) {
17
+ const response = await publisher.edits.insert(
18
+ { packageName },
19
+ { retry: false, signal }
20
+ );
21
+ if (typeof response.data.id !== "string" || response.data.id.trim() === "") {
22
+ throw new Error("Google Play did not return an edit ID.");
23
+ }
24
+ return response.data.id;
25
+ },
26
+ async listBundles(packageName, editId, signal) {
27
+ return (await publisher.edits.bundles.list(
28
+ { packageName, editId },
29
+ { signal }
30
+ )).data.bundles ?? [];
31
+ },
32
+ async uploadBundle(input) {
33
+ const response = await publisher.edits.bundles.upload({
34
+ packageName: input.packageName,
35
+ editId: input.editId,
36
+ media: { mimeType: "application/octet-stream", body: createReadStream(input.aabFile) }
37
+ }, {
38
+ timeout: input.timeoutMs,
39
+ retry: false,
40
+ signal: input.signal,
41
+ // The generated SDK's media endpoint ignores the client's rootUrl.
42
+ ...input.requestRootUrl === void 0 ? {} : { rootUrl: input.requestRootUrl }
43
+ });
44
+ return response.data;
45
+ },
46
+ async getTrack(packageName, editId, signal) {
47
+ return (await publisher.edits.tracks.get(
48
+ { packageName, editId, track: "internal" },
49
+ { signal }
50
+ )).data;
51
+ },
52
+ async updateTrack(packageName, editId, track, signal) {
53
+ await publisher.edits.tracks.update({
54
+ packageName,
55
+ editId,
56
+ track: "internal",
57
+ requestBody: track
58
+ }, { retry: false, signal });
59
+ },
60
+ async validateEdit(packageName, editId, signal) {
61
+ await publisher.edits.validate(
62
+ { packageName, editId },
63
+ { retry: false, signal }
64
+ );
65
+ },
66
+ async commitEdit(packageName, editId, signal) {
67
+ await publisher.edits.commit({
68
+ packageName,
69
+ editId,
70
+ changesInReviewBehavior: "ERROR_IF_IN_REVIEW"
71
+ }, { retry: false, signal });
72
+ }
73
+ };
74
+ }
75
+ export {
76
+ createMockPublisher,
77
+ createServiceAccountPublisher
78
+ };
@@ -0,0 +1,140 @@
1
+ import { type PlatformVersionLedger, type PlatformVersionReleasePlan, type PlatformVersionTargetRequest } from '@mpgd/target-config';
2
+ export interface NativeReleaseReservationInput {
3
+ readonly gameRoot: string;
4
+ readonly releaseKey: string;
5
+ readonly gameId: string;
6
+ readonly gameVersion: string;
7
+ readonly sourceGitSha: string;
8
+ readonly kitGitSha: string;
9
+ readonly targetConfigDigest: string;
10
+ readonly targets: readonly PlatformVersionTargetRequest[];
11
+ /** Required only when a game has no existing release-state ledger. */
12
+ readonly initialLedger?: PlatformVersionLedger | undefined;
13
+ readonly environment?: NodeJS.ProcessEnv;
14
+ readonly signal?: AbortSignal;
15
+ }
16
+ export interface NativeReleaseReservation {
17
+ readonly plan: PlatformVersionReleasePlan;
18
+ readonly stateCommit: string;
19
+ readonly reused: boolean;
20
+ }
21
+ export interface ImmutableNativeBuildRecord {
22
+ readonly releaseKey: string;
23
+ readonly target: 'android' | 'ios';
24
+ readonly buildRunId: string;
25
+ readonly gameVersion: string;
26
+ readonly sourceGitSha: string;
27
+ readonly kitGitSha: string;
28
+ readonly kitPackageVersion: string;
29
+ readonly buildConfigDigest: string;
30
+ readonly targetConfigDigest: string;
31
+ /** Absent only on historical build records predating deployment-profile binding. */
32
+ readonly deployConfigSha256?: string;
33
+ readonly deploymentProfile?: string;
34
+ readonly deploymentDestination?: 'play-internal' | 'testflight';
35
+ readonly internalTestGroupId?: string;
36
+ readonly platformVersion: Readonly<Record<string, unknown>>;
37
+ readonly artifactLocation: string;
38
+ readonly artifactSha256: string;
39
+ readonly releaseManifestSha256: string;
40
+ readonly inspectedAppId: string;
41
+ readonly inspectedSignerSha256?: string | undefined;
42
+ readonly inspectedTeamId?: string | undefined;
43
+ }
44
+ export type NativeSubmissionStatus = 'started' | 'edit-open' | 'upload-committed' | 'uploaded' | 'processing' | 'testflight-ready' | 'action-required' | 'failed' | 'unknown' | 'committed';
45
+ export declare function isNativeSubmissionSettled(status: NativeSubmissionStatus): boolean;
46
+ export interface NativeSubmissionCheckpoint {
47
+ readonly releaseKey: string;
48
+ readonly target: 'android' | 'ios';
49
+ readonly buildRunId: string;
50
+ readonly artifactSha256: string;
51
+ readonly attemptId: string;
52
+ readonly leaseExpiresAt: string;
53
+ readonly status: NativeSubmissionStatus;
54
+ readonly remoteEditId?: string;
55
+ readonly remoteUploadId?: string;
56
+ readonly remoteBuildId?: string;
57
+ }
58
+ export interface NativeReleaseStatus {
59
+ readonly plan: PlatformVersionReleasePlan;
60
+ readonly builds: Partial<Record<'android' | 'ios', ImmutableNativeBuildRecord>>;
61
+ readonly submissions: Partial<Record<'android' | 'ios', NativeSubmissionCheckpoint>>;
62
+ readonly stateCommit: string;
63
+ }
64
+ export interface RecordNativeBuildInput {
65
+ readonly gameRoot: string;
66
+ readonly gameId: string;
67
+ readonly releaseKey: string;
68
+ readonly target: 'android' | 'ios';
69
+ readonly buildRunId: string;
70
+ readonly kitPackageVersion: string;
71
+ readonly buildConfigDigest: string;
72
+ readonly deployConfigSha256?: string;
73
+ readonly deploymentProfile?: string;
74
+ readonly deploymentDestination?: 'play-internal' | 'testflight';
75
+ readonly internalTestGroupId?: string;
76
+ readonly artifactFile: string;
77
+ readonly expectedArtifactSha256: string;
78
+ readonly artifactLocation: string;
79
+ readonly releaseManifestFile: string;
80
+ readonly expectedReleaseManifestSha256: string;
81
+ readonly inspectedAppId: string;
82
+ readonly inspectedSignerSha256?: string | undefined;
83
+ readonly inspectedTeamId?: string | undefined;
84
+ readonly environment?: NodeJS.ProcessEnv;
85
+ readonly signal?: AbortSignal;
86
+ }
87
+ /** Leave one minute of clock-skew headroom below the checkpoint lease cap. */
88
+ export declare function freshSubmissionLeaseExpiresAt(): string;
89
+ /** Reserve version numbers by committing the ledger and plan atomically. */
90
+ export declare function reserveNativeRelease(input: NativeReleaseReservationInput): Promise<NativeReleaseReservation>;
91
+ /** Attach one immutable, content-hashed native artifact to a reserved release. */
92
+ export declare function recordNativeReleaseBuild(input: RecordNativeBuildInput): Promise<{
93
+ readonly record: ImmutableNativeBuildRecord;
94
+ readonly stateCommit: string;
95
+ }>;
96
+ /** Read one explicitly named release from the authoritative game-owned state branch. */
97
+ export declare function readNativeReleaseStatus(input: {
98
+ readonly gameRoot: string;
99
+ readonly gameId: string;
100
+ readonly releaseKey: string;
101
+ readonly environment?: NodeJS.ProcessEnv;
102
+ readonly signal?: AbortSignal;
103
+ }): Promise<NativeReleaseStatus>;
104
+ /** Persist a resumable store checkpoint with the build record in one Git state. */
105
+ export declare function checkpointNativeSubmission(input: {
106
+ readonly gameRoot: string;
107
+ readonly gameId: string;
108
+ readonly checkpoint: NativeSubmissionCheckpoint;
109
+ readonly environment?: NodeJS.ProcessEnv;
110
+ readonly signal?: AbortSignal;
111
+ }): Promise<{
112
+ readonly checkpoint: NativeSubmissionCheckpoint;
113
+ readonly stateCommit: string;
114
+ }>;
115
+ /** Claim an expired attempt without discarding its remote IDs or changing its status. */
116
+ export declare function reclaimNativeSubmission(input: {
117
+ readonly gameRoot: string;
118
+ readonly gameId: string;
119
+ readonly releaseKey: string;
120
+ readonly target: 'android' | 'ios';
121
+ readonly expectedStateCommit: string;
122
+ readonly attemptId: string;
123
+ readonly environment?: NodeJS.ProcessEnv;
124
+ readonly signal?: AbortSignal;
125
+ }): Promise<{
126
+ readonly checkpoint: NativeSubmissionCheckpoint;
127
+ readonly stateCommit: string;
128
+ }>;
129
+ /** Relinquish a completed command's lease so an immediate status poll can resume. */
130
+ export declare function releaseNativeSubmissionLease(input: {
131
+ readonly gameRoot: string;
132
+ readonly gameId: string;
133
+ readonly releaseKey: string;
134
+ readonly target: 'android' | 'ios';
135
+ readonly attemptId: string;
136
+ readonly environment?: NodeJS.ProcessEnv;
137
+ readonly signal?: AbortSignal;
138
+ }): Promise<NativeSubmissionCheckpoint>;
139
+ /** Validate persisted checkpoint shape before any remote mutation or recovery. */
140
+ export declare function assertNativeSubmissionCheckpoint(value: unknown): asserts value is NativeSubmissionCheckpoint;