code-coordinator 0.5.46__py3-none-any.whl
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.
- code_coordinator-0.5.46.dist-info/METADATA +625 -0
- code_coordinator-0.5.46.dist-info/RECORD +295 -0
- code_coordinator-0.5.46.dist-info/WHEEL +5 -0
- code_coordinator-0.5.46.dist-info/entry_points.txt +2 -0
- code_coordinator-0.5.46.dist-info/licenses/LICENSE +110 -0
- code_coordinator-0.5.46.dist-info/top_level.txt +1 -0
- coord/__init__.py +176 -0
- coord/_board_mapping.py +229 -0
- coord/acceptance.py +468 -0
- coord/acceptance_drivers.py +632 -0
- coord/agent.py +7517 -0
- coord/agent_app.py +1555 -0
- coord/agent_update.py +417 -0
- coord/agents/opencode/.gitignore +13 -0
- coord/agents/opencode/agents/work.md +129 -0
- coord/agents/opencode/routing.jsonc +49 -0
- coord/audit.py +301 -0
- coord/auto_loop.py +1440 -0
- coord/board_bool_guard.py +72 -0
- coord/board_service.py +141 -0
- coord/board_wire.py +309 -0
- coord/brain.py +581 -0
- coord/branch_model.py +214 -0
- coord/cargo_cache.py +258 -0
- coord/ci_github.py +386 -0
- coord/ci_store.py +560 -0
- coord/claim.py +353 -0
- coord/cli.py +454 -0
- coord/client.py +610 -0
- coord/commands/__init__.py +1 -0
- coord/commands/_common.py +329 -0
- coord/commands/acceptance.py +916 -0
- coord/commands/agent_ops.py +1339 -0
- coord/commands/audit.py +131 -0
- coord/commands/chat.py +320 -0
- coord/commands/dispatch.py +1780 -0
- coord/commands/dispatch_workers.py +4894 -0
- coord/commands/drive.py +616 -0
- coord/commands/drive_queue.py +1203 -0
- coord/commands/gate_a.py +217 -0
- coord/commands/gates.py +89 -0
- coord/commands/issues.py +681 -0
- coord/commands/lifecycle.py +513 -0
- coord/commands/merge.py +1900 -0
- coord/commands/milestone.py +2081 -0
- coord/commands/plan_followup.py +1243 -0
- coord/commands/plans.py +156 -0
- coord/commands/release.py +2232 -0
- coord/commands/report.py +341 -0
- coord/commands/review.py +1523 -0
- coord/commands/scorecard.py +252 -0
- coord/commands/sessions.py +1930 -0
- coord/commands/setup.py +576 -0
- coord/commands/status.py +2089 -0
- coord/commands/terminal.py +385 -0
- coord/commands/test_gate.py +775 -0
- coord/commands/tui.py +288 -0
- coord/comments.py +718 -0
- coord/config.py +3032 -0
- coord/conflict_fix.py +633 -0
- coord/dao.py +483 -0
- coord/dashboard/__init__.py +0 -0
- coord/dashboard/fixture.py +376 -0
- coord/dashboard/index.html +658 -0
- coord/dashboard/server.py +1894 -0
- coord/dashboard/terminal.py +382 -0
- coord/dashboard/webapp/.gitignore +9 -0
- coord/dashboard/webapp/components.json +17 -0
- coord/dashboard/webapp/dist/assets/Gallery-da3qNiIw.js +71 -0
- coord/dashboard/webapp/dist/assets/Terminal-9CEnUXvW.css +32 -0
- coord/dashboard/webapp/dist/assets/Terminal-skVFCxPU.js +63 -0
- coord/dashboard/webapp/dist/assets/index-DltfZR5f.js +184 -0
- coord/dashboard/webapp/dist/assets/index-Dq4kwTdw.css +1 -0
- coord/dashboard/webapp/dist/assets/workbox-window.prod.es5-BqEJf4Xk.js +2 -0
- coord/dashboard/webapp/dist/icons/icon-192.png +0 -0
- coord/dashboard/webapp/dist/icons/icon-512.png +0 -0
- coord/dashboard/webapp/dist/icons/icon.svg +5 -0
- coord/dashboard/webapp/dist/index.html +38 -0
- coord/dashboard/webapp/dist/manifest.webmanifest +1 -0
- coord/dashboard/webapp/dist/sw.js +1 -0
- coord/dashboard/webapp/dist/workbox-e4022e15.js +1 -0
- coord/dashboard/webapp/e2e/available-gates-terminal.spec.ts +75 -0
- coord/dashboard/webapp/e2e/deep-link.spec.ts +172 -0
- coord/dashboard/webapp/e2e/fixtureServer.ts +155 -0
- coord/dashboard/webapp/e2e/live-update-fixture.spec.ts +113 -0
- coord/dashboard/webapp/e2e/realtime.spec.ts +238 -0
- coord/dashboard/webapp/e2e/shell.spec.ts +309 -0
- coord/dashboard/webapp/e2e/smoke.spec.ts +191 -0
- coord/dashboard/webapp/e2e/terminal.spec.ts +420 -0
- coord/dashboard/webapp/e2e/theme.spec.ts +138 -0
- coord/dashboard/webapp/eslint.config.js +20 -0
- coord/dashboard/webapp/index.html +37 -0
- coord/dashboard/webapp/node_modules/flatted/python/flatted.py +144 -0
- coord/dashboard/webapp/package-lock.json +10584 -0
- coord/dashboard/webapp/package.json +63 -0
- coord/dashboard/webapp/playwright.acceptance.config.ts +166 -0
- coord/dashboard/webapp/playwright.config.ts +93 -0
- coord/dashboard/webapp/postcss.config.js +6 -0
- coord/dashboard/webapp/public/icons/icon-192.png +0 -0
- coord/dashboard/webapp/public/icons/icon-512.png +0 -0
- coord/dashboard/webapp/public/icons/icon.svg +5 -0
- coord/dashboard/webapp/src/App.tsx +140 -0
- coord/dashboard/webapp/src/api/client.ts +199 -0
- coord/dashboard/webapp/src/api/generated.ts +176 -0
- coord/dashboard/webapp/src/components/ConnectionBadge.tsx +52 -0
- coord/dashboard/webapp/src/components/Detail.tsx +800 -0
- coord/dashboard/webapp/src/components/Gallery.tsx +341 -0
- coord/dashboard/webapp/src/components/Home.tsx +435 -0
- coord/dashboard/webapp/src/components/MobileKeyBar.tsx +280 -0
- coord/dashboard/webapp/src/components/PanelHeader.tsx +59 -0
- coord/dashboard/webapp/src/components/PipelineCard.tsx +168 -0
- coord/dashboard/webapp/src/components/SessionCard.tsx +99 -0
- coord/dashboard/webapp/src/components/SessionDetail.tsx +140 -0
- coord/dashboard/webapp/src/components/SessionsList.tsx +81 -0
- coord/dashboard/webapp/src/components/Terminal.tsx +376 -0
- coord/dashboard/webapp/src/components/__tests__/ConnectionBadge.test.tsx +81 -0
- coord/dashboard/webapp/src/components/__tests__/Detail.test.tsx +680 -0
- coord/dashboard/webapp/src/components/__tests__/Gallery.test.tsx +83 -0
- coord/dashboard/webapp/src/components/__tests__/Home.test.tsx +271 -0
- coord/dashboard/webapp/src/components/__tests__/MobileKeyBar.test.tsx +197 -0
- coord/dashboard/webapp/src/components/__tests__/PipelineCard.test.tsx +143 -0
- coord/dashboard/webapp/src/components/__tests__/SessionCard.test.tsx +106 -0
- coord/dashboard/webapp/src/components/__tests__/Terminal.test.tsx +504 -0
- coord/dashboard/webapp/src/components/ui/badge.tsx +41 -0
- coord/dashboard/webapp/src/components/ui/button.tsx +54 -0
- coord/dashboard/webapp/src/components/ui/card.tsx +55 -0
- coord/dashboard/webapp/src/components/ui/dialog.tsx +99 -0
- coord/dashboard/webapp/src/components/ui/dropdown-menu.tsx +189 -0
- coord/dashboard/webapp/src/components/ui/empty-state.tsx +35 -0
- coord/dashboard/webapp/src/components/ui/sheet.tsx +123 -0
- coord/dashboard/webapp/src/components/ui/skeleton.tsx +9 -0
- coord/dashboard/webapp/src/components/ui/tabs.tsx +55 -0
- coord/dashboard/webapp/src/components/ui/theme-provider.tsx +78 -0
- coord/dashboard/webapp/src/components/ui/theme-toggle.tsx +20 -0
- coord/dashboard/webapp/src/components/ui/toast.tsx +123 -0
- coord/dashboard/webapp/src/components/ui/toaster.tsx +30 -0
- coord/dashboard/webapp/src/components/ui/tooltip.tsx +26 -0
- coord/dashboard/webapp/src/components/ui/use-toast.ts +134 -0
- coord/dashboard/webapp/src/index.css +210 -0
- coord/dashboard/webapp/src/lib/pipeline.ts +29 -0
- coord/dashboard/webapp/src/lib/utils.ts +6 -0
- coord/dashboard/webapp/src/main.tsx +46 -0
- coord/dashboard/webapp/src/realtime/RealtimeProvider.tsx +112 -0
- coord/dashboard/webapp/src/realtime/__tests__/RealtimeProvider.test.tsx +189 -0
- coord/dashboard/webapp/src/realtime/__tests__/connection.test.ts +255 -0
- coord/dashboard/webapp/src/realtime/connection.ts +227 -0
- coord/dashboard/webapp/src/realtime/events.ts +100 -0
- coord/dashboard/webapp/src/routes/__tests__/paths.test.ts +92 -0
- coord/dashboard/webapp/src/routes/paths.ts +92 -0
- coord/dashboard/webapp/src/shell/ActivityRail.tsx +335 -0
- coord/dashboard/webapp/src/shell/AppShell.tsx +276 -0
- coord/dashboard/webapp/src/shell/ComingSoon.tsx +33 -0
- coord/dashboard/webapp/src/shell/EmptyDetail.tsx +26 -0
- coord/dashboard/webapp/src/shell/RouteNotFound.tsx +33 -0
- coord/dashboard/webapp/src/shell/ShellLayout.tsx +147 -0
- coord/dashboard/webapp/src/shell/StatusBar.tsx +46 -0
- coord/dashboard/webapp/src/shell/__tests__/ShellLayout.test.tsx +520 -0
- coord/dashboard/webapp/src/shell/__tests__/shellState.test.ts +95 -0
- coord/dashboard/webapp/src/shell/__tests__/stubViewport.ts +40 -0
- coord/dashboard/webapp/src/shell/breakpoints.ts +87 -0
- coord/dashboard/webapp/src/shell/railItems.ts +105 -0
- coord/dashboard/webapp/src/shell/shellState.ts +174 -0
- coord/dashboard/webapp/src/shell/useRegionFocus.ts +95 -0
- coord/dashboard/webapp/src/test-setup.ts +41 -0
- coord/dashboard/webapp/src/vite-env.d.ts +2 -0
- coord/dashboard/webapp/tailwind.config.js +140 -0
- coord/dashboard/webapp/tsconfig.json +25 -0
- coord/dashboard/webapp/tsconfig.node.json +11 -0
- coord/dashboard/webapp/vite.config.ts +71 -0
- coord/db.py +1076 -0
- coord/dead_end.py +332 -0
- coord/deploy/README.md +33 -0
- coord/deploy/coord-agent.service +89 -0
- coord/deploy/coord-db-backup.service +60 -0
- coord/deploy/coord-db-backup.sh +74 -0
- coord/deploy/coord-db-backup.timer +18 -0
- coord/deploy/coord-drive-queue.service +117 -0
- coord/deploy/coord-drive-queue.timer +39 -0
- coord/deploy/coord-notify.service +48 -0
- coord/deploy/coord-notify.timer +24 -0
- coord/deploy/coord-release-propagate.service +83 -0
- coord/deploy/coord-release-propagate.timer +38 -0
- coord/deploy/coord-release-window.service +119 -0
- coord/deploy/coord-release-window.timer +36 -0
- coord/deploy/coord-serve.service +82 -0
- coord/deploy/coord-web-dist-build.service +43 -0
- coord/deploy/coord-web-dist-build.timer +36 -0
- coord/deploy/coord-web.service +125 -0
- coord/deploy_manifest.py +80 -0
- coord/deploy_units.py +384 -0
- coord/deps.py +115 -0
- coord/diagnose.py +1623 -0
- coord/dispatch.py +1009 -0
- coord/dist_name.py +123 -0
- coord/drive.py +3101 -0
- coord/drive_queue.py +2298 -0
- coord/drive_state.py +870 -0
- coord/events.py +381 -0
- coord/failure_class.py +914 -0
- coord/filelock.py +168 -0
- coord/fleet_config_health.py +300 -0
- coord/freshness.py +206 -0
- coord/gate_a.py +469 -0
- coord/gate_b.py +411 -0
- coord/gate_snapshot.py +385 -0
- coord/gates.py +582 -0
- coord/github_ops.py +1954 -0
- coord/goal.py +125 -0
- coord/graph_health.py +348 -0
- coord/health/__init__.py +69 -0
- coord/health/aggregate.py +129 -0
- coord/health/checks/__init__.py +13 -0
- coord/health/checks/agent_install.py +280 -0
- coord/health/checks/cargo_targets.py +171 -0
- coord/health/checks/claude_binary.py +65 -0
- coord/health/checks/deploy_lane_facts.py +458 -0
- coord/health/checks/disk.py +99 -0
- coord/health/checks/fleet_board.py +89 -0
- coord/health/checks/fleet_deploy_lanes.py +469 -0
- coord/health/checks/fleet_phantom.py +69 -0
- coord/health/checks/fleet_unit_drift.py +151 -0
- coord/health/checks/graph.py +192 -0
- coord/health/checks/plan_usage.py +88 -0
- coord/health/checks/repo_state.py +161 -0
- coord/health/checks/spawned_coord.py +465 -0
- coord/health/checks/timer_active.py +254 -0
- coord/health/checks/toolchain.py +547 -0
- coord/health/checks/unit_drift.py +648 -0
- coord/health/checks/unit_enablement.py +171 -0
- coord/health/checks/worktrees.py +96 -0
- coord/health/cli.py +121 -0
- coord/health/context.py +106 -0
- coord/health/fleet_snapshot.py +477 -0
- coord/health/models.py +250 -0
- coord/health/pypi.py +231 -0
- coord/health/registry.py +240 -0
- coord/health/render.py +82 -0
- coord/health/units.py +60 -0
- coord/hooks.py +106 -0
- coord/housekeeping.py +204 -0
- coord/interactive.py +4286 -0
- coord/issue_store.py +1496 -0
- coord/liveness_auditor.py +293 -0
- coord/machine_pause.py +755 -0
- coord/merge_queue.py +4681 -0
- coord/milestone_chat.py +600 -0
- coord/milestone_dispatch.py +943 -0
- coord/milestone_gate.py +709 -0
- coord/milestone_order.py +840 -0
- coord/mock_author.py +334 -0
- coord/models.py +891 -0
- coord/network.py +269 -0
- coord/new_issue_chat.py +229 -0
- coord/notify.py +3226 -0
- coord/openapi.py +404 -0
- coord/overlap_fence.py +133 -0
- coord/parentage.py +200 -0
- coord/parentage_github.py +58 -0
- coord/pipeline.py +481 -0
- coord/plan_parser.py +266 -0
- coord/plans.py +543 -0
- coord/platform_paths.py +43 -0
- coord/pr_body_lint.py +67 -0
- coord/prereqs.py +533 -0
- coord/progress.py +425 -0
- coord/providers/__init__.py +683 -0
- coord/providers/base.py +218 -0
- coord/providers/claude.py +284 -0
- coord/providers/claude_pty.py +610 -0
- coord/providers/opencode.py +896 -0
- coord/reconcile.py +2233 -0
- coord/refine_chat.py +485 -0
- coord/release_cordon.py +525 -0
- coord/release_propagate.py +1176 -0
- coord/release_verify.py +777 -0
- coord/release_window.py +322 -0
- coord/reports.py +1643 -0
- coord/revalidate.py +1101 -0
- coord/review.py +3317 -0
- coord/scorecard.py +484 -0
- coord/serve_app.py +7192 -0
- coord/skills/update-issue/SKILL.md +93 -0
- coord/smoke.py +1030 -0
- coord/split_work.py +210 -0
- coord/stage_projection.py +650 -0
- coord/state.py +5720 -0
- coord/test_author.py +1064 -0
- coord/test_chat.py +352 -0
- coord/test_orchestrator.py +494 -0
- coord/test_report.py +178 -0
- coord/tui_release.py +271 -0
- coord/usage.py +753 -0
- coord/usage_limits.py +358 -0
- coord/usage_rollup.py +709 -0
- coord/worker_events.py +954 -0
coord/commands/gate_a.py
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
"""``coord gate-a`` — record (or read) the human sign-off on a milestone's
|
|
2
|
+
Gate-A contract (#2063).
|
|
3
|
+
|
|
4
|
+
The sibling gate is enforced; this one was not. ``coord test --passed|--fail``
|
|
5
|
+
writes a verdict the pipeline holds review on
|
|
6
|
+
(``PipelineConfig.test_precedes_review``), whereas Gate A said only "merging
|
|
7
|
+
the Gate-A PR is sign-off" — a convention anything able to merge a PR
|
|
8
|
+
satisfies, including a coordinator session, silently, on CI green. It failed
|
|
9
|
+
on two consecutive coord-portal milestones (ms-1/PR #18, ms-2/PR #35): both
|
|
10
|
+
times the mocks were merged unseen, and the operator raised it themselves.
|
|
11
|
+
|
|
12
|
+
This command is the verdict half of the fix. The refusal half lives in
|
|
13
|
+
:func:`coord.milestone_dispatch.issue_oracle_ready` — deliberately at the
|
|
14
|
+
point where the contract is **consumed**, not at the merge, because the
|
|
15
|
+
Gate-A PR is merged with ``gh pr merge`` outside coord entirely.
|
|
16
|
+
|
|
17
|
+
Usage::
|
|
18
|
+
|
|
19
|
+
coord gate-a acme-portal 17 # read the current verdict
|
|
20
|
+
coord gate-a --approved acme-portal 17
|
|
21
|
+
coord gate-a --changes acme-portal 17 --note "status vocabulary is wrong"
|
|
22
|
+
|
|
23
|
+
The verdict is keyed to the **content hash** of
|
|
24
|
+
``tests/acceptance/ms-NN/contract.md`` on the repo's default branch, so a
|
|
25
|
+
later ``coord acceptance mock ... --amend`` invalidates it automatically:
|
|
26
|
+
approving v1 must not silently approve v2.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
from __future__ import annotations
|
|
30
|
+
|
|
31
|
+
import getpass
|
|
32
|
+
import sys
|
|
33
|
+
from pathlib import Path
|
|
34
|
+
|
|
35
|
+
import click
|
|
36
|
+
|
|
37
|
+
from coord import gate_a as gate_a_mod
|
|
38
|
+
from coord.commands._common import _CONFIG_OPTION, _load_config
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _actor() -> str:
|
|
42
|
+
try:
|
|
43
|
+
return getpass.getuser()
|
|
44
|
+
except Exception: # noqa: BLE001 — no passwd entry in some containers
|
|
45
|
+
return "unknown"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _fetch_contract(repo_cfg, milestone_number: int) -> str | None:
|
|
49
|
+
from coord import github_ops # noqa: PLC0415
|
|
50
|
+
from coord.acceptance import gate_a_contract_path # noqa: PLC0415
|
|
51
|
+
|
|
52
|
+
try:
|
|
53
|
+
return github_ops.get_repo_file(
|
|
54
|
+
repo_cfg.github,
|
|
55
|
+
gate_a_contract_path(milestone_number),
|
|
56
|
+
branch=repo_cfg.default_branch,
|
|
57
|
+
)
|
|
58
|
+
except RuntimeError:
|
|
59
|
+
return None
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@click.command(
|
|
63
|
+
"gate-a",
|
|
64
|
+
help=(
|
|
65
|
+
"Record the human sign-off on a milestone's Gate-A contract, or read "
|
|
66
|
+
"the current verdict. Mirrors `coord test --passed|--fail`: nothing "
|
|
67
|
+
"downstream of Gate A dispatches until a verdict exists for the "
|
|
68
|
+
"contract's CURRENT content (#2063)."
|
|
69
|
+
),
|
|
70
|
+
)
|
|
71
|
+
@click.argument("repo")
|
|
72
|
+
@click.argument("tracking_issue", type=int)
|
|
73
|
+
@click.option(
|
|
74
|
+
"--approved",
|
|
75
|
+
"verdict",
|
|
76
|
+
flag_value=gate_a_mod.VERDICT_APPROVED,
|
|
77
|
+
help="A human read the rendered mock(s) + contract.md and signs off.",
|
|
78
|
+
)
|
|
79
|
+
@click.option(
|
|
80
|
+
"--changes",
|
|
81
|
+
"verdict",
|
|
82
|
+
flag_value=gate_a_mod.VERDICT_CHANGES,
|
|
83
|
+
help=(
|
|
84
|
+
"A human read it and wants the contract changed — amend it with "
|
|
85
|
+
"`coord acceptance mock <repo> <issue> --amend \"...\"`."
|
|
86
|
+
),
|
|
87
|
+
)
|
|
88
|
+
@click.option(
|
|
89
|
+
"--note",
|
|
90
|
+
default="",
|
|
91
|
+
help="What you want changed (or any context worth keeping with the verdict).",
|
|
92
|
+
)
|
|
93
|
+
@_CONFIG_OPTION
|
|
94
|
+
def gate_a(
|
|
95
|
+
repo: str,
|
|
96
|
+
tracking_issue: int,
|
|
97
|
+
verdict: str | None,
|
|
98
|
+
note: str,
|
|
99
|
+
config_path: Path,
|
|
100
|
+
) -> None:
|
|
101
|
+
from coord import state # noqa: PLC0415
|
|
102
|
+
from coord import github_ops # noqa: PLC0415
|
|
103
|
+
from coord.audit import record_audit # noqa: PLC0415
|
|
104
|
+
|
|
105
|
+
cfg = _load_config(config_path)
|
|
106
|
+
repo_cfg = cfg.repo(repo)
|
|
107
|
+
if repo_cfg is None:
|
|
108
|
+
click.echo(f"error: unknown repo {repo!r}", err=True)
|
|
109
|
+
sys.exit(2)
|
|
110
|
+
|
|
111
|
+
try:
|
|
112
|
+
issue_data = github_ops.get_issue(repo_cfg.github, tracking_issue)
|
|
113
|
+
except RuntimeError as e:
|
|
114
|
+
click.echo(f"error: could not fetch #{tracking_issue}: {e}", err=True)
|
|
115
|
+
sys.exit(1)
|
|
116
|
+
|
|
117
|
+
milestone_number = (issue_data.get("milestone") or {}).get("number")
|
|
118
|
+
if milestone_number is None:
|
|
119
|
+
click.echo(
|
|
120
|
+
f"error: #{tracking_issue} has no milestone — Gate A is a "
|
|
121
|
+
"milestone-level gate, so there is nothing to sign off on.",
|
|
122
|
+
err=True,
|
|
123
|
+
)
|
|
124
|
+
sys.exit(2)
|
|
125
|
+
milestone_number = int(milestone_number)
|
|
126
|
+
|
|
127
|
+
contract_text = _fetch_contract(repo_cfg, milestone_number)
|
|
128
|
+
if contract_text is None:
|
|
129
|
+
from coord.acceptance import gate_a_contract_path # noqa: PLC0415
|
|
130
|
+
|
|
131
|
+
click.echo(
|
|
132
|
+
f"error: {gate_a_contract_path(milestone_number)!r} does not exist "
|
|
133
|
+
f"on {repo_cfg.github}@{repo_cfg.default_branch} yet — there is no "
|
|
134
|
+
"contract to approve. Run `coord acceptance mock "
|
|
135
|
+
f"{repo} {tracking_issue}` first, and merge its PR.",
|
|
136
|
+
err=True,
|
|
137
|
+
)
|
|
138
|
+
sys.exit(1)
|
|
139
|
+
|
|
140
|
+
sha = gate_a_mod.contract_digest(contract_text)
|
|
141
|
+
|
|
142
|
+
# Read-only mode: no verdict flag => report where this milestone stands.
|
|
143
|
+
if verdict is None:
|
|
144
|
+
stored = state.get_gate_a_approval(
|
|
145
|
+
repo_name=repo_cfg.name, milestone_number=milestone_number
|
|
146
|
+
)
|
|
147
|
+
decision = gate_a_mod.evaluate(
|
|
148
|
+
repo_name=repo_cfg.name,
|
|
149
|
+
milestone_number=milestone_number,
|
|
150
|
+
contract_text=contract_text,
|
|
151
|
+
approval=stored,
|
|
152
|
+
)
|
|
153
|
+
click.echo(
|
|
154
|
+
f"Gate A ms-{milestone_number} ({repo}#{tracking_issue}): "
|
|
155
|
+
f"{gate_a_mod.summarise(decision)}"
|
|
156
|
+
)
|
|
157
|
+
if decision.approval is not None and decision.approval.note:
|
|
158
|
+
click.echo(f" note: {decision.approval.note}")
|
|
159
|
+
if not decision.ok:
|
|
160
|
+
click.echo("")
|
|
161
|
+
click.echo(decision.reason or "")
|
|
162
|
+
sys.exit(1)
|
|
163
|
+
return
|
|
164
|
+
|
|
165
|
+
record = gate_a_mod.make_record(
|
|
166
|
+
repo_name=repo_cfg.name,
|
|
167
|
+
milestone_number=milestone_number,
|
|
168
|
+
verdict=verdict,
|
|
169
|
+
contract_sha=sha,
|
|
170
|
+
tracking_issue=tracking_issue,
|
|
171
|
+
note=note,
|
|
172
|
+
actor=_actor(),
|
|
173
|
+
)
|
|
174
|
+
try:
|
|
175
|
+
state.save_gate_a_approval(record.to_dict())
|
|
176
|
+
except Exception as e: # noqa: BLE001
|
|
177
|
+
click.echo(f"error: could not record Gate A verdict: {e}", err=True)
|
|
178
|
+
sys.exit(1)
|
|
179
|
+
|
|
180
|
+
record_audit(
|
|
181
|
+
tier="business",
|
|
182
|
+
category="gate",
|
|
183
|
+
event_type=f"gate_a_{verdict}",
|
|
184
|
+
actor=record.actor,
|
|
185
|
+
summary=(
|
|
186
|
+
f"Gate A {verdict} for {repo} ms-{milestone_number} "
|
|
187
|
+
f"(contract {gate_a_mod.short_digest(sha)})"
|
|
188
|
+
+ (f": {note}" if note else "")
|
|
189
|
+
),
|
|
190
|
+
repo=repo_cfg.name,
|
|
191
|
+
issue=tracking_issue,
|
|
192
|
+
details={
|
|
193
|
+
"milestone_number": milestone_number,
|
|
194
|
+
"contract_sha": sha,
|
|
195
|
+
"verdict": verdict,
|
|
196
|
+
"note": note,
|
|
197
|
+
},
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
if verdict == gate_a_mod.VERDICT_APPROVED:
|
|
201
|
+
click.echo(
|
|
202
|
+
f"Gate A approved for {repo} ms-{milestone_number} "
|
|
203
|
+
f"(contract {gate_a_mod.short_digest(sha)}) — this milestone's "
|
|
204
|
+
"issues may now dispatch."
|
|
205
|
+
)
|
|
206
|
+
click.echo(
|
|
207
|
+
" An `--amend` to the contract invalidates this; re-approve after one."
|
|
208
|
+
)
|
|
209
|
+
else:
|
|
210
|
+
click.echo(
|
|
211
|
+
f"Gate A changes requested for {repo} ms-{milestone_number} "
|
|
212
|
+
f"(contract {gate_a_mod.short_digest(sha)}) — dispatch stays refused."
|
|
213
|
+
)
|
|
214
|
+
click.echo(
|
|
215
|
+
f' Next: coord acceptance mock {repo} {tracking_issue} --amend "'
|
|
216
|
+
f'{note or "<what to change>"}"'
|
|
217
|
+
)
|
coord/commands/gates.py
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""`coord gates` — read a work row's gate columns and the live gate decision
|
|
2
|
+
(review / test / merge, including #1479 staleness) for one issue.
|
|
3
|
+
|
|
4
|
+
#1657: previously the only way to see ``test_state``/``smoke_test``/
|
|
5
|
+
``review_verdict``/etc. was a hand-extracted bearer token plus a raw
|
|
6
|
+
``/board`` curl, and even that didn't show *why* a gate would refuse (the
|
|
7
|
+
#1479 freshness computation). This command surfaces both from a single,
|
|
8
|
+
read-only, thin-client-reachable call.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import sys
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
import click
|
|
17
|
+
|
|
18
|
+
from coord.commands._common import _CONFIG_OPTION, _load_config
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _gates_via_daemon(svc, params: dict) -> None:
|
|
22
|
+
"""Relay ``coord gates`` to the daemon host (where the canonical board +
|
|
23
|
+
gh live), mirroring ``coord diagnose``/``coord reconcile-merges``. The
|
|
24
|
+
#1479 freshness computation needs live ``gh`` lookups that only work
|
|
25
|
+
where ``gh`` is authenticated — routing the whole read there keeps a
|
|
26
|
+
thin client's answer identical to running it on the daemon directly."""
|
|
27
|
+
from coord.client import post_record # noqa: PLC0415
|
|
28
|
+
|
|
29
|
+
try:
|
|
30
|
+
resp = post_record(svc, "/gates", params, timeout=60.0)
|
|
31
|
+
except Exception as exc: # noqa: BLE001
|
|
32
|
+
click.echo(f"error: gates via daemon failed: {exc}", err=True)
|
|
33
|
+
sys.exit(1)
|
|
34
|
+
output = resp.get("output") or ""
|
|
35
|
+
if output:
|
|
36
|
+
click.echo(output, nl=False)
|
|
37
|
+
if resp.get("error"):
|
|
38
|
+
click.echo(f"error: {resp['error']}", err=True)
|
|
39
|
+
code = resp.get("exit_code") or 0
|
|
40
|
+
if code:
|
|
41
|
+
sys.exit(int(code))
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@click.command(
|
|
45
|
+
help=(
|
|
46
|
+
"Print a work row's gate columns (test_state, smoke_test, test_reason, "
|
|
47
|
+
"test_toolchain, review_state, review_verdict, review_of_assignment_id), "
|
|
48
|
+
"plus the LIVE review/test/merge gate decision — including whether a recorded verdict "
|
|
49
|
+
"is #1479-stale (recorded against a base/branch SHA that has since "
|
|
50
|
+
"moved) and the SHAs compared. Read-only: never mutates board state."
|
|
51
|
+
)
|
|
52
|
+
)
|
|
53
|
+
@click.argument("repo")
|
|
54
|
+
@click.argument("issue", type=int)
|
|
55
|
+
@click.option(
|
|
56
|
+
"--json",
|
|
57
|
+
"as_json",
|
|
58
|
+
is_flag=True,
|
|
59
|
+
help="Emit the report as a JSON object on stdout instead of the human-readable form.",
|
|
60
|
+
)
|
|
61
|
+
@_CONFIG_OPTION
|
|
62
|
+
def gates(repo: str, issue: int, as_json: bool, config_path: Path) -> None:
|
|
63
|
+
# The #1479 freshness comparison needs live `gh` lookups (branch/base
|
|
64
|
+
# SHAs, patch-id, milestone resolution) — only reliably available where
|
|
65
|
+
# `gh` is authenticated. Route the whole read to the daemon host, same
|
|
66
|
+
# whole-command reroute pattern as `coord diagnose`/`coord
|
|
67
|
+
# reconcile-merges`. COORD_GATES_ON_DAEMON guards the daemon against
|
|
68
|
+
# re-routing to itself.
|
|
69
|
+
from coord.board_service import daemon_reroute_target # noqa: PLC0415
|
|
70
|
+
|
|
71
|
+
_svc = daemon_reroute_target("COORD_GATES_ON_DAEMON")
|
|
72
|
+
if _svc is not None:
|
|
73
|
+
_gates_via_daemon(_svc, {"repo": repo, "issue": issue, "as_json": as_json})
|
|
74
|
+
return
|
|
75
|
+
|
|
76
|
+
import json as _json # noqa: PLC0415
|
|
77
|
+
|
|
78
|
+
from coord import github_ops as gh_ops # noqa: PLC0415
|
|
79
|
+
from coord.gates import build_gate_report, format_gate_report, report_to_dict # noqa: PLC0415
|
|
80
|
+
from coord.state import build_board # noqa: PLC0415
|
|
81
|
+
|
|
82
|
+
cfg = _load_config(config_path)
|
|
83
|
+
board = build_board()
|
|
84
|
+
report = build_gate_report(board, cfg, repo, issue, gh_ops=gh_ops)
|
|
85
|
+
|
|
86
|
+
if as_json:
|
|
87
|
+
click.echo(_json.dumps(report_to_dict(report), indent=2))
|
|
88
|
+
else:
|
|
89
|
+
click.echo(format_gate_report(report))
|