gowalk-cicd 1.0.112 → 1.0.113

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/CLAUDE.md CHANGED
@@ -395,8 +395,12 @@ node /path/to/gowalk-cicd/bin/cli.mjs
395
395
  `APP_ROBOT_PLAY_EDIT_BINDING` is panel-provisioned repository data; workflows pass it as
396
396
  `PLAY_EDIT_BINDING` with the repo-local GitHub token and `contents:write`/`actions:read`.
397
397
  `play-upload/owner_run.cjs` protects Python entrypoints; `run.cjs` holds the same owner across
398
- its permitted upload retry. Missing/retired bindings, unknown write outcomes and active owners
399
- refuse before provider work; only exact terminal run/attempt/source proof permits recovery.
398
+ its permitted upload retry. An active owner gets a single 60-second acquisition wait budget;
399
+ each retry revalidates the record, cancellation interrupts the wait, and an expired budget
400
+ never clears an owner. Missing/retired bindings and unknown write outcomes still refuse;
401
+ only exact terminal run/attempt/source proof permits CI-owner recovery.
402
+ Both entrypoints emit brace-free `play_edit_owner` errors with fixed codes and optional child
403
+ exit evidence, preserving the existing upload receipt and refusal. Never reconstruct masked text.
400
404
  Other packages stay parallel. The GitHub coordination request is fixed-origin and does not
401
405
  change the Google account proxy. Update workflow wiring as well as action directories.
402
406
 
package/README.md CHANGED
@@ -361,6 +361,7 @@ the situation and whose **message** is one line of JSON carrying a versioned
361
361
  | `play_review_pending` | `gowalk-cicd/play-review-pending.v1` | `package`, `track`, `version_code` |
362
362
  | `play_upload_result` | `gowalk-cicd/play-upload.v1` | `ok`, `code`, `attempts`, `review_pending`, upstream identity |
363
363
  | `play_upload_diagnostic` | `gowalk-cicd/play-upload-diagnostic.v1` | Fixed process evidence; see below |
364
+ | `play_edit_owner` | `gowalk-cicd/play-owner.v1` | Ownership refusal; no implied upload retry permission |
364
365
  | `store_version_locked` | `gowalk-cicd/store-version-locked.v1` | `version`, `state`, `build_number` |
365
366
  | `firebase_symbols_pending` | `gowalk-cicd/firebase-symbols-pending.v1` | `platform`, `app_id`, `source_sha`, `artifact`, `file`, `sha256`, `status` |
366
367
  | `mobile_upload_reused` | `gowalk-cicd/mobile-upload-reused.v1` | `platform`, `fingerprint`, `run_id`, `run_attempt`, `source_sha`, `deployment_id`, `next_action` |
@@ -475,6 +476,16 @@ bounded to 16 KiB. No provider message, path, URL, track or credential is republ
475
476
  These observations never authorize a retry. Missing commit output does not prove no mutation,
476
477
  and a reported commit does not prove review submission. Unknown failures remain unclassified.
477
478
 
479
+ Package-owner acquisition rechecks an occupied record within one 60-second wait budget before
480
+ provider work. Every read retains the binding and identity checks; cancellation interrupts the wait.
481
+ Time passing never authorizes clearing an owner. A known CI owner still requires exact terminal
482
+ run, attempt and source proof before recovery; the panel releases its own requests.
483
+ Both entrypoints emit `play_edit_owner` as `gowalk-cicd/play-owner.v1 code=<code> child_exit_code=<0-255|unknown>`.
484
+ Codes are `play_owner_binding_required`, `play_owner_unverified`, `play_owner_busy`,
485
+ `play_owner_release_unverified`, `play_owner_cancelled`, `play_owner_principal_unverified`,
486
+ `play_owner_child_failed` and `play_owner_package_unverified`. An unknown child exit proves no outcome.
487
+ These errors preserve the failed job and existing upload receipt; they do not authorize replay.
488
+
478
489
  An expired/deleted edit does not identify its cause. Another edit opened with the
479
490
  same API user/package can invalidate it; avoid competing read edits while uploading.
480
491
  An external actor is not proven by an expired-edit error alone. Ambiguous outcomes
@@ -1 +1 @@
1
- 1.0.112
1
+ 1.0.113
@@ -1 +1 @@
1
- 1.0.112
1
+ 1.0.113
@@ -1,5 +1,7 @@
1
1
  const http = require('./owner_http.cjs');
2
2
  const record = require('./owner_record.cjs');
3
+ const {performance} = require('node:perf_hooks');
4
+ const {setTimeout: delay} = require('node:timers/promises');
3
5
 
