@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
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
<#
|
|
2
|
+
.SYNOPSIS
|
|
3
|
+
Host event-envelope normalization and trusted principal attribution for discovery hooks.
|
|
4
|
+
|
|
5
|
+
.DESCRIPTION
|
|
6
|
+
Claude Code, Codex and the GitHub Copilot harness deliver hook events as JSON
|
|
7
|
+
envelopes with host-specific key spellings. This module normalizes each
|
|
8
|
+
supported envelope into one shape (host, event, session, agent, parent, tool
|
|
9
|
+
name, tool input, working directory) and derives the trusted principal from
|
|
10
|
+
the payload alone: the host is fixed by which hook file runs, the session is
|
|
11
|
+
the envelope session, a specialist is an envelope that carries an `agent_id`
|
|
12
|
+
with a discovery `agent_type`, the root is an envelope without `agent_id`,
|
|
13
|
+
and the parent is the envelope parent when the host supplies one, else the
|
|
14
|
+
literal `root`. Identity never comes from caller arguments, tool input or the
|
|
15
|
+
process environment; when the envelope carries an anomaly the principal is
|
|
16
|
+
unattributed and the calling hook fails closed.
|
|
17
|
+
|
|
18
|
+
Malformed envelopes are typed anomalies, never silent nulls, so a hook can emit
|
|
19
|
+
one deny reason per contract leg that drifted.
|
|
20
|
+
|
|
21
|
+
.NOTES
|
|
22
|
+
Issue #980. Compatible with PowerShell 7+. No filesystem access, no subprocess,
|
|
23
|
+
no network, no wall-clock read, no environment read.
|
|
24
|
+
CONVENTION: this module fails fast at module scope and imports its siblings with -ErrorAction Stop.
|
|
25
|
+
#>
|
|
26
|
+
|
|
27
|
+
Set-StrictMode -Version Latest
|
|
28
|
+
$ErrorActionPreference = 'Stop'
|
|
29
|
+
|
|
30
|
+
$script:SupportedHosts = @('claude-code', 'codex', 'github-copilot')
|
|
31
|
+
$script:RootParentAgentId = 'root'
|
|
32
|
+
# Identifier grammar shared with the sibling modules: one leading alphanumeric,
|
|
33
|
+
# then alphanumerics, dot, underscore, colon or hyphen.
|
|
34
|
+
$script:CorrelationIdPattern = '^[A-Za-z0-9][A-Za-z0-9._:-]*$'
|
|
35
|
+
# The four discovery roles with their optional Codex complexity suffix.
|
|
36
|
+
$script:DiscoveryAgentTypePattern = '^(?<role>legacy-parity-analyst|runtime-characterization-analyst|requirements-reconciler|migration-coverage-reviewer)(?:-c(?:1|2|3|4|3-elevated))?$'
|
|
37
|
+
# Per-host envelope key spellings, most specific first. Claude Code and Codex use
|
|
38
|
+
# snake_case; the Copilot harness uses camelCase and may fall back to snake_case
|
|
39
|
+
# for fields it shares with the Claude shape. Only Copilot documents a parent
|
|
40
|
+
# identifier; the snake_case spelling is read on every host.
|
|
41
|
+
$script:EnvelopeKeys = @{
|
|
42
|
+
'claude-code' = @{ SessionId = @('session_id'); Event = @('hook_event_name'); ToolName = @('tool_name'); ToolInput = @('tool_input'); Cwd = @('cwd'); AgentId = @('agent_id'); AgentType = @('agent_type'); ToolUseId = @('tool_use_id'); ParentAgentId = @('parent_agent_id') }
|
|
43
|
+
'codex' = @{ SessionId = @('session_id'); Event = @('hook_event_name'); ToolName = @('tool_name'); ToolInput = @('tool_input'); Cwd = @('cwd'); AgentId = @('agent_id'); AgentType = @('agent_type'); ToolUseId = @('tool_use_id'); ParentAgentId = @('parent_agent_id') }
|
|
44
|
+
'github-copilot' = @{ SessionId = @('sessionId', 'session_id'); Event = @('hookEventName', 'hook_event_name'); ToolName = @('toolName', 'tool_name'); ToolInput = @('toolInput', 'tool_input'); Cwd = @('cwd'); AgentId = @('agentId', 'agent_id'); AgentType = @('agentType', 'agent_type'); ToolUseId = @('toolUseId', 'tool_use_id'); ParentAgentId = @('parentAgentId', 'parent_agent_id') }
|
|
45
|
+
}
|
|
46
|
+
$script:AnomalyReasonText = @{
|
|
47
|
+
'EmptyEnvelope' = 'the hook received no envelope object'
|
|
48
|
+
'UnsupportedHost' = 'the hook received an envelope for a host that has no native discovery adapter'
|
|
49
|
+
'MissingSessionId' = 'the hook received an envelope without a trusted session identifier'
|
|
50
|
+
'MissingEvent' = 'the hook received an envelope without a hook event name'
|
|
51
|
+
'NonObjectToolInput' = 'the hook received a tool event whose tool input is not an object'
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function Get-DiscoveryEnvelopeMember {
|
|
55
|
+
<#
|
|
56
|
+
.SYNOPSIS
|
|
57
|
+
Returns the first present member among Names on a hashtable or PSCustomObject, else $null.
|
|
58
|
+
#>
|
|
59
|
+
[CmdletBinding()]
|
|
60
|
+
[OutputType([object], [object[]])]
|
|
61
|
+
param(
|
|
62
|
+
[Parameter(Mandatory = $true)][object]$Envelope,
|
|
63
|
+
[Parameter(Mandatory = $true)][string[]]$Names
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
# The first spelling that exists wins, even when its value is null; the unary
|
|
67
|
+
# comma keeps an array value intact across the function boundary.
|
|
68
|
+
foreach ($name in $Names) {
|
|
69
|
+
if ($Envelope -is [System.Collections.IDictionary]) {
|
|
70
|
+
if ($Envelope.Contains($name)) {
|
|
71
|
+
if ($null -eq $Envelope[$name]) { return $null }
|
|
72
|
+
return , $Envelope[$name]
|
|
73
|
+
}
|
|
74
|
+
} elseif ($null -ne $Envelope.PSObject.Properties[$name]) {
|
|
75
|
+
$member = $Envelope.PSObject.Properties[$name].Value
|
|
76
|
+
if ($null -eq $member) { return $null }
|
|
77
|
+
return , $member
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return $null
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function Get-DiscoveryPayloadAnomalyReason {
|
|
84
|
+
<#
|
|
85
|
+
.SYNOPSIS
|
|
86
|
+
Maps an anomaly code to the deny reason text a hook emits.
|
|
87
|
+
#>
|
|
88
|
+
[CmdletBinding()]
|
|
89
|
+
[OutputType([string])]
|
|
90
|
+
param([Parameter(Mandatory = $true)][string]$Code)
|
|
91
|
+
|
|
92
|
+
if (-not $script:AnomalyReasonText.ContainsKey($Code)) {
|
|
93
|
+
throw "Unknown discovery payload anomaly code: $Code"
|
|
94
|
+
}
|
|
95
|
+
return $script:AnomalyReasonText[$Code]
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function Test-DiscoverySupportedHost {
|
|
99
|
+
<#
|
|
100
|
+
.SYNOPSIS
|
|
101
|
+
True when HostName names a host with a native discovery adapter.
|
|
102
|
+
#>
|
|
103
|
+
[CmdletBinding()]
|
|
104
|
+
[OutputType([bool])]
|
|
105
|
+
param([Parameter(Mandatory = $false)][AllowNull()][AllowEmptyString()][string]$HostName)
|
|
106
|
+
|
|
107
|
+
return $script:SupportedHosts -contains $HostName
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function Get-DiscoveryAgentRole {
|
|
111
|
+
<#
|
|
112
|
+
.SYNOPSIS
|
|
113
|
+
Returns the discovery role named by a native agent type, or $null for any other agent.
|
|
114
|
+
.DESCRIPTION
|
|
115
|
+
`legacy-parity-analyst-c3` and `legacy-parity-analyst` both name the
|
|
116
|
+
legacy-parity-analyst role; the comparison is case-sensitive. The agent
|
|
117
|
+
type is the payload `agent_type` the host reports for the running agent.
|
|
118
|
+
#>
|
|
119
|
+
[CmdletBinding()]
|
|
120
|
+
[OutputType([string])]
|
|
121
|
+
param([Parameter(Mandatory = $false)][AllowNull()][AllowEmptyString()][string]$AgentType)
|
|
122
|
+
|
|
123
|
+
if ([string]::IsNullOrWhiteSpace($AgentType)) { return $null }
|
|
124
|
+
$match = [regex]::Match($AgentType, $script:DiscoveryAgentTypePattern)
|
|
125
|
+
if (-not $match.Success) { return $null }
|
|
126
|
+
return $match.Groups['role'].Value
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function ConvertFrom-DiscoveryHostEnvelope {
|
|
130
|
+
<#
|
|
131
|
+
.SYNOPSIS
|
|
132
|
+
Normalizes one host event envelope into the shared discovery shape.
|
|
133
|
+
.DESCRIPTION
|
|
134
|
+
Returns an object with HostName, Event, SessionId, AgentId, AgentType,
|
|
135
|
+
ParentAgentId, ToolName, ToolInput, Cwd, ToolUseId and Anomaly. Anomaly is
|
|
136
|
+
$null for a usable envelope or one of EmptyEnvelope, UnsupportedHost,
|
|
137
|
+
MissingSessionId, MissingEvent and NonObjectToolInput (a PreToolUse event
|
|
138
|
+
whose tool input is absent or not an object). Property-level absence in a
|
|
139
|
+
well-formed tool input is not an anomaly; that is each hook's own scope filter.
|
|
140
|
+
#>
|
|
141
|
+
[CmdletBinding()]
|
|
142
|
+
[OutputType([pscustomobject])]
|
|
143
|
+
param(
|
|
144
|
+
[Parameter(Mandatory = $true)][AllowNull()][AllowEmptyString()][string]$HostName,
|
|
145
|
+
[Parameter(Mandatory = $false)][AllowNull()][object]$Envelope
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
$result = [ordered]@{
|
|
149
|
+
HostName = $HostName; Event = $null; SessionId = $null; AgentId = $null; AgentType = $null; ParentAgentId = $null
|
|
150
|
+
ToolName = $null; ToolInput = $null; Cwd = $null; ToolUseId = $null; Anomaly = $null
|
|
151
|
+
}
|
|
152
|
+
# Decision order: host support, envelope presence, then the trusted fields
|
|
153
|
+
# a hook cannot proceed without (session, event), then tool-input shape.
|
|
154
|
+
if (-not (Test-DiscoverySupportedHost -HostName $HostName)) {
|
|
155
|
+
$result.Anomaly = 'UnsupportedHost'
|
|
156
|
+
return [pscustomobject]$result
|
|
157
|
+
}
|
|
158
|
+
$isObject = ($Envelope -is [System.Collections.IDictionary]) -or ($Envelope -is [System.Management.Automation.PSCustomObject])
|
|
159
|
+
if (-not $isObject) {
|
|
160
|
+
$result.Anomaly = 'EmptyEnvelope'
|
|
161
|
+
return [pscustomobject]$result
|
|
162
|
+
}
|
|
163
|
+
$keys = $script:EnvelopeKeys[$HostName]
|
|
164
|
+
$result.SessionId = Get-DiscoveryEnvelopeMember -Envelope $Envelope -Names $keys.SessionId
|
|
165
|
+
$result.Event = Get-DiscoveryEnvelopeMember -Envelope $Envelope -Names $keys.Event
|
|
166
|
+
$result.ToolName = Get-DiscoveryEnvelopeMember -Envelope $Envelope -Names $keys.ToolName
|
|
167
|
+
$result.ToolInput = Get-DiscoveryEnvelopeMember -Envelope $Envelope -Names $keys.ToolInput
|
|
168
|
+
$result.Cwd = Get-DiscoveryEnvelopeMember -Envelope $Envelope -Names $keys.Cwd
|
|
169
|
+
$result.AgentId = Get-DiscoveryEnvelopeMember -Envelope $Envelope -Names $keys.AgentId
|
|
170
|
+
$result.AgentType = Get-DiscoveryEnvelopeMember -Envelope $Envelope -Names $keys.AgentType
|
|
171
|
+
$result.ParentAgentId = Get-DiscoveryEnvelopeMember -Envelope $Envelope -Names $keys.ParentAgentId
|
|
172
|
+
$result.ToolUseId = Get-DiscoveryEnvelopeMember -Envelope $Envelope -Names $keys.ToolUseId
|
|
173
|
+
if ($result.SessionId -isnot [string] -or [string]::IsNullOrWhiteSpace($result.SessionId)) {
|
|
174
|
+
$result.Anomaly = 'MissingSessionId'
|
|
175
|
+
} elseif ($result.Event -isnot [string] -or [string]::IsNullOrWhiteSpace($result.Event)) {
|
|
176
|
+
$result.Anomaly = 'MissingEvent'
|
|
177
|
+
} elseif ($result.Event -eq 'PreToolUse') {
|
|
178
|
+
$toolInputIsObject = ($result.ToolInput -is [System.Collections.IDictionary]) -or ($result.ToolInput -is [System.Management.Automation.PSCustomObject])
|
|
179
|
+
if (-not $toolInputIsObject) { $result.Anomaly = 'NonObjectToolInput' }
|
|
180
|
+
}
|
|
181
|
+
return [pscustomobject]$result
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function ConvertFrom-DiscoveryHostPayloadText {
|
|
185
|
+
<#
|
|
186
|
+
.SYNOPSIS
|
|
187
|
+
Parses raw envelope JSON text and normalizes it for HostName.
|
|
188
|
+
.DESCRIPTION
|
|
189
|
+
Empty, whitespace-only or unparseable text yields the EmptyEnvelope anomaly
|
|
190
|
+
rather than an exception, so a hook can deny with a reason.
|
|
191
|
+
#>
|
|
192
|
+
[CmdletBinding()]
|
|
193
|
+
[OutputType([pscustomobject])]
|
|
194
|
+
param(
|
|
195
|
+
[Parameter(Mandatory = $true)][AllowNull()][AllowEmptyString()][string]$HostName,
|
|
196
|
+
[Parameter(Mandatory = $false)][AllowNull()][AllowEmptyString()][string]$Text
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
$envelope = $null
|
|
200
|
+
if (-not [string]::IsNullOrWhiteSpace($Text)) {
|
|
201
|
+
try {
|
|
202
|
+
$envelope = ConvertFrom-Json -InputObject $Text -Depth 16 -NoEnumerate
|
|
203
|
+
} catch {
|
|
204
|
+
$envelope = $null
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return ConvertFrom-DiscoveryHostEnvelope -HostName $HostName -Envelope $envelope
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function Get-DiscoveryPayloadIdentifier {
|
|
211
|
+
<#
|
|
212
|
+
.SYNOPSIS
|
|
213
|
+
Returns Value as a string when it is a non-blank string matching the identifier grammar, else $null.
|
|
214
|
+
#>
|
|
215
|
+
[CmdletBinding()]
|
|
216
|
+
[OutputType([string])]
|
|
217
|
+
param([Parameter(Mandatory = $false)][AllowNull()][object]$Value)
|
|
218
|
+
|
|
219
|
+
if ($Value -isnot [string] -or [string]::IsNullOrWhiteSpace($Value)) { return $null }
|
|
220
|
+
if ($Value -notmatch $script:CorrelationIdPattern) { return $null }
|
|
221
|
+
return [string]$Value
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function Get-DiscoveryTrustedPrincipal {
|
|
225
|
+
<#
|
|
226
|
+
.SYNOPSIS
|
|
227
|
+
Derives the trusted principal from a normalized envelope alone.
|
|
228
|
+
.DESCRIPTION
|
|
229
|
+
The host is the adapter that received the envelope and the session is the
|
|
230
|
+
envelope session. An envelope without `agent_id` is the root of its
|
|
231
|
+
session (IsRoot). An envelope whose `agent_id` matches the identifier
|
|
232
|
+
grammar and whose `agent_type` names a discovery role is a specialist, and
|
|
233
|
+
AgentId and Role are set; every other agent leaves both null. ParentAgentId
|
|
234
|
+
is the envelope parent when the host supplies a valid one, else the literal
|
|
235
|
+
`root`. The principal is attributed for every anomaly-free envelope, which
|
|
236
|
+
always carries a session; an anomalous envelope is unattributed with the
|
|
237
|
+
anomaly reason. Nothing in tool input or the process environment supplies
|
|
238
|
+
any of these values.
|
|
239
|
+
.OUTPUTS
|
|
240
|
+
[pscustomobject] HostName, SessionId, ParentAgentId, AgentId, Role, IsRoot,
|
|
241
|
+
Attributed, Reason.
|
|
242
|
+
#>
|
|
243
|
+
[CmdletBinding()]
|
|
244
|
+
[OutputType([pscustomobject])]
|
|
245
|
+
param([Parameter(Mandatory = $true)][pscustomobject]$Normalized)
|
|
246
|
+
|
|
247
|
+
$agentId = Get-DiscoveryPayloadIdentifier -Value $Normalized.AgentId
|
|
248
|
+
$role = $(if ($Normalized.AgentType -is [string]) { Get-DiscoveryAgentRole -AgentType $Normalized.AgentType } else { $null })
|
|
249
|
+
$parent = Get-DiscoveryPayloadIdentifier -Value $Normalized.ParentAgentId
|
|
250
|
+
$isSpecialist = ($null -ne $agentId) -and ($null -ne $role)
|
|
251
|
+
$principal = [ordered]@{
|
|
252
|
+
HostName = $Normalized.HostName
|
|
253
|
+
SessionId = $Normalized.SessionId
|
|
254
|
+
ParentAgentId = $(if ($null -ne $parent) { $parent } else { $script:RootParentAgentId })
|
|
255
|
+
AgentId = $(if ($isSpecialist) { $agentId } else { $null })
|
|
256
|
+
Role = $(if ($isSpecialist) { $role } else { $null })
|
|
257
|
+
IsRoot = ($null -eq $Normalized.AgentId)
|
|
258
|
+
Attributed = $false
|
|
259
|
+
Reason = $null
|
|
260
|
+
}
|
|
261
|
+
# Attribution requires an anomaly-free envelope; an anomaly carries its reason.
|
|
262
|
+
if ($null -ne $Normalized.Anomaly) {
|
|
263
|
+
$principal.Reason = Get-DiscoveryPayloadAnomalyReason -Code $Normalized.Anomaly
|
|
264
|
+
} else {
|
|
265
|
+
$principal.Attributed = $true
|
|
266
|
+
}
|
|
267
|
+
return [pscustomobject]$principal
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
Export-ModuleMember -Function `
|
|
271
|
+
Test-DiscoverySupportedHost, `
|
|
272
|
+
Get-DiscoveryAgentRole, `
|
|
273
|
+
ConvertFrom-DiscoveryHostEnvelope, `
|
|
274
|
+
ConvertFrom-DiscoveryHostPayloadText, `
|
|
275
|
+
Get-DiscoveryTrustedPrincipal, `
|
|
276
|
+
Get-DiscoveryPayloadAnomalyReason
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
<#
|
|
2
|
+
.SYNOPSIS
|
|
3
|
+
Native PreToolUse gating of implementation planning on current configured discovery authority.
|
|
4
|
+
|
|
5
|
+
.DESCRIPTION
|
|
6
|
+
Every native discovery planning adapter (Claude Code, Codex, GitHub Copilot)
|
|
7
|
+
takes its decision from this module. A planning delegation is a native agent
|
|
8
|
+
launch whose target is an atomic-planner or orchestrator profile. When the
|
|
9
|
+
workspace has no protected activation, or a deliberate deactivation, the
|
|
10
|
+
delegation is allowed and the existing planning gates apply unchanged. When the
|
|
11
|
+
workspace is active, the delegation is admitted only when the workspace still
|
|
12
|
+
carries the activated locator (`config/discovery.json`) and domain profile with
|
|
13
|
+
the activated bytes, and the protected session holds a current gateway-written
|
|
14
|
+
`validation-receipt` for mode `planning` issued to the native-root principal of
|
|
15
|
+
this host and session, bound to the activation and workspace, passing, with
|
|
16
|
+
every input it read unchanged and the profile's planning prerequisite
|
|
17
|
+
(`none`, `discovery-complete`, `parity-complete`) reflected in its manifest. A
|
|
18
|
+
structural, agent-output, completion-mode or receipt-version-1 receipt, a
|
|
19
|
+
failing receipt, a stale receipt, a missing, deleted, disabled or malformed
|
|
20
|
+
locator or profile all block before the delegation runs.
|
|
21
|
+
|
|
22
|
+
The hooks verify authority only: no Python runtime is invoked and no
|
|
23
|
+
validation is started here. Store and workspace access flows through injected
|
|
24
|
+
seams.
|
|
25
|
+
|
|
26
|
+
.NOTES
|
|
27
|
+
Issue #980. Compatible with PowerShell 7+. Depends only on the sibling
|
|
28
|
+
DiscoveryEncoding, DiscoveryAuthority, DiscoveryManifest, DiscoveryPayload,
|
|
29
|
+
DiscoveryAuthorization and DiscoveryStartRecord modules.
|
|
30
|
+
CONVENTION: this module fails fast at module scope and imports its siblings with -ErrorAction Stop.
|
|
31
|
+
#>
|
|
32
|
+
|
|
33
|
+
Set-StrictMode -Version Latest
|
|
34
|
+
$ErrorActionPreference = 'Stop'
|
|
35
|
+
|
|
36
|
+
Import-Module (Join-Path $PSScriptRoot 'DiscoveryEncoding.psm1') -ErrorAction Stop
|
|
37
|
+
Import-Module (Join-Path $PSScriptRoot 'DiscoveryAuthority.psm1') -ErrorAction Stop
|
|
38
|
+
Import-Module (Join-Path $PSScriptRoot 'DiscoveryManifest.psm1') -ErrorAction Stop
|
|
39
|
+
Import-Module (Join-Path $PSScriptRoot 'DiscoveryPayload.psm1') -ErrorAction Stop
|
|
40
|
+
Import-Module (Join-Path $PSScriptRoot 'DiscoveryToolRouting.psm1') -ErrorAction Stop
|
|
41
|
+
Import-Module (Join-Path $PSScriptRoot 'DiscoveryAuthorization.psm1') -ErrorAction Stop
|
|
42
|
+
Import-Module (Join-Path $PSScriptRoot 'DiscoveryStartRecord.psm1') -ErrorAction Stop
|
|
43
|
+
|
|
44
|
+
$script:PlanningAgentPattern = '^(?:atomic-planner|orchestrator)(?:-c(?:1|2|3|4|3-elevated))?$'
|
|
45
|
+
$script:PlanningTargetKeys = @('subagent_type', 'agent_type', 'agent')
|
|
46
|
+
$script:LocatorPath = 'config/discovery.json'
|
|
47
|
+
$script:ReceiptsPartition = 'receipts'
|
|
48
|
+
$script:Prerequisites = @('none', 'discovery-complete', 'parity-complete')
|
|
49
|
+
$script:PrerequisitePattern = '(?m)^\s*"?planning_prerequisite"?\s*[:=]\s*"?(?<value>none|discovery-complete|parity-complete)"?\s*,?\s*(?:#.*)?$'
|
|
50
|
+
|
|
51
|
+
function Get-DiscoveryPlanningOutcome {
|
|
52
|
+
<#
|
|
53
|
+
.SYNOPSIS
|
|
54
|
+
Builds one planning outcome (allow or deny) with its code, reason and the authorizing receipt.
|
|
55
|
+
#>
|
|
56
|
+
[CmdletBinding()]
|
|
57
|
+
[OutputType([pscustomobject])]
|
|
58
|
+
param(
|
|
59
|
+
[Parameter(Mandatory = $true)][ValidateSet('allow', 'deny')][string]$Decision,
|
|
60
|
+
[Parameter(Mandatory = $true)][string]$Code,
|
|
61
|
+
[Parameter(Mandatory = $true)][string]$Reason,
|
|
62
|
+
[Parameter(Mandatory = $false)][AllowNull()][object]$Receipt = $null,
|
|
63
|
+
[Parameter(Mandatory = $false)][AllowNull()][string]$Prerequisite = $null
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
return [pscustomobject]@{ Decision = $Decision; Code = $Code; Reason = $Reason; Receipt = $Receipt; Prerequisite = $Prerequisite }
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function Get-DiscoveryPlanningTarget {
|
|
70
|
+
<#
|
|
71
|
+
.SYNOPSIS
|
|
72
|
+
Returns the planner or orchestrator profile a native agent launch targets, or $null for any other call.
|
|
73
|
+
#>
|
|
74
|
+
[CmdletBinding()]
|
|
75
|
+
[OutputType([string])]
|
|
76
|
+
param([Parameter(Mandatory = $true)][pscustomobject]$Normalized)
|
|
77
|
+
|
|
78
|
+
$tool = Resolve-DiscoveryToolName -ToolName $Normalized.ToolName
|
|
79
|
+
if ($tool.Family -ne 'spawn') { return $null }
|
|
80
|
+
$toolInput = $Normalized.ToolInput
|
|
81
|
+
foreach ($key in $script:PlanningTargetKeys) {
|
|
82
|
+
$value = $null
|
|
83
|
+
if ($toolInput -is [System.Collections.IDictionary]) { if ($toolInput.Contains($key)) { $value = $toolInput[$key] } }
|
|
84
|
+
elseif ($toolInput -is [System.Management.Automation.PSCustomObject]) { $property = $toolInput.PSObject.Properties[$key]; if ($null -ne $property) { $value = $property.Value } }
|
|
85
|
+
if ($value -is [string] -and $value -cmatch $script:PlanningAgentPattern) { return $value }
|
|
86
|
+
}
|
|
87
|
+
return $null
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function Get-DiscoveryPlanningPrerequisite {
|
|
91
|
+
<#
|
|
92
|
+
.SYNOPSIS
|
|
93
|
+
Reads `planning_prerequisite` from the profile text (YAML or JSON), defaulting to discovery-complete.
|
|
94
|
+
#>
|
|
95
|
+
[CmdletBinding()]
|
|
96
|
+
[OutputType([string])]
|
|
97
|
+
param([Parameter(Mandatory = $true)][AllowEmptyString()][string]$ProfileText)
|
|
98
|
+
|
|
99
|
+
$match = [regex]::Match($ProfileText, $script:PrerequisitePattern)
|
|
100
|
+
if ($match.Success) { return $match.Groups['value'].Value }
|
|
101
|
+
return 'discovery-complete'
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function Get-DiscoveryWorkspaceFileMismatch {
|
|
105
|
+
<#
|
|
106
|
+
.SYNOPSIS
|
|
107
|
+
Verifies that a workspace file still exists with the activated bytes and is decodable text.
|
|
108
|
+
.OUTPUTS
|
|
109
|
+
[pscustomobject] Code, Reason, Text (the decoded text when valid).
|
|
110
|
+
#>
|
|
111
|
+
[CmdletBinding()]
|
|
112
|
+
[OutputType([pscustomobject])]
|
|
113
|
+
param(
|
|
114
|
+
[Parameter(Mandatory = $true)][string]$WorkspaceRoot,
|
|
115
|
+
[Parameter(Mandatory = $true)][string]$RelativePath,
|
|
116
|
+
[Parameter(Mandatory = $true)][AllowNull()][AllowEmptyString()][string]$ExpectedSha256,
|
|
117
|
+
[Parameter(Mandatory = $true)][string]$Label,
|
|
118
|
+
[Parameter(Mandatory = $true)][scriptblock]$ReadBytes
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
$bytes = & $ReadBytes (Get-DiscoveryWorkspacePath -WorkspaceRoot $WorkspaceRoot -RelativePath $RelativePath)
|
|
122
|
+
if ($null -eq $bytes) {
|
|
123
|
+
return [pscustomobject]@{ Code = "$($Label)_MISSING"; Reason = "the workspace is active but its $($Label.ToLowerInvariant()) $RelativePath is absent"; Text = $null }
|
|
124
|
+
}
|
|
125
|
+
[byte[]]$bytes = @($bytes)
|
|
126
|
+
if ([string]::IsNullOrEmpty($ExpectedSha256) -or (Get-DiscoveryBytesSha256Hex -Bytes $bytes) -cne $ExpectedSha256) {
|
|
127
|
+
return [pscustomobject]@{ Code = "$($Label)_CHANGED"; Reason = "the $($Label.ToLowerInvariant()) $RelativePath differs from the activated bytes"; Text = $null }
|
|
128
|
+
}
|
|
129
|
+
try {
|
|
130
|
+
$text = [System.Text.UTF8Encoding]::new($false, $true).GetString($bytes)
|
|
131
|
+
} catch {
|
|
132
|
+
return [pscustomobject]@{ Code = "$($Label)_MALFORMED"; Reason = "the $($Label.ToLowerInvariant()) $RelativePath is not valid UTF-8"; Text = $null }
|
|
133
|
+
}
|
|
134
|
+
return [pscustomobject]@{ Code = $null; Reason = $null; Text = $text }
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function Get-DiscoveryPlanningReceiptMismatch {
|
|
138
|
+
<#
|
|
139
|
+
.SYNOPSIS
|
|
140
|
+
Compares one candidate receipt against the planning authority contract.
|
|
141
|
+
.DESCRIPTION
|
|
142
|
+
Returns $null when the receipt authorizes planning, otherwise an object with
|
|
143
|
+
Code (RECEIPT_VERSION, WRONG_MODE, IDENTITY, FORGED, BINDING, STALE_ACTIVATION,
|
|
144
|
+
RESULT, PREREQUISITE or STALE_INPUTS) and Reason. The receipt must be a
|
|
145
|
+
schema-2 `validation-receipt` of mode `planning` for the native-root
|
|
146
|
+
principal of this host and session.
|
|
147
|
+
#>
|
|
148
|
+
[CmdletBinding()]
|
|
149
|
+
[OutputType([pscustomobject])]
|
|
150
|
+
param(
|
|
151
|
+
[Parameter(Mandatory = $true)][pscustomobject]$Receipt,
|
|
152
|
+
[Parameter(Mandatory = $true)][pscustomobject]$Normalized,
|
|
153
|
+
[Parameter(Mandatory = $true)][pscustomobject]$Activation,
|
|
154
|
+
[Parameter(Mandatory = $true)][string]$ActivationSha256,
|
|
155
|
+
[Parameter(Mandatory = $true)][pscustomobject]$Location,
|
|
156
|
+
[Parameter(Mandatory = $true)][string]$Prerequisite,
|
|
157
|
+
[Parameter(Mandatory = $true)][scriptblock]$ReadBytes
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
$names = @($Receipt.PSObject.Properties.Name)
|
|
161
|
+
foreach ($required in @('artifact_kind', 'receipt_version', 'mode', 'identity', 'writer', 'workspace', 'activation_sha256', 'validation_result', 'input_manifest')) {
|
|
162
|
+
if ($names -notcontains $required) { return [pscustomobject]@{ Code = 'RECEIPT_VERSION'; Reason = "the receipt lacks '$required' and is not a schema 2.0.0 validation receipt" } }
|
|
163
|
+
}
|
|
164
|
+
if ($Receipt.artifact_kind -ne 'validation-receipt' -or [string]$Receipt.receipt_version -ne '2.0.0' -or [string]$Receipt.schema_version -ne '2.0.0') {
|
|
165
|
+
return [pscustomobject]@{ Code = 'RECEIPT_VERSION'; Reason = 'the receipt is not a receipt-version 2.0.0 validation receipt; a v1 check cannot authorize planning' }
|
|
166
|
+
}
|
|
167
|
+
if ([string]$Receipt.mode -ne 'planning') {
|
|
168
|
+
return [pscustomobject]@{ Code = 'WRONG_MODE'; Reason = "the receipt was issued for mode '$($Receipt.mode)'; only a planning-mode receipt authorizes planning" }
|
|
169
|
+
}
|
|
170
|
+
$identity = $Receipt.identity
|
|
171
|
+
if ($identity -isnot [System.Management.Automation.PSCustomObject] -or $identity.kind -ne 'native-root' -or $identity.host -ne $Normalized.HostName -or $identity.session_id -ne $Normalized.SessionId) {
|
|
172
|
+
return [pscustomobject]@{ Code = 'IDENTITY'; Reason = 'the receipt was not issued to the native-root principal of this host and session' }
|
|
173
|
+
}
|
|
174
|
+
$writer = $Receipt.writer
|
|
175
|
+
if ($writer -isnot [System.Management.Automation.PSCustomObject] -or $writer.kind -ne 'host-gateway' -or $writer.host -ne $Normalized.HostName) {
|
|
176
|
+
return [pscustomobject]@{ Code = 'FORGED'; Reason = 'the receipt was not written by the host gateway' }
|
|
177
|
+
}
|
|
178
|
+
$sameBinding = ([string]$Receipt.workspace.workspace_sha256 -eq $Location.WorkspaceSha256) -and
|
|
179
|
+
([string]$Receipt.workspace.profile_path -eq [string]$Activation.profile_path) -and
|
|
180
|
+
([string]$Receipt.workspace.profile_sha256 -eq [string]$Activation.profile_sha256)
|
|
181
|
+
if (-not $sameBinding) {
|
|
182
|
+
return [pscustomobject]@{ Code = 'BINDING'; Reason = 'the receipt is bound to another workspace or profile than the current activation' }
|
|
183
|
+
}
|
|
184
|
+
if ([string]$Receipt.activation_sha256 -cne $ActivationSha256) {
|
|
185
|
+
return [pscustomobject]@{ Code = 'STALE_ACTIVATION'; Reason = 'the activation record changed after the planning receipt was issued' }
|
|
186
|
+
}
|
|
187
|
+
if ([string]$Receipt.validation_result -ne 'pass') {
|
|
188
|
+
return [pscustomobject]@{ Code = 'RESULT'; Reason = "the planning receipt records '$($Receipt.validation_result)'; configured discovery is incomplete" }
|
|
189
|
+
}
|
|
190
|
+
try {
|
|
191
|
+
$manifest = ConvertFrom-DiscoveryInputManifest -Value $Receipt.input_manifest
|
|
192
|
+
} catch {
|
|
193
|
+
return [pscustomobject]@{ Code = 'RECEIPT_VERSION'; Reason = "the receipt input manifest is invalid: $($_.Exception.Message)" }
|
|
194
|
+
}
|
|
195
|
+
# The prerequisite the profile configures must be visible in what the runtime validated.
|
|
196
|
+
if ($Prerequisite -ne 'none' -and @($manifest['source_repositories']).Count -eq 0) {
|
|
197
|
+
return [pscustomobject]@{ Code = 'PREREQUISITE'; Reason = "the profile requires $Prerequisite but the planning receipt validated no source repository" }
|
|
198
|
+
}
|
|
199
|
+
if ($Prerequisite -eq 'parity-complete' -and @($manifest['target_repositories']).Count -eq 0) {
|
|
200
|
+
return [pscustomobject]@{ Code = 'PREREQUISITE'; Reason = 'the profile requires parity-complete but the planning receipt validated no target repository' }
|
|
201
|
+
}
|
|
202
|
+
$comparison = Compare-DiscoveryInputManifest -Manifest $manifest -WorkspaceRoot ([string]$Receipt.workspace.workspace_root) `
|
|
203
|
+
-ProfilePath ([string]$Activation.profile_path) -ProfileSha256 ([string]$Activation.profile_sha256) -ReadBytes $ReadBytes
|
|
204
|
+
if ($comparison.Drift.Count -gt 0) {
|
|
205
|
+
$summary = @($comparison.Drift | ForEach-Object { "$($_.kind):$($_.path)" }) -join ', '
|
|
206
|
+
return [pscustomobject]@{ Code = 'STALE_INPUTS'; Reason = "inputs changed after the planning receipt: $summary" }
|
|
207
|
+
}
|
|
208
|
+
return $null
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function Get-DiscoveryPlanningDecision {
|
|
212
|
+
<#
|
|
213
|
+
.SYNOPSIS
|
|
214
|
+
Decides one PreToolUse agent launch for a native discovery planning adapter.
|
|
215
|
+
.DESCRIPTION
|
|
216
|
+
Order: envelope anomalies deny; a call that is not a planner or orchestrator
|
|
217
|
+
launch is allowed; a workspace without an activation or with a deliberate
|
|
218
|
+
deactivation is allowed (existing planning behavior); an active workspace
|
|
219
|
+
must still hold the activated locator and profile; then the protected
|
|
220
|
+
session's planning receipts are examined newest first and the first one
|
|
221
|
+
that passes Get-DiscoveryPlanningReceiptMismatch authorizes the launch.
|
|
222
|
+
When none passes, the mismatch of the newest planning-mode candidate (or the
|
|
223
|
+
newest candidate of any mode) is the deny reason.
|
|
224
|
+
.OUTPUTS
|
|
225
|
+
[pscustomobject] Decision, Code, Reason, Receipt, Prerequisite.
|
|
226
|
+
#>
|
|
227
|
+
[CmdletBinding()]
|
|
228
|
+
[OutputType([pscustomobject])]
|
|
229
|
+
param(
|
|
230
|
+
[Parameter(Mandatory = $true)][pscustomobject]$Normalized,
|
|
231
|
+
[Parameter(Mandatory = $true)][hashtable]$HostEnvironment,
|
|
232
|
+
[Parameter(Mandatory = $false)][hashtable]$Store = (Get-DiscoveryStartStore),
|
|
233
|
+
[Parameter(Mandatory = $false)][AllowNull()][scriptblock]$ReadBytes = $null
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
if ($null -ne $Normalized.Anomaly) {
|
|
237
|
+
return Get-DiscoveryPlanningOutcome -Decision 'deny' -Code 'ENVELOPE' -Reason ('payload anomaly - ' + (Get-DiscoveryPayloadAnomalyReason -Code $Normalized.Anomaly) + '. The gate fails closed on an envelope it cannot read.')
|
|
238
|
+
}
|
|
239
|
+
$target = Get-DiscoveryPlanningTarget -Normalized $Normalized
|
|
240
|
+
if ($null -eq $target) {
|
|
241
|
+
return Get-DiscoveryPlanningOutcome -Decision 'allow' -Code 'NOT_PLANNING' -Reason 'the call is not a planner or orchestrator launch'
|
|
242
|
+
}
|
|
243
|
+
if ([string]::IsNullOrWhiteSpace([string]$Normalized.Cwd)) {
|
|
244
|
+
return Get-DiscoveryPlanningOutcome -Decision 'deny' -Code 'WORKSPACE' -Reason 'the native event carries no workspace directory'
|
|
245
|
+
}
|
|
246
|
+
try {
|
|
247
|
+
$location = Resolve-DiscoveryAuthorityLocation -WorkspaceRoot ([string]$Normalized.Cwd) -Platform $HostEnvironment.Platform `
|
|
248
|
+
-UserHome $HostEnvironment.UserHome -LocalAppData $HostEnvironment.LocalAppData -XdgStateHome $HostEnvironment.XdgStateHome
|
|
249
|
+
} catch {
|
|
250
|
+
return Get-DiscoveryPlanningOutcome -Decision 'deny' -Code 'WORKSPACE' -Reason $_.Exception.Message
|
|
251
|
+
}
|
|
252
|
+
$activation = Read-DiscoveryActivationState -Location $location -ReadText $Store.ReadText
|
|
253
|
+
if ($activation.Status -eq 'missing' -or $activation.Status -eq 'inactive') {
|
|
254
|
+
return Get-DiscoveryPlanningOutcome -Decision 'allow' -Code 'NOT_ACTIVE' -Reason "discovery is $($activation.Status) for this workspace; existing planning gates apply unchanged"
|
|
255
|
+
}
|
|
256
|
+
if ($activation.Status -ne 'active') {
|
|
257
|
+
return Get-DiscoveryPlanningOutcome -Decision 'deny' -Code 'ACTIVATION' -Reason "the protected activation record is unreadable ($($activation.Reason)); the gate fails closed"
|
|
258
|
+
}
|
|
259
|
+
if ($null -eq $ReadBytes) {
|
|
260
|
+
$ReadBytes = {
|
|
261
|
+
param([string]$Path)
|
|
262
|
+
if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) { return $null }
|
|
263
|
+
return [System.IO.File]::ReadAllBytes($Path)
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
$workspaceRoot = [string]$Normalized.Cwd
|
|
267
|
+
$record = $activation.Record
|
|
268
|
+
$locator = Get-DiscoveryWorkspaceFileMismatch -WorkspaceRoot $workspaceRoot -RelativePath $script:LocatorPath -ExpectedSha256 ([string]$record.locator_sha256) -Label 'LOCATOR' -ReadBytes $ReadBytes
|
|
269
|
+
if ($null -ne $locator.Code) { return Get-DiscoveryPlanningOutcome -Decision 'deny' -Code $locator.Code -Reason $locator.Reason }
|
|
270
|
+
try { $locatorRecord = ConvertFrom-Json -InputObject $locator.Text -Depth 4 } catch { $locatorRecord = $null }
|
|
271
|
+
if ($locatorRecord -isnot [System.Management.Automation.PSCustomObject] -or $locatorRecord.enabled -ne $true -or [string]$locatorRecord.profile_path -cne [string]$record.profile_path) {
|
|
272
|
+
return Get-DiscoveryPlanningOutcome -Decision 'deny' -Code 'LOCATOR_MALFORMED' -Reason "the locator $($script:LocatorPath) is disabled, malformed or names another profile than the activation"
|
|
273
|
+
}
|
|
274
|
+
$profileFile = Get-DiscoveryWorkspaceFileMismatch -WorkspaceRoot $workspaceRoot -RelativePath ([string]$record.profile_path) -ExpectedSha256 ([string]$record.profile_sha256) -Label 'PROFILE' -ReadBytes $ReadBytes
|
|
275
|
+
if ($null -ne $profileFile.Code) { return Get-DiscoveryPlanningOutcome -Decision 'deny' -Code $profileFile.Code -Reason $profileFile.Reason }
|
|
276
|
+
if ($profileFile.Text -notmatch 'schema_version') {
|
|
277
|
+
return Get-DiscoveryPlanningOutcome -Decision 'deny' -Code 'PROFILE_MALFORMED' -Reason "the profile $($record.profile_path) declares no schema_version"
|
|
278
|
+
}
|
|
279
|
+
$prerequisite = Get-DiscoveryPlanningPrerequisite -ProfileText $profileFile.Text
|
|
280
|
+
$activationSha256 = Get-DiscoverySha256Hex -Text ([string](Invoke-DiscoveryStoreSeam -Seam $Store.ReadText -Arguments @($activation.Path)))
|
|
281
|
+
$directory = "$(Get-DiscoverySessionDirectory -Location $location -SessionId $Normalized.SessionId)/$($script:ReceiptsPartition)"
|
|
282
|
+
$candidates = [System.Collections.Generic.List[object]]::new()
|
|
283
|
+
foreach ($entry in @(Invoke-DiscoveryStoreSeam -Seam $Store.ListEntries -Arguments @($directory))) {
|
|
284
|
+
if ($entry -notlike '*.json') { continue }
|
|
285
|
+
$text = Invoke-DiscoveryStoreSeam -Seam $Store.ReadText -Arguments @("$directory/$entry")
|
|
286
|
+
if ($null -eq $text) { continue }
|
|
287
|
+
try { $receipt = ConvertFrom-Json -InputObject ([string]$text) -Depth 16 } catch { continue }
|
|
288
|
+
if ($receipt -isnot [System.Management.Automation.PSCustomObject]) { continue }
|
|
289
|
+
$issued = ConvertTo-DiscoveryUtcInstant -Value $(if (@($receipt.PSObject.Properties.Name) -contains 'issued_at') { $receipt.issued_at } else { $null })
|
|
290
|
+
$candidates.Add([pscustomobject]@{ Receipt = $receipt; IssuedAt = $(if ($null -ne $issued) { $issued } else { [datetime]::MinValue }) })
|
|
291
|
+
}
|
|
292
|
+
if ($candidates.Count -eq 0) {
|
|
293
|
+
return Get-DiscoveryPlanningOutcome -Decision 'deny' -Code 'NO_RECEIPT' -Reason "discovery is active with prerequisite $prerequisite and the session holds no planning receipt; run validate_discovery_artifacts with mode planning before delegating" -Prerequisite $prerequisite
|
|
294
|
+
}
|
|
295
|
+
$firstMismatch = $null
|
|
296
|
+
foreach ($candidate in @($candidates | Sort-Object -Property IssuedAt -Descending)) {
|
|
297
|
+
$mismatch = Get-DiscoveryPlanningReceiptMismatch -Receipt $candidate.Receipt -Normalized $Normalized -Activation $record -ActivationSha256 $activationSha256 -Location $location -Prerequisite $prerequisite -ReadBytes $ReadBytes
|
|
298
|
+
if ($null -eq $mismatch) {
|
|
299
|
+
return Get-DiscoveryPlanningOutcome -Decision 'allow' -Code 'PLANNING_AUTHORIZED' -Reason "a current planning receipt authorizes $target (prerequisite $prerequisite)" -Receipt $candidate.Receipt -Prerequisite $prerequisite
|
|
300
|
+
}
|
|
301
|
+
# The newest planning-mode candidate explains the refusal; other modes only when nothing else exists.
|
|
302
|
+
if ($null -eq $firstMismatch -or ($firstMismatch.Code -eq 'WRONG_MODE' -and $mismatch.Code -ne 'WRONG_MODE')) { $firstMismatch = $mismatch }
|
|
303
|
+
}
|
|
304
|
+
return Get-DiscoveryPlanningOutcome -Decision 'deny' -Code $firstMismatch.Code -Reason $firstMismatch.Reason -Prerequisite $prerequisite
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function ConvertTo-DiscoveryPlanningHookOutput {
|
|
308
|
+
<#
|
|
309
|
+
.SYNOPSIS
|
|
310
|
+
Renders a planning outcome as the PreToolUse hookSpecificOutput envelope with the hook's reason prefix.
|
|
311
|
+
#>
|
|
312
|
+
[CmdletBinding()]
|
|
313
|
+
[OutputType([System.Collections.Specialized.OrderedDictionary])]
|
|
314
|
+
param(
|
|
315
|
+
[Parameter(Mandatory = $true)][pscustomobject]$Outcome,
|
|
316
|
+
[Parameter(Mandatory = $true)][string]$ReasonPrefix
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
if ($Outcome.Decision -eq 'allow') {
|
|
320
|
+
return [ordered]@{ hookSpecificOutput = [ordered]@{ hookEventName = 'PreToolUse'; permissionDecision = 'allow' } }
|
|
321
|
+
}
|
|
322
|
+
return [ordered]@{
|
|
323
|
+
hookSpecificOutput = [ordered]@{
|
|
324
|
+
hookEventName = 'PreToolUse'
|
|
325
|
+
permissionDecision = 'deny'
|
|
326
|
+
permissionDecisionReason = "$ReasonPrefix $($Outcome.Reason)"
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
Export-ModuleMember -Function `
|
|
332
|
+
Get-DiscoveryPlanningTarget, `
|
|
333
|
+
Get-DiscoveryPlanningPrerequisite, `
|
|
334
|
+
Get-DiscoveryPlanningReceiptMismatch, `
|
|
335
|
+
Get-DiscoveryPlanningDecision, `
|
|
336
|
+
ConvertTo-DiscoveryPlanningHookOutput
|