@danmoisan/drm-copilot-mcp 0.0.6 → 1.0.0

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.
Files changed (62) hide show
  1. package/out/mcp-server.js +10213 -398
  2. package/package.json +2 -2
  3. package/resources/claude-customizations/.claude/hooks/enforce-completion-consistency.ps1 +127 -17
  4. package/resources/claude-customizations/.claude/hooks/enforce-completion-helpers.ps1 +163 -0
  5. package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate.ps1 +0 -12
  6. package/resources/claude-customizations/.claude/hooks/validate-orchestrator-output.ps1 +68 -1
  7. package/resources/config/orchestration-routing.json +3 -3
  8. package/resources/codex-and-agents-customizations/.codex/scripts/post-codex-worktree-session.ps1 +0 -5
  9. package/resources/scripts/dev_tools/__init__.py +0 -0
  10. package/resources/scripts/dev_tools/_orchestrator_state_human_interaction.py +0 -127
  11. package/resources/scripts/dev_tools/_orchestrator_state_routing.py +0 -216
  12. package/resources/scripts/dev_tools/agentic_sync.py +0 -819
  13. package/resources/scripts/dev_tools/codex_native_converter/__init__.py +0 -11
  14. package/resources/scripts/dev_tools/codex_native_converter/__main__.py +0 -6
  15. package/resources/scripts/dev_tools/codex_native_converter/cli.py +0 -11
  16. package/resources/scripts/dev_tools/new_active_feature_folder.py +0 -79
  17. package/resources/scripts/dev_tools/new_active_feature_folder_docs.py +0 -268
  18. package/resources/scripts/dev_tools/new_active_feature_folder_flow.py +0 -366
  19. package/resources/scripts/dev_tools/new_active_feature_folder_io.py +0 -306
  20. package/resources/scripts/dev_tools/new_active_feature_folder_markdown.py +0 -252
  21. package/resources/scripts/dev_tools/new_active_feature_folder_models.py +0 -136
  22. package/resources/scripts/dev_tools/new_potential_bug_entry.py +0 -465
  23. package/resources/scripts/dev_tools/potential_to_issue.py +0 -421
  24. package/resources/scripts/dev_tools/potential_to_issue_content.py +0 -212
  25. package/resources/scripts/dev_tools/pr_context/__init__.py +0 -0
  26. package/resources/scripts/dev_tools/pr_context/collector.py +0 -619
  27. package/resources/scripts/dev_tools/pr_context/feature_docs.py +0 -349
  28. package/resources/scripts/dev_tools/pr_context/git.py +0 -153
  29. package/resources/scripts/dev_tools/pr_context/github.py +0 -549
  30. package/resources/scripts/dev_tools/pr_context/models.py +0 -198
  31. package/resources/scripts/dev_tools/pr_context/render.py +0 -342
  32. package/resources/scripts/dev_tools/pr_context/render_feature_excerpts.py +0 -256
  33. package/resources/scripts/dev_tools/pr_context/render_pr_helpers.py +0 -291
  34. package/resources/scripts/dev_tools/pr_context/summary_helpers.py +0 -386
  35. package/resources/scripts/dev_tools/pr_context/verification_evidence.py +0 -171
  36. package/resources/scripts/dev_tools/prompt_mode_contract.py +0 -152
  37. package/resources/scripts/dev_tools/push_down_claude_customizations.py +0 -403
  38. package/resources/scripts/dev_tools/push_down_claude_filesystem.py +0 -472
  39. package/resources/scripts/dev_tools/push_down_claude_pack_selection.py +0 -401
  40. package/resources/scripts/dev_tools/push_down_codex_and_agents_customizations.py +0 -139
  41. package/resources/scripts/dev_tools/push_down_copilot_customizations.py +0 -504
  42. package/resources/scripts/dev_tools/push_down_copilot_customizations_filesystem.py +0 -217
  43. package/resources/scripts/dev_tools/push_down_copilot_customizations_rewrites.py +0 -293
  44. package/resources/scripts/dev_tools/resolve_file_prompt.py +0 -457
  45. package/resources/scripts/dev_tools/resolve_hard_lock_prompt.py +0 -444
  46. package/resources/scripts/dev_tools/validate_orchestration_artifacts.py +0 -246
  47. package/resources/scripts/dev_tools/validate_orchestration_review_artifacts.py +0 -107
  48. package/resources/scripts/dev_tools/validate_orchestrator_state.py +0 -505
  49. package/resources/scripts/dev_tools/validate_policy_audit_artifact.py +0 -472
  50. package/resources/templates/codex_native_converter.py +0 -35
  51. package/resources/templates/collect_commit_context.py +0 -212
  52. package/resources/templates/collect_pr_context.py +0 -74
  53. package/resources/templates/hello_python.py +0 -11
  54. package/resources/templates/new_active_feature_folder.py +0 -67
  55. package/resources/templates/new_potential_bug_entry.py +0 -54
  56. package/resources/templates/potential_to_issue.py +0 -55
  57. package/resources/templates/push_down_claude_customizations.py +0 -233
  58. package/resources/templates/push_down_codex_and_agents_customizations.py +0 -95
  59. package/resources/templates/push_down_copilot_customizations.py +0 -124
  60. package/resources/templates/resolve_atomic_plan_prompt.py +0 -75
  61. package/resources/templates/resolve_hard_lock_prompt.py +0 -65
  62. package/resources/templates/validate_orchestration_artifacts.py +0 -55
