gowalk-cicd 1.0.113 → 1.0.114

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
@@ -214,6 +214,8 @@ node /path/to/gowalk-cicd/bin/cli.mjs
214
214
  that multiline secret masking can erase. Never reconstruct masked fields or include arbitrary text.
215
215
  Native bundle/Pods failures emit `native_prepare_failed` with fixed refusal codes and observed
216
216
  signals. Unknown signals remain unclassified; never copy child output or commands into annotations.
217
+ Timeout and cancellation retain the existing bounded sanitized diagnostic after owned-child cleanup;
218
+ preserve the failure code, install deadline and retry fences.
217
219
  - The mobile workflow's feature-branch-only `backend-preview` job bootstraps a
218
220
  new app before GitHub registers `deploy-backend.yml` on the default branch.
219
221
  Default-branch deploys continue through the separate backend workflow.
package/README.md CHANGED
@@ -380,6 +380,8 @@ Native bundle and Pods failures emit `native_prepare_failed` with schema
380
380
  Backend failures emit `backend_deploy_failed` with schema `gowalk-cicd/backend-deploy-failed.v1`,
381
381
  the runner/host `phase` and `exit_code`. These receipts preserve the failed result and exclude
382
382
  private command output, commands, hosts and runtime credentials. Empty signals are unclassified.
383
+ Timeout and cancellation preserve the bounded, sanitized child diagnostic after owned-process cleanup;
384
+ observed signals do not authorize another attempt or change the failed result.
383
385
  The backend message is `gowalk-cicd/backend-deploy-failed.v1 phase=<phase> exit_code=<code>`:
384
386
  fixed fields without JSON braces, which GitHub can mask when a multiline secret has brace-only lines.
385
387
  Consumers validate the complete envelope, the known phase and exit code 1–255; older JSON messages
@@ -1 +1 @@
1
- 1.0.113
1
+ 1.0.114
@@ -22,7 +22,7 @@ def command(argv: list[str], cwd: Path, log, *, timeout: float = 900) -> int:
22
22
  try:
23
23
  return native_pods_process.command(argv, cwd, log, timeout=timeout)
24
24
  except native_pods_process.Interrupted:
25
- raise guard.Refused("native_pods_operation_interrupted") from None
25
+ refuse("native_pods_operation_interrupted", log, guard.Refused)
26
26
  except (OSError, RuntimeError, subprocess.SubprocessError):
27
27
  raise guard.Refused("native_pods_process_unverified") from None
28
28
 
@@ -7,7 +7,7 @@ import time
7
7
 
8
8
  import native_bundle
9
9
  import native_dependency_guard as guard
10
- from native_pods_diagnostics import diagnostic
10
+ from native_pods_diagnostics import diagnostic, refuse
11
11
 
