@danmoisan/drm-copilot-mcp 1.1.11 → 1.1.14

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 (66) hide show
  1. package/out/mcp-server.js +368 -209
  2. package/package.json +2 -2
  3. package/resources/claude-customizations/.claude/agents/epic-orchestrator.md +1 -1
  4. package/resources/claude-customizations/.claude/agents/epic-planner.md +4 -1
  5. package/resources/claude-customizations/.claude/agents/parallel-planner.md +18 -1
  6. package/resources/claude-customizations/.claude/agents/pr-author.md +15 -0
  7. package/resources/claude-customizations/.claude/hooks/enforce-completion-consistency.ps1 +16 -8
  8. package/resources/claude-customizations/.claude/hooks/enforce-epic-merge-gate-authorization.ps1 +443 -0
  9. package/resources/claude-customizations/.claude/hooks/enforce-epic-merge-gate.ps1 +33 -37
  10. package/resources/claude-customizations/.claude/hooks/enforce-model-routing-receipt.ps1 +120 -5
  11. package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate-epic-scope.ps1 +127 -0
  12. package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate-helpers.ps1 +172 -24
  13. package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate.ps1 +14 -27
  14. package/resources/claude-customizations/.claude/hooks/enforce-parallel-worktree-removal-gate.ps1 +66 -5
  15. package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill-helpers.ps1 +134 -10
  16. package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill.epic-base-branch.ps1 +37 -8
  17. package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill.ps1 +3 -1
  18. package/resources/claude-customizations/.claude/hooks/enforce-prd-feature-before-planner-helpers.ps1 +332 -0
  19. package/resources/claude-customizations/.claude/hooks/enforce-prd-feature-before-planner.ps1 +211 -182
  20. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadius.psm1 +43 -6
  21. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusScheduling.psm1 +486 -0
  22. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusValidation.psm1 +5 -2
  23. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusWriteIntent.psm1 +446 -0
  24. package/resources/claude-customizations/.claude/lib/codex-routing/CodexDeployment.psm1 +1 -0
  25. package/resources/claude-customizations/.claude/lib/worktree-resolution/EpicScopeReadiness.psm1 +176 -0
  26. package/resources/claude-customizations/.claude/lib/worktree-resolution/EpicScopeResolution.psm1 +369 -0
  27. package/resources/claude-customizations/.claude/lib/worktree-resolution/WorktreeItemResolution.psm1 +392 -0
  28. package/resources/claude-customizations/.claude/lib/worktree-resolution/WorktreeResolution.psm1 +500 -0
  29. package/resources/claude-customizations/.claude/lib/worktree-resolution/WorktreeTargetResolution.psm1 +347 -0
  30. package/resources/claude-customizations/.claude/rules/orchestrator-state.md +27 -0
  31. package/resources/claude-customizations/.claude/rules/parallel-orchestration.md +150 -2
  32. package/resources/claude-customizations/.claude/rules/plan-acceptance-gates.md +5 -5
  33. package/resources/claude-customizations/.claude/skills/cleanup-merged-worktrees/SKILL.md +12 -0
  34. package/resources/claude-customizations/.claude/skills/epic-orchestrate/SKILL.md +14 -1
  35. package/resources/claude-customizations/.claude/skills/epic-plan/SKILL.md +15 -0
  36. package/resources/claude-customizations/.claude/skills/orchestrate/SKILL.md +4 -1
  37. package/resources/claude-customizations/.claude/skills/parallel-add/SKILL.md +32 -17
  38. package/resources/claude-customizations/.claude/skills/parallel-orchestrate/SKILL.md +39 -6
  39. package/resources/claude-customizations/.claude/skills/parallel-plan/SKILL.md +39 -10
  40. package/resources/claude-customizations/.claude/skills/pr-author/SKILL.md +15 -0
  41. package/resources/claude-customizations/.claude/skills/quota-throttling/SKILL.md +221 -0
  42. package/resources/claude-customizations/config/blast-radius.json +14 -1
  43. package/resources/claude-customizations/pack-manifests/core.json +11 -0
  44. package/resources/codex-and-agents-customizations/.agents/skills/codex-model-routing/SKILL.md +13 -14
  45. package/resources/codex-and-agents-customizations/.codex/config.toml +1 -1
  46. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-merge-gate.ps1 +193 -0
  47. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-epic-planning-only.ps1 +15 -5
  48. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate-epic-resolution.ps1 +493 -0
  49. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate-epic-scope.ps1 +189 -0
  50. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate-helpers.ps1 +172 -24
  51. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate.ps1 +14 -27
  52. package/resources/codex-and-agents-customizations/.codex/scripts/Resolve-CodexDeployment.ps1 +87 -0
  53. package/resources/codex-and-agents-customizations/.codex/scripts/Resolve-CodexTopology.ps1 +96 -0
  54. package/resources/codex-and-agents-customizations/.codex/scripts/codex-routing-cli-common.ps1 +370 -0
  55. package/resources/codex-and-agents-customizations/.codex/scripts/epic-child-launch-contract.ps1 +475 -0
  56. package/resources/codex-and-agents-customizations/.codex/scripts/epic-child-launch-runtime.ps1 +445 -0
  57. package/resources/codex-and-agents-customizations/.codex/scripts/epic-child-persistence-runtime.ps1 +118 -0
  58. package/resources/codex-and-agents-customizations/.codex/scripts/epic-child-sandbox-preflight.ps1 +69 -0
  59. package/resources/codex-and-agents-customizations/.codex/scripts/launch-epic-child-wave.ps1 +471 -0
  60. package/resources/codex-and-agents-customizations/.codex/scripts/post-codex-worktree-session.ps1 +280 -0
  61. package/resources/codex-and-agents-customizations/.codex/scripts/resume-epic-child.ps1 +263 -0
  62. package/resources/codex-and-agents-customizations/.codex-variants/csharp-legacy/agents/csharp-typed-engineer.toml +2 -1
  63. package/resources/codex-and-agents-customizations/pack-manifests/core.json +20 -1
  64. package/resources/lib/codex-routing/CodexDeployment.psm1 +315 -0
  65. package/resources/lib/codex-routing/CodexTopology.psm1 +394 -0
  66. package/resources/powershell/PoshQC/settings/pester.runsettings.psd1 +37 -0
