@danmoisan/drm-copilot-mcp 0.0.5 → 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 (96) hide show
  1. package/out/mcp-server.js +10268 -380
  2. package/package.json +2 -2
  3. package/resources/claude-customizations/.claude/agents/orchestrator.md +6 -2
  4. package/resources/claude-customizations/.claude/agents/pr-author.md +78 -0
  5. package/resources/claude-customizations/.claude/agents/task-researcher.md +10 -6
  6. package/resources/claude-customizations/.claude/hooks/enforce-checkpoint-monotonic.ps1 +68 -10
  7. package/resources/claude-customizations/.claude/hooks/enforce-completion-consistency.ps1 +148 -11
  8. package/resources/claude-customizations/.claude/hooks/enforce-completion-helpers.ps1 +163 -0
  9. package/resources/claude-customizations/.claude/hooks/enforce-evidence-locations.ps1 +40 -14
  10. package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate.ps1 +198 -0
  11. package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill.ps1 +153 -10
  12. package/resources/claude-customizations/.claude/hooks/validate-orchestrator-output.ps1 +68 -1
  13. package/resources/claude-customizations/.claude/hooks/validate-pr-author-output.ps1 +136 -0
  14. package/resources/claude-customizations/.claude/hooks/validate-task-researcher-output.ps1 +22 -6
  15. package/resources/claude-customizations/.claude/settings.json +22 -0
  16. package/resources/claude-customizations/.claude/skills/evidence-and-timestamp-conventions/SKILL.md +0 -1
  17. package/resources/claude-customizations/.claude/skills/orchestrate/SKILL.md +76 -2
  18. package/resources/claude-customizations/.claude/skills/research-issue/SKILL.md +5 -3
  19. package/resources/claude-customizations/.claude-variants/csharp-legacy/agents/csharp-typed-engineer.md +69 -0
  20. package/resources/claude-customizations/.claude-variants/csharp-legacy/rules/csharp.md +96 -0
  21. package/resources/claude-customizations/.claude-variants/csharp-legacy/skills/csharp-qa-gate/SKILL.md +77 -0
  22. package/resources/claude-customizations/.claude-variants/csharp-legacy/skills/invoke-csharp-engineer/SKILL.md +64 -0
  23. package/resources/claude-customizations/pack-manifests/core.json +65 -0
  24. package/resources/claude-customizations/pack-manifests/csharp-legacy.json +11 -0
  25. package/resources/claude-customizations/pack-manifests/csharp-modern.json +12 -0
  26. package/resources/claude-customizations/pack-manifests/powershell.json +14 -0
  27. package/resources/claude-customizations/pack-manifests/python.json +14 -0
  28. package/resources/claude-customizations/pack-manifests/typescript.json +9 -0
  29. package/resources/codex-and-agents-customizations/.agents/skills/feature-promotion-lifecycle/SKILL.md +30 -13
  30. package/resources/codex-and-agents-customizations/.agents/skills/orchestrate/SKILL.md +113 -10
  31. package/resources/codex-and-agents-customizations/.agents/skills/orchestrator-workflow/SKILL.md +33 -8
  32. package/resources/codex-and-agents-customizations/.agents/skills/repo-automation-adapter/SKILL.md +12 -7
  33. package/resources/codex-and-agents-customizations/.codex/config.toml +65 -6
  34. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-checkpoint-monotonic.ps1 +303 -0
  35. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-completion-consistency.ps1 +300 -0
  36. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate.ps1 +148 -0
  37. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-pr-author-skill.ps1 +336 -0
  38. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-promotion-mcp-only.ps1 +83 -17
  39. package/resources/config/orchestration-routing.json +5 -13
  40. package/resources/customizations/.github/agents/pr-author.agent.md +25 -0
  41. package/resources/customizations/.github/agents/task-researcher.agent.md +4 -4
  42. package/resources/customizations/.github/prompts/fillout-prd-feature.prompt.md +1 -1
  43. package/resources/customizations/.github/prompts/research-issue.prompt.md +4 -3
  44. package/resources/codex-and-agents-customizations/.codex/scripts/post-codex-worktree-session.ps1 +0 -5
  45. package/resources/scripts/dev_tools/__init__.py +0 -0
  46. package/resources/scripts/dev_tools/_orchestrator_state_human_interaction.py +0 -127
  47. package/resources/scripts/dev_tools/_orchestrator_state_routing.py +0 -216
  48. package/resources/scripts/dev_tools/agentic_sync.py +0 -819
  49. package/resources/scripts/dev_tools/codex_native_converter/__init__.py +0 -11
  50. package/resources/scripts/dev_tools/codex_native_converter/__main__.py +0 -6
  51. package/resources/scripts/dev_tools/codex_native_converter/cli.py +0 -11
  52. package/resources/scripts/dev_tools/new_active_feature_folder.py +0 -79
  53. package/resources/scripts/dev_tools/new_active_feature_folder_docs.py +0 -268
  54. package/resources/scripts/dev_tools/new_active_feature_folder_flow.py +0 -366
  55. package/resources/scripts/dev_tools/new_active_feature_folder_io.py +0 -306
  56. package/resources/scripts/dev_tools/new_active_feature_folder_markdown.py +0 -252
  57. package/resources/scripts/dev_tools/new_active_feature_folder_models.py +0 -136
  58. package/resources/scripts/dev_tools/new_potential_bug_entry.py +0 -465
  59. package/resources/scripts/dev_tools/potential_to_issue.py +0 -421
  60. package/resources/scripts/dev_tools/potential_to_issue_content.py +0 -212
  61. package/resources/scripts/dev_tools/pr_context/__init__.py +0 -0
  62. package/resources/scripts/dev_tools/pr_context/collector.py +0 -619
  63. package/resources/scripts/dev_tools/pr_context/feature_docs.py +0 -349
  64. package/resources/scripts/dev_tools/pr_context/git.py +0 -153
  65. package/resources/scripts/dev_tools/pr_context/github.py +0 -549
  66. package/resources/scripts/dev_tools/pr_context/models.py +0 -198
  67. package/resources/scripts/dev_tools/pr_context/render.py +0 -342
  68. package/resources/scripts/dev_tools/pr_context/render_feature_excerpts.py +0 -256
  69. package/resources/scripts/dev_tools/pr_context/render_pr_helpers.py +0 -291
  70. package/resources/scripts/dev_tools/pr_context/summary_helpers.py +0 -386
  71. package/resources/scripts/dev_tools/pr_context/verification_evidence.py +0 -171
  72. package/resources/scripts/dev_tools/prompt_mode_contract.py +0 -152
  73. package/resources/scripts/dev_tools/push_down_claude_customizations.py +0 -374
  74. package/resources/scripts/dev_tools/push_down_codex_and_agents_customizations.py +0 -139
  75. package/resources/scripts/dev_tools/push_down_copilot_customizations.py +0 -504
  76. package/resources/scripts/dev_tools/push_down_copilot_customizations_filesystem.py +0 -217
  77. package/resources/scripts/dev_tools/push_down_copilot_customizations_rewrites.py +0 -293
  78. package/resources/scripts/dev_tools/resolve_file_prompt.py +0 -457
  79. package/resources/scripts/dev_tools/resolve_hard_lock_prompt.py +0 -444
  80. package/resources/scripts/dev_tools/validate_orchestration_artifacts.py +0 -246
  81. package/resources/scripts/dev_tools/validate_orchestration_review_artifacts.py +0 -107
  82. package/resources/scripts/dev_tools/validate_orchestrator_state.py +0 -428
  83. package/resources/scripts/dev_tools/validate_policy_audit_artifact.py +0 -448
  84. package/resources/templates/codex_native_converter.py +0 -35
  85. package/resources/templates/collect_commit_context.py +0 -212
  86. package/resources/templates/collect_pr_context.py +0 -74
  87. package/resources/templates/hello_python.py +0 -11
  88. package/resources/templates/new_active_feature_folder.py +0 -67
  89. package/resources/templates/new_potential_bug_entry.py +0 -54
  90. package/resources/templates/potential_to_issue.py +0 -55
  91. package/resources/templates/push_down_claude_customizations.py +0 -409
  92. package/resources/templates/push_down_codex_and_agents_customizations.py +0 -95
  93. package/resources/templates/push_down_copilot_customizations.py +0 -124
  94. package/resources/templates/resolve_atomic_plan_prompt.py +0 -75
  95. package/resources/templates/resolve_hard_lock_prompt.py +0 -65
  96. 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,428 +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 or the additive
