@danmoisan/drm-copilot-mcp 1.0.24 → 1.0.26

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 (39) hide show
  1. package/out/mcp-server.js +504 -174
  2. package/package.json +1 -1
  3. package/resources/claude-customizations/.claude/agents/feature-review.md +5 -3
  4. package/resources/claude-customizations/.claude/agents/parallel-orchestrator.md +11 -4
  5. package/resources/claude-customizations/.claude/agents/parallel-planner.md +5 -2
  6. package/resources/claude-customizations/.claude/hooks/enforce-discovery-artifact-gate.ps1 +28 -8
  7. package/resources/claude-customizations/.claude/hooks/validate-discovery-artifact-gate.ps1 +28 -8
  8. package/resources/claude-customizations/.claude/hooks/validate-orchestrator-output.ps1 +117 -46
  9. package/resources/claude-customizations/.claude/lib/bash/parallel-manifest-validate.sh +115 -3
  10. package/resources/claude-customizations/.claude/lib/codex-routing/CodexDeployment.psm1 +312 -0
  11. package/resources/claude-customizations/.claude/lib/codex-routing/CodexTopology.psm1 +392 -0
  12. package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryValidation.psm1 +500 -0
  13. package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorState.psm1 +58 -67
  14. package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateCheckpointValue.psm1 +383 -0
  15. package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateCodexModelReceipts.psm1 +297 -0
  16. package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateCodexTopologyReceipts.psm1 +298 -0
  17. package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateCompletion.psm1 +232 -43
  18. package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateCompletionChecks.psm1 +416 -0
  19. package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateModelReceipts.psm1 +366 -0
  20. package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateReceipts.psm1 +408 -0
  21. package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateRoutingContract.psm1 +428 -0
  22. package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateRoutingMatrix.psm1 +377 -0
  23. package/resources/claude-customizations/.claude/lib/orchestrator-state/OrchestratorStateUnconditional.psm1 +166 -0
  24. package/resources/claude-customizations/.claude/rules/general-unit-test.md +1 -1
  25. package/resources/claude-customizations/.claude/rules/parallel-orchestration.md +28 -3
  26. package/resources/claude-customizations/.claude/rules/powershell.md +1 -1
  27. package/resources/claude-customizations/.claude/rules/quality-tiers.md +3 -3
  28. package/resources/claude-customizations/.claude/skills/feature-review-workflow/SKILL.md +4 -4
  29. package/resources/claude-customizations/.claude/skills/parallel-add/SKILL.md +10 -5
  30. package/resources/claude-customizations/.claude/skills/parallel-orchestrate/SKILL.md +108 -34
  31. package/resources/claude-customizations/.claude/skills/parallel-plan/SKILL.md +71 -9
  32. package/resources/claude-customizations/.claude/skills/parallel-remove/SKILL.md +7 -3
  33. package/resources/claude-customizations/.claude/skills/powershell-qa-gate/SKILL.md +1 -1
  34. package/resources/claude-customizations/config/blast-radius.json +1 -3
  35. package/resources/claude-customizations/pack-manifests/core.json +12 -0
  36. package/resources/codex-and-agents-customizations/.agents/skills/general-unit-test/SKILL.md +1 -1
  37. package/resources/codex-and-agents-customizations/.agents/skills/quality-tiers/SKILL.md +3 -3
  38. package/resources/codex-and-agents-customizations/.codex/config.toml +1 -1
  39. package/resources/powershell/PoshQC/settings/pester.runsettings.psd1 +21 -0
