@danmoisan/drm-copilot-mcp 1.1.23 → 1.1.25

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 (199) hide show
  1. package/out/mcp-server.js +13347 -7926
  2. package/package.json +1 -1
  3. package/resources/claude-customizations/.claude/agents/discovery-capability-sentinel.md +34 -0
  4. package/resources/claude-customizations/.claude/agents/legacy-parity-analyst.md +101 -43
  5. package/resources/claude-customizations/.claude/agents/migration-coverage-reviewer.md +107 -43
  6. package/resources/claude-customizations/.claude/agents/parallel-orchestrator.md +14 -0
  7. package/resources/claude-customizations/.claude/agents/parallel-planner.md +21 -1
  8. package/resources/claude-customizations/.claude/agents/pr-author.md +17 -1
  9. package/resources/claude-customizations/.claude/agents/requirements-reconciler.md +117 -42
  10. package/resources/claude-customizations/.claude/agents/runtime-characterization-analyst.md +103 -43
  11. package/resources/claude-customizations/.claude/hooks/discovery-capability-probe.ps1 +102 -0
  12. package/resources/claude-customizations/.claude/hooks/enforce-discovery-artifact-gate.ps1 +69 -194
  13. package/resources/claude-customizations/.claude/hooks/enforce-discovery-planning-gate.ps1 +87 -0
  14. package/resources/claude-customizations/.claude/hooks/enforce-epic-merge-gate.ps1 +7 -1
  15. package/resources/claude-customizations/.claude/hooks/enforce-model-routing-receipt.ps1 +6 -3
  16. package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate-epic-scope.ps1 +168 -48
  17. package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate.ps1 +1 -1
  18. package/resources/claude-customizations/.claude/hooks/enforce-powershell-batch-budget.ps1 +59 -20
  19. package/resources/claude-customizations/.claude/hooks/enforce-pr-author-command-allowlist.ps1 +44 -3
  20. package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill-helpers.ps1 +60 -4
  21. package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill.epic-base-branch.ps1 +4 -2
  22. package/resources/claude-customizations/.claude/hooks/enforce-python-batch-budget.ps1 +59 -20
  23. package/resources/claude-customizations/.claude/hooks/record-discovery-agent-start.ps1 +132 -0
  24. package/resources/claude-customizations/.claude/hooks/validate-discovery-artifact-gate.ps1 +56 -212
  25. package/resources/claude-customizations/.claude/hooks/validate-orchestrator-output-rejection.ps1 +172 -0
  26. package/resources/claude-customizations/.claude/hooks/validate-orchestrator-output.ps1 +9 -1
  27. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadius.psm1 +9 -3
  28. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusAggregate.psm1 +154 -0
  29. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusCombination.psm1 +228 -0
  30. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusExtraction.psm1 +1 -0
  31. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusNormalization.psm1 +4 -2
  32. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusScheduling.psm1 +63 -16
  33. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusTokenShape.psm1 +56 -1
  34. package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusWriteIntent.psm1 +4 -1
  35. package/resources/claude-customizations/.claude/lib/codex-routing/CodexDeployment.psm1 +5 -1
  36. package/resources/claude-customizations/.claude/lib/codex-routing/CodexDiscoveryRouting.psm1 +161 -0
  37. package/resources/claude-customizations/.claude/lib/codex-routing/CodexTopology.psm1 +25 -2
  38. package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryAuthority.psd1 +90 -0
  39. package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryAuthority.psm1 +493 -0
  40. package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryAuthorization.psm1 +479 -0
  41. package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryCapability.psm1 +468 -0
  42. package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryEncoding.psm1 +156 -0
  43. package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryManifest.psm1 +384 -0
  44. package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryObservation.psm1 +217 -0
  45. package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryPayload.psm1 +276 -0
  46. package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryPlanning.psm1 +336 -0
  47. package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryRootGrant.psm1 +164 -0
  48. package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryStart.psm1 +440 -0
  49. package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryStartRecord.psm1 +296 -0
  50. package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryStop.psm1 +336 -0
  51. package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryToolRouting.psm1 +228 -0
  52. package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryValidation.psm1 +188 -341
  53. package/resources/claude-customizations/.claude/lib/worktree-resolution/HookTargetWorktree.psm1 +391 -0
  54. package/resources/claude-customizations/.claude/rules/orchestrator-state.md +1 -0
  55. package/resources/claude-customizations/.claude/rules/parallel-orchestration.md +150 -18
  56. package/resources/claude-customizations/.claude/settings.json +32 -3
  57. package/resources/claude-customizations/.claude/skills/adjudicate-unspecified-behavior/SKILL.md +135 -0
  58. package/resources/claude-customizations/.claude/skills/characterize-runtime-behavior/SKILL.md +134 -0
  59. package/resources/claude-customizations/.claude/skills/discovery-behavior-reconciliation/SKILL.md +28 -47
  60. package/resources/claude-customizations/.claude/skills/discovery-coverage-ledger/SKILL.md +32 -48
  61. package/resources/claude-customizations/.claude/skills/discovery-parity-matrix/SKILL.md +28 -45
  62. package/resources/claude-customizations/.claude/skills/discovery-repo-inventory/SKILL.md +28 -62
  63. package/resources/claude-customizations/.claude/skills/discovery-runtime-characterization/SKILL.md +28 -45
  64. package/resources/claude-customizations/.claude/skills/discovery-validate-artifacts/SKILL.md +28 -61
  65. package/resources/claude-customizations/.claude/skills/discovery-workflow/SKILL.md +55 -139
  66. package/resources/claude-customizations/.claude/skills/execution-environment/SKILL.md +24 -9
  67. package/resources/claude-customizations/.claude/skills/extract-feature-contract/SKILL.md +133 -0
  68. package/resources/claude-customizations/.claude/skills/generate-acceptance-scenarios/SKILL.md +132 -0
  69. package/resources/claude-customizations/.claude/skills/initialize-discovery-workspace/SKILL.md +131 -0
  70. package/resources/claude-customizations/.claude/skills/inventory-legacy-system/SKILL.md +131 -0
  71. package/resources/claude-customizations/.claude/skills/orchestrate/SKILL.md +5 -4
  72. package/resources/claude-customizations/.claude/skills/parallel-add/SKILL.md +21 -1
  73. package/resources/claude-customizations/.claude/skills/parallel-close/SKILL.md +11 -1
  74. package/resources/claude-customizations/.claude/skills/parallel-orchestrate/SKILL.md +34 -0
  75. package/resources/claude-customizations/.claude/skills/parallel-plan/SKILL.md +69 -6
  76. package/resources/claude-customizations/.claude/skills/parallel-remove/SKILL.md +15 -0
  77. package/resources/claude-customizations/.claude/skills/parallel-run/SKILL.md +6 -0
  78. package/resources/claude-customizations/.claude/skills/pr-author/SKILL.md +21 -4
  79. package/resources/claude-customizations/.claude/skills/reconcile-parity-matrix/SKILL.md +131 -0
  80. package/resources/claude-customizations/.claude/skills/review-discovery-coverage/SKILL.md +129 -0
  81. package/resources/claude-customizations/.claude/skills/validate-discovery-artifacts/SKILL.md +127 -0
  82. package/resources/claude-customizations/config/blast-radius.json +6 -5
  83. package/resources/claude-customizations/config/orchestration-routing.json +61 -1
  84. package/resources/claude-customizations/pack-manifests/core.json +33 -1
  85. package/resources/codex-and-agents-customizations/.agents/skills/adjudicate-unspecified-behavior/SKILL.md +135 -0
  86. package/resources/codex-and-agents-customizations/.agents/skills/atomic-planner/SKILL.md +7 -0
  87. package/resources/codex-and-agents-customizations/.agents/skills/characterize-runtime-behavior/SKILL.md +134 -0
  88. package/resources/codex-and-agents-customizations/.agents/skills/discovery-behavior-reconciliation/SKILL.md +46 -0
  89. package/resources/codex-and-agents-customizations/.agents/skills/discovery-coverage-ledger/SKILL.md +50 -0
  90. package/resources/codex-and-agents-customizations/.agents/skills/discovery-parity-matrix/SKILL.md +46 -0
  91. package/resources/codex-and-agents-customizations/.agents/skills/discovery-repo-inventory/SKILL.md +46 -0
  92. package/resources/codex-and-agents-customizations/.agents/skills/discovery-runtime-characterization/SKILL.md +46 -0
  93. package/resources/codex-and-agents-customizations/.agents/skills/discovery-validate-artifacts/SKILL.md +46 -0
  94. package/resources/codex-and-agents-customizations/.agents/skills/discovery-workflow/SKILL.md +62 -0
  95. package/resources/codex-and-agents-customizations/.agents/skills/execution-environment/SKILL.md +24 -9
  96. package/resources/codex-and-agents-customizations/.agents/skills/extract-feature-contract/SKILL.md +133 -0
  97. package/resources/codex-and-agents-customizations/.agents/skills/generate-acceptance-scenarios/SKILL.md +132 -0
  98. package/resources/codex-and-agents-customizations/.agents/skills/initialize-discovery-workspace/SKILL.md +131 -0
  99. package/resources/codex-and-agents-customizations/.agents/skills/inventory-legacy-system/SKILL.md +131 -0
  100. package/resources/codex-and-agents-customizations/.agents/skills/orchestrator-workflow/SKILL.md +8 -0
  101. package/resources/codex-and-agents-customizations/.agents/skills/reconcile-parity-matrix/SKILL.md +131 -0
  102. package/resources/codex-and-agents-customizations/.agents/skills/review-discovery-coverage/SKILL.md +129 -0
  103. package/resources/codex-and-agents-customizations/.agents/skills/validate-discovery-artifacts/SKILL.md +127 -0
  104. package/resources/codex-and-agents-customizations/.codex/agents/discovery-capability-sentinel.toml +51 -0
  105. package/resources/codex-and-agents-customizations/.codex/agents/legacy-parity-analyst-c1.toml +148 -0
  106. package/resources/codex-and-agents-customizations/.codex/agents/legacy-parity-analyst-c2.toml +148 -0
  107. package/resources/codex-and-agents-customizations/.codex/agents/legacy-parity-analyst-c3-elevated.toml +148 -0
  108. package/resources/codex-and-agents-customizations/.codex/agents/legacy-parity-analyst-c3.toml +148 -0
  109. package/resources/codex-and-agents-customizations/.codex/agents/legacy-parity-analyst-c4.toml +148 -0
  110. package/resources/codex-and-agents-customizations/.codex/agents/legacy-parity-analyst.toml +148 -0
  111. package/resources/codex-and-agents-customizations/.codex/agents/migration-coverage-reviewer-c1.toml +154 -0
  112. package/resources/codex-and-agents-customizations/.codex/agents/migration-coverage-reviewer-c2.toml +154 -0
  113. package/resources/codex-and-agents-customizations/.codex/agents/migration-coverage-reviewer-c3-elevated.toml +154 -0
  114. package/resources/codex-and-agents-customizations/.codex/agents/migration-coverage-reviewer-c3.toml +154 -0
  115. package/resources/codex-and-agents-customizations/.codex/agents/migration-coverage-reviewer-c4.toml +154 -0
  116. package/resources/codex-and-agents-customizations/.codex/agents/migration-coverage-reviewer.toml +154 -0
  117. package/resources/codex-and-agents-customizations/.codex/agents/requirements-reconciler-c1.toml +164 -0
  118. package/resources/codex-and-agents-customizations/.codex/agents/requirements-reconciler-c2.toml +164 -0
  119. package/resources/codex-and-agents-customizations/.codex/agents/requirements-reconciler-c3-elevated.toml +164 -0
  120. package/resources/codex-and-agents-customizations/.codex/agents/requirements-reconciler-c3.toml +164 -0
  121. package/resources/codex-and-agents-customizations/.codex/agents/requirements-reconciler-c4.toml +164 -0
  122. package/resources/codex-and-agents-customizations/.codex/agents/requirements-reconciler.toml +164 -0
  123. package/resources/codex-and-agents-customizations/.codex/agents/runtime-characterization-analyst-c1.toml +150 -0
  124. package/resources/codex-and-agents-customizations/.codex/agents/runtime-characterization-analyst-c2.toml +150 -0
  125. package/resources/codex-and-agents-customizations/.codex/agents/runtime-characterization-analyst-c3-elevated.toml +150 -0
  126. package/resources/codex-and-agents-customizations/.codex/agents/runtime-characterization-analyst-c3.toml +150 -0
  127. package/resources/codex-and-agents-customizations/.codex/agents/runtime-characterization-analyst-c4.toml +150 -0
  128. package/resources/codex-and-agents-customizations/.codex/agents/runtime-characterization-analyst.toml +150 -0
  129. package/resources/codex-and-agents-customizations/.codex/config.toml +33 -3
  130. package/resources/codex-and-agents-customizations/.codex/hooks/codex-agent-profile-attestation.ps1 +22 -0
  131. package/resources/codex-and-agents-customizations/.codex/hooks/discovery-capability-probe.ps1 +97 -0
  132. package/resources/codex-and-agents-customizations/.codex/hooks/discovery-start-attestation.ps1 +171 -0
  133. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-codex-model-routing.ps1 +4 -1
  134. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-discovery-artifact-gate.ps1 +187 -0
  135. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-discovery-planning-gate.ps1 +149 -0
  136. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate-epic-scope.ps1 +239 -2
  137. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate.ps1 +6 -15
  138. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-powershell-batch-budget.ps1 +54 -15
  139. package/resources/codex-and-agents-customizations/.codex/hooks/enforce-python-batch-budget.ps1 +54 -15
  140. package/resources/codex-and-agents-customizations/.codex/hooks/hook-target-worktree.ps1 +369 -0
  141. package/resources/codex-and-agents-customizations/.codex/hooks/record-subagent-routing-attestation.ps1 +8 -0
  142. package/resources/codex-and-agents-customizations/.codex/hooks/validate-codex-subagent-routing.ps1 +22 -1
  143. package/resources/codex-and-agents-customizations/.codex/hooks/validate-discovery-agent-output.ps1 +178 -0
  144. package/resources/codex-and-agents-customizations/.codex/scripts/Resolve-CodexTopology.ps1 +11 -1
  145. package/resources/codex-and-agents-customizations/pack-manifests/core.json +63 -1
  146. package/resources/config/orchestration-routing.json +61 -1
  147. package/resources/customizations/.github/agents/discovery-capability-sentinel.agent.md +34 -0
  148. package/resources/customizations/.github/agents/legacy-parity-analyst.agent.md +122 -0
  149. package/resources/customizations/.github/agents/migration-coverage-reviewer.agent.md +128 -0
  150. package/resources/customizations/.github/agents/requirements-reconciler.agent.md +138 -0
  151. package/resources/customizations/.github/agents/runtime-characterization-analyst.agent.md +124 -0
  152. package/resources/customizations/.github/hooks/discovery-agent-start.ps1 +139 -0
  153. package/resources/customizations/.github/hooks/discovery-agent-stop.ps1 +123 -0
  154. package/resources/customizations/.github/hooks/discovery-capability-probe.ps1 +98 -0
  155. package/resources/customizations/.github/hooks/discovery-planning.ps1 +91 -0
  156. package/resources/customizations/.github/hooks/discovery-pre-tool.ps1 +123 -0
  157. package/resources/customizations/.github/hooks/discovery.json +35 -0
  158. package/resources/customizations/.github/instructions/mermaid.instructions.md +85 -0
  159. package/resources/customizations/.github/prompts/execute-hard-lock.prompt.md +105 -0
  160. package/resources/customizations/.github/skills/adjudicate-unspecified-behavior/SKILL.md +135 -0
  161. package/resources/customizations/.github/skills/characterize-runtime-behavior/SKILL.md +134 -0
  162. package/resources/customizations/.github/skills/discovery-behavior-reconciliation/SKILL.md +46 -0
  163. package/resources/customizations/.github/skills/discovery-coverage-ledger/SKILL.md +50 -0
  164. package/resources/customizations/.github/skills/discovery-parity-matrix/SKILL.md +46 -0
  165. package/resources/customizations/.github/skills/discovery-repo-inventory/SKILL.md +46 -0
  166. package/resources/customizations/.github/skills/discovery-runtime-characterization/SKILL.md +46 -0
  167. package/resources/customizations/.github/skills/discovery-validate-artifacts/SKILL.md +46 -0
  168. package/resources/customizations/.github/skills/discovery-workflow/SKILL.md +62 -0
  169. package/resources/customizations/.github/skills/execution-environment/SKILL.md +24 -9
  170. package/resources/customizations/.github/skills/extract-feature-contract/SKILL.md +133 -0
  171. package/resources/customizations/.github/skills/generate-acceptance-scenarios/SKILL.md +132 -0
  172. package/resources/customizations/.github/skills/initialize-discovery-workspace/SKILL.md +131 -0
  173. package/resources/customizations/.github/skills/inventory-legacy-system/SKILL.md +131 -0
  174. package/resources/customizations/.github/skills/reconcile-parity-matrix/SKILL.md +131 -0
  175. package/resources/customizations/.github/skills/review-discovery-coverage/SKILL.md +129 -0
  176. package/resources/customizations/.github/skills/validate-discovery-artifacts/SKILL.md +127 -0
  177. package/resources/lib/codex-routing/CodexDeployment.psm1 +5 -1
  178. package/resources/lib/codex-routing/CodexDiscoveryRouting.psm1 +161 -0
  179. package/resources/lib/codex-routing/CodexTopology.psm1 +25 -2
  180. package/resources/lib/discovery-validation/DiscoveryAuthority.psd1 +90 -0
  181. package/resources/lib/discovery-validation/DiscoveryAuthority.psm1 +493 -0
  182. package/resources/lib/discovery-validation/DiscoveryAuthorization.psm1 +479 -0
  183. package/resources/lib/discovery-validation/DiscoveryCapability.psm1 +468 -0
  184. package/resources/lib/discovery-validation/DiscoveryEncoding.psm1 +156 -0
  185. package/resources/lib/discovery-validation/DiscoveryManifest.psm1 +384 -0
  186. package/resources/lib/discovery-validation/DiscoveryObservation.psm1 +217 -0
  187. package/resources/lib/discovery-validation/DiscoveryPayload.psm1 +276 -0
  188. package/resources/lib/discovery-validation/DiscoveryPlanning.psm1 +336 -0
  189. package/resources/lib/discovery-validation/DiscoveryRootGrant.psm1 +164 -0
  190. package/resources/lib/discovery-validation/DiscoveryStart.psm1 +440 -0
  191. package/resources/lib/discovery-validation/DiscoveryStartRecord.psm1 +296 -0
  192. package/resources/lib/discovery-validation/DiscoveryStop.psm1 +336 -0
  193. package/resources/lib/discovery-validation/DiscoveryToolRouting.psm1 +228 -0
  194. package/resources/powershell/PoshQC/PoshQC.Parallel.psm1 +452 -0
  195. package/resources/powershell/PoshQC/PoshQC.Shard.psm1 +106 -8
  196. package/resources/powershell/PoshQC/PoshQC.Testing.psm1 +12 -0
  197. package/resources/powershell/PoshQC/PoshQC.psd1 +6 -1
  198. package/resources/powershell/PoshQC/PoshQC.psm1 +7 -1
  199. package/resources/powershell/PoshQC/README.md +20 -1
