Coverage for src/keel/workblock.py: 100%

31 statements  

« prev     ^ index     » next       coverage.py v7.16.2, created at 2026-10-02 20:26 +0000

1"""Shared work-block contract for daytime and overnight queue runs.""" 

2 

3from __future__ import annotations 

4 

5from collections.abc import Sequence 

6from typing import Any 

7 

8from . import checkpoint, ledger 

9from . import config as cfg 

10 

11WORK_BLOCK_SCHEMA_VERSION = "keel.work-block.v1" 

12OUTCOME_BUCKETS = ( 

13 "shipped", 

14 "pr_open_not_merged", 

15 "deferred", 

16 "blocked", 

17 "skipped", 

18 "needs_input", 

19) 

20#: The staffing flags a work block / overnight session accepts and hands down. 

21#: Before #1017 a batch passed only ``operator_consent.delegated_agent_scope``, so a run 

22#: launched with ``--delegate codex --effort high`` produced children that re-resolved 

23#: from config and ran the default team — the operator's choice reached the parent and 

24#: died there. 

25DELEGATION_FLAGS = ("--delegate", "--review-delegate", "--effort", "--team", "--reviewers") 

26 

27#: The child handoff every queued issue is dispatched through. Written out in full so the 

28#: adapter prose and the published contract cannot describe different handoffs. 

29CHILD_HANDOFF_TEMPLATE = ( 

30 "/keel:ship <issue> [--delegate <provider[:model]>] [--review-delegate <provider>] " 

31 "[--effort <low|medium|high>] [--team <profile>] [--reviewers <n>]" 

32) 

33 

34STOP_CONDITIONS = ( 

35 "queue-exhausted", 

36 "max-items-reached", 

37 "time-budget-exhausted", 

38 "operator-pause", 

39 "consent-gap", 

40 "needs-input", 

41 "blocked-finding", 

42 "merge-window-close", 

43 "three-consecutive-unresolved-ci-failures", 

44 "user-cancelled", 

45) 

46 

47 

48def child_ship_args( 

49 *, 

50 delegate: str | None = None, 

51 review_delegates: Sequence[str] = (), 

52 effort: str | None = None, 

53 team_profile: str | None = None, 

54 reviewer_override: int | None = None, 

55) -> tuple[str, ...]: 

56 """The staffing flags to append to every child ship handoff — set values only. 

57 

58 Deterministic and ordered, because the child handoff line is quoted verbatim into the 

59 session report: a set that reordered itself between two issues would read as two 

60 different teams having run. 

61 """ 

62 args: list[str] = [] 

63 if delegate: 

64 args += ["--delegate", delegate] 

65 for reviewer in review_delegates: 

66 if reviewer: 

67 args += ["--review-delegate", reviewer] 

68 if effort: 

69 args += ["--effort", effort] 

70 if team_profile: 

71 args += ["--team", team_profile] 

72 if reviewer_override is not None: 

73 args += ["--reviewers", str(reviewer_override)] 

74 return tuple(args) 

75 

76 

77def delegation_as_dict( 

78 *, 

79 delegate: str | None = None, 

80 review_delegates: Sequence[str] = (), 

81 effort: str | None = None, 

82 team_profile: str | None = None, 

83 reviewer_override: int | None = None, 

84) -> dict[str, Any]: 

85 """What this block hands to each child ship, and what it must report afterwards.""" 

86 return { 

87 "flags": list(DELEGATION_FLAGS), 

88 "child_handoff_template": CHILD_HANDOFF_TEMPLATE, 

89 "propagate_to_every_child_ship": True, 

90 "record_effective_values_in_session_report": True, 

91 "effective": { 

92 "delegate": delegate, 

93 "review_delegates": [reviewer for reviewer in review_delegates if reviewer], 

94 "effort": effort, 

95 "team": team_profile, 

96 "reviewers": reviewer_override, 

97 }, 

98 "child_args": list( 

99 child_ship_args( 

100 delegate=delegate, 

101 review_delegates=review_delegates, 

102 effort=effort, 

103 team_profile=team_profile, 

104 reviewer_override=reviewer_override, 

105 ) 

106 ), 

107 } 

108 

109 

110def contract_as_dict( 

111 *, 

112 config: cfg.ProjectConfig, 

113 mode: str, 

114 transport: dict[str, Any] | None = None, 

115 delegation: dict[str, Any] | None = None, 

116) -> dict[str, Any]: 

117 """Return the consumer-neutral queue/work-block primitive contract.""" 

118 if mode not in {"daytime", "overnight"}: 

119 raise ValueError("work-block mode must be daytime or overnight") 

120 github = transport or {} 

121 return { 

122 "schema_version": WORK_BLOCK_SCHEMA_VERSION, 

123 "mode": mode, 

124 "base_branch": config.base_branch, 

125 "timezone": config.timezone, 

126 "merge_window": config.merge_window, 

127 "merge_window_mode": config.merge_window_mode, 

128 "github_transport": github, 

129 "queue": { 

130 "accepted_inputs": ["explicit_issue_numbers", "queue_selector"], 

131 "explicit_issue_order": "as-provided", 

132 "selector_order": [ 

133 "priority", 

134 "issue_number", 

135 ], 

136 "snapshot_once_per_session": True, 

137 "refresh_readiness_between_issues": True, 

138 }, 

139 "per_issue": { 

140 "handoff_command": "ship", 

141 "isolated_branch_worktree": True, 

142 "child_inherits_operator_consent_scope": True, 

143 "child_inherits_team_assignment": True, 

144 "child_honors_capture_contract": True, 

145 "child_appends_run_ledger": True, 

146 "child_uses_merge_lock": True, 

147 "child_rechecks_merge_window": True, 

148 }, 

149 "failure_policy": { 

150 "branch_contamination_policy": "one issue cannot reuse another issue worktree", 

151 "non_ready_policy": "skip-or-stop-by-mode-policy", 

152 "continue_after_blocked": mode == "overnight", 

153 "daytime_operator_can_redirect_between_items": mode == "daytime", 

154 }, 

155 "delegation": delegation if delegation is not None else delegation_as_dict(), 

156 "checkpoint": checkpoint.checkpoint_contract_as_dict(config), 

157 "run_ledger": ledger.ledger_contract_as_dict(config), 

158 "final_report": { 

159 "required": True, 

160 "outcome_buckets": list(OUTCOME_BUCKETS), 

161 "source": "child ship results + run ledger + checkpoint", 

162 }, 

163 "stop_conditions": list(STOP_CONDITIONS), 

164 "shared_with": ["overnight"] if mode == "daytime" else ["work-block"], 

165 }