@@ -0,0 +1,297 @@
1
+ <#
2
+ .SYNOPSIS
3
+ Portable codex_model_routing_receipts per-entry checks (inventory family U6.X).
4
+
5
+ .DESCRIPTION
6
+ Destination-runtime PowerShell port of
7
+ `scripts/dev_tools/_orchestrator_state_codex_model_routing.py`, covering
8
+ parity-inventory rows U6.X1 through U6.X11: the list and object shape, the ten
9
+ required keys, the non-empty phase, the resolver-invalid-inputs surface, the
10
+ ceiling monotonicity rule, the three ceiling-transition rules, and the
11
+ resolved-key comparison.
12
+
13
+ SINGLE-IMPLEMENTATION RULE. The expected deployment is obtained by calling
14
+ `Resolve-CodexDeployment` from `.claude/lib/codex-routing/CodexDeployment.psm1`.
15
+ The profile table, the C3 overlay rule, and the forced-persona rule are never
16
+ re-implemented here.
17
+
18
+ Row U6.X11 renders both sides of a mismatch with Python `repr()` semantics
19
+ (`{expected!r}` / `{actual!r}`), so it uses the shared `ConvertTo-PythonReprText`
20
+ renderer. Row U6.X5 interpolates the resolver's exception text with Python
21
+ `str()` semantics, so the resolver's ArgumentException Message is used verbatim.
22
+
23
+ Every function is pure: it reads no file, starts no process, and never mutates
24
+ its input.
25
+ #>
26
+
27
+ Set-StrictMode -Version Latest
28
+
29
+ # Import the shared checkpoint-value primitives and the single Codex deployment
30
+ # resolver, resolved relative to this module's directory so both imports travel
31
+ # with the pushed-down pack regardless of the working directory.
32
+ Import-Module (Join-Path -Path $PSScriptRoot -ChildPath 'OrchestratorStateCheckpointValue.psm1') -Force
33
+ $script:CodexDeploymentModulePath = Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath '..') -ChildPath (Join-Path -Path 'codex-routing' -ChildPath 'CodexDeployment.psm1')
34
+ Import-Module $script:CodexDeploymentModulePath -Force
35
+
36
+ # The checkpoint key this family validates.
37
+ $script:CODEX_MODEL_ROUTING_RECEIPTS_KEY = 'codex_model_routing_receipts'
38
+
39
+ # The ten keys every receipt must carry, and the nine of them the resolver
40
+ # reproduces. Pinned to _REQUIRED_KEYS / _RESOLVED_KEYS in the Python reference;
41
+ # `phase` is checkpoint-only bookkeeping and is not resolver output.
42
+ $script:REQUIRED_RECEIPT_KEYS = @(
43
+ 'logical_agent',
44
+ 'deployment_agent',
45
+ 'phase',
46
+ 'complexity_band',
47
+ 'execution_context',
48
+ 'orchestration_complexity_ceiling',
49
+ 'c3_overlay_applied',
50
+ 'c3_overlay_reason',
51
+ 'model',
52
+ 'model_reasoning_effort'
53
+ )
54
+ $script:RESOLVED_RECEIPT_KEYS = @($script:REQUIRED_RECEIPT_KEYS | Where-Object { $_ -ne 'phase' })
55
+
56
+ # The complexity-band ordering used by the ceiling monotonicity comparison.
57
+ $script:BAND_ORDER = @('C1', 'C2', 'C3', 'C4')
58
+
59
+
60
+ function Get-CodexCeilingTransitionError {
61
+ <#
62
+ .SYNOPSIS
63
+ Return one receipt's ceiling-transition errors (rows U6.X7-U6.X10).
64
+ .DESCRIPTION
65
+ Private helper mirroring _validate_ceiling_transition. Transition evidence
66
+ is required exactly when the orchestration ceiling rises: absent when it
67
+ does not rise, and otherwise an object recording the exact from/to pair and
68
+ a non-empty unique list of affected delegation ids.
69
+ .PARAMETER Receipt
70
+ The deserialized receipt object.
71
+ .PARAMETER Prefix
72
+ The error-message prefix for this receipt position.
73
+ .PARAMETER PreviousCeiling
74
+ The previous receipt's resolved ceiling, or $null for the first receipt.
75
+ .PARAMETER CurrentCeiling
76
+ This receipt's resolved ceiling.
77
+ .OUTPUTS
78
+ System.String[] - zero or more error strings.
79
+ #>
80
+ [CmdletBinding()]
81
+ [OutputType([string[]])]
82
+ param(
83
+ [Parameter(Mandatory = $true)]
84
+ [psobject] $Receipt,
85
+
86
+ [Parameter(Mandatory = $true)]
87
+ [string] $Prefix,
88
+
89
+ [Parameter(Mandatory = $true)]
90
+ [AllowNull()]
91
+ [AllowEmptyString()]
92
+ [string] $PreviousCeiling,
93
+
94
+ [Parameter(Mandatory = $true)]
95
+ [string] $CurrentCeiling
96
+ )
97
+
98
+ $errors = [System.Collections.Generic.List[string]]::new()
99
+ $transition = (Get-CheckpointObjectMember -Owner $Receipt -Name 'ceiling_transition').Value
100
+
101
+ # U6.X7: with no previous ceiling, or an unchanged ceiling, transition
102
+ # evidence must be absent entirely.
103
+ if ([string]::IsNullOrEmpty($PreviousCeiling) -or ($CurrentCeiling -ceq $PreviousCeiling)) {
104
+ if ($null -ne $transition) {
105
+ $errors.Add("$Prefix.ceiling_transition must be absent unless the ceiling rises.")
106
+ }
107
+ return $errors.ToArray()
108
+ }
109
+
110
+ # U6.X8: a risen ceiling requires an object recording the increase.
111
+ if (-not (Test-CheckpointObjectValue -Value $transition)) {
112
+ $errors.Add("$Prefix.ceiling_transition must record a ceiling increase.")
113
+ return $errors.ToArray()
114
+ }
115
+
116
+ # U6.X9: the recorded from/to pair must be the actual transition.
117
+ $from = (Get-CheckpointObjectMember -Owner $transition -Name 'from').Value
118
+ $to = (Get-CheckpointObjectMember -Owner $transition -Name 'to').Value
119
+ if (-not (Test-PythonValueEqual -Actual $from -Expected $PreviousCeiling) -or
120
+ -not (Test-PythonValueEqual -Actual $to -Expected $CurrentCeiling)) {
121
+ $errors.Add("$Prefix.ceiling_transition must record $PreviousCeiling to $CurrentCeiling.")
122
+ }
123
+
124
+ # U6.X10: the affected delegation ids must be a non-empty list of distinct,
125
+ # non-blank strings. A non-list value is treated as empty, matching Python.
126
+ $affected = (Get-CheckpointObjectMember -Owner $transition -Name 'affected_delegation_ids').Value
127
+ $affectedItems = @()
128
+ if (Test-CheckpointListValue -Value $affected) { $affectedItems = @($affected) }
129
+ $malformed = $affectedItems.Count -eq 0
130
+ if (-not $malformed) {
131
+ $distinct = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::Ordinal)
132
+ foreach ($item in $affectedItems) {
133
+ if (-not ($item -is [string]) -or [string]::IsNullOrWhiteSpace([string]$item)) {
134
+ $malformed = $true
135
+ break
136
+ }
137
+ [void]$distinct.Add([string]$item)
138
+ }
139
+ if (-not $malformed -and $distinct.Count -ne $affectedItems.Count) { $malformed = $true }
140
+ }
141
+ if ($malformed) {
142
+ $errors.Add("$Prefix.ceiling_transition.affected_delegation_ids must be a non-empty unique string list.")
143
+ }
144
+
145
+ return $errors.ToArray()
146
+ }
147
+
148
+ function Get-CodexModelRoutingResolvedKeyError {
149
+ <#
150
+ .SYNOPSIS
151
+ Return the resolved-key mismatch errors for one receipt (row U6.X11).
152
+ .DESCRIPTION
153
+ Private helper comparing each of the nine resolver-reproduced keys against
154
+ the resolver output. Both sides render with Python repr() semantics because
155
+ the inventory template uses {expected!r} and {actual!r}.
156
+ .PARAMETER Receipt
157
+ The deserialized receipt object.
158
+ .PARAMETER Prefix
159
+ The error-message prefix for this receipt position.
160
+ .PARAMETER Expected
161
+ The resolver output hashtable.
162
+ .OUTPUTS
163
+ System.String[] - zero or more error strings.
164
+ #>
165
+ [CmdletBinding()]
166
+ [OutputType([string[]])]
167
+ param(
168
+ [Parameter(Mandatory = $true)]
169
+ [psobject] $Receipt,
170
+
171
+ [Parameter(Mandatory = $true)]
172
+ [string] $Prefix,
173
+
174
+ [Parameter(Mandatory = $true)]
175
+ [hashtable] $Expected
176
+ )
177
+
178
+ $errors = [System.Collections.Generic.List[string]]::new()
179
+
180
+ # Compare every resolver-reproduced key so a receipt reports all of its
181
+ # mismatches at once rather than only the first.
182
+ foreach ($key in $script:RESOLVED_RECEIPT_KEYS) {
183
+ $actual = (Get-CheckpointObjectMember -Owner $Receipt -Name $key).Value
184
+ if (-not (Test-PythonValueEqual -Actual $actual -Expected $Expected[$key])) {
185
+ $expectedText = ConvertTo-PythonReprText -Value $Expected[$key]
186
+ $actualText = ConvertTo-PythonReprText -Value $actual
187
+ $errors.Add("$Prefix.$key must be $expectedText, found $actualText.")
188
+ }
189
+ }
190
+
191
+ return $errors.ToArray()
192
+ }
193
+
194
+ function Get-OrchestratorStateCodexModelRoutingReceiptError {
195
+ <#
196
+ .SYNOPSIS
197
+ Return the codex_model_routing_receipts errors (rows U6.X1-U6.X11).
198
+ .DESCRIPTION
199
+ Public entry mirroring validate_codex_model_routing_receipts. Walks the
200
+ receipt array in order, carrying the previous resolved ceiling forward so
201
+ the monotonicity and transition rules can be applied, and reports every
202
+ malformed receipt with its own index.
203
+
204
+ Control flow reproduces the Python reference exactly: missing keys stop
205
+ that receipt; a resolver failure stops that receipt and leaves the carried
206
+ ceiling unchanged; a monotonicity violation suppresses the transition check
207
+ for that receipt but still advances the carried ceiling.
208
+ .PARAMETER Value
209
+ The raw deserialized value of the codex_model_routing_receipts key.
210
+ .OUTPUTS
211
+ System.String[] - zero or more error strings.
212
+ #>
213
+ [CmdletBinding()]
214
+ [OutputType([string[]])]
215
+ param(
216
+ [Parameter(Mandatory = $true)]
217
+ [AllowNull()]
218
+ [object] $Value
219
+ )
220
+
221
+ $errors = [System.Collections.Generic.List[string]]::new()
222
+
223
+ # U6.X1: the caller invokes this only when the key is present, so a non-list
224
+ # value is itself the error and nothing further can be inspected.
225
+ if (-not (Test-CheckpointListValue -Value $Value)) {
226
+ $errors.Add("Checkpoint $($script:CODEX_MODEL_ROUTING_RECEIPTS_KEY) must be a list when present.")
227
+ return $errors.ToArray()
228
+ }
229
+
230
+ $previousCeiling = $null
231
+ $index = 0
232
+ foreach ($item in @($Value)) {
233
+ $prefix = "Checkpoint $($script:CODEX_MODEL_ROUTING_RECEIPTS_KEY)[$index]"
234
+ $index++
235
+
236
+ # U6.X2: a non-object entry has no keys to inspect.
237
+ if (-not (Test-CheckpointObjectValue -Value $item)) {
238
+ $errors.Add("$prefix must be an object.")
239
+ continue
240
+ }
241
+
242
+ # U6.X3: a receipt missing any required key stops here, because the
243
+ # resolver cannot be called without complete inputs.
244
+ $names = @(Get-CheckpointObjectMemberName -Owner $item)
245
+ $missing = @($script:REQUIRED_RECEIPT_KEYS | Where-Object { $names -notcontains $_ })
246
+ if ($missing.Count -gt 0) {
247
+ $errors.Add("$prefix missing required keys: $($missing -join ', ').")
248
+ continue
249
+ }
250
+
251
+ # U6.X4: the phase is checkpoint bookkeeping; a malformed phase is
252
+ # reported but does not stop the resolver comparison.
253
+ $phase = (Get-CheckpointObjectMember -Owner $item -Name 'phase').Value
254
+ if (-not ($phase -is [string]) -or [string]::IsNullOrWhiteSpace([string]$phase)) {
255
+ $errors.Add("$prefix.phase must be a non-empty string.")
256
+ }
257
+
258
+ # U6.X5: resolve through the single Codex deployment resolver. Only the
259
+ # ValueError-equivalent surface is caught, matching the Python except
260
+ # clause; every input is coerced with Python str() semantics first.
261
+ $expected = $null
262
+ try {
263
+ $expected = Resolve-CodexDeployment `
264
+ -LogicalAgent (ConvertTo-PythonDisplayText -Value (Get-CheckpointObjectMember -Owner $item -Name 'logical_agent').Value) `
265
+ -ComplexityBand (ConvertTo-PythonDisplayText -Value (Get-CheckpointObjectMember -Owner $item -Name 'complexity_band').Value) `
266
+ -ExecutionContext (ConvertTo-PythonDisplayText -Value (Get-CheckpointObjectMember -Owner $item -Name 'execution_context').Value) `
267
+ -OrchestrationComplexityCeiling (ConvertTo-PythonDisplayText -Value (Get-CheckpointObjectMember -Owner $item -Name 'orchestration_complexity_ceiling').Value)
268
+ } catch [System.ArgumentException] {
269
+ $errors.Add("$prefix has invalid routing inputs: $($_.Exception.Message)")
270
+ continue
271
+ }
272
+
273
+ # U6.X6 and the transition rules. A ceiling that drops is a monotonicity
274
+ # violation and suppresses the transition check for this receipt; the
275
+ # carried ceiling advances either way.
276
+ $currentCeiling = [string]$expected['orchestration_complexity_ceiling']
277
+ if ($null -ne $previousCeiling -and
278
+ ($script:BAND_ORDER.IndexOf($currentCeiling) -lt $script:BAND_ORDER.IndexOf([string]$previousCeiling))) {
279
+ $errors.Add("$prefix.orchestration_complexity_ceiling must be monotonic; found $currentCeiling after $previousCeiling.")
280
+ } else {
281
+ $errors.AddRange([string[]]@(
282
+ Get-CodexCeilingTransitionError -Receipt $item -Prefix $prefix `
283
+ -PreviousCeiling $previousCeiling -CurrentCeiling $currentCeiling
284
+ ))
285
+ }
286
+ $previousCeiling = $currentCeiling
287
+
288
+ # U6.X11: every resolver-reproduced key must match the resolver output.
289
+ $errors.AddRange([string[]]@(Get-CodexModelRoutingResolvedKeyError -Receipt $item -Prefix $prefix -Expected $expected))
290
+ }
291
+
292
+ return $errors.ToArray()
293
+ }
294
+
295
+ # Only the family entry point is exported; the transition and resolved-key helpers
296
+ # stay private so the ordered, ceiling-carrying walk cannot be bypassed.
297
+ Export-ModuleMember -Function Get-OrchestratorStateCodexModelRoutingReceiptError
@@ -0,0 +1,298 @@
1
+ <#
2
+ .SYNOPSIS
3
+ Portable codex_topology_receipts per-entry checks (inventory family U6.T).
4
+
5
+ .DESCRIPTION
6
+ Destination-runtime PowerShell port of
7
+ `scripts/dev_tools/_orchestrator_state_codex_topology.py`, covering
8
+ parity-inventory rows U6.T1 through U6.T11: the list and object shape, the
9
+ thirteen required keys, the non-empty phase, the resolver input-type checks
10
+ (languages, the two file counts, the cross-cutting flag, the execution
11
+ context, and the root-persona enum), the resolver-invalid-inputs surface, and
12
+ the resolved-key comparison.
13
+
14
+ SINGLE-IMPLEMENTATION RULE. The expected topology is obtained by calling
15
+ `Resolve-CodexTopology`, and the permitted root personas are read from
16
+ `Get-CodexForcedRootPersona`, both from
17
+ `.claude/lib/codex-routing/CodexTopology.psm1`. The language-budget table and
18
+ the escalation precedence are never re-implemented here.
19
+
20
+ Row U6.T6 rejects a boolean where an integer is required, reproducing the
21
+ Python guard that exists because bool is a subclass of int. Row U6.T11 renders
22
+ both sides of a mismatch with Python `repr()` semantics, which for the
23
+ `languages` key means a Python list literal. Row U6.T10 interpolates the
24
+ resolver's exception text with Python `str()` semantics.
25
+
26
+ Every function is pure: it reads no file, starts no process, and never mutates
27
+ its input.
28
+ #>
29
+
30
+ Set-StrictMode -Version Latest
31
+
32
+ # Import the shared checkpoint-value primitives and the single Codex topology
33
+ # resolver, resolved relative to this module's directory so both imports travel
34
+ # with the pushed-down pack regardless of the working directory.
35
+ Import-Module (Join-Path -Path $PSScriptRoot -ChildPath 'OrchestratorStateCheckpointValue.psm1') -Force
36
+ $script:CodexTopologyModulePath = Join-Path -Path (Join-Path -Path $PSScriptRoot -ChildPath '..') -ChildPath (Join-Path -Path 'codex-routing' -ChildPath 'CodexTopology.psm1')
37
+ Import-Module $script:CodexTopologyModulePath -Force
38
+
39
+ # The checkpoint key this family validates.
40
+ $script:CODEX_TOPOLOGY_RECEIPTS_KEY = 'codex_topology_receipts'
41
+
42
+ # The thirteen keys every receipt must carry, and the twelve of them the resolver
43
+ # reproduces. Pinned to _REQUIRED_KEYS / _RESOLVED_KEYS in the Python reference;
44
+ # `phase` is checkpoint-only bookkeeping and is not resolver output.
45
+ $script:REQUIRED_RECEIPT_KEYS = @(
46
+ 'phase',
47
+ 'execution_context',
48
+ 'languages',
49
+ 'production_file_count',
50
+ 'test_file_count',
51
+ 'cross_cutting',
52
+ 'root_persona',
53
+ 'route',
54
+ 'topology',
55
+ 'logical_agent',
56
+ 'routing_reason',
57
+ 'max_production_files',
58
+ 'max_test_files'
59
+ )
60
+ $script:RESOLVED_RECEIPT_KEYS = @($script:REQUIRED_RECEIPT_KEYS | Where-Object { $_ -ne 'phase' })
61
+
62
+ # The two file-count keys subject to the integer-not-boolean rule.
63
+ $script:FILE_COUNT_KEYS = @('production_file_count', 'test_file_count')
64
+
65
+ # Rendered form of the Python sorted FORCED_ROOT_PERSONAS tuple, used verbatim in
66
+ # the root-persona message. The membership test itself reads the live set from the
67
+ # resolver module so the enum has one source.
68
+ $script:FORCED_ROOT_PERSONAS_PYTHON_TUPLE = "('epic-orchestrator', 'epic-planner')"
69
+
70
+ # The integral CLR types a JSON integer can deserialize to. A CLR boolean is
71
+ # deliberately absent, matching the Python bool rejection.
72
+ $script:INTEGRAL_TYPES = @([int], [long], [short], [byte])
73
+
74
+
75
+ function Get-CodexTopologyInputError {
76
+ <#
77
+ .SYNOPSIS
78
+ Return one receipt's resolver-input type errors (rows U6.T5-U6.T9).
79
+ .DESCRIPTION
80
+ Private helper mirroring _receipt_inputs. Every input the resolver
81
+ consumes is type-checked here first, in the Python order, so the resolver
82
+ is never called with a value it would reject by type. A non-empty result
83
+ means the receipt is skipped before resolution, matching the Python
84
+ `if inputs is None: continue` branch.
85
+ .PARAMETER Receipt
86
+ The deserialized receipt object.
87
+ .PARAMETER Prefix
88
+ The error-message prefix for this receipt position.
89
+ .OUTPUTS
90
+ System.String[] - zero or more error strings.
91
+ #>
92
+ [CmdletBinding()]
93
+ [OutputType([string[]])]
94
+ param(
95
+ [Parameter(Mandatory = $true)]
96
+ [psobject] $Receipt,
97
+
98
+ [Parameter(Mandatory = $true)]
99
+ [string] $Prefix
100
+ )
101
+
102
+ $errors = [System.Collections.Generic.List[string]]::new()
103
+
104
+ # U6.T5: languages must be a list in which every member is a non-blank string.
105
+ $languages = (Get-CheckpointObjectMember -Owner $Receipt -Name 'languages').Value
106
+ $languagesValid = Test-CheckpointListValue -Value $languages
107
+ if ($languagesValid) {
108
+ foreach ($language in @($languages)) {
109
+ if (-not ($language -is [string]) -or [string]::IsNullOrWhiteSpace([string]$language)) {
110
+ $languagesValid = $false
111
+ break
112
+ }
113
+ }
114
+ }
115
+ if (-not $languagesValid) {
116
+ $errors.Add("$Prefix.languages must be a list of non-empty strings.")
117
+ }
118
+
119
+ # U6.T6: both file counts must be integers, and a boolean is explicitly not an
120
+ # integer here even though Python's bool subclasses int.
121
+ foreach ($key in $script:FILE_COUNT_KEYS) {
122
+ $value = (Get-CheckpointObjectMember -Owner $Receipt -Name $key).Value
123
+ $isIntegral = $false
124
+ if ($null -ne $value -and -not ($value -is [bool])) {
125
+ foreach ($integralType in $script:INTEGRAL_TYPES) {
126
+ if ($value.GetType() -eq $integralType) { $isIntegral = $true; break }
127
+ }
128
+ }
129
+ if (-not $isIntegral) {
130
+ $errors.Add("$Prefix.$key must be an integer.")
131
+ }
132
+ }
133
+
134
+ # U6.T7 and U6.T8: the cross-cutting flag and the execution context.
135
+ $crossCutting = (Get-CheckpointObjectMember -Owner $Receipt -Name 'cross_cutting').Value
136
+ if (-not ($crossCutting -is [bool])) {
137
+ $errors.Add("$Prefix.cross_cutting must be a boolean.")
138
+ }
139
+ $receiptExecutionContext = (Get-CheckpointObjectMember -Owner $Receipt -Name 'execution_context').Value
140
+ if (-not ($receiptExecutionContext -is [string])) {
141
+ $errors.Add("$Prefix.execution_context must be a string.")
142
+ }
143
+
144
+ # U6.T9: root_persona is optional, but a present value must be a forced root
145
+ # persona. The permitted set is read from the resolver module, not restated.
146
+ $rootPersona = (Get-CheckpointObjectMember -Owner $Receipt -Name 'root_persona').Value
147
+ if ($null -ne $rootPersona) {
148
+ $permitted = @(Get-CodexForcedRootPersona)
149
+ if (-not ($rootPersona -is [string]) -or ($permitted -cnotcontains [string]$rootPersona)) {
150
+ $errors.Add("$Prefix.root_persona must be null or one of $($script:FORCED_ROOT_PERSONAS_PYTHON_TUPLE).")
151
+ }
152
+ }
153
+
154
+ return $errors.ToArray()
155
+ }
156
+
157
+ function Get-CodexTopologyResolvedKeyError {
158
+ <#
159
+ .SYNOPSIS
160
+ Return the resolved-key mismatch errors for one receipt (row U6.T11).
161
+ .DESCRIPTION
162
+ Private helper comparing each of the twelve resolver-reproduced keys
163
+ against the resolver output. Both sides render with Python repr()
164
+ semantics, which for the languages key produces a Python list literal.
165
+ .PARAMETER Receipt
166
+ The deserialized receipt object.
167
+ .PARAMETER Prefix
168
+ The error-message prefix for this receipt position.
169
+ .PARAMETER Expected
170
+ The resolver output hashtable.
171
+ .OUTPUTS
172
+ System.String[] - zero or more error strings.
173
+ #>
174
+ [CmdletBinding()]
175
+ [OutputType([string[]])]
176
+ param(
177
+ [Parameter(Mandatory = $true)]
178
+ [psobject] $Receipt,
179
+
180
+ [Parameter(Mandatory = $true)]
181
+ [string] $Prefix,
182
+
183
+ [Parameter(Mandatory = $true)]
184
+ [hashtable] $Expected
185
+ )
186
+
187
+ $errors = [System.Collections.Generic.List[string]]::new()
188
+
189
+ # Compare every resolver-reproduced key so a receipt reports all of its
190
+ # mismatches at once rather than only the first.
191
+ foreach ($key in $script:RESOLVED_RECEIPT_KEYS) {
192
+ $actual = (Get-CheckpointObjectMember -Owner $Receipt -Name $key).Value
193
+ if (-not (Test-PythonValueEqual -Actual $actual -Expected $Expected[$key])) {
194
+ $expectedText = ConvertTo-PythonReprText -Value $Expected[$key]
195
+ $actualText = ConvertTo-PythonReprText -Value $actual
196
+ $errors.Add("$Prefix.$key must be $expectedText, found $actualText.")
197
+ }
198
+ }
199
+
200
+ return $errors.ToArray()
201
+ }
202
+
203
+ function Get-OrchestratorStateCodexTopologyReceiptError {
204
+ <#
205
+ .SYNOPSIS
206
+ Return the codex_topology_receipts errors (inventory rows U6.T1-U6.T11).
207
+ .DESCRIPTION
208
+ Public entry mirroring validate_codex_topology_receipts. Each receipt is
209
+ validated independently, in order, and reported with its own index.
210
+
211
+ Control flow reproduces the Python reference exactly: missing keys stop
212
+ that receipt before any type check; a malformed phase is reported but does
213
+ not stop the receipt; any resolver-input type error stops the receipt
214
+ before resolution; and a resolver failure stops the receipt before the
215
+ resolved-key comparison.
216
+ .PARAMETER Value
217
+ The raw deserialized value of the codex_topology_receipts key.
218
+ .OUTPUTS
219
+ System.String[] - zero or more error strings.
220
+ #>
221
+ [CmdletBinding()]
222
+ [OutputType([string[]])]
223
+ param(
224
+ [Parameter(Mandatory = $true)]
225
+ [AllowNull()]
226
+ [object] $Value
227
+ )
228
+
229
+ $errors = [System.Collections.Generic.List[string]]::new()
230
+
231
+ # U6.T1: the caller invokes this only when the key is present, so a non-list
232
+ # value is itself the error and nothing further can be inspected.
233
+ if (-not (Test-CheckpointListValue -Value $Value)) {
234
+ $errors.Add("Checkpoint $($script:CODEX_TOPOLOGY_RECEIPTS_KEY) must be a list when present.")
235
+ return $errors.ToArray()
236
+ }
237
+
238
+ $index = 0
239
+ foreach ($item in @($Value)) {
240
+ $prefix = "Checkpoint $($script:CODEX_TOPOLOGY_RECEIPTS_KEY)[$index]"
241
+ $index++
242
+
243
+ # U6.T2: a non-object entry has no keys to inspect.
244
+ if (-not (Test-CheckpointObjectValue -Value $item)) {
245
+ $errors.Add("$prefix must be an object.")
246
+ continue
247
+ }
248
+
249
+ # U6.T3: a receipt missing any required key stops here, because the
250
+ # resolver cannot be called without complete inputs.
251
+ $names = @(Get-CheckpointObjectMemberName -Owner $item)
252
+ $missing = @($script:REQUIRED_RECEIPT_KEYS | Where-Object { $names -notcontains $_ })
253
+ if ($missing.Count -gt 0) {
254
+ $errors.Add("$prefix missing required keys: $($missing -join ', ').")
255
+ continue
256
+ }
257
+
258
+ # U6.T4: the phase is checkpoint bookkeeping; a malformed phase is
259
+ # reported but does not stop the resolver comparison.
260
+ $phase = (Get-CheckpointObjectMember -Owner $item -Name 'phase').Value
261
+ if (-not ($phase -is [string]) -or [string]::IsNullOrWhiteSpace([string]$phase)) {
262
+ $errors.Add("$prefix.phase must be a non-empty string.")
263
+ }
264
+
265
+ # U6.T5-U6.T9: any resolver-input type error stops this receipt, so the
266
+ # resolver is never handed a value it would reject by type.
267
+ $inputErrors = @(Get-CodexTopologyInputError -Receipt $item -Prefix $prefix)
268
+ if ($inputErrors.Count -gt 0) {
269
+ $errors.AddRange([string[]]$inputErrors)
270
+ continue
271
+ }
272
+
273
+ # U6.T10: resolve through the single Codex topology resolver. Only the
274
+ # ValueError-equivalent surface is caught, matching the Python except.
275
+ $expected = $null
276
+ try {
277
+ $expected = Resolve-CodexTopology `
278
+ -Language (Get-CheckpointObjectMember -Owner $item -Name 'languages').Value `
279
+ -ProductionFileCount (Get-CheckpointObjectMember -Owner $item -Name 'production_file_count').Value `
280
+ -TestFileCount (Get-CheckpointObjectMember -Owner $item -Name 'test_file_count').Value `
281
+ -ExecutionContext ([string](Get-CheckpointObjectMember -Owner $item -Name 'execution_context').Value) `
282
+ -CrossCutting (Get-CheckpointObjectMember -Owner $item -Name 'cross_cutting').Value `
283
+ -RootPersona (Get-CheckpointObjectMember -Owner $item -Name 'root_persona').Value
284
+ } catch [System.ArgumentException] {
285
+ $errors.Add("$prefix has invalid routing inputs: $($_.Exception.Message)")
286
+ continue
287
+ }
288
+
289
+ # U6.T11: every resolver-reproduced key must match the resolver output.
290
+ $errors.AddRange([string[]]@(Get-CodexTopologyResolvedKeyError -Receipt $item -Prefix $prefix -Expected $expected))
291
+ }
292
+
293
+ return $errors.ToArray()
294
+ }
295
+
296
+ # Only the family entry point is exported; the input-type and resolved-key helpers
297
+ # stay private so no consumer can skip the ordered per-receipt walk.
298
+ Export-ModuleMember -Function Get-OrchestratorStateCodexTopologyReceiptError