@@ -1,107 +0,0 @@
1
- """Validate non-policy review-oriented orchestration artifacts.
2
-
3
- Purpose:
4
- Hold the code-review and feature-audit validators while re-exporting the
5
- split policy-audit validator from its dedicated module.
6
-
7
- Usage:
8
- Import the public validation functions from
9
- ``scripts.dev_tools.validate_orchestration_artifacts`` or directly from this
10
- module when a caller needs the review-specific validators.
11
-
12
- Flow:
13
- 1. Delegate policy-audit validation to the split policy-audit module.
14
- 2. Validate code-review and feature-audit headings locally.
15
- 3. Return a list of contract violations for the caller to surface.
16
-
17
- Invariants / Constraints:
18
- - The public validator functions return lists of error strings and do not
19
- print or mutate files.
20
- - The module preserves the existing import surface for callers that still
21
- import ``validate_policy_audit_text`` from this module.
22
-
23
- Side Effects:
24
- None.
25
- """
26
-
27
- from __future__ import annotations
28
-
29
- from dev_tools.validate_policy_audit_artifact import validate_policy_audit_text
30
-
31
- __all__ = [
32
- "validate_policy_audit_text",
33
- "validate_code_review_text",
34
- "validate_feature_audit_text",
35
- ]
36
-
37
- CODE_REVIEW_REQUIRED_HEADINGS = (
38
- "## Executive Summary",
39
- "## Findings Table",
40
- )
41
- FEATURE_AUDIT_REQUIRED_HEADINGS = (
42
- "## Scope and Baseline",
43
- "## Acceptance Criteria Inventory",
44
- "## Acceptance Criteria Evaluation",
45
- "## Summary",
46
- "## Acceptance Criteria Check-off",
47
- )
48
-
49
-
50
- def validate_code_review_text(text: str) -> list[str]:
51
- """Validate audit-grade code-review structure.
52
-
53
- Purpose:
54
- Require the minimal headings and findings table that repository code
55
- reviews must include.
56
-
57
- Args:
58
- text (str): Full code-review artifact text.
59
-
60
- Returns:
61
- list[str]: Validation errors for missing headings or table structure.
62
-
63
- Raises:
64
- None.
65
-
66
- Side Effects:
67
- None.
68
- """
69
-
70
- errors: list[str] = []
71
- for heading in CODE_REVIEW_REQUIRED_HEADINGS:
72
- if heading not in text:
73
- errors.append(f"Code review missing required heading: {heading}")
74
- table_header = (
75
- "| Severity | File | Location | Finding | Recommendation | "
76
- "Rationale | Evidence |"
77
- )
78
- if table_header not in text:
79
- errors.append("Code review missing the required findings table header.")
80
- return errors
81
-
82
-
83
- def validate_feature_audit_text(text: str) -> list[str]:
84
- """Validate feature-audit structure.
85
-
86
- Purpose:
87
- Require the canonical sections that tie implementation evidence back to
88
- acceptance criteria.
89
-
90
- Args:
91
- text (str): Full feature-audit artifact text.
92
-
93
- Returns:
94
- list[str]: Validation errors for any missing canonical sections.
95
-
96
- Raises:
97
- None.
98
-
99
- Side Effects:
100
- None.
101
- """
102
-
103
- errors: list[str] = []
104
- for heading in FEATURE_AUDIT_REQUIRED_HEADINGS:
105
- if heading not in text:
106
- errors.append(f"Feature audit missing required heading: {heading}")
107
- return errors
@@ -1,505 +0,0 @@
1
- """Validate orchestration checkpoint state artifacts.
2
-
3
- Purpose:
4
- Hold the orchestrator-state validation logic and receipt-namespace rules so
5
- the stable CLI entrypoint can remain small while preserving the existing
6
- validator contract.
7
-
8
- Usage:
9
- Import ``validate_orchestrator_state_text`` from
10
- ``scripts.dev_tools.validate_orchestration_artifacts`` or directly from this
11
- module when a caller needs checkpoint validation.
12
-
13
- Flow:
14
- 1. Parse the checkpoint JSON payload.
15
- 2. Validate required top-level keys and status values.
16
- 3. Validate either the legacy list-based receipts, top-level promotion
17
- receipts, or the additive ``delegation_receipts.promotion.*``
18
- namespace.
19
-
20
- Invariants / Constraints:
21
- - The validator accepts the legacy list, top-level promotion receipts, and
22
- the additive promotion namespace forms for receipt evidence.
23
- - Unsupported namespace keys are rejected.
24
- - The validator returns error strings and never mutates the checkpoint.
25
-
26
- """
27
-
28
- from __future__ import annotations
29
-
30
- import json
31
- from typing import Any, cast
32
-
33
- from dev_tools._orchestrator_state_human_interaction import (
34
- HUMAN_INTERACTION_KEY,
35
- _validate_human_interaction,
36
- )
37
- from dev_tools._orchestrator_state_routing import validate_routing_contract
38
-
39
- REQUIRED_STATE_KEYS = (
40
- "objective",
41
- "change_budget_estimate",
42
- "path_selected",
43
- "promotion-type",
44
- "short-name",
45
- "relativeFile",
46
- "long-name",
47
- "issue-num",
48
- "feature-folder",
49
- "work-mode",
50
- "plan-path",
51
- "completed_steps",
52
- "next_step",
53
- "last_updated",
54
- "step5_status",
55
- "step6_status",
56
- "step7_status",
57
- "step8_status",
58
- "step9_status",
59
- "step10_status",
60
- "delegation_receipts",
61
- "blocked_reason",
62
- )
63
- VALID_STEP_STATUS = {
64
- "not-applicable",
65
- "pending",
66
- "delegated",
67
- "verified",
68
- "blocked",
69
- "not_started",
70
- "in_progress",
71
- "completed",
72
- }
73
- VALID_BLOCKED_REASONS = {
74
- "none",
75
- "spawn_agent_unavailable",
76
- "delegation_launch_failed",
77
- "delegate_no_receipt",
78
- "delegate_contract_incomplete",
79
- "validator_failed",
80
- "user_requested_stop",
81
- }
82
- REQUIRED_RECEIPT_KEYS = (
83
- "step",
84
- "agent_name",
85
- "agent_id",
86
- "skill_source",
87
- "started_at",
88
- "completed_at",
89
- "result_signal",
90
- "artifact_paths",
91
- )
92
- PROMOTION_RECEIPT_NAMESPACE_KEY = "promotion"
93
- PROMOTION_RECEIPT_KEYS = (
94
- "potential_entry",
95
- "issue",
96
- "feature_folder",
97
- )
98
- ISSUE_232 = "232"
99
- ISSUE_232_BRANCH = "feature/harden-orchestrate-skill-232"
100
- PR_GATE_KEYS = ("pr_number", "pr_url", "head_branch", "head_sha")
101
- CI_GATE_KEYS = ("conclusion", "head_sha", "verified_at")
102
- REMEDIATION_LOOP_KEY = "remediation_loop"
103
- REMEDIATION_CYCLES_KEY = "cycles"
104
- # Execution statuses that may only be recorded once a cycle's preflight gate has
105
- # cleared; recording any of these before preflight clears is a malformed cycle.
106
- EXECUTION_STATUSES_REQUIRING_CLEAR_PREFLIGHT = {
107
- "in_progress",
108
- "complete",
109
- "failed",
110
- }
111
- PREFLIGHT_CLEARED_STATUS = "clear"
112
-
113
-
114
- def _validate_remediation_cycle(index: int, cycle: dict[str, Any]) -> list[str]:
115
- """Validate the three invariants for one remediation cycle.
116
-
117
- Purpose:
118
- Enforce the orchestrator-state remediation-cycle invariants documented
119
- in `.claude/rules/orchestrator-state.md` for a single cycle object:
120
- non-empty `plan_path`, execution only after a cleared preflight, and a
121
- satisfied exit gate only with zero blocking findings.
122
-
123
- Args:
124
- index (int): Zero-based position of this cycle within the
125
- `remediation_loop.cycles` array, used for error context.
126
- cycle (dict[str, Any]): The raw cycle object extracted from the
127
- checkpoint JSON.
128
-
129
- Returns:
130
- list[str]: One error string per violated invariant; an empty list when
131
- the cycle satisfies all three invariants.
132
-
133
- Raises:
134
- None.
135
-
136
- Side Effects:
137
- None.
138
- """
139
-
140
- errors: list[str] = []
141
-
142
- # Invariant 1: plan_path must be a non-empty, non-whitespace string.
143
- plan_path = cycle.get("plan_path")
144
- if not isinstance(plan_path, str) or not plan_path.strip():
145
- errors.append(
146
- f"Checkpoint remediation cycle #{index} plan_path must be a "
147
- "non-empty string."
148
- )
149
-
150
- # Invariant 2: an execution status in the blocked set requires that the
151
- # cycle's preflight gate reports exactly the cleared status.
152
- execution_status = cycle.get("execution_status")
153
- if execution_status in EXECUTION_STATUSES_REQUIRING_CLEAR_PREFLIGHT:
154
- preflight = cycle.get("preflight")
155
- # Read the nested preflight final status defensively; a missing or
156
- # non-object preflight cannot satisfy the cleared requirement.
157
- preflight_status: object = (
158
- cast("dict[str, Any]", preflight).get("final_status")
159
- if isinstance(preflight, dict)
160
- else None
161
- )
162
- if preflight_status != PREFLIGHT_CLEARED_STATUS:
163
- errors.append(
164
- f"Checkpoint remediation cycle #{index} execution_status is "
165
- f"{execution_status} but preflight.final_status is not 'clear'."
166
- )
167
-
168
- # Invariant 3: a satisfied exit gate requires zero blocking findings.
169
- if cycle.get("exit_condition_met") is True and cycle.get("blocking_count") != 0:
170
- errors.append(
171
- f"Checkpoint remediation cycle #{index} exit_condition_met is true "
172
- "but blocking_count is not 0."
173
- )
174
-
175
- return errors
176
-
177
-
178
- def _validate_remediation_loop(remediation_loop: object) -> list[str]:
179
- errors: list[str] = []
180
-
181
- # A non-object remediation_loop carries no cycles to validate; treat it as
182
- # nothing to enforce rather than fabricating a structural error here.
183
- if not isinstance(remediation_loop, dict):
184
- return errors
185
- loop_map = cast("dict[str, Any]", remediation_loop)
186
-
187
- cycles = loop_map.get(REMEDIATION_CYCLES_KEY)
188
- if not isinstance(cycles, list):
189
- return errors
190
- cycle_list = cast("list[object]", cycles)
191
-
192
- # Validate each cycle independently so callers receive a complete error
193
- # list instead of stopping at the first malformed cycle.
194
- for index, cycle in enumerate(cycle_list):
195
- if not isinstance(cycle, dict):
196
- errors.append(f"Checkpoint remediation cycle #{index} must be an object.")
197
- continue
198
- errors.extend(_validate_remediation_cycle(index, cast("dict[str, Any]", cycle)))
199
-
200
- return errors
201
-
202
-
203
- def _missing_object_keys(value: object, keys: tuple[str, ...]) -> list[str]:
204
- if not isinstance(value, dict):
205
- return list(keys)
206
- value_map = cast("dict[str, object]", value)
207
- missing: list[str] = []
208
- for key in keys:
209
- item = value_map.get(key)
210
- if item is None or (isinstance(item, str) and not item.strip()):
211
- missing.append(key)
212
- return missing
213
-
214
-
215
- def _validate_completion_pr_gate(state: dict[str, Any]) -> list[str]:
216
- pr_gate = state.get("pr_gate")
217
- missing = _missing_object_keys(pr_gate, PR_GATE_KEYS)
218
- if not isinstance(pr_gate, dict):
219
- return [
220
- "Checkpoint completion validation failed: pr_gate must be an object "
221
- f"with keys: {', '.join(PR_GATE_KEYS)}."
222
- ]
223
- errors: list[str] = []
224
- if missing:
225
- errors.append(
226
- "Checkpoint completion validation failed: pr_gate missing required "
227
- f"fields: {', '.join(missing)}."
228
- )
229
- if state.get("issue-num") == ISSUE_232:
230
- head_branch = cast("dict[str, object]", pr_gate).get("head_branch")
231
- if head_branch != ISSUE_232_BRANCH:
232
- errors.append(
233
- "Checkpoint completion validation failed: pr_gate.head_branch "
234
- f"must be {ISSUE_232_BRANCH} for Issue #232."
235
- )
236
- return errors
237
-
238
-
239
- def _validate_completion_ci_gate(state: dict[str, Any]) -> list[str]:
240
- ci_gate = state.get("ci_gate")
241
- missing = _missing_object_keys(ci_gate, CI_GATE_KEYS)
242
- if not isinstance(ci_gate, dict):
243
- return [
244
- "Checkpoint completion validation failed: ci_gate must be an object "
245
- f"with keys: {', '.join(CI_GATE_KEYS)}."
246
- ]
247
- errors: list[str] = []
248
- if missing:
249
- errors.append(
250
- "Checkpoint completion validation failed: ci_gate missing required "
251
- f"fields: {', '.join(missing)}."
252
- )
253
- ci_map = cast("dict[str, object]", ci_gate)
254
- if ci_map.get("conclusion") != "success":
255
- errors.append(
256
- "Checkpoint completion validation failed: ci_gate.conclusion must be "
257
- "success."
258
- )
259
- pr_gate = state.get("pr_gate")
260
- if isinstance(pr_gate, dict):
261
- pr_map = cast("dict[str, object]", pr_gate)
262
- pr_head_sha = pr_map.get("head_sha")
263
- else:
264
- pr_head_sha = None
265
- if pr_head_sha is not None and ci_map.get("head_sha") != pr_head_sha:
266
- errors.append(
267
- "Checkpoint completion validation failed: ci_gate.head_sha must match "
268
- "pr_gate.head_sha."
269
- )
270
- return errors
271
-
272
-
273
- def _validate_issue_232_promotion_receipts(state: dict[str, Any]) -> list[str]:
274
- if state.get("issue-num") != ISSUE_232:
275
- return []
276
- receipts = state.get("delegation_receipts")
277
- promotion_source = "promotion_receipts"
278
- promotion: object = state.get("promotion_receipts")
279
- if isinstance(receipts, dict):
280
- receipts_map = cast("dict[str, object]", receipts)
281
- namespaced_promotion = receipts_map.get(PROMOTION_RECEIPT_NAMESPACE_KEY)
282
- if namespaced_promotion is not None:
283
- promotion_source = "delegation_receipts.promotion"
284
- promotion = namespaced_promotion
285
- missing = _missing_object_keys(promotion, PROMOTION_RECEIPT_KEYS)
286
- return (
287
- [
288
- "Checkpoint completion validation failed: "
289
- f"{promotion_source} missing required Issue #232 "
290
- f"receipt keys: {', '.join(missing)}."
291
- ]
292
- if missing
293
- else []
294
- )
295
-
296
-
297
- def _validate_list_delegation_receipts(receipts: list[object]) -> list[str]:
298
- """Validate the legacy list-based delegation receipt payload.
299
-
300
- Purpose:
301
- Preserve compatibility with older checkpoints that store delegation
302
- receipts as a list of receipt objects.
303
-
304
- Args:
305
- receipts (list[object]): Raw receipt payload extracted from the
306
- checkpoint JSON.
307
-
308
- Returns:
309
- list[str]: Validation errors for any malformed receipt objects.
310
-
311
- Raises:
312
- None.
313
-
314
- Side Effects:
315
- None.
316
- """
317
-
318
- errors: list[str] = []
319
-
320
- # Validate each legacy receipt independently so callers receive a complete
321
- # error list instead of stopping at the first malformed item.
322
- for index, receipt in enumerate(receipts):
323
- if not isinstance(receipt, dict):
324
- errors.append(f"Checkpoint delegation receipt #{index} must be an object.")
325
- continue
326
- for key in REQUIRED_RECEIPT_KEYS:
327
- if key not in receipt:
328
- errors.append(
329
- f"Checkpoint delegation receipt #{index} missing key: {key}"
330
- )
331
- artifact_paths = cast("dict[str, Any]", receipt).get("artifact_paths")
332
- if artifact_paths is not None and not isinstance(artifact_paths, list):
333
- errors.append(
334
- "Checkpoint delegation receipt "
335
- f"#{index} artifact_paths must be a list."
336
- )
337
-
338
- return errors
339
-
340
-
341
- def _validate_namespaced_delegation_receipts(receipts: dict[str, Any]) -> list[str]:
342
- """Validate the additive object namespace form of delegation receipts.
343
-
344
- Purpose:
345
- Enforce the reviewed ``delegation_receipts.promotion.*`` contract while
346
- keeping the raw receipt payloads opaque to the validator.
347
-
348
- Args:
349
- receipts (dict[str, Any]): Object-form receipt payload extracted from
350
- the checkpoint JSON.
351
-
352
- Returns:
353
- list[str]: Validation errors for unsupported object-shape keys.
354
-
355
- Raises:
356
- None.
357
-
358
- Side Effects:
359
- None.
360
- """
361
-
362
- errors: list[str] = []
363
- unsupported_keys = sorted(
364
- key for key in receipts if key != PROMOTION_RECEIPT_NAMESPACE_KEY
365
- )
366
- for key in unsupported_keys:
367
- errors.append(
368
- f"Checkpoint delegation_receipts object contains unsupported key: {key}"
369
- )
370
-
371
- promotion_receipts = receipts.get(PROMOTION_RECEIPT_NAMESPACE_KEY)
372
- if promotion_receipts is None:
373
- return errors
374
- if not isinstance(promotion_receipts, dict):
375
- errors.append(
376
- "Checkpoint delegation_receipts.promotion must be an object namespace."
377
- )
378
- return errors
379
-
380
- promotion_receipt_map = cast("dict[str, Any]", promotion_receipts)
381
- unsupported_promotion_keys = sorted(
382
- key for key in promotion_receipt_map if key not in PROMOTION_RECEIPT_KEYS
383
- )
384
-
385
- # Reject unknown nested keys while leaving the documented raw receipt values
386
- # untouched and unnormalized.
387
- for key in unsupported_promotion_keys:
388
- errors.append(
389
- "Checkpoint delegation_receipts.promotion contains unsupported key: "
390
- f"{key}"
391
- )
392
-
393
- return errors
394
-
395
-
396
- def validate_orchestrator_state_text(
397
- text: str, *, require_complete: bool = False
398
- ) -> list[str]:
399
- """Validate checkpoint schema and completion-state fields.
400
-
401
- Purpose:
402
- Enforce the repository contract for orchestrator-state artifacts before
403
- resume or review workflows rely on the checkpoint contents.
404
-
405
- Args:
406
- text (str): Raw checkpoint JSON text.
407
- require_complete (bool): When True, require all tracked lifecycle states
408
- to be completion-safe.
409
-
410
- Returns:
411
- list[str]: Validation errors for malformed or incomplete checkpoint
412
- state.
413
-
414
- Raises:
415
- None.
416
-
417
- Side Effects:
418
- None.
419
- """
420
-
421
- errors: list[str] = []
422
- try:
423
- state = json.loads(text)
424
- except json.JSONDecodeError as exc:
425
- return [f"Checkpoint is not valid JSON: {exc}"]
426
-
427
- if not isinstance(state, dict):
428
- return ["Checkpoint root must be a JSON object."]
429
- state_map = cast("dict[str, Any]", state)
430
-
431
- # Require the canonical top-level fields before evaluating deeper state and
432
- # receipt invariants.
433
- for key in REQUIRED_STATE_KEYS:
434
- if key not in state_map:
435
- errors.append(f"Checkpoint missing required key: {key}")
436
-
437
- for key in (
438
- "step5_status",
439
- "step6_status",
440
- "step7_status",
441
- "step8_status",
442
- "step9_status",
443
- "step10_status",
444
- ):
445
- value = state_map.get(key)
446
- if value is not None and value not in VALID_STEP_STATUS:
447
- errors.append(f"Checkpoint has invalid {key}: {value}")
448
-
449
- blocked_reason = state_map.get("blocked_reason")
450
- if blocked_reason is not None and blocked_reason not in VALID_BLOCKED_REASONS:
451
- errors.append(f"Checkpoint has invalid blocked_reason: {blocked_reason}")
452
-
453
- receipts = state_map.get("delegation_receipts")
454
- if receipts is not None:
455
- if isinstance(receipts, list):
456
- errors.extend(
457
- _validate_list_delegation_receipts(cast("list[object]", receipts))
458
- )
459
- elif isinstance(receipts, dict):
460
- errors.extend(
461
- _validate_namespaced_delegation_receipts(
462
- cast("dict[str, Any]", receipts)
463
- )
464
- )
465
- else:
466
- errors.append(
467
- "Checkpoint delegation_receipts must be a list or object namespace."
468
- )
469
-
470
- # Apply the additive remediation-cycle invariants only when the checkpoint
471
- # carries a remediation_loop; absent the key, behavior is unchanged.
472
- if REMEDIATION_LOOP_KEY in state_map:
473
- errors.extend(_validate_remediation_loop(state_map.get(REMEDIATION_LOOP_KEY)))
474
-
475
- # Apply the additive human_interaction invariants only when the checkpoint
476
- # carries a human_interaction key; absent the key, behavior is unchanged.
477
- if HUMAN_INTERACTION_KEY in state_map:
478
- errors.extend(_validate_human_interaction(state_map.get(HUMAN_INTERACTION_KEY)))
479
-
480
- if require_complete:
481
- # Enforce completion-safe lifecycle states only when the caller opts into
482
- # the stricter completion gate.
483
- for key in (
484
- "step5_status",
485
- "step6_status",
486
- "step7_status",
487
- "step8_status",
488
- "step9_status",
489
- "step10_status",
490
- ):
491
- value = state_map.get(key)
492
- if value in {"pending", "blocked"}:
493
- errors.append(
494
- f"Checkpoint completion validation failed: {key} is {value}."
495
- )
496
- if state_map.get("blocked_reason") not in {None, "none"}:
497
- errors.append(
498
- "Checkpoint completion validation failed: blocked_reason is not `none`."
499
- )
500
- errors.extend(_validate_completion_pr_gate(state_map))
501
- errors.extend(_validate_completion_ci_gate(state_map))
502
- errors.extend(_validate_issue_232_promotion_receipts(state_map))
503
- errors.extend(validate_routing_contract(state_map))
504
-
505
- return errors