@danmoisan/drm-copilot-mcp 1.0.13 → 1.0.15

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 (131) hide show
  1. package/out/mcp-server.js +3160 -458
  2. package/package.json +1 -1
  3. package/resources/claude-customizations/.claude/agents/epic-orchestrator.md +24 -0
  4. package/resources/claude-customizations/.claude/agents/epic-planner.md +121 -0
  5. package/resources/claude-customizations/.claude/agents/orchestrator.md +2 -2
  6. package/resources/claude-customizations/.claude/hooks/enforce-epic-invocation-origin.ps1 +246 -0
  7. package/resources/claude-customizations/.claude/settings.json +9 -2
  8. package/resources/claude-customizations/.claude/skills/epic-orchestrate/SKILL.md +7 -0
  9. package/resources/claude-customizations/.claude/skills/epic-plan/SKILL.md +189 -0
  10. package/resources/claude-customizations/.claude/skills/epic-run/SKILL.md +38 -0
  11. package/resources/claude-customizations/.claude/skills/orchestrate/SKILL.md +21 -2
  12. package/resources/claude-customizations/pack-manifests/core.json +4 -0
  13. package/resources/codex-and-agents-customizations/.agents/skills/architecture-boundaries/SKILL.md +0 -1
  14. package/resources/codex-and-agents-customizations/.agents/skills/codex-model-routing/SKILL.md +100 -0
  15. package/resources/codex-and-agents-customizations/.agents/skills/codex-model-routing/agents/openai.yaml +4 -0
  16. package/resources/codex-and-agents-customizations/.agents/skills/epic-orchestrate/SKILL.md +170 -0
  17. package/resources/codex-and-agents-customizations/.agents/skills/epic-plan/SKILL.md +230 -0
  18. package/resources/codex-and-agents-customizations/.agents/skills/epic-run/SKILL.md +39 -0
  19. package/resources/codex-and-agents-customizations/.agents/skills/general-code-change/SKILL.md +0 -1
  20. package/resources/codex-and-agents-customizations/.agents/skills/general-unit-test/SKILL.md +0 -1
  21. package/resources/codex-and-agents-customizations/.agents/skills/orchestrate/SKILL.md +89 -9
  22. package/resources/codex-and-agents-customizations/.agents/skills/orchestrator-workflow/SKILL.md +39 -4
  23. package/resources/codex-and-agents-customizations/.agents/skills/quality-tiers/SKILL.md +0 -1
  24. package/resources/codex-and-agents-customizations/.agents/skills/tonality/SKILL.md +0 -1
  25. package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c1.toml +153 -0
  26. package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c2.toml +153 -0
  27. package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c3-elevated.toml +153 -0
  28. package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c3.toml +153 -0
  29. package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor-c4.toml +153 -0
  30. package/resources/codex-and-agents-customizations/.codex/agents/atomic-executor.toml +2 -0
  31. package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c1.toml +95 -0
  32. package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c2.toml +95 -0
  33. package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c3-elevated.toml +95 -0
  34. package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c3.toml +95 -0
  35. package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner-c4.toml +95 -0
  36. package/resources/codex-and-agents-customizations/.codex/agents/atomic-planner.toml +2 -0
  37. package/resources/codex-and-agents-customizations/.codex/agents/csharp-typed-engineer-c1.toml +99 -0
  38. package/resources/codex-and-agents-customizations/.codex/agents/csharp-typed-engineer-c2.toml +99 -0
  39. package/resources/codex-and-agents-customizations/.codex/agents/csharp-typed-engineer-c3-elevated.toml +99 -0
  40. package/resources/codex-and-agents-customizations/.codex/agents/csharp-typed-engineer-c3.toml +99 -0
  41. package/resources/codex-and-agents-customizations/.codex/agents/csharp-typed-engineer-c4.toml +99 -0
  42. package/resources/codex-and-agents-customizations/.codex/agents/csharp-typed-engineer.toml +2 -0
  43. package/resources/codex-and-agents-customizations/.codex/agents/epic-orchestrator.toml +96 -0
  44. package/resources/codex-and-agents-customizations/.codex/agents/epic-planner.toml +97 -0
  45. package/resources/codex-and-agents-customizations/.codex/agents/feature-reviewer-c1.toml +62 -0
  46. package/resources/codex-and-agents-customizations/.codex/agents/feature-reviewer-c2.toml +62 -0
  47. package/resources/codex-and-agents-customizations/.codex/agents/feature-reviewer-c3-elevated.toml +62 -0
  48. package/resources/codex-and-agents-customizations/.codex/agents/feature-reviewer-c3.toml +62 -0
  49. package/resources/codex-and-agents-customizations/.codex/agents/feature-reviewer-c4.toml +62 -0
  50. package/resources/codex-and-agents-customizations/.codex/agents/feature-reviewer.toml +2 -0
  51. package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c1.toml +200 -0
  52. package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c2.toml +200 -0
  53. package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c3-elevated.toml +200 -0
  54. package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c3.toml +200 -0
  55. package/resources/codex-and-agents-customizations/.codex/agents/orchestrator-c4.toml +200 -0
  56. package/resources/codex-and-agents-customizations/.codex/agents/orchestrator.toml +53 -3
  57. package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c1.toml +110 -0
  58. package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c2.toml +110 -0
  59. package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c3-elevated.toml +110 -0
  60. package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c3.toml +110 -0
  61. package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer-c4.toml +110 -0
  62. package/resources/codex-and-agents-customizations/.codex/agents/powershell-typed-engineer.toml +2 -0
  63. package/resources/codex-and-agents-customizations/.codex/agents/pr-author-c1.toml +28 -0
  64. package/resources/codex-and-agents-customizations/.codex/agents/pr-author-c2.toml +28 -0
  65. package/resources/codex-and-agents-customizations/.codex/agents/pr-author-c3-elevated.toml +28 -0
  66. package/resources/codex-and-agents-customizations/.codex/agents/pr-author-c3.toml +28 -0
  67. package/resources/codex-and-agents-customizations/.codex/agents/pr-author-c4.toml +28 -0
  68. package/resources/codex-and-agents-customizations/.codex/agents/pr-author.toml +2 -0
  69. package/resources/codex-and-agents-customizations/.codex/agents/prd-feature-c1.toml +55 -0
  70. package/resources/codex-and-agents-customizations/.codex/agents/prd-feature-c2.toml +55 -0
  71. package/resources/codex-and-agents-customizations/.codex/agents/prd-feature-c3-elevated.toml +55 -0
  72. package/resources/codex-and-agents-customizations/.codex/agents/prd-feature-c3.toml +55 -0
  73. package/resources/codex-and-agents-customizations/.codex/agents/prd-feature-c4.toml +55 -0
  74. package/resources/codex-and-agents-customizations/.codex/agents/prd-feature.toml +2 -0
  75. package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c1.toml +102 -0
  76. package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c2.toml +102 -0
  77. package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c3-elevated.toml +102 -0
  78. package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c3.toml +102 -0
  79. package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer-c4.toml +102 -0
  80. package/resources/codex-and-agents-customizations/.codex/agents/python-typed-engineer.toml +2 -0
  81. package/resources/codex-and-agents-customizations/.codex/agents/task-researcher-c1.toml +105 -0
  82. package/resources/codex-and-agents-customizations/.codex/agents/task-researcher-c2.toml +105 -0
  83. package/resources/codex-and-agents-customizations/.codex/agents/task-researcher-c3-elevated.toml +105 -0
  84. package/resources/codex-and-agents-customizations/.codex/agents/task-researcher-c3.toml +105 -0
  85. package/resources/codex-and-agents-customizations/.codex/agents/task-researcher-c4.toml +105 -0
  86. package/resources/codex-and-agents-customizations/.codex/agents/task-researcher.toml +2 -0
  87. package/resources/codex-and-agents-customizations/.codex/agents/typescript-engineer-c1.toml +50 -0
  88. package/resources/codex-and-agents-customizations/.codex/agents/typescript-engineer-c2.toml +50 -0
  89. package/resources/codex-and-agents-customizations/.codex/agents/typescript-engineer-c3-elevated.toml +50 -0
  90. package/resources/codex-and-agents-customizations/.codex/agents/typescript-engineer-c3.toml +50 -0
  91. package/resources/codex-and-agents-customizations/.codex/agents/typescript-engineer-c4.toml +50 -0
  92. package/resources/codex-and-agents-customizations/.codex/agents/typescript-engineer.toml +2 -0
  93. package/resources/codex-and-agents-customizations/.codex/config.toml +202 -37
  94. package/resources/codex-and-agents-customizations/.codex/hooks/authorize-root-epic-invocation.ps1 +236 -0
  95. package/resources/codex-and-agents-customizations/.codex/hooks/check-powershell-test-purity.ps1 +178 -69
  96. package/resources/codex-and-agents-customizations/.codex/hooks/check-python-test-purity.ps1 +92 -19
  97. package/resources/codex-and-agents-customizations/.codex/hooks/codex-agent-profile-attestation.ps1 +199 -0
  98. package/resources/codex-and-agents-customizations/.codex/hooks/codex-authority-store.ps1 +182 -0
  99. package/resources/codex-and-agents-customizations/.codex/hooks/codex-epic-child-launch-attestation.ps1 +129 -0
  100. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-checkpoint-monotonic.ps1 +148 -31
  101. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-codex-model-routing.ps1 +198 -0
  102. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-completion-consistency.ps1 +28 -19
  103. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-child-worktree-binding.ps1 +325 -0
  104. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-merge-gate.ps1 +140 -0
  105. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-planning-only.ps1 +292 -0
  106. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-root-invocation.ps1 +135 -0
  107. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-wave-barrier.ps1 +295 -0
  108. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-worktree-removal-gate.ps1 +151 -0
  109. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-evidence-locations.ps1 +99 -34
  110. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate.ps1 +143 -27
  111. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-powershell-batch-budget.ps1 +88 -43
  112. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-promotion-mcp-only.ps1 +65 -20
  113. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-python-batch-budget.ps1 +88 -42
  114. package/resources/codex-and-agents-customizations/.codex/hooks/record-subagent-routing-attestation.ps1 +414 -0
  115. package/resources/codex-and-agents-customizations/.codex/hooks/validate-bash.ps1 +164 -51
  116. package/resources/codex-and-agents-customizations/.codex/hooks/validate-codex-subagent-routing.ps1 +154 -0
  117. package/resources/codex-and-agents-customizations/.codex/hooks/validate-feature-review-coverage.ps1 +58 -23
  118. package/resources/codex-and-agents-customizations/AGENTS.md +27 -0
  119. package/resources/codex-and-agents-customizations/pack-manifests/core.json +66 -1
  120. package/resources/codex-and-agents-customizations/pack-manifests/csharp-legacy.json +6 -1
  121. package/resources/codex-and-agents-customizations/pack-manifests/csharp-modern.json +6 -1
  122. package/resources/codex-and-agents-customizations/pack-manifests/powershell.json +6 -1
  123. package/resources/codex-and-agents-customizations/pack-manifests/python.json +6 -1
  124. package/resources/codex-and-agents-customizations/pack-manifests/typescript.json +6 -1
  125. package/resources/config/orchestration-routing.json +167 -0
  126. package/resources/powershell/PoshQC/PoshQC.Analyzer.psm1 +1 -1
  127. package/resources/powershell/PoshQC/PoshQC.ScanConfig.psm1 +125 -0
  128. package/resources/powershell/PoshQC/PoshQC.Testing.psm1 +14 -2
  129. package/resources/powershell/PoshQC/PoshQC.psd1 +0 -4
  130. package/resources/powershell/PoshQC/PoshQC.psm1 +26 -6
  131. package/resources/powershell/PoshQC/settings/pester.runsettings.psd1 +34 -18
