@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.
- package/out/mcp-server.js +13347 -7926
- package/package.json +1 -1
- package/resources/claude-customizations/.claude/agents/discovery-capability-sentinel.md +34 -0
- package/resources/claude-customizations/.claude/agents/legacy-parity-analyst.md +101 -43
- package/resources/claude-customizations/.claude/agents/migration-coverage-reviewer.md +107 -43
- package/resources/claude-customizations/.claude/agents/parallel-orchestrator.md +14 -0
- package/resources/claude-customizations/.claude/agents/parallel-planner.md +21 -1
- package/resources/claude-customizations/.claude/agents/pr-author.md +17 -1
- package/resources/claude-customizations/.claude/agents/requirements-reconciler.md +117 -42
- package/resources/claude-customizations/.claude/agents/runtime-characterization-analyst.md +103 -43
- package/resources/claude-customizations/.claude/hooks/discovery-capability-probe.ps1 +102 -0
- package/resources/claude-customizations/.claude/hooks/enforce-discovery-artifact-gate.ps1 +69 -194
- package/resources/claude-customizations/.claude/hooks/enforce-discovery-planning-gate.ps1 +87 -0
- package/resources/claude-customizations/.claude/hooks/enforce-epic-merge-gate.ps1 +7 -1
- package/resources/claude-customizations/.claude/hooks/enforce-model-routing-receipt.ps1 +6 -3
- package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate-epic-scope.ps1 +168 -48
- package/resources/claude-customizations/.claude/hooks/enforce-orchestration-preimplementation-gate.ps1 +1 -1
- package/resources/claude-customizations/.claude/hooks/enforce-powershell-batch-budget.ps1 +59 -20
- package/resources/claude-customizations/.claude/hooks/enforce-pr-author-command-allowlist.ps1 +44 -3
- package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill-helpers.ps1 +60 -4
- package/resources/claude-customizations/.claude/hooks/enforce-pr-author-skill.epic-base-branch.ps1 +4 -2
- package/resources/claude-customizations/.claude/hooks/enforce-python-batch-budget.ps1 +59 -20
- package/resources/claude-customizations/.claude/hooks/record-discovery-agent-start.ps1 +132 -0
- package/resources/claude-customizations/.claude/hooks/validate-discovery-artifact-gate.ps1 +56 -212
- package/resources/claude-customizations/.claude/hooks/validate-orchestrator-output-rejection.ps1 +172 -0
- package/resources/claude-customizations/.claude/hooks/validate-orchestrator-output.ps1 +9 -1
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadius.psm1 +9 -3
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusAggregate.psm1 +154 -0
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusCombination.psm1 +228 -0
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusExtraction.psm1 +1 -0
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusNormalization.psm1 +4 -2
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusScheduling.psm1 +63 -16
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusTokenShape.psm1 +56 -1
- package/resources/claude-customizations/.claude/lib/blast-radius/BlastRadiusWriteIntent.psm1 +4 -1
- package/resources/claude-customizations/.claude/lib/codex-routing/CodexDeployment.psm1 +5 -1
- package/resources/claude-customizations/.claude/lib/codex-routing/CodexDiscoveryRouting.psm1 +161 -0
- package/resources/claude-customizations/.claude/lib/codex-routing/CodexTopology.psm1 +25 -2
- package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryAuthority.psd1 +90 -0
- package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryAuthority.psm1 +493 -0
- package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryAuthorization.psm1 +479 -0
- package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryCapability.psm1 +468 -0
- package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryEncoding.psm1 +156 -0
- package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryManifest.psm1 +384 -0
- package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryObservation.psm1 +217 -0
- package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryPayload.psm1 +276 -0
- package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryPlanning.psm1 +336 -0
- package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryRootGrant.psm1 +164 -0
- package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryStart.psm1 +440 -0
- package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryStartRecord.psm1 +296 -0
- package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryStop.psm1 +336 -0
- package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryToolRouting.psm1 +228 -0
- package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryValidation.psm1 +188 -341
- package/resources/claude-customizations/.claude/lib/worktree-resolution/HookTargetWorktree.psm1 +391 -0
- package/resources/claude-customizations/.claude/rules/orchestrator-state.md +1 -0
- package/resources/claude-customizations/.claude/rules/parallel-orchestration.md +150 -18
- package/resources/claude-customizations/.claude/settings.json +32 -3
- package/resources/claude-customizations/.claude/skills/adjudicate-unspecified-behavior/SKILL.md +135 -0
- package/resources/claude-customizations/.claude/skills/characterize-runtime-behavior/SKILL.md +134 -0
- package/resources/claude-customizations/.claude/skills/discovery-behavior-reconciliation/SKILL.md +28 -47
- package/resources/claude-customizations/.claude/skills/discovery-coverage-ledger/SKILL.md +32 -48
- package/resources/claude-customizations/.claude/skills/discovery-parity-matrix/SKILL.md +28 -45
- package/resources/claude-customizations/.claude/skills/discovery-repo-inventory/SKILL.md +28 -62
- package/resources/claude-customizations/.claude/skills/discovery-runtime-characterization/SKILL.md +28 -45
- package/resources/claude-customizations/.claude/skills/discovery-validate-artifacts/SKILL.md +28 -61
- package/resources/claude-customizations/.claude/skills/discovery-workflow/SKILL.md +55 -139
- package/resources/claude-customizations/.claude/skills/execution-environment/SKILL.md +24 -9
- package/resources/claude-customizations/.claude/skills/extract-feature-contract/SKILL.md +133 -0
- package/resources/claude-customizations/.claude/skills/generate-acceptance-scenarios/SKILL.md +132 -0
- package/resources/claude-customizations/.claude/skills/initialize-discovery-workspace/SKILL.md +131 -0
- package/resources/claude-customizations/.claude/skills/inventory-legacy-system/SKILL.md +131 -0
- package/resources/claude-customizations/.claude/skills/orchestrate/SKILL.md +5 -4
- package/resources/claude-customizations/.claude/skills/parallel-add/SKILL.md +21 -1
- package/resources/claude-customizations/.claude/skills/parallel-close/SKILL.md +11 -1
- package/resources/claude-customizations/.claude/skills/parallel-orchestrate/SKILL.md +34 -0
- package/resources/claude-customizations/.claude/skills/parallel-plan/SKILL.md +69 -6
- package/resources/claude-customizations/.claude/skills/parallel-remove/SKILL.md +15 -0
- package/resources/claude-customizations/.claude/skills/parallel-run/SKILL.md +6 -0
- package/resources/claude-customizations/.claude/skills/pr-author/SKILL.md +21 -4
- package/resources/claude-customizations/.claude/skills/reconcile-parity-matrix/SKILL.md +131 -0
- package/resources/claude-customizations/.claude/skills/review-discovery-coverage/SKILL.md +129 -0
- package/resources/claude-customizations/.claude/skills/validate-discovery-artifacts/SKILL.md +127 -0
- package/resources/claude-customizations/config/blast-radius.json +6 -5
- package/resources/claude-customizations/config/orchestration-routing.json +61 -1
- package/resources/claude-customizations/pack-manifests/core.json +33 -1
- package/resources/codex-and-agents-customizations/.agents/skills/adjudicate-unspecified-behavior/SKILL.md +135 -0
- package/resources/codex-and-agents-customizations/.agents/skills/atomic-planner/SKILL.md +7 -0
- package/resources/codex-and-agents-customizations/.agents/skills/characterize-runtime-behavior/SKILL.md +134 -0
- package/resources/codex-and-agents-customizations/.agents/skills/discovery-behavior-reconciliation/SKILL.md +46 -0
- package/resources/codex-and-agents-customizations/.agents/skills/discovery-coverage-ledger/SKILL.md +50 -0
- package/resources/codex-and-agents-customizations/.agents/skills/discovery-parity-matrix/SKILL.md +46 -0
- package/resources/codex-and-agents-customizations/.agents/skills/discovery-repo-inventory/SKILL.md +46 -0
- package/resources/codex-and-agents-customizations/.agents/skills/discovery-runtime-characterization/SKILL.md +46 -0
- package/resources/codex-and-agents-customizations/.agents/skills/discovery-validate-artifacts/SKILL.md +46 -0
- package/resources/codex-and-agents-customizations/.agents/skills/discovery-workflow/SKILL.md +62 -0
- package/resources/codex-and-agents-customizations/.agents/skills/execution-environment/SKILL.md +24 -9
- package/resources/codex-and-agents-customizations/.agents/skills/extract-feature-contract/SKILL.md +133 -0
- package/resources/codex-and-agents-customizations/.agents/skills/generate-acceptance-scenarios/SKILL.md +132 -0
- package/resources/codex-and-agents-customizations/.agents/skills/initialize-discovery-workspace/SKILL.md +131 -0
- package/resources/codex-and-agents-customizations/.agents/skills/inventory-legacy-system/SKILL.md +131 -0
- package/resources/codex-and-agents-customizations/.agents/skills/orchestrator-workflow/SKILL.md +8 -0
- package/resources/codex-and-agents-customizations/.agents/skills/reconcile-parity-matrix/SKILL.md +131 -0
- package/resources/codex-and-agents-customizations/.agents/skills/review-discovery-coverage/SKILL.md +129 -0
- package/resources/codex-and-agents-customizations/.agents/skills/validate-discovery-artifacts/SKILL.md +127 -0
- package/resources/codex-and-agents-customizations/.codex/agents/discovery-capability-sentinel.toml +51 -0
- package/resources/codex-and-agents-customizations/.codex/agents/legacy-parity-analyst-c1.toml +148 -0
- package/resources/codex-and-agents-customizations/.codex/agents/legacy-parity-analyst-c2.toml +148 -0
- package/resources/codex-and-agents-customizations/.codex/agents/legacy-parity-analyst-c3-elevated.toml +148 -0
- package/resources/codex-and-agents-customizations/.codex/agents/legacy-parity-analyst-c3.toml +148 -0
- package/resources/codex-and-agents-customizations/.codex/agents/legacy-parity-analyst-c4.toml +148 -0
- package/resources/codex-and-agents-customizations/.codex/agents/legacy-parity-analyst.toml +148 -0
- package/resources/codex-and-agents-customizations/.codex/agents/migration-coverage-reviewer-c1.toml +154 -0
- package/resources/codex-and-agents-customizations/.codex/agents/migration-coverage-reviewer-c2.toml +154 -0
- package/resources/codex-and-agents-customizations/.codex/agents/migration-coverage-reviewer-c3-elevated.toml +154 -0
- package/resources/codex-and-agents-customizations/.codex/agents/migration-coverage-reviewer-c3.toml +154 -0
- package/resources/codex-and-agents-customizations/.codex/agents/migration-coverage-reviewer-c4.toml +154 -0
- package/resources/codex-and-agents-customizations/.codex/agents/migration-coverage-reviewer.toml +154 -0
- package/resources/codex-and-agents-customizations/.codex/agents/requirements-reconciler-c1.toml +164 -0
- package/resources/codex-and-agents-customizations/.codex/agents/requirements-reconciler-c2.toml +164 -0
- package/resources/codex-and-agents-customizations/.codex/agents/requirements-reconciler-c3-elevated.toml +164 -0
- package/resources/codex-and-agents-customizations/.codex/agents/requirements-reconciler-c3.toml +164 -0
- package/resources/codex-and-agents-customizations/.codex/agents/requirements-reconciler-c4.toml +164 -0
- package/resources/codex-and-agents-customizations/.codex/agents/requirements-reconciler.toml +164 -0
- package/resources/codex-and-agents-customizations/.codex/agents/runtime-characterization-analyst-c1.toml +150 -0
- package/resources/codex-and-agents-customizations/.codex/agents/runtime-characterization-analyst-c2.toml +150 -0
- package/resources/codex-and-agents-customizations/.codex/agents/runtime-characterization-analyst-c3-elevated.toml +150 -0
- package/resources/codex-and-agents-customizations/.codex/agents/runtime-characterization-analyst-c3.toml +150 -0
- package/resources/codex-and-agents-customizations/.codex/agents/runtime-characterization-analyst-c4.toml +150 -0
- package/resources/codex-and-agents-customizations/.codex/agents/runtime-characterization-analyst.toml +150 -0
- package/resources/codex-and-agents-customizations/.codex/config.toml +33 -3
- package/resources/codex-and-agents-customizations/.codex/hooks/codex-agent-profile-attestation.ps1 +22 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/discovery-capability-probe.ps1 +97 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/discovery-start-attestation.ps1 +171 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-codex-model-routing.ps1 +4 -1
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-discovery-artifact-gate.ps1 +187 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-discovery-planning-gate.ps1 +149 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate-epic-scope.ps1 +239 -2
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-orchestration-preimplementation-gate.ps1 +6 -15
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-powershell-batch-budget.ps1 +54 -15
- package/resources/codex-and-agents-customizations/.codex/hooks/enforce-python-batch-budget.ps1 +54 -15
- package/resources/codex-and-agents-customizations/.codex/hooks/hook-target-worktree.ps1 +369 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/record-subagent-routing-attestation.ps1 +8 -0
- package/resources/codex-and-agents-customizations/.codex/hooks/validate-codex-subagent-routing.ps1 +22 -1
- package/resources/codex-and-agents-customizations/.codex/hooks/validate-discovery-agent-output.ps1 +178 -0
- package/resources/codex-and-agents-customizations/.codex/scripts/Resolve-CodexTopology.ps1 +11 -1
- package/resources/codex-and-agents-customizations/pack-manifests/core.json +63 -1
- package/resources/config/orchestration-routing.json +61 -1
- package/resources/customizations/.github/agents/discovery-capability-sentinel.agent.md +34 -0
- package/resources/customizations/.github/agents/legacy-parity-analyst.agent.md +122 -0
- package/resources/customizations/.github/agents/migration-coverage-reviewer.agent.md +128 -0
- package/resources/customizations/.github/agents/requirements-reconciler.agent.md +138 -0
- package/resources/customizations/.github/agents/runtime-characterization-analyst.agent.md +124 -0
- package/resources/customizations/.github/hooks/discovery-agent-start.ps1 +139 -0
- package/resources/customizations/.github/hooks/discovery-agent-stop.ps1 +123 -0
- package/resources/customizations/.github/hooks/discovery-capability-probe.ps1 +98 -0
- package/resources/customizations/.github/hooks/discovery-planning.ps1 +91 -0
- package/resources/customizations/.github/hooks/discovery-pre-tool.ps1 +123 -0
- package/resources/customizations/.github/hooks/discovery.json +35 -0
- package/resources/customizations/.github/instructions/mermaid.instructions.md +85 -0
- package/resources/customizations/.github/prompts/execute-hard-lock.prompt.md +105 -0
- package/resources/customizations/.github/skills/adjudicate-unspecified-behavior/SKILL.md +135 -0
- package/resources/customizations/.github/skills/characterize-runtime-behavior/SKILL.md +134 -0
- package/resources/customizations/.github/skills/discovery-behavior-reconciliation/SKILL.md +46 -0
- package/resources/customizations/.github/skills/discovery-coverage-ledger/SKILL.md +50 -0
- package/resources/customizations/.github/skills/discovery-parity-matrix/SKILL.md +46 -0
- package/resources/customizations/.github/skills/discovery-repo-inventory/SKILL.md +46 -0
- package/resources/customizations/.github/skills/discovery-runtime-characterization/SKILL.md +46 -0
- package/resources/customizations/.github/skills/discovery-validate-artifacts/SKILL.md +46 -0
- package/resources/customizations/.github/skills/discovery-workflow/SKILL.md +62 -0
- package/resources/customizations/.github/skills/execution-environment/SKILL.md +24 -9
- package/resources/customizations/.github/skills/extract-feature-contract/SKILL.md +133 -0
- package/resources/customizations/.github/skills/generate-acceptance-scenarios/SKILL.md +132 -0
- package/resources/customizations/.github/skills/initialize-discovery-workspace/SKILL.md +131 -0
- package/resources/customizations/.github/skills/inventory-legacy-system/SKILL.md +131 -0
- package/resources/customizations/.github/skills/reconcile-parity-matrix/SKILL.md +131 -0
- package/resources/customizations/.github/skills/review-discovery-coverage/SKILL.md +129 -0
- package/resources/customizations/.github/skills/validate-discovery-artifacts/SKILL.md +127 -0
- package/resources/lib/codex-routing/CodexDeployment.psm1 +5 -1
- package/resources/lib/codex-routing/CodexDiscoveryRouting.psm1 +161 -0
- package/resources/lib/codex-routing/CodexTopology.psm1 +25 -2
- package/resources/lib/discovery-validation/DiscoveryAuthority.psd1 +90 -0
- package/resources/lib/discovery-validation/DiscoveryAuthority.psm1 +493 -0
- package/resources/lib/discovery-validation/DiscoveryAuthorization.psm1 +479 -0
- package/resources/lib/discovery-validation/DiscoveryCapability.psm1 +468 -0
- package/resources/lib/discovery-validation/DiscoveryEncoding.psm1 +156 -0
- package/resources/lib/discovery-validation/DiscoveryManifest.psm1 +384 -0
- package/resources/lib/discovery-validation/DiscoveryObservation.psm1 +217 -0
- package/resources/lib/discovery-validation/DiscoveryPayload.psm1 +276 -0
- package/resources/lib/discovery-validation/DiscoveryPlanning.psm1 +336 -0
- package/resources/lib/discovery-validation/DiscoveryRootGrant.psm1 +164 -0
- package/resources/lib/discovery-validation/DiscoveryStart.psm1 +440 -0
- package/resources/lib/discovery-validation/DiscoveryStartRecord.psm1 +296 -0
- package/resources/lib/discovery-validation/DiscoveryStop.psm1 +336 -0
- package/resources/lib/discovery-validation/DiscoveryToolRouting.psm1 +228 -0
- package/resources/powershell/PoshQC/PoshQC.Parallel.psm1 +452 -0
- package/resources/powershell/PoshQC/PoshQC.Shard.psm1 +106 -8
- package/resources/powershell/PoshQC/PoshQC.Testing.psm1 +12 -0
- package/resources/powershell/PoshQC/PoshQC.psd1 +6 -1
- package/resources/powershell/PoshQC/PoshQC.psm1 +7 -1
- package/resources/powershell/PoshQC/README.md +20 -1
package/resources/claude-customizations/.claude/lib/discovery-validation/DiscoveryAuthorization.psm1
ADDED
|
@@ -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
|