4
6
  async function seal(client, selected) {
5
7
  for (let attempt = 0; attempt < 3; attempt++) {
@@ -14,16 +16,27 @@ async function seal(client, selected) {
14
16
  throw new Error('play_owner_release_unverified');
15
17
  }
16
18
 
17
- async function acquire(client, selected, attempted = () => {}) {
19
+ async function acquire(client, selected, attempted = () => {}, waiting = {}) {
20
+ const now = waiting.now || (() => performance.now());
21
+ const sleep = waiting.sleep || ((ms, signal) => delay(ms, undefined, {signal}));
22
+ const deadline = now() + 60000;
18
23
  const protocol = (await client.read('protocol.json')).value;
19
24
  if (protocol?.schema !== record.schema || protocol.repository !== selected.value.repo
20
25
  || Object.keys(protocol).length !== 2) throw new Error('play_owner_unverified');
21
- for (let attempt = 0; attempt < 3; attempt++) {
26
+ for (let attempt = 0; attempt < 3;) {
27
+ if (waiting.signal?.aborted) throw new Error('play_owner_cancelled');
22
28
  const row = record.validate(await client.read(selected.path), selected);
23
29
  if (row.value.owner !== null) {
24
30
  const owner = row.value.owner;
25
- if (owner.kind !== 'ci' || !record.terminal(await client.run(owner), owner)) throw new Error('play_owner_busy');
31
+ if (owner.kind !== 'ci' || !record.terminal(await client.run(owner), owner)) {
32
+ const remaining = deadline - now();
33
+ if (remaining <= 0) throw new Error('play_owner_busy');
34
+ await sleep(Math.min(5000, remaining), waiting.signal);
35
+ continue;
36
+ }
26
37
  }
38
+ if (waiting.signal?.aborted) throw new Error('play_owner_cancelled');
39
+ attempt++;
27
40
  const desired = record.replacement(row, selected.owner);
28
41
  attempted();
29
42
  try { await client.write(selected.path, row, desired); } catch { /* Only exact readback may adopt it. */ }
@@ -33,14 +46,15 @@ async function acquire(client, selected, attempted = () => {}) {
33
46
  throw new Error('play_owner_busy');
34
47
  }
35
48
 
36
- async function withOwner(env, packageName, operation, transport) {
49
+ async function withOwner(env, packageName, operation, transport, timing = {}) {
37
50
  const selected = record.binding(env, packageName);
38
51
  const client = http.client(selected.value.repo, env.GITHUB_TOKEN, transport);
52
+ const controller = new AbortController();
39
53
  let cancelled = false, dirty = false, result, failure;
40
- const stop = () => { cancelled = true; };
54
+ const stop = () => { cancelled = true; controller.abort(); };
41
55
  process.on('SIGTERM', stop); process.on('SIGINT', stop);
42
56
  try {
43
- await acquire(client, selected, () => { dirty = true; });
57
+ await acquire(client, selected, () => { dirty = true; }, {...timing, signal: controller.signal});
44
58
  if (cancelled) throw new Error('play_owner_cancelled');
45
59
  result = await operation(() => cancelled);
46
60
  } catch (error) { failure = error; }
@@ -48,7 +62,7 @@ async function withOwner(env, packageName, operation, transport) {
48
62
  try { if (dirty) await seal(client, selected); } catch { failure = new Error('play_owner_release_unverified'); }
49
63
  process.off('SIGTERM', stop); process.off('SIGINT', stop);
50
64
  }
51
- if (cancelled && !failure) failure = new Error('play_owner_cancelled');
65
+ if (cancelled && (!failure || failure.name === 'AbortError')) failure = new Error('play_owner_cancelled');
52
66
  if (failure) {
53
67
  if (result !== undefined) failure.operationResult = result;
54
68
  throw failure;
@@ -58,4 +72,10 @@ async function withOwner(env, packageName, operation, transport) {
58
72
 
59
73
  const codes = ['play_owner_binding_required', 'play_owner_unverified', 'play_owner_busy',
60
74
  'play_owner_release_unverified', 'play_owner_cancelled', 'play_owner_principal_unverified'];
61
- module.exports = {acquire, seal, withOwner, codes};
75
+ function annotation(reason, childExit) {
76
+ const allowed = [...codes, 'play_owner_child_failed', 'play_owner_package_unverified'];
77
+ const code = allowed.includes(reason) ? reason : 'play_owner_unverified';
78
+ const exit = Number.isInteger(childExit) && childExit >= 0 && childExit <= 255 ? childExit : 'unknown';
79
+ return `::error title=play_edit_owner::gowalk-cicd/play-owner.v1 code=${code} child_exit_code=${exit}`;
80
+ }
81
+ module.exports = {acquire, seal, withOwner, codes, annotation};
@@ -51,9 +51,7 @@ async function main(argv = process.argv.slice(2), env = process.env) {
51
51
  } catch (error) {
52
52
  const code = [...owner.codes, 'play_owner_child_failed', 'play_owner_package_unverified'].includes(error.message)
53
53
  ? error.message : 'play_owner_unverified';
54
- const receipt = {schema: 'gowalk-cicd/play-owner.v1', code};
55
- if (Number.isInteger(error.operationResult?.code)) receipt.child_exit_code = error.operationResult.code;
56
- console.log(`::error title=play_edit_owner::${JSON.stringify(receipt)}`);
54
+ console.log(owner.annotation(code, error.operationResult?.code));
57
55
  process.exitCode = 1;
58
56
  }
59
57
  }
@@ -44,6 +44,7 @@ async function main() {
44
44
  upload(program, env, directory, processRunner.execute, 3600000, cancelled,
45
45
  (attempt, result) => console.log(diagnostics.annotation(attempt, result))));
46
46
  } catch (error) {
47
+ if (owner.codes.includes(error.message)) console.log(owner.annotation(error.message));
47
48
  const allowed = ['assigned_proxy_required', 'runner_context_required', 'uploader_integrity_refused',
48
49
  'uploader_source_unavailable', ...owner.codes];
49
50
  receipt = {ok: false, code: allowed.includes(error.message) ? error.message : 'uploader_failed',
@@ -1 +1 @@
1
- 1.0.112
1
+ 1.0.113
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gowalk-cicd",
3
- "version": "1.0.112",
3
+ "version": "1.0.113",
4
4
  "description": "Zero-config GitHub Actions delivery for iOS TestFlight and Android Google Play.",
5
5
  "type": "module",
6
6
  "bin": {