diff --git a/README.md b/README.md index 255565f..2468f3c 100644 --- a/README.md +++ b/README.md @@ -11,11 +11,11 @@ integration PRs, follows `main` through production, and pauses after failures. ## Install -Install the reviewed `v0.2.5` source commit directly from GitHub: +Install the reviewed `v0.2.6` source commit directly from GitHub: ```bash python3 -m pip install \ - 'deploybot-merge-queue[mcp] @ git+https://github.com/Forward-Future/DeployBot.git@94fe042a58c731343c8bbd73d0bdfcb38a55866f' + 'deploybot-merge-queue[mcp] @ git+https://github.com/Forward-Future/DeployBot.git@a0e2c833282e63bf5db4339f99773aa9ba2f0c82' deploybot init ``` @@ -86,7 +86,7 @@ aligned with `pipeline.ci_workflows`. The privileged worker never checks out or executes pull-request code. Pin the Action to the full reviewed release commit: ```yaml -- uses: Forward-Future/DeployBot@94fe042a58c731343c8bbd73d0bdfcb38a55866f +- uses: Forward-Future/DeployBot@a0e2c833282e63bf5db4339f99773aa9ba2f0c82 ``` The Action uses GitHub's built-in workflow token. GitHub intentionally does not diff --git a/adapters/claude-code/.mcp.json b/adapters/claude-code/.mcp.json index 51dcc92..5921093 100644 --- a/adapters/claude-code/.mcp.json +++ b/adapters/claude-code/.mcp.json @@ -4,7 +4,7 @@ "command": "uvx", "args": [ "--from", - "deploybot-merge-queue[mcp] @ git+https://github.com/Forward-Future/DeployBot.git@94fe042a58c731343c8bbd73d0bdfcb38a55866f", + "deploybot-merge-queue[mcp] @ git+https://github.com/Forward-Future/DeployBot.git@a0e2c833282e63bf5db4339f99773aa9ba2f0c82", "deploybot-mcp" ] } diff --git a/adapters/codex/agent-merge-queue/.mcp.json b/adapters/codex/agent-merge-queue/.mcp.json index 51dcc92..5921093 100644 --- a/adapters/codex/agent-merge-queue/.mcp.json +++ b/adapters/codex/agent-merge-queue/.mcp.json @@ -4,7 +4,7 @@ "command": "uvx", "args": [ "--from", - "deploybot-merge-queue[mcp] @ git+https://github.com/Forward-Future/DeployBot.git@94fe042a58c731343c8bbd73d0bdfcb38a55866f", + "deploybot-merge-queue[mcp] @ git+https://github.com/Forward-Future/DeployBot.git@a0e2c833282e63bf5db4339f99773aa9ba2f0c82", "deploybot-mcp" ] } diff --git a/adapters/cursor/.cursor/mcp.json b/adapters/cursor/.cursor/mcp.json index fa50b07..5921093 100644 --- a/adapters/cursor/.cursor/mcp.json +++ b/adapters/cursor/.cursor/mcp.json @@ -4,7 +4,7 @@ "command": "uvx", "args": [ "--from", - "deploybot-merge-queue[mcp] @ git+https://github.com/Forward-Future/DeployBot.git@94fe042a58c731343c8bbd73d0bdfcb38a55866f", + "deploybot-merge-queue[mcp] @ git+https://github.com/Forward-Future/DeployBot.git@a0e2c833282e63bf5db4339f99773aa9ba2f0c82", "deploybot-mcp" ] } diff --git a/examples/github-workflow.yml b/examples/github-workflow.yml index d693f02..cdc2c06 100644 --- a/examples/github-workflow.yml +++ b/examples/github-workflow.yml @@ -55,5 +55,5 @@ jobs: with: ref: ${{ github.event.repository.default_branch }} persist-credentials: false - # v0.2.5 implementation; keep the full commit for privileged workflows. - - uses: Forward-Future/DeployBot@94fe042a58c731343c8bbd73d0bdfcb38a55866f + # v0.2.6 implementation; keep the full commit for privileged workflows. + - uses: Forward-Future/DeployBot@a0e2c833282e63bf5db4339f99773aa9ba2f0c82 diff --git a/pyproject.toml b/pyproject.toml index 4b3a8d8..865d432 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "deploybot-merge-queue" -version = "0.2.5" +version = "0.2.6" description = "DeployBot: a provider-neutral GitHub merge queue for coding agents" readme = "README.md" license = "MIT" diff --git a/src/agent_merge_queue/__init__.py b/src/agent_merge_queue/__init__.py index 5059a71..b30d4d6 100644 --- a/src/agent_merge_queue/__init__.py +++ b/src/agent_merge_queue/__init__.py @@ -1,3 +1,3 @@ """DeployBot: a provider-neutral GitHub merge queue for coding agents.""" -__version__ = "0.2.5" +__version__ = "0.2.6" diff --git a/src/agent_merge_queue/pipeline.py b/src/agent_merge_queue/pipeline.py index ce28c45..1239116 100644 --- a/src/agent_merge_queue/pipeline.py +++ b/src/agent_merge_queue/pipeline.py @@ -54,7 +54,9 @@ def release_state( # the downstream run against the default-branch SHA, so it can otherwise # hide a real deployment (or look like a deployment failure) for that same # revision. A skipped run did not attempt a release and is not evidence. - ci_fence = str((ci or {}).get("updated_at") or (ci or {}).get("created_at") or "") + ci_fence = parse_time( + str((ci or {}).get("updated_at") or (ci or {}).get("created_at") or "") + ) deploy = latest_run( [ run @@ -63,7 +65,14 @@ def release_state( str(run.get("status") or "") == "completed" and str(run.get("conclusion") or "") == "skipped" ) - and (not ci_fence or str(run.get("created_at") or "") >= ci_fence) + and ( + ci_fence is None + or ( + (created_at := parse_time(str(run.get("created_at") or ""))) + is not None + and created_at >= ci_fence + ) + ) ], config.deploy_workflows, main_sha, diff --git a/tests/test_pipeline.py b/tests/test_pipeline.py index 1d94274..2d3da9f 100644 --- a/tests/test_pipeline.py +++ b/tests/test_pipeline.py @@ -105,6 +105,33 @@ def test_new_successful_ci_supersedes_an_older_failed_deploy(self) -> None: self.assertEqual(value["state"], "awaiting-deploy") self.assertIsNone(value["latest_deploy"]) + def test_release_fence_compares_normalized_timestamps(self) -> None: + sha = "a" * 40 + runs = [ + { + "id": 1, + "name": "CI", + "head_sha": sha, + "status": "completed", + "conclusion": "success", + "created_at": "2026-06-20T00:00:00Z", + "updated_at": "2026-06-20T01:00:00+01:00", + }, + { + "id": 2, + "name": "Deploy", + "head_sha": sha, + "status": "completed", + "conclusion": "success", + "created_at": "2026-06-20T00:30:00.000Z", + }, + ] + + value = release_state(main_sha=sha, runs=runs, config=CONFIG.pipeline) + + self.assertEqual(value["state"], "verified") + self.assertEqual(value["latest_deploy"]["id"], 2) + def test_follow_dispatches_deploy_after_token_dispatched_ci(self) -> None: sha = "a" * 40 ci = { diff --git a/tests/test_skill.py b/tests/test_skill.py index 439d66d..f8623eb 100644 --- a/tests/test_skill.py +++ b/tests/test_skill.py @@ -6,7 +6,7 @@ ROOT = Path(__file__).resolve().parents[1] CANONICAL = ROOT / "skills" / "deploybot" / "SKILL.md" -RELEASE_COMMIT = "94fe042a58c731343c8bbd73d0bdfcb38a55866f" +RELEASE_COMMIT = "a0e2c833282e63bf5db4339f99773aa9ba2f0c82" CHECKOUT_COMMIT = "9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0"