@@ -0,0 +1,479 @@
1
+ <#
2
+ .SYNOPSIS
3
+ Native PreToolUse authorization decisions and one-use tool-authorization records.
4
+
5
+ .DESCRIPTION
6
+ Every native discovery PreToolUse adapter (Claude Code, Codex, GitHub Copilot)
7
+ takes its decision from this module. The caller's identity comes from the hook
8
+ payload alone: an envelope without `agent_id` is the root of its session, and
9
+ an envelope whose `agent_id` carries a discovery `agent_type` is a specialist.
10
+
11
+ A root call of `prepare_discovery_agent`, `run_discovery_doctor` or a
12
+ controlled discovery tool is allowed with a one-use `root-grant` that the MCP
13
+ server consumes by tool, operation and input digest (the prepare and
14
+ controlled grants require a current supported capability attestation); every
15
+ other root tool is allowed so the ordinary gates apply. A sentinel envelope is
16
+ observed and never granted. For a specialist, only the controlled discovery MCP
17
+ tools and read-only native tools are admitted: shell, terminal, exec and
18
+ evaluation tools, generic Write/Edit/apply_patch mutations, subagent spawning,
19
+ every other MCP server, root-only discovery operations, unknown operations and
20
+ caller-supplied identity fields are denied before execution. The specialist's
21
+ first controlled call starts the single prepared invocation of its session,
22
+ parent and agent type; every controlled call must name that invocation through
23
+ `invocation_ref`, and the admitted request is recorded as one short-lived,
24
+ one-use `tool-authorization` whose input digest is
25
+ `sha256(canonical_json({inputs, operation, tool}))`, exactly as the gateway
26
+ recomputes it.
27
+
28
+ No value in tool input can supply identity or authority; every binding comes
29
+ from the payload principal and the protected activation and invocation records.
30
+ Store access flows through injected seams; the module never invokes a process.
31
+
32
+ .NOTES
33
+ Issue #980. Compatible with PowerShell 7+. Depends only on the sibling
34
+ DiscoveryEncoding, DiscoveryAuthority, DiscoveryPayload, DiscoveryToolRouting,
35
+ DiscoveryRootGrant, DiscoveryObservation, DiscoveryStartRecord and DiscoveryStart modules.
36
+ CONVENTION: this module fails fast at module scope and imports its siblings with -ErrorAction Stop.
37
+ #>
38
+
39
+ Set-StrictMode -Version Latest
40
+ $ErrorActionPreference = 'Stop'
41
+
42
+ Import-Module (Join-Path $PSScriptRoot 'DiscoveryEncoding.psm1') -ErrorAction Stop
43
+ Import-Module (Join-Path $PSScriptRoot 'DiscoveryAuthority.psm1') -ErrorAction Stop
44
+ Import-Module (Join-Path $PSScriptRoot 'DiscoveryPayload.psm1') -ErrorAction Stop
45
+ Import-Module (Join-Path $PSScriptRoot 'DiscoveryToolRouting.psm1') -ErrorAction Stop
46
+ Import-Module (Join-Path $PSScriptRoot 'DiscoveryRootGrant.psm1') -ErrorAction Stop
47
+ Import-Module (Join-Path $PSScriptRoot 'DiscoveryObservation.psm1') -ErrorAction Stop
48
+ Import-Module (Join-Path $PSScriptRoot 'DiscoveryStartRecord.psm1') -ErrorAction Stop
49
+ Import-Module (Join-Path $PSScriptRoot 'DiscoveryStart.psm1') -ErrorAction Stop
50
+
51
+ $script:AuthorizationSchema = 'urn:drm-copilot:discovery:2.0.0:tool-authorization'
52
+ # Schema timestamp spelling for issued_at and expires_at.
53
+ $script:TimestampFormat = "yyyy-MM-dd'T'HH:mm:ss'Z'"
54
+ $script:DiscoveryRoles = @('legacy-parity-analyst', 'runtime-characterization-analyst', 'requirements-reconciler', 'migration-coverage-reviewer')
55
+ $script:CorrelationIdPattern = '^[A-Za-z0-9][A-Za-z0-9._:-]*$'
56
+
57
+ function Find-DiscoveryAgentInvocation {
58
+ <#
59
+ .SYNOPSIS
60
+ Finds the single started invocation bound to the attributed agent in the protected session.
61
+ .DESCRIPTION
62
+ Enumerates `<session>/invocations/*/invocation.json` through the ListEntries
63
+ and ReadText seams and returns the one record whose host, session, agent and
64
+ role equal the principal's and whose state is `started` with a start
65
+ attestation. Zero or several matches return $null; an unreadable record is
66
+ skipped so a corrupt neighbour cannot admit a request.
67
+ #>
68
+ [CmdletBinding()]
69
+ [OutputType([pscustomobject])]
70
+ param(
71
+ [Parameter(Mandatory = $true)][pscustomobject]$Location,
72
+ [Parameter(Mandatory = $true)][pscustomobject]$Principal,
73
+ [Parameter(Mandatory = $true)][hashtable]$Store
74
+ )
75
+
76
+ $directory = "$(Get-DiscoverySessionDirectory -Location $Location -SessionId $Principal.SessionId)/invocations"
77
+ $bindings = [System.Collections.Generic.List[object]]::new()
78
+ # Every partition is inspected; only an exact identity binding in the started state counts.
79
+ foreach ($entry in @(Invoke-DiscoveryStoreSeam -Seam $Store.ListEntries -Arguments @($directory))) {
80
+ $text = Invoke-DiscoveryStoreSeam -Seam $Store.ReadText -Arguments @("$directory/$entry/invocation.json")
81
+ if ($null -eq $text) { continue }
82
+ try { $record = ConvertFrom-Json -InputObject ([string]$text) -Depth 16 } catch { continue }
83
+ if ($null -eq $record -or $record -isnot [System.Management.Automation.PSCustomObject]) { continue }
84
+ $bound = ($record.host -eq $Principal.HostName) -and ($record.session_id -eq $Principal.SessionId) -and
85
+ ($record.agent_id -eq $Principal.AgentId) -and ($record.role -eq $Principal.Role) -and
86
+ ($record.state -eq 'started') -and -not [string]::IsNullOrWhiteSpace([string]$record.start_attestation_ref)
87
+ if ($bound) { $bindings.Add([pscustomobject]@{ Record = $record; Directory = "$directory/$entry" }) }
88
+ }
89
+ if ($bindings.Count -ne 1) { return $null }
90
+ return $bindings[0]
91
+ }
92
+
93
+ function ConvertTo-DiscoveryToolAuthorizationRecord {
94
+ <#
95
+ .SYNOPSIS
96
+ Builds one `tool-authorization` record bound to the principal, invocation and request digest.
97
+ .DESCRIPTION
98
+ State is always `issued`; the gateway consumes it exactly once. `issued_at`
99
+ and `expires_at` come from the injected instant and TTL, never from the
100
+ request. The record is state in the protected store; no file is changed here.
101
+ #>
102
+ [CmdletBinding()]
103
+ [OutputType([System.Collections.Specialized.OrderedDictionary])]
104
+ param(
105
+ [Parameter(Mandatory = $true)][pscustomobject]$Principal,
106
+ [Parameter(Mandatory = $true)][pscustomobject]$Invocation,
107
+ [Parameter(Mandatory = $true)][string]$Tool,
108
+ [Parameter(Mandatory = $true)][string]$Operation,
109
+ [Parameter(Mandatory = $true)][string]$InputSha256,
110
+ [Parameter(Mandatory = $true)][string]$RequestId,
111
+ [Parameter(Mandatory = $true)][string]$EventId,
112
+ [Parameter(Mandatory = $true)][string]$HandlerSha256,
113
+ [Parameter(Mandatory = $true)][datetime]$Now,
114
+ [Parameter(Mandatory = $true)][string]$Nonce,
115
+ [Parameter(Mandatory = $false)][int]$TtlSeconds = 300
116
+ )
117
+
118
+ $workspace = [ordered]@{
119
+ workspace_root = [string]$Invocation.workspace.workspace_root
120
+ workspace_sha256 = [string]$Invocation.workspace.workspace_sha256
121
+ profile_path = [string]$Invocation.workspace.profile_path
122
+ profile_sha256 = [string]$Invocation.workspace.profile_sha256
123
+ }
124
+ return [ordered]@{
125
+ '$schema' = $script:AuthorizationSchema
126
+ schema_version = '2.0.0'
127
+ artifact_kind = 'tool-authorization'
128
+ id = "authz-$Nonce"
129
+ authorization_ref = "authz-$Nonce"
130
+ request_id = $RequestId
131
+ workspace = $workspace
132
+ identity = [ordered]@{
133
+ host = $Principal.HostName
134
+ session_id = $Principal.SessionId
135
+ agent_id = $Principal.AgentId
136
+ role = $Principal.Role
137
+ invocation_id = [string]$Invocation.invocation_id
138
+ }
139
+ tool = $Tool
140
+ operation = $Operation
141
+ input_sha256 = $InputSha256
142
+ # Names the invocation itself (as invocation_ref and the start attestation do), never the record id.
143
+ invocation_ref = [string]$Invocation.invocation_id
144
+ start_attestation_ref = [string]$Invocation.start_attestation_ref
145
+ capability_ref = [string]$Invocation.capability_ref
146
+ issued_at = $Now.ToUniversalTime().ToString($script:TimestampFormat, [System.Globalization.CultureInfo]::InvariantCulture)
147
+ expires_at = $Now.ToUniversalTime().AddSeconds($TtlSeconds).ToString($script:TimestampFormat, [System.Globalization.CultureInfo]::InvariantCulture)
148
+ state = 'issued'
149
+ consumed_at = $null
150
+ writer = [ordered]@{ kind = 'native-pretooluse'; host = $Principal.HostName; event_id = $EventId; handler_sha256 = $HandlerSha256 }
151
+ }
152
+ }
153
+
154
+ function Get-DiscoveryDefaultStore {
155
+ <#
156
+ .SYNOPSIS
157
+ Returns the real-filesystem store seams (ReadText, ListEntries, WriteText, ReplaceText, EnsureDirectory).
158
+ .DESCRIPTION
159
+ WriteText creates the file exclusively, so a second writer for the same
160
+ authorization reference fails instead of replacing a record; ReplaceText is
161
+ the explicit in-place rewrite that only the session capability attestation uses.
162
+ #>
163
+ [CmdletBinding()]
164
+ [OutputType([hashtable])]
165
+ param()
166
+
167
+ return @{
168
+ ReadText = {
169
+ param([string]$Path)
170
+ if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) { return $null }
171
+ return [System.IO.File]::ReadAllText($Path, [System.Text.Encoding]::UTF8)
172
+ }
173
+ ListEntries = {
174
+ param([string]$Directory)
175
+ if (-not (Test-Path -LiteralPath $Directory -PathType Container)) { return @() }
176
+ return @(Get-ChildItem -LiteralPath $Directory -Force | Sort-Object -Property Name | ForEach-Object { $_.Name })
177
+ }
178
+ WriteText = {
179
+ param([string]$Path, [string]$Text)
180
+ $stream = [System.IO.File]::Open($Path, [System.IO.FileMode]::CreateNew, [System.IO.FileAccess]::Write, [System.IO.FileShare]::None)
181
+ try {
182
+ $bytes = [System.Text.Encoding]::UTF8.GetBytes($Text)
183
+ $stream.Write($bytes, 0, $bytes.Length)
184
+ } finally { $stream.Dispose() }
185
+ }
186
+ ReplaceText = {
187
+ param([string]$Path, [string]$Text)
188
+ [System.IO.File]::WriteAllText($Path, $Text, [System.Text.UTF8Encoding]::new($false))
189
+ }
190
+ EnsureDirectory = { param([string]$Directory) [void](New-Item -ItemType Directory -Path $Directory -Force) }
191
+ }
192
+ }
193
+
194
+ function Get-DiscoveryAuthorizationNonce {
195
+ <#
196
+ .SYNOPSIS
197
+ Returns 20 lowercase hex characters from the platform random generator.
198
+ #>
199
+ [CmdletBinding()]
200
+ [OutputType([string])]
201
+ param()
202
+
203
+ $bytes = [byte[]]::new(10)
204
+ [System.Security.Cryptography.RandomNumberGenerator]::Fill($bytes)
205
+ return [Convert]::ToHexString($bytes).ToLowerInvariant()
206
+ }
207
+
208
+ function Get-DiscoveryPreToolOutcome {
209
+ <#
210
+ .SYNOPSIS
211
+ Builds one decision object (allow or deny) with its code and reason.
212
+ #>
213
+ [CmdletBinding()]
214
+ [OutputType([pscustomobject])]
215
+ param(
216
+ [Parameter(Mandatory = $true)][ValidateSet('allow', 'deny')][string]$Decision,
217
+ [Parameter(Mandatory = $true)][string]$Code,
218
+ [Parameter(Mandatory = $true)][string]$Reason,
219
+ [Parameter(Mandatory = $false)][AllowNull()][object]$Record = $null,
220
+ [Parameter(Mandatory = $false)][AllowNull()][string]$RecordPath = $null
221
+ )
222
+
223
+ return [pscustomobject]@{ Decision = $Decision; Code = $Code; Reason = $Reason; Record = $Record; RecordPath = $RecordPath }
224
+ }
225
+
226
+ function Resolve-DiscoveryGateLocation {
227
+ <#
228
+ .SYNOPSIS
229
+ Resolves the protected-store location of the event's workspace, or returns the WORKSPACE deny outcome.
230
+ .OUTPUTS
231
+ [pscustomobject] Location (or $null) and Outcome (the deny, or $null).
232
+ #>
233
+ [CmdletBinding()]
234
+ [OutputType([pscustomobject])]
235
+ param(
236
+ [Parameter(Mandatory = $true)][pscustomobject]$Normalized,
237
+ [Parameter(Mandatory = $true)][hashtable]$HostEnvironment
238
+ )
239
+
240
+ if ([string]::IsNullOrWhiteSpace([string]$Normalized.Cwd)) {
241
+ return [pscustomobject]@{ Location = $null; Outcome = (Get-DiscoveryPreToolOutcome -Decision 'deny' -Code 'WORKSPACE' -Reason 'the native event carries no workspace directory') }
242
+ }
243
+ try {
244
+ $location = Resolve-DiscoveryAuthorityLocation -WorkspaceRoot ([string]$Normalized.Cwd) -Platform $HostEnvironment.Platform `
245
+ -UserHome $HostEnvironment.UserHome -LocalAppData $HostEnvironment.LocalAppData -XdgStateHome $HostEnvironment.XdgStateHome
246
+ } catch {
247
+ return [pscustomobject]@{ Location = $null; Outcome = (Get-DiscoveryPreToolOutcome -Decision 'deny' -Code 'WORKSPACE' -Reason $_.Exception.Message) }
248
+ }
249
+ return [pscustomobject]@{ Location = $location; Outcome = $null }
250
+ }
251
+
252
+ function Get-DiscoveryRootDecision {
253
+ <#
254
+ .SYNOPSIS
255
+ Decides a root call: a one-use root grant for prepare, doctor and the controlled tools; every other tool is allowed.
256
+ .DESCRIPTION
257
+ A root grant for prepare and for a controlled tool requires a current
258
+ supported capability attestation of the session; the doctor grant does not,
259
+ because the probe produces that attestation. A root call must not carry
260
+ `invocation_ref` or an identity field; the server takes host, session and
261
+ parent from the consumed grant.
262
+ #>
263
+ [CmdletBinding()]
264
+ [OutputType([pscustomobject])]
265
+ param(
266
+ [Parameter(Mandatory = $true)][pscustomobject]$Normalized,
267
+ [Parameter(Mandatory = $true)][pscustomobject]$Principal,
268
+ [Parameter(Mandatory = $true)][pscustomobject]$Tool,
269
+ [Parameter(Mandatory = $true)][hashtable]$HostEnvironment,
270
+ [Parameter(Mandatory = $true)][string]$HandlerSha256,
271
+ [Parameter(Mandatory = $true)][hashtable]$Store,
272
+ [Parameter(Mandatory = $true)][datetime]$Now,
273
+ [Parameter(Mandatory = $true)][string]$Nonce,
274
+ [Parameter(Mandatory = $true)][int]$TtlSeconds
275
+ )
276
+
277
+ $grantable = @('prepare_discovery_agent', 'run_discovery_doctor') + @(Get-DiscoveryControlledToolName)
278
+ if ($Tool.Family -ne 'mcp-discovery' -or $grantable -cnotcontains $Tool.Name) {
279
+ return Get-DiscoveryPreToolOutcome -Decision 'allow' -Code 'ROOT' -Reason "root call of $($Tool.Name); ordinary gates apply"
280
+ }
281
+ $inputs = $Normalized.ToolInput
282
+ if ($null -ne (Get-DiscoveryInputMember -ToolInput $inputs -Name 'invocation_ref')) {
283
+ return Get-DiscoveryPreToolOutcome -Decision 'deny' -Code 'INVOCATION_REF' -Reason 'a root call must not carry invocation_ref; only a started specialist names its invocation'
284
+ }
285
+ foreach ($key in (Get-DiscoveryCallerIdentityKey)) {
286
+ if ($null -ne (Get-DiscoveryInputMember -ToolInput $inputs -Name $key)) {
287
+ return Get-DiscoveryPreToolOutcome -Decision 'deny' -Code 'CALLER_IDENTITY' -Reason "tool input must not carry the identity field '$key'; identity comes from the native event"
288
+ }
289
+ }
290
+ # The operation of prepare and doctor is fixed; a controlled tool derives it from its input.
291
+ switch ($Tool.Name) {
292
+ 'prepare_discovery_agent' { $operation = 'prepare-agent' }
293
+ 'run_discovery_doctor' { $operation = 'doctor' }
294
+ default { $operation = Get-DiscoveryToolOperation -Tool $Tool.Name -ToolInput $inputs }
295
+ }
296
+ if ($null -eq $operation) {
297
+ return Get-DiscoveryPreToolOutcome -Decision 'deny' -Code 'OPERATION' -Reason "the operation of $($Tool.Name) could not be derived from its input"
298
+ }
299
+ $resolved = Resolve-DiscoveryGateLocation -Normalized $Normalized -HostEnvironment $HostEnvironment
300
+ if ($null -ne $resolved.Outcome) { return $resolved.Outcome }
301
+ if ($Tool.Name -ne 'run_discovery_doctor') {
302
+ $deficiency = Test-DiscoveryRootGrantPrecondition -Location $resolved.Location -Principal $Principal -Store $Store -Now $Now
303
+ if ($null -ne $deficiency) {
304
+ return Get-DiscoveryPreToolOutcome -Decision 'deny' -Code 'CAPABILITY' -Reason "no root grant for $($Tool.Name): $deficiency"
305
+ }
306
+ }
307
+ $digest = Get-DiscoveryInputDigest -Tool $Tool.Name -Operation $operation -Inputs $inputs
308
+ $grant = ConvertTo-DiscoveryRootGrantRecord -Principal $Principal -Tool $Tool.Name -Operation $operation -InputSha256 $digest `
309
+ -ToolUseId ([string]$Normalized.ToolUseId) -HandlerSha256 $HandlerSha256 -Now $Now -Nonce $Nonce -TtlSeconds $TtlSeconds
310
+ try {
311
+ $path = Write-DiscoveryRootGrant -Location $resolved.Location -Record $grant -Store $Store
312
+ } catch {
313
+ return Get-DiscoveryPreToolOutcome -Decision 'deny' -Code 'STORE' -Reason "the root grant could not be recorded: $($_.Exception.Message)"
314
+ }
315
+ return Get-DiscoveryPreToolOutcome -Decision 'allow' -Code 'ROOT_GRANTED' -Reason "granted root call of $($Tool.Name) $operation once" -Record $grant -RecordPath $path
316
+ }
317
+
318
+ function Get-DiscoveryPreToolDecision {
319
+ <#
320
+ .SYNOPSIS
321
+ Decides one PreToolUse event for a native discovery adapter.
322
+ .DESCRIPTION
323
+ Order of decision: envelope anomalies deny; a sentinel envelope is observed
324
+ through the sentinel decision; a root envelope (no agent_id) is allowed, with
325
+ a one-use root grant for prepare, doctor and the controlled tools; an agent
326
+ that is not a discovery specialist is allowed so ordinary gates apply, unless
327
+ it declares a discovery agent type without a usable agent identifier; for a
328
+ specialist the tool family routes (read-only natives allow; shell, mutation,
329
+ spawn, foreign MCP and unknown tools deny), a controlled discovery tool must
330
+ name a known non-root operation without caller identity fields, the
331
+ protected activation must be active, the agent's started invocation is
332
+ resolved (its first controlled call starts the single prepared invocation of
333
+ its session, parent and agent type), `invocation_ref` must name that
334
+ invocation, and only then is a one-use record written and the call allowed.
335
+ .PARAMETER Store
336
+ Seams ReadText, ListEntries, WriteText, ReplaceText, EnsureDirectory and the
337
+ optional ReadBytes or ReadAgentProfile profile seams; defaults to the real filesystem.
338
+ .OUTPUTS
339
+ [pscustomobject] Decision, Code, Reason, Record, RecordPath.
340
+ #>
341
+ [CmdletBinding()]
342
+ [OutputType([pscustomobject])]
343
+ param(
344
+ [Parameter(Mandatory = $true)][pscustomobject]$Normalized,
345
+ [Parameter(Mandatory = $true)][hashtable]$HostEnvironment,
346
+ [Parameter(Mandatory = $true)][string]$HandlerSha256,
347
+ [Parameter(Mandatory = $false)][hashtable]$Store = (Get-DiscoveryDefaultStore),
348
+ [Parameter(Mandatory = $false)][datetime]$Now = [datetime]::UtcNow,
349
+ [Parameter(Mandatory = $false)][string]$Nonce = (Get-DiscoveryAuthorizationNonce),
350
+ [Parameter(Mandatory = $false)][int]$TtlSeconds = 300
351
+ )
352
+
353
+ if ($null -ne $Normalized.Anomaly) {
354
+ return Get-DiscoveryPreToolOutcome -Decision 'deny' -Code 'ENVELOPE' -Reason ('payload anomaly - ' + (Get-DiscoveryPayloadAnomalyReason -Code $Normalized.Anomaly) + '. The gate fails closed on an envelope it cannot read.')
355
+ }
356
+ $principal = Get-DiscoveryTrustedPrincipal -Normalized $Normalized
357
+ $tool = Resolve-DiscoveryToolName -ToolName $Normalized.ToolName
358
+ # Sentinel envelopes are observed before any grant path; root calls take the grant path.
359
+ if (Test-DiscoverySentinelEnvelope -Normalized $Normalized -Tool $tool) {
360
+ $resolved = Resolve-DiscoveryGateLocation -Normalized $Normalized -HostEnvironment $HostEnvironment
361
+ if ($null -ne $resolved.Outcome) { return $resolved.Outcome }
362
+ return Get-DiscoverySentinelDecision -Normalized $Normalized -Principal $principal -Tool $tool -Location $resolved.Location -Store $Store `
363
+ -HandlerSha256 $HandlerSha256 -Now $Now -Nonce $Nonce -TtlSeconds $TtlSeconds
364
+ }
365
+ if ($principal.IsRoot) {
366
+ return Get-DiscoveryRootDecision -Normalized $Normalized -Principal $principal -Tool $tool -HostEnvironment $HostEnvironment `
367
+ -HandlerSha256 $HandlerSha256 -Store $Store -Now $Now -Nonce $Nonce -TtlSeconds $TtlSeconds
368
+ }
369
+ if ($null -eq $principal.Role) {
370
+ # A discovery agent type without a usable agent identifier cannot be bound and fails closed.
371
+ $declared = $(if ($Normalized.AgentType -is [string]) { Get-DiscoveryAgentRole -AgentType $Normalized.AgentType } else { $null })
372
+ if ($null -ne $declared) {
373
+ return Get-DiscoveryPreToolOutcome -Decision 'deny' -Code 'IDENTITY' -Reason "the discovery agent could not be attributed: the payload agent identifier is not a usable identifier for $declared"
374
+ }
375
+ return Get-DiscoveryPreToolOutcome -Decision 'allow' -Code 'NOT_DISCOVERY_AGENT' -Reason 'the calling agent is not a discovery specialist; ordinary gates apply'
376
+ }
377
+ # Family routing: only read-only natives and controlled discovery tools proceed.
378
+ switch ($tool.Family) {
379
+ 'read' { return Get-DiscoveryPreToolOutcome -Decision 'allow' -Code 'READ_ONLY' -Reason "read-only tool $($tool.Name) is permitted" }
380
+ 'shell' { return Get-DiscoveryPreToolOutcome -Decision 'deny' -Code 'SHELL' -Reason "shell, terminal, exec and evaluation tools are not available to discovery role $($principal.Role) ($($tool.Name))" }
381
+ 'mutation' { return Get-DiscoveryPreToolOutcome -Decision 'deny' -Code 'MUTATION' -Reason "generic file mutation tool $($tool.Name) is not available to discovery role $($principal.Role); draft mutations go through the controlled discovery tools" }
382
+ 'spawn' { return Get-DiscoveryPreToolOutcome -Decision 'deny' -Code 'SPAWN' -Reason "discovery role $($principal.Role) cannot launch nested agents ($($tool.Name))" }
383
+ 'mcp-other' { return Get-DiscoveryPreToolOutcome -Decision 'deny' -Code 'FOREIGN_MCP' -Reason "MCP server '$($tool.Server)' is outside the discovery grant" }
384
+ 'other' { return Get-DiscoveryPreToolOutcome -Decision 'deny' -Code 'UNKNOWN_TOOL' -Reason "tool $($tool.Name) is outside the discovery grant" }
385
+ }
386
+ if ((Get-DiscoveryRootOnlyToolName) -contains $tool.Name) {
387
+ return Get-DiscoveryPreToolOutcome -Decision 'deny' -Code 'ROOT_ONLY' -Reason "$($tool.Name) is a root-only discovery operation and cannot be used by role $($principal.Role)"
388
+ }
389
+ if ((Get-DiscoveryControlledToolName) -notcontains $tool.Name) {
390
+ return Get-DiscoveryPreToolOutcome -Decision 'deny' -Code 'UNKNOWN_TOOL' -Reason "$($tool.Name) is not a controlled discovery tool"
391
+ }
392
+ $inputs = $Normalized.ToolInput
393
+ foreach ($key in (Get-DiscoveryCallerIdentityKey)) {
394
+ if ($null -ne (Get-DiscoveryInputMember -ToolInput $inputs -Name $key)) {
395
+ return Get-DiscoveryPreToolOutcome -Decision 'deny' -Code 'CALLER_IDENTITY' -Reason "tool input must not carry the identity field '$key'; identity comes from the native event"
396
+ }
397
+ }
398
+ $operation = Get-DiscoveryToolOperation -Tool $tool.Name -ToolInput $inputs
399
+ if ($null -eq $operation) {
400
+ return Get-DiscoveryPreToolOutcome -Decision 'deny' -Code 'OPERATION' -Reason "the operation of $($tool.Name) could not be derived from its input"
401
+ }
402
+ if ((Get-DiscoveryRootOnlyOperationName) -contains $operation) {
403
+ return Get-DiscoveryPreToolOutcome -Decision 'deny' -Code 'ROOT_ONLY' -Reason "operation $operation requires the local-user principal"
404
+ }
405
+ $resolved = Resolve-DiscoveryGateLocation -Normalized $Normalized -HostEnvironment $HostEnvironment
406
+ if ($null -ne $resolved.Outcome) { return $resolved.Outcome }
407
+ $location = $resolved.Location
408
+ $activation = Read-DiscoveryActivationState -Location $location -ReadText $Store.ReadText
409
+ if ($activation.Status -ne 'active') {
410
+ return Get-DiscoveryPreToolOutcome -Decision 'deny' -Code 'ACTIVATION' -Reason "discovery is not active for this workspace ($($activation.Status): $($activation.Reason))"
411
+ }
412
+ $invocation = Find-DiscoveryAgentInvocation -Location $location -Principal $principal -Store $Store
413
+ if ($null -eq $invocation) {
414
+ # First controlled call: bind the agent to the single prepared invocation and start it.
415
+ $started = Start-DiscoveryPreparedInvocation -Location $location -Principal $principal -AgentId $principal.AgentId -AgentType ([string]$Normalized.AgentType) `
416
+ -Activation $activation.Record -WorkspaceRoot ([string]$Normalized.Cwd) -Store $Store -HandlerSha256 $HandlerSha256 -Now $Now -Nonce $Nonce
417
+ if ($started.Decision -ne 'attest') {
418
+ return Get-DiscoveryPreToolOutcome -Decision 'deny' -Code $started.Code -Reason $started.Reason
419
+ }
420
+ $invocation = [pscustomobject]@{ Record = $started.Invocation; Directory = $started.Directory }
421
+ }
422
+ $record = $invocation.Record
423
+ $invocationRef = Get-DiscoveryInputMember -ToolInput $inputs -Name 'invocation_ref'
424
+ if ($invocationRef -isnot [string] -or $invocationRef -cne [string]$record.invocation_id) {
425
+ return Get-DiscoveryPreToolOutcome -Decision 'deny' -Code 'INVOCATION_REF' -Reason "tool input must carry invocation_ref $($record.invocation_id), the started invocation of agent $($principal.AgentId)"
426
+ }
427
+ $sameBinding = ([string]$record.workspace.workspace_sha256 -eq $location.WorkspaceSha256) -and
428
+ ([string]$record.workspace.profile_path -eq [string]$activation.Record.profile_path) -and
429
+ ([string]$record.workspace.profile_sha256 -eq [string]$activation.Record.profile_sha256)
430
+ if (-not $sameBinding) {
431
+ return Get-DiscoveryPreToolOutcome -Decision 'deny' -Code 'BINDING' -Reason 'the invocation is bound to another workspace or profile than the current activation'
432
+ }
433
+ $digest = Get-DiscoveryInputDigest -Tool $tool.Name -Operation $operation -Inputs $inputs
434
+ $requestId = [string]$Normalized.ToolUseId
435
+ if ($requestId -cnotmatch $script:CorrelationIdPattern) { $requestId = "event-$Nonce" }
436
+ $authorization = ConvertTo-DiscoveryToolAuthorizationRecord -Principal $principal -Invocation $record -Tool $tool.Name -Operation $operation `
437
+ -InputSha256 $digest -RequestId $requestId -EventId $requestId -HandlerSha256 $HandlerSha256 -Now $Now -Nonce $Nonce -TtlSeconds $TtlSeconds
438
+ $directory = "$($invocation.Directory)/authorizations"
439
+ $path = "$directory/$($authorization['authorization_ref']).json"
440
+ try {
441
+ Invoke-DiscoveryStoreSeam -Seam $Store.EnsureDirectory -Arguments @($directory)
442
+ Invoke-DiscoveryStoreSeam -Seam $Store.WriteText -Arguments @($path, (($authorization | ConvertTo-Json -Depth 8) + "`n"))
443
+ } catch {
444
+ return Get-DiscoveryPreToolOutcome -Decision 'deny' -Code 'STORE' -Reason "the one-use authorization could not be recorded: $($_.Exception.Message)"
445
+ }
446
+ return Get-DiscoveryPreToolOutcome -Decision 'allow' -Code 'AUTHORIZED' -Reason "authorized $($tool.Name) $operation for $($principal.Role)" -Record $authorization -RecordPath $path
447
+ }
448
+
449
+ function ConvertTo-DiscoveryPreToolHookOutput {
450
+ <#
451
+ .SYNOPSIS
452
+ Renders a decision as the PreToolUse hookSpecificOutput envelope with the hook's reason prefix.
453
+ #>
454
+ [CmdletBinding()]
455
+ [OutputType([System.Collections.Specialized.OrderedDictionary])]
456
+ param(
457
+ [Parameter(Mandatory = $true)][pscustomobject]$Outcome,
458
+ [Parameter(Mandatory = $true)][string]$ReasonPrefix
459
+ )
460
+
461
+ if ($Outcome.Decision -eq 'allow') {
462
+ return [ordered]@{ hookSpecificOutput = [ordered]@{ hookEventName = 'PreToolUse'; permissionDecision = 'allow' } }
463
+ }
464
+ return [ordered]@{
465
+ hookSpecificOutput = [ordered]@{
466
+ hookEventName = 'PreToolUse'
467
+ permissionDecision = 'deny'
468
+ permissionDecisionReason = "$ReasonPrefix $($Outcome.Reason)"
469
+ }
470
+ }
471
+ }
472
+
473
+ Export-ModuleMember -Function `
474
+ Find-DiscoveryAgentInvocation, `
475
+ ConvertTo-DiscoveryToolAuthorizationRecord, `
476
+ Get-DiscoveryDefaultStore, `
477
+ Get-DiscoveryAuthorizationNonce, `
478
+ Get-DiscoveryPreToolDecision, `
479
+ ConvertTo-DiscoveryPreToolHookOutput