@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,296 @@
|
|
|
1
|
+
<#
|
|
2
|
+
.SYNOPSIS
|
|
3
|
+
Agent-profile, record and store primitives for native discovery start attestation.
|
|
4
|
+
|
|
5
|
+
.DESCRIPTION
|
|
6
|
+
The SubagentStart adapters of Claude Code, Codex and GitHub Copilot share these
|
|
7
|
+
primitives: the per-host agent profile location and its Markdown front-matter
|
|
8
|
+
reader, UTC instant coercion for record timestamps, the started transition of
|
|
9
|
+
a protected invocation record (System.Text.Json preserves every untouched
|
|
10
|
+
value verbatim), the `start-attestation` record and the real filesystem store
|
|
11
|
+
seams. The discovery agent-type grammar lives in DiscoveryPayload.psm1
|
|
12
|
+
(`Get-DiscoveryAgentRole`). Nothing here reads an environment value or
|
|
13
|
+
decides; the decision lives in DiscoveryStart.psm1.
|
|
14
|
+
|
|
15
|
+
.NOTES
|
|
16
|
+
Issue #980. Compatible with PowerShell 7+. Depends only on the sibling
|
|
17
|
+
DiscoveryEncoding module.
|
|
18
|
+
CONVENTION: this module fails fast at module scope and imports its siblings with -ErrorAction Stop.
|
|
19
|
+
#>
|
|
20
|
+
|
|
21
|
+
Set-StrictMode -Version Latest
|
|
22
|
+
$ErrorActionPreference = 'Stop'
|
|
23
|
+
|
|
24
|
+
Import-Module (Join-Path $PSScriptRoot 'DiscoveryEncoding.psm1') -ErrorAction Stop
|
|
25
|
+
|
|
26
|
+
$script:StartAttestationSchema = 'urn:drm-copilot:discovery:2.0.0:start-attestation'
|
|
27
|
+
$script:StartAttestationFile = 'start-attestation.json'
|
|
28
|
+
# Where each host keeps the generated specialist profile the root launches.
|
|
29
|
+
$script:AgentProfileLayout = @{
|
|
30
|
+
'claude-code' = @{ Directory = '.claude/agents'; Suffix = '.md' }
|
|
31
|
+
'codex' = @{ Directory = '.codex/agents'; Suffix = '.toml' }
|
|
32
|
+
'github-copilot' = @{ Directory = '.github/agents'; Suffix = '.agent.md' }
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function Get-DiscoveryAgentProfilePath {
|
|
36
|
+
<#
|
|
37
|
+
.SYNOPSIS
|
|
38
|
+
Returns the workspace-relative profile path of a native discovery agent on HostName.
|
|
39
|
+
#>
|
|
40
|
+
[CmdletBinding()]
|
|
41
|
+
[OutputType([string])]
|
|
42
|
+
param(
|
|
43
|
+
[Parameter(Mandatory = $true)][ValidateSet('claude-code', 'codex', 'github-copilot')][string]$HostName,
|
|
44
|
+
[Parameter(Mandatory = $true)][string]$AgentType
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
if ($AgentType -notmatch '^[A-Za-z0-9][A-Za-z0-9._-]*$') {
|
|
48
|
+
throw "AGENT_PROFILE_INVALID: '$AgentType' is not a valid agent profile name."
|
|
49
|
+
}
|
|
50
|
+
$layout = $script:AgentProfileLayout[$HostName]
|
|
51
|
+
return "$($layout.Directory)/$AgentType$($layout.Suffix)"
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function ConvertFrom-DiscoveryMarkdownProfile {
|
|
55
|
+
<#
|
|
56
|
+
.SYNOPSIS
|
|
57
|
+
Reads name, model and effort from the YAML front matter of a Markdown agent profile.
|
|
58
|
+
.DESCRIPTION
|
|
59
|
+
Only scalar `key: value` lines of the first front-matter block are read; a
|
|
60
|
+
missing block or a missing `name` is a typed failure. Model and effort are
|
|
61
|
+
$null when the host profile does not declare them.
|
|
62
|
+
.OUTPUTS
|
|
63
|
+
[pscustomobject] ProfilePath, ProfileSha256, ProfileName, ProfileModel, ProfileReasoningEffort.
|
|
64
|
+
#>
|
|
65
|
+
[CmdletBinding()]
|
|
66
|
+
[OutputType([pscustomobject])]
|
|
67
|
+
param(
|
|
68
|
+
[Parameter(Mandatory = $true)][AllowEmptyString()][string]$Text,
|
|
69
|
+
[Parameter(Mandatory = $true)][string]$RelativePath,
|
|
70
|
+
[Parameter(Mandatory = $true)][string]$Sha256
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
$lines = $Text -split "`r?`n"
|
|
74
|
+
if ($lines.Count -lt 2 -or $lines[0].Trim() -ne '---') {
|
|
75
|
+
throw "AGENT_PROFILE_INVALID: '$RelativePath' has no YAML front matter."
|
|
76
|
+
}
|
|
77
|
+
$values = @{}
|
|
78
|
+
$closed = $false
|
|
79
|
+
foreach ($line in $lines[1..($lines.Count - 1)]) {
|
|
80
|
+
if ($line.Trim() -eq '---') { $closed = $true; break }
|
|
81
|
+
if ($line -match '^(?<key>[A-Za-z_][A-Za-z0-9_-]*):\s*(?<value>[^#]*?)\s*(?:#.*)?$' -and -not [string]::IsNullOrWhiteSpace($Matches['value'])) {
|
|
82
|
+
$values[$Matches['key']] = $Matches['value'].Trim().Trim('"', "'")
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (-not $closed) { throw "AGENT_PROFILE_INVALID: '$RelativePath' has an unterminated front-matter block." }
|
|
86
|
+
if (-not $values.ContainsKey('name')) { throw "AGENT_PROFILE_INVALID: '$RelativePath' declares no name." }
|
|
87
|
+
return [pscustomobject]@{
|
|
88
|
+
ProfilePath = $RelativePath
|
|
89
|
+
ProfileSha256 = $Sha256
|
|
90
|
+
ProfileName = [string]$values['name']
|
|
91
|
+
ProfileModel = $(if ($values.ContainsKey('model')) { [string]$values['model'] } else { $null })
|
|
92
|
+
ProfileReasoningEffort = $(if ($values.ContainsKey('effort')) { [string]$values['effort'] } elseif ($values.ContainsKey('reasoning_effort')) { [string]$values['reasoning_effort'] } else { $null })
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function Read-DiscoveryAgentProfile {
|
|
97
|
+
<#
|
|
98
|
+
.SYNOPSIS
|
|
99
|
+
Reads and hashes the Markdown agent profile of AgentType under WorkspaceRoot.
|
|
100
|
+
.DESCRIPTION
|
|
101
|
+
Claude Code and GitHub Copilot profiles are Markdown with front matter; the
|
|
102
|
+
Codex adapter reads its TOML profiles through its own parser and converts the
|
|
103
|
+
result, so this reader refuses the codex host. ReadBytes is the filesystem seam.
|
|
104
|
+
#>
|
|
105
|
+
[CmdletBinding()]
|
|
106
|
+
[OutputType([pscustomobject])]
|
|
107
|
+
param(
|
|
108
|
+
[Parameter(Mandatory = $true)][ValidateSet('claude-code', 'github-copilot')][string]$HostName,
|
|
109
|
+
[Parameter(Mandatory = $true)][string]$AgentType,
|
|
110
|
+
[Parameter(Mandatory = $true)][string]$WorkspaceRoot,
|
|
111
|
+
[Parameter(Mandatory = $false)][scriptblock]$ReadBytes = {
|
|
112
|
+
param([string]$Path)
|
|
113
|
+
if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) { return $null }
|
|
114
|
+
return [System.IO.File]::ReadAllBytes($Path)
|
|
115
|
+
}
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
$relativePath = Get-DiscoveryAgentProfilePath -HostName $HostName -AgentType $AgentType
|
|
119
|
+
$bytes = & $ReadBytes "$($WorkspaceRoot.TrimEnd('/', '\'))/$relativePath"
|
|
120
|
+
if ($null -eq $bytes) { throw "AGENT_PROFILE_INVALID: '$relativePath' does not exist." }
|
|
121
|
+
$bytes = [byte[]]$bytes
|
|
122
|
+
try {
|
|
123
|
+
$text = [System.Text.UTF8Encoding]::new($false, $true).GetString($bytes)
|
|
124
|
+
} catch {
|
|
125
|
+
throw "AGENT_PROFILE_INVALID: '$relativePath' is not valid UTF-8."
|
|
126
|
+
}
|
|
127
|
+
return ConvertFrom-DiscoveryMarkdownProfile -Text $text -RelativePath $relativePath -Sha256 (Get-DiscoveryBytesSha256Hex -Bytes $bytes)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function ConvertTo-DiscoveryUtcInstant {
|
|
131
|
+
<#
|
|
132
|
+
.SYNOPSIS
|
|
133
|
+
Coerces a decoded JSON timestamp (string or DateTime) to a UTC DateTime, or $null.
|
|
134
|
+
#>
|
|
135
|
+
[CmdletBinding()]
|
|
136
|
+
[OutputType([nullable[datetime]])]
|
|
137
|
+
param([Parameter(Mandatory = $false)][AllowNull()][object]$Value)
|
|
138
|
+
|
|
139
|
+
if ($Value -is [datetime]) { return $Value.ToUniversalTime() }
|
|
140
|
+
if ($Value -is [datetimeoffset]) { return $Value.UtcDateTime }
|
|
141
|
+
$parsed = [datetimeoffset]::MinValue
|
|
142
|
+
$styles = [System.Globalization.DateTimeStyles]::AssumeUniversal -bor [System.Globalization.DateTimeStyles]::AdjustToUniversal
|
|
143
|
+
if ($Value -is [string] -and [datetimeoffset]::TryParse($Value, [cultureinfo]::InvariantCulture, $styles, [ref]$parsed)) { return $parsed.UtcDateTime }
|
|
144
|
+
return $null
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function ConvertTo-DiscoveryStartedInvocationText {
|
|
148
|
+
<#
|
|
149
|
+
.SYNOPSIS
|
|
150
|
+
Rewrites a prepared invocation record text to the started state.
|
|
151
|
+
.DESCRIPTION
|
|
152
|
+
Only `state`, `agent_id` and `start_attestation_ref` change; every other value,
|
|
153
|
+
including timestamps, keeps its original spelling because the document is
|
|
154
|
+
edited as a JSON node tree rather than re-serialized from PowerShell objects.
|
|
155
|
+
#>
|
|
156
|
+
[CmdletBinding()]
|
|
157
|
+
[OutputType([string])]
|
|
158
|
+
param(
|
|
159
|
+
[Parameter(Mandatory = $true)][string]$Text,
|
|
160
|
+
[Parameter(Mandatory = $true)][string]$AgentId,
|
|
161
|
+
[Parameter(Mandatory = $true)][string]$StartAttestationRef
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
$node = [System.Text.Json.Nodes.JsonNode]::Parse($Text)
|
|
165
|
+
if ($node -isnot [System.Text.Json.Nodes.JsonObject]) { throw 'INVOCATION_RECORD_INVALID: the invocation record is not a JSON object.' }
|
|
166
|
+
$node['state'] = [System.Text.Json.Nodes.JsonValue]::Create('started')
|
|
167
|
+
$node['agent_id'] = [System.Text.Json.Nodes.JsonValue]::Create($AgentId)
|
|
168
|
+
$node['start_attestation_ref'] = [System.Text.Json.Nodes.JsonValue]::Create($StartAttestationRef)
|
|
169
|
+
$options = [System.Text.Json.JsonSerializerOptions]::new()
|
|
170
|
+
$options.WriteIndented = $true
|
|
171
|
+
$options.Encoder = [System.Text.Encodings.Web.JavaScriptEncoder]::UnsafeRelaxedJsonEscaping
|
|
172
|
+
return $node.ToJsonString($options) + "`n"
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function ConvertTo-DiscoveryStartAttestationRecord {
|
|
176
|
+
<#
|
|
177
|
+
.SYNOPSIS
|
|
178
|
+
Builds the `start-attestation` record binding the trusted principal to one prepared invocation.
|
|
179
|
+
.DESCRIPTION
|
|
180
|
+
Every identity value comes from the principal, the protected record and the
|
|
181
|
+
host's profile read; the SubagentStart payload contributes only the agent
|
|
182
|
+
identifier the host assigned. ActualModel and ActualReasoningEffort are $null
|
|
183
|
+
when the host does not expose them in its start event.
|
|
184
|
+
#>
|
|
185
|
+
[CmdletBinding()]
|
|
186
|
+
[OutputType([System.Collections.Specialized.OrderedDictionary])]
|
|
187
|
+
param(
|
|
188
|
+
[Parameter(Mandatory = $true)][pscustomobject]$Principal,
|
|
189
|
+
[Parameter(Mandatory = $true)][string]$AgentId,
|
|
190
|
+
[Parameter(Mandatory = $true)][string]$AgentType,
|
|
191
|
+
[Parameter(Mandatory = $true)][pscustomobject]$Record,
|
|
192
|
+
[Parameter(Mandatory = $true)][pscustomobject]$AgentProfile,
|
|
193
|
+
[Parameter(Mandatory = $false)][AllowNull()][string]$ActualModel,
|
|
194
|
+
[Parameter(Mandatory = $false)][AllowNull()][string]$ActualReasoningEffort,
|
|
195
|
+
[Parameter(Mandatory = $true)][string]$HandlerSha256,
|
|
196
|
+
[Parameter(Mandatory = $true)][datetime]$Now,
|
|
197
|
+
[Parameter(Mandatory = $true)][string]$Nonce
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
return [ordered]@{
|
|
201
|
+
'$schema' = $script:StartAttestationSchema
|
|
202
|
+
schema_version = '2.0.0'
|
|
203
|
+
artifact_kind = 'start-attestation'
|
|
204
|
+
id = "start-$Nonce"
|
|
205
|
+
start_attestation_ref = "start-$Nonce"
|
|
206
|
+
invocation_ref = [string]$Record.invocation_id
|
|
207
|
+
workspace = [ordered]@{
|
|
208
|
+
workspace_root = [string]$Record.workspace.workspace_root
|
|
209
|
+
workspace_sha256 = [string]$Record.workspace.workspace_sha256
|
|
210
|
+
profile_path = [string]$Record.workspace.profile_path
|
|
211
|
+
profile_sha256 = [string]$Record.workspace.profile_sha256
|
|
212
|
+
}
|
|
213
|
+
host = $Principal.HostName
|
|
214
|
+
session_id = $Principal.SessionId
|
|
215
|
+
parent_agent_id = $Principal.ParentAgentId
|
|
216
|
+
agent_id = $AgentId
|
|
217
|
+
agent_type = $AgentType
|
|
218
|
+
role = [string]$Record.role
|
|
219
|
+
deployment = [ordered]@{
|
|
220
|
+
agent_name = [string]$Record.deployment.agent_name
|
|
221
|
+
model = [string]$Record.deployment.model
|
|
222
|
+
reasoning_effort = [string]$Record.deployment.reasoning_effort
|
|
223
|
+
actual_model = $(if ([string]::IsNullOrEmpty($ActualModel)) { $null } else { $ActualModel })
|
|
224
|
+
actual_effort = $(if ([string]::IsNullOrEmpty($ActualReasoningEffort)) { $null } else { $ActualReasoningEffort })
|
|
225
|
+
}
|
|
226
|
+
agent_profile = [ordered]@{
|
|
227
|
+
path = $AgentProfile.ProfilePath
|
|
228
|
+
sha256 = $AgentProfile.ProfileSha256
|
|
229
|
+
name = $AgentProfile.ProfileName
|
|
230
|
+
}
|
|
231
|
+
capability_ref = [string]$Record.capability_ref
|
|
232
|
+
runtime = $Record.runtime
|
|
233
|
+
attested_at = $Now.ToUniversalTime().ToString("yyyy-MM-dd'T'HH:mm:ss'Z'")
|
|
234
|
+
writer = [ordered]@{ host = $Principal.HostName; event = 'SubagentStart'; handler_sha256 = $HandlerSha256 }
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function Get-DiscoveryStartStore {
|
|
239
|
+
<#
|
|
240
|
+
.SYNOPSIS
|
|
241
|
+
Returns the real-filesystem store seams (ReadText, ListEntries, WriteText, ReplaceText).
|
|
242
|
+
.DESCRIPTION
|
|
243
|
+
WriteText creates the attestation exclusively, so a second start for the same
|
|
244
|
+
invocation fails; ReplaceText rewrites the invocation record in place.
|
|
245
|
+
#>
|
|
246
|
+
[CmdletBinding()]
|
|
247
|
+
[OutputType([hashtable])]
|
|
248
|
+
param()
|
|
249
|
+
|
|
250
|
+
return @{
|
|
251
|
+
ReadText = {
|
|
252
|
+
param([string]$Path)
|
|
253
|
+
if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) { return $null }
|
|
254
|
+
return [System.IO.File]::ReadAllText($Path, [System.Text.Encoding]::UTF8)
|
|
255
|
+
}
|
|
256
|
+
ListEntries = {
|
|
257
|
+
param([string]$Directory)
|
|
258
|
+
if (-not (Test-Path -LiteralPath $Directory -PathType Container)) { return @() }
|
|
259
|
+
return @(Get-ChildItem -LiteralPath $Directory -Force | Sort-Object -Property Name | ForEach-Object { $_.Name })
|
|
260
|
+
}
|
|
261
|
+
WriteText = {
|
|
262
|
+
param([string]$Path, [string]$Text)
|
|
263
|
+
$stream = [System.IO.File]::Open($Path, [System.IO.FileMode]::CreateNew, [System.IO.FileAccess]::Write, [System.IO.FileShare]::None)
|
|
264
|
+
try {
|
|
265
|
+
$bytes = [System.Text.Encoding]::UTF8.GetBytes($Text)
|
|
266
|
+
$stream.Write($bytes, 0, $bytes.Length)
|
|
267
|
+
} finally { $stream.Dispose() }
|
|
268
|
+
}
|
|
269
|
+
ReplaceText = {
|
|
270
|
+
param([string]$Path, [string]$Text)
|
|
271
|
+
[System.IO.File]::WriteAllText($Path, $Text, [System.Text.UTF8Encoding]::new($false))
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function Get-DiscoveryStartAttestationFileName {
|
|
277
|
+
<#
|
|
278
|
+
.SYNOPSIS
|
|
279
|
+
Returns the file name of the start attestation inside an invocation directory.
|
|
280
|
+
#>
|
|
281
|
+
[CmdletBinding()]
|
|
282
|
+
[OutputType([string])]
|
|
283
|
+
param()
|
|
284
|
+
|
|
285
|
+
return $script:StartAttestationFile
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
Export-ModuleMember -Function `
|
|
289
|
+
Get-DiscoveryAgentProfilePath, `
|
|
290
|
+
ConvertFrom-DiscoveryMarkdownProfile, `
|
|
291
|
+
Read-DiscoveryAgentProfile, `
|
|
292
|
+
ConvertTo-DiscoveryUtcInstant, `
|
|
293
|
+
ConvertTo-DiscoveryStartedInvocationText, `
|
|
294
|
+
ConvertTo-DiscoveryStartAttestationRecord, `
|
|
295
|
+
Get-DiscoveryStartStore, `
|
|
296
|
+
Get-DiscoveryStartAttestationFileName
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
<#
|
|
2
|
+
.SYNOPSIS
|
|
3
|
+
Native SubagentStop validation of a discovery specialist's protected output authority.
|
|
4
|
+
|
|
5
|
+
.DESCRIPTION
|
|
6
|
+
Every native discovery SubagentStop adapter (Claude Code, Codex, GitHub
|
|
7
|
+
Copilot) takes its decision from this module. A stop whose agent type is not
|
|
8
|
+
a discovery specialist is skipped, so ordinary and epic stop validation keeps
|
|
9
|
+
its existing path. For a discovery specialist the stop result is derived from
|
|
10
|
+
the protected invocation bound to the host-assigned agent identity, never from
|
|
11
|
+
the final assistant message: the invocation must be finalized through the
|
|
12
|
+
gateway, its completion receipt must be the gateway-written `validation-receipt`
|
|
13
|
+
for this invocation, session, role and gate mode, bound to the current
|
|
14
|
+
activation and workspace, with every required output still present, every
|
|
15
|
+
input unchanged since the receipt (re-read and re-hashed) and output_valid
|
|
16
|
+
true; only the migration-coverage-reviewer may stop on a valid failing report.
|
|
17
|
+
Filenames or success prose in the final text cannot admit a stop, and a blank
|
|
18
|
+
final text cannot skip the validation.
|
|
19
|
+
|
|
20
|
+
Store and workspace access flows through injected seams; no process is invoked
|
|
21
|
+
and no orchestrator checkpoint is read or written.
|
|
22
|
+
|
|
23
|
+
.NOTES
|
|
24
|
+
Issue #980. Compatible with PowerShell 7+. Depends only on the sibling
|
|
25
|
+
DiscoveryEncoding, DiscoveryAuthority, DiscoveryManifest, DiscoveryPayload and
|
|
26
|
+
DiscoveryStartRecord modules.
|
|
27
|
+
CONVENTION: this module fails fast at module scope and imports its siblings with -ErrorAction Stop.
|
|
28
|
+
#>
|
|
29
|
+
|
|
30
|
+
Set-StrictMode -Version Latest
|
|
31
|
+
$ErrorActionPreference = 'Stop'
|
|
32
|
+
|
|
33
|
+
Import-Module (Join-Path $PSScriptRoot 'DiscoveryEncoding.psm1') -ErrorAction Stop
|
|
34
|
+
Import-Module (Join-Path $PSScriptRoot 'DiscoveryAuthority.psm1') -ErrorAction Stop
|
|
35
|
+
Import-Module (Join-Path $PSScriptRoot 'DiscoveryManifest.psm1') -ErrorAction Stop
|
|
36
|
+
Import-Module (Join-Path $PSScriptRoot 'DiscoveryPayload.psm1') -ErrorAction Stop
|
|
37
|
+
Import-Module (Join-Path $PSScriptRoot 'DiscoveryStartRecord.psm1') -ErrorAction Stop
|
|
38
|
+
|
|
39
|
+
$script:ReceiptsPartition = 'receipts'
|
|
40
|
+
$script:ReviewerRole = 'migration-coverage-reviewer'
|
|
41
|
+
$script:ResultTokenPattern = '(?<![A-Za-z])(PASS|FAIL|BLOCKED)(?![A-Za-z])'
|
|
42
|
+
$script:CorrelationIdPattern = '^[A-Za-z0-9][A-Za-z0-9._:-]*$'
|
|
43
|
+
$script:RuntimeKeys = @('build_sha256', 'package_version', 'policy_sha256', 'policy_version', 'schema_version')
|
|
44
|
+
|
|
45
|
+
function Get-DiscoveryStopOutcome {
|
|
46
|
+
<#
|
|
47
|
+
.SYNOPSIS
|
|
48
|
+
Builds one stop outcome (skip, allow or block) with its code and reason.
|
|
49
|
+
#>
|
|
50
|
+
[CmdletBinding()]
|
|
51
|
+
[OutputType([pscustomobject])]
|
|
52
|
+
param(
|
|
53
|
+
[Parameter(Mandatory = $true)][ValidateSet('skip', 'allow', 'block')][string]$Decision,
|
|
54
|
+
[Parameter(Mandatory = $true)][string]$Code,
|
|
55
|
+
[Parameter(Mandatory = $true)][string]$Reason,
|
|
56
|
+
[Parameter(Mandatory = $false)][AllowNull()][object]$Receipt = $null,
|
|
57
|
+
[Parameter(Mandatory = $false)][AllowNull()][object]$Invocation = $null
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
return [pscustomobject]@{ Decision = $Decision; Code = $Code; Reason = $Reason; Receipt = $Receipt; Invocation = $Invocation }
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function Read-DiscoveryStopJson {
|
|
64
|
+
<#
|
|
65
|
+
.SYNOPSIS
|
|
66
|
+
Reads one JSON object from the store, returning Text and Record or $null members.
|
|
67
|
+
#>
|
|
68
|
+
[CmdletBinding()]
|
|
69
|
+
[OutputType([pscustomobject])]
|
|
70
|
+
param(
|
|
71
|
+
[Parameter(Mandatory = $true)][hashtable]$Store,
|
|
72
|
+
[Parameter(Mandatory = $true)][string]$Path
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
$text = Invoke-DiscoveryStoreSeam -Seam $Store.ReadText -Arguments @($Path)
|
|
76
|
+
if ($null -eq $text) { return [pscustomobject]@{ Text = $null; Record = $null } }
|
|
77
|
+
try { $record = ConvertFrom-Json -InputObject ([string]$text) -Depth 16 } catch { $record = $null }
|
|
78
|
+
if ($record -isnot [System.Management.Automation.PSCustomObject]) { $record = $null }
|
|
79
|
+
return [pscustomobject]@{ Text = [string]$text; Record = $record }
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function Find-DiscoveryAgentInvocationForStop {
|
|
83
|
+
<#
|
|
84
|
+
.SYNOPSIS
|
|
85
|
+
Finds the discovery invocations bound to the stopping agent (started or finalized).
|
|
86
|
+
.DESCRIPTION
|
|
87
|
+
Enumerates `<session>/invocations/*/invocation.json` and keeps every
|
|
88
|
+
`discovery-invocation` whose host, session, parent, agent type and agent id
|
|
89
|
+
equal the stopping principal's. An unreadable record is skipped.
|
|
90
|
+
.OUTPUTS
|
|
91
|
+
[pscustomobject] Count, Record, Directory.
|
|
92
|
+
#>
|
|
93
|
+
[CmdletBinding()]
|
|
94
|
+
[OutputType([pscustomobject])]
|
|
95
|
+
param(
|
|
96
|
+
[Parameter(Mandatory = $true)][pscustomobject]$Location,
|
|
97
|
+
[Parameter(Mandatory = $true)][pscustomobject]$Principal,
|
|
98
|
+
[Parameter(Mandatory = $true)][string]$AgentType,
|
|
99
|
+
[Parameter(Mandatory = $true)][string]$AgentId,
|
|
100
|
+
[Parameter(Mandatory = $true)][hashtable]$Store
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
$directory = "$(Get-DiscoverySessionDirectory -Location $Location -SessionId $Principal.SessionId)/invocations"
|
|
104
|
+
$bindings = [System.Collections.Generic.List[object]]::new()
|
|
105
|
+
foreach ($entry in @(Invoke-DiscoveryStoreSeam -Seam $Store.ListEntries -Arguments @($directory))) {
|
|
106
|
+
$read = Read-DiscoveryStopJson -Store $Store -Path "$directory/$entry/invocation.json"
|
|
107
|
+
$record = $read.Record
|
|
108
|
+
if ($null -eq $record) { continue }
|
|
109
|
+
$names = @($record.PSObject.Properties.Name)
|
|
110
|
+
$missing = @(@('artifact_kind', 'host', 'session_id', 'parent_agent_id', 'agent_id', 'state', 'deployment') | Where-Object { $names -notcontains $_ })
|
|
111
|
+
if ($missing.Count -gt 0 -or $record.deployment -isnot [System.Management.Automation.PSCustomObject]) { continue }
|
|
112
|
+
$bound = ($record.artifact_kind -eq 'discovery-invocation') -and ($record.host -eq $Principal.HostName) -and
|
|
113
|
+
($record.session_id -eq $Principal.SessionId) -and ($record.parent_agent_id -eq $Principal.ParentAgentId) -and
|
|
114
|
+
([string]$record.deployment.agent_name -ceq $AgentType) -and ([string]$record.agent_id -ceq $AgentId) -and
|
|
115
|
+
(@('started', 'finalized') -contains [string]$record.state)
|
|
116
|
+
if ($bound) { $bindings.Add([pscustomobject]@{ Record = $record; Directory = "$directory/$entry" }) }
|
|
117
|
+
}
|
|
118
|
+
if ($bindings.Count -ne 1) { return [pscustomobject]@{ Count = $bindings.Count; Record = $null; Directory = $null } }
|
|
119
|
+
return [pscustomobject]@{ Count = 1; Record = $bindings[0].Record; Directory = $bindings[0].Directory }
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function Get-DiscoveryReceiptMismatch {
|
|
123
|
+
<#
|
|
124
|
+
.SYNOPSIS
|
|
125
|
+
Compares the completion receipt against the invocation, principal, activation and current workspace.
|
|
126
|
+
.DESCRIPTION
|
|
127
|
+
Returns $null when the receipt authorizes the stop, otherwise an object with
|
|
128
|
+
Code (RECEIPT, GATE, IDENTITY, FORGED, BINDING, STALE_ACTIVATION, RUNTIME,
|
|
129
|
+
OUTPUTS, OUTPUT_INVALID, RESULT, STALE_INPUTS or OUTPUT_MISSING) and Reason.
|
|
130
|
+
#>
|
|
131
|
+
[CmdletBinding()]
|
|
132
|
+
[OutputType([pscustomobject])]
|
|
133
|
+
param(
|
|
134
|
+
[Parameter(Mandatory = $true)][pscustomobject]$Receipt,
|
|
135
|
+
[Parameter(Mandatory = $true)][pscustomobject]$Record,
|
|
136
|
+
[Parameter(Mandatory = $true)][pscustomobject]$Principal,
|
|
137
|
+
[Parameter(Mandatory = $true)][string]$AgentId,
|
|
138
|
+
[Parameter(Mandatory = $true)][pscustomobject]$Activation,
|
|
139
|
+
[Parameter(Mandatory = $true)][string]$ActivationSha256,
|
|
140
|
+
[Parameter(Mandatory = $true)][pscustomobject]$Location,
|
|
141
|
+
[Parameter(Mandatory = $true)][scriptblock]$ReadBytes
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
$names = @($Receipt.PSObject.Properties.Name)
|
|
145
|
+
foreach ($required in @('artifact_kind', 'receipt_ref', 'mode', 'identity', 'writer', 'workspace', 'activation_sha256', 'runtime', 'required_outputs', 'output_valid', 'validation_result', 'input_manifest')) {
|
|
146
|
+
if ($names -notcontains $required) { return [pscustomobject]@{ Code = 'RECEIPT'; Reason = "the completion receipt lacks '$required'" } }
|
|
147
|
+
}
|
|
148
|
+
if ($Receipt.artifact_kind -ne 'validation-receipt' -or [string]$Receipt.receipt_ref -cne [string]$Record.completion_receipt_ref) {
|
|
149
|
+
return [pscustomobject]@{ Code = 'RECEIPT'; Reason = 'the completion receipt is not the validation-receipt the invocation names' }
|
|
150
|
+
}
|
|
151
|
+
if ([string]$Receipt.mode -ne 'agent-output') {
|
|
152
|
+
return [pscustomobject]@{ Code = 'GATE'; Reason = "the completion receipt was issued for gate mode '$($Receipt.mode)', not agent-output" }
|
|
153
|
+
}
|
|
154
|
+
$identity = $Receipt.identity
|
|
155
|
+
$identityBound = ($identity -is [System.Management.Automation.PSCustomObject]) -and ($identity.kind -eq 'discovery-agent') -and
|
|
156
|
+
($identity.host -eq $Principal.HostName) -and ($identity.session_id -eq $Principal.SessionId) -and
|
|
157
|
+
([string]$identity.agent_id -ceq $AgentId) -and ([string]$identity.role -ceq [string]$Record.role) -and
|
|
158
|
+
([string]$identity.invocation_id -ceq [string]$Record.invocation_id)
|
|
159
|
+
if (-not $identityBound) {
|
|
160
|
+
return [pscustomobject]@{ Code = 'IDENTITY'; Reason = 'the completion receipt is bound to another agent, session, role or invocation' }
|
|
161
|
+
}
|
|
162
|
+
$writer = $Receipt.writer
|
|
163
|
+
if ($writer -isnot [System.Management.Automation.PSCustomObject] -or $writer.kind -ne 'host-gateway' -or $writer.host -ne $Principal.HostName) {
|
|
164
|
+
return [pscustomobject]@{ Code = 'FORGED'; Reason = 'the completion receipt was not written by the host gateway' }
|
|
165
|
+
}
|
|
166
|
+
$sameBinding = ([string]$Receipt.workspace.workspace_sha256 -eq $Location.WorkspaceSha256) -and
|
|
167
|
+
([string]$Receipt.workspace.workspace_root -eq [string]$Record.workspace.workspace_root) -and
|
|
168
|
+
([string]$Receipt.workspace.profile_path -eq [string]$Activation.profile_path) -and
|
|
169
|
+
([string]$Receipt.workspace.profile_sha256 -eq [string]$Activation.profile_sha256)
|
|
170
|
+
if (-not $sameBinding) {
|
|
171
|
+
return [pscustomobject]@{ Code = 'BINDING'; Reason = 'the completion receipt is bound to another workspace or profile than the current activation' }
|
|
172
|
+
}
|
|
173
|
+
if ([string]$Receipt.activation_sha256 -cne $ActivationSha256) {
|
|
174
|
+
return [pscustomobject]@{ Code = 'STALE_ACTIVATION'; Reason = 'the activation record changed after the completion receipt was issued' }
|
|
175
|
+
}
|
|
176
|
+
foreach ($key in $script:RuntimeKeys) {
|
|
177
|
+
if ([string]$Receipt.runtime.$key -cne [string]$Record.runtime.$key) {
|
|
178
|
+
return [pscustomobject]@{ Code = 'RUNTIME'; Reason = "runtime pin $key of the receipt differs from the invocation" }
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
$outputs = $Receipt.required_outputs
|
|
182
|
+
if ($outputs -isnot [System.Management.Automation.PSCustomObject] -or [string]$outputs.manifest_sha256 -cne [string]$Record.required_outputs.manifest_sha256) {
|
|
183
|
+
return [pscustomobject]@{ Code = 'OUTPUTS'; Reason = 'the completion receipt does not carry the required-output manifest of the invocation' }
|
|
184
|
+
}
|
|
185
|
+
if ($Receipt.output_valid -isnot [bool] -or -not [bool]$Receipt.output_valid) {
|
|
186
|
+
return [pscustomobject]@{ Code = 'OUTPUT_INVALID'; Reason = 'the completion receipt does not mark the required outputs valid' }
|
|
187
|
+
}
|
|
188
|
+
$result = [string]$Receipt.validation_result
|
|
189
|
+
if ($result -ne 'pass' -and -not ($result -eq 'fail' -and [string]$Record.role -eq $script:ReviewerRole)) {
|
|
190
|
+
return [pscustomobject]@{ Code = 'RESULT'; Reason = "validation result '$result' does not complete role $($Record.role); only the reviewer may stop on a valid failing report" }
|
|
191
|
+
}
|
|
192
|
+
try {
|
|
193
|
+
$manifest = ConvertFrom-DiscoveryInputManifest -Value $Receipt.input_manifest
|
|
194
|
+
} catch {
|
|
195
|
+
return [pscustomobject]@{ Code = 'RECEIPT'; Reason = "the completion receipt input manifest is invalid: $($_.Exception.Message)" }
|
|
196
|
+
}
|
|
197
|
+
$comparison = Compare-DiscoveryInputManifest -Manifest $manifest -WorkspaceRoot ([string]$Record.workspace.workspace_root) `
|
|
198
|
+
-ProfilePath ([string]$Activation.profile_path) -ProfileSha256 ([string]$Activation.profile_sha256) -ReadBytes $ReadBytes
|
|
199
|
+
if ($comparison.Drift.Count -gt 0) {
|
|
200
|
+
$summary = @($comparison.Drift | ForEach-Object { "$($_.kind):$($_.path)" }) -join ', '
|
|
201
|
+
return [pscustomobject]@{ Code = 'STALE_INPUTS'; Reason = "inputs changed after the completion receipt: $summary" }
|
|
202
|
+
}
|
|
203
|
+
foreach ($output in @($outputs.outputs)) {
|
|
204
|
+
$rowNames = @($output.PSObject.Properties.Name)
|
|
205
|
+
if (@('artifact_kind', 'id', 'path') | Where-Object { $rowNames -notcontains $_ -or [string]::IsNullOrWhiteSpace([string]$output.$_) }) {
|
|
206
|
+
return [pscustomobject]@{ Code = 'OUTPUTS'; Reason = 'a required output row lacks its kind, id or path' }
|
|
207
|
+
}
|
|
208
|
+
$bytes = & $ReadBytes (Get-DiscoveryWorkspacePath -WorkspaceRoot ([string]$Record.workspace.workspace_root) -RelativePath ([string]$output.path))
|
|
209
|
+
if ($null -eq $bytes) {
|
|
210
|
+
return [pscustomobject]@{ Code = 'OUTPUT_MISSING'; Reason = "required output $($output.path) ($($output.artifact_kind) $($output.id)) is absent from the workspace" }
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return $null
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function Get-DiscoveryStopDecision {
|
|
217
|
+
<#
|
|
218
|
+
.SYNOPSIS
|
|
219
|
+
Decides one SubagentStop event for a native discovery adapter.
|
|
220
|
+
.DESCRIPTION
|
|
221
|
+
Order: envelope anomalies block; a non-discovery agent type is skipped; a
|
|
222
|
+
missing agent identity in the payload blocks; the activation must be active; exactly one
|
|
223
|
+
started or finalized invocation must bind this agent; the invocation must be
|
|
224
|
+
finalized with a completion receipt that passes Get-DiscoveryReceiptMismatch;
|
|
225
|
+
finally the final text must state the explicit result the receipt proves.
|
|
226
|
+
When StopHookActive is true a block becomes an allow with code CONTINUED so
|
|
227
|
+
the host does not loop; the authority remains with the receipts.
|
|
228
|
+
.OUTPUTS
|
|
229
|
+
[pscustomobject] Decision, Code, Reason, Receipt, Invocation.
|
|
230
|
+
#>
|
|
231
|
+
[CmdletBinding()]
|
|
232
|
+
[OutputType([pscustomobject])]
|
|
233
|
+
param(
|
|
234
|
+
[Parameter(Mandatory = $true)][pscustomobject]$Normalized,
|
|
235
|
+
[Parameter(Mandatory = $true)][hashtable]$HostEnvironment,
|
|
236
|
+
[Parameter(Mandatory = $false)][AllowNull()][AllowEmptyString()][string]$AssistantText = '',
|
|
237
|
+
[Parameter(Mandatory = $false)][bool]$StopHookActive = $false,
|
|
238
|
+
[Parameter(Mandatory = $false)][hashtable]$Store = (Get-DiscoveryStartStore),
|
|
239
|
+
[Parameter(Mandatory = $false)][AllowNull()][scriptblock]$ReadBytes = $null
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
$outcome = Get-DiscoveryStopVerdict -Normalized $Normalized -HostEnvironment $HostEnvironment `
|
|
243
|
+
-AssistantText $AssistantText -Store $Store -ReadBytes $ReadBytes
|
|
244
|
+
if ($outcome.Decision -eq 'block' -and $StopHookActive -and $outcome.Code -ne 'ENVELOPE') {
|
|
245
|
+
return Get-DiscoveryStopOutcome -Decision 'allow' -Code 'CONTINUED' -Reason "$($outcome.Reason) (a continuation was already requested; the protected receipts remain authoritative)" -Receipt $outcome.Receipt -Invocation $outcome.Invocation
|
|
246
|
+
}
|
|
247
|
+
return $outcome
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function Get-DiscoveryStopVerdict {
|
|
251
|
+
<#
|
|
252
|
+
.SYNOPSIS
|
|
253
|
+
Evaluates the stop without the continuation rule; see Get-DiscoveryStopDecision.
|
|
254
|
+
#>
|
|
255
|
+
[CmdletBinding()]
|
|
256
|
+
[OutputType([pscustomobject])]
|
|
257
|
+
param(
|
|
258
|
+
[Parameter(Mandatory = $true)][pscustomobject]$Normalized,
|
|
259
|
+
[Parameter(Mandatory = $true)][hashtable]$HostEnvironment,
|
|
260
|
+
[Parameter(Mandatory = $false)][AllowNull()][AllowEmptyString()][string]$AssistantText = '',
|
|
261
|
+
[Parameter(Mandatory = $true)][hashtable]$Store,
|
|
262
|
+
[Parameter(Mandatory = $false)][AllowNull()][scriptblock]$ReadBytes = $null
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
if ($null -ne $Normalized.Anomaly) {
|
|
266
|
+
return Get-DiscoveryStopOutcome -Decision 'block' -Code 'ENVELOPE' -Reason ('payload anomaly - ' + (Get-DiscoveryPayloadAnomalyReason -Code $Normalized.Anomaly))
|
|
267
|
+
}
|
|
268
|
+
$agentType = [string]$Normalized.AgentType
|
|
269
|
+
$role = Get-DiscoveryAgentRole -AgentType $agentType
|
|
270
|
+
if ($null -eq $role) {
|
|
271
|
+
return Get-DiscoveryStopOutcome -Decision 'skip' -Code 'NOT_DISCOVERY_AGENT' -Reason 'the stopping agent is not a discovery specialist; the ordinary stop validation applies'
|
|
272
|
+
}
|
|
273
|
+
# The principal (host, session, parent, agent) comes from the stop payload alone.
|
|
274
|
+
$principal = Get-DiscoveryTrustedPrincipal -Normalized $Normalized
|
|
275
|
+
if ($null -eq $principal.AgentId) {
|
|
276
|
+
return Get-DiscoveryStopOutcome -Decision 'block' -Code 'IDENTITY' -Reason 'the native stop event carries no agent identifier for the discovery specialist'
|
|
277
|
+
}
|
|
278
|
+
$agentId = $principal.AgentId
|
|
279
|
+
if ([string]::IsNullOrWhiteSpace([string]$Normalized.Cwd)) {
|
|
280
|
+
return Get-DiscoveryStopOutcome -Decision 'block' -Code 'WORKSPACE' -Reason 'the native event carries no workspace directory'
|
|
281
|
+
}
|
|
282
|
+
try {
|
|
283
|
+
$location = Resolve-DiscoveryAuthorityLocation -WorkspaceRoot ([string]$Normalized.Cwd) -Platform $HostEnvironment.Platform `
|
|
284
|
+
-UserHome $HostEnvironment.UserHome -LocalAppData $HostEnvironment.LocalAppData -XdgStateHome $HostEnvironment.XdgStateHome
|
|
285
|
+
} catch {
|
|
286
|
+
return Get-DiscoveryStopOutcome -Decision 'block' -Code 'WORKSPACE' -Reason $_.Exception.Message
|
|
287
|
+
}
|
|
288
|
+
$activation = Read-DiscoveryActivationState -Location $location -ReadText $Store.ReadText
|
|
289
|
+
if ($activation.Status -ne 'active') {
|
|
290
|
+
return Get-DiscoveryStopOutcome -Decision 'block' -Code 'ACTIVATION' -Reason "discovery is not active for this workspace ($($activation.Status): $($activation.Reason))"
|
|
291
|
+
}
|
|
292
|
+
$activationText = [string](Invoke-DiscoveryStoreSeam -Seam $Store.ReadText -Arguments @($activation.Path))
|
|
293
|
+
$found = Find-DiscoveryAgentInvocationForStop -Location $location -Principal $principal -AgentType $agentType -AgentId $agentId -Store $Store
|
|
294
|
+
if ($found.Count -eq 0) {
|
|
295
|
+
return Get-DiscoveryStopOutcome -Decision 'block' -Code 'NO_INVOCATION' -Reason "no protected invocation binds agent $agentId ($agentType) in this session; the specialist stopped without an attested start"
|
|
296
|
+
}
|
|
297
|
+
if ($found.Count -gt 1) {
|
|
298
|
+
return Get-DiscoveryStopOutcome -Decision 'block' -Code 'AMBIGUOUS' -Reason "$($found.Count) protected invocations bind agent $agentId; the stop cannot be attributed to one"
|
|
299
|
+
}
|
|
300
|
+
$record = $found.Record
|
|
301
|
+
if ([string]$record.role -cne $role) {
|
|
302
|
+
return Get-DiscoveryStopOutcome -Decision 'block' -Code 'ROLE' -Reason "agent type $agentType acts as $role but the invocation was prepared for role $($record.role)" -Invocation $record
|
|
303
|
+
}
|
|
304
|
+
if ([string]$record.state -ne 'finalized' -or [string]::IsNullOrWhiteSpace([string]$record.completion_receipt_ref)) {
|
|
305
|
+
return Get-DiscoveryStopOutcome -Decision 'block' -Code 'NOT_FINALIZED' -Reason "invocation $($record.invocation_id) was not finalized through finalize_discovery_agent; no output authority exists for this stop" -Invocation $record
|
|
306
|
+
}
|
|
307
|
+
$read = Read-DiscoveryStopJson -Store $Store -Path "$($found.Directory)/$($script:ReceiptsPartition)/$($record.completion_receipt_ref).json"
|
|
308
|
+
if ($null -eq $read.Record) {
|
|
309
|
+
return Get-DiscoveryStopOutcome -Decision 'block' -Code 'RECEIPT' -Reason "the completion receipt $($record.completion_receipt_ref) is missing or unreadable in the protected store" -Invocation $record
|
|
310
|
+
}
|
|
311
|
+
if ($null -eq $ReadBytes) {
|
|
312
|
+
$ReadBytes = {
|
|
313
|
+
param([string]$Path)
|
|
314
|
+
if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) { return $null }
|
|
315
|
+
return [System.IO.File]::ReadAllBytes($Path)
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
$mismatch = Get-DiscoveryReceiptMismatch -Receipt $read.Record -Record $record -Principal $principal -AgentId $agentId -Activation $activation.Record `
|
|
319
|
+
-ActivationSha256 (Get-DiscoverySha256Hex -Text $activationText) -Location $location -ReadBytes $ReadBytes
|
|
320
|
+
if ($null -ne $mismatch) {
|
|
321
|
+
return Get-DiscoveryStopOutcome -Decision 'block' -Code $mismatch.Code -Reason $mismatch.Reason -Receipt $read.Record -Invocation $record
|
|
322
|
+
}
|
|
323
|
+
# The final text never grants authority; it must only state the result the receipt proves.
|
|
324
|
+
$expectedToken = $(if ([string]$read.Record.validation_result -eq 'pass') { 'PASS' } else { 'FAIL' })
|
|
325
|
+
$tokens = @([regex]::Matches([string]$AssistantText, $script:ResultTokenPattern) | ForEach-Object { $_.Groups[1].Value })
|
|
326
|
+
if ($tokens -notcontains $expectedToken) {
|
|
327
|
+
return Get-DiscoveryStopOutcome -Decision 'block' -Code 'RESULT_TEXT' -Reason "the final message must state the explicit $expectedToken result the protected receipt proves" -Receipt $read.Record -Invocation $record
|
|
328
|
+
}
|
|
329
|
+
return Get-DiscoveryStopOutcome -Decision 'allow' -Code 'VALIDATED' -Reason "invocation $($record.invocation_id) of $role completed with a current $($read.Record.validation_result) receipt" -Receipt $read.Record -Invocation $record
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
Export-ModuleMember -Function `
|
|
333
|
+
Find-DiscoveryAgentInvocationForStop, `
|
|
334
|
+
Get-DiscoveryReceiptMismatch, `
|
|
335
|
+
Get-DiscoveryStopDecision, `
|
|
336
|
+
Get-DiscoveryStopVerdict
|