@@ -0,0 +1,445 @@
1
+ # Git, trust, and process-configuration helpers for epic-child launches and resumes.
2
+
3
+ $script:CodexChildTrustedSurfaces = @(
4
+ '.codex',
5
+ '.agents',
6
+ 'AGENTS.md',
7
+ 'config/orchestration-routing.json'
8
+ )
9
+
10
+ function Invoke-CodexChildGit {
11
+ [CmdletBinding()]
12
+ [OutputType([object[]])]
13
+ param([Parameter(Mandatory)][string[]] $GitArgs)
14
+ $output = @(& git @GitArgs 2>&1)
15
+ if ($LASTEXITCODE -ne 0) {
16
+ throw "EPIC_CHILD_LAUNCH_BLOCKED: git $($GitArgs -join ' ') failed: $($output -join ' ')"
17
+ }
18
+ return $output
19
+ }
20
+
21
+ function Test-CodexChildGit {
22
+ [CmdletBinding()]
23
+ [OutputType([bool])]
24
+ param([Parameter(Mandatory)][string[]] $GitArgs)
25
+ & git @GitArgs *> $null
26
+ return $LASTEXITCODE -eq 0
27
+ }
28
+
29
+ function Get-CodexChildGitScalar {
30
+ [OutputType([string])]
31
+ param([Parameter(Mandatory)][string[]] $GitArgs)
32
+ $output = @(Invoke-CodexChildGit -GitArgs $GitArgs)
33
+ if ($output.Count -ne 1 -or [string]::IsNullOrWhiteSpace([string]$output[0])) {
34
+ throw "EPIC_CHILD_LAUNCH_BLOCKED: git $($GitArgs -join ' ') did not return one value."
35
+ }
36
+ return ([string]$output[0]).Trim()
37
+ }
38
+
39
+ function Test-CodexChildCustomizationClean {
40
+ [OutputType([bool])]
41
+ param([AllowEmptyCollection()][string[]] $StatusLines)
42
+ return @($StatusLines | Where-Object { -not [string]::IsNullOrWhiteSpace($_) }).Count -eq 0
43
+ }
44
+
45
+ function Set-CodexChildReceiptState {
46
+ [CmdletBinding(SupportsShouldProcess)]
47
+ param(
48
+ [Parameter(Mandatory)] $Receipt,
49
+ [Parameter(Mandatory)][ValidateSet('launching', 'active', 'completed', 'failed')][string] $State,
50
+ [AllowEmptyString()][string] $SessionId = '',
51
+ [Nullable[int]] $ExitCode = $null,
52
+ [AllowEmptyString()][string] $FailureReason = ''
53
+ )
54
+ if (-not $PSCmdlet.ShouldProcess([string]$Receipt.receipt_path, "Set epic-child receipt state to '$State'")) {
55
+ return
56
+ }
57
+ $now = [datetimeoffset]::UtcNow.ToString('o'); $Receipt.state = $State
58
+ if ($State -eq 'launching') {
59
+ $Receipt.resume_started_at = $now; $Receipt.expires_at = [datetimeoffset]::UtcNow.AddDays(7).ToString('o')
60
+ } elseif ($State -eq 'active') {
61
+ if ([string]::IsNullOrWhiteSpace($SessionId)) { throw 'EPIC_CHILD_LAUNCH_BLOCKED: active receipt requires a session id.' }
62
+ $Receipt.codex_session_id = $SessionId; $Receipt.session_bound_at = $now
63
+ } elseif ($State -eq 'completed') {
64
+ $Receipt.exit_code = 0; $Receipt.completed_at = $now
65
+ } else {
66
+ $Receipt.exit_code = $(if ($null -eq $ExitCode) { -1 } else { [int]$ExitCode })
67
+ $Receipt.failure_reason = $FailureReason; $Receipt.failed_at = $now
68
+ }
69
+ Write-CodexChildJsonAtomic -Path ([string]$Receipt.receipt_path) -Value $Receipt
70
+ }
71
+
72
+ function Get-CodexChildCommonDirectory {
73
+ [OutputType([string])]
74
+ param([Parameter(Mandatory)][string] $WorktreePath)
75
+ $raw = Get-CodexChildGitScalar -GitArgs @(
76
+ '-C', $WorktreePath, 'rev-parse', '--path-format=absolute', '--git-common-dir'
77
+ )
78
+ return Get-CodexChildCanonicalPath -Path $raw -BasePath $WorktreePath
79
+ }
80
+
81
+ function Get-CodexChildSurfaceObjectMap {
82
+ [OutputType([System.Collections.Specialized.OrderedDictionary])]
83
+ param([Parameter(Mandatory)][string] $RepositoryPath, [Parameter(Mandatory)][string] $Commit)
84
+ $objects = [ordered]@{}
85
+ foreach ($surface in $script:CodexChildTrustedSurfaces) {
86
+ $objects[$surface] = Get-CodexChildGitScalar -GitArgs @(
87
+ '-C', $RepositoryPath, 'rev-parse', "$Commit`:$surface"
88
+ )
89
+ }
90
+ return $objects
91
+ }
92
+
93
+ function Test-CodexChildSurfaceObjectsEqual {
94
+ [OutputType([bool])]
95
+ param([Parameter(Mandatory)] $Expected, [Parameter(Mandatory)] $Actual)
96
+ foreach ($surface in $script:CodexChildTrustedSurfaces) {
97
+ if ([string]$Expected.$surface -ne [string]$Actual.$surface) {
98
+ return $false
99
+ }
100
+ }
101
+ return $true
102
+ }
103
+
104
+ function Get-CodexChildSurfaceFingerprint {
105
+ [OutputType([string])]
106
+ param([Parameter(Mandatory)] $SurfaceObjects)
107
+ $lines = foreach ($surface in $script:CodexChildTrustedSurfaces) {
108
+ "$surface=$([string]$SurfaceObjects.$surface)"
109
+ }
110
+ return Get-CodexChildSha256 -Value ($lines -join "`n")
111
+ }
112
+
113
+ function Get-CodexChildTrustedProfile {
114
+ [CmdletBinding()]
115
+ param(
116
+ [Parameter(Mandatory)][string] $WorktreePath,
117
+ [Parameter(Mandatory)][string] $ChildHead,
118
+ [Parameter(Mandatory)][string] $AgentName
119
+ )
120
+ $relativePath = ".codex/agents/$AgentName.toml"
121
+ $profilePath = Join-Path $WorktreePath $relativePath
122
+ $expectedBlob = Get-CodexChildGitScalar -GitArgs @(
123
+ '-C', $WorktreePath, 'rev-parse', "$ChildHead`:$relativePath"
124
+ )
125
+ $stream = [System.IO.File]::Open($profilePath, [System.IO.FileMode]::Open,
126
+ [System.IO.FileAccess]::Read, [System.IO.FileShare]::Read)
127
+ try {
128
+ $workingBlob = Get-CodexChildGitScalar -GitArgs @(
129
+ '-C', $WorktreePath, 'hash-object', '--', $relativePath
130
+ )
131
+ if ($workingBlob -cne $expectedBlob) {
132
+ throw "EPIC_CHILD_LAUNCH_BLOCKED: generated profile bytes differ from child HEAD: $profilePath"
133
+ }
134
+ $memory = [System.IO.MemoryStream]::new()
135
+ try { $stream.CopyTo($memory); $bytes = $memory.ToArray() } finally { $memory.Dispose() }
136
+ } finally {
137
+ $stream.Dispose()
138
+ }
139
+ $offset = if ($bytes.Length -ge 3 -and $bytes[0] -eq 0xEF -and $bytes[1] -eq 0xBB -and $bytes[2] -eq 0xBF) { 3 } else { 0 }
140
+ try {
141
+ $raw = [System.Text.UTF8Encoding]::new($false, $true).GetString($bytes, $offset, $bytes.Length - $offset)
142
+ } catch {
143
+ throw "EPIC_CHILD_LAUNCH_BLOCKED: generated profile is not valid UTF-8: $profilePath"
144
+ }
145
+ return [pscustomobject]@{
146
+ Path = $profilePath; Raw = $raw
147
+ Sha256 = [Convert]::ToHexString([System.Security.Cryptography.SHA256]::HashData($bytes)).ToLowerInvariant()
148
+ }
149
+ }
150
+
151
+ function Get-CodexChildRuntimeContext {
152
+ [CmdletBinding()]
153
+ param([Parameter(Mandatory)] $Spec, [Parameter(Mandatory)][string] $RepositoryRoot)
154
+ $rootTop = Get-CodexChildGitScalar -GitArgs @('-C', $RepositoryRoot, 'rev-parse', '--show-toplevel')
155
+ $rootTop = Get-CodexChildCanonicalPath -Path $rootTop -BasePath $RepositoryRoot
156
+ if ($rootTop -ne $RepositoryRoot) {
157
+ throw 'EPIC_CHILD_LAUNCH_BLOCKED: RepositoryRoot must be the trusted Git worktree root.'
158
+ }
159
+ $rootCommon = Get-CodexChildCommonDirectory -WorktreePath $RepositoryRoot
160
+ $rootHead = Get-CodexChildGitScalar -GitArgs @('-C', $RepositoryRoot, 'rev-parse', 'HEAD')
161
+ $rootBranch = Get-CodexChildGitScalar -GitArgs @('-C', $RepositoryRoot, 'branch', '--show-current')
162
+ if ($rootBranch -cne [string]$Spec.integration_branch) {
163
+ throw 'EPIC_CHILD_LAUNCH_BLOCKED: RepositoryRoot must be checked out on the integration branch.'
164
+ }
165
+ $rootStatus = @(Invoke-CodexChildGit -GitArgs @(
166
+ '-C', $RepositoryRoot, 'status', '--porcelain=v1', '--untracked-files=all'
167
+ ))
168
+ if (-not (Test-CodexChildCustomizationClean -StatusLines $rootStatus)) {
169
+ throw 'EPIC_CHILD_LAUNCH_BLOCKED: RepositoryRoot must be completely clean at its committed integration tip.'
170
+ }
171
+ $rootSurfaces = Get-CodexChildSurfaceObjectMap -RepositoryPath $RepositoryRoot -Commit $rootHead
172
+ $integrationRef = "refs/heads/$([string]$Spec.integration_branch)"
173
+ $integrationHead = Get-CodexChildGitScalar -GitArgs @(
174
+ '-C', $RepositoryRoot, 'rev-parse', '--verify', $integrationRef
175
+ )
176
+ if ($rootHead -ne $integrationHead) {
177
+ throw 'EPIC_CHILD_LAUNCH_BLOCKED: RepositoryRoot HEAD must equal the integration branch tip.'
178
+ }
179
+ $profiles = [System.Collections.Generic.Dictionary[string, object]]::new([System.StringComparer]::Ordinal)
180
+ $pathComparer = if ($IsWindows) { [System.StringComparer]::OrdinalIgnoreCase } else { [System.StringComparer]::Ordinal }
181
+ $branches = [System.Collections.Generic.Dictionary[string, string]]::new($pathComparer)
182
+ foreach ($entry in @($Spec.launches)) {
183
+ if (-not (Test-CodexChildActiveFeatureFolder -Path ([string]$entry.feature_folder))) {
184
+ throw "EPIC_CHILD_LAUNCH_BLOCKED: feature_folder is not a final active feature identity: $($entry.feature_folder)"
185
+ }
186
+ if ([string]$entry.deployment_agent -notmatch '^[a-z0-9][a-z0-9._-]{0,79}$') {
187
+ throw "EPIC_CHILD_LAUNCH_BLOCKED: deployment_agent is not a safe generated profile name: $($entry.deployment_agent)"
188
+ }
189
+ $worktree = Get-CodexChildCanonicalPath -Path ([string]$entry.worktree_path) -BasePath $RepositoryRoot
190
+ if ($worktree -eq $RepositoryRoot -or -not (Test-Path -LiteralPath $worktree -PathType Container)) {
191
+ throw "EPIC_CHILD_LAUNCH_BLOCKED: child path must be a distinct existing worktree: $worktree"
192
+ }
193
+ $activeRoot = Get-CodexChildCanonicalPath -Path 'docs/features/active' -BasePath $RepositoryRoot
194
+ $rootFeatureFolder = Get-CodexChildCanonicalPath -Path ([string]$entry.feature_folder) -BasePath $RepositoryRoot
195
+ $childFeatureFolder = Get-CodexChildCanonicalPath -Path ([string]$entry.feature_folder) -BasePath $worktree
196
+ $folderComparison = if ($IsWindows) { [System.StringComparison]::OrdinalIgnoreCase } else { [System.StringComparison]::Ordinal }
197
+ if (-not $rootFeatureFolder.StartsWith($activeRoot + [System.IO.Path]::DirectorySeparatorChar, $folderComparison) -or
198
+ -not (Test-Path -LiteralPath $rootFeatureFolder -PathType Container) -or
199
+ -not (Test-Path -LiteralPath $childFeatureFolder -PathType Container)) {
200
+ throw "EPIC_CHILD_LAUNCH_BLOCKED: final active feature_folder must exist in RepositoryRoot and child HEAD: $($entry.feature_folder)"
201
+ }
202
+ $top = Get-CodexChildGitScalar -GitArgs @('-C', $worktree, 'rev-parse', '--show-toplevel')
203
+ if ((Get-CodexChildCanonicalPath -Path $top -BasePath $RepositoryRoot) -ne $worktree) {
204
+ throw "EPIC_CHILD_LAUNCH_BLOCKED: path is not the root of its Git worktree: $worktree"
205
+ }
206
+ if ((Get-CodexChildCommonDirectory -WorktreePath $worktree) -ne $rootCommon) {
207
+ throw "EPIC_CHILD_LAUNCH_BLOCKED: child does not share RepositoryRoot's Git common directory: $worktree"
208
+ }
209
+ $branch = Get-CodexChildGitScalar -GitArgs @('-C', $worktree, 'branch', '--show-current')
210
+ if ($branch -cne [string]$entry.branch_name) {
211
+ throw "EPIC_CHILD_LAUNCH_BLOCKED: child branch does not match the launch entry: $worktree"
212
+ }
213
+ $status = @(Invoke-CodexChildGit -GitArgs @(
214
+ '-C', $worktree, 'status', '--porcelain=v1', '--untracked-files=all'
215
+ ))
216
+ if (-not (Test-CodexChildCustomizationClean -StatusLines $status)) {
217
+ throw "EPIC_CHILD_LAUNCH_BLOCKED: child worktree must be completely clean: $worktree"
218
+ }
219
+ $childHead = Get-CodexChildGitScalar -GitArgs @('-C', $worktree, 'rev-parse', 'HEAD')
220
+ if (-not (Test-CodexChildGit -GitArgs @(
221
+ '-C', $worktree, 'merge-base', '--is-ancestor', $integrationHead, $childHead
222
+ ))) {
223
+ throw "EPIC_CHILD_LAUNCH_BLOCKED: child HEAD does not descend from the integration branch: $worktree"
224
+ }
225
+ $childSurfaces = Get-CodexChildSurfaceObjectMap -RepositoryPath $worktree -Commit $childHead
226
+ if (-not (Test-CodexChildSurfaceObjectsEqual -Expected $rootSurfaces -Actual $childSurfaces)) {
227
+ throw "EPIC_CHILD_LAUNCH_BLOCKED: child customizations differ from trusted RepositoryRoot HEAD: $worktree"
228
+ }
229
+ $agentName = [string]$entry.deployment_agent
230
+ $profileData = Get-CodexChildTrustedProfile -WorktreePath $worktree -ChildHead $childHead -AgentName $agentName
231
+ $profilePath = [string]$profileData.Path; $profileRaw = [string]$profileData.Raw
232
+ $agentProfile = ConvertFrom-CodexAgentProfile -ProfileRaw $profileRaw
233
+ $metadata = [ordered]@{
234
+ profile_path = $profilePath
235
+ profile_sha256 = [string]$profileData.Sha256
236
+ worktree_path = $worktree
237
+ child_head = $childHead
238
+ trusted_repository_root = $RepositoryRoot
239
+ trusted_repository_head = $rootHead
240
+ git_common_directory = $rootCommon
241
+ integration_head = $integrationHead
242
+ trusted_surface_objects = $rootSurfaces
243
+ trusted_surface_sha256 = Get-CodexChildSurfaceFingerprint -SurfaceObjects $rootSurfaces
244
+ }
245
+ foreach ($item in $metadata.GetEnumerator()) {
246
+ $agentProfile | Add-Member -NotePropertyName $item.Key -NotePropertyValue $item.Value
247
+ }
248
+ $profileKey = Get-CodexChildProfileKey -WorktreePath $worktree -AgentName $agentName `
249
+ -RepositoryRoot $RepositoryRoot
250
+ $profiles[$profileKey] = $agentProfile
251
+ $branches[$worktree] = $branch
252
+ }
253
+ return [pscustomobject]@{ Profiles = $profiles; Branches = $branches }
254
+ }
255
+
256
+ function ConvertTo-CodexChildTomlString {
257
+ [OutputType([string])]
258
+ param([Parameter(Mandatory)][string] $Value)
259
+ $escaped = $Value.Replace('\', '\\').Replace('"', '\"').Replace("`b", '\b').
260
+ Replace("`t", '\t').Replace("`n", '\n').Replace("`f", '\f').Replace("`r", '\r')
261
+ return '"' + $escaped + '"'
262
+ }
263
+
264
+ function Get-CodexChildCommandContext {
265
+ [CmdletBinding()]
266
+ param()
267
+ $commands = @(Get-Command codex -All -ErrorAction Stop)
268
+ foreach ($name in @('OPENAI_API_KEY', 'AZURE_OPENAI_API_KEY', 'CODEX_API_KEY', 'OPENAI_ACCESS_TOKEN')) {
269
+ if (-not [string]::IsNullOrWhiteSpace([Environment]::GetEnvironmentVariable($name))) {
270
+ throw "EPIC_CHILD_LAUNCH_BLOCKED: environment-based Codex authentication is not supported for isolated children: $name"
271
+ }
272
+ }
273
+ $commandPath = [string]$commands[0].Source
274
+ $pathComparer = if ($IsWindows) { [System.StringComparer]::OrdinalIgnoreCase } else { [System.StringComparer]::Ordinal }
275
+ $denied = [System.Collections.Generic.HashSet[string]]::new($pathComparer)
276
+ foreach ($command in $commands) {
277
+ if (-not [string]::IsNullOrWhiteSpace([string]$command.Source)) {
278
+ $denied.Add([System.IO.Path]::GetFullPath([string]$command.Source)) | Out-Null
279
+ }
280
+ }
281
+ $commandRoot = Split-Path $commandPath -Parent
282
+ $packageRoot = Join-Path $commandRoot 'node_modules/@openai/codex'
283
+ if (Test-Path -LiteralPath $packageRoot -PathType Container) {
284
+ $denied.Add([System.IO.Path]::GetFullPath($packageRoot)) | Out-Null
285
+ }
286
+ $codexHome = if ([string]::IsNullOrWhiteSpace([string]$env:CODEX_HOME)) {
287
+ Join-Path ([Environment]::GetFolderPath([Environment+SpecialFolder]::UserProfile)) '.codex'
288
+ } else {
289
+ [string]$env:CODEX_HOME
290
+ }
291
+ $authPath = Join-Path $codexHome 'auth.json'
292
+ if (-not (Test-Path -LiteralPath $authPath -PathType Leaf)) {
293
+ throw 'EPIC_CHILD_LAUNCH_BLOCKED: isolated children require file-based Codex authentication under CODEX_HOME.'
294
+ }
295
+ $denied.Add([System.IO.Path]::GetFullPath($authPath)) | Out-Null
296
+ $denied.Add([System.IO.Path]::GetFullPath($codexHome)) | Out-Null
297
+ foreach ($name in @('app-server-control', 'app-server-daemon')) {
298
+ $path = Join-Path $codexHome $name
299
+ if (Test-Path -LiteralPath $path) {
300
+ $denied.Add([System.IO.Path]::GetFullPath($path)) | Out-Null
301
+ }
302
+ }
303
+ return [pscustomobject]@{
304
+ CommandPath = $commandPath
305
+ DeniedPaths = [string[]]@($denied | Sort-Object)
306
+ CredentialRoot = [System.IO.Path]::GetFullPath($codexHome)
307
+ OriginalAuthPath = [System.IO.Path]::GetFullPath($authPath)
308
+ }
309
+ }
310
+
311
+ function Get-CodexChildPermissionOverride {
312
+ [OutputType([string])]
313
+ param([Parameter(Mandatory)][string[]] $DeniedPaths)
314
+ $entries = foreach ($path in $DeniedPaths) {
315
+ $key = ConvertTo-CodexChildTomlString -Value $path
316
+ "$key = 'deny'"
317
+ }
318
+ return "permissions.epic-child-workspace={ extends='orchestrator-workspace', filesystem={ $($entries -join ', ') } }"
319
+ }
320
+
321
+ function Get-CodexChildProjectsOverride {
322
+ [OutputType([string])]
323
+ param([Parameter(Mandatory)][string] $WorktreePath)
324
+ $path = ConvertTo-CodexChildTomlString -Value $WorktreePath
325
+ return "projects={ $path = { trust_level='trusted' } }"
326
+ }
327
+
328
+ function Get-CodexChildAuthorityRoot {
329
+ [OutputType([string])]
330
+ param()
331
+ $base = if ($IsWindows) { [Environment]::GetFolderPath('LocalApplicationData') } else {
332
+ [System.IO.Path]::GetTempPath()
333
+ }
334
+ return [System.IO.Path]::GetFullPath((Join-Path $base 'OpenAI/CodexEpicChildren'))
335
+ }
336
+
337
+ function New-CodexChildIsolatedHome {
338
+ [CmdletBinding(SupportsShouldProcess)]
339
+ [OutputType([string])]
340
+ param(
341
+ [Parameter(Mandatory)][string] $RepositoryRoot,
342
+ [Parameter(Mandatory)][string] $WaveId,
343
+ [Parameter(Mandatory)][string] $LaunchId,
344
+ [Parameter(Mandatory)][string] $OriginalAuthPath,
345
+ [scriptblock] $PathExists = { param([string] $Path) Test-Path -LiteralPath $Path },
346
+ [scriptblock] $CreateDirectory = { param([string] $Path) [System.IO.Directory]::CreateDirectory($Path) | Out-Null },
347
+ [scriptblock] $CopyFile = { param([string] $Source, [string] $Destination) [System.IO.File]::Copy($Source, $Destination, $false) },
348
+ [scriptblock] $DeleteDirectory = { param([string] $Path) [System.IO.Directory]::Delete($Path, $true) },
349
+ [scriptblock] $AssertAuthority = {
350
+ param([string] $Authority, [string] $Repository)
351
+ Assert-CodexChildAuthorityOutsideRepository -AuthorityPath $Authority -RepositoryRoot $Repository
352
+ }
353
+ )
354
+ $repositoryKey = Get-CodexChildSha256 -Value $RepositoryRoot
355
+ $isolatedHome = [System.IO.Path]::GetFullPath((Join-Path (Get-CodexChildAuthorityRoot) `
356
+ "$repositoryKey/$WaveId/$LaunchId"))
357
+ & $AssertAuthority $isolatedHome $RepositoryRoot
358
+ if (& $PathExists $isolatedHome) {
359
+ throw "EPIC_CHILD_LAUNCH_BLOCKED: isolated CODEX_HOME already exists: $isolatedHome"
360
+ }
361
+ if ($PSCmdlet.ShouldProcess($isolatedHome, 'Create isolated epic-child CODEX_HOME')) {
362
+ try {
363
+ & $CreateDirectory $isolatedHome
364
+ & $CopyFile $OriginalAuthPath (Join-Path $isolatedHome 'auth.json')
365
+ } catch {
366
+ $creationError = $_
367
+ try {
368
+ if (& $PathExists $isolatedHome) {
369
+ & $DeleteDirectory $isolatedHome
370
+ }
371
+ } catch {
372
+ throw "EPIC_CHILD_LAUNCH_BLOCKED: isolated CODEX_HOME creation failed and cleanup also failed: $creationError Cleanup: $_"
373
+ }
374
+ throw $creationError
375
+ }
376
+ }
377
+ return $isolatedHome
378
+ }
379
+
380
+ function Restore-CodexChildIsolatedAuth {
381
+ [CmdletBinding(SupportsShouldProcess)]
382
+ param([Parameter(Mandatory)][string] $HomePath, [Parameter(Mandatory)][string] $OriginalAuthPath)
383
+ $target = Join-Path $HomePath 'auth.json'
384
+ if (Test-Path -LiteralPath $target) {
385
+ throw 'EPIC_CHILD_RESUME_BLOCKED: isolated auth copy already exists before resume.'
386
+ }
387
+ if ($PSCmdlet.ShouldProcess($target, 'Restore isolated Codex authentication')) {
388
+ $source = $null; $destination = $null; $ownsDestination = $false
389
+ try {
390
+ $source = [System.IO.File]::OpenRead($OriginalAuthPath)
391
+ $destination = [System.IO.File]::Open($target, [System.IO.FileMode]::CreateNew,
392
+ [System.IO.FileAccess]::Write, [System.IO.FileShare]::None)
393
+ $ownsDestination = $true
394
+ $source.CopyTo($destination); $destination.Flush($true)
395
+ } catch {
396
+ $copyError = $_
397
+ if ($null -ne $destination) { $destination.Dispose(); $destination = $null }
398
+ if ($null -ne $source) { $source.Dispose(); $source = $null }
399
+ if ($ownsDestination -and (Test-Path -LiteralPath $target)) {
400
+ [System.IO.File]::Delete($target)
401
+ }
402
+ throw $copyError
403
+ } finally {
404
+ if ($null -ne $destination) { $destination.Dispose() }
405
+ if ($null -ne $source) { $source.Dispose() }
406
+ }
407
+ }
408
+ }
409
+
410
+ function Remove-CodexChildIsolatedAuth {
411
+ [CmdletBinding(SupportsShouldProcess)]
412
+ param([Parameter(Mandatory)][string] $HomePath)
413
+ $target = Join-Path $HomePath 'auth.json'
414
+ if ((Test-Path -LiteralPath $target) -and $PSCmdlet.ShouldProcess($target, 'Remove isolated Codex authentication')) {
415
+ [System.IO.File]::Delete($target)
416
+ }
417
+ }
418
+
419
+ function Remove-CodexChildIsolatedHome {
420
+ [CmdletBinding(SupportsShouldProcess)]
421
+ param([Parameter(Mandatory)][string] $HomePath)
422
+ $authorityRoot = Get-CodexChildAuthorityRoot
423
+ $canonical = [System.IO.Path]::GetFullPath($HomePath)
424
+ if (-not $canonical.StartsWith($authorityRoot + [System.IO.Path]::DirectorySeparatorChar,
425
+ [System.StringComparison]::OrdinalIgnoreCase)) {
426
+ throw 'EPIC_CHILD_LAUNCH_BLOCKED: refusing to remove a CODEX_HOME outside launcher authority.'
427
+ }
428
+ if ((Test-Path -LiteralPath $canonical) -and
429
+ $PSCmdlet.ShouldProcess($canonical, 'Remove failed epic-child CODEX_HOME')) {
430
+ [System.IO.Directory]::Delete($canonical, $true)
431
+ }
432
+ }
433
+
434
+ function Get-CodexChildShellEnvironmentOverrideList {
435
+ [OutputType([string[]])]
436
+ param([Parameter(Mandatory)][string] $WorktreePath)
437
+ $isolatedHome = Join-Path $WorktreePath '.codex/epic-child-no-auth'
438
+ return @(
439
+ 'shell_environment_policy.exclude=["OPENAI_API_KEY","AZURE_OPENAI_API_KEY","CODEX_API_KEY","OPENAI_ACCESS_TOKEN"]',
440
+ "shell_environment_policy.set={ CODEX_HOME=$(ConvertTo-CodexChildTomlString -Value $isolatedHome) }"
441
+ )
442
+ }
443
+
444
+ . (Join-Path $PSScriptRoot 'epic-child-persistence-runtime.ps1')
445
+ . (Join-Path $PSScriptRoot 'epic-child-sandbox-preflight.ps1')
@@ -0,0 +1,118 @@
1
+ # Atomic receipt persistence and semantic wave-lock helpers for Codex epic children.
2
+
3
+ function Write-CodexChildJsonCreateNew {
4
+ [CmdletBinding()]
5
+ param(
6
+ [Parameter(Mandatory)][string] $Path,
7
+ [Parameter(Mandatory)] $Value,
8
+ [scriptblock] $EnsureDirectory = {
9
+ param([string] $DirectoryPath)
10
+ [System.IO.Directory]::CreateDirectory($DirectoryPath) | Out-Null
11
+ },
12
+ [scriptblock] $OpenFile = {
13
+ param([string] $FilePath)
14
+ [System.IO.File]::Open($FilePath, [System.IO.FileMode]::CreateNew,
15
+ [System.IO.FileAccess]::Write, [System.IO.FileShare]::None)
16
+ }
17
+ )
18
+ & $EnsureDirectory (Split-Path $Path -Parent)
19
+ $stream = & $OpenFile $Path
20
+ try {
21
+ $writer = [System.IO.StreamWriter]::new($stream, [System.Text.UTF8Encoding]::new($false))
22
+ try {
23
+ $writer.Write(($Value | ConvertTo-Json -Depth 32)); $writer.Flush()
24
+ if ($stream -is [System.IO.FileStream]) { $stream.Flush($true) } else { $stream.Flush() }
25
+ } finally { $writer.Dispose() }
26
+ } finally { $stream.Dispose() }
27
+ }
28
+
29
+ function Write-CodexChildJsonAtomic {
30
+ [CmdletBinding()]
31
+ param([Parameter(Mandatory)][string] $Path, [Parameter(Mandatory)] $Value)
32
+ [System.IO.Directory]::CreateDirectory((Split-Path $Path -Parent)) | Out-Null
33
+ $temporaryPath = "$Path.tmp.$([guid]::NewGuid().ToString('N'))"
34
+ try {
35
+ Write-CodexChildJsonCreateNew -Path $temporaryPath -Value $Value
36
+ [System.IO.File]::Move($temporaryPath, $Path, $true)
37
+ } finally {
38
+ if (Test-Path -LiteralPath $temporaryPath) { Remove-Item -LiteralPath $temporaryPath -Force }
39
+ }
40
+ }
41
+
42
+ function Enter-CodexChildWaveLock {
43
+ [OutputType([System.IO.Stream])]
44
+ param(
45
+ [Parameter(Mandatory)][string] $Path,
46
+ [scriptblock] $OpenLock = {
47
+ param([string] $LockPath)
48
+ [System.IO.File]::Open($LockPath, [System.IO.FileMode]::OpenOrCreate,
49
+ [System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::None)
50
+ }
51
+ )
52
+ try {
53
+ $stream = & $OpenLock $Path
54
+ } catch {
55
+ throw "EPIC_CHILD_LAUNCH_BLOCKED: semantic wave lock is already held: $Path"
56
+ }
57
+ $writer = $null
58
+ try {
59
+ $stream.SetLength(0)
60
+ $writer = [System.IO.StreamWriter]::new($stream, [System.Text.UTF8Encoding]::new($false), 1024, $true)
61
+ $writer.Write("pid=$PID`nacquired_at=$([datetimeoffset]::UtcNow.ToString('o'))")
62
+ $writer.Flush()
63
+ if ($stream -is [System.IO.FileStream]) { $stream.Flush($true) } else { $stream.Flush() }
64
+ $writer.Dispose()
65
+ $writer = $null
66
+ return $stream
67
+ } catch {
68
+ if ($null -ne $writer) { $writer.Dispose() }
69
+ $stream.Dispose()
70
+ throw "EPIC_CHILD_LAUNCH_BLOCKED: semantic wave lock initialization failed: $Path $_"
71
+ }
72
+ }
73
+
74
+ function Get-CodexChildSemanticWaveLockPath {
75
+ [OutputType([string])]
76
+ param([Parameter(Mandatory)] $Spec, [Parameter(Mandatory)][string] $ArtifactRoot)
77
+ $launchRoot = Split-Path $ArtifactRoot -Parent
78
+ $branchKey = (Get-CodexChildSha256 -Value ([string]$Spec.integration_branch)).Substring(0, 16)
79
+ $name = "$($Spec.checkpoint_kind).wave-$([int]$Spec.wave_number).$branchKey.lock"
80
+ return Join-Path (Join-Path $launchRoot '.locks') $name
81
+ }
82
+
83
+ function Assert-CodexChildAuthorityOutsideRepository {
84
+ [CmdletBinding()]
85
+ param(
86
+ [Parameter(Mandatory)][string] $AuthorityPath,
87
+ [Parameter(Mandatory)][string] $RepositoryRoot,
88
+ [scriptblock] $ResolveExistingAncestor = {
89
+ param([string] $Candidate)
90
+ $current = [System.IO.Path]::GetFullPath($Candidate)
91
+ while (-not (Test-Path -LiteralPath $current -PathType Container)) {
92
+ $parent = Split-Path $current -Parent
93
+ if ([string]::IsNullOrWhiteSpace($parent) -or $parent -eq $current) { throw 'no existing authority ancestor.' }
94
+ $current = $parent
95
+ }
96
+ $item = Get-Item -LiteralPath $current -Force
97
+ if (($item.Attributes -band [System.IO.FileAttributes]::ReparsePoint) -ne 0) {
98
+ return $item.ResolveLinkTarget($true).FullName
99
+ }
100
+ return $item.FullName
101
+ },
102
+ [scriptblock] $IsInsideGit = {
103
+ param([string] $Candidate)
104
+ Test-CodexChildGit -GitArgs @('-C', $Candidate, 'rev-parse', '--is-inside-work-tree')
105
+ }
106
+ )
107
+ $comparison = if ($IsWindows) { [System.StringComparison]::OrdinalIgnoreCase } else { [System.StringComparison]::Ordinal }
108
+ $authority = [System.IO.Path]::GetFullPath($AuthorityPath)
109
+ $repository = [System.IO.Path]::GetFullPath($RepositoryRoot)
110
+ if ($authority.Equals($repository, $comparison) -or
111
+ $authority.StartsWith($repository + [System.IO.Path]::DirectorySeparatorChar, $comparison)) {
112
+ throw 'EPIC_CHILD_LAUNCH_BLOCKED: isolated CODEX_HOME authority is inside RepositoryRoot.'
113
+ }
114
+ $ancestor = & $ResolveExistingAncestor $authority
115
+ if (& $IsInsideGit $ancestor) {
116
+ throw 'EPIC_CHILD_LAUNCH_BLOCKED: isolated CODEX_HOME authority resolves inside a Git repository.'
117
+ }
118
+ }
@@ -0,0 +1,69 @@
1
+ # Restrictive sandbox construction and bounded behavioral preflight for Codex epic children.
2
+
3
+ function Get-CodexChildSandboxProbeStartInfo {
4
+ [CmdletBinding()]
5
+ [OutputType([System.Diagnostics.ProcessStartInfo])]
6
+ param(
7
+ [Parameter(Mandatory)][string] $WorktreePath,
8
+ [Parameter(Mandatory)][string] $CodexHomePath,
9
+ [Parameter(Mandatory)][string] $CommandPath,
10
+ [Parameter(Mandatory)][string[]] $DeniedPaths,
11
+ [Parameter(Mandatory)][string] $DeniedProbePath
12
+ )
13
+ if (-not (Test-Path -LiteralPath $DeniedProbePath)) {
14
+ throw 'EPIC_CHILD_LAUNCH_BLOCKED: sandbox deny probe path must exist.'
15
+ }
16
+ $comparison = if ($IsWindows) { [System.StringComparison]::OrdinalIgnoreCase } else { [System.StringComparison]::Ordinal }
17
+ $probeCanonical = [System.IO.Path]::GetFullPath($DeniedProbePath)
18
+ if (-not @($DeniedPaths | Where-Object {
19
+ $probeCanonical.Equals([System.IO.Path]::GetFullPath($_), $comparison)
20
+ })) {
21
+ throw 'EPIC_CHILD_LAUNCH_BLOCKED: sandbox deny probe path is absent from the deny profile.'
22
+ }
23
+ $isShim = $CommandPath.EndsWith('.ps1', [System.StringComparison]::OrdinalIgnoreCase)
24
+ $info = [System.Diagnostics.ProcessStartInfo]::new($(if ($isShim) { 'pwsh' } else { $CommandPath }))
25
+ $info.UseShellExecute = $false; $info.CreateNoWindow = $true
26
+ $info.RedirectStandardOutput = $true; $info.RedirectStandardError = $true
27
+ $info.WorkingDirectory = $WorktreePath; $info.Environment['CODEX_HOME'] = $CodexHomePath
28
+ if ($isShim) { foreach ($item in @('-NoProfile', '-File', $CommandPath)) { $info.ArgumentList.Add($item) } }
29
+ $projects = Get-CodexChildProjectsOverride -WorktreePath $WorktreePath
30
+ $permissions = Get-CodexChildPermissionOverride -DeniedPaths $DeniedPaths
31
+ foreach ($item in @('sandbox', '-C', $WorktreePath, '-P', 'epic-child-workspace',
32
+ '-c', $projects, '-c', $permissions)) { $info.ArgumentList.Add($item) }
33
+ if ($IsWindows) { $info.ArgumentList.Add('-c'); $info.ArgumentList.Add('windows.sandbox="elevated"') }
34
+ $probeScript = @'
35
+ $ErrorActionPreference = 'Stop'
36
+ try { Get-Item -LiteralPath $args[0] -ErrorAction Stop | Out-Null } catch { exit 92 }
37
+ try { Get-Item -LiteralPath $args[1] -ErrorAction Stop | Out-Null; exit 93 } catch { exit 0 }
38
+ '@
39
+ foreach ($item in @('pwsh', '-NoProfile', '-Command', $probeScript, $WorktreePath, $DeniedProbePath)) {
40
+ $info.ArgumentList.Add($item)
41
+ }
42
+ return $info
43
+ }
44
+
45
+ function Invoke-CodexChildSandboxProbe {
46
+ [CmdletBinding()]
47
+ param(
48
+ [Parameter(Mandatory)][System.Diagnostics.ProcessStartInfo] $StartInfo,
49
+ [scriptblock] $NewProcess = { [System.Diagnostics.Process]::new() }
50
+ )
51
+ $process = & $NewProcess; $process.StartInfo = $StartInfo
52
+ if (-not $process.Start()) { throw 'EPIC_CHILD_LAUNCH_BLOCKED: sandbox preflight process did not start.' }
53
+ $outputTask = $process.StandardOutput.ReadToEndAsync(); $errorTask = $process.StandardError.ReadToEndAsync()
54
+ if (-not $process.WaitForExit(15000)) {
55
+ $process.Kill($true); $process.WaitForExit()
56
+ throw 'EPIC_CHILD_LAUNCH_BLOCKED: elevated Windows sandbox preflight timed out after 15 seconds.'
57
+ }
58
+ $output = $outputTask.Result; $errorOutput = $errorTask.Result
59
+ return [pscustomobject]@{ ExitCode = $process.ExitCode; Output = $output; Error = $errorOutput }
60
+ }
61
+
62
+ function Assert-CodexChildSandboxPreflight {
63
+ [CmdletBinding()]
64
+ param([Parameter(Mandatory)][System.Diagnostics.ProcessStartInfo] $StartInfo)
65
+ $result = Invoke-CodexChildSandboxProbe -StartInfo $StartInfo
66
+ if ($result.ExitCode -ne 0) {
67
+ throw "EPIC_CHILD_LAUNCH_BLOCKED: restrictive sandbox preflight failed: $($result.Error.Trim())"
68
+ }
69
+ }