@@ -0,0 +1,198 @@
1
+ <#
2
+ .SYNOPSIS
3
+ Denies covered subagent mutations when the actual Codex model/profile does not match routing.
4
+ #>
5
+ [CmdletBinding()]
6
+ param()
7
+
8
+ . (Join-Path $PSScriptRoot 'codex-authority-store.ps1')
9
+ . (Join-Path $PSScriptRoot 'codex-agent-profile-attestation.ps1')
10
+
11
+ $script:CodexModelGateRepositoryRoot = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent
12
+
13
+ function ConvertFrom-CodexModelGateJson {
14
+ [CmdletBinding()]
15
+ param([Parameter(Mandatory)][string] $Raw, [Parameter(Mandatory)][string] $Name)
16
+
17
+ if ([string]::IsNullOrWhiteSpace($Raw)) {
18
+ throw "MODEL_ROUTING_ATTESTATION_BLOCKED: $Name is empty."
19
+ }
20
+ try {
21
+ return $Raw | ConvertFrom-Json -ErrorAction Stop
22
+ } catch {
23
+ throw "MODEL_ROUTING_ATTESTATION_BLOCKED: $Name is malformed JSON: $_"
24
+ }
25
+ }
26
+
27
+ function Get-CodexModelGateAttestationKey {
28
+ [CmdletBinding()]
29
+ [OutputType([string])]
30
+ param([Parameter(Mandatory)][string] $TranscriptPath)
31
+
32
+ $bytes = [System.Text.Encoding]::UTF8.GetBytes($TranscriptPath)
33
+ return [Convert]::ToHexString([System.Security.Cryptography.SHA256]::HashData($bytes)).ToLowerInvariant()
34
+ }
35
+
36
+ function Test-CodexModelGateProfileAttestation {
37
+ [CmdletBinding()]
38
+ [OutputType([bool])]
39
+ param(
40
+ [Parameter(Mandatory)] $Payload,
41
+ [Parameter(Mandatory)] $Attestation,
42
+ [Parameter(Mandatory)][string] $RepositoryRoot
43
+ )
44
+
45
+ if ($Attestation.schema_version -isnot [ValueType] -or
46
+ [int]$Attestation.schema_version -ne 2 -or
47
+ $Attestation.routing_valid -isnot [bool] -or
48
+ -not [bool]$Attestation.routing_valid -or
49
+ -not [string]::IsNullOrWhiteSpace([string]$Attestation.profile_validation_error)) {
50
+ return $false
51
+ }
52
+ foreach ($requiredField in @(
53
+ 'agent_type', 'actual_model', 'expected_model',
54
+ 'actual_reasoning_effort', 'expected_reasoning_effort',
55
+ 'profile_name', 'profile_model', 'profile_path', 'profile_sha256'
56
+ )) {
57
+ if ([string]::IsNullOrWhiteSpace([string]$Attestation.$requiredField)) {
58
+ return $false
59
+ }
60
+ }
61
+ if ([string]$Attestation.profile_sha256 -notmatch '^[0-9a-f]{64}$') {
62
+ return $false
63
+ }
64
+ try {
65
+ $agentProfile = Get-CodexAgentProfileAttestation `
66
+ -RepositoryRoot $RepositoryRoot `
67
+ -AgentType ([string]$Attestation.agent_type)
68
+ } catch {
69
+ return $false
70
+ }
71
+ if (-not (Test-CodexAgentProfileBinding `
72
+ -AgentProfile $agentProfile `
73
+ -AgentType ([string]$Attestation.agent_type) `
74
+ -ActualModel ([string]$Payload.model) `
75
+ -ExpectedModel ([string]$Attestation.expected_model) `
76
+ -ExpectedReasoningEffort ([string]$Attestation.expected_reasoning_effort) `
77
+ -ExpectedProfilePath ([string]$Attestation.profile_path) `
78
+ -ExpectedProfileSha256 ([string]$Attestation.profile_sha256))) {
79
+ return $false
80
+ }
81
+ return [string]$Payload.model -ceq [string]$Attestation.actual_model -and
82
+ [string]$Attestation.profile_name -ceq [string]$agentProfile.profile_name -and
83
+ [string]$Attestation.profile_model -ceq [string]$agentProfile.profile_model -and
84
+ [string]$Attestation.actual_reasoning_effort -ceq [string]$agentProfile.profile_reasoning_effort
85
+ }
86
+
87
+ function Invoke-CodexModelRoutingDecision {
88
+ [CmdletBinding()]
89
+ [OutputType([System.Collections.Specialized.OrderedDictionary])]
90
+ param(
91
+ [Parameter(Mandatory)][string] $PayloadRaw,
92
+ [AllowNull()][AllowEmptyString()][string] $AttestationRaw
93
+ )
94
+
95
+ $payload = ConvertFrom-CodexModelGateJson -Raw $PayloadRaw -Name 'PreToolUse input'
96
+ if ([string]::IsNullOrWhiteSpace($AttestationRaw)) {
97
+ if (-not [string]::IsNullOrWhiteSpace([string]$payload.agent_type) -and
98
+ (Test-CodexModelGateAgentType -AgentType ([string]$payload.agent_type))) {
99
+ return Get-CodexModelGateDenyDecision -Reason (
100
+ "MODEL_ROUTING_ATTESTATION_BLOCKED: routed agent '$($payload.agent_type)' has " +
101
+ 'no matching SubagentStart attestation. Relaunch the exact generated profile.'
102
+ )
103
+ }
104
+ return $null
105
+ }
106
+
107
+ $attestation = ConvertFrom-CodexModelGateJson -Raw $AttestationRaw -Name 'routing attestation'
108
+ if (Test-CodexModelGateProfileAttestation `
109
+ -Payload $payload `
110
+ -Attestation $attestation `
111
+ -RepositoryRoot $script:CodexModelGateRepositoryRoot) {
112
+ return $null
113
+ }
114
+
115
+ return Get-CodexModelGateDenyDecision -Reason (
116
+ "MODEL_ROUTING_ATTESTATION_BLOCKED: agent '$($attestation.agent_type)' has model, " +
117
+ 'reasoning, or profile drift from its persisted deployment receipt. Correct the ' +
118
+ 'receipt and relaunch the exact generated profile.'
119
+ )
120
+ }
121
+
122
+ function Test-CodexModelGateAgentType {
123
+ [CmdletBinding()]
124
+ [OutputType([bool])]
125
+ param([Parameter(Mandatory)][string] $AgentType)
126
+
127
+ return $AgentType -match '-c(?:1|2|3|4|3-elevated)$' -or @(
128
+ 'orchestrator', 'atomic-planner', 'atomic-executor', 'feature-review',
129
+ 'feature-reviewer', 'task-researcher', 'prd-feature', 'pr-author',
130
+ 'python-typed-engineer', 'powershell-typed-engineer',
131
+ 'csharp-typed-engineer', 'typescript-engineer',
132
+ 'epic-planner', 'epic-orchestrator'
133
+ ) -contains $AgentType
134
+ }
135
+
136
+ function Get-CodexModelGateDenyDecision {
137
+ [CmdletBinding()]
138
+ [OutputType([System.Collections.Specialized.OrderedDictionary])]
139
+ param([Parameter(Mandatory)][string] $Reason)
140
+
141
+ return [ordered]@{
142
+ hookSpecificOutput = [ordered]@{
143
+ hookEventName = 'PreToolUse'
144
+ permissionDecision = 'deny'
145
+ permissionDecisionReason = $Reason
146
+ }
147
+ }
148
+ }
149
+
150
+ if ($MyInvocation.InvocationName -eq '.') {
151
+ return
152
+ }
153
+
154
+ try {
155
+ $payloadRaw = [Console]::In.ReadToEnd()
156
+ $payload = ConvertFrom-CodexModelGateJson -Raw $payloadRaw -Name 'PreToolUse input'
157
+ $attestationRaw = ''
158
+ $repositoryRoot = $script:CodexModelGateRepositoryRoot
159
+ $sessionId = [string]$payload.session_id
160
+ $transcriptPath = [string]$payload.transcript_path
161
+ if (-not [string]::IsNullOrWhiteSpace($transcriptPath) -and
162
+ -not [string]::IsNullOrWhiteSpace($sessionId)) {
163
+ $key = Get-CodexModelGateAttestationKey -TranscriptPath $transcriptPath
164
+ $path = Get-CodexAuthorityAttestationPath `
165
+ -RepositoryRoot $repositoryRoot `
166
+ -SessionId $sessionId `
167
+ -AttestationKey $key
168
+ if (Test-Path -LiteralPath $path -PathType Leaf) {
169
+ $attestationRaw = Get-Content -Raw -LiteralPath $path
170
+ }
171
+ }
172
+ if ([string]::IsNullOrWhiteSpace($attestationRaw) -and
173
+ -not [string]::IsNullOrWhiteSpace([string]$payload.agent_id) -and
174
+ -not [string]::IsNullOrWhiteSpace($sessionId)) {
175
+ $stateRoot = Get-CodexAuthorityStateRoot `
176
+ -RepositoryRoot $repositoryRoot `
177
+ -SessionId $sessionId
178
+ foreach ($path in Get-ChildItem -LiteralPath $stateRoot -Filter 'codex-routing-attestation.*.json' -File -ErrorAction SilentlyContinue) {
179
+ $candidate = Get-Content -Raw -LiteralPath $path.FullName
180
+ try {
181
+ if ([string]($candidate | ConvertFrom-Json -ErrorAction Stop).agent_id -eq [string]$payload.agent_id) {
182
+ $attestationRaw = $candidate
183
+ break
184
+ }
185
+ } catch {
186
+ continue
187
+ }
188
+ }
189
+ }
190
+ $decision = Invoke-CodexModelRoutingDecision -PayloadRaw $payloadRaw -AttestationRaw $attestationRaw
191
+ if ($null -ne $decision) {
192
+ $decision | ConvertTo-Json -Compress -Depth 5 | Write-Output
193
+ }
194
+ exit 0
195
+ } catch {
196
+ [Console]::Error.WriteLine([string]$_)
197
+ exit 2
198
+ }
@@ -5,8 +5,8 @@
5
5
  evidence.
6
6
 
7
7
  .DESCRIPTION
8
- Invoked by the Claude Code PreToolUse hook on Write or Edit operations. The
9
- hook activates only when the target file_path normalizes to
8
+ Invoked by the Codex PreToolUse hook on apply_patch operations. The hook
9
+ reads one JSON object from stdin and activates only when the target path normalizes to
10
10
  artifacts/orchestration/orchestrator-state.json.
11
11
 
12
12
  A written checkpoint asserts completion when any of the following hold:
@@ -41,6 +41,9 @@
41
41
  [CmdletBinding()]
42
42
  param()
43
43
 
44
+ # Reuse the adjacent hook's read-only Codex apply_patch adapter.
45
+ . (Join-Path $PSScriptRoot 'enforce-checkpoint-monotonic.ps1')
46
+
44
47
  # Dot-source the shared validation helpers. Guarded so a missing file produces a
45
48
  # clear error and so dot-sourcing this hook in tests loads the helpers too.
46
49
  $script:CompletionHelpersPath = Join-Path $PSScriptRoot 'enforce-completion-helpers.ps1'
@@ -314,7 +317,7 @@ function Resolve-EditedCheckpointContent {
314
317
  function Invoke-CompletionConsistencyDecision {
315
318
  <#
316
319
  .SYNOPSIS
317
- Parses CLAUDE_TOOL_INPUT and returns an allow-or-block decision based on
320
+ Parses mapped Codex tool_input JSON and returns an allow-or-deny decision based on
318
321
  completion-evidence consistency.
319
322
  #>
320
323
  [CmdletBinding()]
@@ -340,7 +343,7 @@ function Invoke-CompletionConsistencyDecision {
340
343
  $toolInput = $ToolInputRaw | ConvertFrom-Json -ErrorAction Stop
341
344
  }
342
345
  catch {
343
- throw "enforce-completion-consistency hook received malformed JSON in CLAUDE_TOOL_INPUT: $_"
346
+ throw "enforce-completion-consistency received malformed mapped tool_input JSON: $_"
344
347
  }
345
348
 
346
349
  $filePath = $toolInput.file_path
@@ -360,9 +363,10 @@ function Invoke-CompletionConsistencyDecision {
360
363
  if (-not $content) {
361
364
  $content = Resolve-EditedCheckpointContent -ToolInput $toolInput -CheckpointReader $CheckpointReader
362
365
  if (-not $content) {
363
- # No content, and the Edit could not be resolved against on-disk
364
- # state (missing file or non-matching patch): defer and allow.
365
- return [ordered]@{ hookSpecificOutput = [ordered]@{ hookEventName = 'PreToolUse'; permissionDecision = 'allow' } }
366
+ return [ordered]@{ hookSpecificOutput = [ordered]@{
367
+ hookEventName = 'PreToolUse'; permissionDecision = 'deny'
368
+ permissionDecisionReason = 'COMPLETION_CONSISTENCY_BLOCKED: the canonical checkpoint cannot be deleted, emptied, or replaced through an unresolved patch.'
369
+ } }
366
370
  }
367
371
  }
368
372
 
@@ -370,9 +374,10 @@ function Invoke-CompletionConsistencyDecision {
370
374
  $payload = ConvertFrom-CheckpointJson -Json $content
371
375
  }
372
376
  catch {
373
- # The content itself is not valid JSON. Let downstream tools surface the
374
- # error rather than blocking with a misleading reason here.
375
- return [ordered]@{ hookSpecificOutput = [ordered]@{ hookEventName = 'PreToolUse'; permissionDecision = 'allow' } }
377
+ return [ordered]@{ hookSpecificOutput = [ordered]@{
378
+ hookEventName = 'PreToolUse'; permissionDecision = 'deny'
379
+ permissionDecisionReason = 'COMPLETION_CONSISTENCY_BLOCKED: the canonical checkpoint must remain valid JSON.'
380
+ } }
376
381
  }
377
382
 
378
383
  if (-not (Test-CompletionAsserted -Payload $payload)) {
@@ -404,13 +409,17 @@ if ($MyInvocation.InvocationName -eq '.') {
404
409
  }
405
410
 
406
411
  try {
407
- $decision = Invoke-CompletionConsistencyDecision -ToolInputRaw $env:CLAUDE_TOOL_INPUT
408
- }
409
- catch {
410
- Write-Error $_
411
- exit 1
412
+ $payload = ConvertFrom-CodexCheckpointHookPayload -PayloadRaw ([Console]::In.ReadToEnd())
413
+ foreach ($toolInput in @(ConvertTo-CodexApplyPatchCheckpointInput -Payload $payload)) {
414
+ $toolInputRaw = $toolInput | ConvertTo-Json -Compress -Depth 20
415
+ $decision = Invoke-CompletionConsistencyDecision -ToolInputRaw $toolInputRaw
416
+ if ($decision.hookSpecificOutput.permissionDecision -eq 'deny') {
417
+ $decision | ConvertTo-Json -Compress -Depth 5 | Write-Output
418
+ exit 0
419
+ }
420
+ }
421
+ exit 0
422
+ } catch {
423
+ [Console]::Error.WriteLine([string]$_)
424
+ exit 2
412
425
  }
413
-
414
- $decision | ConvertTo-Json -Compress -Depth 5 | Write-Output
415
-
416
- exit 0
@@ -0,0 +1,325 @@
1
+ <#
2
+ .SYNOPSIS
3
+ Denies epic-child mutations outside the launch-authorized worktree and profile.
4
+
5
+ .DESCRIPTION
6
+ A launcher-created environment attestation activates this guard. The guard fails closed when
7
+ the hook repository root, payload working directory, live branch, immutable launch receipt,
8
+ specification hash, deployment profile, model, reasoning effort, or delegation differs from
9
+ the launch authorization. Every drm-copilot MCP call must name the authorized workspace_root.
10
+ #>
11
+ [CmdletBinding()]
12
+ param()
13
+
14
+ $contractPath = Join-Path (Split-Path $PSScriptRoot -Parent) 'scripts/epic-child-launch-contract.ps1'
15
+ if (Test-Path -LiteralPath $contractPath -PathType Leaf) {
16
+ . $contractPath
17
+ }
18
+
19
+ function ConvertFrom-CodexChildGuardJson {
20
+ [CmdletBinding()]
21
+ param([Parameter(Mandatory)][string] $Raw, [Parameter(Mandatory)][string] $Name)
22
+
23
+ if ([string]::IsNullOrWhiteSpace($Raw)) {
24
+ throw "EPIC_WORKTREE_BINDING_BLOCKED: $Name is empty."
25
+ }
26
+ try {
27
+ return $Raw | ConvertFrom-Json -Depth 32 -ErrorAction Stop
28
+ } catch {
29
+ throw "EPIC_WORKTREE_BINDING_BLOCKED: $Name is malformed JSON: $_"
30
+ }
31
+ }
32
+
33
+ function Get-CodexChildGuardCanonicalPath {
34
+ [CmdletBinding()]
35
+ [OutputType([string])]
36
+ param([Parameter(Mandatory)][string] $Path, [Parameter(Mandatory)][string] $BasePath)
37
+
38
+ $candidate = if ([System.IO.Path]::IsPathFullyQualified($Path)) {
39
+ $Path
40
+ } else {
41
+ Join-Path $BasePath $Path
42
+ }
43
+ return [System.IO.Path]::GetFullPath($candidate).TrimEnd(
44
+ [System.IO.Path]::DirectorySeparatorChar,
45
+ [System.IO.Path]::AltDirectorySeparatorChar
46
+ )
47
+ }
48
+
49
+ function Get-CodexChildGuardDenyDecision {
50
+ [CmdletBinding()]
51
+ [OutputType([System.Collections.Specialized.OrderedDictionary])]
52
+ param([Parameter(Mandatory)][string] $Reason)
53
+
54
+ return [ordered]@{
55
+ hookSpecificOutput = [ordered]@{
56
+ hookEventName = 'PreToolUse'
57
+ permissionDecision = 'deny'
58
+ permissionDecisionReason = "EPIC_WORKTREE_BINDING_BLOCKED: $Reason"
59
+ }
60
+ }
61
+ }
62
+
63
+ function Test-CodexChildGuardMutation {
64
+ [CmdletBinding()]
65
+ [OutputType([bool])]
66
+ param([Parameter(Mandatory)] $Payload)
67
+
68
+ $toolName = [string]$Payload.tool_name
69
+ if ($toolName -in @('apply_patch', 'Edit', 'Write')) {
70
+ return $true
71
+ }
72
+ if ($toolName -like 'mcp__*') {
73
+ return $true
74
+ }
75
+ if ($toolName -in @('Bash', 'shell_command')) {
76
+ return $true
77
+ }
78
+ return $false
79
+ }
80
+
81
+ function Test-CodexChildGuardProtectedPathMutation {
82
+ [CmdletBinding()]
83
+ [OutputType([bool])]
84
+ param(
85
+ [Parameter(Mandatory)] $Payload,
86
+ [Parameter(Mandatory)][string] $ReceiptPath,
87
+ [Parameter(Mandatory)][string] $SpecPath,
88
+ [Parameter(Mandatory)][string] $ProfilePath,
89
+ [Parameter(Mandatory)][string] $WorktreePath
90
+ )
91
+
92
+ if (-not (Test-CodexChildGuardMutation -Payload $Payload)) {
93
+ return $false
94
+ }
95
+ $inputText = (($Payload.tool_input | ConvertTo-Json -Depth 16 -Compress) -replace '\\+', '/').ToLowerInvariant()
96
+ if ($inputText -match 'codex_epic_child|(?:^|[/"''])(?:\.codex|\.agents)(?:[/"'']|$)|agents\.md|orchestration-routing\.json') {
97
+ return $true
98
+ }
99
+ foreach ($path in @(
100
+ $ReceiptPath, (Split-Path $ReceiptPath -Parent), $SpecPath, $ProfilePath,
101
+ (Join-Path $WorktreePath '.codex/config.toml'),
102
+ (Join-Path $WorktreePath '.codex/hooks'),
103
+ (Join-Path $WorktreePath '.codex/scripts'),
104
+ (Join-Path $WorktreePath '.agents'),
105
+ (Join-Path $WorktreePath 'AGENTS.md'),
106
+ (Join-Path $WorktreePath 'config/orchestration-routing.json')
107
+ )) {
108
+ $normalized = $path.Replace('\', '/').ToLowerInvariant()
109
+ if ($inputText.Contains($normalized) -or $inputText.Contains((Split-Path $normalized -Leaf))) {
110
+ return $true
111
+ }
112
+ }
113
+ return $false
114
+ }
115
+
116
+ function Test-CodexChildGuardNestedCodexInvocation {
117
+ [OutputType([bool])]
118
+ param([Parameter(Mandatory)] $Payload)
119
+ if ([string]$Payload.tool_name -notin @('Bash', 'shell_command')) {
120
+ return $false
121
+ }
122
+ $command = [string]$Payload.tool_input.command
123
+ return $command -match '(?i)(?:^|[\s;&|`"''])codex(?:\.exe|\.cmd|\.ps1)?(?:[\s;&|`"'']|$)|@openai[/\\]codex|codex\.js'
124
+ }
125
+
126
+ function Test-CodexChildGuardAttestation {
127
+ [CmdletBinding()]
128
+ [OutputType([string[]])]
129
+ param(
130
+ [Parameter(Mandatory)] $Payload,
131
+ [Parameter(Mandatory)] $Receipt,
132
+ [Parameter(Mandatory)] $Attestation,
133
+ [Parameter(Mandatory)][string] $HookRepositoryRoot,
134
+ [Parameter(Mandatory)][string] $LiveBranch,
135
+ [Parameter(Mandatory)][string] $ActualSpecSha256,
136
+ [Parameter(Mandatory)][string] $ActualProfileSha256
137
+ )
138
+
139
+ $errors = [System.Collections.Generic.List[string]]::new()
140
+ $expectedWorktree = Get-CodexChildGuardCanonicalPath -Path ([string]$Receipt.worktree_path) -BasePath $HookRepositoryRoot
141
+ $actualRoot = Get-CodexChildGuardCanonicalPath -Path $HookRepositoryRoot -BasePath $HookRepositoryRoot
142
+ $payloadCwd = if ([string]::IsNullOrWhiteSpace([string]$Payload.cwd)) {
143
+ ''
144
+ } else {
145
+ Get-CodexChildGuardCanonicalPath -Path ([string]$Payload.cwd) -BasePath $actualRoot
146
+ }
147
+ if ($actualRoot -ne $expectedWorktree -or $payloadCwd -ne $expectedWorktree) {
148
+ $errors.Add('payload.cwd, hook repository root, and expected worktree must match exactly.')
149
+ }
150
+ if ([string]$LiveBranch -cne [string]$Receipt.branch_name) {
151
+ $errors.Add('the live Git branch does not match the launch receipt.')
152
+ }
153
+ $pairs = @(
154
+ @('launch_id', 'launch_id'),
155
+ @('delegation_id', 'delegation_id'),
156
+ @('execution_context', 'execution_context'),
157
+ @('agent', 'deployment_agent'),
158
+ @('model', 'model'),
159
+ @('reasoning_effort', 'model_reasoning_effort'),
160
+ @('profile_sha256', 'profile_sha256'),
161
+ @('expected_worktree', 'worktree_path'),
162
+ @('receipt_path', 'receipt_path'),
163
+ @('spec_path', 'spec_path')
164
+ )
165
+ foreach ($pair in $pairs) {
166
+ if ([string]$Attestation.($pair[0]) -cne [string]$Receipt.($pair[1])) {
167
+ $errors.Add("environment attestation $($pair[0]) does not match the launch receipt.")
168
+ }
169
+ }
170
+ if (Get-Command Get-CodexChildActiveReceiptErrorList -ErrorAction SilentlyContinue) {
171
+ foreach ($receiptError in @(Get-CodexChildActiveReceiptErrorList -Receipt $Receipt `
172
+ -ExpectedSessionId ([string]$Payload.session_id))) {
173
+ $errors.Add($receiptError)
174
+ }
175
+ } else {
176
+ $errors.Add('launch receipt validator is unavailable.')
177
+ }
178
+ if ([string]$ActualSpecSha256 -ne [string]$Receipt.spec_sha256) {
179
+ $errors.Add('the immutable launch specification hash has changed.')
180
+ }
181
+ $expectedProfile = Join-Path $expectedWorktree ".codex/agents/$($Receipt.deployment_agent).toml"
182
+ if ((Get-CodexChildGuardCanonicalPath -Path ([string]$Receipt.profile_path) -BasePath $expectedWorktree) -ne
183
+ (Get-CodexChildGuardCanonicalPath -Path $expectedProfile -BasePath $expectedWorktree) -or
184
+ [string]$ActualProfileSha256 -ne [string]$Receipt.profile_sha256) {
185
+ $errors.Add('the checked-in deployment profile path or hash has changed.')
186
+ }
187
+ if (-not [string]::IsNullOrWhiteSpace([string]$Payload.model) -and
188
+ [string]$Payload.model -cne [string]$Receipt.model) {
189
+ $errors.Add('the hook payload model does not match the launch receipt.')
190
+ }
191
+ if (-not [string]::IsNullOrWhiteSpace([string]$Payload.agent_type) -and
192
+ [string]$Payload.agent_type -cne [string]$Receipt.deployment_agent) {
193
+ $errors.Add('the hook payload agent type does not match the launch receipt.')
194
+ }
195
+ $payloadReasoning = if (@($Payload.PSObject.Properties.Name) -contains 'model_reasoning_effort') {
196
+ [string]$Payload.model_reasoning_effort
197
+ } elseif (@($Payload.PSObject.Properties.Name) -contains 'reasoning_effort') {
198
+ [string]$Payload.reasoning_effort
199
+ } else {
200
+ ''
201
+ }
202
+ if (-not [string]::IsNullOrWhiteSpace($payloadReasoning) -and
203
+ $payloadReasoning -cne [string]$Receipt.model_reasoning_effort) {
204
+ $errors.Add('the hook payload reasoning effort does not match the launch receipt.')
205
+ }
206
+ return $errors.ToArray()
207
+ }
208
+
209
+ function Invoke-CodexEpicChildGuardDecision {
210
+ [CmdletBinding()]
211
+ [OutputType([System.Collections.Specialized.OrderedDictionary])]
212
+ param(
213
+ [Parameter(Mandatory)][string] $PayloadRaw,
214
+ [AllowNull()][AllowEmptyString()][string] $ReceiptRaw,
215
+ [Parameter(Mandatory)] $Attestation,
216
+ [Parameter(Mandatory)][string] $HookRepositoryRoot,
217
+ [Parameter(Mandatory)][AllowEmptyString()][string] $LiveBranch,
218
+ [Parameter(Mandatory)][AllowEmptyString()][string] $ActualSpecSha256,
219
+ [Parameter(Mandatory)][AllowEmptyString()][string] $ActualProfileSha256
220
+ )
221
+
222
+ $payload = ConvertFrom-CodexChildGuardJson -Raw $PayloadRaw -Name 'PreToolUse input'
223
+ if ([string]::IsNullOrWhiteSpace([string]$Attestation.launch_id)) {
224
+ return $null
225
+ }
226
+ $isMcp = [string]$payload.tool_name -like 'mcp__*'
227
+ $isMutation = Test-CodexChildGuardMutation -Payload $payload
228
+ if (-not $isMcp -and -not $isMutation) {
229
+ return $null
230
+ }
231
+ if ([string]::IsNullOrWhiteSpace($ReceiptRaw)) {
232
+ return Get-CodexChildGuardDenyDecision -Reason 'the launcher authorization receipt is missing.'
233
+ }
234
+ $receipt = ConvertFrom-CodexChildGuardJson -Raw $ReceiptRaw -Name 'launch receipt'
235
+ if ($isMcp -and [string]$payload.tool_name -notlike 'mcp__drm-copilot__*') {
236
+ return Get-CodexChildGuardDenyDecision -Reason 'only repository-scoped drm-copilot MCP tools are authorized for an epic child.'
237
+ }
238
+ if (Test-CodexChildGuardNestedCodexInvocation -Payload $payload) {
239
+ return Get-CodexChildGuardDenyDecision -Reason 'nested Codex execution is prohibited inside an epic child.'
240
+ }
241
+ if ($isMcp) {
242
+ $workspaceRoot = [string]$payload.tool_input.workspace_root
243
+ $expected = Get-CodexChildGuardCanonicalPath -Path ([string]$receipt.worktree_path) -BasePath $HookRepositoryRoot
244
+ if ([string]::IsNullOrWhiteSpace($workspaceRoot) -or
245
+ (Get-CodexChildGuardCanonicalPath -Path $workspaceRoot -BasePath $HookRepositoryRoot) -ne $expected) {
246
+ return Get-CodexChildGuardDenyDecision -Reason 'drm-copilot MCP calls require workspace_root equal to the authorized worktree.'
247
+ }
248
+ }
249
+ if (Test-CodexChildGuardProtectedPathMutation -Payload $payload `
250
+ -ReceiptPath ([string]$receipt.receipt_path) -SpecPath ([string]$receipt.spec_path) `
251
+ -ProfilePath ([string]$receipt.profile_path) -WorktreePath ([string]$receipt.worktree_path)) {
252
+ return Get-CodexChildGuardDenyDecision -Reason 'the launch authorization or committed Codex customizations cannot be mutated by the child.'
253
+ }
254
+ $errors = Test-CodexChildGuardAttestation -Payload $payload -Receipt $receipt -Attestation $Attestation `
255
+ -HookRepositoryRoot $HookRepositoryRoot -LiveBranch $LiveBranch -ActualSpecSha256 $ActualSpecSha256 `
256
+ -ActualProfileSha256 $ActualProfileSha256
257
+ if ($errors.Count -gt 0) {
258
+ return Get-CodexChildGuardDenyDecision -Reason ($errors -join ' ')
259
+ }
260
+ return $null
261
+ }
262
+
263
+ function Invoke-CodexChildGuardGit {
264
+ [CmdletBinding()]
265
+ param([Parameter(Mandatory)][string[]] $GitArgs)
266
+
267
+ return & git @GitArgs 2>&1
268
+ }
269
+
270
+ if ($MyInvocation.InvocationName -eq '.') {
271
+ return
272
+ }
273
+
274
+ try {
275
+ $payloadRaw = [Console]::In.ReadToEnd()
276
+ $repositoryRoot = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent
277
+ $attestation = [pscustomobject]@{
278
+ launch_id = [string]$env:CODEX_EPIC_CHILD_LAUNCH_ID
279
+ receipt_path = [string]$env:CODEX_EPIC_CHILD_LAUNCH_RECEIPT
280
+ spec_path = [string]$env:CODEX_EPIC_CHILD_LAUNCH_SPEC
281
+ expected_worktree = [string]$env:CODEX_EPIC_CHILD_EXPECTED_WORKTREE
282
+ delegation_id = [string]$env:CODEX_EPIC_CHILD_DELEGATION_ID
283
+ execution_context = [string]$env:CODEX_EPIC_CHILD_EXECUTION_CONTEXT
284
+ agent = [string]$env:CODEX_EPIC_CHILD_AGENT
285
+ model = [string]$env:CODEX_EPIC_CHILD_MODEL
286
+ reasoning_effort = [string]$env:CODEX_EPIC_CHILD_REASONING_EFFORT
287
+ profile_sha256 = [string]$env:CODEX_EPIC_CHILD_PROFILE_SHA256
288
+ }
289
+ $receiptRaw = if (-not [string]::IsNullOrWhiteSpace($attestation.receipt_path) -and
290
+ (Test-Path -LiteralPath $attestation.receipt_path -PathType Leaf)) {
291
+ Get-Content -Raw -LiteralPath $attestation.receipt_path
292
+ } else {
293
+ ''
294
+ }
295
+ $actualSpecSha256 = if (-not [string]::IsNullOrWhiteSpace($attestation.spec_path) -and
296
+ (Test-Path -LiteralPath $attestation.spec_path -PathType Leaf)) {
297
+ (Get-FileHash -LiteralPath $attestation.spec_path -Algorithm SHA256).Hash.ToLowerInvariant()
298
+ } else {
299
+ ''
300
+ }
301
+ $receipt = if ([string]::IsNullOrWhiteSpace($receiptRaw)) { $null } else {
302
+ ConvertFrom-CodexChildGuardJson -Raw $receiptRaw -Name 'launch receipt'
303
+ }
304
+ $actualProfileSha256 = if ($null -ne $receipt -and
305
+ -not [string]::IsNullOrWhiteSpace([string]$receipt.profile_path) -and
306
+ (Test-Path -LiteralPath ([string]$receipt.profile_path) -PathType Leaf)) {
307
+ (Get-FileHash -LiteralPath ([string]$receipt.profile_path) -Algorithm SHA256).Hash.ToLowerInvariant()
308
+ } else {
309
+ ''
310
+ }
311
+ $liveBranch = [string](Invoke-CodexChildGuardGit -GitArgs @('-C', $repositoryRoot, 'branch', '--show-current'))
312
+ if ($LASTEXITCODE -ne 0) {
313
+ $liveBranch = ''
314
+ }
315
+ $decision = Invoke-CodexEpicChildGuardDecision -PayloadRaw $payloadRaw -ReceiptRaw $receiptRaw `
316
+ -Attestation $attestation -HookRepositoryRoot $repositoryRoot -LiveBranch $liveBranch.Trim() `
317
+ -ActualSpecSha256 $actualSpecSha256 -ActualProfileSha256 $actualProfileSha256
318
+ if ($null -ne $decision) {
319
+ $decision | ConvertTo-Json -Compress -Depth 5 | Write-Output
320
+ }
321
+ exit 0
322
+ } catch {
323
+ [Console]::Error.WriteLine([string]$_)
324
+ exit 2
325
+ }