17
- ``delegation_receipts.promotion.*`` namespace.
18
-
19
- Invariants / Constraints:
20
- - The validator accepts both the legacy list and the additive promotion
21
- namespace forms for ``delegation_receipts``.
22
- - Unsupported namespace keys are rejected.
23
- - The validator returns error strings and never mutates the checkpoint.
24
-
25
- Side Effects:
26
- None.
27
- """
28
-
29
- from __future__ import annotations
30
-
31
- import json
32
- from typing import Any, cast
33
-
34
- from dev_tools._orchestrator_state_human_interaction import (
35
- HUMAN_INTERACTION_KEY,
36
- _validate_human_interaction,
37
- )
38
- from dev_tools._orchestrator_state_routing import validate_routing_contract
39
-
40
- REQUIRED_STATE_KEYS = (
41
- "objective",
42
- "change_budget_estimate",
43
- "path_selected",
44
- "promotion-type",
45
- "short-name",
46
- "relativeFile",
47
- "long-name",
48
- "issue-num",
49
- "feature-folder",
50
- "work-mode",
51
- "plan-path",
52
- "completed_steps",
53
- "next_step",
54
- "last_updated",
55
- "step5_status",
56
- "step6_status",
57
- "step7_status",
58
- "step8_status",
59
- "step9_status",
60
- "step10_status",
61
- "delegation_receipts",
62
- "blocked_reason",
63
- )
64
- VALID_STEP_STATUS = {
65
- "not-applicable",
66
- "pending",
67
- "delegated",
68
- "verified",
69
- "blocked",
70
- "not_started",
71
- "in_progress",
72
- "completed",
73
- }
74
- VALID_BLOCKED_REASONS = {
75
- "none",
76
- "spawn_agent_unavailable",
77
- "delegation_launch_failed",
78
- "delegate_no_receipt",
79
- "delegate_contract_incomplete",
80
- "validator_failed",
81
- "user_requested_stop",
82
- }
83
- REQUIRED_RECEIPT_KEYS = (
84
- "step",
85
- "agent_name",
86
- "agent_id",
87
- "skill_source",
88
- "started_at",
89
- "completed_at",
90
- "result_signal",
91
- "artifact_paths",
92
- )
93
- PROMOTION_RECEIPT_NAMESPACE_KEY = "promotion"
94
- PROMOTION_RECEIPT_KEYS = (
95
- "potential_entry",
96
- "issue",
97
- "feature_folder",
98
- )
99
- REMEDIATION_LOOP_KEY = "remediation_loop"
100
- REMEDIATION_CYCLES_KEY = "cycles"
101
- # Execution statuses that may only be recorded once a cycle's preflight gate has
102
- # cleared; recording any of these before preflight clears is a malformed cycle.
103
- EXECUTION_STATUSES_REQUIRING_CLEAR_PREFLIGHT = {
104
- "in_progress",
105
- "complete",
106
- "failed",
107
- }
108
- PREFLIGHT_CLEARED_STATUS = "clear"
109
-
110
-
111
- def _validate_remediation_cycle(index: int, cycle: dict[str, Any]) -> list[str]:
112
- """Validate the three invariants for one remediation cycle.
113
-
114
- Purpose:
115
- Enforce the orchestrator-state remediation-cycle invariants documented
116
- in `.claude/rules/orchestrator-state.md` for a single cycle object:
117
- non-empty `plan_path`, execution only after a cleared preflight, and a
118
- satisfied exit gate only with zero blocking findings.
119
-
120
- Args:
121
- index (int): Zero-based position of this cycle within the
122
- `remediation_loop.cycles` array, used for error context.
123
- cycle (dict[str, Any]): The raw cycle object extracted from the
124
- checkpoint JSON.
125
-
126
- Returns:
127
- list[str]: One error string per violated invariant; an empty list when
128
- the cycle satisfies all three invariants.
129
-
130
- Raises:
131
- None.
132
-
133
- Side Effects:
134
- None.
135
- """
136
-
137
- errors: list[str] = []
138
-
139
- # Invariant 1: plan_path must be a non-empty, non-whitespace string.
140
- plan_path = cycle.get("plan_path")
141
- if not isinstance(plan_path, str) or not plan_path.strip():
142
- errors.append(
143
- f"Checkpoint remediation cycle #{index} plan_path must be a "
144
- "non-empty string."
145
- )
146
-
147
- # Invariant 2: an execution status in the blocked set requires that the
148
- # cycle's preflight gate reports exactly the cleared status.
149
- execution_status = cycle.get("execution_status")
150
- if execution_status in EXECUTION_STATUSES_REQUIRING_CLEAR_PREFLIGHT:
151
- preflight = cycle.get("preflight")
152
- # Read the nested preflight final status defensively; a missing or
153
- # non-object preflight cannot satisfy the cleared requirement.
154
- preflight_status: object = (
155
- cast("dict[str, Any]", preflight).get("final_status")
156
- if isinstance(preflight, dict)
157
- else None
158
- )
159
- if preflight_status != PREFLIGHT_CLEARED_STATUS:
160
- errors.append(
161
- f"Checkpoint remediation cycle #{index} execution_status is "
162
- f"{execution_status} but preflight.final_status is not 'clear'."
163
- )
164
-
165
- # Invariant 3: a satisfied exit gate requires zero blocking findings.
166
- if cycle.get("exit_condition_met") is True and cycle.get("blocking_count") != 0:
167
- errors.append(
168
- f"Checkpoint remediation cycle #{index} exit_condition_met is true "
169
- "but blocking_count is not 0."
170
- )
171
-
172
- return errors
173
-
174
-
175
- def _validate_remediation_loop(remediation_loop: object) -> list[str]:
176
- """Validate the remediation-loop structure and each of its cycles.
177
-
178
- Purpose:
179
- Apply the additive remediation-cycle invariants only when a checkpoint
180
- carries a `remediation_loop`. When the structure is absent, malformed,
181
- or has no cycles, the function produces no errors so existing
182
- step-based checkpoints validate unchanged.
183
-
184
- Args:
185
- remediation_loop (object): The raw value of the checkpoint's top-level
186
- `remediation_loop` key.
187
-
188
- Returns:
189
- list[str]: Validation errors collected across all cycles; empty when no
190
- cycles are present or the structure carries no cycle objects.
191
-
192
- Raises:
193
- None.
194
-
195
- Side Effects:
196
- None.
197
- """
198
-
199
- errors: list[str] = []
200
-
201
- # A non-object remediation_loop carries no cycles to validate; treat it as
202
- # nothing to enforce rather than fabricating a structural error here.
203
- if not isinstance(remediation_loop, dict):
204
- return errors
205
- loop_map = cast("dict[str, Any]", remediation_loop)
206
-
207
- cycles = loop_map.get(REMEDIATION_CYCLES_KEY)
208
- if not isinstance(cycles, list):
209
- return errors
210
- cycle_list = cast("list[object]", cycles)
211
-
212
- # Validate each cycle independently so callers receive a complete error
213
- # list instead of stopping at the first malformed cycle.
214
- for index, cycle in enumerate(cycle_list):
215
- if not isinstance(cycle, dict):
216
- errors.append(f"Checkpoint remediation cycle #{index} must be an object.")
217
- continue
218
- errors.extend(_validate_remediation_cycle(index, cast("dict[str, Any]", cycle)))
219
-
220
- return errors
221
-
222
-
223
- def _validate_list_delegation_receipts(receipts: list[object]) -> list[str]:
224
- """Validate the legacy list-based delegation receipt payload.
225
-
226
- Purpose:
227
- Preserve compatibility with older checkpoints that store delegation
228
- receipts as a list of receipt objects.
229
-
230
- Args:
231
- receipts (list[object]): Raw receipt payload extracted from the
232
- checkpoint JSON.
233
-
234
- Returns:
235
- list[str]: Validation errors for any malformed receipt objects.
236
-
237
- Raises:
238
- None.
239
-
240
- Side Effects:
241
- None.
242
- """
243
-
244
- errors: list[str] = []
245
-
246
- # Validate each legacy receipt independently so callers receive a complete
247
- # error list instead of stopping at the first malformed item.
248
- for index, receipt in enumerate(receipts):
249
- if not isinstance(receipt, dict):
250
- errors.append(f"Checkpoint delegation receipt #{index} must be an object.")
251
- continue
252
- for key in REQUIRED_RECEIPT_KEYS:
253
- if key not in receipt:
254
- errors.append(
255
- f"Checkpoint delegation receipt #{index} missing key: {key}"
256
- )
257
- artifact_paths = cast("dict[str, Any]", receipt).get("artifact_paths")
258
- if artifact_paths is not None and not isinstance(artifact_paths, list):
259
- errors.append(
260
- "Checkpoint delegation receipt "
261
- f"#{index} artifact_paths must be a list."
262
- )
263
-
264
- return errors
265
-
266
-
267
- def _validate_namespaced_delegation_receipts(receipts: dict[str, Any]) -> list[str]:
268
- """Validate the additive object namespace form of delegation receipts.
269
-
270
- Purpose:
271
- Enforce the reviewed ``delegation_receipts.promotion.*`` contract while
272
- keeping the raw receipt payloads opaque to the validator.
273
-
274
- Args:
275
- receipts (dict[str, Any]): Object-form receipt payload extracted from
276
- the checkpoint JSON.
277
-
278
- Returns:
279
- list[str]: Validation errors for unsupported object-shape keys.
280
-
281
- Raises:
282
- None.
283
-
284
- Side Effects:
285
- None.
286
- """
287
-
288
- errors: list[str] = []
289
- unsupported_keys = sorted(
290
- key for key in receipts if key != PROMOTION_RECEIPT_NAMESPACE_KEY
291
- )
292
- for key in unsupported_keys:
293
- errors.append(
294
- f"Checkpoint delegation_receipts object contains unsupported key: {key}"
295
- )
296
-
297
- promotion_receipts = receipts.get(PROMOTION_RECEIPT_NAMESPACE_KEY)
298
- if promotion_receipts is None:
299
- return errors
300
- if not isinstance(promotion_receipts, dict):
301
- errors.append(
302
- "Checkpoint delegation_receipts.promotion must be an object namespace."
303
- )
304
- return errors
305
-
306
- promotion_receipt_map = cast("dict[str, Any]", promotion_receipts)
307
- unsupported_promotion_keys = sorted(
308
- key for key in promotion_receipt_map if key not in PROMOTION_RECEIPT_KEYS
309
- )
310
-
311
- # Reject unknown nested keys while leaving the documented raw receipt values
312
- # untouched and unnormalized.
313
- for key in unsupported_promotion_keys:
314
- errors.append(
315
- "Checkpoint delegation_receipts.promotion contains unsupported key: "
316
- f"{key}"
317
- )
318
-
319
- return errors
320
-
321
-
322
- def validate_orchestrator_state_text(
323
- text: str, *, require_complete: bool = False
324
- ) -> list[str]:
325
- """Validate checkpoint schema and completion-state fields.
326
-
327
- Purpose:
328
- Enforce the repository contract for orchestrator-state artifacts before
329
- resume or review workflows rely on the checkpoint contents.
330
-
331
- Args:
332
- text (str): Raw checkpoint JSON text.
333
- require_complete (bool): When True, require all tracked lifecycle states
334
- to be completion-safe.
335
-
336
- Returns:
337
- list[str]: Validation errors for malformed or incomplete checkpoint
338
- state.
339
-
340
- Raises:
341
- None.
342
-
343
- Side Effects:
344
- None.
345
- """
346
-
347
- errors: list[str] = []
348
- try:
349
- state = json.loads(text)
350
- except json.JSONDecodeError as exc:
351
- return [f"Checkpoint is not valid JSON: {exc}"]
352
-
353
- if not isinstance(state, dict):
354
- return ["Checkpoint root must be a JSON object."]
355
- state_map = cast("dict[str, Any]", state)
356
-
357
- # Require the canonical top-level fields before evaluating deeper state and
358
- # receipt invariants.
359
- for key in REQUIRED_STATE_KEYS:
360
- if key not in state_map:
361
- errors.append(f"Checkpoint missing required key: {key}")
362
-
363
- for key in (
364
- "step5_status",
365
- "step6_status",
366
- "step7_status",
367
- "step8_status",
368
- "step9_status",
369
- "step10_status",
370
- ):
371
- value = state_map.get(key)
372
- if value is not None and value not in VALID_STEP_STATUS:
373
- errors.append(f"Checkpoint has invalid {key}: {value}")
374
-
375
- blocked_reason = state_map.get("blocked_reason")
376
- if blocked_reason is not None and blocked_reason not in VALID_BLOCKED_REASONS:
377
- errors.append(f"Checkpoint has invalid blocked_reason: {blocked_reason}")
378
-
379
- receipts = state_map.get("delegation_receipts")
380
- if receipts is not None:
381
- if isinstance(receipts, list):
382
- errors.extend(
383
- _validate_list_delegation_receipts(cast("list[object]", receipts))
384
- )
385
- elif isinstance(receipts, dict):
386
- errors.extend(
387
- _validate_namespaced_delegation_receipts(
388
- cast("dict[str, Any]", receipts)
389
- )
390
- )
391
- else:
392
- errors.append(
393
- "Checkpoint delegation_receipts must be a list or object namespace."
394
- )
395
-
396
- # Apply the additive remediation-cycle invariants only when the checkpoint
397
- # carries a remediation_loop; absent the key, behavior is unchanged.
398
- if REMEDIATION_LOOP_KEY in state_map:
399
- errors.extend(_validate_remediation_loop(state_map.get(REMEDIATION_LOOP_KEY)))
400
-
401
- # Apply the additive human_interaction invariants only when the checkpoint
402
- # carries a human_interaction key; absent the key, behavior is unchanged.
403
- if HUMAN_INTERACTION_KEY in state_map:
404
- errors.extend(_validate_human_interaction(state_map.get(HUMAN_INTERACTION_KEY)))
405
-
406
- if require_complete:
407
- # Enforce completion-safe lifecycle states only when the caller opts into
408
- # the stricter completion gate.
409
- for key in (
410
- "step5_status",
411
- "step6_status",
412
- "step7_status",
413
- "step8_status",
414
- "step9_status",
415
- "step10_status",
416
- ):
417
- value = state_map.get(key)
418
- if value in {"pending", "blocked"}:
419
- errors.append(
420
- f"Checkpoint completion validation failed: {key} is {value}."
421
- )
422
- if state_map.get("blocked_reason") not in {None, "none"}:
423
- errors.append(
424
- "Checkpoint completion validation failed: blocked_reason is not `none`."
425
- )
426
- errors.extend(validate_routing_contract(state_map))
427
-
428
- return errors