12
12
  TERMINAL = re.compile(
13
13
  r"certificate|SSL peer|TLS|authentication|unauthorized|forbidden|permission denied|"
@@ -27,7 +27,7 @@ def install(argv: list[str], folder, log, snapshot: dict, *, timeout: float = 90
27
27
  native_bundle.unchanged(snapshot)
28
28
  remaining = deadline - time.monotonic()
29
29
  if remaining <= 0:
30
- raise guard.Refused("native_pods_operation_interrupted")
30
+ refuse("native_pods_operation_interrupted", log, guard.Refused)
31
31
  with tempfile.TemporaryFile() as child_log:
32
32
  code = native_bundle.command(argv, folder, child_log, timeout=remaining)
33
33
  body = diagnostic(child_log)
@@ -142,6 +142,7 @@ class PodsTests(unittest.TestCase):
142
142
  self.locked()
143
143
  script = self.root / "pod"
144
144
  script.write_text('#!' + sys.executable + '\nimport os,time\nfrom pathlib import Path\n'
145
+ 'print("Downloading dependencies\\nRead timed out\\nPRIVATE_SENTINEL", flush=True)\n'
145
146
  'Path("owned-child").write_text(str(os.getpid()))\ntime.sleep(30)\n')
146
147
  script.chmod(0o700)
147
148
  self.env["PATH"] = str(self.root) + os.pathsep + self.env["PATH"]
@@ -155,8 +156,11 @@ class PodsTests(unittest.TestCase):
155
156
  output, errors = child.communicate(timeout=10)
156
157
  self.assertTrue((self.root / "owned-child").exists())
157
158
  self.assertEqual(json.loads(output)["code"], "native_pods_operation_interrupted")
159
+ self.assertIn("Downloading dependencies", json.loads(output).get("diagnostic", ""))
160
+ self.assertNotIn("PRIVATE_SENTINEL", output + errors)
158
161
  self.assertTrue(errors.startswith("::error title=native_prepare_failed::"))
159
162
  self.assertEqual(json.loads(errors.strip().split("::", 2)[2])["code"], "native_pods_operation_interrupted")
163
+ self.assertEqual(json.loads(errors.strip().split("::", 2)[2])["signals"], ["connection_timeout"])
160
164
  owned_pid = int((self.root / "owned-child").read_text())
161
165
  deadline = time.monotonic() + 5
162
166
  while time.monotonic() < deadline:
@@ -95,6 +95,7 @@ class RetryTests(unittest.TestCase):
95
95
  self.assertEqual([c.kwargs["timeout"] for c in command.call_args_list], [899, 200])
96
96
  command.reset_mock()
97
97
  with patch.object(native_pods_retry.time, "monotonic", side_effect=[0, 1, 901]):
98
- with self.assertRaisesRegex(guard.Refused, "native_pods_operation_interrupted"):
98
+ with self.assertRaisesRegex(guard.Refused, "native_pods_operation_interrupted") as caught:
99
99
  native_pods_retry.install(["fixture"], self.root, log, {})
100
100
  self.assertEqual(command.call_count, 1)
101
+ self.assertEqual(caught.exception.diagnostic, PARTIAL)
@@ -0,0 +1,34 @@
1
+ """Keep a real timed-out Pod child's sanitized evidence after its private log closes."""
2
+ import os
3
+ from pathlib import Path
4
+ import sys
5
+ import tempfile
6
+ import unittest
7
+ from unittest.mock import patch
8
+
9
+ import native_dependency_guard as guard
10
+ import native_pods_retry
11
+
12
+
13
+ @unittest.skipUnless(os.name == "posix", "owned POSIX process groups")
14
+ class TimeoutTests(unittest.TestCase):
15
+ def test_timeout_retains_bounded_diagnostic_without_retrying_or_leaving_child(self):
16
+ with tempfile.TemporaryDirectory(prefix="native-pods-timeout-") as folder:
17
+ root = Path(folder)
18
+ script = ('import os,time; from pathlib import Path; '
19
+ 'Path("attempts").open("a").write("attempt\\n"); '
20
+ 'print("x" * 20000, flush=True); '
21
+ 'print("Downloading dependencies\\nRead timed out\\nPRIVATE_SENTINEL", flush=True); '
22
+ 'Path("child").write_text(str(os.getpid())); time.sleep(30)')
23
+ with tempfile.TemporaryFile() as log, patch.dict(os.environ, {"TEST_TOKEN": "PRIVATE_SENTINEL"}):
24
+ with self.assertRaisesRegex(guard.Refused, "native_pods_operation_interrupted") as caught:
25
+ native_pods_retry.install([sys.executable, "-c", script], root, log, {}, timeout=1)
26
+ self.assertEqual((root / "attempts").read_text(), "attempt\n")
27
+ with self.assertRaises(ProcessLookupError):
28
+ os.kill(int((root / "child").read_text()), 0)
29
+ body = getattr(caught.exception, "diagnostic", "")
30
+ self.assertIn("Downloading dependencies", body)
31
+ self.assertIn("Read timed out", body)
32
+ self.assertNotIn("PRIVATE_SENTINEL", body)
33
+ self.assertLessEqual(len(body), 4000)
34
+ self.assertLessEqual(len(body.splitlines()), 30)
@@ -1 +1 @@
1
- 1.0.113
1
+ 1.0.114
@@ -1 +1 @@
1
- 1.0.113
1
+ 1.0.114
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gowalk-cicd",
3
- "version": "1.0.113",
3
+ "version": "1.0.114",
4
4
  "description": "Zero-config GitHub Actions delivery for iOS TestFlight and Android Google Play.",
5
5
  "type": "module",
6
6
  "bin": {