From 4db8fc295b004b218f3e8b176a86a66ed39e0203 Mon Sep 17 00:00:00 2001 From: Justin Emerson Date: Tue, 25 Aug 2026 22:05:52 -0700 Subject: [PATCH 01/29] fix(tools): resolve exact boolean wire transforms Recognize only AST-exact Boolean casts and zero-argument ToString/ToLower chains while preserving the resolver's never-guess boundary. Co-Authored-By: Claude Opus 5 (1M context) --- Tests/PfbCmdletParamTools.Tests.ps1 | 59 +++++++++++++++++++++++++++++ tools/lib/PfbCmdletParamTools.ps1 | 38 ++++++++++++++++++- 2 files changed, 96 insertions(+), 1 deletion(-) diff --git a/Tests/PfbCmdletParamTools.Tests.ps1 b/Tests/PfbCmdletParamTools.Tests.ps1 index 9785bae1..eadae292 100644 --- a/Tests/PfbCmdletParamTools.Tests.ps1 +++ b/Tests/PfbCmdletParamTools.Tests.ps1 @@ -1343,6 +1343,65 @@ function Test-Fixture { } } +Describe 'Exact boolean wire-value transforms (issue #141)' { + BeforeAll { + function Get-TestBooleanWireFunctionAst { + param([string]$Source) + $tokens = $null; $errs = $null + $ast = [System.Management.Automation.Language.Parser]::ParseInput($Source, [ref]$tokens, [ref]$errs) + $ast.FindAll({ param($n) $n -is [System.Management.Automation.Language.FunctionDefinitionAst] }, $true) | Select-Object -First 1 + } + } + + It 'resolves an exact [bool] cast through an index assignment' { + $funcAst = Get-TestBooleanWireFunctionAst 'function Test-Fixture { param([switch]$Destroyed) $body = @{}; $body[''destroyed''] = [bool]$Destroyed }' + $result = Get-PfbWireNameForParameter -FunctionAst $funcAst -ParameterName 'Destroyed' -IsBooleanLikeParameter + $result.WireName | Should -Be 'destroyed' + $result.TargetVariable | Should -Be 'body' + } + + It 'resolves the exact zero-argument ToString/ToLower chain through an index assignment' { + $funcAst = Get-TestBooleanWireFunctionAst 'function Test-Fixture { param([switch]$Flagged) $queryParams = @{}; $queryParams[''flagged''] = ([bool]$Flagged).ToString().ToLower() }' + $result = Get-PfbWireNameForParameter -FunctionAst $funcAst -ParameterName 'Flagged' -IsBooleanLikeParameter + $result.WireName | Should -Be 'flagged' + $result.TargetVariable | Should -Be 'queryParams' + } + + It 'resolves the new exact forms through the hashtable-literal value path' -ForEach @( + @{ Parameter = 'Destroyed'; WireName = 'destroyed'; TargetVariable = 'body'; Value = '[bool]$Destroyed' } + @{ Parameter = 'Flagged'; WireName = 'flagged'; TargetVariable = 'queryParams'; Value = '([bool]$Flagged).ToString().ToLower()' } + ) { + $source = 'function Test-Fixture { param([switch]$' + $Parameter + ') $' + $TargetVariable + ' = @{ ''' + $WireName + ''' = ' + $Value + ' } }' + $funcAst = Get-TestBooleanWireFunctionAst $source + $result = Get-PfbWireNameForParameter -FunctionAst $funcAst -ParameterName $Parameter -IsBooleanLikeParameter + $result.WireName | Should -Be $WireName + $result.TargetVariable | Should -Be $TargetVariable + } + + It 'refuses ' -ForEach @( + @{ Case = 'a cast rooted at a different variable'; Value = '[bool]$Other' } + @{ Case = 'a cast of a composite operand'; Value = '[bool]($Param -or $Other)' } + @{ Case = 'a method chain rooted at a different variable'; Value = '([bool]$Other).ToString().ToLower()' } + @{ Case = 'a ToString call carrying an argument'; Value = '([bool]$Param).ToString(''x'')' } + @{ Case = 'a method chain ending in a member other than ToLower'; Value = '([bool]$Param).ToString().Trim()' } + @{ Case = 'a unary expression over member access'; Value = '(-not $Param.IsPresent)' } + @{ Case = 'string interpolation that merely mentions the parameter'; Value = '"$Param"' } + ) { + $source = 'function Test-Fixture { param([switch]$Param, [switch]$Other) $body = @{}; $body[''k''] = ' + $Value + ' }' + $funcAst = Get-TestBooleanWireFunctionAst $source + Get-PfbWireNameForParameter -FunctionAst $funcAst -ParameterName 'Param' -IsBooleanLikeParameter | Should -BeNullOrEmpty + } + + It 'refuses when -IsBooleanLikeParameter is absent for a string parameter' -ForEach @( + @{ Shape = 'an exact [bool] cast'; Value = '[bool]$Param' } + @{ Shape = 'the exact zero-argument ToString/ToLower chain'; Value = '([bool]$Param).ToString().ToLower()' } + ) { + $source = 'function Test-Fixture { param([string]$Param) $body = @{}; $body[''k''] = ' + $Value + ' }' + $funcAst = Get-TestBooleanWireFunctionAst $source + Get-PfbWireNameForParameter -FunctionAst $funcAst -ParameterName 'Param' | Should -BeNullOrEmpty + } +} + Describe 'Get-PfbCmdletParameterInventory - wire surface' { BeforeAll { $script:inventoryRoot = Join-Path $TestDrive 'WireSurface/Public' diff --git a/tools/lib/PfbCmdletParamTools.ps1 b/tools/lib/PfbCmdletParamTools.ps1 index 58edc997..4b58dc23 100644 --- a/tools/lib/PfbCmdletParamTools.ps1 +++ b/tools/lib/PfbCmdletParamTools.ps1 @@ -157,6 +157,10 @@ function Test-PfbWireValueIsParameter { $Param -- direct @($Param) -- array-wrapped $Param -join ',' -- joined into a plural query key + [bool]$Param -- exact Boolean cast, BOOLEAN-LIKE parameters only + ([bool]$Param).ToString().ToLower() + -- exact zero-argument method chain rooted at that cast, + BOOLEAN-LIKE parameters only 'literal' -- ONLY for a BOOLEAN-LIKE parameter whose mere presence is keyed to a hardcoded string, and only inside an `if ($Param)` guard @@ -177,7 +181,10 @@ function Test-PfbWireValueIsParameter { means the wire value is derived from something other than this parameter alone, and stays refused. Refused (correctly, per this file's "never guess" contract): anything else, e.g. - `"$Param"`. The array-projection shape `@($Param | ForEach-Object { @{ name = $_ } })` + `"$Param"`. Member access, unary expressions and composite operands remain refused: + each can derive the wire value from a property, operation or additional operand rather + than from the named parameter alone. The array-projection shape + `@($Param | ForEach-Object { @{ name = $_ } })` is also refused HERE by design -- it is matched by the sibling Test-PfbWireValueIsParameterProjection, and only ever from the nested-reference resolver, which credits the OUTER key. Matching it in this predicate would let the @@ -206,6 +213,35 @@ function Test-PfbWireValueIsParameter { if ($joinLeft -and $joinLeft.VariablePath.UserPath -eq $ParameterName) { return $true } } + if ($IsBooleanLikeParameter) { + # Exact [bool]$Param, optionally beneath the exact zero-argument + # `(...).ToString().ToLower()` chain. Walk AST nodes rather than text so member access, + # unary expressions and composite cast operands cannot be mistaken for the parameter. + $boolCast = $expr -as [System.Management.Automation.Language.ConvertExpressionAst] + if (-not $boolCast) { + $toLower = $expr -as [System.Management.Automation.Language.InvokeMemberExpressionAst] + if ($toLower -and $toLower.Arguments.Count -eq 0 -and + $toLower.Member -is [System.Management.Automation.Language.StringConstantExpressionAst] -and + $toLower.Member.Value -eq 'ToLower') { + $toString = $toLower.Expression -as [System.Management.Automation.Language.InvokeMemberExpressionAst] + if ($toString -and $toString.Arguments.Count -eq 0 -and + $toString.Member -is [System.Management.Automation.Language.StringConstantExpressionAst] -and + $toString.Member.Value -eq 'ToString') { + $parenthesizedRoot = $toString.Expression -as [System.Management.Automation.Language.ParenExpressionAst] + if ($parenthesizedRoot) { + $chainRoot = Resolve-PfbSingleExpression -Ast $parenthesizedRoot.Pipeline + $boolCast = $chainRoot -as [System.Management.Automation.Language.ConvertExpressionAst] + } + } + } + } + + if ($boolCast -and $boolCast.Type.TypeName.FullName -eq 'bool') { + $castChild = $boolCast.Child -as [System.Management.Automation.Language.VariableExpressionAst] + if ($castChild -and $castChild.VariablePath.UserPath -eq $ParameterName) { return $true } + } + } + if ($IsBooleanLikeParameter -and $expr -is [System.Management.Automation.Language.StringConstantExpressionAst]) { if (Test-PfbAssignmentGuardedBySwitch -Assignment $ValueAst -ParameterName $ParameterName) { return $true } } From 9d86981b254f8d3bc7cda2f2680fcf9e117c7ec1 Mon Sep 17 00:00:00 2001 From: Justin Emerson Date: Tue, 25 Aug 2026 23:14:39 -0700 Subject: [PATCH 02/29] test(tools): harden boolean transform matching Exercise method arity and Boolean type aliases while keeping wire-key fixtures independent of parameter names. Co-Authored-By: Claude Opus 5 (1M context) --- Tests/PfbCmdletParamTools.Tests.ps1 | 37 +++++++++++++++++++---------- tools/lib/PfbCmdletParamTools.ps1 | 30 +++++++++++++++++++---- 2 files changed, 49 insertions(+), 18 deletions(-) diff --git a/Tests/PfbCmdletParamTools.Tests.ps1 b/Tests/PfbCmdletParamTools.Tests.ps1 index eadae292..000bb877 100644 --- a/Tests/PfbCmdletParamTools.Tests.ps1 +++ b/Tests/PfbCmdletParamTools.Tests.ps1 @@ -1353,37 +1353,48 @@ Describe 'Exact boolean wire-value transforms (issue #141)' { } } - It 'resolves an exact [bool] cast through an index assignment' { - $funcAst = Get-TestBooleanWireFunctionAst 'function Test-Fixture { param([switch]$Destroyed) $body = @{}; $body[''destroyed''] = [bool]$Destroyed }' - $result = Get-PfbWireNameForParameter -FunctionAst $funcAst -ParameterName 'Destroyed' -IsBooleanLikeParameter + It 'resolves an exact [bool] cast through an index assignment without guessing the wire key from the parameter name' { + $funcAst = Get-TestBooleanWireFunctionAst 'function Test-Fixture { param([switch]$Param) $body = @{}; $body[''destroyed''] = [bool]$Param }' + $result = Get-PfbWireNameForParameter -FunctionAst $funcAst -ParameterName 'Param' -IsBooleanLikeParameter $result.WireName | Should -Be 'destroyed' $result.TargetVariable | Should -Be 'body' } - It 'resolves the exact zero-argument ToString/ToLower chain through an index assignment' { - $funcAst = Get-TestBooleanWireFunctionAst 'function Test-Fixture { param([switch]$Flagged) $queryParams = @{}; $queryParams[''flagged''] = ([bool]$Flagged).ToString().ToLower() }' - $result = Get-PfbWireNameForParameter -FunctionAst $funcAst -ParameterName 'Flagged' -IsBooleanLikeParameter + It 'resolves the exact zero-argument ToString/ToLower chain through an index assignment without guessing the wire key from the parameter name' { + $funcAst = Get-TestBooleanWireFunctionAst 'function Test-Fixture { param([switch]$Param) $queryParams = @{}; $queryParams[''flagged''] = ([bool]$Param).ToString().ToLower() }' + $result = Get-PfbWireNameForParameter -FunctionAst $funcAst -ParameterName 'Param' -IsBooleanLikeParameter $result.WireName | Should -Be 'flagged' $result.TargetVariable | Should -Be 'queryParams' } - It 'resolves the new exact forms through the hashtable-literal value path' -ForEach @( - @{ Parameter = 'Destroyed'; WireName = 'destroyed'; TargetVariable = 'body'; Value = '[bool]$Destroyed' } - @{ Parameter = 'Flagged'; WireName = 'flagged'; TargetVariable = 'queryParams'; Value = '([bool]$Flagged).ToString().ToLower()' } + It 'resolves the new exact forms through the hashtable-literal value path without guessing the wire key' -ForEach @( + @{ WireName = 'destroyed'; TargetVariable = 'body'; Value = '[bool]$Param' } + @{ WireName = 'flagged'; TargetVariable = 'queryParams'; Value = '([bool]$Param).ToString().ToLower()' } ) { - $source = 'function Test-Fixture { param([switch]$' + $Parameter + ') $' + $TargetVariable + ' = @{ ''' + $WireName + ''' = ' + $Value + ' } }' + $source = 'function Test-Fixture { param([switch]$Param) $' + $TargetVariable + ' = @{ ''' + $WireName + ''' = ' + $Value + ' } }' $funcAst = Get-TestBooleanWireFunctionAst $source - $result = Get-PfbWireNameForParameter -FunctionAst $funcAst -ParameterName $Parameter -IsBooleanLikeParameter + $result = Get-PfbWireNameForParameter -FunctionAst $funcAst -ParameterName 'Param' -IsBooleanLikeParameter $result.WireName | Should -Be $WireName $result.TargetVariable | Should -Be $TargetVariable } + It 'accepts the spelling as the Boolean cast type' -ForEach @( + @{ CastType = 'Boolean' } + @{ CastType = 'System.Boolean' } + ) { + $source = 'function Test-Fixture { param([switch]$Param) $body = @{}; $body[''destroyed''] = [' + $CastType + ']$Param }' + $funcAst = Get-TestBooleanWireFunctionAst $source + (Get-PfbWireNameForParameter -FunctionAst $funcAst -ParameterName 'Param' -IsBooleanLikeParameter).WireName | Should -Be 'destroyed' + } + It 'refuses ' -ForEach @( @{ Case = 'a cast rooted at a different variable'; Value = '[bool]$Other' } @{ Case = 'a cast of a composite operand'; Value = '[bool]($Param -or $Other)' } @{ Case = 'a method chain rooted at a different variable'; Value = '([bool]$Other).ToString().ToLower()' } - @{ Case = 'a ToString call carrying an argument'; Value = '([bool]$Param).ToString(''x'')' } - @{ Case = 'a method chain ending in a member other than ToLower'; Value = '([bool]$Param).ToString().Trim()' } + @{ Case = 'a ToString call carrying an argument without the full chain'; Value = '([bool]$Param).ToString(''x'')' } + @{ Case = 'a ToString call carrying an argument in the full chain'; Value = '([bool]$Param).ToString("G").ToLower()' } + @{ Case = 'a ToLower call carrying an argument'; Value = '([bool]$Param).ToString().ToLower([System.Globalization.CultureInfo]::InvariantCulture)' } + @{ Case = 'a method chain ending in a member other than ToLower'; Value = '([bool]$Param).ToString().Trim()' } @{ Case = 'a unary expression over member access'; Value = '(-not $Param.IsPresent)' } @{ Case = 'string interpolation that merely mentions the parameter'; Value = '"$Param"' } ) { diff --git a/tools/lib/PfbCmdletParamTools.ps1 b/tools/lib/PfbCmdletParamTools.ps1 index 4b58dc23..203e5815 100644 --- a/tools/lib/PfbCmdletParamTools.ps1 +++ b/tools/lib/PfbCmdletParamTools.ps1 @@ -144,6 +144,23 @@ function Resolve-PfbSingleExpression { return $node } +function Test-PfbInvokeHasNoArguments { + <# + .SYNOPSIS + True when an InvokeMemberExpressionAst represents a zero-argument method call. + #> + [CmdletBinding()] + param( + [Parameter(Mandatory)] + [System.Management.Automation.Language.InvokeMemberExpressionAst]$Invoke + ) + + # A zero-argument call exposes Arguments as $null, not as an empty collection -- + # and @($null).Count is 1, so the null case must be tested before wrapping. + if ($null -eq $Invoke.Arguments) { return $true } + return (@($Invoke.Arguments).Count -eq 0) +} + function Test-PfbWireValueIsParameter { <# .SYNOPSIS @@ -220,11 +237,11 @@ function Test-PfbWireValueIsParameter { $boolCast = $expr -as [System.Management.Automation.Language.ConvertExpressionAst] if (-not $boolCast) { $toLower = $expr -as [System.Management.Automation.Language.InvokeMemberExpressionAst] - if ($toLower -and $toLower.Arguments.Count -eq 0 -and + if ($toLower -and (Test-PfbInvokeHasNoArguments -Invoke $toLower) -and $toLower.Member -is [System.Management.Automation.Language.StringConstantExpressionAst] -and $toLower.Member.Value -eq 'ToLower') { $toString = $toLower.Expression -as [System.Management.Automation.Language.InvokeMemberExpressionAst] - if ($toString -and $toString.Arguments.Count -eq 0 -and + if ($toString -and (Test-PfbInvokeHasNoArguments -Invoke $toString) -and $toString.Member -is [System.Management.Automation.Language.StringConstantExpressionAst] -and $toString.Member.Value -eq 'ToString') { $parenthesizedRoot = $toString.Expression -as [System.Management.Automation.Language.ParenExpressionAst] @@ -236,9 +253,12 @@ function Test-PfbWireValueIsParameter { } } - if ($boolCast -and $boolCast.Type.TypeName.FullName -eq 'bool') { - $castChild = $boolCast.Child -as [System.Management.Automation.Language.VariableExpressionAst] - if ($castChild -and $castChild.VariablePath.UserPath -eq $ParameterName) { return $true } + if ($boolCast) { + $castType = $boolCast.Type.TypeName.GetReflectionType() + if ($castType -eq [bool]) { + $castChild = $boolCast.Child -as [System.Management.Automation.Language.VariableExpressionAst] + if ($castChild -and $castChild.VariablePath.UserPath -eq $ParameterName) { return $true } + } } } From 4da55f02b57f67df3fb589cd9014b9b155392b10 Mon Sep 17 00:00:00 2001 From: Justin Emerson Date: Wed, 26 Aug 2026 12:33:13 -0700 Subject: [PATCH 03/29] fix(tools): trace ToArray query helper arguments Extract Get-PfbHelperArgumentSourceVariable so the Add-PfbCommonQueryParams -Names/-Ids readers accept a bare variable or an exact zero-argument $var.ToArray() call, and nothing else. Real shape: Get-PfbUserGroupQuotaPolicy hands its [List[string]] accumulators to the helper's [string[]]-typed parameters as $allNames.ToArray(). Guards, one per early-return line: not static, member literally ToArray, zero arguments (via Test-PfbInvokeHasNoArguments), bare VariableExpressionAst target. The static guard is separate because $var::ToArray() satisfies all three others -- it parses as an InvokeMemberExpressionAst with member ToArray, no arguments, and a bare variable target -- so only .Static rejects it. Extracted rather than inlined for testability: the guards are now reachable from a unit test against a parsed AST, independent of the fixture-file path, giving each guard a second kill route. A guard with one route is one outer-check bug away from being silently uncovered. The fixture builder now throws on parse errors. `-Names [SomeType]::ToArray()` does not parse in command-argument mode -- PowerShell emits ExpectedExpression and splits it into a bareword plus a ParenExpressionAst -- so a fixture written that way never reaches any guard while the suite stays green. Inventory: 2071 -> 2073 resolved of 2168, the two rows being Get-PfbUserGroupQuotaPolicy -Name -> names and -Id -> ids, Query/GET on user-group-quota-policies. Nothing else moves. Tests 119 -> 136, both editions, 0 failed 0 skipped. --- Tests/PfbCmdletParamTools.Tests.ps1 | 175 ++++++++++++++++++++++++++++ tools/lib/PfbCmdletParamTools.ps1 | 64 +++++++++- 2 files changed, 235 insertions(+), 4 deletions(-) diff --git a/Tests/PfbCmdletParamTools.Tests.ps1 b/Tests/PfbCmdletParamTools.Tests.ps1 index 000bb877..da8c24fe 100644 --- a/Tests/PfbCmdletParamTools.Tests.ps1 +++ b/Tests/PfbCmdletParamTools.Tests.ps1 @@ -308,6 +308,37 @@ function Get-PfbFixtureHelperNoBoundParams { Add-PfbCommonQueryParams -Into $queryParams -BoundParameters $someOtherDictionary Invoke-PfbApiRequest -Array $Array -Method GET -Endpoint 'helper-no-bound' -QueryParams $queryParams -AutoPaginate } +'@ + + # Real Get-PfbUserGroupQuotaPolicy shape (issue #141 Task 2): the SAME process-block + # accumulators as Get-PfbFixtureHelperAccumulator, but handed to the helper as + # `$allNames.ToArray()` -- the helper's -Names/-Ids are [string[]]-typed, so a + # [List[string]] accumulator must be converted at the call site. Resolution must still + # go parameter -> accumulator -> helper argument; the parameter names (Label/Marker) + # deliberately share no word with the wire keys (names/ids), so a pass cannot come + # from guessing the key off the parameter name. + Set-Content -Path (Join-Path $fixtureDir 'Get-PfbFixtureHelperToArray.ps1') -Value @' +function Get-PfbFixtureHelperToArray { + [CmdletBinding()] + param( + [Parameter()] [PSCustomObject]$Array, + [Parameter(ValueFromPipeline)] [string[]]$Label, + [Parameter()] [string[]]$Marker + ) + begin { + $allNames = [System.Collections.Generic.List[string]]::new() + $allIds = [System.Collections.Generic.List[string]]::new() + } + process { + if ($Label) { foreach ($n in $Label) { $allNames.Add($n) } } + if ($Marker) { foreach ($i in $Marker) { $allIds.Add($i) } } + } + end { + $queryParams = @{} + Add-PfbCommonQueryParams -Into $queryParams -BoundParameters $PSBoundParameters -Names $allNames.ToArray() -Ids $allIds.ToArray() + Invoke-PfbApiRequest -Array $Array -Method GET -Endpoint 'helper-toarray' -QueryParams $queryParams -AutoPaginate + } +} '@ # --- Hashtable-literal-initializer fixtures --------------------------------------- @@ -627,6 +658,150 @@ Describe 'Add-PfbCommonQueryParams awareness (issue #32/#33)' { } } +Describe 'Add-PfbCommonQueryParams exact $var.ToArray() helper arguments (issue #141 Task 2)' { + # A [List[string]] accumulator cannot bind to the helper's [string[]]-typed -Names/-Ids + # directly, so a cmdlet hands it over as $accumulator.ToArray() (real: + # Get-PfbUserGroupQuotaPolicy). The resolver credits the underlying source variable -- + # never the method call's result -- and only for the exact zero-argument ToArray-on-a- + # bare-variable shape. Anything else derives its value from something other than one + # variable alone and stays refused. + + BeforeAll { + function Get-PfbToArrayHelperAst { + param([string]$NamesArgument) + $tokens = $null; $errs = $null + $source = 'function Test-Fixture { param([string]$Param) $queryParams = @{}; ' + + 'Add-PfbCommonQueryParams -Into $queryParams -BoundParameters $PSBoundParameters -Names ' + + $NamesArgument + ' }' + $ast = [System.Management.Automation.Language.Parser]::ParseInput($source, [ref]$tokens, [ref]$errs) + # A fixture that does not parse is not a test -- it is a string the resolver + # declines to read, and every assertion over it passes for the wrong reason. + # `-Names [SomeType]::ToArray()` is the live example: in command-argument parsing + # mode PowerShell emits ExpectedExpression and splits it into a bareword plus a + # ParenExpressionAst, so the fixture never reaches the guard it appears to test. + if ($errs.Count -gt 0) { + throw "Fixture source for argument '$NamesArgument' does not parse: $($errs[0].Message)" + } + $ast.FindAll({ param($n) $n -is [System.Management.Automation.Language.FunctionDefinitionAst] }, $true) | Select-Object -First 1 + } + } + + It 'resolves -Label/-Marker through the accumulator path even though the helper receives $allNames.ToArray()' { + # Inventory-level positive: parameter -> Find-PfbAccumulatorVariable ($allNames) -> + # Get-PfbWireNameForParameter('allNames') -> helper argument $allNames.ToArray(). + # Neutral parameter names: 'names'/'ids' share no word with 'Label'/'Marker', so the + # wire key can only have come from the mapping, not from the parameter's name. + $name = $inventory | Where-Object { $_.Cmdlet -eq 'Get-PfbFixtureHelperToArray' -and $_.Parameter -eq 'Label' } + $name.WireName | Should -Be 'names' + $name.Surface | Should -Be 'Typed' + $name.WireSurface | Should -Be 'Query' + $name.Endpoint | Should -Be 'helper-toarray' + $name.Method | Should -Be 'GET' + $id = $inventory | Where-Object { $_.Cmdlet -eq 'Get-PfbFixtureHelperToArray' -and $_.Parameter -eq 'Marker' } + $id.WireName | Should -Be 'ids' + $id.Surface | Should -Be 'Typed' + $id.WireSurface | Should -Be 'Query' + } + + It 'resolves a DIRECT helper argument of the exact $var.ToArray() shape to the source variable' { + # No foreach accumulator involved: $Param itself is the call site's ToArray target. + $funcAst = Get-PfbToArrayHelperAst '$Param.ToArray()' + $result = Get-PfbCommonQueryParamHelperWireName -FunctionAst $funcAst -ParameterName 'Param' + $result.WireName | Should -Be 'names' + $result.TargetVariable | Should -Be 'queryParams' + } + + It 'refuses a helper argument of -- the value is not a bare variable or exact $var.ToArray() on one' -ForEach @( + # Deliberately the ARITY guard's own coverage: `.Clone()` and `.ToArray().ToString()` + # are already refused by the member-name check, and `($left + $right).ToArray()` by the + # bare-target check -- but `$Param.ToArray($Param)` has member ToArray, a bare-variable + # target, and differs from the accepted shape ONLY by carrying an argument. If deleting + # the Test-PfbInvokeHasNoArguments condition leaves the suite green, this negative is + # not testing what it was written to protect. + @{ Shape = 'a member call other than ToArray ($var.Clone())'; Argument = '$Param.Clone()' } + @{ Shape = 'a ToArray() call on a composite target'; Argument = '($Param + $Param).ToArray()' } + @{ Shape = 'a ToArray() call carrying an argument'; Argument = '$Param.ToArray($Param)' } + @{ Shape = 'a method CHAIN past ToArray ($var.ToArray().ToString())'; Argument = '$Param.ToArray().ToString()' } + # The STATIC guard's own coverage, and the only shape here that reaches it. This + # parses as an InvokeMemberExpressionAst whose member is literally ToArray, carries + # zero arguments, and whose Expression is a bare VariableExpressionAst -- it passes + # every other guard and is refused ONLY by the Static test. A bare + # `[SomeType]::ToArray()` would NOT do this job: it does not parse in argument mode. + @{ Shape = 'a STATIC call on a variable type ($var::ToArray())'; Argument = '$Param::ToArray()' } + ) { + $funcAst = Get-PfbToArrayHelperAst $Argument + Get-PfbCommonQueryParamHelperWireName -FunctionAst $funcAst -ParameterName 'Param' | Should -BeNullOrEmpty + } + + It 'refuses a ToArray()-wrapped accumulator fed by two different parameters (never guesses ownership)' { + # The shared-accumulator refusal must hold through the .ToArray() call exactly as it + # does for a bare $allNames: Find-PfbAccumulatorVariable returns $null for both + # parameters before the helper argument is ever consulted. + $tokens = $null; $errs = $null + $source = @' +function Test-Fixture { + param([string[]]$First, [string[]]$Second) + $allNames = [System.Collections.Generic.List[string]]::new() + $queryParams = @{} + foreach ($n in $First) { $allNames.Add($n) } + foreach ($n in $Second) { $allNames.Add($n) } + Add-PfbCommonQueryParams -Into $queryParams -BoundParameters $PSBoundParameters -Names $allNames.ToArray() +} +'@ + $ast = [System.Management.Automation.Language.Parser]::ParseInput($source, [ref]$tokens, [ref]$errs) + $funcAst = $ast.FindAll({ param($n) $n -is [System.Management.Automation.Language.FunctionDefinitionAst] }, $true) | Select-Object -First 1 + Find-PfbAccumulatorVariable -FunctionAst $funcAst -ParameterName 'First' | Should -BeNullOrEmpty + Find-PfbAccumulatorVariable -FunctionAst $funcAst -ParameterName 'Second' | Should -BeNullOrEmpty + Get-PfbWireNameForParameter -FunctionAst $funcAst -ParameterName 'First' | Should -BeNullOrEmpty + Get-PfbWireNameForParameter -FunctionAst $funcAst -ParameterName 'Second' | Should -BeNullOrEmpty + } + + Context 'Get-PfbHelperArgumentSourceVariable, exercised directly' { + # A second kill route for each guard, independent of the helper-call and fixture-file + # paths above. Those reach the guards only through Get-PfbCommonQueryParamHelperWireName's + # element walk, so a change to the walk -- an outer check that refuses a shape earlier -- + # can silently stop a guard from ever being reached while the suite stays green. That is + # exactly the defect Task 1 shipped. Asserting on the extracted function removes the + # dependency: these fail if a guard is deleted no matter what the caller does. + BeforeAll { + function Get-PfbHelperArgumentAst { + param([string]$Expression) + $tokens = $null; $errs = $null + $ast = [System.Management.Automation.Language.Parser]::ParseInput( + "`$x = $Expression", [ref]$tokens, [ref]$errs) + if ($errs.Count -gt 0) { + throw "Expression '$Expression' does not parse: $($errs[0].Message)" + } + $assignment = $ast.FindAll({ + param($n) $n -is [System.Management.Automation.Language.AssignmentStatementAst] + }, $true) | Select-Object -First 1 + $assignment.Right.Expression + } + } + + It 'accepts and returns the source variable' -ForEach @( + @{ Shape = 'a bare variable'; Expression = '$allNames'; Expected = 'allNames' } + @{ Shape = 'exact zero-argument ToArray()'; Expression = '$allNames.ToArray()'; Expected = 'allNames' } + ) { + Get-PfbHelperArgumentSourceVariable -ArgumentAst (Get-PfbHelperArgumentAst $Expression) | + Should -Be $Expected + } + + It 'refuses ' -ForEach @( + @{ Shape = 'an argument-bearing call (ARITY guard)'; Expression = '$allNames.ToArray($n)' } + @{ Shape = 'a static call on a variable (STATIC guard)'; Expression = '$allNames::ToArray()' } + @{ Shape = 'a composite target (BARE-TARGET guard)'; Expression = '($allNames + $extra).ToArray()' } + @{ Shape = 'a different member name'; Expression = '$allNames.Clone()' } + @{ Shape = 'a chain past ToArray'; Expression = '$allNames.ToArray().ToString()' } + @{ Shape = 'a member-access target'; Expression = '$obj.Items.ToArray()' } + @{ Shape = 'a static call on a type literal'; Expression = '[System.Array]::Empty()' } + ) { + Get-PfbHelperArgumentSourceVariable -ArgumentAst (Get-PfbHelperArgumentAst $Expression) | + Should -BeNullOrEmpty + } + } +} + Describe 'Get-PfbCommonQueryParamMap stays in sync with Private/Add-PfbCommonQueryParams.ps1' { # Guards the one hazard of hardcoding the mapping: the helper gains, loses, or renames a # key and this tools/ mirror silently keeps reporting the old contract. Derives the truth diff --git a/tools/lib/PfbCmdletParamTools.ps1 b/tools/lib/PfbCmdletParamTools.ps1 index 203e5815..7abec078 100644 --- a/tools/lib/PfbCmdletParamTools.ps1 +++ b/tools/lib/PfbCmdletParamTools.ps1 @@ -400,7 +400,10 @@ function Get-PfbCommonQueryParamMap { the signal is whichever variable the call site passes to that argument. That is usually a `process`-block accumulator ($allNames), not the parameter itself -- handled for free by Get-PfbCmdletParameterInventory's existing - Find-PfbAccumulatorVariable retry. + Find-PfbAccumulatorVariable retry. A call site may also hand the accumulator + over as the exact zero-argument `$allNames.ToArray()` (the helper's -Names/-Ids + are [string[]]-typed; real: Get-PfbUserGroupQuotaPolicy) -- same source variable, + unwrapped structurally by Get-PfbCommonQueryParamHelperWireName. NOT included: the non-generic keys (file_system_names, policy_names, role_names, member_names, ...). Per issue #32's design those cmdlets deliberately kept their own @@ -428,6 +431,50 @@ function Get-PfbCommonQueryParamMap { } } +function Get-PfbHelperArgumentSourceVariable { + <# + .SYNOPSIS + Returns the source variable behind an Add-PfbCommonQueryParams helper argument. + .DESCRIPTION + Accepted, shape-exactly, are a bare variable and a zero-argument instance + `$variable.ToArray()` call. Everything else is refused: a different member name, + a non-variable invocation target, an argument-bearing call, a longer member chain, + or a static invocation. The helper's generic key must never be credited to data + whose source cannot be identified exactly. + + `$var::ToArray()` is the reason the static check is a guard of its own rather than + a consequence of the others: it parses as an InvokeMemberExpressionAst whose member + is literally ToArray, carries zero arguments, and whose Expression is a bare + VariableExpressionAst -- so it passes every other test here and resolves unless + Static is tested explicitly. + + Extracted rather than inlined so the guards are reachable from a unit test against a + parsed AST, independent of the fixture-file path. A guard with a single kill route is + one outer-check bug away from being silently uncovered. + .OUTPUTS + $null, or the source variable's bare name (without '$'). + #> + [CmdletBinding()] + param( + [Parameter(Mandatory)] + [System.Management.Automation.Language.Ast]$ArgumentAst + ) + + $bare = $ArgumentAst -as [System.Management.Automation.Language.VariableExpressionAst] + if ($bare) { return $bare.VariablePath.UserPath } + + $invoke = $ArgumentAst -as [System.Management.Automation.Language.InvokeMemberExpressionAst] + if (-not $invoke) { return $null } + if ($invoke.Static) { return $null } + if ($invoke.Member -isnot [System.Management.Automation.Language.StringConstantExpressionAst]) { return $null } + if ($invoke.Member.Value -ne 'ToArray') { return $null } + if (-not (Test-PfbInvokeHasNoArguments -Invoke $invoke)) { return $null } + + $target = $invoke.Expression -as [System.Management.Automation.Language.VariableExpressionAst] + if (-not $target) { return $null } + return $target.VariablePath.UserPath +} + function Get-PfbCommonQueryParamHelperWireName { <# .SYNOPSIS @@ -438,8 +485,12 @@ function Get-PfbCommonQueryParamHelperWireName { (that variable is what Get-PfbEndpointForVariable later traces to an Invoke-PfbApiRequest call, so without it there is nothing to attribute), requires -BoundParameters to be literally $PSBoundParameters before trusting the - ByParameterName rule, only reads plain variable arguments, and returns $null if two - helper calls in the same function disagree on the (WireName, TargetVariable) pair. + ByParameterName rule, and returns $null if two helper calls in the same function + disagree on the (WireName, TargetVariable) pair. A ByHelperArgument's value is read + only as a plain variable (`-Names $allNames`) or the exact zero-argument + `$var.ToArray()` call on a bare variable (real: Get-PfbUserGroupQuotaPolicy, which + must convert its [List[string]] accumulators to arrays for the helper's [string[]] + parameters); any other member call shape stays refused. .OUTPUTS $null, or [PSCustomObject]@{ WireName; TargetVariable } -- same shape as Get-PfbWireNameForParameter. @@ -491,7 +542,12 @@ function Get-PfbCommonQueryParamHelperWireName { if ($argVar -and $argVar.VariablePath.UserPath -eq 'PSBoundParameters') { $forwardsBoundParameters = $true } } elseif ($map.ByHelperArgument.Contains($el.ParameterName)) { - if ($argVar) { $argumentVariables[$el.ParameterName] = $argVar.VariablePath.UserPath } + # Bare `$allNames` or the exact zero-argument `$allNames.ToArray()` -- how a + # [List[string]] accumulator is handed to the helper's [string[]]-typed + # -Names/-Ids (real: Get-PfbUserGroupQuotaPolicy). Every refusal reason lives + # in Get-PfbHelperArgumentSourceVariable, one guard per line. + $sourceName = Get-PfbHelperArgumentSourceVariable -ArgumentAst $argExpr + if ($sourceName) { $argumentVariables[$el.ParameterName] = $sourceName } } } From c679feaef979c0019df22957957f6bd15aeb1b70 Mon Sep 17 00:00:00 2001 From: Justin Emerson Date: Wed, 26 Aug 2026 15:32:07 -0700 Subject: [PATCH 04/29] test(tools): close review Minors on helper-argument tracing Two reviewers (opus5-medium, kimi-k3-max) both APPROVED 4da55f0. Three live Minor findings, all addressed here; no behaviour change. - Document the dynamic-member guard as StrictMode-defensive rather than behavioural. It has no mutation kill route because deleting it leaves the next line refusing the same shape while this file runs without Set-StrictMode -- an equivalent mutant, not an uncovered guard. Measured by re-running the shape corpus against the mutated build. - Assert the shared-accumulator fixture parses. It was the one new fixture parsing inline source without the assertion the other two builders make. - Relabel two direct negatives that are refused by the member-name guard, not the STATIC guard. Measured under mutation: `$allNames::ToArray()` is the only shape in that block that reaches Static. Scoped Pester: 136 passed / 0 failed / 0 skipped, both editions, unchanged. Co-Authored-By: Claude Opus 5 (1M context) --- Tests/PfbCmdletParamTools.Tests.ps1 | 8 ++++++-- tools/lib/PfbCmdletParamTools.ps1 | 3 +++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/Tests/PfbCmdletParamTools.Tests.ps1 b/Tests/PfbCmdletParamTools.Tests.ps1 index da8c24fe..364422a7 100644 --- a/Tests/PfbCmdletParamTools.Tests.ps1 +++ b/Tests/PfbCmdletParamTools.Tests.ps1 @@ -749,6 +749,7 @@ function Test-Fixture { } '@ $ast = [System.Management.Automation.Language.Parser]::ParseInput($source, [ref]$tokens, [ref]$errs) + $errs.Count | Should -Be 0 -Because 'a fixture that does not parse is inert, and nothing here would go red' $funcAst = $ast.FindAll({ param($n) $n -is [System.Management.Automation.Language.FunctionDefinitionAst] }, $true) | Select-Object -First 1 Find-PfbAccumulatorVariable -FunctionAst $funcAst -ParameterName 'First' | Should -BeNullOrEmpty Find-PfbAccumulatorVariable -FunctionAst $funcAst -ParameterName 'Second' | Should -BeNullOrEmpty @@ -792,9 +793,12 @@ function Test-Fixture { @{ Shape = 'a static call on a variable (STATIC guard)'; Expression = '$allNames::ToArray()' } @{ Shape = 'a composite target (BARE-TARGET guard)'; Expression = '($allNames + $extra).ToArray()' } @{ Shape = 'a different member name'; Expression = '$allNames.Clone()' } - @{ Shape = 'a chain past ToArray'; Expression = '$allNames.ToArray().ToString()' } + @{ Shape = 'a chain past ToArray (member name is ToString, not the STATIC guard)'; Expression = '$allNames.ToArray().ToString()' } @{ Shape = 'a member-access target'; Expression = '$obj.Items.ToArray()' } - @{ Shape = 'a static call on a type literal'; Expression = '[System.Array]::Empty()' } + # Refused by the member-name guard (member is Empty), NOT by the STATIC guard -- + # measured under mutation. `$allNames::ToArray()` above is the only shape here that + # reaches Static. Kept as a redundant negative; the label must not overstate it. + @{ Shape = 'a type-literal call whose member is not ToArray'; Expression = '[System.Array]::Empty()' } ) { Get-PfbHelperArgumentSourceVariable -ArgumentAst (Get-PfbHelperArgumentAst $Expression) | Should -BeNullOrEmpty diff --git a/tools/lib/PfbCmdletParamTools.ps1 b/tools/lib/PfbCmdletParamTools.ps1 index 7abec078..56e49a02 100644 --- a/tools/lib/PfbCmdletParamTools.ps1 +++ b/tools/lib/PfbCmdletParamTools.ps1 @@ -466,6 +466,9 @@ function Get-PfbHelperArgumentSourceVariable { $invoke = $ArgumentAst -as [System.Management.Automation.Language.InvokeMemberExpressionAst] if (-not $invoke) { return $null } if ($invoke.Static) { return $null } + # Defensive, not behavioural: a dynamic member name ($var.$name()) exposes no .Value, so + # the next line already refuses it while this file runs without Set-StrictMode. Kept so the + # refusal survives a future strict mode, which is why no mutation of it can be killed. if ($invoke.Member -isnot [System.Management.Automation.Language.StringConstantExpressionAst]) { return $null } if ($invoke.Member.Value -ne 'ToArray') { return $null } if (-not (Test-PfbInvokeHasNoArguments -Invoke $invoke)) { return $null } From a1c3d9f0902fa799490215377f862af07ea90172 Mon Sep 17 00:00:00 2001 From: Justin Emerson Date: Wed, 26 Aug 2026 17:00:22 -0700 Subject: [PATCH 05/29] refactor(tools): derive payload role from request arguments The wire-name resolver decided a variable's request role by its NAME: a `switch ($TargetVariable) { 'body' { 'Body' } 'queryParams' { 'Query' } }` trust gate that no request argument ever had to confirm. That was wrong in both directions. A variable called $q, $payload or $destroyQuery got no role at all even when it was handed straight to Invoke-PfbApiRequest, and a variable called $body would have been reported as a Body landing even if the only place it went was -QueryParams. Get-PfbRequestRoleForVariable replaces the gate. It scans the function's Invoke-PfbApiRequest calls, derives the surface from the command PARAMETER the variable is passed to, and derives the operation from literal -Method and -Endpoint arguments. Both argument forms are read (`-Body $x` and `-Body:$x`), a nonliteral operation argument leaves the operation unread rather than guessed, and an unread landing still counts as a landing -- a readable call never speaks for one it could not read. Arbitration is now over the complete tuple. Every landing an idiom finds is collected, identical tuples collapse to one, and a complete answer is returned only when exactly one distinct tuple survives; otherwise only the components every candidate agrees on are kept and the rest are nulled. This is what keeps Remove-PfbFileSystem -DeleteLinkOnEradication honest: it writes the same key into $destroyQuery (reaching a PATCH) and into $queryParams (reaching a DELETE), and retiring the name gate makes $destroyQuery the earlier AST match, so a first-match resolver would report PATCH and hide the DELETE outright. It keeps its key and its Query surface and names no operation. Get-PfbEndpointForVariable stays as a compatibility wrapper and delegates; it keeps no copy of the name switch. Over the real Public/ tree this resolves 30 (cmdlet, parameter) pairs that were previously unresolved, all of them payload variables the old gate could not see by name. It also withdraws one previously-resolved row -- Update-PfbBucketAuditFilter -BucketName, which is written to two different query keys ('bucket_names' and, in the -Name default branch, 'names') and so has no single wire name to report. The old answer was true but partial, and picking it depended on source order. Co-Authored-By: Claude Opus 5 (1M context) --- Tests/PfbCmdletParamTools.Tests.ps1 | 732 +++++++++++++++++++++++++++- tools/lib/PfbCmdletParamTools.ps1 | 432 ++++++++++++---- 2 files changed, 1062 insertions(+), 102 deletions(-) diff --git a/Tests/PfbCmdletParamTools.Tests.ps1 b/Tests/PfbCmdletParamTools.Tests.ps1 index 364422a7..927fa5dc 100644 --- a/Tests/PfbCmdletParamTools.Tests.ps1 +++ b/Tests/PfbCmdletParamTools.Tests.ps1 @@ -39,6 +39,8 @@ function New-PfbFixtureAlertWatcher { $body = @{} if ($MinimumSeverity) { $body['minimum_notification_severity'] = $MinimumSeverity } } + + Invoke-PfbApiRequest -Array $Array -Method POST -Endpoint 'alert-watchers' -Body $body } '@ @@ -72,6 +74,8 @@ function New-PfbFixtureNetworkInterface { $body["attached_servers"] = @($AttachedServers | ForEach-Object { @{ name = $_ } }) } } + + Invoke-PfbApiRequest -Array $Array -Method POST -Endpoint 'network-interfaces' -Body $body } '@ @@ -435,7 +439,58 @@ function New-PfbFixtureNestedReference { '@ $script:helperPath = Join-Path $repoRoot 'Private/Add-PfbCommonQueryParams.ps1' + $script:publicDir = Join-Path $repoRoot 'Public' $script:inventory = Get-PfbCmdletParameterInventory -PublicDirectory $fixtureDir + + # --- issue #141 Task 3 test bed --------------------------------------------------- + # Single parse point for every inline fixture added by Task 3, and the ONLY place that + # gets to decide a fixture is usable. Get-PfbCmdletParameterInventory discards its own + # $parseErrors, so a fixture that does not parse is not a test at all -- it is a string + # the resolver declines to read, and every assertion over it passes for the wrong reason. + function script:Get-PfbRoleFixtureAst { + param( + # One element per source line; joined with a real newline here so a fixture is + # never silently collapsed onto one line by the output field separator. + [Parameter(Mandatory)] + [string[]]$Source + ) + $text = $Source -join [System.Environment]::NewLine + $tokens = $null + $parseErrors = $null + $ast = [System.Management.Automation.Language.Parser]::ParseInput($text, [ref]$tokens, [ref]$parseErrors) + if (@($parseErrors).Count -ne 0) { + throw ("Fixture source does not parse ({0} error(s)): {1}`n{2}" -f @($parseErrors).Count, $parseErrors[0].Message, $text) + } + $funcAst = $ast.FindAll({ param($n) $n -is [System.Management.Automation.Language.FunctionDefinitionAst] }, $true) | + Select-Object -First 1 + if (-not $funcAst) { throw "Fixture source defines no function:`n$text" } + return $funcAst + } + + # Builds the one-assignment role fixture used across the Task 3 positives. Every name in + # it is deliberately unrelated to every other (test-bed rule 4): the payload variable is + # supplied by the caller, the parameter is -Zeta, the wire key is 'alpha' and the + # endpoint is 'widgets'. No two of those share a word, so a resolution can only have come + # from reading the request argument. + function script:New-PfbRoleFixtureSource { + param( + [Parameter(Mandatory)] + [string]$Variable, + + # Verbatim payload argument, e.g. '-QueryParams $q' or '-Body:$payload'. + [Parameter(Mandatory)] + [string]$PayloadArgument + ) + $v = '$' + $Variable + return @( + 'function Test-Fixture {' + ' param([string]$Zeta)' + (' ' + $v + ' = @{}') + (' ' + $v + "['alpha'] = " + '$Zeta') + (" Invoke-PfbApiRequest -Method PATCH -Endpoint 'widgets' " + $PayloadArgument) + '}' + ) -join [System.Environment]::NewLine + } } Describe 'Get-PfbCmdletParameterInventory' { @@ -1000,7 +1055,7 @@ Describe 'Nested single-key reference-object awareness' { # an unresolved parameter Typed -- never rename an already-resolved wire name. $tokens = $null; $errs = $null $ast = [System.Management.Automation.Language.Parser]::ParseInput( - 'function Test-Fixture { param([string]$Name) $body = @{}; $body["owner"] = @{ name = $Name }; $body["name"] = $Name }', [ref]$tokens, [ref]$errs) + 'function Test-Fixture { param([string]$Name) $body = @{}; $body["owner"] = @{ name = $Name }; $body["name"] = $Name; Invoke-PfbApiRequest -Method POST -Endpoint ''fixtures'' -Body $body }', [ref]$tokens, [ref]$errs) $funcAst = $ast.FindAll({ param($n) $n -is [System.Management.Automation.Language.FunctionDefinitionAst] }, $true) | Select-Object -First 1 (Get-PfbWireNameForParameter -FunctionAst $funcAst -ParameterName 'Name').WireName | Should -Be 'name' } @@ -1022,7 +1077,7 @@ Describe 'Nested single-key reference-object awareness' { # 'Array-of-references projection awareness' Describe block. $tokens = $null; $errs = $null $ast = [System.Management.Automation.Language.Parser]::ParseInput( - 'function Test-Fixture { param([string[]]$Servers) $body = @{}; $body["attached_servers"] = @($Servers | ForEach-Object { @{ name = $_ } }) }', [ref]$tokens, [ref]$errs) + 'function Test-Fixture { param([string[]]$Servers) $body = @{}; $body["attached_servers"] = @($Servers | ForEach-Object { @{ name = $_ } }); Invoke-PfbApiRequest -Method POST -Endpoint ''fixtures'' -Body $body }', [ref]$tokens, [ref]$errs) $funcAst = $ast.FindAll({ param($n) $n -is [System.Management.Automation.Language.FunctionDefinitionAst] }, $true) | Select-Object -First 1 (Get-PfbNestedReferenceWireNameForParameter -FunctionAst $funcAst -ParameterName 'Servers').WireName | Should -Be 'attached_servers' } @@ -1046,8 +1101,20 @@ Describe 'Array-of-references projection awareness' { BeforeAll { function Get-TestFunctionAst { param([string]$Source) + # Every fixture in this block is a single-line function whose payload variable is + # $body or $queryParams. Since issue #141 Task 3 those names carry no authority on + # their own: a variable earns a request role only by being passed to + # Invoke-PfbApiRequest -Body/-QueryParams, so the tail below is what makes these + # fixtures resolvable at all. It deliberately covers only $body and $queryParams, + # which is what keeps the $nfsBody negative in this block a real negative. + $trimmed = $Source.TrimEnd() + if (-not $trimmed.EndsWith('}')) { throw "Fixture must end with the function's closing brace: $Source" } + $tail = 'Invoke-PfbApiRequest -Method POST -Endpoint ''fixtures'' -Body $body -QueryParams $queryParams' + $withRequest = $trimmed.Substring(0, $trimmed.Length - 1) + '; ' + $tail + ' }' + $tokens = $null; $errs = $null - $ast = [System.Management.Automation.Language.Parser]::ParseInput($Source, [ref]$tokens, [ref]$errs) + $ast = [System.Management.Automation.Language.Parser]::ParseInput($withRequest, [ref]$tokens, [ref]$errs) + if (@($errs).Count -ne 0) { throw "Fixture source does not parse: $($errs[0].Message)`n$withRequest" } $ast.FindAll({ param($n) $n -is [System.Management.Automation.Language.FunctionDefinitionAst] }, $true) | Select-Object -First 1 } } @@ -1494,8 +1561,10 @@ function Test-Fixture { if ($PSBoundParameters.ContainsKey('RemoteDefaultExports')) { $queryParams['remote_default_exports'] = if ($RemoteDefaultExports) { 'true' } else { 'false' } } + Invoke-PfbApiRequest -Method POST -Endpoint 'conditional' -QueryParams $queryParams } '@, [ref]$tokens, [ref]$errs) + @($errs).Count | Should -Be 0 $funcAst = $ast.FindAll({ param($n) $n -is [System.Management.Automation.Language.FunctionDefinitionAst] }, $true) | Select-Object -First 1 $result = Get-PfbWireNameForParameter -FunctionAst $funcAst -ParameterName 'RemoteDefaultExports' -IsBooleanLikeParameter $result.WireName | Should -Be 'remote_default_exports' @@ -1515,8 +1584,12 @@ function Test-Fixture { Body = '$queryParams["k"] = if ($Param) { "true" } elseif ($Other) { "maybe" } else { "false" }' } ) { $tokens = $null; $errs = $null - $source = 'function Test-Fixture { param([Nullable[bool]]$Param, [Nullable[bool]]$Other) ' + $Body + ' }' + # The trailing request call gives $queryParams a genuine Query role, so each case is + # refused by its SHAPE rule and not merely because the payload variable is inert. + $source = 'function Test-Fixture { param([Nullable[bool]]$Param, [Nullable[bool]]$Other) ' + $Body + + '; Invoke-PfbApiRequest -Method POST -Endpoint ''fixtures'' -QueryParams $queryParams }' $ast = [System.Management.Automation.Language.Parser]::ParseInput($source, [ref]$tokens, [ref]$errs) + @($errs).Count | Should -Be 0 $funcAst = $ast.FindAll({ param($n) $n -is [System.Management.Automation.Language.FunctionDefinitionAst] }, $true) | Select-Object -First 1 Get-PfbWireNameForParameter -FunctionAst $funcAst -ParameterName 'Param' -IsBooleanLikeParameter | Should -BeNullOrEmpty } @@ -1526,8 +1599,17 @@ Describe 'Exact boolean wire-value transforms (issue #141)' { BeforeAll { function Get-TestBooleanWireFunctionAst { param([string]$Source) + # Since issue #141 Task 3 a payload variable earns its request role from being + # passed to Invoke-PfbApiRequest, not from being called $body/$queryParams, so + # the appended tail is what makes these fixtures resolvable at all. + $trimmed = $Source.TrimEnd() + if (-not $trimmed.EndsWith('}')) { throw "Fixture must end with the function's closing brace: $Source" } + $tail = 'Invoke-PfbApiRequest -Method POST -Endpoint ''fixtures'' -Body $body -QueryParams $queryParams' + $withRequest = $trimmed.Substring(0, $trimmed.Length - 1) + '; ' + $tail + ' }' + $tokens = $null; $errs = $null - $ast = [System.Management.Automation.Language.Parser]::ParseInput($Source, [ref]$tokens, [ref]$errs) + $ast = [System.Management.Automation.Language.Parser]::ParseInput($withRequest, [ref]$tokens, [ref]$errs) + if (@($errs).Count -ne 0) { throw "Fixture source does not parse: $($errs[0].Message)`n$withRequest" } $ast.FindAll({ param($n) $n -is [System.Management.Automation.Language.FunctionDefinitionAst] }, $true) | Select-Object -First 1 } } @@ -1621,3 +1703,643 @@ function Get-Thing { $rec.WireSurface | Should -Be 'Body' } } + +# ===================================================================================== +# issue #141 Task 3 -- argument-proven payload role tracing +# +# The resolver used to decide a variable's request role from its NAME, via a +# `switch ($TargetVariable) { 'body' {...} 'queryParams' {...} }` trust gate. That is an +# inference from a name, which the never-guess contract forbids: it both missed every +# cmdlet using $q / $payload / $destroyQuery and would have mislabelled a variable called +# $body that was actually passed to -QueryParams. The role is now read from the request +# argument itself. +# +# Every fixture below deliberately decouples the payload variable name, the parameter name +# and the wire key from one another, and several actively CONTRADICT the retired gate, so +# no assertion here can pass by reading a name. +# ===================================================================================== + +Describe 'Task 3 fixture bed' { + It 'refuses a fixture source that does not parse' { + # Get-PfbCmdletParameterInventory discards its own $parseErrors, so an unparseable + # fixture is not a failing test -- it is a string the resolver never reads, and every + # assertion over it passes vacuously. + { Get-PfbRoleFixtureAst @('function Test-Fixture {', ' $q = @{', '}') } | + Should -Throw -ExpectedMessage '*does not parse*' + } + + It 'refuses a fixture source that defines no function' { + { Get-PfbRoleFixtureAst @('$q = @{}') } | Should -Throw -ExpectedMessage '*defines no function*' + } +} + +Describe 'Get-PfbRequestRoleForVariable: role is proven by the request argument (issue #141 Task 3, Steps 1-2)' { + + It 'derives for $ passed as ""' -ForEach @( + # Step 1 -- names that carry no role information at all. + @{ Variable = 'q'; PayloadArgument = '-QueryParams $q'; Expected = 'Query' } + @{ Variable = 'payload'; PayloadArgument = '-Body $payload'; Expected = 'Body' } + @{ Variable = 'destroyQuery'; PayloadArgument = '-QueryParams $destroyQuery'; Expected = 'Query' } + # Step 1 -- the colon argument form parks the value on CommandParameterAst.Argument + # instead of the next command element. Reading only the next element misses it. + @{ Variable = 'payload'; PayloadArgument = '-Body:$payload'; Expected = 'Body' } + @{ Variable = 'q'; PayloadArgument = '-QueryParams:$q'; Expected = 'Query' } + # Step 2 -- the name-reversal detector. This class has zero current occurrences in + # Public/, which is exactly why it needs a permanent test rather than a survey: under + # the retired name gate both of these resolved to the surface their NAME implied, + # which is the opposite of the surface they are actually sent on. + @{ Variable = 'body'; PayloadArgument = '-QueryParams $body'; Expected = 'Query' } + @{ Variable = 'queryParams'; PayloadArgument = '-Body $queryParams'; Expected = 'Body' } + ) { + $funcAst = Get-PfbRoleFixtureAst (New-PfbRoleFixtureSource -Variable $Variable -PayloadArgument $PayloadArgument) + $role = Get-PfbRequestRoleForVariable -FunctionAst $funcAst -TargetVariable $Variable + $role | Should -Not -BeNullOrEmpty + $role.TargetVariable | Should -Be $Variable + $role.WireSurface | Should -Be $Expected + $role.Method | Should -Be 'PATCH' + $role.Endpoint | Should -Be 'widgets' + } + + It 'derives Body for a parameter handed straight to -Body with no intermediate variable' { + $funcAst = Get-PfbRoleFixtureAst @( + 'function Test-Fixture {' + ' param([hashtable]$Tags)' + ' Invoke-PfbApiRequest -Method POST -Endpoint ''widgets'' -Body $Tags' + '}' + ) + $role = Get-PfbRequestRoleForVariable -FunctionAst $funcAst -TargetVariable 'Tags' + $role.WireSurface | Should -Be 'Body' + $role.Method | Should -Be 'POST' + $role.Endpoint | Should -Be 'widgets' + } + + It 'reads the colon argument form on -Method and -Endpoint too' { + $funcAst = Get-PfbRoleFixtureAst @( + 'function Test-Fixture {' + ' param([string]$Zeta)' + ' $q = @{}' + ' $q[''alpha''] = $Zeta' + ' Invoke-PfbApiRequest -Method:''PATCH'' -Endpoint:''widgets'' -QueryParams:$q' + '}' + ) + $role = Get-PfbRequestRoleForVariable -FunctionAst $funcAst -TargetVariable 'q' + $role.WireSurface | Should -Be 'Query' + $role.Method | Should -Be 'PATCH' + $role.Endpoint | Should -Be 'widgets' + } +} + +Describe 'Get-PfbRequestRoleForVariable: ambiguity and refusal (issue #141 Task 3, Step 3)' { + + It 'keeps the surface but nulls the operation when the same variable feeds two different ' -ForEach @( + @{ Differs = 'endpoints'; SecondCall = ' Invoke-PfbApiRequest -Method PATCH -Endpoint ''gadgets'' -QueryParams $q' } + @{ Differs = 'methods'; SecondCall = ' Invoke-PfbApiRequest -Method DELETE -Endpoint ''widgets'' -QueryParams $q' } + ) { + $funcAst = Get-PfbRoleFixtureAst @( + 'function Test-Fixture {' + ' param([string]$Zeta)' + ' $q = @{}' + ' $q[''alpha''] = $Zeta' + ' Invoke-PfbApiRequest -Method PATCH -Endpoint ''widgets'' -QueryParams $q' + $SecondCall + '}' + ) + $role = Get-PfbRequestRoleForVariable -FunctionAst $funcAst -TargetVariable 'q' + $role | Should -Not -BeNullOrEmpty + $role.WireSurface | Should -Be 'Query' + $role.Method | Should -BeNullOrEmpty + $role.Endpoint | Should -BeNullOrEmpty + } + + It 'collapses two IDENTICAL operations to one resolution rather than calling them ambiguous' { + $funcAst = Get-PfbRoleFixtureAst @( + 'function Test-Fixture {' + ' param([string]$Zeta)' + ' $q = @{}' + ' $q[''alpha''] = $Zeta' + ' if ($Zeta) { Invoke-PfbApiRequest -Method PATCH -Endpoint ''widgets'' -QueryParams $q }' + ' else { Invoke-PfbApiRequest -Method PATCH -Endpoint ''widgets'' -QueryParams $q }' + '}' + ) + $role = Get-PfbRequestRoleForVariable -FunctionAst $funcAst -TargetVariable 'q' + $role.WireSurface | Should -Be 'Query' + $role.Method | Should -Be 'PATCH' + $role.Endpoint | Should -Be 'widgets' + } + + It 'returns $null when one variable is sent on BOTH surfaces, across two calls' { + $funcAst = Get-PfbRoleFixtureAst @( + 'function Test-Fixture {' + ' param([string]$Zeta)' + ' $shared = @{}' + ' $shared[''alpha''] = $Zeta' + ' Invoke-PfbApiRequest -Method PATCH -Endpoint ''widgets'' -Body $shared' + ' Invoke-PfbApiRequest -Method PATCH -Endpoint ''widgets'' -QueryParams $shared' + '}' + ) + Get-PfbRequestRoleForVariable -FunctionAst $funcAst -TargetVariable 'shared' | Should -BeNullOrEmpty + } + + It 'returns $null when one variable is sent on BOTH surfaces of a single call' { + $funcAst = Get-PfbRoleFixtureAst @( + 'function Test-Fixture {' + ' param([string]$Zeta)' + ' $shared = @{}' + ' $shared[''alpha''] = $Zeta' + ' Invoke-PfbApiRequest -Method PATCH -Endpoint ''widgets'' -Body $shared -QueryParams $shared' + '}' + ) + Get-PfbRequestRoleForVariable -FunctionAst $funcAst -TargetVariable 'shared' | Should -BeNullOrEmpty + } + + It 'returns $null for a variable with zero matching calls: ' -ForEach @( + @{ Case = 'the request sends a different variable' + Call = ' Invoke-PfbApiRequest -Method GET -Endpoint ''widgets'' -QueryParams $other' + } + @{ Case = 'the variable is bound to a parameter that is not a payload' + Call = ' Invoke-PfbApiRequest -Method GET -Endpoint ''widgets'' -Headers $q' + } + @{ Case = 'the payload goes to some other command entirely' + Call = ' Send-FixtureElsewhere -Method GET -Endpoint ''widgets'' -QueryParams $q' + } + ) { + $funcAst = Get-PfbRoleFixtureAst @( + 'function Test-Fixture {' + ' param([string]$Zeta)' + ' $q = @{}' + ' $q[''alpha''] = $Zeta' + ' $other = @{}' + $Call + '}' + ) + Get-PfbRequestRoleForVariable -FunctionAst $funcAst -TargetVariable 'q' | Should -BeNullOrEmpty + } + + It 'returns $null when the payload argument is not the bare variable: ' -ForEach @( + # Each of these routes through the variable actually under test, so an over-matching + # guard -- one that credits any -Body argument, or any argument merely MENTIONING the + # variable -- resolves it and the test goes red. + @{ Case = 'a hashtable literal'; Argument = '@{}'; Variable = 'payload' } + @{ Case = 'a member access on it'; Argument = '$wrapper.Inner'; Variable = 'wrapper' } + @{ Case = 'an expression containing it'; Argument = '($payload + @{})'; Variable = 'payload' } + @{ Case = 'an index into it'; Argument = '$payload[''alpha'']'; Variable = 'payload' } + ) { + $v = '$' + $Variable + $funcAst = Get-PfbRoleFixtureAst @( + 'function Test-Fixture {' + ' param([string]$Zeta)' + (' ' + $v + ' = @{}') + (' ' + $v + '[''alpha''] = $Zeta') + (' Invoke-PfbApiRequest -Method POST -Endpoint ''widgets'' -Body ' + $Argument) + '}' + ) + Get-PfbRequestRoleForVariable -FunctionAst $funcAst -TargetVariable $Variable | Should -BeNullOrEmpty + } + + It 'keeps the surface but nulls the operation when one matching call has a nonliteral ' -ForEach @( + @{ Nonliteral = '-Method' + FirstCall = ' Invoke-PfbApiRequest -Method $Verb -Endpoint ''widgets'' -QueryParams $q' + } + @{ Nonliteral = '-Endpoint' + FirstCall = ' Invoke-PfbApiRequest -Method PATCH -Endpoint $Route -QueryParams $q' + } + ) { + # The literal sibling call below is the trap: the retired implementation skipped any + # call it could not fully read, so the one call it COULD read won outright and the + # unread landing vanished from the report. An unreadable landing is still a landing. + $funcAst = Get-PfbRoleFixtureAst @( + 'function Test-Fixture {' + ' param([string]$Zeta, [string]$Verb, [string]$Route)' + ' $q = @{}' + ' $q[''alpha''] = $Zeta' + $FirstCall + ' Invoke-PfbApiRequest -Method GET -Endpoint ''gizmos'' -QueryParams $q' + '}' + ) + $role = Get-PfbRequestRoleForVariable -FunctionAst $funcAst -TargetVariable 'q' + $role | Should -Not -BeNullOrEmpty + $role.WireSurface | Should -Be 'Query' + $role.Method | Should -BeNullOrEmpty + $role.Endpoint | Should -BeNullOrEmpty + } + + It 'nulls the operation for a LONE call whose is nonliteral' -ForEach @( + @{ Nonliteral = '-Method'; Call = ' Invoke-PfbApiRequest -Method $Verb -Endpoint ''widgets'' -QueryParams $q' } + @{ Nonliteral = '-Endpoint'; Call = ' Invoke-PfbApiRequest -Method PATCH -Endpoint $Route -QueryParams $q' } + ) { + # Separate from the sibling-call case above, and not redundant with it: with only one + # call there is no second operation to disagree with, so this is the only shape that + # fails if the reader takes a nonliteral argument's TEXT for its value. + $funcAst = Get-PfbRoleFixtureAst @( + 'function Test-Fixture {' + ' param([string]$Zeta, [string]$Verb, [string]$Route)' + ' $q = @{}' + ' $q[''alpha''] = $Zeta' + $Call + '}' + ) + $role = Get-PfbRequestRoleForVariable -FunctionAst $funcAst -TargetVariable 'q' + $role.WireSurface | Should -Be 'Query' + $role.Method | Should -BeNullOrEmpty + $role.Endpoint | Should -BeNullOrEmpty + } + + It 'nulls the operation when a matching call omits -Method or -Endpoint altogether' { + $funcAst = Get-PfbRoleFixtureAst @( + 'function Test-Fixture {' + ' param([string]$Zeta)' + ' $q = @{}' + ' $q[''alpha''] = $Zeta' + ' Invoke-PfbApiRequest -Endpoint ''widgets'' -QueryParams $q' + ' Invoke-PfbApiRequest -Method GET -Endpoint ''widgets'' -QueryParams $q' + '}' + ) + $role = Get-PfbRequestRoleForVariable -FunctionAst $funcAst -TargetVariable 'q' + $role.WireSurface | Should -Be 'Query' + $role.Method | Should -BeNullOrEmpty + $role.Endpoint | Should -BeNullOrEmpty + } + + It 'returns $null for a trailing payload switch with no argument at all' { + # Test-bed rule 5: a zero-argument invocation exposes Arguments as $null and + # @($null).Count is 1, so the arity guard has to test the null case before wrapping. + # Here the analogous trap is a -Body with nothing after it, at the end of the call. + $funcAst = Get-PfbRoleFixtureAst @( + 'function Test-Fixture {' + ' param([string]$Zeta)' + ' $q = @{}' + ' $q[''alpha''] = $Zeta' + ' Invoke-PfbApiRequest -Method GET -Endpoint ''widgets'' -QueryParams' + '}' + ) + Get-PfbRequestRoleForVariable -FunctionAst $funcAst -TargetVariable 'q' | Should -BeNullOrEmpty + } +} + +Describe 'Get-PfbWireNameForParameter: multi-landing abstention (issue #141 Task 3, Step 4)' { + + BeforeAll { + # Mirrors Remove-PfbFileSystem -DeleteLinkOnEradication, the one live shape where a + # single parameter writes the same key into two different payload variables that reach + # two different operations. Names are decoupled: the parameter is -Purge, the key is + # 'alpha_beta', the endpoint is 'widgets'. + # + # $destroyQuery reaches exactly one operation (PATCH widgets). $queryParams reaches two + # (PATCH widgets and DELETE widgets) and is therefore itself operation-ambiguous. So + # the two candidate tuples are + # (alpha_beta, Query, PATCH, widgets) via $destroyQuery + # (alpha_beta, Query, , ) via $queryParams + # and only WireName and WireSurface are common to both. + $script:destroyBranch = @( + ' $destroyQuery = @{} + $queryParams' + ' if ($Purge) { $destroyQuery[''alpha_beta''] = ''true'' }' + ' $disableBody = @{ nfs = @{ enabled = $false } }' + ' Invoke-PfbApiRequest -Array $Array -Method PATCH -Endpoint ''widgets'' -Body $disableBody -QueryParams $queryParams' + ' $body = @{ destroyed = $true }' + ' Invoke-PfbApiRequest -Array $Array -Method PATCH -Endpoint ''widgets'' -Body $body -QueryParams $destroyQuery' + ) + $script:eradicateBranch = @( + ' if ($Purge) { $queryParams[''alpha_beta''] = ''true'' }' + ' Invoke-PfbApiRequest -Array $Array -Method DELETE -Endpoint ''widgets'' -QueryParams $queryParams' + ) + + function script:New-PfbMultiLandingFixture { + param([Parameter(Mandatory)][ValidateSet('DestroyFirst', 'EradicateFirst')][string]$Order) + $first = if ($Order -eq 'DestroyFirst') { $script:destroyBranch } else { $script:eradicateBranch } + $second = if ($Order -eq 'DestroyFirst') { $script:eradicateBranch } else { $script:destroyBranch } + $condition = if ($Order -eq 'DestroyFirst') { ' if (-not $Purge) {' } else { ' if ($Purge) {' } + return @( + 'function Remove-FixtureThing {' + ' [CmdletBinding()]' + ' param([string]$Zeta, [switch]$Purge, [PSCustomObject]$Array)' + ' $queryParams = @{}' + ' if ($Zeta) { $queryParams[''names''] = $Zeta }' + $condition + $first + ' }' + ' else {' + $second + ' }' + '}' + ) + } + } + + It 'preserves only the facts common to every landing, with the branches in order' -ForEach @( + @{ Order = 'DestroyFirst' } + @{ Order = 'EradicateFirst' } + ) { + $funcAst = Get-PfbRoleFixtureAst (New-PfbMultiLandingFixture -Order $Order) + $wire = Get-PfbWireNameForParameter -FunctionAst $funcAst -ParameterName 'Purge' -IsBooleanLikeParameter + $wire | Should -Not -BeNullOrEmpty + $wire.WireName | Should -Be 'alpha_beta' + $wire.WireSurface | Should -Be 'Query' + $wire.Method | Should -BeNullOrEmpty + $wire.Endpoint | Should -BeNullOrEmpty + } + + It 'gives the identical answer whichever branch comes first in the source' { + # Source order is the specific failure mode here. Retiring the name gate makes + # $destroyQuery the earlier AST match, so a first-match resolver would confidently + # report PATCH/widgets and hide the DELETE landing entirely. + $first = Get-PfbWireNameForParameter -FunctionAst (Get-PfbRoleFixtureAst (New-PfbMultiLandingFixture -Order 'DestroyFirst')) -ParameterName 'Purge' -IsBooleanLikeParameter + $second = Get-PfbWireNameForParameter -FunctionAst (Get-PfbRoleFixtureAst (New-PfbMultiLandingFixture -Order 'EradicateFirst')) -ParameterName 'Purge' -IsBooleanLikeParameter + foreach ($component in 'WireName', 'WireSurface', 'Method', 'Endpoint', 'TargetVariable') { + $first.$component | Should -Be $second.$component -Because "component $component must not depend on source order" + } + } + + It 'does not let a weaker idiom answer after a stronger one has abstained' { + # $q is keyed twice under different names, so the index tier abstains outright. The + # hashtable literal in the same function offers a third name. Falling through to it + # would be first-match arbitration wearing a different hat: the strongest evidence + # was ambiguous, and a weaker idiom does not get to break the tie. + $funcAst = Get-PfbRoleFixtureAst @( + 'function Test-Fixture {' + ' param([string]$Zeta)' + ' $q = @{ ''gamma'' = $Zeta }' + ' $q[''alpha''] = $Zeta' + ' $q[''beta''] = $Zeta' + ' Invoke-PfbApiRequest -Method PATCH -Endpoint ''widgets'' -QueryParams $q' + '}' + ) + # Control: the weaker idiom really would answer 'gamma' if it were consulted. + (Get-PfbHashtableLiteralWireNameForParameter -FunctionAst $funcAst -ParameterName 'Zeta').WireName | Should -Be 'gamma' + Get-PfbWireNameForParameter -FunctionAst $funcAst -ParameterName 'Zeta' | Should -BeNullOrEmpty + } + + It 'nulls the METHOD too when the landings agree on it but disagree on the endpoint' { + # Half an operation is not an operation. The two landings below share their method, + # so a component-wise merge that forgot to pair method with endpoint would emit + # PATCH against no endpoint at all -- a fact no consumer can use and no call makes. + $funcAst = Get-PfbRoleFixtureAst @( + 'function Test-Fixture {' + ' param([string]$Zeta)' + ' $q = @{}' + ' $r = @{}' + ' $q[''alpha''] = $Zeta' + ' $r[''alpha''] = $Zeta' + ' Invoke-PfbApiRequest -Method PATCH -Endpoint ''widgets'' -QueryParams $q' + ' Invoke-PfbApiRequest -Method PATCH -Endpoint ''gadgets'' -QueryParams $r' + '}' + ) + $wire = Get-PfbWireNameForParameter -FunctionAst $funcAst -ParameterName 'Zeta' + $wire.WireName | Should -Be 'alpha' + $wire.WireSurface | Should -Be 'Query' + $wire.Method | Should -BeNullOrEmpty + $wire.Endpoint | Should -BeNullOrEmpty + } + + It 'collapses repeated occurrences of the SAME tuple to one complete resolution' { + $funcAst = Get-PfbRoleFixtureAst @( + 'function Test-Fixture {' + ' param([string]$Zeta)' + ' $q = @{}' + ' if ($Zeta) { $q[''alpha''] = $Zeta }' + ' else { $q[''alpha''] = $Zeta }' + ' Invoke-PfbApiRequest -Method PATCH -Endpoint ''widgets'' -QueryParams $q' + '}' + ) + $wire = Get-PfbWireNameForParameter -FunctionAst $funcAst -ParameterName 'Zeta' + $wire.WireName | Should -Be 'alpha' + $wire.WireSurface | Should -Be 'Query' + $wire.Method | Should -Be 'PATCH' + $wire.Endpoint | Should -Be 'widgets' + } + + It 'collapses the same tuple reached through two DIFFERENT variables to one complete resolution' { + # Two payload variables, identical key, identical surface, identical operation. The + # tuple is what is deduplicated, so this is one landing, not an ambiguity -- but the + # variable itself is not common to both, so TargetVariable is not claimed. + $funcAst = Get-PfbRoleFixtureAst @( + 'function Test-Fixture {' + ' param([string]$Zeta)' + ' $q = @{}' + ' $r = @{}' + ' if ($Zeta) { $q[''alpha''] = $Zeta }' + ' if ($Zeta) { $r[''alpha''] = $Zeta }' + ' Invoke-PfbApiRequest -Method PATCH -Endpoint ''widgets'' -QueryParams $q' + ' Invoke-PfbApiRequest -Method PATCH -Endpoint ''widgets'' -QueryParams $r' + '}' + ) + $wire = Get-PfbWireNameForParameter -FunctionAst $funcAst -ParameterName 'Zeta' + $wire.WireName | Should -Be 'alpha' + $wire.WireSurface | Should -Be 'Query' + $wire.Method | Should -Be 'PATCH' + $wire.Endpoint | Should -Be 'widgets' + $wire.TargetVariable | Should -BeNullOrEmpty + } + + It 'nulls the surface when one parameter lands on both Body and Query through different variables' { + $funcAst = Get-PfbRoleFixtureAst @( + 'function Test-Fixture {' + ' param([string]$Zeta)' + ' $q = @{}' + ' $p = @{}' + ' if ($Zeta) { $q[''alpha''] = $Zeta }' + ' if ($Zeta) { $p[''alpha''] = $Zeta }' + ' Invoke-PfbApiRequest -Method PATCH -Endpoint ''widgets'' -QueryParams $q -Body $p' + '}' + ) + $wire = Get-PfbWireNameForParameter -FunctionAst $funcAst -ParameterName 'Zeta' + $wire.WireName | Should -Be 'alpha' + $wire.WireSurface | Should -Be 'Unresolved' + $wire.Method | Should -Be 'PATCH' + $wire.Endpoint | Should -Be 'widgets' + } + + It 'refuses the whole resolution when the candidates do not even agree on the wire name' { + # Nothing nameable is proven, so there is no wire name to report. Returning a record + # with a null WireName would also suppress the accumulator retry in the inventory. + $funcAst = Get-PfbRoleFixtureAst @( + 'function Test-Fixture {' + ' param([string]$Zeta)' + ' $q = @{}' + ' $q[''alpha''] = $Zeta' + ' $q[''beta''] = $Zeta' + ' Invoke-PfbApiRequest -Method PATCH -Endpoint ''widgets'' -QueryParams $q' + '}' + ) + Get-PfbWireNameForParameter -FunctionAst $funcAst -ParameterName 'Zeta' | Should -BeNullOrEmpty + } + + It 'skips a landing whose payload variable has no proven role at all' { + # $nfsBody is keyed but never sent, so crediting it would name a field that does not + # exist at the top level of any request this cmdlet makes. + $funcAst = Get-PfbRoleFixtureAst @( + 'function Test-Fixture {' + ' param([string]$Zeta)' + ' $nfsBody = @{}' + ' $nfsBody[''alpha''] = $Zeta' + ' $q = @{}' + ' $q[''beta''] = $Zeta' + ' Invoke-PfbApiRequest -Method PATCH -Endpoint ''widgets'' -QueryParams $q' + '}' + ) + $wire = Get-PfbWireNameForParameter -FunctionAst $funcAst -ParameterName 'Zeta' + $wire.WireName | Should -Be 'beta' + $wire.WireSurface | Should -Be 'Query' + } +} + +Describe 'Get-PfbEndpointForVariable delegates to the role trace (issue #141 Task 3)' { + It 'resolves an arbitrarily named payload variable, which the retired name switch could not' { + $funcAst = Get-PfbRoleFixtureAst (New-PfbRoleFixtureSource -Variable 'q' -PayloadArgument '-QueryParams $q') + $result = Get-PfbEndpointForVariable -FunctionAst $funcAst -TargetVariable 'q' + $result.Method | Should -Be 'PATCH' + $result.Endpoint | Should -Be 'widgets' + } + + It 'returns $null when the surface is proven but the operation is not' { + $funcAst = Get-PfbRoleFixtureAst @( + 'function Test-Fixture {' + ' param([string]$Zeta)' + ' $q = @{}' + ' $q[''alpha''] = $Zeta' + ' Invoke-PfbApiRequest -Method PATCH -Endpoint ''widgets'' -QueryParams $q' + ' Invoke-PfbApiRequest -Method PATCH -Endpoint ''gadgets'' -QueryParams $q' + '}' + ) + Get-PfbEndpointForVariable -FunctionAst $funcAst -TargetVariable 'q' | Should -BeNullOrEmpty + } +} + +Describe 'Real-tree characterization of the argument-proven role (issue #141 Task 3, Step 7)' { + # Every input set below is derived at RUN TIME from the real Public/ tree. Nothing here + # pins a row count: a count would either go stale on the next cmdlet added or, worse, + # pass while the rows underneath it changed. + + BeforeAll { + $script:realFunctions = @{} + foreach ($file in @(Get-ChildItem -Path $script:publicDir -Filter '*.ps1' -Recurse -File)) { + $tokens = $null; $errs = $null + $fileAst = [System.Management.Automation.Language.Parser]::ParseFile($file.FullName, [ref]$tokens, [ref]$errs) + @($errs).Count | Should -Be 0 -Because "$($file.FullName) must parse for its functions to be analysable at all" + foreach ($fn in $fileAst.FindAll({ param($n) $n -is [System.Management.Automation.Language.FunctionDefinitionAst] }, $true)) { + $script:realFunctions[$fn.Name] = $fn + } + } + + $script:realInventory = @(Get-PfbCmdletParameterInventory -PublicDirectory $script:publicDir) + $script:realTyped = @($script:realInventory | Where-Object { $_.Surface -eq 'Typed' }) + + # Distinct variable names that receive a literal string-keyed index assignment of the + # given wire key inside one function -- used to prove that a Typed row with NO target + # variable is a genuine multi-landing abstention rather than a lost fact. + function script:Get-PfbTestWireKeyVariable { + param($FunctionAst, [string]$WireName) + $found = [System.Collections.Generic.List[string]]::new() + foreach ($assignment in $FunctionAst.FindAll({ param($n) $n -is [System.Management.Automation.Language.AssignmentStatementAst] }, $true)) { + $index = $assignment.Left -as [System.Management.Automation.Language.IndexExpressionAst] + if (-not $index) { continue } + if ($index.Index -isnot [System.Management.Automation.Language.StringConstantExpressionAst]) { continue } + if ($index.Index.Value -ne $WireName) { continue } + $target = $index.Target -as [System.Management.Automation.Language.VariableExpressionAst] + if (-not $target) { continue } + if (-not $found.Contains($target.VariablePath.UserPath)) { $found.Add($target.VariablePath.UserPath) } + } + return $found + } + } + + It 'gives every Typed row target variable a role whose surface and operation match the row' { + $checked = 0 + $offenders = [System.Collections.Generic.List[string]]::new() + + foreach ($record in $script:realTyped) { + if (-not $record.TargetVariable) { continue } + $checked++ + $key = '{0}|{1}|{2}|{3}' -f $record.Cmdlet, $record.Parameter, $record.WireName, $record.TargetVariable + + $funcAst = $script:realFunctions[$record.Cmdlet] + if (-not $funcAst) { $offenders.Add("MISSINGFUNC $key"); continue } + + $role = Get-PfbRequestRoleForVariable -FunctionAst $funcAst -TargetVariable $record.TargetVariable + if (-not $role) { $offenders.Add("NOROLE $key"); continue } + + if ($role.WireSurface -ne $record.WireSurface) { + $offenders.Add(('SURFACE {0} role={1} row={2}' -f $key, $role.WireSurface, $record.WireSurface)) + } + if ($role.Method -ne $record.Method -or $role.Endpoint -ne $record.Endpoint) { + $offenders.Add(('OPERATION {0} role={1}|{2} row={3}|{4}' -f $key, $role.Method, $role.Endpoint, $record.Method, $record.Endpoint)) + } + } + + $checked | Should -BeGreaterThan 0 -Because 'an empty input set would make this assertion vacuous' + $offenders -join "`n" | Should -BeNullOrEmpty + } + + It 'leaves a Typed row without a target variable only where two or more payload variables carry that wire key' { + # The abstention path: when a parameter lands on more than one payload variable the + # resolver keeps only the facts every candidate agrees on, so TargetVariable drops + # out. That must never be how an ordinary single-landing row looks. + $offenders = [System.Collections.Generic.List[string]]::new() + + foreach ($record in $script:realTyped) { + if ($record.TargetVariable) { continue } + $funcAst = $script:realFunctions[$record.Cmdlet] + $variables = @(script:Get-PfbTestWireKeyVariable -FunctionAst $funcAst -WireName $record.WireName) + if ($variables.Count -lt 2) { + $offenders.Add(('{0}|{1}|{2} carriers={3}' -f $record.Cmdlet, $record.Parameter, $record.WireName, ($variables -join ','))) + } + } + + $offenders -join "`n" | Should -BeNullOrEmpty + } + + It 'finds Remove-PfbFileSystem to be the only cmdlet passing more than one distinct variable to a single request surface' { + # This is a characterization of the tree as it stands, not a rule the resolver may + # rely on: the arbitration is general, and nothing in tools/lib names this cmdlet. + $surfaceParameter = @{ Body = 'Body'; Query = 'QueryParams' } + $multi = [System.Collections.Generic.List[string]]::new() + + foreach ($name in $script:realFunctions.Keys) { + $funcAst = $script:realFunctions[$name] + foreach ($surface in $surfaceParameter.Keys) { + $parameterName = $surfaceParameter[$surface] + $variables = [System.Collections.Generic.List[string]]::new() + + foreach ($command in $funcAst.FindAll({ + param($n) + $n -is [System.Management.Automation.Language.CommandAst] -and + $n.GetCommandName() -eq 'Invoke-PfbApiRequest' + }, $true)) { + $elements = @($command.CommandElements) + for ($i = 0; $i -lt $elements.Count; $i++) { + $element = $elements[$i] -as [System.Management.Automation.Language.CommandParameterAst] + if (-not $element -or $element.ParameterName -ne $parameterName) { continue } + $argument = $element.Argument + if (-not $argument -and ($i + 1) -lt $elements.Count -and + $elements[$i + 1] -isnot [System.Management.Automation.Language.CommandParameterAst]) { + $argument = $elements[$i + 1] + } + $variable = $argument -as [System.Management.Automation.Language.VariableExpressionAst] + if ($variable -and -not $variables.Contains($variable.VariablePath.UserPath)) { + $variables.Add($variable.VariablePath.UserPath) + } + } + } + + if ($variables.Count -gt 1) { + $multi.Add(('{0} {1}: {2}' -f $name, $surface, (($variables | Sort-Object) -join ','))) + } + } + } + + @($multi | ForEach-Object { ($_ -split ' ')[0] } | Select-Object -Unique) | Should -Be @('Remove-PfbFileSystem') + } + + It 'keeps Remove-PfbFileSystem -DeleteLinkOnEradication on its shared query key and refuses to name an operation' { + # The live hazard the abstention exists for: the same wire key is written into + # $destroyQuery (which reaches a PATCH) and into $queryParams (which reaches a + # DELETE). Reporting either operation would be a source-order accident. + $record = $script:realInventory | + Where-Object { $_.Cmdlet -eq 'Remove-PfbFileSystem' -and $_.Parameter -eq 'DeleteLinkOnEradication' } + + $record | Should -Not -BeNullOrEmpty + $record.WireName | Should -Be 'delete_link_on_eradication' + $record.WireSurface | Should -Be 'Query' + $record.Method | Should -BeNullOrEmpty + $record.Endpoint | Should -BeNullOrEmpty + } +} diff --git a/tools/lib/PfbCmdletParamTools.ps1 b/tools/lib/PfbCmdletParamTools.ps1 index 56e49a02..4fbbe425 100644 --- a/tools/lib/PfbCmdletParamTools.ps1 +++ b/tools/lib/PfbCmdletParamTools.ps1 @@ -574,16 +574,141 @@ function Get-PfbCommonQueryParamHelperWireName { return [PSCustomObject]@{ WireName = $parts[0]; TargetVariable = $parts[1] } } +function Resolve-PfbWireLandingArbitration { + <# + .SYNOPSIS + Reduces the candidate wire landings of ONE parameter to at most one resolution, + keeping only what every candidate agrees on. + .DESCRIPTION + A candidate is one proven assignment of the parameter into a payload variable, joined + to that variable's argument-proven request role: the complete tuple + (WireName, WireSurface, Method, Endpoint), plus the TargetVariable it came through. + + Arbitration is deliberately independent of AST traversal order. Selecting the first + match -- which is what this file did before issue #141 Task 3 -- publishes whichever + landing the parser happened to reach first and silently discards the rest, and the + name gate was the only thing hiding how bad that is. Retiring the gate makes + Remove-PfbFileSystem's $destroyQuery the earlier match for -DeleteLinkOnEradication, + so first-match would have reported PATCH file-systems with confidence while the DELETE + file-systems landing of the very same key vanished from the report. + + So: components that every candidate shares are kept, and the rest are nulled. There is + no separate deduplication pass because none is needed -- repeated identical tuples + agree on every component by construction, and therefore survive whole. A key assigned + in both arms of an if/else is one landing, not an ambiguity. + + WireName is the one component whose absence voids the whole resolution. With no agreed + key there is nothing nameable left to publish, and returning a record carrying a null + WireName would additionally suppress the accumulator retry in + Get-PfbCmdletParameterInventory, which fires only when this returns $null. + .OUTPUTS + $null, or [PSCustomObject]@{ WireName; TargetVariable; WireSurface; Method; Endpoint }. + WireSurface is 'Body', 'Query' or 'Unresolved'; Method and Endpoint are either both + populated or both $null. + #> + [CmdletBinding()] + param( + # [PSCustomObject]@{ WireName; TargetVariable; WireSurface; Method; Endpoint } + [Parameter(Mandatory)] + [AllowEmptyCollection()] + [object[]]$Candidate + ) + + $candidates = @($Candidate) + if ($candidates.Count -eq 0) { return $null } + + $agreedValue = { + param($Property) + $first = $candidates[0].$Property + foreach ($item in $candidates) { + if ($item.$Property -ne $first) { return $null } + } + return $first + } + + $wireName = & $agreedValue 'WireName' + if (-not $wireName) { return $null } + + $wireSurface = & $agreedValue 'WireSurface' + if (-not $wireSurface) { $wireSurface = 'Unresolved' } + + $method = & $agreedValue 'Method' + $endpoint = & $agreedValue 'Endpoint' + if (-not ($method -and $endpoint)) { + $method = $null + $endpoint = $null + } + + return [PSCustomObject]@{ + WireName = $wireName + TargetVariable = (& $agreedValue 'TargetVariable') + WireSurface = $wireSurface + Method = $method + Endpoint = $endpoint + } +} + +function New-PfbWireLanding { + <# + .SYNOPSIS + Builds one arbitration candidate from a proven (key, payload variable) assignment, + or $null when that variable has no argument-proven request role. + .DESCRIPTION + This is the role gate, and it stands exactly where the + `-notin @('body', 'queryParams')` name gate used to. A variable that is never handed + to Invoke-PfbApiRequest -Body/-QueryParams is an intermediate, not a payload: + New-PfbFileSystem's $nfsBody is keyed and then folded into $body under a + sub-object, so crediting -ExportPolicy with $nfsBody's 'export_policy' key would name + a top-level field that no request this cmdlet makes actually has. The old gate + excluded such variables by recognising two blessed names; this one excludes them by + failing to prove they are sent, which also admits $q, $payload and $destroyQuery. + .OUTPUTS + $null, or [PSCustomObject]@{ WireName; TargetVariable; WireSurface; Method; Endpoint } + #> + [CmdletBinding()] + param( + [Parameter(Mandatory)] + [System.Management.Automation.Language.FunctionDefinitionAst]$FunctionAst, + + [Parameter(Mandatory)] + [string]$WireName, + + [Parameter(Mandatory)] + [string]$TargetVariable + ) + + $role = Get-PfbRequestRoleForVariable -FunctionAst $FunctionAst -TargetVariable $TargetVariable + if (-not $role) { return $null } + + return [PSCustomObject]@{ + WireName = $WireName + TargetVariable = $TargetVariable + WireSurface = $role.WireSurface + Method = $role.Method + Endpoint = $role.Endpoint + } +} + function Get-PfbWireNameForParameter { <# .SYNOPSIS Finds the request-body or query-string key a given parameter is assigned to inside a cmdlet function body, or $null if no simple assignment pattern matches. + .DESCRIPTION + Four idioms are tried in a fixed precedence, and the FIRST idiom that produces any + proven landing answers -- including by abstaining. Precedence is between idioms only; + within one idiom every landing is collected and arbitrated together + (Resolve-PfbWireLandingArbitration), so the answer never depends on which assignment + the parser reached first. + + A tier that finds landings and then abstains does not fall through to the next tier. + Falling through would let a weaker idiom quietly supply a name for a parameter whose + stronger, ambiguous evidence had just been discarded -- which is the first-match + failure wearing a different hat. .OUTPUTS - $null, or [PSCustomObject]@{ WireName; TargetVariable } -- TargetVariable is the - literal variable name the assignment targeted ('body' or 'queryParams'), needed - by Get-PfbEndpointForVariable to find the specific Invoke-PfbApiRequest call(s) - that variable is later passed to. + $null, or [PSCustomObject]@{ WireName; TargetVariable; WireSurface; Method; Endpoint }. + TargetVariable is the payload variable the assignment targeted, or $null when the + landings came through more than one; WireSurface is 'Body', 'Query' or 'Unresolved'. #> [CmdletBinding()] param( @@ -602,23 +727,24 @@ function Get-PfbWireNameForParameter { $node.Left -is [System.Management.Automation.Language.IndexExpressionAst] }, $true) + $landings = [System.Collections.Generic.List[object]]::new() + foreach ($assign in $assignments) { $indexExpr = $assign.Left $targetVar = $indexExpr.Target -as [System.Management.Automation.Language.VariableExpressionAst] if (-not $targetVar) { continue } - if ($targetVar.VariablePath.UserPath -notin @('body', 'queryParams')) { continue } $keyExpr = $indexExpr.Index -as [System.Management.Automation.Language.StringConstantExpressionAst] if (-not $keyExpr) { continue } if (Test-PfbWireValueIsParameter -ValueAst $assign.Right -ParameterName $ParameterName -IsBooleanLikeParameter:$IsBooleanLikeParameter) { - return [PSCustomObject]@{ - WireName = $keyExpr.Value - TargetVariable = $targetVar.VariablePath.UserPath - } + $landing = New-PfbWireLanding -FunctionAst $FunctionAst -WireName $keyExpr.Value -TargetVariable $targetVar.VariablePath.UserPath + if ($landing) { $landings.Add($landing) } } } + if ($landings.Count -gt 0) { return (Resolve-PfbWireLandingArbitration -Candidate $landings.ToArray()) } + # Second idiom: the whole hashtable is built as a LITERAL initializer rather than keyed # into afterwards -- `$queryParams = @{ 'names' = $Name }`, the dominant shape across # New-Pfb*/Remove-Pfb*/Update-Pfb* (New-PfbApiClient, New-PfbAlertWatcher, @@ -641,7 +767,14 @@ function Get-PfbWireNameForParameter { # LAST: a cmdlet whose Name/Id-equivalent maps to a non-generic key (policy_names, # file_system_names, ...) kept its own explicit line after the helper call, and that # literal must win. - return Get-PfbCommonQueryParamHelperWireName -FunctionAst $FunctionAst -ParameterName $ParameterName + $helperLandings = [System.Collections.Generic.List[object]]::new() + foreach ($helperMatch in @(Get-PfbCommonQueryParamHelperWireName -FunctionAst $FunctionAst -ParameterName $ParameterName)) { + if (-not $helperMatch) { continue } + $landing = New-PfbWireLanding -FunctionAst $FunctionAst -WireName $helperMatch.WireName -TargetVariable $helperMatch.TargetVariable + if ($landing) { $helperLandings.Add($landing) } + } + if ($helperLandings.Count -eq 0) { return $null } + return Resolve-PfbWireLandingArbitration -Candidate $helperLandings.ToArray() } function Get-PfbHashtableLiteralWireNameForParameter { @@ -660,8 +793,8 @@ function Get-PfbHashtableLiteralWireNameForParameter { matched by the same Test-PfbWireValueIsParameter used by the index-assignment path, so a pipeline transform is still refused rather than guessed at. .OUTPUTS - $null, or [PSCustomObject]@{ WireName; TargetVariable } -- same shape as - Get-PfbWireNameForParameter. + $null, or [PSCustomObject]@{ WireName; TargetVariable; WireSurface; Method; Endpoint } + -- same shape as Get-PfbWireNameForParameter, arbitrated the same way. #> [CmdletBinding()] param( @@ -680,9 +813,10 @@ function Get-PfbHashtableLiteralWireNameForParameter { $node.Left -is [System.Management.Automation.Language.VariableExpressionAst] }, $true)) + $landings = [System.Collections.Generic.List[object]]::new() + foreach ($assign in $assignments) { $targetVar = $assign.Left -as [System.Management.Automation.Language.VariableExpressionAst] - if ($targetVar.VariablePath.UserPath -notin @('body', 'queryParams')) { continue } $hashtable = (Resolve-PfbSingleExpression -Ast $assign.Right) -as [System.Management.Automation.Language.HashtableAst] if (-not $hashtable) { continue } @@ -695,15 +829,14 @@ function Get-PfbHashtableLiteralWireNameForParameter { if (-not $keyExpr) { continue } if (Test-PfbWireValueIsParameter -ValueAst $pair.Item2 -ParameterName $ParameterName -IsBooleanLikeParameter:$IsBooleanLikeParameter) { - return [PSCustomObject]@{ - WireName = $keyExpr.Value - TargetVariable = $targetVar.VariablePath.UserPath - } + $landing = New-PfbWireLanding -FunctionAst $FunctionAst -WireName $keyExpr.Value -TargetVariable $targetVar.VariablePath.UserPath + if ($landing) { $landings.Add($landing) } } } } - return $null + if ($landings.Count -eq 0) { return $null } + return Resolve-PfbWireLandingArbitration -Candidate $landings.ToArray() } function Get-PfbNestedReferenceWireNameForParameter { @@ -724,8 +857,9 @@ function Get-PfbNestedReferenceWireNameForParameter { field, and the endpoint's gap analysis only ever asks whether `account` is covered. Never guesses, matching the rest of this file: - - the target variable must be body/queryParams (an intermediate like - New-PfbFileSystem's $nfsBody is not traceable to an Invoke-PfbApiRequest call); + - the target variable must have an argument-proven request role (an intermediate + like New-PfbFileSystem's $nfsBody is never handed to Invoke-PfbApiRequest, so + its keys are not the keys of any request); - the outer key must be a literal string constant; - the nested hashtable must have EXACTLY ONE key/value pair, itself string-keyed -- a multi-key sub-object is a composite whose per-field ownership cannot be @@ -744,8 +878,8 @@ function Get-PfbNestedReferenceWireNameForParameter { only ever turn an unresolved parameter into a Typed one, never rename an already-resolved wire name. .OUTPUTS - $null, or [PSCustomObject]@{ WireName; TargetVariable } -- same shape as - Get-PfbWireNameForParameter. + $null, or [PSCustomObject]@{ WireName; TargetVariable; WireSurface; Method; Endpoint } + -- same shape as Get-PfbWireNameForParameter, arbitrated the same way. #> [CmdletBinding()] param( @@ -780,29 +914,33 @@ function Get-PfbNestedReferenceWireNameForParameter { }, $true)) # Index form first, then the literal-initializer form, mirroring the order - # Get-PfbWireNameForParameter uses for the direct shapes. + # Get-PfbWireNameForParameter uses for the direct shapes. Each form collects all of its + # landings and arbitrates them together; the literal form is consulted only when the + # index form proved nothing at all. + $indexLandings = [System.Collections.Generic.List[object]]::new() + foreach ($assign in $assignments) { $indexExpr = $assign.Left -as [System.Management.Automation.Language.IndexExpressionAst] if (-not $indexExpr) { continue } $targetVar = $indexExpr.Target -as [System.Management.Automation.Language.VariableExpressionAst] if (-not $targetVar) { continue } - if ($targetVar.VariablePath.UserPath -notin @('body', 'queryParams')) { continue } $keyExpr = $indexExpr.Index -as [System.Management.Automation.Language.StringConstantExpressionAst] if (-not $keyExpr) { continue } if (& $isReferenceObjectFor $assign.Right) { - return [PSCustomObject]@{ - WireName = $keyExpr.Value - TargetVariable = $targetVar.VariablePath.UserPath - } + $landing = New-PfbWireLanding -FunctionAst $FunctionAst -WireName $keyExpr.Value -TargetVariable $targetVar.VariablePath.UserPath + if ($landing) { $indexLandings.Add($landing) } } } + if ($indexLandings.Count -gt 0) { return (Resolve-PfbWireLandingArbitration -Candidate $indexLandings.ToArray()) } + + $literalLandings = [System.Collections.Generic.List[object]]::new() + foreach ($assign in $assignments) { $targetVar = $assign.Left -as [System.Management.Automation.Language.VariableExpressionAst] if (-not $targetVar) { continue } - if ($targetVar.VariablePath.UserPath -notin @('body', 'queryParams')) { continue } $hashtable = (Resolve-PfbSingleExpression -Ast $assign.Right) -as [System.Management.Automation.Language.HashtableAst] if (-not $hashtable) { continue } @@ -812,15 +950,14 @@ function Get-PfbNestedReferenceWireNameForParameter { if (-not $keyExpr) { continue } if (& $isReferenceObjectFor $pair.Item2) { - return [PSCustomObject]@{ - WireName = $keyExpr.Value - TargetVariable = $targetVar.VariablePath.UserPath - } + $landing = New-PfbWireLanding -FunctionAst $FunctionAst -WireName $keyExpr.Value -TargetVariable $targetVar.VariablePath.UserPath + if ($landing) { $literalLandings.Add($landing) } } } } - return $null + if ($literalLandings.Count -eq 0) { return $null } + return Resolve-PfbWireLandingArbitration -Candidate $literalLandings.ToArray() } function Find-PfbAccumulatorVariable { @@ -902,22 +1039,47 @@ function Find-PfbAccumulatorVariable { return $accumulatorName } -function Get-PfbEndpointForVariable { +function Get-PfbRequestRoleForVariable { <# .SYNOPSIS - Finds the (Method, Endpoint) pair a body/queryParams variable is passed to via - Invoke-PfbApiRequest -Body/-QueryParams within a function, IF every such call - agrees on exactly one (Method, Endpoint) pair. + The request role a variable actually plays -- Body or Query, and the operation it + reaches -- read from the ARGUMENTS of the Invoke-PfbApiRequest calls it is passed + to, never from its name. .DESCRIPTION - Never guesses: returns $null when the variable feeds zero Invoke-PfbApiRequest - calls, or more than one call with a DIFFERENT (Method, Endpoint) pair (e.g. - Get-PfbNode's try/catch fallback that reuses the same $queryParams against two - genuinely different endpoints, 'nodes' then 'blades' -- correctly ambiguous, - not a case to force-pick one of). Only literal, unquoted-bareword-or-quoted- - string -Method/-Endpoint arguments are recognized, matching the exclusively - literal style every cmdlet in this repo actually uses for both. + This replaces a `switch ($TargetVariable) { 'body' {...} 'queryParams' {...} }` trust + gate, which was an inference from a name and so a standing violation of the + never-guess contract in both directions (issue #141). It under-reported: a cmdlet + keying into $q, $payload or $destroyQuery had no role at all, so every parameter it + proved was silently dropped. And it could over-report: a variable literally named + $body but passed to -QueryParams would have been published as a Body landing. That + second class has zero current occurrences in Public/, which is precisely why it needs + a resolver that cannot express it rather than a one-off survey. + + A call contributes a LANDING when the variable is passed, as the bare variable, to + -Body (surface 'Body') or -QueryParams (surface 'Query'). Both argument forms are + read: `-Body $payload` parks the value in the NEXT command element, while + `-Body:$payload` parks it on the CommandParameterAst's own .Argument. Reading only + the next element -- as the retired implementation did -- misses the colon form. + + Anything other than the bare variable is refused: `-Body @{}`, `-Body $wrapper.Inner`, + `-Body ($payload + @{})`, `-Body $payload['alpha']`. In none of those is the + variable's key set provably the key set of the request. + + Method and Endpoint come from LITERAL -Method/-Endpoint arguments of the same call, + matching the exclusively literal style every cmdlet in this repo uses for both, and + are reported only when every landing agrees on one operation. A call whose -Method or + -Endpoint is a variable, or which omits one, still counts as a landing with an + UNKNOWN operation -- it is exactly the landing that cannot be read, so allowing a + readable sibling call to win would publish an operation the variable does not + exclusively reach. + + Returns $null when the variable has no landing at all, and when it lands on BOTH + surfaces: a payload sent as body in one call and as query string in another has no + single role to report, and picking either would be a guess. .OUTPUTS - $null, or [PSCustomObject]@{ Method; Endpoint } + $null, or [PSCustomObject]@{ TargetVariable; WireSurface; Method; Endpoint }. + WireSurface is 'Body' or 'Query'. Method and Endpoint are either both populated or + both $null -- half an operation identifies nothing. #> [CmdletBinding()] param( @@ -928,56 +1090,133 @@ function Get-PfbEndpointForVariable { [string]$TargetVariable ) - $targetParamName = switch ($TargetVariable) { - 'body' { 'Body' } - 'queryParams' { 'QueryParams' } - default { $null } - } - if (-not $targetParamName) { return $null } - - $commands = $FunctionAst.FindAll({ + $commands = @($FunctionAst.FindAll({ param($node) $node -is [System.Management.Automation.Language.CommandAst] -and $node.GetCommandName() -eq 'Invoke-PfbApiRequest' - }, $true) + }, $true)) - $pairs = [System.Collections.Generic.List[string]]::new() + $landings = [System.Collections.Generic.List[object]]::new() foreach ($cmd in $commands) { - $elements = $cmd.CommandElements - $usesVariable = $false + $elements = @($cmd.CommandElements) + $surfaces = [System.Collections.Generic.List[string]]::new() $method = $null $endpoint = $null for ($i = 0; $i -lt $elements.Count; $i++) { - $el = $elements[$i] - if ($el -isnot [System.Management.Automation.Language.CommandParameterAst]) { continue } - $next = if ($i + 1 -lt $elements.Count) { $elements[$i + 1] } else { $null } - if (-not $next) { continue } - - if ($el.ParameterName -eq $targetParamName -and - $next -is [System.Management.Automation.Language.VariableExpressionAst] -and - $next.VariablePath.UserPath -eq $TargetVariable) { - $usesVariable = $true - } - elseif ($el.ParameterName -eq 'Method' -and $next -is [System.Management.Automation.Language.StringConstantExpressionAst]) { - $method = $next.Value + $el = $elements[$i] -as [System.Management.Automation.Language.CommandParameterAst] + if (-not $el) { continue } + + # `-Name:$value` carries its argument on the parameter itself; `-Name $value` + # carries it in the next element -- but only if that element is not itself a + # parameter, which is how `-QueryParams -AutoPaginate` and a trailing + # `-QueryParams` with nothing after it are kept from binding a non-argument. + $arg = $el.Argument + if (-not $arg -and ($i + 1) -lt $elements.Count) { + $nextElement = $elements[$i + 1] + if ($nextElement -isnot [System.Management.Automation.Language.CommandParameterAst]) { + $arg = $nextElement + } } - elseif ($el.ParameterName -eq 'Endpoint' -and $next -is [System.Management.Automation.Language.StringConstantExpressionAst]) { - $endpoint = $next.Value + if (-not $arg) { continue } + + switch ($el.ParameterName) { + 'Body' { + $var = $arg -as [System.Management.Automation.Language.VariableExpressionAst] + if ($var -and $var.VariablePath.UserPath -eq $TargetVariable -and -not $surfaces.Contains('Body')) { + $surfaces.Add('Body') + } + } + 'QueryParams' { + $var = $arg -as [System.Management.Automation.Language.VariableExpressionAst] + if ($var -and $var.VariablePath.UserPath -eq $TargetVariable -and -not $surfaces.Contains('Query')) { + $surfaces.Add('Query') + } + } + 'Method' { + if ($arg -is [System.Management.Automation.Language.StringConstantExpressionAst]) { $method = $arg.Value } + } + 'Endpoint' { + if ($arg -is [System.Management.Automation.Language.StringConstantExpressionAst]) { $endpoint = $arg.Value } + } } } - if ($usesVariable -and $method -and $endpoint) { - $pairs.Add("$($method.ToUpperInvariant())|$endpoint") + foreach ($surface in $surfaces) { + $landings.Add([PSCustomObject]@{ Surface = $surface; Method = $method; Endpoint = $endpoint }) } } - $distinct = @($pairs | Select-Object -Unique) - if ($distinct.Count -ne 1) { return $null } + if ($landings.Count -eq 0) { return $null } - $parts = $distinct[0] -split '\|', 2 - return [PSCustomObject]@{ Method = $parts[0]; Endpoint = $parts[1] } + $distinctSurfaces = [System.Collections.Generic.List[string]]::new() + foreach ($landing in $landings) { + if (-not $distinctSurfaces.Contains($landing.Surface)) { $distinctSurfaces.Add($landing.Surface) } + } + if ($distinctSurfaces.Count -ne 1) { return $null } + + # An empty string is the sentinel for 'this landing's operation could not be read'. It + # participates in the distinctness test like any other value, which is what stops a + # readable call from speaking for an unreadable one. + $distinctOperations = [System.Collections.Generic.List[string]]::new() + foreach ($landing in $landings) { + $operation = '' + if ($landing.Method -and $landing.Endpoint) { + $operation = '{0}|{1}' -f $landing.Method.ToUpperInvariant(), $landing.Endpoint + } + if (-not $distinctOperations.Contains($operation)) { $distinctOperations.Add($operation) } + } + + $resolvedMethod = $null + $resolvedEndpoint = $null + if ($distinctOperations.Count -eq 1 -and $distinctOperations[0] -ne '') { + $parts = $distinctOperations[0] -split '\|', 2 + $resolvedMethod = $parts[0] + $resolvedEndpoint = $parts[1] + } + + return [PSCustomObject]@{ + TargetVariable = $TargetVariable + WireSurface = $distinctSurfaces[0] + Method = $resolvedMethod + Endpoint = $resolvedEndpoint + } +} + +function Get-PfbEndpointForVariable { + <# + .SYNOPSIS + Compatibility wrapper: the (Method, Endpoint) pair a payload variable reaches, or + $null when that operation is not provably unique. + .DESCRIPTION + Delegates wholly to Get-PfbRequestRoleForVariable and keeps no independent notion of + which variables are payloads -- the name switch this function used to carry is the + defect issue #141 Task 3 removed, and reintroducing a copy of it here would restore + the defect for every caller of this entry point. + + Still returns $null when the variable feeds zero Invoke-PfbApiRequest calls, or more + than one call with a DIFFERENT (Method, Endpoint) pair -- Get-PfbNode's try/catch + fallback reuses one $queryParams against 'nodes' then 'blades', which is correctly + ambiguous rather than a case to force-pick one of. It now additionally returns $null + when the surface itself is ambiguous, and resolves variables of any name. + .OUTPUTS + $null, or [PSCustomObject]@{ Method; Endpoint } + #> + [CmdletBinding()] + param( + [Parameter(Mandatory)] + [System.Management.Automation.Language.FunctionDefinitionAst]$FunctionAst, + + [Parameter(Mandatory)] + [string]$TargetVariable + ) + + $role = Get-PfbRequestRoleForVariable -FunctionAst $FunctionAst -TargetVariable $TargetVariable + if (-not $role) { return $null } + if (-not ($role.Method -and $role.Endpoint)) { return $null } + + return [PSCustomObject]@{ Method = $role.Method; Endpoint = $role.Endpoint } } function Get-PfbCmdletBodyInsertionTarget { @@ -1125,14 +1364,16 @@ function Get-PfbCmdletParameterInventory { [PSCustomObject]@{ File; Line; Cmdlet; Parameter; HasValidateSet; ValidateSetValues; WireName; TargetVariable; WireSurface; Surface; Endpoint; Method } - TargetVariable is the resolved assignment target ('queryParams' or 'body'), and - WireSurface is its coarse classification ('Query' | 'Body' | 'Unresolved') -- the - distinction between a query selector and a request-body property, which name - shape alone cannot supply. + TargetVariable is the resolved assignment target -- the payload variable name, of + whatever spelling -- or $null when the parameter proved landings through more than + one. WireSurface is the coarse classification ('Query' | 'Body' | 'Unresolved') of + the surface those landings agree on: the distinction between a query selector and a + request-body property, which name shape alone cannot supply, and which is read from + the -Body/-QueryParams argument the variable is passed as rather than from the + variable's own name (see Get-PfbRequestRoleForVariable). - Endpoint/Method are $null unless the parameter's wire-name assignment resolved - to exactly one Invoke-PfbApiRequest call's endpoint (see - Get-PfbEndpointForVariable) -- never guessed. + Endpoint/Method are $null unless every landing of the parameter agrees on one + literal Invoke-PfbApiRequest (method, endpoint) pair -- never guessed. Line is the parameter's own declaration line ($p.Extent.StartLineNumber), alongside the File it already carried -- so a consumer reporting on a @@ -1194,21 +1435,18 @@ function Get-PfbCmdletParameterInventory { } $wireName = if ($wireInfo) { $wireInfo.WireName } else { $null } - $endpointInfo = if ($wireInfo) { Get-PfbEndpointForVariable -FunctionAst $funcAst -TargetVariable $wireInfo.TargetVariable } else { $null } - $surface = if ($wireName) { 'Typed' } elseif ($hasAttributesParam) { 'AttributesOnly' } else { 'TypedUnresolved' } - # Which request surface the parameter reaches: Get-PfbWireNameForParameter's - # TargetVariable is literally 'queryParams' or 'body'. A selector is a query - # parameter; a request-body property is not, however selector-shaped its name. + # Surface, method and endpoint all arrive already arbitrated from + # Get-PfbWireNameForParameter, which resolves them from the request arguments + # of the calls the payload variable is actually passed to (issue #141 Task 3). + # They are deliberately NOT re-derived here from TargetVariable: that was the + # retired name gate, and a parameter landing through more than one payload + # variable has no single TargetVariable to re-derive them from. $targetVariable = if ($wireInfo) { $wireInfo.TargetVariable } else { $null } - $wireSurface = switch ($targetVariable) { - 'queryParams' { 'Query' } - 'body' { 'Body' } - default { 'Unresolved' } - } + $wireSurface = if ($wireInfo) { $wireInfo.WireSurface } else { 'Unresolved' } $results.Add([PSCustomObject]@{ File = $file.FullName @@ -1221,8 +1459,8 @@ function Get-PfbCmdletParameterInventory { TargetVariable = $targetVariable WireSurface = $wireSurface Surface = $surface - Endpoint = if ($endpointInfo) { $endpointInfo.Endpoint } else { $null } - Method = if ($endpointInfo) { $endpointInfo.Method } else { $null } + Endpoint = if ($wireInfo) { $wireInfo.Endpoint } else { $null } + Method = if ($wireInfo) { $wireInfo.Method } else { $null } }) } } From 6639d72124bf84eda2064f260e683248be45e905 Mon Sep 17 00:00:00 2001 From: Justin Emerson Date: Wed, 26 Aug 2026 18:05:06 -0700 Subject: [PATCH 06/29] fix(tools): make the landing abstention sticky at every tier and compare components ordinally Review round 1 on the issue #141 Task 3 resolver. Finding 1 (major). Get-PfbWireNameForParameter documented that a tier which finds candidate landings and then abstains ends the resolution, but only the index tier implemented it. The literal, nested-reference and helper tiers gated on the truthiness of the arbitrated answer, which cannot distinguish "found nothing" from "found landings that disagreed", so an abstaining tier fell through and let a weaker idiom publish the arbitrary pick the abstention exists to refuse. Split the two landing producers out of their arbitrating wrappers (Get-PfbHashtableLiteralWireLanding, Get-PfbNestedReferenceWireLanding) and gate every tier on landings.Count instead. The same invariant now holds for the two sub-forms inside the nested-reference tier. Finding 2 (major). Resolve-PfbWireLandingArbitration merged candidate components with -ne, which is case-insensitive for strings: two landings differing only in the case of a wire key or an endpoint were judged to agree and the first one the parser reached was published, with no clash reported. Compare with [string]::Equals(..., Ordinal), matching the ordinal List[string].Contains distinctness tests in Get-PfbRequestRoleForVariable. Dropped the matching ToUpperInvariant on the method so the tracer reports what the source says rather than a literal that appears nowhere in it. Minor. Corrected the comment on the -isnot [CommandParameterAst] argument-binding guard, which claimed to prevent a misread that every downstream branch already re-validates away; it is kept for correctness at the point of binding, not because a test covers it. Added the parse assertion test-bed rule 1 asks for to the two on-disk fixtures in the issue #99 Describe. Tests. One tier-boundary test per boundary, each carrying a control assertion that the later tier really would have answered; four case-sensitivity tests; and a real-tree enumeration of the whole one-variable-many-keys population, masked instances included, so a future unmasking is a named event rather than a regression from nowhere. No behaviour change on the real tree: all 2168 inventory rows are byte-identical to the previous commit. --- Tests/PfbCmdletParamTools.Tests.ps1 | 265 ++++++++++++++++++++++++++++ tools/lib/PfbCmdletParamTools.ps1 | 145 ++++++++++++--- 2 files changed, 390 insertions(+), 20 deletions(-) diff --git a/Tests/PfbCmdletParamTools.Tests.ps1 b/Tests/PfbCmdletParamTools.Tests.ps1 index 927fa5dc..601720df 100644 --- a/Tests/PfbCmdletParamTools.Tests.ps1 +++ b/Tests/PfbCmdletParamTools.Tests.ps1 @@ -1524,6 +1524,16 @@ function New-PfbFixtureConditionalLiteral { } '@ + # Get-PfbCmdletParameterInventory DISCARDS its parse errors, so an unparseable fixture + # would simply contribute no rows and every negative assertion below would pass for + # the wrong reason. Assert parseability up front rather than inferring it from a + # confusing "record is null" failure downstream. + foreach ($fixture in @(Get-ChildItem -Path $script:condDir -Filter '*.ps1' -File)) { + $tokens = $null; $errs = $null + [System.Management.Automation.Language.Parser]::ParseFile($fixture.FullName, [ref]$tokens, [ref]$errs) | Out-Null + @($errs).Count | Should -Be 0 -Because "$($fixture.Name) must parse for the inventory to see it at all" + } + $script:condInventory = Get-PfbCmdletParameterInventory -PublicDirectory $script:condDir } @@ -2342,4 +2352,259 @@ Describe 'Real-tree characterization of the argument-proven role (issue #141 Tas $record.Method | Should -BeNullOrEmpty $record.Endpoint | Should -BeNullOrEmpty } + + It 'enumerates the whole one-variable-many-keys population, masked instances included' { + # The OTHER shape that makes arbitration withdraw a fact: one parameter written to two + # or more distinct literal wire keys on ONE payload variable. Those candidates cannot + # agree on WireName, so the resolution is refused outright. + # + # This test deliberately enumerates the MASKED instances as well -- a multi-key write + # into a variable that has no provable request role today is inert, but it becomes a + # withdrawn row the moment anything gives that variable a role (a new call site, a + # nested-reference idiom the resolver learns). Listing only the role-bearing instance + # would make such an unmasking look like a regression appearing from nowhere. Pinning + # both classes makes it a known, named event: the diff on this list is the notice. + # + # The classification is computed, never hard-coded -- only the resulting inventory is + # asserted, and nothing in tools/lib knows any of these names. + $population = [System.Collections.Generic.List[string]]::new() + + foreach ($name in $script:realFunctions.Keys) { + $funcAst = $script:realFunctions[$name] + if (-not $funcAst.Body.ParamBlock) { continue } + $parameters = @($funcAst.Body.ParamBlock.Parameters | ForEach-Object { $_.Name.VariablePath.UserPath }) + if ($parameters.Count -eq 0) { continue } + + # "|" -> distinct literal wire keys that parameter is written to + $keysByCarrier = @{} + + foreach ($assignment in $funcAst.FindAll({ param($n) $n -is [System.Management.Automation.Language.AssignmentStatementAst] }, $true)) { + $writes = [System.Collections.Generic.List[object]]::new() + + $index = $assignment.Left -as [System.Management.Automation.Language.IndexExpressionAst] + if ($index) { + $target = $index.Target -as [System.Management.Automation.Language.VariableExpressionAst] + $key = $index.Index -as [System.Management.Automation.Language.StringConstantExpressionAst] + if ($target -and $key) { + $writes.Add([PSCustomObject]@{ Variable = $target.VariablePath.UserPath; WireName = $key.Value; Value = $assignment.Right }) + } + } + else { + $target = $assignment.Left -as [System.Management.Automation.Language.VariableExpressionAst] + $hashtable = (Resolve-PfbSingleExpression -Ast $assignment.Right) -as [System.Management.Automation.Language.HashtableAst] + if ($target -and $hashtable) { + foreach ($pair in $hashtable.KeyValuePairs) { + $key = $pair.Item1 -as [System.Management.Automation.Language.StringConstantExpressionAst] + if ($key) { + $writes.Add([PSCustomObject]@{ Variable = $target.VariablePath.UserPath; WireName = $key.Value; Value = $pair.Item2 }) + } + } + } + } + + foreach ($write in $writes) { + foreach ($parameter in $parameters) { + # Both boolean-like readings are tried because the carrier shape, not the + # parameter's type, is what this test is enumerating. + $isParameter = (Test-PfbWireValueIsParameter -ValueAst $write.Value -ParameterName $parameter) -or + (Test-PfbWireValueIsParameter -ValueAst $write.Value -ParameterName $parameter -IsBooleanLikeParameter) + if (-not $isParameter) { continue } + + $carrier = '{0}|{1}' -f $parameter, $write.Variable + if (-not $keysByCarrier.ContainsKey($carrier)) { + $keysByCarrier[$carrier] = [System.Collections.Generic.List[string]]::new() + } + if (-not $keysByCarrier[$carrier].Contains($write.WireName)) { + $keysByCarrier[$carrier].Add($write.WireName) + } + } + } + } + + foreach ($carrier in $keysByCarrier.Keys) { + if ($keysByCarrier[$carrier].Count -lt 2) { continue } + $parts = $carrier -split '\|', 2 + $role = Get-PfbRequestRoleForVariable -FunctionAst $funcAst -TargetVariable $parts[1] + $class = if ($role) { 'ROLE-BEARING' } else { 'MASKED' } + $population.Add(('{0} {1}|{2}|{3} keys={4}' -f $class, $name, $parts[0], $parts[1], + (($keysByCarrier[$carrier] | Sort-Object) -join ','))) + } + } + + @($population | Sort-Object) | Should -Be @( + 'MASKED Update-PfbFileSystem|NfsEnabled|nfsBody keys=v3_enabled,v4_1_enabled' + 'ROLE-BEARING Update-PfbBucketAuditFilter|BucketName|queryParams keys=bucket_names,names' + ) + } +} + +Describe 'An abstention is sticky at EVERY tier boundary (issue #141 Task 3)' { + # Get-PfbWireNameForParameter consults four tiers in a fixed precedence: index assignment, + # hashtable literal, nested reference, then the Add-PfbCommonQueryParams helper. The + # invariant is that a tier which FINDS candidate landings and then abstains must end the + # resolution -- it must not fall through and let a weaker idiom answer, because the weaker + # idiom's answer would be exactly the arbitrary pick the abstention exists to refuse. + # + # This has to be tested per BOUNDARY, not once. An earlier revision implemented it at the + # tier-1 boundary alone and gated the other three on the truthiness of the arbitrated + # answer, which cannot tell "found nothing" from "found landings and abstained"; the + # single boundary that was covered was the single boundary that worked. Each test below + # therefore carries a CONTROL assertion proving the later tier really would have answered + # -- without it the test would pass on a fixture where nothing resolves for any reason. + + It 'stops at the LITERAL tier and does not fall through to the nested-reference tier' { + $funcAst = Get-PfbRoleFixtureAst @( + 'function Test-Fixture {' + ' param([string]$Zeta)' + ' $q = @{ ''alpha'' = $Zeta; ''beta'' = $Zeta }' + ' $q[''owner''] = @{ name = $Zeta }' + ' Invoke-PfbApiRequest -Method PATCH -Endpoint ''widgets'' -QueryParams $q' + '}' + ) + + # Control: the literal tier really does find two role-bearing landings and abstain... + @(Get-PfbHashtableLiteralWireLanding -FunctionAst $funcAst -ParameterName 'Zeta').Count | Should -Be 2 + Get-PfbHashtableLiteralWireNameForParameter -FunctionAst $funcAst -ParameterName 'Zeta' | Should -BeNullOrEmpty + # ...and the nested tier really would answer if it were reached. + (Get-PfbNestedReferenceWireNameForParameter -FunctionAst $funcAst -ParameterName 'Zeta').WireName | Should -Be 'owner' + + Get-PfbWireNameForParameter -FunctionAst $funcAst -ParameterName 'Zeta' | Should -BeNullOrEmpty + } + + It 'stops at the LITERAL tier and does not fall through to the helper tier' { + $funcAst = Get-PfbRoleFixtureAst @( + 'function Test-Fixture {' + ' param([string]$Filter)' + ' $q = @{ ''alpha'' = $Filter; ''beta'' = $Filter }' + ' Add-PfbCommonQueryParams -Into $q -BoundParameters $PSBoundParameters' + ' Invoke-PfbApiRequest -Method GET -Endpoint ''widgets'' -QueryParams $q' + '}' + ) + + @(Get-PfbHashtableLiteralWireLanding -FunctionAst $funcAst -ParameterName 'Filter').Count | Should -Be 2 + Get-PfbHashtableLiteralWireNameForParameter -FunctionAst $funcAst -ParameterName 'Filter' | Should -BeNullOrEmpty + (Get-PfbCommonQueryParamHelperWireName -FunctionAst $funcAst -ParameterName 'Filter').WireName | Should -Be 'filter' + + Get-PfbWireNameForParameter -FunctionAst $funcAst -ParameterName 'Filter' | Should -BeNullOrEmpty + } + + It 'stops at the NESTED-REFERENCE tier and does not fall through to the helper tier' { + $funcAst = Get-PfbRoleFixtureAst @( + 'function Test-Fixture {' + ' param([string]$Filter)' + ' $q = @{}' + ' $q[''owner''] = @{ name = $Filter }' + ' $q[''creator''] = @{ name = $Filter }' + ' Add-PfbCommonQueryParams -Into $q -BoundParameters $PSBoundParameters' + ' Invoke-PfbApiRequest -Method GET -Endpoint ''widgets'' -QueryParams $q' + '}' + ) + + @(Get-PfbNestedReferenceWireLanding -FunctionAst $funcAst -ParameterName 'Filter').Count | Should -Be 2 + Get-PfbNestedReferenceWireNameForParameter -FunctionAst $funcAst -ParameterName 'Filter' | Should -BeNullOrEmpty + (Get-PfbCommonQueryParamHelperWireName -FunctionAst $funcAst -ParameterName 'Filter').WireName | Should -Be 'filter' + + Get-PfbWireNameForParameter -FunctionAst $funcAst -ParameterName 'Filter' | Should -BeNullOrEmpty + } + + It 'stops at the nested INDEX sub-form and does not fall through to the nested LITERAL sub-form' { + # The nested-reference tier has two sub-forms with their own precedence, so the same + # invariant has to hold one level down as well. + $source = @( + 'function Test-Fixture {' + ' param([string]$Zeta)' + ' $q = @{ ''gamma'' = @{ name = $Zeta } }' + 'BODY' + ' Invoke-PfbApiRequest -Method PATCH -Endpoint ''widgets'' -QueryParams $q' + '}' + ) + + # Control: with the index sub-form absent, the literal sub-form answers 'gamma'. + $controlAst = Get-PfbRoleFixtureAst ($source | Where-Object { $_ -ne 'BODY' }) + (Get-PfbNestedReferenceWireNameForParameter -FunctionAst $controlAst -ParameterName 'Zeta').WireName | Should -Be 'gamma' + (Get-PfbWireNameForParameter -FunctionAst $controlAst -ParameterName 'Zeta').WireName | Should -Be 'gamma' + + $funcAst = Get-PfbRoleFixtureAst ($source | ForEach-Object { + if ($_ -eq 'BODY') { ' $q[''owner''] = @{ name = $Zeta }'; ' $q[''creator''] = @{ name = $Zeta }' } else { $_ } + }) + Get-PfbNestedReferenceWireNameForParameter -FunctionAst $funcAst -ParameterName 'Zeta' | Should -BeNullOrEmpty + Get-PfbWireNameForParameter -FunctionAst $funcAst -ParameterName 'Zeta' | Should -BeNullOrEmpty + } +} + +Describe 'Landing components are compared ORDINALLY (issue #141 Task 3)' { + # PowerShell's -ne is case-INSENSITIVE for strings. A component-wise merge written with it + # judges 'names' and 'Names' to agree and then publishes whichever the parser reached + # first, which is the source-order artefact the arbitration exists to eliminate -- and it + # does so silently, because no clash is ever detected. Wire keys, HTTP methods and + # endpoints are all case-sensitive on the wire. + + It 'refuses the resolution when two landings differ only in the CASE of the wire key ()' -ForEach @( + @{ Order = 'lower first'; First = 'names'; Second = 'Names' } + @{ Order = 'upper first'; First = 'Names'; Second = 'names' } + ) { + $funcAst = Get-PfbRoleFixtureAst @( + 'function Test-Fixture {' + ' param([string]$Zeta)' + ' $q = @{}' + (' $q[''{0}''] = $Zeta' -f $First) + (' $q[''{0}''] = $Zeta' -f $Second) + ' Invoke-PfbApiRequest -Method PATCH -Endpoint ''widgets'' -QueryParams $q' + '}' + ) + Get-PfbWireNameForParameter -FunctionAst $funcAst -ParameterName 'Zeta' | Should -BeNullOrEmpty + } + + It 'nulls the operation when two landings differ only in the CASE of the endpoint ()' -ForEach @( + @{ Order = 'capitalised first'; First = 'Widgets'; Second = 'widgets' } + @{ Order = 'lower first'; First = 'widgets'; Second = 'Widgets' } + ) { + $funcAst = Get-PfbRoleFixtureAst @( + 'function Test-Fixture {' + ' param([string]$Zeta)' + ' $first = @{}' + ' $first[''alpha''] = $Zeta' + ' $second = @{}' + ' $second[''alpha''] = $Zeta' + (' Invoke-PfbApiRequest -Method PATCH -Endpoint ''{0}'' -QueryParams $first' -f $First) + (' Invoke-PfbApiRequest -Method PATCH -Endpoint ''{0}'' -QueryParams $second' -f $Second) + '}' + ) + $wire = Get-PfbWireNameForParameter -FunctionAst $funcAst -ParameterName 'Zeta' + $wire.WireName | Should -Be 'alpha' + $wire.WireSurface | Should -Be 'Query' + $wire.Endpoint | Should -BeNullOrEmpty + $wire.Method | Should -BeNullOrEmpty + } + + It 'treats two calls whose -Method differs only in case as DIFFERENT operations' { + # Guards the deliberate absence of case folding in Get-PfbRequestRoleForVariable: + # folding would merge these two calls into one operation and name it. + $funcAst = Get-PfbRoleFixtureAst @( + 'function Test-Fixture {' + ' param([string]$Zeta)' + ' $q = @{}' + ' $q[''alpha''] = $Zeta' + ' Invoke-PfbApiRequest -Method PATCH -Endpoint ''widgets'' -QueryParams $q' + ' Invoke-PfbApiRequest -Method patch -Endpoint ''widgets'' -QueryParams $q' + '}' + ) + $role = Get-PfbRequestRoleForVariable -FunctionAst $funcAst -TargetVariable 'q' + $role.WireSurface | Should -Be 'Query' + $role.Method | Should -BeNullOrEmpty + $role.Endpoint | Should -BeNullOrEmpty + } + + It 'reports the method VERBATIM rather than a case-folded literal that appears nowhere in the source' { + $funcAst = Get-PfbRoleFixtureAst @( + 'function Test-Fixture {' + ' param([string]$Zeta)' + ' $q = @{}' + ' $q[''alpha''] = $Zeta' + ' Invoke-PfbApiRequest -Method patch -Endpoint ''widgets'' -QueryParams $q' + '}' + ) + $role = Get-PfbRequestRoleForVariable -FunctionAst $funcAst -TargetVariable 'q' + $role.Method | Should -BeExactly 'patch' + } } diff --git a/tools/lib/PfbCmdletParamTools.ps1 b/tools/lib/PfbCmdletParamTools.ps1 index 4fbbe425..566f6dbb 100644 --- a/tools/lib/PfbCmdletParamTools.ps1 +++ b/tools/lib/PfbCmdletParamTools.ps1 @@ -617,11 +617,24 @@ function Resolve-PfbWireLandingArbitration { $candidates = @($Candidate) if ($candidates.Count -eq 0) { return $null } + # ORDINAL, deliberately. PowerShell's -ne is case-INSENSITIVE for strings, so a + # component-wise merge written with it judges 'names' and 'Names' to agree and then + # publishes whichever the parser reached first -- a source-order artefact of exactly the + # kind this whole function exists to eliminate, and one that would go unnoticed because + # the clash is never reported. It would also put this function at odds with + # Get-PfbRequestRoleForVariable, whose List[string].Contains distinctness tests are + # ordinal: the role tracer and the arbitrator have to mean the same thing by "the same + # string". Wire keys, HTTP methods and endpoints are all case-sensitive on the wire, and + # there being no differing-case pair in Public/ today is the same argument that would + # have justified keeping the name switch. $agreedValue = { param($Property) $first = $candidates[0].$Property foreach ($item in $candidates) { - if ($item.$Property -ne $first) { return $null } + $value = $item.$Property + if ($null -eq $value -and $null -eq $first) { continue } + if ($null -eq $value -or $null -eq $first) { return $null } + if (-not [string]::Equals([string]$value, [string]$first, [System.StringComparison]::Ordinal)) { return $null } } return $first } @@ -705,6 +718,21 @@ function Get-PfbWireNameForParameter { Falling through would let a weaker idiom quietly supply a name for a parameter whose stronger, ambiguous evidence had just been discarded -- which is the first-match failure wearing a different hat. + + That invariant is a property of HOW each tier is consulted, so it has to be + implemented at all four, not just the first. Every tier is asked for its LANDINGS + (Get-PfbHashtableLiteralWireLanding, Get-PfbNestedReferenceWireLanding), and the + decision to answer is made on `landings.Count -gt 0` -- never on the truthiness of an + arbitrated result, which cannot tell "found nothing" from "found landings that + disagreed". An earlier revision of this function got that right for the index tier + and wrong for the other three: a literal tier holding two disagreeing keys returned + $null and the nested tier then published its own key, exactly the guess the tier + order exists to prevent. + + The one abstention that is NOT sticky lives inside + Get-PfbCommonQueryParamHelperWireName, which returns $null when two helper calls + disagree. That is harmless only because the helper tier is last, so its abstention + and its silence have the same consequence: no answer at all. .OUTPUTS $null, or [PSCustomObject]@{ WireName; TargetVariable; WireSurface; Method; Endpoint }. TargetVariable is the payload variable the assignment targeted, or $null when the @@ -751,15 +779,19 @@ function Get-PfbWireNameForParameter { # New-PfbObjectStoreAccount, the whole Policy/*Rule family, ...). Runs after the index # form, not instead of it: a cmdlet routinely does both (literal initializer for its # -Name, then `$body['x'] = $X` lines), and both key sets must resolve. - $literalMatch = Get-PfbHashtableLiteralWireNameForParameter -FunctionAst $FunctionAst -ParameterName $ParameterName -IsBooleanLikeParameter:$IsBooleanLikeParameter - if ($literalMatch) { return $literalMatch } + # + # Every tier below is consulted for its LANDINGS, never for its arbitrated answer. Asking + # `if ($literalMatch)` instead would read an abstention as a miss and fall through, which + # is the whole failure this function exists to prevent -- see the .DESCRIPTION. + $literalLandings = @(Get-PfbHashtableLiteralWireLanding -FunctionAst $FunctionAst -ParameterName $ParameterName -IsBooleanLikeParameter:$IsBooleanLikeParameter) + if ($literalLandings.Count -gt 0) { return (Resolve-PfbWireLandingArbitration -Candidate $literalLandings) } # Third idiom: a nested single-key REFERENCE OBJECT -- `$body['account'] = @{ name = # $Account }` -- whose wire field is the OUTER key. Runs strictly after both direct # forms above so it can only ever add a resolution, never rename one: a parameter that # already resolved via a direct assignment returned before reaching here. - $nestedMatch = Get-PfbNestedReferenceWireNameForParameter -FunctionAst $FunctionAst -ParameterName $ParameterName -IsBooleanLikeParameter:$IsBooleanLikeParameter - if ($nestedMatch) { return $nestedMatch } + $nestedLandings = @(Get-PfbNestedReferenceWireLanding -FunctionAst $FunctionAst -ParameterName $ParameterName -IsBooleanLikeParameter:$IsBooleanLikeParameter) + if ($nestedLandings.Count -gt 0) { return (Resolve-PfbWireLandingArbitration -Candidate $nestedLandings) } # No literal assignment of any shape in this function body -- but the parameter may # still reach the wire through the shared Private/Add-PfbCommonQueryParams.ps1 helper, @@ -794,7 +826,40 @@ function Get-PfbHashtableLiteralWireNameForParameter { so a pipeline transform is still refused rather than guessed at. .OUTPUTS $null, or [PSCustomObject]@{ WireName; TargetVariable; WireSurface; Method; Endpoint } - -- same shape as Get-PfbWireNameForParameter, arbitrated the same way. + -- same shape as Get-PfbWireNameForParameter, arbitrated the same way. Callers that + need to tell "this idiom found nothing" apart from "this idiom found landings and + then abstained" must use Get-PfbHashtableLiteralWireLanding instead: both outcomes + are $null here. + #> + [CmdletBinding()] + param( + [Parameter(Mandatory)] + [System.Management.Automation.Language.FunctionDefinitionAst]$FunctionAst, + + [Parameter(Mandatory)] + [string]$ParameterName, + + [switch]$IsBooleanLikeParameter + ) + + $landings = @(Get-PfbHashtableLiteralWireLanding -FunctionAst $FunctionAst -ParameterName $ParameterName -IsBooleanLikeParameter:$IsBooleanLikeParameter) + if ($landings.Count -eq 0) { return $null } + return Resolve-PfbWireLandingArbitration -Candidate $landings +} + +function Get-PfbHashtableLiteralWireLanding { + <# + .SYNOPSIS + Every hashtable-literal landing for a parameter, unarbitrated. + .DESCRIPTION + Exists so an idiom's ABSTENTION is distinguishable from its silence. An arbitrated + $null means either "no landing" or "landings that disagreed", and + Get-PfbWireNameForParameter must not treat those alike: falling through to a weaker + idiom after a stronger one abstained lets the weaker one publish a wire name for a + parameter whose better evidence was just discarded, which is first-match arbitration + wearing a different hat. + .OUTPUTS + An array, possibly empty, of the landing objects New-PfbWireLanding builds. #> [CmdletBinding()] param( @@ -835,8 +900,7 @@ function Get-PfbHashtableLiteralWireNameForParameter { } } - if ($landings.Count -eq 0) { return $null } - return Resolve-PfbWireLandingArbitration -Candidate $landings.ToArray() + return $landings.ToArray() } function Get-PfbNestedReferenceWireNameForParameter { @@ -879,7 +943,38 @@ function Get-PfbNestedReferenceWireNameForParameter { already-resolved wire name. .OUTPUTS $null, or [PSCustomObject]@{ WireName; TargetVariable; WireSurface; Method; Endpoint } - -- same shape as Get-PfbWireNameForParameter, arbitrated the same way. + -- same shape as Get-PfbWireNameForParameter, arbitrated the same way. As with the + hashtable-literal form, a caller that must tell abstention from silence has to use + Get-PfbNestedReferenceWireLanding. + #> + [CmdletBinding()] + param( + [Parameter(Mandatory)] + [System.Management.Automation.Language.FunctionDefinitionAst]$FunctionAst, + + [Parameter(Mandatory)] + [string]$ParameterName, + + [switch]$IsBooleanLikeParameter + ) + + $landings = @(Get-PfbNestedReferenceWireLanding -FunctionAst $FunctionAst -ParameterName $ParameterName -IsBooleanLikeParameter:$IsBooleanLikeParameter) + if ($landings.Count -eq 0) { return $null } + return Resolve-PfbWireLandingArbitration -Candidate $landings +} + +function Get-PfbNestedReferenceWireLanding { + <# + .SYNOPSIS + Every nested-reference landing for a parameter, unarbitrated -- see + Get-PfbHashtableLiteralWireLanding for why the unarbitrated form exists. + .DESCRIPTION + Two sub-forms in a fixed order, the index form then the literal-initializer form, + mirroring the order Get-PfbWireNameForParameter uses for the direct shapes. The + literal sub-form is consulted only when the index sub-form found NOTHING, so an index + sub-form that found landings owns the answer even if those landings then disagree. + .OUTPUTS + An array, possibly empty, of the landing objects New-PfbWireLanding builds. #> [CmdletBinding()] param( @@ -913,10 +1008,6 @@ function Get-PfbNestedReferenceWireNameForParameter { $node -is [System.Management.Automation.Language.AssignmentStatementAst] }, $true)) - # Index form first, then the literal-initializer form, mirroring the order - # Get-PfbWireNameForParameter uses for the direct shapes. Each form collects all of its - # landings and arbitrates them together; the literal form is consulted only when the - # index form proved nothing at all. $indexLandings = [System.Collections.Generic.List[object]]::new() foreach ($assign in $assignments) { @@ -934,7 +1025,7 @@ function Get-PfbNestedReferenceWireNameForParameter { } } - if ($indexLandings.Count -gt 0) { return (Resolve-PfbWireLandingArbitration -Candidate $indexLandings.ToArray()) } + if ($indexLandings.Count -gt 0) { return $indexLandings.ToArray() } $literalLandings = [System.Collections.Generic.List[object]]::new() @@ -956,8 +1047,7 @@ function Get-PfbNestedReferenceWireNameForParameter { } } - if ($literalLandings.Count -eq 0) { return $null } - return Resolve-PfbWireLandingArbitration -Candidate $literalLandings.ToArray() + return $literalLandings.ToArray() } function Find-PfbAccumulatorVariable { @@ -1109,9 +1199,17 @@ function Get-PfbRequestRoleForVariable { if (-not $el) { continue } # `-Name:$value` carries its argument on the parameter itself; `-Name $value` - # carries it in the next element -- but only if that element is not itself a - # parameter, which is how `-QueryParams -AutoPaginate` and a trailing - # `-QueryParams` with nothing after it are kept from binding a non-argument. + # carries it in the next element. + # + # The `-isnot [CommandParameterAst]` test below stops `-QueryParams -AutoPaginate` + # from binding the following SWITCH as an argument. Be aware that it is currently + # unobservable and cannot be mutation-killed: every branch that consumes $arg + # re-validates it (`-as [VariableExpressionAst]`, `-is [StringConstant...]`), and a + # CommandParameterAst fails all of them, so deleting this test changes no result. + # It is kept because it makes the binding rule correct AT THE POINT the argument + # is chosen rather than by luck downstream -- the next branch added here would + # otherwise inherit a bug none of the existing tests can see. Do not read its + # presence as evidence that a test covers it. $arg = $el.Argument if (-not $arg -and ($i + 1) -lt $elements.Count) { $nextElement = $elements[$i + 1] @@ -1159,11 +1257,18 @@ function Get-PfbRequestRoleForVariable { # An empty string is the sentinel for 'this landing's operation could not be read'. It # participates in the distinctness test like any other value, which is what stops a # readable call from speaking for an unreadable one. + # + # The method is NOT case-folded on the way in. Folding would report a `-Method 'get'` as + # 'GET' -- a literal that appears nowhere in the source -- and would quietly merge two + # operations this function is supposed to be able to tell apart. List[string].Contains is + # ordinal, so differing case reads as differing operations, which matches the ordinal + # comparison Resolve-PfbWireLandingArbitration uses. Every -Method argument in Public/ is + # upper case today (all 544 of them), so this costs nothing and forecloses a guess. $distinctOperations = [System.Collections.Generic.List[string]]::new() foreach ($landing in $landings) { $operation = '' if ($landing.Method -and $landing.Endpoint) { - $operation = '{0}|{1}' -f $landing.Method.ToUpperInvariant(), $landing.Endpoint + $operation = '{0}|{1}' -f $landing.Method, $landing.Endpoint } if (-not $distinctOperations.Contains($operation)) { $distinctOperations.Add($operation) } } From 1fcab556e4ec1c87fdb11057ceeccd8ee344d859 Mon Sep 17 00:00:00 2001 From: Justin Emerson Date: Wed, 26 Aug 2026 18:25:16 -0700 Subject: [PATCH 07/29] test(tools): kill the surviving nested-literal abstention mutant The Task 3 re-review found one surviving mutant: returning $literalLandings[0] instead of arbitrating over the whole array leaves the suite green at 192/0/0. Not an equivalent mutant -- on two disagreeing outer keys the resolver correctly abstains and the mutant publishes the first one, which is first-match arbitration alive in the code path fix round 1 restructured. The existing sibling test cannot reach it: the nested tier consults its INDEX sub-form first, so with any index assignment present the literal sub-form never answers. The new fixture has no index assignment at all, making the literal sub-form the only producer. Carries a control assertion -- one outer key alone resolves to 'gamma' -- so the refusal is the disagreement talking rather than an inert fixture. Scoped Pester: 193 passed / 0 failed / 0 skipped, both editions. Mutant re-applied and confirmed KILLED in both editions, by this test alone (192/1). Co-Authored-By: Claude Opus 5 (1M context) --- Tests/PfbCmdletParamTools.Tests.ps1 | 35 +++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/Tests/PfbCmdletParamTools.Tests.ps1 b/Tests/PfbCmdletParamTools.Tests.ps1 index 601720df..ed29e2e6 100644 --- a/Tests/PfbCmdletParamTools.Tests.ps1 +++ b/Tests/PfbCmdletParamTools.Tests.ps1 @@ -2530,6 +2530,41 @@ Describe 'An abstention is sticky at EVERY tier boundary (issue #141 Task 3)' { Get-PfbNestedReferenceWireNameForParameter -FunctionAst $funcAst -ParameterName 'Zeta' | Should -BeNullOrEmpty Get-PfbWireNameForParameter -FunctionAst $funcAst -ParameterName 'Zeta' | Should -BeNullOrEmpty } + + It 'abstains when the nested LITERAL sub-form alone finds two disagreeing landings' { + # The sibling test above proves the INDEX sub-form does not fall through to the LITERAL + # one. It cannot prove the literal sub-form ARBITRATES rather than taking its first + # landing, because the index sub-form answers before the literal one is ever reached. + # With no index assignment at all the literal sub-form is the only producer, and + # returning $literalLandings[0] instead of arbitrating over the whole array survives + # every other test in this file -- measured as a surviving mutant in review, on exactly + # this fixture. + $source = @( + 'function Test-Fixture {' + ' param([string]$Zeta)' + ' $q = @{ ''gamma'' = @{ name = $Zeta }; ''delta'' = @{ name = $Zeta } }' + ' Invoke-PfbApiRequest -Method PATCH -Endpoint ''widgets'' -QueryParams $q' + '}' + ) + + # Control: one outer key alone resolves, so this fixture shape is capable of answering + # and the refusal below is the disagreement talking, not an inert fixture. + $controlAst = Get-PfbRoleFixtureAst @( + 'function Test-Fixture {' + ' param([string]$Zeta)' + ' $q = @{ ''gamma'' = @{ name = $Zeta } }' + ' Invoke-PfbApiRequest -Method PATCH -Endpoint ''widgets'' -QueryParams $q' + '}' + ) + (Get-PfbNestedReferenceWireNameForParameter -FunctionAst $controlAst -ParameterName 'Zeta').WireName | + Should -Be 'gamma' + + # Both outer keys are real landings on the same variable, role and operation, differing + # only in the wire name -- so no name is provable and the whole resolution is refused. + $funcAst = Get-PfbRoleFixtureAst $source + Get-PfbNestedReferenceWireNameForParameter -FunctionAst $funcAst -ParameterName 'Zeta' | Should -BeNullOrEmpty + Get-PfbWireNameForParameter -FunctionAst $funcAst -ParameterName 'Zeta' | Should -BeNullOrEmpty + } } Describe 'Landing components are compared ORDINALLY (issue #141 Task 3)' { From 5018f88231878d0a4abe656c2272f5e3dc977ba3 Mon Sep 17 00:00:00 2001 From: Justin Emerson Date: Wed, 26 Aug 2026 18:26:25 -0700 Subject: [PATCH 08/29] docs(tools): bound the abstention invariant at the function return The re-review noted the .DESCRIPTION asserted the sticky-abstention invariant as a general property. It holds within Get-PfbWireNameForParameter and stops at its return: an abstention is signalled as $null, which is also how silence is signalled, and Get-PfbCmdletParameterInventory retries through Find-PfbAccumulatorVariable on any falsy result. Measured end-to-end: a parameter written to both $q[alpha] and $q[beta] AND fed to an accumulator keyed at $q[names] abstains in the resolver and still emits a Typed row naming names. Latent -- no cmdlet in Public/ has that shape, as neither known multi-key parameter has an accumulator -- and it predates the tier work rather than being introduced by it. Comment only. An over-broad doc claim about this invariant is the same defect the tier fix addressed, one level up. Co-Authored-By: Claude Opus 5 (1M context) --- tools/lib/PfbCmdletParamTools.ps1 | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tools/lib/PfbCmdletParamTools.ps1 b/tools/lib/PfbCmdletParamTools.ps1 index 566f6dbb..3195ffc2 100644 --- a/tools/lib/PfbCmdletParamTools.ps1 +++ b/tools/lib/PfbCmdletParamTools.ps1 @@ -733,6 +733,19 @@ function Get-PfbWireNameForParameter { Get-PfbCommonQueryParamHelperWireName, which returns $null when two helper calls disagree. That is harmless only because the helper tier is last, so its abstention and its silence have the same consequence: no answer at all. + + SCOPE OF THE INVARIANT -- it holds WITHIN this function, and stops at its return. + This function signals an abstention the only way its contract allows, by returning + $null, and $null is also how it signals silence. Its one production caller, + Get-PfbCmdletParameterInventory, retries through Find-PfbAccumulatorVariable whenever + the result is falsy, so an abstention here is read there as "nothing found" and a + fifth source is consulted. Measured: a parameter written to both $q['alpha'] and + $q['beta'] AND fed to an accumulator keyed at $q['names'] abstains here and still + emits a Typed row naming 'names'. No cmdlet in Public/ has that shape today -- neither + known multi-key parameter has an accumulator -- so this is latent, and it predates the + tier work rather than being introduced by it. A caller that must distinguish the two + cases has to consult the ...WireLanding producers directly; do not infer from a $null + here that no landings existed. .OUTPUTS $null, or [PSCustomObject]@{ WireName; TargetVariable; WireSurface; Method; Endpoint }. TargetVariable is the payload variable the assignment targeted, or $null when the From 0fe10f0ff9570c429a40a60590e3cb3875bb192c Mon Sep 17 00:00:00 2001 From: Justin Emerson Date: Wed, 26 Aug 2026 19:47:03 -0700 Subject: [PATCH 09/29] fix(tools): trace payload roles without name gates Integrate issue #141 Task 3's role tracing into the cmdlet parameter inventory and retire the remaining name gates. Resolve-PfbParameterWireLanding now returns { Landings; Resolution } so the inventory can tell an ABSTENTION from SILENCE. Both used to surface as $null, and the Find-PfbAccumulatorVariable retry fired on either -- so a parameter whose own landings disagreed had a fifth source consulted on its behalf and was republished with a confident name. The retry now fires only on Landings.Count -eq 0. Add two non-applicable Surface values, neither of which is a failure to resolve: OutsideStandardRequest (the declaring function issues no Invoke-PfbApiRequest at all) and NotWireParameter (an audited request control that steers the call rather than appearing in it). The latter is an enumerated Cmdlet|Parameter allowlist, never a name pattern -- the suite re-validates every entry against the real AST so a stale one fails loudly, and a fixture pair proves two identically shaped -Eradicate switches classify differently purely by identity. Add tools/Compare-PfbInventoryTuple.ps1 and Compare-PfbInventoryTupleSet: a row-level regression gate over Surface|WireName|WireSurface|Method| Endpoint. Task 3 withdrew Update-PfbBucketAuditFilter -BucketName while the Typed total went UP, and only a hand diff inside a code review caught it. A change passes only when a declaration names its exact before and after; a declaration that matched nothing fails too, so the gate cannot rot into a rubber stamp. Co-Authored-By: Claude Opus 5 (1M context) --- Tests/PfbCmdletParamTools.Tests.ps1 | 725 ++++++++++++++++++ tools/Compare-PfbInventoryTuple.ps1 | 143 ++++ tools/README.md | 11 + .../issue-141-task4.json | 172 +++++ tools/lib/PfbCmdletParamTools.ps1 | 417 ++++++++-- 5 files changed, 1425 insertions(+), 43 deletions(-) create mode 100644 tools/Compare-PfbInventoryTuple.ps1 create mode 100644 tools/inventory-tuple-baselines/issue-141-task4.json diff --git a/Tests/PfbCmdletParamTools.Tests.ps1 b/Tests/PfbCmdletParamTools.Tests.ps1 index ed29e2e6..1c101ebb 100644 --- a/Tests/PfbCmdletParamTools.Tests.ps1 +++ b/Tests/PfbCmdletParamTools.Tests.ps1 @@ -2643,3 +2643,728 @@ Describe 'Landing components are compared ORDINALLY (issue #141 Task 3)' { $role.Method | Should -BeExactly 'patch' } } + +Describe 'Every resolver tier accepts an arbitrary target variable, and only a proven role (issue #141 Task 4, Step 1)' { + # Four tiers, one test each, in two halves. The POSITIVE half proves the tier no longer + # requires the payload variable to be spelled $body/$queryParams: every fixture names it + # $zulu, a word the retired name gate never knew. The NEGATIVE half proves the tier did not + # simply drop the check -- the same $zulu, keyed identically, resolves to nothing when it is + # never handed to a request. Neither half means anything without the other: the positives + # alone pass against a resolver that credits any hashtable, and the negatives alone pass + # against one that credits none. + + It 'resolves the tier through an arbitrarily named payload variable' -ForEach @( + @{ Tier = 'index-assignment'; Expected = 'alpha' + Body = @(' $zulu = @{}', ' $zulu[''alpha''] = $Zeta') } + @{ Tier = 'hashtable-literal-initializer'; Expected = 'alpha' + Body = @(' $zulu = @{ ''alpha'' = $Zeta }') } + @{ Tier = 'nested-single-key-reference'; Expected = 'owner' + Body = @(' $zulu = @{}', ' $zulu[''owner''] = @{ name = $Zeta }') } + @{ Tier = 'Add-PfbCommonQueryParams helper'; Expected = 'names' + Body = @(' $zulu = @{}', ' Add-PfbCommonQueryParams -Into $zulu -BoundParameters $PSBoundParameters -Names $Zeta') } + ) { + $funcAst = Get-PfbRoleFixtureAst (@( + 'function Test-Fixture {' + ' param([string]$Zeta)' + ) + $Body + @( + ' Invoke-PfbApiRequest -Method GET -Endpoint ''widgets'' -QueryParams $zulu' + '}' + )) + $wire = Get-PfbWireNameForParameter -FunctionAst $funcAst -ParameterName 'Zeta' + $wire.WireName | Should -Be $Expected + $wire.TargetVariable | Should -Be 'zulu' + $wire.WireSurface | Should -Be 'Query' + $wire.Endpoint | Should -Be 'widgets' + } + + It 'refuses the tier when the same variable is never handed to a request' -ForEach @( + @{ Tier = 'index-assignment' + Body = @(' $zulu = @{}', ' $zulu[''alpha''] = $Zeta') } + @{ Tier = 'hashtable-literal-initializer' + Body = @(' $zulu = @{ ''alpha'' = $Zeta }') } + @{ Tier = 'nested-single-key-reference' + Body = @(' $zulu = @{}', ' $zulu[''owner''] = @{ name = $Zeta }') } + @{ Tier = 'Add-PfbCommonQueryParams helper' + Body = @(' $zulu = @{}', ' Add-PfbCommonQueryParams -Into $zulu -BoundParameters $PSBoundParameters -Names $Zeta') } + ) { + # Same fixture as the positive above, minus the payload argument. The call itself is + # kept so the function still contains an Invoke-PfbApiRequest -- otherwise this would + # pass for a reason that has nothing to do with the role trace. + $funcAst = Get-PfbRoleFixtureAst (@( + 'function Test-Fixture {' + ' param([string]$Zeta)' + ) + $Body + @( + ' Invoke-PfbApiRequest -Method GET -Endpoint ''widgets''' + '}' + )) + Get-PfbWireNameForParameter -FunctionAst $funcAst -ParameterName 'Zeta' | Should -BeNullOrEmpty + } +} + +Describe 'Required boundary fixtures (issue #141 Task 4, Step 2)' { + + It 'resolves to /' -ForEach @( + # The two boundaries named literally in the plan. Their wire key echoes the parameter + # name, so each is followed by a decoupled twin: on its own, a fixture whose key and + # parameter share a word cannot distinguish reading the source from guessing. + @{ Shape = '$q[''names''] = $Name sent as -QueryParams'; Parameter = 'Name'; Boolean = $false + Body = @(' $q = @{}', ' $q[''names''] = $Name'); PayloadArgument = '-QueryParams $q' + Method = 'GET'; Expected = 'names'; ExpectedSurface = 'Query'; ExpectedTarget = 'q' } + @{ Shape = 'the same shape with key and parameter sharing no word'; Parameter = 'Zeta'; Boolean = $false + Body = @(' $q = @{}', ' $q[''alpha''] = $Zeta'); PayloadArgument = '-QueryParams $q' + Method = 'GET'; Expected = 'alpha'; ExpectedSurface = 'Query'; ExpectedTarget = 'q' } + @{ Shape = '$payload[''enabled''] = [bool]$Enabled sent as -Body'; Parameter = 'Enabled'; Boolean = $true + Body = @(' $payload = @{}', ' $payload[''enabled''] = [bool]$Enabled'); PayloadArgument = '-Body $payload' + Method = 'PATCH'; Expected = 'enabled'; ExpectedSurface = 'Body'; ExpectedTarget = 'payload' } + @{ Shape = 'the same [bool] cast with key and parameter sharing no word'; Parameter = 'Zeta'; Boolean = $true + Body = @(' $payload = @{}', ' $payload[''omega''] = [bool]$Zeta'); PayloadArgument = '-Body $payload' + Method = 'PATCH'; Expected = 'omega'; ExpectedSurface = 'Body'; ExpectedTarget = 'payload' } + # Casing. There is no special case for these spellings anywhere in the resolver any + # more, which is exactly why they are worth a test: they must resolve because they are + # passed to a request argument, not because of how they are written. + @{ Shape = 'a payload variable literally named $QueryParams'; Parameter = 'Zeta'; Boolean = $false + Body = @(' $QueryParams = @{}', ' $QueryParams[''alpha''] = $Zeta'); PayloadArgument = '-QueryParams $QueryParams' + Method = 'GET'; Expected = 'alpha'; ExpectedSurface = 'Query'; ExpectedTarget = 'QueryParams' } + @{ Shape = 'a payload variable literally named $Body'; Parameter = 'Zeta'; Boolean = $false + Body = @(' $Body = @{}', ' $Body[''alpha''] = $Zeta'); PayloadArgument = '-Body $Body' + Method = 'POST'; Expected = 'alpha'; ExpectedSurface = 'Body'; ExpectedTarget = 'Body' } + # The name reversal, one level further out than Task 3 tested it. Task 3 asserted the + # ROLE; this asserts the surface that reaches an inventory row, which is the value a + # consumer actually reads. + @{ Shape = 'a variable named $body sent as -QueryParams'; Parameter = 'Zeta'; Boolean = $false + Body = @(' $body = @{}', ' $body[''alpha''] = $Zeta'); PayloadArgument = '-QueryParams $body' + Method = 'GET'; Expected = 'alpha'; ExpectedSurface = 'Query'; ExpectedTarget = 'body' } + @{ Shape = 'a variable named $queryParams sent as -Body'; Parameter = 'Zeta'; Boolean = $false + Body = @(' $queryParams = @{}', ' $queryParams[''alpha''] = $Zeta'); PayloadArgument = '-Body $queryParams' + Method = 'POST'; Expected = 'alpha'; ExpectedSurface = 'Body'; ExpectedTarget = 'queryParams' } + ) { + $funcAst = Get-PfbRoleFixtureAst (@( + 'function Test-Fixture {' + (' param([{0}]${1})' -f $(if ($Boolean) { 'Nullable[bool]' } else { 'string[]' }), $Parameter) + ) + $Body + @( + (' Invoke-PfbApiRequest -Method {0} -Endpoint ''widgets'' {1}' -f $Method, $PayloadArgument) + '}' + )) + $wire = Get-PfbWireNameForParameter -FunctionAst $funcAst -ParameterName $Parameter -IsBooleanLikeParameter:$Boolean + $wire | Should -Not -BeNullOrEmpty + $wire.WireName | Should -BeExactly $Expected + $wire.WireSurface | Should -Be $ExpectedSurface + $wire.TargetVariable | Should -BeExactly $ExpectedTarget + $wire.Method | Should -Be $Method + $wire.Endpoint | Should -Be 'widgets' + } + + It 'knows the Body role of a parameter handed straight to -Body, and still reports NO wire name for it' { + # The real Set-PfbWorkloadTag -Tags shape. The role is a fact about the whole payload; + # the field-level key is not knowable from it, and the old gate's temptation was to + # invent one from the parameter name. A row with WireName = 'Tags' would be attributed + # against a spec field that does not exist. + $funcAst = Get-PfbRoleFixtureAst @( + 'function Test-Fixture {' + ' param([hashtable]$Tags)' + ' Invoke-PfbApiRequest -Method POST -Endpoint ''widgets'' -Body $Tags' + '}' + ) + + # Control: the role IS provable, so the refusal below is about the missing KEY and not + # about a fixture the role tracer could not read at all. + $role = Get-PfbRequestRoleForVariable -FunctionAst $funcAst -TargetVariable 'Tags' + $role.WireSurface | Should -Be 'Body' + $role.Method | Should -Be 'POST' + $role.Endpoint | Should -Be 'widgets' + + Get-PfbWireNameForParameter -FunctionAst $funcAst -ParameterName 'Tags' | Should -BeNullOrEmpty + } + + It 'does not credit an intermediate sub-body as a top-level payload' { + # $nfsBody is keyed, then nested one level down inside the variable that is actually + # sent. Crediting it would publish 'export_policy' as a top-level field of PATCH + # widgets, which is a field that endpoint does not have. + $funcAst = Get-PfbRoleFixtureAst @( + 'function Test-Fixture {' + ' param([string]$Policy)' + ' $nfsBody = @{}' + ' $nfsBody[''export_policy''] = @{ name = $Policy }' + ' $body = @{}' + ' $body[''nfs''] = $nfsBody' + ' Invoke-PfbApiRequest -Method PATCH -Endpoint ''widgets'' -Body $body' + '}' + ) + + # Control: the OUTER variable does have a proven role here, so this fixture is one the + # role tracer reads successfully -- the refusal is specific to $nfsBody. + (Get-PfbRequestRoleForVariable -FunctionAst $funcAst -TargetVariable 'body').WireSurface | Should -Be 'Body' + + Get-PfbRequestRoleForVariable -FunctionAst $funcAst -TargetVariable 'nfsBody' | Should -BeNullOrEmpty + Get-PfbWireNameForParameter -FunctionAst $funcAst -ParameterName 'Policy' | Should -BeNullOrEmpty + } + + It 'leaves a parameter keyed into an unused local unresolved' { + $funcAst = Get-PfbRoleFixtureAst @( + 'function Test-Fixture {' + ' param([string]$Zeta, [string]$Omega)' + ' $scratch = @{}' + ' $scratch[''alpha''] = $Zeta' + ' $q = @{}' + ' $q[''beta''] = $Omega' + ' Invoke-PfbApiRequest -Method GET -Endpoint ''widgets'' -QueryParams $q' + '}' + ) + + # Control: an identically-shaped assignment into the variable that IS sent resolves, so + # the refusal below is the unused local talking and not an inert fixture. + (Get-PfbWireNameForParameter -FunctionAst $funcAst -ParameterName 'Omega').WireName | Should -Be 'beta' + + Get-PfbWireNameForParameter -FunctionAst $funcAst -ParameterName 'Zeta' | Should -BeNullOrEmpty + } +} + +Describe 'The inventory does not launder an abstention into a name (issue #141 Task 4, Step 3)' { + # Get-PfbWireNameForParameter spends $null twice -- once for "no idiom proved anything" and + # once for "an idiom proved landings that disagreed" -- so a caller that retries on $null + # retries after an abstention too, and consults a FIFTH source on behalf of a parameter + # whose own evidence had just been ruled contradictory. That is the tier stickiness + # escaping through the caller. The inventory now asks Resolve-PfbParameterWireLanding and + # retries only on an empty Landings array. + + BeforeAll { + $script:launderDir = Join-Path $TestDrive 'Task4Launder/Public' + New-Item -ItemType Directory -Path $script:launderDir -Force | Out-Null + + # -Zeta lands on TWO disagreeing keys of the variable that is sent ('alpha', 'beta'), + # AND feeds an accumulator that lands on a third ('names'). Every name in the fixture is + # unrelated to every other, so a row naming any of them can only have come from the AST. + Set-Content -Path (Join-Path $script:launderDir 'Get-PfbFixtureLaundered.ps1') -Encoding UTF8 -Value @' +function Get-PfbFixtureLaundered { + param([string[]]$Zeta) + $allNames = [System.Collections.Generic.List[string]]::new() + $q = @{} + $q['alpha'] = $Zeta + $q['beta'] = $Zeta + foreach ($item in $Zeta) { $allNames.Add($item) } + $q['names'] = $allNames -join ',' + Invoke-PfbApiRequest -Method GET -Endpoint 'widgets' -QueryParams $q +} +'@ + + # The control, and the reason this pair is a test rather than a coincidence: the SAME + # accumulator route, with the two disagreeing direct landings deleted. The retry fires, + # reaches $q['names'], and publishes a confident 'names'. That is precisely the answer + # the fixture above must NOT produce. + Set-Content -Path (Join-Path $script:launderDir 'Get-PfbFixtureLaunderedControl.ps1') -Encoding UTF8 -Value @' +function Get-PfbFixtureLaunderedControl { + param([string[]]$Zeta) + $allNames = [System.Collections.Generic.List[string]]::new() + $q = @{} + foreach ($item in $Zeta) { $allNames.Add($item) } + $q['names'] = $allNames -join ',' + Invoke-PfbApiRequest -Method GET -Endpoint 'widgets' -QueryParams $q +} +'@ + + $script:launderInventory = @(Get-PfbCmdletParameterInventory -PublicDirectory $script:launderDir) + } + + It 'still resolves the accumulator route when the parameter has no direct landings of its own' { + $row = $script:launderInventory | Where-Object { $_.Cmdlet -eq 'Get-PfbFixtureLaunderedControl' } + $row.WireName | Should -Be 'names' + $row.Surface | Should -Be 'Typed' + $row.WireSurface | Should -Be 'Query' + } + + It 'reports a parameter whose own landings disagreed as unresolved, not as the accumulator''s key' { + $row = $script:launderInventory | Where-Object { $_.Cmdlet -eq 'Get-PfbFixtureLaundered' } + $row | Should -Not -BeNullOrEmpty -Because 'an absent row would make every assertion below vacuous' + $row.WireName | Should -BeNullOrEmpty + $row.Surface | Should -Be 'TypedUnresolved' + $row.WireSurface | Should -Be 'Unresolved' + } + + It 'proves the retry route was available to that parameter and was declined on purpose' { + # Without this, the test above would also pass if Find-PfbAccumulatorVariable simply + # failed to see the accumulator -- a refusal for the wrong reason. + $tokens = $null; $errs = $null + $fileAst = [System.Management.Automation.Language.Parser]::ParseFile( + (Join-Path $script:launderDir 'Get-PfbFixtureLaundered.ps1'), [ref]$tokens, [ref]$errs) + @($errs).Count | Should -Be 0 + $funcAst = $fileAst.FindAll({ param($n) $n -is [System.Management.Automation.Language.FunctionDefinitionAst] }, $true) | Select-Object -First 1 + + Find-PfbAccumulatorVariable -FunctionAst $funcAst -ParameterName 'Zeta' | Should -Be 'allNames' + (Get-PfbWireNameForParameter -FunctionAst $funcAst -ParameterName 'allNames').WireName | Should -Be 'names' + + # ...and the resolution really is an ABSTENTION rather than silence: landings were + # found, and they arbitrated to nothing. + $resolved = Resolve-PfbParameterWireLanding -FunctionAst $funcAst -ParameterName 'Zeta' + $resolved.Landings.Count | Should -Be 2 + $resolved.Resolution | Should -BeNullOrEmpty + } +} + +Describe 'The Surface ladder (issue #141 Task 4, Steps 4-5)' { + # Five values, decided in a fixed order. Each fixture below differs from its neighbour in + # exactly one respect, so a test that goes red names the rung that broke. + + BeforeAll { + $script:ladderDir = Join-Path $TestDrive 'Task4Ladder/Public' + New-Item -ItemType Directory -Path $script:ladderDir -Force | Out-Null + + # Zero Invoke-PfbApiRequest calls. -Zeta is even keyed into a hashtable, which the + # resolver still refuses because that hashtable is never sent -- so this also proves + # OutsideStandardRequest is decided by the CALL's absence and not by the parameter + # happening to be unmentioned. + Set-Content -Path (Join-Path $script:ladderDir 'Get-PfbFixtureNoRequest.ps1') -Encoding UTF8 -Value @' +function Get-PfbFixtureNoRequest { + param([string]$Zeta, [switch]$Omega) + $q = @{} + $q['alpha'] = $Zeta + return $q +} +'@ + + # Same unresolvable parameter, twice, differing only in the -Attributes escape hatch. + Set-Content -Path (Join-Path $script:ladderDir 'Get-PfbFixtureLadder.ps1') -Encoding UTF8 -Value @' +function Get-PfbFixtureLadderBare { + param([string]$Zeta) + Invoke-PfbApiRequest -Method GET -Endpoint 'widgets' +} + +function Get-PfbFixtureLadderAttributes { + param([string]$Zeta, [hashtable]$Attributes) + Invoke-PfbApiRequest -Method GET -Endpoint 'widgets' +} +'@ + + # A stand-in for a real allowlisted cmdlet: same NAME, so the audited identity matches, + # but three sibling parameters that are not on the list. -Omega is the load-bearing one + # -- it is a [switch], it is unresolved, and it sits in the same function, so the only + # thing separating it from -Eradicate is the allowlist itself. It also carries the + # -Attributes hatch, which puts NotWireParameter and AttributesOnly in direct + # competition and pins their order. + Set-Content -Path (Join-Path $script:ladderDir 'Remove-PfbBucket.ps1') -Encoding UTF8 -Value @' +function Remove-PfbBucket { + param([string]$Zeta, [switch]$Eradicate, [switch]$Omega, [hashtable]$Attributes) + if (-not $Eradicate) { $q = @{} } + Invoke-PfbApiRequest -Method DELETE -Endpoint 'buckets' +} +'@ + + # The name/identity separation, from the other side. Both switches here carry a name that + # IS on the audited allowlist (-Eradicate, -Force), on a cmdlet that is not. Found by + # mutation: rekeying the allowlist from 'Cmdlet|Parameter' to the bare parameter name left + # every other test in this file green, because no real Public/ cmdlet exposes an + # -Eradicate or -Force that is off the list. That is a property of today's tree, not of + # the resolver, so this fixture supplies the counterexample the tree does not. + Set-Content -Path (Join-Path $script:ladderDir 'Remove-PfbFixtureNotAllowlisted.ps1') -Encoding UTF8 -Value @' +function Remove-PfbFixtureNotAllowlisted { + param([switch]$Eradicate, [switch]$Force, [hashtable]$Attributes) + if (-not $Eradicate) { $q = @{} } + Invoke-PfbApiRequest -Method DELETE -Endpoint 'widgets' +} +'@ + + $script:ladderInventory = @(Get-PfbCmdletParameterInventory -PublicDirectory $script:ladderDir) + function script:Get-PfbLadderSurface { + param([string]$Cmdlet, [string]$Parameter) + $row = $script:ladderInventory | Where-Object { $_.Cmdlet -eq $Cmdlet -and $_.Parameter -eq $Parameter } + if (-not $row) { throw "No inventory row for $Cmdlet -$Parameter; the fixture never reached the resolver." } + return $row.Surface + } + } + + It 'classifies of as ' -ForEach @( + @{ Cmdlet = 'Get-PfbFixtureNoRequest'; Parameter = 'Zeta'; Expected = 'OutsideStandardRequest' } + @{ Cmdlet = 'Get-PfbFixtureNoRequest'; Parameter = 'Omega'; Expected = 'OutsideStandardRequest' } + @{ Cmdlet = 'Get-PfbFixtureLadderBare'; Parameter = 'Zeta'; Expected = 'TypedUnresolved' } + @{ Cmdlet = 'Get-PfbFixtureLadderAttributes'; Parameter = 'Zeta'; Expected = 'AttributesOnly' } + @{ Cmdlet = 'Remove-PfbBucket'; Parameter = 'Eradicate'; Expected = 'NotWireParameter' } + @{ Cmdlet = 'Remove-PfbBucket'; Parameter = 'Omega'; Expected = 'AttributesOnly' } + @{ Cmdlet = 'Remove-PfbBucket'; Parameter = 'Zeta'; Expected = 'AttributesOnly' } + @{ Cmdlet = 'Remove-PfbFixtureNotAllowlisted'; Parameter = 'Eradicate'; Expected = 'AttributesOnly' } + @{ Cmdlet = 'Remove-PfbFixtureNotAllowlisted'; Parameter = 'Force'; Expected = 'AttributesOnly' } + ) { + Get-PfbLadderSurface -Cmdlet $Cmdlet -Parameter $Parameter | Should -Be $Expected + } + + It 'keys NotWireParameter on the audited Cmdlet|Parameter identity, never on the parameter name' { + # The pair below is the whole point: two -Eradicate switches, identical in shape, in + # functions that differ only by name, classified differently. The allowlist is the only + # thing that can produce that difference, so a name-keyed allowlist cannot pass this. + Get-PfbLadderSurface -Cmdlet 'Remove-PfbBucket' -Parameter 'Eradicate' | Should -Be 'NotWireParameter' + Get-PfbLadderSurface -Cmdlet 'Remove-PfbFixtureNotAllowlisted' -Parameter 'Eradicate' | Should -Be 'AttributesOnly' + # And the names really are shared with the audited list, or the pair proves nothing. + $allowlistedNames = @(Get-PfbNotWireParameterAllowlist | ForEach-Object { ($_ -split '\|')[1] } | Sort-Object -Unique) + $allowlistedNames | Should -Contain 'Eradicate' + $allowlistedNames | Should -Contain 'Force' + @(Get-PfbNotWireParameterAllowlist) | Should -Not -Contain 'Remove-PfbFixtureNotAllowlisted|Eradicate' + @(Get-PfbNotWireParameterAllowlist) | Should -Not -Contain 'Remove-PfbFixtureNotAllowlisted|Force' + } + + It 'emits nothing outside the declared set of Surface values' { + $declared = @(Get-PfbParameterSurfaceName) + $declared.Count | Should -Be 5 + foreach ($row in $script:ladderInventory) { $declared | Should -Contain $row.Surface } + } + + It 'gives a non-applicable row no wire facts to be misread as data' { + foreach ($row in ($script:ladderInventory | Where-Object { $_.Surface -in @('NotWireParameter', 'OutsideStandardRequest') })) { + $row.WireName | Should -BeNullOrEmpty + $row.WireSurface | Should -Be 'Unresolved' + $row.Endpoint | Should -BeNullOrEmpty + $row.Method | Should -BeNullOrEmpty + } + } +} + +Describe 'Real-tree acceptance properties of the integrated resolver (issue #141 Task 4, Steps 5 and 10)' { + # Every input set here is derived at RUN TIME from the real Public/ tree, and no assertion + # pins a row count: a count would go stale on the next cmdlet added, or -- worse -- keep + # passing while the rows underneath it changed. + + BeforeAll { + $script:t4Functions = @{} + foreach ($file in @(Get-ChildItem -Path $script:publicDir -Filter '*.ps1' -Recurse -File)) { + $tokens = $null; $errs = $null + $fileAst = [System.Management.Automation.Language.Parser]::ParseFile($file.FullName, [ref]$tokens, [ref]$errs) + @($errs).Count | Should -Be 0 -Because "$($file.FullName) must parse for its functions to be analysable at all" + foreach ($fn in $fileAst.FindAll({ param($n) $n -is [System.Management.Automation.Language.FunctionDefinitionAst] }, $true)) { + $script:t4Functions[$fn.Name] = $fn + } + } + $script:t4Inventory = @(Get-PfbCmdletParameterInventory -PublicDirectory $script:publicDir) + $script:t4Rows = @{} + foreach ($row in $script:t4Inventory) { $script:t4Rows['{0}|{1}' -f $row.Cmdlet, $row.Parameter] = $row } + + function script:Get-PfbT4Row { + param([string]$Identity) + $row = $script:t4Rows[$Identity] + if (-not $row) { throw "No inventory row for '$Identity'. The identity is stale, and every assertion over it would be vacuous." } + return $row + } + } + + Context 'the audited NotWireParameter allowlist, re-validated against the AST (Step 5)' { + # The allowlist is the one place in the resolver where a fact is asserted by a human + # rather than read from the source, so it is the one place that can rot silently. Each + # assertion below is a separate way for a stale entry to fail. + + It 'contains exactly the six identities enumerated below and nothing else' { + # Pester resolves -ForEach at DISCOVERY time, before the root BeforeAll dot-sources + # the library, so the per-entry cases below cannot be generated from the allowlist + # itself -- they are restated by hand. This test is what keeps the restatement + # honest: an entry added to or removed from the resolver without a matching case + # here fails, rather than quietly going unvalidated. + @(Get-PfbNotWireParameterAllowlist) | Sort-Object -Culture '' | Should -Be @( + 'Remove-PfbBucket|Eradicate' + 'Remove-PfbFileSystem|Eradicate' + 'Remove-PfbFileSystemSession|Force' + 'Remove-PfbFileSystemSnapshot|Eradicate' + 'Remove-PfbRealm|Eradicate' + 'Remove-PfbServer|Eradicate' + ) + } + + It 'still describes a real, boolean-like, keyless request control: ' -ForEach @( + @{ Identity = 'Remove-PfbBucket|Eradicate' } + @{ Identity = 'Remove-PfbFileSystem|Eradicate' } + @{ Identity = 'Remove-PfbFileSystemSession|Force' } + @{ Identity = 'Remove-PfbFileSystemSnapshot|Eradicate' } + @{ Identity = 'Remove-PfbRealm|Eradicate' } + @{ Identity = 'Remove-PfbServer|Eradicate' } + ) { + $parts = $Identity -split '\|', 2 + $cmdletName = $parts[0] + $parameterName = $parts[1] + + $funcAst = $script:t4Functions[$cmdletName] + $funcAst | Should -Not -BeNullOrEmpty -Because "the allowlist names $cmdletName, which must still exist in Public/" + + $p = @($funcAst.Body.ParamBlock.Parameters | Where-Object { $_.Name.VariablePath.UserPath -eq $parameterName }) + $p.Count | Should -Be 1 -Because "$cmdletName must still declare exactly one -$parameterName" + + # Boolean-like: the same test the inventory applies, not a name pattern. + $p[0].StaticType | Should -BeIn @([System.Management.Automation.SwitchParameter], [bool], [System.Nullable[bool]]) + + # No resolved wire key, and no landings at all -- an entry that acquires one must + # fail here rather than being silently shadowed by the 'Typed' rung above it. + $resolved = Resolve-PfbParameterWireLanding -FunctionAst $funcAst -ParameterName $parameterName -IsBooleanLikeParameter + $resolved.Landings.Count | Should -Be 0 + $resolved.Resolution | Should -BeNullOrEmpty + + # Not a direct payload variable either (the -Body $Tags shape). + Get-PfbRequestRoleForVariable -FunctionAst $funcAst -TargetVariable $parameterName | Should -BeNullOrEmpty + + # The cmdlet must still be ON the standard request path. If it stopped making any + # Invoke-PfbApiRequest call, OutsideStandardRequest would claim this row first and + # the allowlist entry would be dead code that no test ever exercised again. + Test-PfbFunctionMakesStandardRequest -FunctionAst $funcAst | Should -BeTrue + + # ...and it still has the documented CONTROL shape: the parameter steers a branch. + # This is what separates an audited control from a parameter that merely failed to + # resolve, and it is the check that fails if someone adds an entry on the strength + # of its name alone. + $conditions = @($funcAst.FindAll({ param($n) $n -is [System.Management.Automation.Language.IfStatementAst] }, $true) | + ForEach-Object { $_.Clauses } | ForEach-Object { $_.Item1 }) + $referenced = @($conditions | Where-Object { + @($_.FindAll({ + param($n) + $n -is [System.Management.Automation.Language.VariableExpressionAst] -and + $n.VariablePath.UserPath -eq $parameterName + }, $true)).Count -gt 0 + }) + $referenced.Count | Should -BeGreaterThan 0 -Because "-$parameterName must still gate a branch of $cmdletName to be a request control" + + (Get-PfbT4Row -Identity $Identity).Surface | Should -Be 'NotWireParameter' + } + + It 'is the ONLY source of NotWireParameter rows -- nothing is inferred from failure to resolve' { + $allowlist = @(Get-PfbNotWireParameterAllowlist) + $emitted = @($script:t4Inventory | + Where-Object { $_.Surface -eq 'NotWireParameter' } | + ForEach-Object { '{0}|{1}' -f $_.Cmdlet, $_.Parameter } | + Sort-Object -Culture '') + $emitted | Should -Be (@($allowlist) | Sort-Object -Culture '') + } + + It 'does not claim , which is a real wire-affecting parameter' -ForEach @( + # The five body-affecting New-PfbFileSystem switches. Every one of them is + # boolean-like and Remove-Pfb*-adjacent in shape, so a rule keyed on "[switch] that + # did not resolve" -- the error this task exists to avoid repeating -- would sweep + # them up. + @{ Identity = 'New-PfbFileSystem|Writable' } + @{ Identity = 'New-PfbFileSystem|SafeguardAcls' } + @{ Identity = 'New-PfbFileSystem|SnapshotDirectoryEnabled' } + @{ Identity = 'New-PfbFileSystem|FastRemoveDirectoryEnabled' } + @{ Identity = 'New-PfbFileSystem|SmbContinuousAvailabilityEnabled' } + # Two non-boolean shapes that are genuinely unresolved today, which is the state + # closest to NotWireParameter and therefore the one most likely to be confused with + # it. Deliberately NOT Get-PfbUserGroupQuotaPolicy -Name/-Id: Task 2 made those + # 'Typed', so asserting they are not NotWireParameter exercises nothing. + @{ Identity = 'New-PfbFileSystemSnapshot|SourceName' } + @{ Identity = 'Set-PfbWorkloadTag|Tags' } + ) { + (Get-PfbT4Row -Identity $Identity).Surface | Should -Not -Be 'NotWireParameter' + } + + It 'claims no boolean-like parameter of New-PfbFileSystem at all' { + # The named five above go stale if the cmdlet is refactored; this one cannot. + $funcAst = $script:t4Functions['New-PfbFileSystem'] + $funcAst | Should -Not -BeNullOrEmpty + $booleanLike = @($funcAst.Body.ParamBlock.Parameters | Where-Object { + $_.StaticType -in @([System.Management.Automation.SwitchParameter], [bool], [System.Nullable[bool]]) + } | ForEach-Object { $_.Name.VariablePath.UserPath }) + $booleanLike.Count | Should -BeGreaterThan 0 -Because 'an empty set would make this assertion vacuous' + foreach ($name in $booleanLike) { + (Get-PfbT4Row -Identity ('New-PfbFileSystem|{0}' -f $name)).Surface | Should -Not -Be 'NotWireParameter' + } + } + } + + Context 'the three acceptance populations (Step 10)' { + + It 'resolves the $var.ToArray() helper pair to its declared query keys' { + # Get-PfbUserGroupQuotaPolicy hands two [List[string]] accumulators to + # Add-PfbCommonQueryParams as $allNames.ToArray()/$allIds.ToArray(). Both keys come + # from the helper's own mapping, not from the parameter names. + $name = Get-PfbT4Row -Identity 'Get-PfbUserGroupQuotaPolicy|Name' + $name.WireName | Should -BeExactly 'names' + $name.Surface | Should -Be 'Typed' + $name.WireSurface | Should -Be 'Query' + + $id = Get-PfbT4Row -Identity 'Get-PfbUserGroupQuotaPolicy|Id' + $id.WireName | Should -BeExactly 'ids' + $id.Surface | Should -Be 'Typed' + $id.WireSurface | Should -Be 'Query' + + # Control: this really is the .ToArray() shape and not a plain helper argument that + # would resolve with the arity guard deleted. + $funcAst = $script:t4Functions['Get-PfbUserGroupQuotaPolicy'] + $toArrayCalls = @($funcAst.FindAll({ + param($n) + $n -is [System.Management.Automation.Language.InvokeMemberExpressionAst] -and + $n.Member -is [System.Management.Automation.Language.StringConstantExpressionAst] -and + $n.Member.Value -eq 'ToArray' + }, $true)) + $toArrayCalls.Count | Should -Be 2 + } + + It 'resolves from its literal source key , never a plausible guess' -ForEach @( + # 'gids'/'uids' are the keys in the source. A resolver that reasoned from the + # parameter name would produce 'group_ids'/'user_ids', which are not fields of these + # endpoints -- and which no assertion on Surface alone would catch. + @{ Identity = 'New-PfbQuotaGroup|GroupId'; Expected = 'gids'; Method = 'POST'; Endpoint = 'quotas/groups' } + @{ Identity = 'Update-PfbQuotaGroup|GroupId'; Expected = 'gids'; Method = 'PATCH'; Endpoint = 'quotas/groups' } + @{ Identity = 'Remove-PfbQuotaGroup|GroupId'; Expected = 'gids'; Method = 'DELETE'; Endpoint = 'quotas/groups' } + @{ Identity = 'New-PfbQuotaUser|UserId'; Expected = 'uids'; Method = 'POST'; Endpoint = 'quotas/users' } + @{ Identity = 'Get-PfbFileSystemGroupQuota|GroupId'; Expected = 'gids'; Method = 'GET'; Endpoint = 'file-system-group-quotas' } + ) { + $row = Get-PfbT4Row -Identity $Identity + $row.WireName | Should -BeExactly $Expected + $row.Surface | Should -Be 'Typed' + $row.WireSurface | Should -Be 'Query' + $row.Method | Should -Be $Method + $row.Endpoint | Should -Be $Endpoint + } + + It 'records body-role landings, and promotes no intermediate sub-body to a top-level field' { + # No count is asserted: the population is whatever the tree contains today. What is + # asserted is that it is non-empty, that every member names a variable the cmdlet + # really passes to -Body, and that no member's wire key came from a hashtable that + # is only ever nested inside another one. + $bodyRows = @($script:t4Inventory | Where-Object { $_.Surface -eq 'Typed' -and $_.WireSurface -eq 'Body' }) + $bodyRows.Count | Should -BeGreaterThan 0 -Because 'an empty population would make the rest of this test vacuous' + + $offenders = [System.Collections.Generic.List[string]]::new() + foreach ($row in $bodyRows) { + if (-not $row.TargetVariable) { continue } + $funcAst = $script:t4Functions[$row.Cmdlet] + if (-not $funcAst) { $offenders.Add("MISSINGFUNC $($row.Cmdlet)"); continue } + $role = Get-PfbRequestRoleForVariable -FunctionAst $funcAst -TargetVariable $row.TargetVariable + if (-not $role) { + # The only way a Body row can have a target with no role: it does not exist. + $offenders.Add(('NOROLE {0}|{1} target={2}' -f $row.Cmdlet, $row.Parameter, $row.TargetVariable)) + continue + } + if ($role.WireSurface -ne 'Body') { + $offenders.Add(('SURFACE {0}|{1} target={2} role={3}' -f $row.Cmdlet, $row.Parameter, $row.TargetVariable, $role.WireSurface)) + } + } + $offenders -join "`n" | Should -BeNullOrEmpty + + # The specific sub-body hazard, named. New-PfbFileSystem builds $nfsBody/$smbBody + # and nests them under $body; neither is ever passed to a request argument, so no + # row may be attributed to either. + $subBodyRows = @($bodyRows | Where-Object { $_.Cmdlet -eq 'New-PfbFileSystem' -and $_.TargetVariable -in @('nfsBody', 'smbBody', 'httpBody', 'multiProtocolBody') }) + $subBodyRows | Should -BeNullOrEmpty + + # Control: New-PfbFileSystem really does build a sub-body, so the emptiness above is + # a refusal and not an absence of the shape. + $newFs = $script:t4Functions['New-PfbFileSystem'] + $newFs | Should -Not -BeNullOrEmpty + $subBodyAssignments = @($newFs.FindAll({ + param($n) + $n -is [System.Management.Automation.Language.AssignmentStatementAst] -and + $n.Left -is [System.Management.Automation.Language.IndexExpressionAst] -and + $n.Left.Target -is [System.Management.Automation.Language.VariableExpressionAst] -and + $n.Left.Target.VariablePath.UserPath -eq 'nfsBody' + }, $true)) + $subBodyAssignments.Count | Should -BeGreaterThan 0 -Because '$nfsBody must still be keyed into for its exclusion to mean anything' + Get-PfbRequestRoleForVariable -FunctionAst $newFs -TargetVariable 'nfsBody' | Should -BeNullOrEmpty + } + } +} + +Describe 'Compare-PfbInventoryTupleSet: the row-level regression gate (issue #141 Task 4, Step 9)' { + # The gate exists because a resolver change can WITHDRAW a resolution while every total goes + # up. Its own failure mode is rotting into a rubber stamp, so the tests below spend as much + # effort on what it must REFUSE as on what it must accept. + + BeforeAll { + function script:New-PfbTupleRow { + param( + [string]$Cmdlet = 'Get-PfbThing', + [string]$Parameter = 'Zeta', + [string]$Surface = 'Typed', + $WireName = 'alpha', + $WireSurface = 'Query', + $Method = 'GET', + $Endpoint = 'widgets' + ) + [PSCustomObject]@{ + Cmdlet = $Cmdlet; Parameter = $Parameter; Surface = $Surface + WireName = $WireName; WireSurface = $WireSurface; Method = $Method; Endpoint = $Endpoint + } + } + } + + It 'renders a row as Surface|WireName|WireSurface|Method|Endpoint, with $null as empty' { + $set = Get-PfbInventoryTupleSet -Inventory @(New-PfbTupleRow -Surface 'TypedUnresolved' -WireName $null -WireSurface 'Unresolved' -Method $null -Endpoint $null) + $set['Get-PfbThing|Zeta'] | Should -BeExactly 'TypedUnresolved||Unresolved||' + } + + It 'is clean when nothing moved' { + $rows = @(New-PfbTupleRow) + $result = Compare-PfbInventoryTupleSet -Baseline $rows -Current $rows + $result.IsClean | Should -BeTrue + $result.Removed.Count | Should -Be 0 + $result.Changed.Count | Should -Be 0 + } + + It 'fails on a removed row, which no total would show' { + $result = Compare-PfbInventoryTupleSet -Baseline @(New-PfbTupleRow) -Current @() + $result.Removed | Should -Be @('Get-PfbThing|Zeta') + $result.IsClean | Should -BeFalse + } + + It 'fails on an undeclared change' { + $result = Compare-PfbInventoryTupleSet -Baseline @(New-PfbTupleRow) -Current @(New-PfbTupleRow -WireName 'beta') + $result.Changed.Count | Should -Be 1 + $result.Undeclared.Count | Should -Be 1 + $result.Undeclared[0].From | Should -BeExactly 'Typed|alpha|Query|GET|widgets' + $result.Undeclared[0].To | Should -BeExactly 'Typed|beta|Query|GET|widgets' + $result.IsClean | Should -BeFalse + } + + It 'treats a Typed -> AttributesOnly withdrawal as a CHANGE, not an absence' { + # The Update-PfbBucketAuditFilter -BucketName case in miniature, and the reason Surface + # is in the tuple at all: a gate keyed on row identity alone reads this as "still there". + $result = Compare-PfbInventoryTupleSet -Baseline @(New-PfbTupleRow -Surface 'Typed' -WireName 'bucket_names') ` + -Current @(New-PfbTupleRow -Surface 'AttributesOnly' -WireName $null -WireSurface 'Unresolved' -Method $null -Endpoint $null) + $result.Removed.Count | Should -Be 0 + $result.Undeclared.Count | Should -Be 1 + $result.IsClean | Should -BeFalse + } + + It 'accepts a change that was declared with its exact before and after' { + $declaration = [PSCustomObject]@{ + Key = 'Get-PfbThing|Zeta' + From = 'Typed|alpha|Query|GET|widgets' + To = 'Typed|beta|Query|GET|widgets' + } + $result = Compare-PfbInventoryTupleSet -Baseline @(New-PfbTupleRow) -Current @(New-PfbTupleRow -WireName 'beta') -DeclaredChange @($declaration) + $result.Changed.Count | Should -Be 1 + $result.Undeclared.Count | Should -Be 0 + $result.UnusedDeclaration.Count | Should -Be 0 + $result.IsClean | Should -BeTrue + } + + It 'refuses a declaration that names the right row but the wrong ' -ForEach @( + @{ Half = 'before'; From = 'Typed|WRONG|Query|GET|widgets'; To = 'Typed|beta|Query|GET|widgets' } + @{ Half = 'after'; From = 'Typed|alpha|Query|GET|widgets'; To = 'Typed|WRONG|Query|GET|widgets' } + ) { + # "This row is expected to move" would pre-authorise every subsequent move on that row. + $declaration = [PSCustomObject]@{ Key = 'Get-PfbThing|Zeta'; From = $From; To = $To } + $result = Compare-PfbInventoryTupleSet -Baseline @(New-PfbTupleRow) -Current @(New-PfbTupleRow -WireName 'beta') -DeclaredChange @($declaration) + $result.Undeclared.Count | Should -Be 1 + $result.UnusedDeclaration.Count | Should -Be 1 + $result.IsClean | Should -BeFalse + } + + It 'fails on a declaration that matched nothing, so the gate cannot rot into a rubber stamp' { + $declaration = [PSCustomObject]@{ + Key = 'Get-PfbGone|Zeta'; From = 'Typed|alpha|Query|GET|widgets'; To = 'Typed|beta|Query|GET|widgets' + } + $rows = @(New-PfbTupleRow) + $result = Compare-PfbInventoryTupleSet -Baseline $rows -Current $rows -DeclaredChange @($declaration) + $result.Changed.Count | Should -Be 0 + $result.UnusedDeclaration.Count | Should -Be 1 + $result.IsClean | Should -BeFalse + } + + It 'reports an added row without failing -- a new cmdlet legitimately adds rows' { + $result = Compare-PfbInventoryTupleSet -Baseline @(New-PfbTupleRow) ` + -Current @(New-PfbTupleRow; New-PfbTupleRow -Cmdlet 'Get-PfbNewThing') + $result.Added | Should -Be @('Get-PfbNewThing|Zeta') + $result.IsClean | Should -BeTrue + } + + It 'compares the ORDINALLY, so a case-only difference is a change' -ForEach @( + @{ Component = 'wire key'; First = @{ WireName = 'names' }; Second = @{ WireName = 'Names' } } + @{ Component = 'method'; First = @{ Method = 'GET' }; Second = @{ Method = 'get' } } + @{ Component = 'endpoint'; First = @{ Endpoint = 'widgets' }; Second = @{ Endpoint = 'Widgets' } } + ) { + # PowerShell's own -ne would judge every pair here equal and report a clean gate. + $result = Compare-PfbInventoryTupleSet -Baseline @(New-PfbTupleRow @First) -Current @(New-PfbTupleRow @Second) + $result.Changed.Count | Should -Be 1 -Because "a case-insensitive comparison would miss the $Component" + $result.IsClean | Should -BeFalse + } +} diff --git a/tools/Compare-PfbInventoryTuple.ps1 b/tools/Compare-PfbInventoryTuple.ps1 new file mode 100644 index 00000000..2723ead0 --- /dev/null +++ b/tools/Compare-PfbInventoryTuple.ps1 @@ -0,0 +1,143 @@ +#Requires -Version 7.0 +<# +.SYNOPSIS + Row-level regression gate for a change to the Public/ wire-name resolver: reports every + inventory row whose resolution tuple moved, or vanished, between a git ref and the + working tree -- and fails unless every move was declared in advance. +.DESCRIPTION + A resolver change can WITHDRAW a resolution as easily as add one, and no total shows it. + Issue #141 Task 3 raised the Typed count by 61 while silently demoting + Update-PfbBucketAuditFilter -BucketName from a confident 'bucket_names' to unresolved; + the only thing that caught it was a human diffing rows by hand in a code review. This + script is that diff, made runnable and repeatable. + + Both sides are inventoried by their OWN copy of tools/lib/PfbCmdletParamTools.ps1, each in + a separate child process, so the baseline is resolved by the baseline's resolver rather + than re-resolved by the new one. Public/ is taken from each side too, so a ref that + predates a cmdlet is not accused of losing it. + + Exit code is 0 when the comparison is clean and 1 otherwise, so this is usable as a gate + in a script or a workflow step. "Clean" means: nothing removed, every changed tuple + matched a declaration, and every declaration matched a change. Added rows never fail -- + a new cmdlet legitimately adds rows. +.PARAMETER RepoPath + The repository (or worktree) to compare. Defaults to this script's parent. +.PARAMETER BaselineRef + Any git ref resolvable in -RepoPath. Defaults to origin/main. +.PARAMETER DeclarationPath + Optional JSON file: an array of { "key": "|", "from": "", + "to": "" }, where a tuple is 'Surface|WireName|WireSurface|Method|Endpoint' with + $null rendered as the empty string -- exactly what this script prints for an undeclared + change, so a reviewed change can be pasted straight in. + tools/inventory-tuple-baselines/issue-141-task4.json is the worked example. +.EXAMPLE + ./tools/Compare-PfbInventoryTuple.ps1 -BaselineRef origin/main ` + -DeclarationPath ./tools/inventory-tuple-baselines/issue-141-task4.json +#> +[CmdletBinding()] +param( + [string]$RepoPath, + [string]$BaselineRef = 'origin/main', + [string]$DeclarationPath +) + +$ErrorActionPreference = 'Stop' + +$scriptDir = $PSScriptRoot +if (-not $RepoPath) { $RepoPath = Split-Path -Parent $scriptDir } +$RepoPath = (Resolve-Path -Path $RepoPath).Path + +. (Join-Path $scriptDir 'lib/PfbCmdletParamTools.ps1') + +# The dump runs in a child process against a tree chosen at runtime, so it cannot be a +# committed script inside that tree -- the baseline ref generally predates this file. +$dumpSource = @' +param([Parameter(Mandatory)][string]$TreePath) +$ErrorActionPreference = 'Stop' +. (Join-Path $TreePath 'tools/lib/PfbCmdletParamTools.ps1') +foreach ($row in (Get-PfbCmdletParameterInventory -PublicDirectory (Join-Path $TreePath 'Public'))) { + # Double-quoted deliberately: this text is carried here inside a LITERAL here-string, so + # the backtick escapes survive verbatim and are interpreted as tabs by the child, which is + # the only separator guaranteed absent from a cmdlet name, wire key, method or endpoint. + ("{0}`t{1}`t{2}`t{3}`t{4}`t{5}`t{6}" -f $row.Cmdlet, $row.Parameter, $row.Surface, $row.WireName, $row.WireSurface, $row.Method, $row.Endpoint) +} +'@ + +$hostExe = [System.Diagnostics.Process]::GetCurrentProcess().MainModule.FileName +$scratch = Join-Path ([System.IO.Path]::GetTempPath()) ("pfb-tuple-" + [guid]::NewGuid().ToString('N')) +New-Item -ItemType Directory -Path $scratch -Force | Out-Null + +function Read-PfbTupleDump { + param([string]$TreePath, [string]$DumpScript) + + $output = & $hostExe -NoProfile -NonInteractive -File $DumpScript -TreePath $TreePath + if ($LASTEXITCODE -ne 0) { throw "Inventory dump failed for '$TreePath' (exit $LASTEXITCODE)." } + + $rows = foreach ($line in @($output)) { + if ([string]::IsNullOrWhiteSpace($line)) { continue } + $f = $line -split "`t", 7 + [PSCustomObject]@{ + Cmdlet = $f[0] + Parameter = $f[1] + Surface = $f[2] + WireName = $f[3] + WireSurface = $f[4] + Method = $f[5] + Endpoint = $f[6] + } + } + return @($rows) +} + +try { + $dumpScript = Join-Path $scratch 'Dump-PfbInventoryTuple.ps1' + Set-Content -Path $dumpScript -Value $dumpSource -Encoding UTF8 + + $baselineTree = Join-Path $scratch 'baseline' + New-Item -ItemType Directory -Path $baselineTree -Force | Out-Null + + # git archive, not `git worktree add`: it materialises a detached snapshot of exactly the + # two paths that matter without touching the repository's worktree list, so a failure + # here cannot leave a registered worktree behind for someone to prune. + $archive = Join-Path $scratch 'baseline.tar' + & git -C $RepoPath archive --format=tar --output=$archive $BaselineRef tools Public + if ($LASTEXITCODE -ne 0) { throw "git archive failed for ref '$BaselineRef' in '$RepoPath'." } + & tar -x -f $archive -C $baselineTree + if ($LASTEXITCODE -ne 0) { throw "Extracting the baseline archive failed." } + + $baselineRows = Read-PfbTupleDump -TreePath $baselineTree -DumpScript $dumpScript + $currentRows = Read-PfbTupleDump -TreePath $RepoPath -DumpScript $dumpScript + + $declarations = @() + if ($DeclarationPath) { + $declarations = @(Get-Content -Path $DeclarationPath -Raw | ConvertFrom-Json | ForEach-Object { + [PSCustomObject]@{ Key = $_.key; From = $_.from; To = $_.to } + }) + } + + $result = Compare-PfbInventoryTupleSet -Baseline $baselineRows -Current $currentRows -DeclaredChange $declarations + + Write-Host "baseline ref : $BaselineRef" + Write-Host "baseline rows : $($baselineRows.Count)" + Write-Host "current rows : $($currentRows.Count)" + Write-Host "declarations : $($declarations.Count)" + Write-Host "removed : $($result.Removed.Count)" + Write-Host "added : $($result.Added.Count)" + Write-Host "changed : $($result.Changed.Count)" + Write-Host "undeclared : $($result.Undeclared.Count)" + Write-Host "unused declaration: $($result.UnusedDeclaration.Count)" + + foreach ($key in $result.Removed) { Write-Host "REMOVED $key" } + foreach ($change in $result.Undeclared) { Write-Host "UNDECLARED $($change.Key) $($change.From) => $($change.To)" } + foreach ($declaration in $result.UnusedDeclaration) { Write-Host "STALE-DECL $($declaration.Key) $($declaration.From) => $($declaration.To)" } + + if ($result.IsClean) { + Write-Host 'RESULT: CLEAN' -ForegroundColor Green + exit 0 + } + Write-Host 'RESULT: REGRESSION' -ForegroundColor Red + exit 1 +} +finally { + Remove-Item -Path $scratch -Recurse -Force -ErrorAction SilentlyContinue +} diff --git a/tools/README.md b/tools/README.md index c4a878b7..f9e1dcd6 100644 --- a/tools/README.md +++ b/tools/README.md @@ -307,6 +307,17 @@ Run in this order: `ArgumentCompleter`" is a categorically different judgment call than "does this endpoint have an addable gap" -- and remain genuinely "reported, not resolved, requires a human decision." That norm is not reversed by this change. + - **Later refinement (issue #141 Task 4): "unresolved" stopped meaning "not `Typed`".** A row's + `Surface` now has five values, and two of them -- `NotWireParameter` (an audited request + control such as `-Eradicate`/`-Force`) and `OutsideStandardRequest` (the declaring cmdlet + issues no `Invoke-PfbApiRequest` call at all) -- say the parameter is **not a wire field**, + which is not the same claim as "its wire field could not be found". 34 real parameters were + in that position and each was lowering `confidence` on every endpoint its cmdlet reaches. + Only `AttributesOnly` and `TypedUnresolved` populate `unresolvedParameters` now. + `Get-PfbParameterCoverageGaps` branches **exhaustively** on `Surface` and throws on a value + it has not been taught, so a sixth value cannot inherit a meaning by falling on the far side + of a negation. `Build-PfbFieldCmdletMap.ps1` gained a matching `notApplicable` collection and + asserts that every inventory row lands in exactly one of its five buckets. **The false-positive resolution procedure (decision 6).** This report accepts **false positives in order to eliminate false negatives**. A field is listed as missing even though diff --git a/tools/inventory-tuple-baselines/issue-141-task4.json b/tools/inventory-tuple-baselines/issue-141-task4.json new file mode 100644 index 00000000..ebfea392 --- /dev/null +++ b/tools/inventory-tuple-baselines/issue-141-task4.json @@ -0,0 +1,172 @@ +[ + { + "key": "Connect-PfbArray|AllArrays", + "from": "TypedUnresolved||Unresolved||", + "to": "OutsideStandardRequest||Unresolved||" + }, + { + "key": "Connect-PfbArray|ApiToken", + "from": "TypedUnresolved||Unresolved||", + "to": "OutsideStandardRequest||Unresolved||" + }, + { + "key": "Connect-PfbArray|ApiVersion", + "from": "TypedUnresolved||Unresolved||", + "to": "OutsideStandardRequest||Unresolved||" + }, + { + "key": "Connect-PfbArray|ClientId", + "from": "TypedUnresolved||Unresolved||", + "to": "OutsideStandardRequest||Unresolved||" + }, + { + "key": "Connect-PfbArray|Context", + "from": "TypedUnresolved||Unresolved||", + "to": "OutsideStandardRequest||Unresolved||" + }, + { + "key": "Connect-PfbArray|Credential", + "from": "TypedUnresolved||Unresolved||", + "to": "OutsideStandardRequest||Unresolved||" + }, + { + "key": "Connect-PfbArray|Endpoint", + "from": "TypedUnresolved||Unresolved||", + "to": "OutsideStandardRequest||Unresolved||" + }, + { + "key": "Connect-PfbArray|HttpTimeout", + "from": "TypedUnresolved||Unresolved||", + "to": "OutsideStandardRequest||Unresolved||" + }, + { + "key": "Connect-PfbArray|IgnoreCertificateError", + "from": "TypedUnresolved||Unresolved||", + "to": "OutsideStandardRequest||Unresolved||" + }, + { + "key": "Connect-PfbArray|Issuer", + "from": "TypedUnresolved||Unresolved||", + "to": "OutsideStandardRequest||Unresolved||" + }, + { + "key": "Connect-PfbArray|KeyId", + "from": "TypedUnresolved||Unresolved||", + "to": "OutsideStandardRequest||Unresolved||" + }, + { + "key": "Connect-PfbArray|Kind", + "from": "TypedUnresolved||Unresolved||", + "to": "OutsideStandardRequest||Unresolved||" + }, + { + "key": "Connect-PfbArray|Password", + "from": "TypedUnresolved||Unresolved||", + "to": "OutsideStandardRequest||Unresolved||" + }, + { + "key": "Connect-PfbArray|PrivateKeyFile", + "from": "TypedUnresolved||Unresolved||", + "to": "OutsideStandardRequest||Unresolved||" + }, + { + "key": "Connect-PfbArray|PrivateKeyPassword", + "from": "TypedUnresolved||Unresolved||", + "to": "OutsideStandardRequest||Unresolved||" + }, + { + "key": "Connect-PfbArray|Username", + "from": "TypedUnresolved||Unresolved||", + "to": "OutsideStandardRequest||Unresolved||" + }, + { + "key": "Get-PfbApiVersion|Endpoint", + "from": "TypedUnresolved||Unresolved||", + "to": "OutsideStandardRequest||Unresolved||" + }, + { + "key": "Get-PfbApiVersion|IgnoreCertificateError", + "from": "TypedUnresolved||Unresolved||", + "to": "OutsideStandardRequest||Unresolved||" + }, + { + "key": "Get-PfbConnection|Endpoint", + "from": "TypedUnresolved||Unresolved||", + "to": "OutsideStandardRequest||Unresolved||" + }, + { + "key": "Invoke-PfbInContext|AllArrays", + "from": "TypedUnresolved||Unresolved||", + "to": "OutsideStandardRequest||Unresolved||" + }, + { + "key": "Invoke-PfbInContext|Context", + "from": "TypedUnresolved||Unresolved||", + "to": "OutsideStandardRequest||Unresolved||" + }, + { + "key": "Invoke-PfbInContext|Kind", + "from": "TypedUnresolved||Unresolved||", + "to": "OutsideStandardRequest||Unresolved||" + }, + { + "key": "Invoke-PfbInContext|ScriptBlock", + "from": "TypedUnresolved||Unresolved||", + "to": "OutsideStandardRequest||Unresolved||" + }, + { + "key": "Remove-PfbBucket|Eradicate", + "from": "TypedUnresolved||Unresolved||", + "to": "NotWireParameter||Unresolved||" + }, + { + "key": "Remove-PfbFileSystem|Eradicate", + "from": "TypedUnresolved||Unresolved||", + "to": "NotWireParameter||Unresolved||" + }, + { + "key": "Remove-PfbFileSystemSession|Force", + "from": "TypedUnresolved||Unresolved||", + "to": "NotWireParameter||Unresolved||" + }, + { + "key": "Remove-PfbFileSystemSnapshot|Eradicate", + "from": "TypedUnresolved||Unresolved||", + "to": "NotWireParameter||Unresolved||" + }, + { + "key": "Remove-PfbRealm|Eradicate", + "from": "TypedUnresolved||Unresolved||", + "to": "NotWireParameter||Unresolved||" + }, + { + "key": "Remove-PfbServer|Eradicate", + "from": "TypedUnresolved||Unresolved||", + "to": "NotWireParameter||Unresolved||" + }, + { + "key": "Set-PfbContext|AllArrays", + "from": "TypedUnresolved||Unresolved||", + "to": "OutsideStandardRequest||Unresolved||" + }, + { + "key": "Set-PfbContext|AllowErrors", + "from": "TypedUnresolved||Unresolved||", + "to": "OutsideStandardRequest||Unresolved||" + }, + { + "key": "Set-PfbContext|Context", + "from": "TypedUnresolved||Unresolved||", + "to": "OutsideStandardRequest||Unresolved||" + }, + { + "key": "Set-PfbContext|Kind", + "from": "TypedUnresolved||Unresolved||", + "to": "OutsideStandardRequest||Unresolved||" + }, + { + "key": "Set-PfbCredential|Credential", + "from": "TypedUnresolved||Unresolved||", + "to": "OutsideStandardRequest||Unresolved||" + } +] diff --git a/tools/lib/PfbCmdletParamTools.ps1 b/tools/lib/PfbCmdletParamTools.ps1 index 3195ffc2..f616933e 100644 --- a/tools/lib/PfbCmdletParamTools.ps1 +++ b/tools/lib/PfbCmdletParamTools.ps1 @@ -702,11 +702,12 @@ function New-PfbWireLanding { } } -function Get-PfbWireNameForParameter { +function Resolve-PfbParameterWireLanding { <# .SYNOPSIS - Finds the request-body or query-string key a given parameter is assigned to - inside a cmdlet function body, or $null if no simple assignment pattern matches. + The whole wire-landing resolution of ONE parameter: the landings the winning idiom + proved, AND the single answer they arbitrate to -- returned together, so a caller can + tell an abstention from a silence. .DESCRIPTION Four idioms are tried in a fixed precedence, and the FIRST idiom that produces any proven landing answers -- including by abstaining. Precedence is between idioms only; @@ -724,32 +725,32 @@ function Get-PfbWireNameForParameter { (Get-PfbHashtableLiteralWireLanding, Get-PfbNestedReferenceWireLanding), and the decision to answer is made on `landings.Count -gt 0` -- never on the truthiness of an arbitrated result, which cannot tell "found nothing" from "found landings that - disagreed". An earlier revision of this function got that right for the index tier + disagreed". An earlier revision of this resolver got that right for the index tier and wrong for the other three: a literal tier holding two disagreeing keys returned $null and the nested tier then published its own key, exactly the guess the tier order exists to prevent. - The one abstention that is NOT sticky lives inside + RETURNING BOTH HALVES is what carries that same distinction ACROSS the return, which + a lone arbitrated value cannot. `Resolution` is $null both when nothing was found and + when what was found disagreed; `Landings` is empty only in the first case. Issue #141 + Task 4 introduced this shape because Get-PfbCmdletParameterInventory's accumulator + retry fired on the arbitrated $null, so a parameter that had just abstained had a + FIFTH source consulted on its behalf and could be published with a confident name -- + an abstention laundered into an answer. That caller now retries only on an empty + `Landings`, i.e. only on genuine silence. + + One abstention remains invisible here, and deliberately so: the one inside Get-PfbCommonQueryParamHelperWireName, which returns $null when two helper calls - disagree. That is harmless only because the helper tier is last, so its abstention - and its silence have the same consequence: no answer at all. - - SCOPE OF THE INVARIANT -- it holds WITHIN this function, and stops at its return. - This function signals an abstention the only way its contract allows, by returning - $null, and $null is also how it signals silence. Its one production caller, - Get-PfbCmdletParameterInventory, retries through Find-PfbAccumulatorVariable whenever - the result is falsy, so an abstention here is read there as "nothing found" and a - fifth source is consulted. Measured: a parameter written to both $q['alpha'] and - $q['beta'] AND fed to an accumulator keyed at $q['names'] abstains here and still - emits a Typed row naming 'names'. No cmdlet in Public/ has that shape today -- neither - known multi-key parameter has an accumulator -- so this is latent, and it predates the - tier work rather than being introduced by it. A caller that must distinguish the two - cases has to consult the ...WireLanding producers directly; do not infer from a $null - here that no landings existed. + disagree. It collapses to an empty helper tier and so reads as silence. Surfacing it + would change which parameters reach the accumulator retry -- a resolution change -- + and issue #141 Task 4 is required to leave every real-tree resolution tuple untouched. + It is recorded here rather than fixed silently. .OUTPUTS - $null, or [PSCustomObject]@{ WireName; TargetVariable; WireSurface; Method; Endpoint }. - TargetVariable is the payload variable the assignment targeted, or $null when the - landings came through more than one; WireSurface is 'Body', 'Query' or 'Unresolved'. + [PSCustomObject]@{ Landings; Resolution }. + Landings is the winning idiom's UNARBITRATED candidate array, empty when no idiom + proved anything at all. Resolution is Resolve-PfbWireLandingArbitration's verdict over + exactly those landings -- $null, or + [PSCustomObject]@{ WireName; TargetVariable; WireSurface; Method; Endpoint }. #> [CmdletBinding()] param( @@ -784,7 +785,14 @@ function Get-PfbWireNameForParameter { } } - if ($landings.Count -gt 0) { return (Resolve-PfbWireLandingArbitration -Candidate $landings.ToArray()) } + # $null, never an empty array, means "no tier has answered yet": an empty array is falsy + # in PowerShell but so is a one-element array holding $null, and the tiers below are + # selected on `-eq $null` precisely so no truthiness rule is being relied on anywhere in + # this function. Every tier below is consulted for its LANDINGS, never for its arbitrated + # answer -- asking `if ($literalMatch)` instead would read an abstention as a miss and + # fall through, which is the whole failure this resolver exists to prevent. + $tierLandings = $null + if ($landings.Count -gt 0) { $tierLandings = $landings.ToArray() } # Second idiom: the whole hashtable is built as a LITERAL initializer rather than keyed # into afterwards -- `$queryParams = @{ 'names' = $Name }`, the dominant shape across @@ -792,19 +800,19 @@ function Get-PfbWireNameForParameter { # New-PfbObjectStoreAccount, the whole Policy/*Rule family, ...). Runs after the index # form, not instead of it: a cmdlet routinely does both (literal initializer for its # -Name, then `$body['x'] = $X` lines), and both key sets must resolve. - # - # Every tier below is consulted for its LANDINGS, never for its arbitrated answer. Asking - # `if ($literalMatch)` instead would read an abstention as a miss and fall through, which - # is the whole failure this function exists to prevent -- see the .DESCRIPTION. - $literalLandings = @(Get-PfbHashtableLiteralWireLanding -FunctionAst $FunctionAst -ParameterName $ParameterName -IsBooleanLikeParameter:$IsBooleanLikeParameter) - if ($literalLandings.Count -gt 0) { return (Resolve-PfbWireLandingArbitration -Candidate $literalLandings) } + if ($null -eq $tierLandings) { + $literalLandings = @(Get-PfbHashtableLiteralWireLanding -FunctionAst $FunctionAst -ParameterName $ParameterName -IsBooleanLikeParameter:$IsBooleanLikeParameter) + if ($literalLandings.Count -gt 0) { $tierLandings = $literalLandings } + } # Third idiom: a nested single-key REFERENCE OBJECT -- `$body['account'] = @{ name = # $Account }` -- whose wire field is the OUTER key. Runs strictly after both direct # forms above so it can only ever add a resolution, never rename one: a parameter that - # already resolved via a direct assignment returned before reaching here. - $nestedLandings = @(Get-PfbNestedReferenceWireLanding -FunctionAst $FunctionAst -ParameterName $ParameterName -IsBooleanLikeParameter:$IsBooleanLikeParameter) - if ($nestedLandings.Count -gt 0) { return (Resolve-PfbWireLandingArbitration -Candidate $nestedLandings) } + # already resolved via a direct assignment stopped at the tier that proved it. + if ($null -eq $tierLandings) { + $nestedLandings = @(Get-PfbNestedReferenceWireLanding -FunctionAst $FunctionAst -ParameterName $ParameterName -IsBooleanLikeParameter:$IsBooleanLikeParameter) + if ($nestedLandings.Count -gt 0) { $tierLandings = $nestedLandings } + } # No literal assignment of any shape in this function body -- but the parameter may # still reach the wire through the shared Private/Add-PfbCommonQueryParams.ps1 helper, @@ -812,14 +820,60 @@ function Get-PfbWireNameForParameter { # LAST: a cmdlet whose Name/Id-equivalent maps to a non-generic key (policy_names, # file_system_names, ...) kept its own explicit line after the helper call, and that # literal must win. - $helperLandings = [System.Collections.Generic.List[object]]::new() - foreach ($helperMatch in @(Get-PfbCommonQueryParamHelperWireName -FunctionAst $FunctionAst -ParameterName $ParameterName)) { - if (-not $helperMatch) { continue } - $landing = New-PfbWireLanding -FunctionAst $FunctionAst -WireName $helperMatch.WireName -TargetVariable $helperMatch.TargetVariable - if ($landing) { $helperLandings.Add($landing) } + if ($null -eq $tierLandings) { + $helperLandings = [System.Collections.Generic.List[object]]::new() + foreach ($helperMatch in @(Get-PfbCommonQueryParamHelperWireName -FunctionAst $FunctionAst -ParameterName $ParameterName)) { + if (-not $helperMatch) { continue } + $landing = New-PfbWireLanding -FunctionAst $FunctionAst -WireName $helperMatch.WireName -TargetVariable $helperMatch.TargetVariable + if ($landing) { $helperLandings.Add($landing) } + } + if ($helperLandings.Count -gt 0) { $tierLandings = $helperLandings.ToArray() } + } + + if ($null -eq $tierLandings) { + return [PSCustomObject]@{ Landings = @(); Resolution = $null } + } + + return [PSCustomObject]@{ + Landings = @($tierLandings) + Resolution = (Resolve-PfbWireLandingArbitration -Candidate $tierLandings) } - if ($helperLandings.Count -eq 0) { return $null } - return Resolve-PfbWireLandingArbitration -Candidate $helperLandings.ToArray() +} + +function Get-PfbWireNameForParameter { + <# + .SYNOPSIS + Finds the request-body or query-string key a given parameter is assigned to + inside a cmdlet function body, or $null if no simple assignment pattern matches. + .DESCRIPTION + A thin projection of Resolve-PfbParameterWireLanding onto its arbitrated half. All of + the tier precedence, the within-tier arbitration and the sticky-abstention invariant + live there; see that function's .DESCRIPTION. + + SCOPE OF THE INVARIANT -- the stickiness holds inside the resolver and stops at THIS + function's return, because $null is the only signal this shape has and it is spent + twice: once for "no idiom proved anything" and once for "an idiom proved landings that + disagreed". A caller that must tell those apart -- Get-PfbCmdletParameterInventory + must, or its accumulator retry launders an abstention into a confident name -- calls + Resolve-PfbParameterWireLanding and reads `Landings`. Do not infer from a $null here + that no landings existed. + .OUTPUTS + $null, or [PSCustomObject]@{ WireName; TargetVariable; WireSurface; Method; Endpoint }. + TargetVariable is the payload variable the assignment targeted, or $null when the + landings came through more than one; WireSurface is 'Body', 'Query' or 'Unresolved'. + #> + [CmdletBinding()] + param( + [Parameter(Mandatory)] + [System.Management.Automation.Language.FunctionDefinitionAst]$FunctionAst, + + [Parameter(Mandatory)] + [string]$ParameterName, + + [switch]$IsBooleanLikeParameter + ) + + return (Resolve-PfbParameterWireLanding -FunctionAst $FunctionAst -ParameterName $ParameterName -IsBooleanLikeParameter:$IsBooleanLikeParameter).Resolution } function Get-PfbHashtableLiteralWireNameForParameter { @@ -1276,7 +1330,7 @@ function Get-PfbRequestRoleForVariable { # operations this function is supposed to be able to tell apart. List[string].Contains is # ordinal, so differing case reads as differing operations, which matches the ordinal # comparison Resolve-PfbWireLandingArbitration uses. Every -Method argument in Public/ is - # upper case today (all 544 of them), so this costs nothing and forecloses a guess. + # upper case today (all 541 of them), so this costs nothing and forecloses a guess. $distinctOperations = [System.Collections.Generic.List[string]]::new() foreach ($landing in $landings) { $operation = '' @@ -1473,6 +1527,119 @@ function Get-PfbCmdletBodyInsertionTarget { } } +# Every value Get-PfbCmdletParameterInventory can put in a row's Surface field. Consumers +# branch on Surface EXHAUSTIVELY against this list rather than on "not Typed", so adding a +# value here is a compile-time-ish event: the consumers throw on a Surface they were never +# taught, instead of quietly folding it into whichever bucket their negation happened to +# catch. See tools/lib/PfbApiDriftTools.ps1's Get-PfbParameterCoverageGaps. +# +# The two NON-APPLICABLE values are the point of issue #141 Task 4. Before it, a parameter +# that is not a wire field AT ALL was indistinguishable from one whose wire field this +# AST-only resolver merely failed to find, so it lowered the drift report's confidence in +# every endpoint its cmdlet reaches -- doubt manufactured out of a parameter that could not +# have covered a gap in the first place. +$script:PfbParameterSurfaces = @( + # The parameter's wire key is proven. + 'Typed' + # Not proven, and the cmdlet exposes an -Attributes escape hatch the field may reach through. + 'AttributesOnly' + # Not proven, and there is no escape hatch either -- a real gap in this resolver's reach. + 'TypedUnresolved' + # NON-APPLICABLE: an audited request control, not a field. See $script:PfbNotWireParameters. + 'NotWireParameter' + # NON-APPLICABLE: the declaring function issues no Invoke-PfbApiRequest call at all. + 'OutsideStandardRequest' +) + +# The audited allowlist behind the 'NotWireParameter' surface: parameters that are proven, by +# reading the cmdlet, to steer the request rather than to appear in it. Every entry was read +# individually -- this is the one place in this file where a fact is asserted by a human +# rather than resolved from the AST, so it is deliberately an enumeration of exact +# 'Cmdlet|Parameter' identities and NOT a name pattern. `-Eradicate` and `-Force` as SHAPES +# mean nothing: New-PfbFileSystem's body switches are switches too, and a future +# `-Eradicate` that did become a wire field would be silently mis-filed by any rule keyed on +# the name. Tests/PfbCmdletParamTools.Tests.ps1 re-validates every entry against the real +# Public/ AST, so an entry that goes stale (its cmdlet or parameter disappears, or the +# parameter acquires a provable wire landing) fails the suite rather than rotting here. +# +# Remove-PfbBucket|Eradicate `if (-not $Eradicate)` selects which request to +# Remove-PfbFileSystem|Eradicate issue (destroy vs. eradicate) and gates the +# Remove-PfbFileSystemSnapshot|Eradicate ShouldProcess prompt; it is never keyed into a +# Remove-PfbRealm|Eradicate payload. +# Remove-PfbServer|Eradicate +# Remove-PfbFileSystemSession|Force `if (-not $Force) { throw ... }` decides whether +# any request is made at all. +$script:PfbNotWireParameters = @( + 'Remove-PfbBucket|Eradicate' + 'Remove-PfbFileSystem|Eradicate' + 'Remove-PfbFileSystemSession|Force' + 'Remove-PfbFileSystemSnapshot|Eradicate' + 'Remove-PfbRealm|Eradicate' + 'Remove-PfbServer|Eradicate' +) + +function Get-PfbParameterSurfaceName { + <# + .SYNOPSIS + Every legal value of an inventory row's Surface field, in classification order. + .DESCRIPTION + Exposed as a function rather than read as $script:PfbParameterSurfaces by consumers + and tests, so the single source of truth survives being dot-sourced into a Pester + scope where a $script:-qualified read resolves against the test file instead. + .OUTPUTS + [string[]] + #> + [CmdletBinding()] + param() + return @($script:PfbParameterSurfaces) +} + +function Get-PfbNotWireParameterAllowlist { + <# + .SYNOPSIS + The audited 'Cmdlet|Parameter' identities classified 'NotWireParameter'. + .OUTPUTS + [string[]], each entry '|'. + #> + [CmdletBinding()] + param() + return @($script:PfbNotWireParameters) +} + +function Test-PfbFunctionMakesStandardRequest { + <# + .SYNOPSIS + Whether a function issues at least one Invoke-PfbApiRequest call. + .DESCRIPTION + The structural fact behind the 'OutsideStandardRequest' surface. A function with no + such call has no request for a parameter to land in, so NONE of its parameters can + resolve -- New-PfbWireLanding refuses every candidate for want of a role -- and + reporting all of them as "wire name unresolved" describes a failure that never + happened. Connect-PfbArray, Set-PfbContext, Set-PfbCredential and Invoke-PfbInContext + are the real shapes: connection, context and credential plumbing. + + Deliberately a plain "does this call exist" question and NOT an attempt to decide + whether the cmdlet reaches the API by some other route. A cmdlet that reaches it + through a Private/ helper would be misdescribed by the NAME of this surface, so the + name says exactly what is measured: outside the standard request path. + .OUTPUTS + [bool] + #> + [CmdletBinding()] + param( + [Parameter(Mandatory)] + [System.Management.Automation.Language.FunctionDefinitionAst]$FunctionAst + ) + + $calls = @($FunctionAst.FindAll({ + param($node) + $node -is [System.Management.Automation.Language.CommandAst] -and + $node.GetCommandName() -eq 'Invoke-PfbApiRequest' + }, $true)) + + return ($calls.Count -gt 0) +} + function Get-PfbCmdletParameterInventory { <# .SYNOPSIS @@ -1493,6 +1660,20 @@ function Get-PfbCmdletParameterInventory { Endpoint/Method are $null unless every landing of the parameter agrees on one literal Invoke-PfbApiRequest (method, endpoint) pair -- never guessed. + Surface is one of Get-PfbParameterSurfaceName's five values, decided in that order: + - 'Typed' -- a wire key was proven. + - 'OutsideStandardRequest' -- the declaring function issues no Invoke-PfbApiRequest + call, so there is no request for this parameter to have landed in. NON-APPLICABLE: + not an unresolved wire name, and never a reason to doubt an endpoint's gap list. + - 'NotWireParameter' -- an audited request control + (Get-PfbNotWireParameterAllowlist). Also NON-APPLICABLE. + - 'AttributesOnly' -- unresolved, but the cmdlet has an -Attributes escape hatch. + - 'TypedUnresolved' -- unresolved with no escape hatch. + The last two are the only ones that lower a consumer's confidence. Splitting the two + non-applicable states out of them is issue #141 Task 4: before it, 34 real parameters + that are not wire fields at all were reported as wire names this resolver had failed + to find, and each one cast doubt on every endpoint its cmdlet reaches. + Line is the parameter's own declaration line ($p.Extent.StartLineNumber), alongside the File it already carried -- so a consumer reporting on a non-'Typed' Surface (Get-PfbParameterCoverageGaps's `confidence.unresolvedParameters` @@ -1523,6 +1704,9 @@ function Get-PfbCmdletParameterInventory { if (-not $paramBlock) { continue } $hasAttributesParam = [bool]($paramBlock.Parameters | Where-Object { $_.Name.VariablePath.UserPath -eq 'Attributes' }) + # Hoisted out of the parameter loop: it is a fact about the FUNCTION, and asking + # it per parameter would re-walk the whole function body once per declaration. + $makesStandardRequest = Test-PfbFunctionMakesStandardRequest -FunctionAst $funcAst foreach ($p in $paramBlock.Parameters) { $paramName = $p.Name.VariablePath.UserPath @@ -1544,8 +1728,18 @@ function Get-PfbCmdletParameterInventory { [bool] [System.Nullable[bool]] ) - $wireInfo = Get-PfbWireNameForParameter -FunctionAst $funcAst -ParameterName $paramName -IsBooleanLikeParameter:$isBooleanLike - if (-not $wireInfo) { + # Resolve-PfbParameterWireLanding, not Get-PfbWireNameForParameter: the retry + # below must fire on SILENCE only, and the arbitrated value alone cannot tell + # silence from an abstention (both are $null). Retrying after an abstention + # consults a FIFTH source on behalf of a parameter whose own evidence had just + # been ruled contradictory, and republishes it with a confident name -- the + # exact laundering the tier stickiness exists to prevent, escaping through the + # caller. Measured before the fix: a parameter written to both $q['alpha'] and + # $q['beta'] AND fed to an accumulator keyed at $q['names'] emitted a Typed row + # naming 'names'. + $primary = Resolve-PfbParameterWireLanding -FunctionAst $funcAst -ParameterName $paramName -IsBooleanLikeParameter:$isBooleanLike + $wireInfo = $primary.Resolution + if ($primary.Landings.Count -eq 0) { $accumulatorName = Find-PfbAccumulatorVariable -FunctionAst $funcAst -ParameterName $paramName if ($accumulatorName) { $wireInfo = Get-PfbWireNameForParameter -FunctionAst $funcAst -ParameterName $accumulatorName @@ -1553,7 +1747,17 @@ function Get-PfbCmdletParameterInventory { } $wireName = if ($wireInfo) { $wireInfo.WireName } else { $null } + # Classification order matters, and is asserted by the Surface ladder tests. + # The two NON-APPLICABLE states are tested BEFORE the two unresolved ones + # because they are answers, not failures: 'OutsideStandardRequest' first + # because it is a property of the whole function and subsumes every parameter + # on it, then the audited per-parameter allowlist. 'Typed' still outranks both + # -- a proven landing is a proven landing, and the allowlist is re-validated + # against the real AST by the suite precisely so an entry that acquires one + # fails loudly rather than being shadowed here. $surface = if ($wireName) { 'Typed' } + elseif (-not $makesStandardRequest) { 'OutsideStandardRequest' } + elseif ($script:PfbNotWireParameters -contains ('{0}|{1}' -f $funcAst.Name, $paramName)) { 'NotWireParameter' } elseif ($hasAttributesParam) { 'AttributesOnly' } else { 'TypedUnresolved' } @@ -1600,3 +1804,130 @@ function Get-PfbCmdletParameterInventory { # for the (not currently occurring) case of one function name declared twice. return @($results | Sort-Object -Property Cmdlet, Parameter, File, Line -Culture '') } + +function Get-PfbInventoryTupleSet { + <# + .SYNOPSIS + Reduces an inventory to the row-identity -> resolution-tuple map the regression gate + compares. + .DESCRIPTION + Identity is 'Cmdlet|Parameter'; the tuple is + 'Surface|WireName|WireSurface|Method|Endpoint', with $null rendered as the empty + string. TargetVariable is deliberately NOT in the tuple: it names the local variable a + landing came through, so it changes whenever the resolver's internal accounting does + (issue #141 Task 3 nulled it for Remove-PfbFileSystem -DeleteLinkOnEradication, which + now proves two landings) without any consumer reading it and without one byte of the + request changing. File and Line are out for the same reason in reverse -- they move + whenever anyone edits a cmdlet, and would swamp a real regression in noise. + .OUTPUTS + [System.Collections.Generic.Dictionary[string,string]], ordinal-keyed. + #> + [CmdletBinding()] + param( + [Parameter(Mandatory)] + [AllowEmptyCollection()] + [object[]]$Inventory + ) + + $set = [System.Collections.Generic.Dictionary[string, string]]::new([System.StringComparer]::Ordinal) + foreach ($row in $Inventory) { + if ($null -eq $row) { continue } + $key = '{0}|{1}' -f $row.Cmdlet, $row.Parameter + $set[$key] = '{0}|{1}|{2}|{3}|{4}' -f $row.Surface, $row.WireName, $row.WireSurface, $row.Method, $row.Endpoint + } + return $set +} + +function Compare-PfbInventoryTupleSet { + <# + .SYNOPSIS + The row-level regression gate for a resolver change: every inventory row that + disappeared, and every row whose resolution tuple moved without being declared. + .DESCRIPTION + Issue #141 Task 4 exists because a resolver change can WITHDRAW a resolution as + easily as add one, and the totals do not show it -- Update-PfbBucketAuditFilter + -BucketName went from a confident 'bucket_names' to unresolved while the Typed count + went UP, and the only thing that caught it was a human diffing rows by hand inside a + code review. This makes that diff runnable. + + A change is tolerated only when it is DECLARED, and a declaration must name the exact + before and after tuple, not just the row: "this row is expected to move" would let any + subsequent move through unseen. Comparison is ordinal throughout, matching + Resolve-PfbWireLandingArbitration -- 'names' and 'Names' are different answers. + + A declaration that matched nothing is reported in UnusedDeclaration and fails the + gate. A stale declaration is how a gate rots into a rubber stamp: it silently pre- + authorises whatever change later happens to land on that row. + + Added rows are reported but never fail: a new cmdlet legitimately adds rows, and this + gate is about what the resolver stopped knowing. + .PARAMETER DeclaredChange + Objects with Key ('|'), From and To (tuple strings, as + Get-PfbInventoryTupleSet renders them). + .OUTPUTS + [PSCustomObject]@{ Removed; Added; Changed; Undeclared; UnusedDeclaration; IsClean }. + Removed/Added are [string[]] of row identities. Changed/Undeclared are + [PSCustomObject]@{ Key; From; To }[]. IsClean is $true only when nothing was removed, + every change was declared, and every declaration was used. + #> + [CmdletBinding()] + param( + [Parameter(Mandatory)] + [AllowEmptyCollection()] + [object[]]$Baseline, + + [Parameter(Mandatory)] + [AllowEmptyCollection()] + [object[]]$Current, + + [AllowEmptyCollection()] + [object[]]$DeclaredChange = @() + ) + + $baselineSet = Get-PfbInventoryTupleSet -Inventory $Baseline + $currentSet = Get-PfbInventoryTupleSet -Inventory $Current + + $removed = [System.Collections.Generic.List[string]]::new() + $changed = [System.Collections.Generic.List[object]]::new() + foreach ($key in $baselineSet.get_Keys()) { + if (-not $currentSet.ContainsKey($key)) { $removed.Add($key); continue } + if (-not [string]::Equals($baselineSet[$key], $currentSet[$key], [System.StringComparison]::Ordinal)) { + $changed.Add([PSCustomObject]@{ Key = $key; From = $baselineSet[$key]; To = $currentSet[$key] }) + } + } + + $added = [System.Collections.Generic.List[string]]::new() + foreach ($key in $currentSet.get_Keys()) { + if (-not $baselineSet.ContainsKey($key)) { $added.Add($key) } + } + + $declarations = @($DeclaredChange | Where-Object { $null -ne $_ }) + $matchedDeclaration = [System.Collections.Generic.List[object]]::new() + $undeclared = [System.Collections.Generic.List[object]]::new() + foreach ($change in $changed) { + $hit = $null + foreach ($declaration in $declarations) { + if ([string]::Equals([string]$declaration.Key, $change.Key, [System.StringComparison]::Ordinal) -and + [string]::Equals([string]$declaration.From, $change.From, [System.StringComparison]::Ordinal) -and + [string]::Equals([string]$declaration.To, $change.To, [System.StringComparison]::Ordinal)) { + $hit = $declaration + break + } + } + if ($hit) { $matchedDeclaration.Add($hit) } else { $undeclared.Add($change) } + } + + $unusedDeclaration = [System.Collections.Generic.List[object]]::new() + foreach ($declaration in $declarations) { + if (-not $matchedDeclaration.Contains($declaration)) { $unusedDeclaration.Add($declaration) } + } + + return [PSCustomObject]@{ + Removed = $removed.ToArray() + Added = $added.ToArray() + Changed = $changed.ToArray() + Undeclared = $undeclared.ToArray() + UnusedDeclaration = $unusedDeclaration.ToArray() + IsClean = ($removed.Count -eq 0 -and $undeclared.Count -eq 0 -and $unusedDeclaration.Count -eq 0) + } +} From 9cb59d2729127a7b21060a980454a7c70fbc7758 Mon Sep 17 00:00:00 2001 From: Justin Emerson Date: Wed, 26 Aug 2026 19:47:17 -0700 Subject: [PATCH 10/29] fix(tools): make drift confidence an allowlist over Surface Get-PfbParameterCoverageGaps decided doubt with `Surface -ne 'Typed'`. That is a denylist: it reads "anything I cannot resolve is doubt about this endpoint", so the two non-applicable surfaces would have been swept into confidence.unresolvedParameters and demoted 34 real endpoints from high to partial on the strength of parameters that are not wire fields at all and could never have covered a gap. Replace it with an exhaustive `switch ($row.Surface)` whose `default` throws. A new Surface value must now be assigned a meaning here, by hand; until it is, the report fails rather than silently taking whichever side the negation happened to fall on. The second `-ne 'Typed'`, in Get-PfbWireNameCmdletCounts, is left as it is with a comment saying why: its `-or -not $row.WireName` companion already excludes every row a non-applicable surface can produce, and that guard is load-bearing for Typed rows too. Co-Authored-By: Claude Opus 5 (1M context) --- Tests/PfbApiDriftTools.Tests.ps1 | 108 +++++++++++++++++++++++++++++++ tools/lib/PfbApiDriftTools.ps1 | 51 +++++++++++---- 2 files changed, 148 insertions(+), 11 deletions(-) diff --git a/Tests/PfbApiDriftTools.Tests.ps1 b/Tests/PfbApiDriftTools.Tests.ps1 index 2363e29a..8587ee9f 100644 --- a/Tests/PfbApiDriftTools.Tests.ps1 +++ b/Tests/PfbApiDriftTools.Tests.ps1 @@ -532,6 +532,114 @@ Describe 'Get-PfbParameterCoverageGaps' { $gap.Confidence.Level | Should -Be 'partial' } } + + Context 'confidence is lowered by UNRESOLVED surfaces only, never by non-applicable ones (issue #141 Task 4, Step 7)' { + # Before Task 4 this function asked `Surface -ne 'Typed'`, which is a denylist: every + # value invented upstream lands in the "lower the confidence" branch by default. Two + # such values now exist and neither is a gap -- a parameter that is not a wire field + # at all is not a wire field this tool failed to find -- so 34 real parameters were + # casting doubt on every endpoint their cmdlets reach. The condition is now an + # exhaustive switch, and these tests pin both halves of it. + + BeforeAll { + $script:t4CapMap = [PSCustomObject]@{ + endpoints = [PSCustomObject]@{ + 'DELETE /widgets' = [PSCustomObject]@{ + minVersion = '2.0' + parameters = [PSCustomObject]@{ names = '2.0'; kind = '2.0' } + bodyProperties = [PSCustomObject]@{} + } + } + } + $script:t4Endpoints = @( + [PSCustomObject]@{ Key = 'DELETE /widgets'; Method = 'DELETE'; Endpoint = '/widgets'; Resolved = $true; Cmdlet = 'Remove-PfbFixtureWidget'; File = 'x' } + ) + + # One resolved row so the endpoint has a real gap ('kind') to report either way, + # plus one row whose Surface is the variable under test. + function script:New-PfbT4Inventory { + param([string]$Surface) + @( + [PSCustomObject]@{ Cmdlet = 'Remove-PfbFixtureWidget'; Parameter = 'Name'; Surface = 'Typed' + WireName = 'names'; HasValidateSet = $false; ValidateSetValues = $null + Endpoint = 'widgets'; Method = 'DELETE'; File = 'x'; Line = 3 } + [PSCustomObject]@{ Cmdlet = 'Remove-PfbFixtureWidget'; Parameter = 'Zeta'; Surface = $Surface + WireName = $null; HasValidateSet = $false; ValidateSetValues = $null + Endpoint = $null; Method = $null; File = 'x'; Line = 4 } + ) + } + } + + It 'keeps confidence high for a row, which is an answer and not a gap' -ForEach @( + @{ Surface = 'NotWireParameter' } + @{ Surface = 'OutsideStandardRequest' } + ) { + $result = Get-PfbParameterCoverageGaps -CapabilityMap $t4CapMap -CmdletInventory (New-PfbT4Inventory -Surface $Surface) -CalledEndpoints $t4Endpoints + $gap = $result | Where-Object { $_.Endpoint -eq 'DELETE /widgets' } + $gap | Should -Not -BeNullOrEmpty -Because 'the endpoint must still be reported; only its confidence is at issue' + $gap.MissingQueryParameters | Should -Be @('kind') + $gap.Confidence.Level | Should -Be 'high' + $gap.Confidence.UnresolvedParameters | Should -BeNullOrEmpty + $gap.Confidence.Caveat | Should -BeNullOrEmpty + } + + It 'still lowers confidence for a row, which is a real gap in this tool''s reach' -ForEach @( + @{ Surface = 'AttributesOnly'; ExpectEscapeHatch = $true } + @{ Surface = 'TypedUnresolved'; ExpectEscapeHatch = $false } + ) { + # The control for the pair above. Without it, both tests would also pass against a + # function that had stopped lowering confidence for anything at all. + $result = Get-PfbParameterCoverageGaps -CapabilityMap $t4CapMap -CmdletInventory (New-PfbT4Inventory -Surface $Surface) -CalledEndpoints $t4Endpoints + $gap = $result | Where-Object { $_.Endpoint -eq 'DELETE /widgets' } + $gap.Confidence.Level | Should -Be 'partial' + @($gap.Confidence.UnresolvedParameters.Parameter) | Should -Be @('Zeta') + ($gap.Confidence.UnresolvedParameters | Where-Object Parameter -eq 'Zeta').Surface | Should -Be $Surface + ($gap.Confidence.UnresolvedParameters | Where-Object Parameter -eq 'Zeta').Line | Should -Be 4 + if ($ExpectEscapeHatch) { + $gap.Confidence.EscapeHatchOnly | Should -Be @('Zeta') + } + else { + $gap.Confidence.EscapeHatchOnly | Should -BeNullOrEmpty + } + } + + It 'does not let a non-applicable row''s wire name into the exposed set that suppresses gaps' { + # 'Typed' is the only branch that contributes to the exposed-wire-name set. A + # non-applicable row carries no WireName in practice, but the switch must not admit + # one if a future row ever does -- otherwise a request control could silently + # suppress a real missing field. + $inventory = @( + [PSCustomObject]@{ Cmdlet = 'Remove-PfbFixtureWidget'; Parameter = 'Name'; Surface = 'Typed' + WireName = 'names'; HasValidateSet = $false; ValidateSetValues = $null + Endpoint = 'widgets'; Method = 'DELETE'; File = 'x'; Line = 3 } + [PSCustomObject]@{ Cmdlet = 'Remove-PfbFixtureWidget'; Parameter = 'Eradicate'; Surface = 'NotWireParameter' + WireName = 'kind'; HasValidateSet = $false; ValidateSetValues = $null + Endpoint = $null; Method = $null; File = 'x'; Line = 4 } + ) + $result = Get-PfbParameterCoverageGaps -CapabilityMap $t4CapMap -CmdletInventory $inventory -CalledEndpoints $t4Endpoints + $gap = $result | Where-Object { $_.Endpoint -eq 'DELETE /widgets' } + $gap.MissingQueryParameters | Should -Be @('kind') + } + + It 'throws on a Surface value it has never been taught, rather than guessing a bucket' { + # The whole point of replacing the denylist: an unknown value must be a loud + # failure, not a silent default into either bucket. The message has to name the row + # so the failure is actionable. + { Get-PfbParameterCoverageGaps -CapabilityMap $t4CapMap -CmdletInventory (New-PfbT4Inventory -Surface 'SomethingNew') -CalledEndpoints $t4Endpoints } | + Should -Throw -ExpectedMessage '*SomethingNew*' + } + + It 'covers every Surface the inventory can actually emit' { + # Ties the switch's arms to the producer's enum. If a sixth Surface is added to + # tools/lib/PfbCmdletParamTools.ps1 without a branch here, the throw above turns + # from a guard into a real outage on the next report build -- and this test is what + # says so at the time the value is added. + foreach ($surface in @(Get-PfbParameterSurfaceName)) { + { Get-PfbParameterCoverageGaps -CapabilityMap $t4CapMap -CmdletInventory (New-PfbT4Inventory -Surface $surface) -CalledEndpoints $t4Endpoints } | + Should -Not -Throw -Because "'$surface' is a value Get-PfbCmdletParameterInventory emits today" + } + } + } } Describe 'Get-PfbValidateSetDrift' { diff --git a/tools/lib/PfbApiDriftTools.ps1 b/tools/lib/PfbApiDriftTools.ps1 index fd419cb9..6741c663 100644 --- a/tools/lib/PfbApiDriftTools.ps1 +++ b/tools/lib/PfbApiDriftTools.ps1 @@ -300,8 +300,11 @@ function Get-PfbParameterCoverageGaps { [PSCustomObject]@{ Level; UnresolvedParameters; EscapeHatchOnly; Caveat }: - Level is 'high' iff UnresolvedParameters is empty, else 'partial'. - UnresolvedParameters is [PSCustomObject]@{ Parameter; Surface; File; Line }[] - -- every non-Typed (AttributesOnly or TypedUnresolved) parameter on any - cmdlet calling this endpoint, Line coming from + -- every UNRESOLVED (AttributesOnly or TypedUnresolved) parameter on any + cmdlet calling this endpoint. "Unresolved", not "non-Typed": the two + non-applicable surfaces (NotWireParameter, OutsideStandardRequest) are non-Typed + too and are deliberately excluded, because a parameter that is not a wire field + at all is not a wire field this tool failed to find. Line comes from Get-PfbCmdletParameterInventory's Line field ($p.Extent.StartLineNumber) so every caveat is a click-through. - EscapeHatchOnly is the subset of UnresolvedParameters' Parameter names whose @@ -383,16 +386,36 @@ function Get-PfbParameterCoverageGaps { # report as unresolved for that cmdlet. $rows = @($inventoryByCmdlet[$cmdletName] | Where-Object { $null -ne $_ }) foreach ($row in $rows) { - if ($row.Surface -ne 'Typed') { - $unresolved.Add([PSCustomObject]@{ - Parameter = $row.Parameter - Surface = $row.Surface - File = $row.File - Line = $row.Line - }) - continue + # EXHAUSTIVE on Surface, and deliberately not `-ne 'Typed'`. The negation was a + # denylist: it read "anything I cannot resolve is doubt about this endpoint", + # so the two NON-APPLICABLE surfaces issue #141 Task 4 introduced would have + # been swept straight into $unresolved and demoted 'high' to 'partial' on the + # strength of a parameter that is not a wire field at all -- 34 real + # parameters, none of which could ever have covered a gap. A new Surface value + # must be assigned a meaning HERE, by hand, and until it is this throws rather + # than silently taking whichever side the negation happened to fall on. + switch ($row.Surface) { + 'Typed' { + if ($row.WireName) { [void]$exposedWireNames.Add($row.WireName) } + } + 'AttributesOnly' { + $unresolved.Add([PSCustomObject]@{ Parameter = $row.Parameter; Surface = $row.Surface; File = $row.File; Line = $row.Line }) + } + 'TypedUnresolved' { + $unresolved.Add([PSCustomObject]@{ Parameter = $row.Parameter; Surface = $row.Surface; File = $row.File; Line = $row.Line }) + } + # An audited request control: it steers the call rather than appearing in + # it, so it neither covers a gap nor casts doubt on one. + 'NotWireParameter' { } + # Its declaring function issues no Invoke-PfbApiRequest at all, so it is + # not a parameter of THIS endpoint in any sense. (Reachable in a real run + # only through a hand-built inventory: such a cmdlet contributes no called + # endpoint, so it does not normally join one of these groups.) + 'OutsideStandardRequest' { } + default { + throw ("Get-PfbParameterCoverageGaps: inventory row {0} -{1} carries Surface '{2}', which this function has never been taught to classify. Assign it explicitly -- as covering (like 'Typed'), as doubt-casting (like 'TypedUnresolved') or as non-applicable (like 'NotWireParameter') -- rather than letting it default." -f $cmdletName, $row.Parameter, $row.Surface) + } } - if ($row.WireName) { [void]$exposedWireNames.Add($row.WireName) } } } @@ -1042,6 +1065,12 @@ function Get-PfbWireNameCmdletCounts { $map = [System.Collections.Generic.Dictionary[string, object]]::new() foreach ($row in $CmdletInventory) { + # An ALLOWLIST despite being spelled as a negation: the condition admits Surface + # 'Typed' and nothing else, so a new Surface value is excluded by construction and + # cannot enter this map. Contrast Get-PfbParameterCoverageGaps, where the same + # spelling was a genuine denylist because the excluded branch is the one that ACTS. + # Nothing here needs a per-value decision, so this is left as it is on purpose rather + # than for want of noticing (issue #141 Task 4 consumer sweep). if ($row.Surface -ne 'Typed' -or -not $row.WireName) { continue } if (-not $map.ContainsKey($row.WireName)) { $map[$row.WireName] = [System.Collections.Generic.HashSet[string]]::new() } [void]$map[$row.WireName].Add($row.Cmdlet) From 44877b76ef12918e75cbdc0d74ee4787093c1132 Mon Sep 17 00:00:00 2001 From: Justin Emerson Date: Wed, 26 Aug 2026 19:48:08 -0700 Subject: [PATCH 11/29] fix(tools): separate non-applicable rows in the field-cmdlet map Build-PfbFieldCmdletMap.ps1 had three buckets and no reconciliation, so a Surface it had not been taught was filtered out by all three Where-Objects and vanished from the report without a word -- the quietest possible failure, and the one issue #141 exists to stop. Emit a fourth bucket, notApplicable, carrying `surface` per row. It is deliberately NOT folded into typedUnresolved: that list is read as "the tool could not find this field wire name", and these rows are not fields. The Markdown gets its own heading for the same reason -- a reader triaging work should not be handed six -Eradicate switches as work. Assert the partition instead of assuming it: every inventory row must land in exactly one of five buckets, the fifth being typed-with-ValidateSet, which is emitted nowhere because this report recommends ADDING one. A row that lands in none throws and names the counts. coverage-baseline.psd1: Build-PfbFieldCmdletMap.Tests.ps1 15 -> 25. The two new Describes exercise the script itself, which carries `#Requires -Version 7.0`, so they take the file existing PS7 gate. Measured on Windows PowerShell 5.1, not inferred. Co-Authored-By: Claude Opus 5 (1M context) --- Tests/Build-PfbFieldCmdletMap.Tests.ps1 | 193 ++++++++++++++++++++++++ Tests/coverage-baseline.psd1 | 10 +- tools/Build-PfbFieldCmdletMap.ps1 | 32 ++++ 3 files changed, 234 insertions(+), 1 deletion(-) diff --git a/Tests/Build-PfbFieldCmdletMap.Tests.ps1 b/Tests/Build-PfbFieldCmdletMap.Tests.ps1 index e56c6255..f799115a 100644 --- a/Tests/Build-PfbFieldCmdletMap.Tests.ps1 +++ b/Tests/Build-PfbFieldCmdletMap.Tests.ps1 @@ -282,6 +282,199 @@ Describe 'Build-PfbFieldCmdletMap' -Skip:($PSVersionTable.PSVersion.Major -lt 7) } } +Describe 'Every inventory row lands in exactly one of five buckets (issue #141 Task 4, Step 8)' -Skip:($PSVersionTable.PSVersion.Major -lt 7) { + # Before Task 4 this script had three buckets and no reconciliation: a row whose Surface + # it had not been taught was filtered out by all three `Where-Object`s and vanished from + # the report without a word. That is the quietest possible failure, and the two + # non-applicable surfaces Task 4 adds are exactly the kind of value that would have hit + # it. The fixture tree below deliberately populates all five buckets at once so the + # partition assertion has something to be wrong about. + + BeforeAll { + $script:partPublicDir = Join-Path $TestDrive 'PublicPartition' + New-Item -ItemType Directory -Path $partPublicDir -Force | Out-Null + + # Buckets 1 and 2: a typed parameter with no ValidateSet (reported) and one with a + # ValidateSet (deliberately reported nowhere -- this script recommends ADDING one). + Set-Content -Path (Join-Path $partPublicDir 'New-PfbPartitionWidget.ps1') -Value @' +function New-PfbPartitionWidget { + param( + [Parameter()] [string]$StableField, + [Parameter()] [ValidateSet('x', 'y')] [string]$ValidatedField, + [Parameter()] [PSCustomObject]$Array + ) + $body = @{} + if ($StableField) { $body["stable_field"] = $StableField } + if ($ValidatedField) { $body["changing_field"] = $ValidatedField } + Invoke-PfbApiRequest -Array $Array -Method POST -Endpoint 'widgets' -Body $body +} +'@ + + # Bucket 3: typed, unresolvable, and no -Attributes escape hatch. + Set-Content -Path (Join-Path $partPublicDir 'Get-PfbPartitionUnresolved.ps1') -Value @' +function Get-PfbPartitionUnresolved { + param([Parameter()] [string]$Mystery, [Parameter()] [PSCustomObject]$Array) + Invoke-PfbApiRequest -Array $Array -Method GET -Endpoint 'widgets' +} +'@ + + # Bucket 4: same unresolvable shape, but an -Attributes escape hatch exists. + Set-Content -Path (Join-Path $partPublicDir 'Get-PfbPartitionAttributed.ps1') -Value @' +function Get-PfbPartitionAttributed { + param([Parameter()] [string]$Mystery, [Parameter()] [hashtable]$Attributes, [Parameter()] [PSCustomObject]$Array) + Invoke-PfbApiRequest -Array $Array -Method GET -Endpoint 'widgets' +} +'@ + + # Bucket 5a: NotWireParameter. The function is named Remove-PfbBucket on purpose -- + # the surface is keyed on the audited 'Cmdlet|Parameter' identity, never on the + # parameter's name, so a fixture called anything else could not reach this branch and + # a test that renamed it would silently stop testing it. + Set-Content -Path (Join-Path $partPublicDir 'Remove-PfbBucket.ps1') -Value @' +function Remove-PfbBucket { + param([Parameter()] [string]$Name, [Parameter()] [switch]$Eradicate, [Parameter()] [PSCustomObject]$Array) + if (-not $Eradicate) { throw 'refusing without -Eradicate' } + $queryParams = @{} + $queryParams["names"] = $Name + Invoke-PfbApiRequest -Array $Array -Method DELETE -Endpoint 'buckets' -QueryParams $queryParams +} +'@ + + # Bucket 5b: OutsideStandardRequest -- no Invoke-PfbApiRequest call anywhere. + Set-Content -Path (Join-Path $partPublicDir 'Set-PfbPartitionContext.ps1') -Value @' +function Set-PfbPartitionContext { + param([Parameter()] [string]$Context) + $script:PfbPartitionContext = $Context +} +'@ + + $script:partOutput = Join-Path $TestDrive 'partitionOutput/map.json' + $script:partReport = Join-Path $TestDrive 'partitionOutput/report.md' + & $buildScript -SpecsDirectory $specsDir -PublicDirectory $partPublicDir -OutputPath $partOutput -ReportPath $partReport + $script:partManifest = Get-Content -Path $partOutput -Raw | ConvertFrom-Json -Depth 20 + $script:partInventory = @(Get-PfbCmdletParameterInventory -PublicDirectory $partPublicDir) + + function script:Get-PfbPartitionIdentity { + param($Rows, [string]$CmdletProperty = 'cmdlet', [string]$ParameterProperty = 'parameter') + @($Rows | ForEach-Object { '{0}|{1}' -f $_.$CmdletProperty, $_.$ParameterProperty }) + } + } + + It 'accounts for every row exactly once across the four emitted buckets plus the deliberately-unemitted fifth' { + $emitted = @() + $emitted += Get-PfbPartitionIdentity -Rows $partManifest.entries + $emitted += Get-PfbPartitionIdentity -Rows $partManifest.attributesOnly + $emitted += Get-PfbPartitionIdentity -Rows $partManifest.typedUnresolved + $emitted += Get-PfbPartitionIdentity -Rows $partManifest.notApplicable + $unemitted = Get-PfbPartitionIdentity -Rows @($partInventory | Where-Object { $_.Surface -eq 'Typed' -and $_.HasValidateSet }) -CmdletProperty 'Cmdlet' -ParameterProperty 'Parameter' + + $all = @($emitted) + @($unemitted) + $expected = Get-PfbPartitionIdentity -Rows $partInventory -CmdletProperty 'Cmdlet' -ParameterProperty 'Parameter' + + # Count first: set equality alone would pass if one row were classified into two + # buckets, which is the other half of "exactly one". + $all.Count | Should -Be $expected.Count + @($all | Sort-Object) | Should -Be @($expected | Sort-Object) + } + + It 'populates the bucket, so the reconciliation above is not vacuous' -ForEach @( + @{ Bucket = 'entries' } + @{ Bucket = 'attributesOnly' } + @{ Bucket = 'typedUnresolved' } + @{ Bucket = 'notApplicable' } + ) { + @($partManifest.$Bucket).Count | Should -BeGreaterThan 0 + } + + It 'keeps the non-applicable rows out of typedUnresolved and records which reason applies to each' { + $notApplicable = @($partManifest.notApplicable) + $notApplicable.Count | Should -Be 2 + ($notApplicable | Where-Object { $_.cmdlet -eq 'Remove-PfbBucket' }).surface | Should -Be 'NotWireParameter' + ($notApplicable | Where-Object { $_.cmdlet -eq 'Set-PfbPartitionContext' }).surface | Should -Be 'OutsideStandardRequest' + + # The control for the two exclusions below: typedUnresolved is populated, so + # -Not -Contain is testing separation and not an empty list. + @($partManifest.typedUnresolved).Count | Should -Be 1 + (Get-PfbPartitionIdentity -Rows $partManifest.typedUnresolved) | Should -Not -Contain 'Remove-PfbBucket|Eradicate' + (Get-PfbPartitionIdentity -Rows $partManifest.typedUnresolved) | Should -Not -Contain 'Set-PfbPartitionContext|Context' + } + + It 'gives the non-applicable rows their own Markdown heading, with the reason on each line' { + $text = Get-Content -Path $partReport -Raw + $text | Should -Match '## Not a wire field \(nothing to inspect\): 2' + $text | Should -Match '- `Remove-PfbBucket -Eradicate` \(NotWireParameter\)' + $text | Should -Match '- `Set-PfbPartitionContext -Context` \(OutsideStandardRequest\)' + # And the section it must NOT have been folded into still reports its own single row. + $text | Should -Match '## Typed but unresolved wire name \(needs manual inspection\): 1' + } + + It 'emits a typed parameter that already has a ValidateSet nowhere at all' { + $withValidateSet = @($partInventory | Where-Object { $_.Surface -eq 'Typed' -and $_.HasValidateSet }) + $withValidateSet.Count | Should -Be 1 -Because 'the fixture must actually contain one, or this test proves nothing' + (Get-PfbPartitionIdentity -Rows $partManifest.entries) | Should -Not -Contain 'New-PfbPartitionWidget|ValidatedField' + (Get-PfbPartitionIdentity -Rows $partManifest.attributesOnly) | Should -Not -Contain 'New-PfbPartitionWidget|ValidatedField' + (Get-PfbPartitionIdentity -Rows $partManifest.typedUnresolved) | Should -Not -Contain 'New-PfbPartitionWidget|ValidatedField' + (Get-PfbPartitionIdentity -Rows $partManifest.notApplicable) | Should -Not -Contain 'New-PfbPartitionWidget|ValidatedField' + (Get-Content -Path $partReport -Raw) | Should -Not -Match 'ValidatedField' + } +} + +Describe 'The partition assertion refuses a Surface the script has not been taught (issue #141 Task 4, Step 8)' -Skip:($PSVersionTable.PSVersion.Major -lt 7) { + # No Public/ fixture can produce an unknown Surface -- the ladder only ever emits the five + # declared values -- so the only honest way to exercise the assertion is to replace the + # inventory function the script depends on. The script dot-sources tools/lib/ relative to + # its OWN location, which would overwrite any override made from here, so the whole + # script + lib pair is copied to TestDrive and the override appended to the copied lib. + + BeforeAll { + $script:shimDir = Join-Path $TestDrive 'toolsShim' + New-Item -ItemType Directory -Path $shimDir -Force | Out-Null + Copy-Item -Path (Join-Path $repoRoot 'tools/lib') -Destination $shimDir -Recurse -Force + Copy-Item -Path $buildScript -Destination $shimDir -Force + $script:shimScript = Join-Path $shimDir 'Build-PfbFieldCmdletMap.ps1' + + Add-Content -Path (Join-Path $shimDir 'lib/PfbCmdletParamTools.ps1') -Value @' + +# --- issue #141 Task 4 test shim, appended by Tests/Build-PfbFieldCmdletMap.Tests.ps1 --- +function Get-PfbCmdletParameterInventory { + [CmdletBinding()] + param([Parameter(Mandatory)][string]$PublicDirectory) + return @([PSCustomObject]@{ + File = 'shim.ps1'; Line = 1; Cmdlet = 'Get-PfbShimmed'; Parameter = 'Zeta' + HasValidateSet = $false; ValidateSetValues = $null + WireName = $env:PFB_T4_SHIM_WIRENAME; TargetVariable = $null; WireSurface = $null + Surface = $env:PFB_T4_SHIM_SURFACE; Endpoint = $null; Method = $null + }) +} +'@ + $script:shimOutput = Join-Path $TestDrive 'shimOutput/map.json' + $script:shimReport = Join-Path $TestDrive 'shimOutput/report.md' + } + + AfterAll { + Remove-Item -Path Env:PFB_T4_SHIM_SURFACE -ErrorAction SilentlyContinue + Remove-Item -Path Env:PFB_T4_SHIM_WIRENAME -ErrorAction SilentlyContinue + } + + It 'throws, naming the counts, when a row carries a Surface no bucket claims' { + $env:PFB_T4_SHIM_SURFACE = 'SomethingNew' + $env:PFB_T4_SHIM_WIRENAME = '' + { & $shimScript -SpecsDirectory $specsDir -PublicDirectory $publicDir -OutputPath $shimOutput -ReportPath $shimReport } | + Should -Throw -ExpectedMessage '*Inventory partition is incomplete: 1 rows in, 0 classified*' + } + + It 'builds cleanly through the same shim when the row carries a Surface a bucket does claim' { + # The control. Without it the test above would still pass against a script that threw + # unconditionally, or one whose copied-and-shimmed harness was broken in some way that + # had nothing to do with the Surface value. + $env:PFB_T4_SHIM_SURFACE = 'Typed' + $env:PFB_T4_SHIM_WIRENAME = 'stable_field' + { & $shimScript -SpecsDirectory $specsDir -PublicDirectory $publicDir -OutputPath $shimOutput -ReportPath $shimReport } | + Should -Not -Throw + (Get-Content -Path $shimOutput -Raw | ConvertFrom-Json -Depth 20).entries.parameter | Should -Be 'Zeta' + } +} + Describe 'Build-PfbFieldCmdletMap (real generated artifacts, skips gracefully if absent)' -Skip:($PSVersionTable.PSVersion.Major -lt 7) { It 'produces a manifest against the real Public/ tree and tools/specs/ cache' { $realSpecsDir = Join-Path $repoRoot 'tools/specs' diff --git a/Tests/coverage-baseline.psd1 b/Tests/coverage-baseline.psd1 index 862fa8a9..0f13a95d 100644 --- a/Tests/coverage-baseline.psd1 +++ b/Tests/coverage-baseline.psd1 @@ -222,6 +222,9 @@ # the gate asserts that reconciliation on every run, so a container the walk misses is # a red rather than a quietly smaller number. # + # Since seeding, one entry has moved: Build-PfbFieldCmdletMap.Tests.ps1 15 -> 25 for + # issue #141 Task 4 (see its own note below), so the entries now sum to 307. + # # Every entry is the same cause: a Describe carrying # -Skip:($PSVersionTable.PSVersion.Major -lt 7), or a whole tooling file gated that way, # because the generator or spec-walking code under test needs pwsh 7. They RUN on 7 -- @@ -240,7 +243,12 @@ 'Update-PfbTestModuleImport.Tests.ps1' = 34 'PfbPipelineSelectorTools.Tests.ps1' = 33 'PfbSelectorProbeHarness.Tests.ps1' = 16 - 'Build-PfbFieldCmdletMap.Tests.ps1' = 15 + # 15 -> 25 for issue #141 Task 4: two new Describes (the five-bucket partition + # reconciliation and the unknown-Surface refusal) exercise + # tools/Build-PfbFieldCmdletMap.ps1 itself, which carries `#Requires -Version 7.0`, + # so they take the file's existing PS7 gate. Measured on Windows PowerShell 5.1, + # not inferred -- 0 passed / 25 skipped for this file alone. + 'Build-PfbFieldCmdletMap.Tests.ps1' = 25 'PfbPipelineSelectorRail.Tests.ps1' = 11 'Build-PfbResponseShapeMap.Tests.ps1' = 9 'Build-PfbDeadKeyReport.Tests.ps1' = 6 diff --git a/tools/Build-PfbFieldCmdletMap.ps1 b/tools/Build-PfbFieldCmdletMap.ps1 index 15ed1d9b..23e0da65 100644 --- a/tools/Build-PfbFieldCmdletMap.ps1 +++ b/tools/Build-PfbFieldCmdletMap.ps1 @@ -78,6 +78,31 @@ $candidates = @($inventory | Where-Object { $_.Surface -eq 'Typed' -and -not $_. $attributesOnly = @($inventory | Where-Object { $_.Surface -eq 'AttributesOnly' } | ForEach-Object { [ordered]@{ cmdlet = $_.Cmdlet; parameter = $_.Parameter } }) $typedUnresolved = @($inventory | Where-Object { $_.Surface -eq 'TypedUnresolved' } | ForEach-Object { [ordered]@{ cmdlet = $_.Cmdlet; parameter = $_.Parameter } }) +# Non-applicable residual (issue #141 Task 4). Its own collection, NOT folded into +# typedUnresolved: that list is read as "the tool could not find this field's wire name", and +# these rows are not fields. `surface` is carried per row because the two reasons are not +# interchangeable to a reader deciding what to do next -- 'NotWireParameter' is an audited +# request control and needs nothing done, while 'OutsideStandardRequest' says the whole cmdlet +# sits off the standard request path and is where a future reviewer would look first if that +# ever stopped being true. +$notApplicable = @($inventory | Where-Object { $_.Surface -in @('NotWireParameter', 'OutsideStandardRequest') } | + ForEach-Object { [ordered]@{ cmdlet = $_.Cmdlet; parameter = $_.Parameter; surface = $_.Surface } }) + +# Every inventory row lands in exactly one of five buckets, and this asserts it rather than +# assuming it. Four are emitted below; the fifth (Typed WITH a ValidateSet) is deliberately +# emitted nowhere -- this report recommends ADDING a ValidateSet, and those parameters already +# have one. Without this check a Surface value added upstream and not taught to this script +# would simply disappear from the report, which is the quietest possible failure and exactly +# the one issue #141 exists to stop. +$typedWithValidateSet = @($inventory | Where-Object { $_.Surface -eq 'Typed' -and $_.HasValidateSet }) +$partitioned = $candidates.Count + $typedWithValidateSet.Count + $attributesOnly.Count + $typedUnresolved.Count + $notApplicable.Count +if ($partitioned -ne $inventory.Count) { + throw ("Inventory partition is incomplete: $($inventory.Count) rows in, $partitioned classified " + + "(typed-no-validateset $($candidates.Count), typed-with-validateset $($typedWithValidateSet.Count), " + + "attributesOnly $($attributesOnly.Count), typedUnresolved $($typedUnresolved.Count), notApplicable $($notApplicable.Count)). " + + 'A Surface value this script has not been taught would otherwise vanish from the report silently.') +} + $entries = foreach ($cand in $candidates) { $hint = Get-PfbResourceHint -CmdletName $cand.Cmdlet $resolution = Resolve-PfbFieldValueEnum -WireName $cand.WireName -ResourceHint $hint -Endpoint $cand.Endpoint -Method $cand.Method -History $history -OldestVersion $oldestVersion @@ -100,6 +125,7 @@ $manifest = [ordered]@{ entries = $entries attributesOnly = $attributesOnly typedUnresolved = $typedUnresolved + notApplicable = $notApplicable } $outputDir = Split-Path -Parent $OutputPath @@ -147,6 +173,12 @@ $mdLines.Add("## Typed but unresolved wire name (needs manual inspection): $($ty $mdLines.Add('') foreach ($u in $typedUnresolved) { $mdLines.Add("- ``$($u.cmdlet) -$($u.parameter)``") } $mdLines.Add('') +$mdLines.Add("## Not a wire field (nothing to inspect): $($notApplicable.Count)") +$mdLines.Add('') +$mdLines.Add('Listed separately from the section above on purpose: these parameters are not fields whose wire name went unresolved, so they are not work. `NotWireParameter` is an audited request control (`-Eradicate`, `-Force`); `OutsideStandardRequest` means the declaring cmdlet issues no `Invoke-PfbApiRequest` call at all.') +$mdLines.Add('') +foreach ($n in $notApplicable) { $mdLines.Add("- ``$($n.cmdlet) -$($n.parameter)`` ($($n.surface))") } +$mdLines.Add('') Set-Content -Path $ReportPath -Value ($mdLines -join "`n") -Encoding UTF8 Write-Host "Wrote $($entries.Count) entries to $OutputPath and $ReportPath" -ForegroundColor Green From c4b203086dc6f325983865133f2a56323bfb44e5 Mon Sep 17 00:00:00 2001 From: Justin Emerson Date: Wed, 26 Aug 2026 22:10:00 -0700 Subject: [PATCH 12/29] fix(tools): stop claiming non-applicable rows miss the wire The Markdown heading for the non-applicable bucket read "Not a wire field (nothing to inspect)". That is a confident false statement about six real cmdlets. Connect-PfbArray -Username/-Password are OutsideStandardRequest and are POSTed to /api/login by hand (Public/Connection/Connect-PfbArray.ps1:331 builds the body, :351 sends it via Invoke-WebRequest); -ClientId/-Issuer/-KeyId reach the OAuth2 token request the same way. Plan Correction 1 requires these rows be classified as outside the standard request-payload resolver, NOT as having no wire effect, and Completion Condition 7 spells out the same limit. Publishing the stronger claim in prose is the same class of error as writing a wrong row into JSON -- the never-guess contract does not stop at the artifact boundary. The heading now names the resolver's reach rather than the parameter's behaviour, and a disclaimer under it says what OutsideStandardRequest does and does not mean, with the Connect-PfbArray case as the worked example. The new test asserts BOTH halves: the retired wording must not come back, and the disclaimer must be present. Asserting only the first could be satisfied by deleting the section entirely, which would lose the rows instead of describing them correctly. Coverage pin: Build-PfbFieldCmdletMap.Tests.ps1 25 -> 26 on Windows PowerShell 5.1 for that test, measured not inferred. The header note's arithmetic was also wrong independently of this change -- it said one entry had moved since the 297 seed and summed to 307, but PfbApiDriftTools.Tests.ps1 had already moved 8 -> 12 for issue #113. Recomputed from the map: 297 + 4 + 11 = 312. --- Tests/Build-PfbFieldCmdletMap.Tests.ps1 | 18 +++++++++++++++++- Tests/coverage-baseline.psd1 | 24 ++++++++++++++++++------ tools/Build-PfbFieldCmdletMap.ps1 | 25 ++++++++++++++++++------- 3 files changed, 53 insertions(+), 14 deletions(-) diff --git a/Tests/Build-PfbFieldCmdletMap.Tests.ps1 b/Tests/Build-PfbFieldCmdletMap.Tests.ps1 index f799115a..ffe9658b 100644 --- a/Tests/Build-PfbFieldCmdletMap.Tests.ps1 +++ b/Tests/Build-PfbFieldCmdletMap.Tests.ps1 @@ -401,13 +401,29 @@ function Set-PfbPartitionContext { It 'gives the non-applicable rows their own Markdown heading, with the reason on each line' { $text = Get-Content -Path $partReport -Raw - $text | Should -Match '## Not a wire field \(nothing to inspect\): 2' + $text | Should -Match "## Outside this resolver's reach \(no standard-request field to inspect\): 2" $text | Should -Match '- `Remove-PfbBucket -Eradicate` \(NotWireParameter\)' $text | Should -Match '- `Set-PfbPartitionContext -Context` \(OutsideStandardRequest\)' # And the section it must NOT have been folded into still reports its own single row. $text | Should -Match '## Typed but unresolved wire name \(needs manual inspection\): 1' } + It 'says only that the resolver cannot see these payloads, never that the parameters miss the wire' { + # This heading used to read "Not a wire field (nothing to inspect)", which is a + # confident FALSE claim about six real cmdlets: Connect-PfbArray -Username/-Password + # reach /api/login through Invoke-WebRequest (Public/Connection/Connect-PfbArray.ps1:331 + # and :351), and -ClientId/-Issuer/-KeyId reach the OAuth2 token request the same way. + # Plan Correction 1 requires these rows be classified as outside the standard + # request-payload resolver, NOT as having no wire effect. Both halves are asserted: + # the retired wording must not come back, and the disclaimer must actually be present, + # or a future reword could satisfy the first half by deleting the section entirely. + $text = Get-Content -Path $partReport -Raw + $text | Should -Not -Match 'Not a wire field' + $text | Should -Not -Match 'nothing to inspect' + $text | Should -Match 'does \*\*not\*\* mean the parameter has no wire effect' + $text | Should -Match 'Connect-PfbArray -Username' + } + It 'emits a typed parameter that already has a ValidateSet nowhere at all' { $withValidateSet = @($partInventory | Where-Object { $_.Surface -eq 'Typed' -and $_.HasValidateSet }) $withValidateSet.Count | Should -Be 1 -Because 'the fixture must actually contain one, or this test proves nothing' diff --git a/Tests/coverage-baseline.psd1 b/Tests/coverage-baseline.psd1 index 0f13a95d..cb40fe01 100644 --- a/Tests/coverage-baseline.psd1 +++ b/Tests/coverage-baseline.psd1 @@ -222,8 +222,18 @@ # the gate asserts that reconciliation on every run, so a container the walk misses is # a red rather than a quietly smaller number. # - # Since seeding, one entry has moved: Build-PfbFieldCmdletMap.Tests.ps1 15 -> 25 for - # issue #141 Task 4 (see its own note below), so the entries now sum to 307. + # Since seeding, TWO entries have moved, and the arithmetic below accounts for both: + # PfbApiDriftTools.Tests.ps1 8 -> 12 for issue #113 (+4), and + # Build-PfbFieldCmdletMap.Tests.ps1 15 -> 26 for issue #141 Task 4 (+11). Each carries + # its own note at its entry. 297 + 4 + 11 = 312, which is what the entries sum to. + # + # Recompute this total from the map itself rather than adjusting it by the delta in + # hand -- an earlier revision of this note said "one entry has moved ... sum to 307", + # which was wrong in both halves because it was written against the #141 change alone + # and never reconciled with #113's, already landed. A running total that is only ever + # incremented drifts silently; the check is + # `(Import-PowerShellDataFile Tests/coverage-baseline.psd1).ExpectedSkips.Values | + # Measure-Object -Sum`. # # Every entry is the same cause: a Describe carrying # -Skip:($PSVersionTable.PSVersion.Major -lt 7), or a whole tooling file gated that way, @@ -243,12 +253,14 @@ 'Update-PfbTestModuleImport.Tests.ps1' = 34 'PfbPipelineSelectorTools.Tests.ps1' = 33 'PfbSelectorProbeHarness.Tests.ps1' = 16 - # 15 -> 25 for issue #141 Task 4: two new Describes (the five-bucket partition + # 15 -> 26 for issue #141 Task 4: two new Describes (the five-bucket partition # reconciliation and the unknown-Surface refusal) exercise # tools/Build-PfbFieldCmdletMap.ps1 itself, which carries `#Requires -Version 7.0`, - # so they take the file's existing PS7 gate. Measured on Windows PowerShell 5.1, - # not inferred -- 0 passed / 25 skipped for this file alone. - 'Build-PfbFieldCmdletMap.Tests.ps1' = 25 + # so they take the file's existing PS7 gate. 25 of those 26 landed with the Task 4 + # commits; the 26th is the review-fix test asserting the non-applicable section + # never claims those parameters miss the wire. Measured on Windows PowerShell 5.1, + # not inferred -- 0 passed / 26 skipped for this file alone. + 'Build-PfbFieldCmdletMap.Tests.ps1' = 26 'PfbPipelineSelectorRail.Tests.ps1' = 11 'Build-PfbResponseShapeMap.Tests.ps1' = 9 'Build-PfbDeadKeyReport.Tests.ps1' = 6 diff --git a/tools/Build-PfbFieldCmdletMap.ps1 b/tools/Build-PfbFieldCmdletMap.ps1 index 23e0da65..1f5f0eef 100644 --- a/tools/Build-PfbFieldCmdletMap.ps1 +++ b/tools/Build-PfbFieldCmdletMap.ps1 @@ -80,11 +80,22 @@ $typedUnresolved = @($inventory | Where-Object { $_.Surface -eq 'TypedUnresolved # Non-applicable residual (issue #141 Task 4). Its own collection, NOT folded into # typedUnresolved: that list is read as "the tool could not find this field's wire name", and -# these rows are not fields. `surface` is carried per row because the two reasons are not -# interchangeable to a reader deciding what to do next -- 'NotWireParameter' is an audited -# request control and needs nothing done, while 'OutsideStandardRequest' says the whole cmdlet -# sits off the standard request path and is where a future reviewer would look first if that -# ever stopped being true. +# neither of these rows is a standard-request field whose name went missing. +# +# What that does NOT license is the stronger claim that the parameter never reaches the wire. +# 'OutsideStandardRequest' is a statement about THIS RESOLVER'S REACH -- the declaring cmdlet +# issues no Invoke-PfbApiRequest call, so there is no standard payload to read a key out of -- +# and several of these parameters demonstrably do reach the array by other means: +# Public/Connection/Connect-PfbArray.ps1:331 builds @{ username = $Username; password = ... } +# and POSTs it to /api/login at :351 via Invoke-WebRequest. Rendering these rows as "not a +# wire field" would be a confident false statement about six real cmdlets, which is the same +# class of error the never-guess contract exists to prevent, merely in prose instead of JSON. +# +# `surface` is carried per row because the two reasons are not interchangeable to a reader +# deciding what to do next -- 'NotWireParameter' is an audited request control with no query or +# body key and needs nothing done, while 'OutsideStandardRequest' says the whole cmdlet sits off +# the standard request path and is where a future reviewer would look first if that ever stopped +# being true. $notApplicable = @($inventory | Where-Object { $_.Surface -in @('NotWireParameter', 'OutsideStandardRequest') } | ForEach-Object { [ordered]@{ cmdlet = $_.Cmdlet; parameter = $_.Parameter; surface = $_.Surface } }) @@ -173,9 +184,9 @@ $mdLines.Add("## Typed but unresolved wire name (needs manual inspection): $($ty $mdLines.Add('') foreach ($u in $typedUnresolved) { $mdLines.Add("- ``$($u.cmdlet) -$($u.parameter)``") } $mdLines.Add('') -$mdLines.Add("## Not a wire field (nothing to inspect): $($notApplicable.Count)") +$mdLines.Add("## Outside this resolver's reach (no standard-request field to inspect): $($notApplicable.Count)") $mdLines.Add('') -$mdLines.Add('Listed separately from the section above on purpose: these parameters are not fields whose wire name went unresolved, so they are not work. `NotWireParameter` is an audited request control (`-Eradicate`, `-Force`); `OutsideStandardRequest` means the declaring cmdlet issues no `Invoke-PfbApiRequest` call at all.') +$mdLines.Add('Listed separately from the section above on purpose: neither is a standard-request field whose wire name went unresolved. `NotWireParameter` is an audited request control (`-Eradicate`, `-Force`) with no query or body key. `OutsideStandardRequest` means the declaring cmdlet issues no `Invoke-PfbApiRequest` call, so this resolver cannot see its payload -- it does **not** mean the parameter has no wire effect; `Connect-PfbArray -Username`/`-Password`, for example, reach `/api/login` through bespoke HTTP.') $mdLines.Add('') foreach ($n in $notApplicable) { $mdLines.Add("- ``$($n.cmdlet) -$($n.parameter)`` ($($n.surface))") } $mdLines.Add('') From 3498fc7a39677c25e297265410f5f9ca5896a549 Mon Sep 17 00:00:00 2001 From: Justin Emerson Date: Wed, 26 Aug 2026 22:10:22 -0700 Subject: [PATCH 13/29] fix(tools): make the declaration file carry its own baseline ref Compare-PfbInventoryTuple.ps1 defaulted -BaselineRef to origin/main while the declaration file recorded nothing about the ref its tuples were measured at, so the documented invocation only worked by coincidence. On a stacked branch, origin/main is not an ancestor of the measured ref, and every improvement made by the branches in between is then reported as an undeclared change. The next maintainer either concludes the tool is broken or pastes those rows in to silence it -- pre-authorising movement nobody reviewed, which is the anti-rubber-stamp rail running backwards. The file is now an object with a required baselineRef, which wins over the parameter default when -BaselineRef is not explicitly passed. Explicit -BaselineRef still overrides, so comparing declarations against some other ref stays possible. The resolved ref and its provenance are printed on every run. A bare array is refused with its own diagnostic rather than quietly accepted. That refusal needs ConvertFrom-Json -NoEnumerate: without it the pipeline unrolls a ONE-element JSON array into a bare PSCustomObject, the -is [array] test never fires, and a single-declaration array falls through to the less specific "no baselineRef" error. Both refusal paths were probed and each now emits its own message. Retirement is documented rather than made a softer rail. Once a declared change lands, every entry matches nothing and the run goes red with one STALE-DECL per entry; the script now prints a NOTE naming that case and pointing at tools/inventory-tuple-baselines/landed/, which it never reads. Teaching the unused-declaration rail to tolerate the expected case would also teach it to tolerate the typo'd key it was built to find. Also resolves tar by absolute path on Windows: a bare `tar` under a pwsh launched from Git Bash gets GNU tar, which reads the leading C: of the archive path as a remote host spec and aborts with "Cannot connect to C: resolve failed". --- tools/Compare-PfbInventoryTuple.ps1 | 107 +++++- .../issue-141-task4.json | 349 +++++++++--------- 2 files changed, 267 insertions(+), 189 deletions(-) diff --git a/tools/Compare-PfbInventoryTuple.ps1 b/tools/Compare-PfbInventoryTuple.ps1 index 2723ead0..119414d7 100644 --- a/tools/Compare-PfbInventoryTuple.ps1 +++ b/tools/Compare-PfbInventoryTuple.ps1 @@ -23,16 +23,47 @@ .PARAMETER RepoPath The repository (or worktree) to compare. Defaults to this script's parent. .PARAMETER BaselineRef - Any git ref resolvable in -RepoPath. Defaults to origin/main. + Any git ref resolvable in -RepoPath. Defaults to origin/main -- but a declaration file is + only valid against the ref it was MEASURED at, so when -DeclarationPath is supplied and this + parameter is not explicitly passed, the file's own `baselineRef` is used instead, and the + resolved ref plus its provenance are printed. Passing -BaselineRef explicitly always wins, + which is what keeps "compare these declarations against some other ref" possible. + + Getting this wrong is not a cosmetic failure. On a stacked branch, origin/main is not an + ancestor of the ref the declarations were measured at, so every improvement made by the + branches in between is reported as an undeclared change. The next maintainer then either + concludes the tool is broken or pastes those rows in to silence it -- pre-authorising + movement nobody reviewed, which is the anti-rubber-stamp rail running in reverse. .PARAMETER DeclarationPath - Optional JSON file: an array of { "key": "|", "from": "", - "to": "" }, where a tuple is 'Surface|WireName|WireSurface|Method|Endpoint' with - $null rendered as the empty string -- exactly what this script prints for an undeclared - change, so a reviewed change can be pasted straight in. - tools/inventory-tuple-baselines/issue-141-task4.json is the worked example. + Optional JSON file. An OBJECT, not a bare array, with two required keys: + + { + "baselineRef": "", + "declarations": [ { "key": "|", "from": "", "to": "" } ] + } + + A tuple is 'Surface|WireName|WireSurface|Method|Endpoint' with $null rendered as the empty + string -- exactly what this script prints for an undeclared change, so a reviewed change can + be pasted straight in. A bare array is REFUSED rather than quietly accepted, because an array + cannot carry the ref it was measured at and that omission is the defect described under + -BaselineRef. tools/inventory-tuple-baselines/issue-141-task4.json is the worked example. + + RETIREMENT, and why it is a documented step rather than a softer rail. A declaration file + describes a change that has not landed yet. Once its commits ARE the baseline, every entry + matches nothing and IsClean goes false with one STALE-DECL per entry. That is correct: a + declaration matching nothing is exactly what the unused-declaration rail exists to catch, and + teaching the rail to tolerate the expected case would also teach it to tolerate the typo'd + key it was built to find. So retire the file when the change merges -- move it to + tools/inventory-tuple-baselines/landed/, which this script never reads. The record is kept, + the gate stops firing, and nothing is pre-authorised for a future run. .EXAMPLE - ./tools/Compare-PfbInventoryTuple.ps1 -BaselineRef origin/main ` + # Normal use. The ref comes from the declaration file, so this is correct even on a stacked + # branch whose base is not an ancestor of origin/main. + ./tools/Compare-PfbInventoryTuple.ps1 ` -DeclarationPath ./tools/inventory-tuple-baselines/issue-141-task4.json +.EXAMPLE + # No declarations: report every row that moved against origin/main. Exits 1 if any did. + ./tools/Compare-PfbInventoryTuple.ps1 -BaselineRef origin/main #> [CmdletBinding()] param( @@ -67,6 +98,47 @@ $hostExe = [System.Diagnostics.Process]::GetCurrentProcess().MainModule.FileName $scratch = Join-Path ([System.IO.Path]::GetTempPath()) ("pfb-tuple-" + [guid]::NewGuid().ToString('N')) New-Item -ItemType Directory -Path $scratch -Force | Out-Null +# Windows ships bsdtar as %SystemRoot%\System32\tar.exe, but PATH order decides which `tar` a +# bare invocation gets, and under a pwsh launched from Git Bash it gets GNU tar. GNU tar parses +# the leading `C:` of the archive path as a REMOTE HOST spec and aborts with +# "Cannot connect to C: resolve failed", so the extraction fails on a machine where the identical +# command works from a native PowerShell. Resolve the Windows binary by absolute path instead of +# trusting PATH. +$tarExe = if ($IsWindows) { Join-Path $env:SystemRoot 'System32\tar.exe' } else { 'tar' } + +# Declarations are read BEFORE the archive, because the file carries the ref the archive must be +# taken at. Reading it afterwards would mean resolving the baseline from a default that the file +# is about to contradict. +$declarations = @() +if ($DeclarationPath) { + # -NoEnumerate matters: without it a ONE-element JSON array is unrolled by the pipeline into + # a bare PSCustomObject, so the array check below silently misses the single-declaration case + # and the file falls through to the less specific 'no baselineRef' error instead. + $declarationFile = Get-Content -Path $DeclarationPath -Raw | ConvertFrom-Json -NoEnumerate + if ($declarationFile -is [array]) { + throw ("Declaration file '$DeclarationPath' is a bare array. It must be an object with " + + "'baselineRef' and 'declarations' keys -- an array cannot record the ref its tuples " + + 'were measured against, and comparing them against the wrong ref reports every ' + + 'intervening improvement as an undeclared change.') + } + if ([string]::IsNullOrWhiteSpace([string]$declarationFile.baselineRef)) { + throw "Declaration file '$DeclarationPath' has no 'baselineRef'. See .PARAMETER DeclarationPath." + } + $declarations = @($declarationFile.declarations | ForEach-Object { + [PSCustomObject]@{ Key = $_.key; From = $_.from; To = $_.to } + }) + + # An explicit -BaselineRef always wins; otherwise the file's ref beats the parameter default. + if (-not $PSBoundParameters.ContainsKey('BaselineRef')) { + $BaselineRef = [string]$declarationFile.baselineRef + $refSource = "declaration file" + } + else { + $refSource = '-BaselineRef (explicit; overrides the declaration file''s {0})' -f $declarationFile.baselineRef + } +} +if (-not $refSource) { $refSource = if ($PSBoundParameters.ContainsKey('BaselineRef')) { '-BaselineRef' } else { 'parameter default' } } + function Read-PfbTupleDump { param([string]$TreePath, [string]$DumpScript) @@ -102,22 +174,15 @@ try { $archive = Join-Path $scratch 'baseline.tar' & git -C $RepoPath archive --format=tar --output=$archive $BaselineRef tools Public if ($LASTEXITCODE -ne 0) { throw "git archive failed for ref '$BaselineRef' in '$RepoPath'." } - & tar -x -f $archive -C $baselineTree - if ($LASTEXITCODE -ne 0) { throw "Extracting the baseline archive failed." } + & $tarExe -x -f $archive -C $baselineTree + if ($LASTEXITCODE -ne 0) { throw "Extracting the baseline archive with '$tarExe' failed." } $baselineRows = Read-PfbTupleDump -TreePath $baselineTree -DumpScript $dumpScript $currentRows = Read-PfbTupleDump -TreePath $RepoPath -DumpScript $dumpScript - $declarations = @() - if ($DeclarationPath) { - $declarations = @(Get-Content -Path $DeclarationPath -Raw | ConvertFrom-Json | ForEach-Object { - [PSCustomObject]@{ Key = $_.key; From = $_.from; To = $_.to } - }) - } - $result = Compare-PfbInventoryTupleSet -Baseline $baselineRows -Current $currentRows -DeclaredChange $declarations - Write-Host "baseline ref : $BaselineRef" + Write-Host "baseline ref : $BaselineRef (from $refSource)" Write-Host "baseline rows : $($baselineRows.Count)" Write-Host "current rows : $($currentRows.Count)" Write-Host "declarations : $($declarations.Count)" @@ -131,6 +196,14 @@ try { foreach ($change in $result.Undeclared) { Write-Host "UNDECLARED $($change.Key) $($change.From) => $($change.To)" } foreach ($declaration in $result.UnusedDeclaration) { Write-Host "STALE-DECL $($declaration.Key) $($declaration.From) => $($declaration.To)" } + # The one expected way to reach an all-stale run is to have already merged the change the + # file declares. Say so here rather than leaving the reader to infer it, because the fix is + # to retire the file and NOT to edit it into agreement with a tree it no longer describes. + if ($declarations.Count -gt 0 -and $result.UnusedDeclaration.Count -eq $declarations.Count -and $result.Changed.Count -eq 0) { + Write-Host ("NOTE: every declaration is stale and nothing moved, which is what a LANDED change looks like. " + + "Retire this file -- move it to tools/inventory-tuple-baselines/landed/ -- rather than editing it.") + } + if ($result.IsClean) { Write-Host 'RESULT: CLEAN' -ForegroundColor Green exit 0 diff --git a/tools/inventory-tuple-baselines/issue-141-task4.json b/tools/inventory-tuple-baselines/issue-141-task4.json index ebfea392..bcca4fbf 100644 --- a/tools/inventory-tuple-baselines/issue-141-task4.json +++ b/tools/inventory-tuple-baselines/issue-141-task4.json @@ -1,172 +1,177 @@ -[ - { - "key": "Connect-PfbArray|AllArrays", - "from": "TypedUnresolved||Unresolved||", - "to": "OutsideStandardRequest||Unresolved||" - }, - { - "key": "Connect-PfbArray|ApiToken", - "from": "TypedUnresolved||Unresolved||", - "to": "OutsideStandardRequest||Unresolved||" - }, - { - "key": "Connect-PfbArray|ApiVersion", - "from": "TypedUnresolved||Unresolved||", - "to": "OutsideStandardRequest||Unresolved||" - }, - { - "key": "Connect-PfbArray|ClientId", - "from": "TypedUnresolved||Unresolved||", - "to": "OutsideStandardRequest||Unresolved||" - }, - { - "key": "Connect-PfbArray|Context", - "from": "TypedUnresolved||Unresolved||", - "to": "OutsideStandardRequest||Unresolved||" - }, - { - "key": "Connect-PfbArray|Credential", - "from": "TypedUnresolved||Unresolved||", - "to": "OutsideStandardRequest||Unresolved||" - }, - { - "key": "Connect-PfbArray|Endpoint", - "from": "TypedUnresolved||Unresolved||", - "to": "OutsideStandardRequest||Unresolved||" - }, - { - "key": "Connect-PfbArray|HttpTimeout", - "from": "TypedUnresolved||Unresolved||", - "to": "OutsideStandardRequest||Unresolved||" - }, - { - "key": "Connect-PfbArray|IgnoreCertificateError", - "from": "TypedUnresolved||Unresolved||", - "to": "OutsideStandardRequest||Unresolved||" - }, - { - "key": "Connect-PfbArray|Issuer", - "from": "TypedUnresolved||Unresolved||", - "to": "OutsideStandardRequest||Unresolved||" - }, - { - "key": "Connect-PfbArray|KeyId", - "from": "TypedUnresolved||Unresolved||", - "to": "OutsideStandardRequest||Unresolved||" - }, - { - "key": "Connect-PfbArray|Kind", - "from": "TypedUnresolved||Unresolved||", - "to": "OutsideStandardRequest||Unresolved||" - }, - { - "key": "Connect-PfbArray|Password", - "from": "TypedUnresolved||Unresolved||", - "to": "OutsideStandardRequest||Unresolved||" - }, - { - "key": "Connect-PfbArray|PrivateKeyFile", - "from": "TypedUnresolved||Unresolved||", - "to": "OutsideStandardRequest||Unresolved||" - }, - { - "key": "Connect-PfbArray|PrivateKeyPassword", - "from": "TypedUnresolved||Unresolved||", - "to": "OutsideStandardRequest||Unresolved||" - }, - { - "key": "Connect-PfbArray|Username", - "from": "TypedUnresolved||Unresolved||", - "to": "OutsideStandardRequest||Unresolved||" - }, - { - "key": "Get-PfbApiVersion|Endpoint", - "from": "TypedUnresolved||Unresolved||", - "to": "OutsideStandardRequest||Unresolved||" - }, - { - "key": "Get-PfbApiVersion|IgnoreCertificateError", - "from": "TypedUnresolved||Unresolved||", - "to": "OutsideStandardRequest||Unresolved||" - }, - { - "key": "Get-PfbConnection|Endpoint", - "from": "TypedUnresolved||Unresolved||", - "to": "OutsideStandardRequest||Unresolved||" - }, - { - "key": "Invoke-PfbInContext|AllArrays", - "from": "TypedUnresolved||Unresolved||", - "to": "OutsideStandardRequest||Unresolved||" - }, - { - "key": "Invoke-PfbInContext|Context", - "from": "TypedUnresolved||Unresolved||", - "to": "OutsideStandardRequest||Unresolved||" - }, - { - "key": "Invoke-PfbInContext|Kind", - "from": "TypedUnresolved||Unresolved||", - "to": "OutsideStandardRequest||Unresolved||" - }, - { - "key": "Invoke-PfbInContext|ScriptBlock", - "from": "TypedUnresolved||Unresolved||", - "to": "OutsideStandardRequest||Unresolved||" - }, - { - "key": "Remove-PfbBucket|Eradicate", - "from": "TypedUnresolved||Unresolved||", - "to": "NotWireParameter||Unresolved||" - }, - { - "key": "Remove-PfbFileSystem|Eradicate", - "from": "TypedUnresolved||Unresolved||", - "to": "NotWireParameter||Unresolved||" - }, - { - "key": "Remove-PfbFileSystemSession|Force", - "from": "TypedUnresolved||Unresolved||", - "to": "NotWireParameter||Unresolved||" - }, - { - "key": "Remove-PfbFileSystemSnapshot|Eradicate", - "from": "TypedUnresolved||Unresolved||", - "to": "NotWireParameter||Unresolved||" - }, - { - "key": "Remove-PfbRealm|Eradicate", - "from": "TypedUnresolved||Unresolved||", - "to": "NotWireParameter||Unresolved||" - }, - { - "key": "Remove-PfbServer|Eradicate", - "from": "TypedUnresolved||Unresolved||", - "to": "NotWireParameter||Unresolved||" - }, - { - "key": "Set-PfbContext|AllArrays", - "from": "TypedUnresolved||Unresolved||", - "to": "OutsideStandardRequest||Unresolved||" - }, - { - "key": "Set-PfbContext|AllowErrors", - "from": "TypedUnresolved||Unresolved||", - "to": "OutsideStandardRequest||Unresolved||" - }, - { - "key": "Set-PfbContext|Context", - "from": "TypedUnresolved||Unresolved||", - "to": "OutsideStandardRequest||Unresolved||" - }, - { - "key": "Set-PfbContext|Kind", - "from": "TypedUnresolved||Unresolved||", - "to": "OutsideStandardRequest||Unresolved||" - }, - { - "key": "Set-PfbCredential|Credential", - "from": "TypedUnresolved||Unresolved||", - "to": "OutsideStandardRequest||Unresolved||" - } -] +{ + "baselineRef": "5018f88231878d0a4abe656c2272f5e3dc977ba3", + "note": "Issue #141 Task 4. Every entry moves Surface only; WireName, WireSurface, Method and Endpoint are identical on both sides, so no resolution was gained or lost. RETIRE THIS FILE when the change merges: move it to landed/, which Compare-PfbInventoryTuple.ps1 does not read. Editing it to agree with a tree it no longer describes would pre-authorise 34 rows against future movement.", + "declarations": + [ + { + "key": "Connect-PfbArray|AllArrays", + "from": "TypedUnresolved||Unresolved||", + "to": "OutsideStandardRequest||Unresolved||" + }, + { + "key": "Connect-PfbArray|ApiToken", + "from": "TypedUnresolved||Unresolved||", + "to": "OutsideStandardRequest||Unresolved||" + }, + { + "key": "Connect-PfbArray|ApiVersion", + "from": "TypedUnresolved||Unresolved||", + "to": "OutsideStandardRequest||Unresolved||" + }, + { + "key": "Connect-PfbArray|ClientId", + "from": "TypedUnresolved||Unresolved||", + "to": "OutsideStandardRequest||Unresolved||" + }, + { + "key": "Connect-PfbArray|Context", + "from": "TypedUnresolved||Unresolved||", + "to": "OutsideStandardRequest||Unresolved||" + }, + { + "key": "Connect-PfbArray|Credential", + "from": "TypedUnresolved||Unresolved||", + "to": "OutsideStandardRequest||Unresolved||" + }, + { + "key": "Connect-PfbArray|Endpoint", + "from": "TypedUnresolved||Unresolved||", + "to": "OutsideStandardRequest||Unresolved||" + }, + { + "key": "Connect-PfbArray|HttpTimeout", + "from": "TypedUnresolved||Unresolved||", + "to": "OutsideStandardRequest||Unresolved||" + }, + { + "key": "Connect-PfbArray|IgnoreCertificateError", + "from": "TypedUnresolved||Unresolved||", + "to": "OutsideStandardRequest||Unresolved||" + }, + { + "key": "Connect-PfbArray|Issuer", + "from": "TypedUnresolved||Unresolved||", + "to": "OutsideStandardRequest||Unresolved||" + }, + { + "key": "Connect-PfbArray|KeyId", + "from": "TypedUnresolved||Unresolved||", + "to": "OutsideStandardRequest||Unresolved||" + }, + { + "key": "Connect-PfbArray|Kind", + "from": "TypedUnresolved||Unresolved||", + "to": "OutsideStandardRequest||Unresolved||" + }, + { + "key": "Connect-PfbArray|Password", + "from": "TypedUnresolved||Unresolved||", + "to": "OutsideStandardRequest||Unresolved||" + }, + { + "key": "Connect-PfbArray|PrivateKeyFile", + "from": "TypedUnresolved||Unresolved||", + "to": "OutsideStandardRequest||Unresolved||" + }, + { + "key": "Connect-PfbArray|PrivateKeyPassword", + "from": "TypedUnresolved||Unresolved||", + "to": "OutsideStandardRequest||Unresolved||" + }, + { + "key": "Connect-PfbArray|Username", + "from": "TypedUnresolved||Unresolved||", + "to": "OutsideStandardRequest||Unresolved||" + }, + { + "key": "Get-PfbApiVersion|Endpoint", + "from": "TypedUnresolved||Unresolved||", + "to": "OutsideStandardRequest||Unresolved||" + }, + { + "key": "Get-PfbApiVersion|IgnoreCertificateError", + "from": "TypedUnresolved||Unresolved||", + "to": "OutsideStandardRequest||Unresolved||" + }, + { + "key": "Get-PfbConnection|Endpoint", + "from": "TypedUnresolved||Unresolved||", + "to": "OutsideStandardRequest||Unresolved||" + }, + { + "key": "Invoke-PfbInContext|AllArrays", + "from": "TypedUnresolved||Unresolved||", + "to": "OutsideStandardRequest||Unresolved||" + }, + { + "key": "Invoke-PfbInContext|Context", + "from": "TypedUnresolved||Unresolved||", + "to": "OutsideStandardRequest||Unresolved||" + }, + { + "key": "Invoke-PfbInContext|Kind", + "from": "TypedUnresolved||Unresolved||", + "to": "OutsideStandardRequest||Unresolved||" + }, + { + "key": "Invoke-PfbInContext|ScriptBlock", + "from": "TypedUnresolved||Unresolved||", + "to": "OutsideStandardRequest||Unresolved||" + }, + { + "key": "Remove-PfbBucket|Eradicate", + "from": "TypedUnresolved||Unresolved||", + "to": "NotWireParameter||Unresolved||" + }, + { + "key": "Remove-PfbFileSystem|Eradicate", + "from": "TypedUnresolved||Unresolved||", + "to": "NotWireParameter||Unresolved||" + }, + { + "key": "Remove-PfbFileSystemSession|Force", + "from": "TypedUnresolved||Unresolved||", + "to": "NotWireParameter||Unresolved||" + }, + { + "key": "Remove-PfbFileSystemSnapshot|Eradicate", + "from": "TypedUnresolved||Unresolved||", + "to": "NotWireParameter||Unresolved||" + }, + { + "key": "Remove-PfbRealm|Eradicate", + "from": "TypedUnresolved||Unresolved||", + "to": "NotWireParameter||Unresolved||" + }, + { + "key": "Remove-PfbServer|Eradicate", + "from": "TypedUnresolved||Unresolved||", + "to": "NotWireParameter||Unresolved||" + }, + { + "key": "Set-PfbContext|AllArrays", + "from": "TypedUnresolved||Unresolved||", + "to": "OutsideStandardRequest||Unresolved||" + }, + { + "key": "Set-PfbContext|AllowErrors", + "from": "TypedUnresolved||Unresolved||", + "to": "OutsideStandardRequest||Unresolved||" + }, + { + "key": "Set-PfbContext|Context", + "from": "TypedUnresolved||Unresolved||", + "to": "OutsideStandardRequest||Unresolved||" + }, + { + "key": "Set-PfbContext|Kind", + "from": "TypedUnresolved||Unresolved||", + "to": "OutsideStandardRequest||Unresolved||" + }, + { + "key": "Set-PfbCredential|Credential", + "from": "TypedUnresolved||Unresolved||", + "to": "OutsideStandardRequest||Unresolved||" + } + ] +} From 6eefe7fc42e639958da881f45ceda3a0b26346eb Mon Sep 17 00:00:00 2001 From: Justin Emerson Date: Wed, 26 Aug 2026 22:10:43 -0700 Subject: [PATCH 14/29] docs(tools): document the resolver regression gate in the toolchain README Compare-PfbInventoryTuple.ps1 was the only script in tools/ with no entry in tools/README.md, so the one place a contributor looks to find out what is in this directory did not mention the gate that a resolver change is supposed to pass. An undiscoverable gate is not a gate. Adds a "Resolver regression gate" section covering why totals cannot catch a withdrawn resolution (the real Task 3 case: Typed +61 while Update-PfbBucketAuditFilter -BucketName silently went from bucket_names to unresolved), the working invocation, the declaration-file shape, and the retirement step. Also adds it to the numbered list, flagged as the one entry there that generates nothing and runs on demand rather than as part of a normal pipeline run. The issue-#141 Task 4 paragraph in the field-to-cmdlet section is corrected the same way the report heading was: OutsideStandardRequest is a statement about this resolver's reach, not about whether the parameter reaches the wire. --- tools/README.md | 82 +++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 80 insertions(+), 2 deletions(-) diff --git a/tools/README.md b/tools/README.md index f9e1dcd6..b3f13f5c 100644 --- a/tools/README.md +++ b/tools/README.md @@ -310,8 +310,14 @@ Run in this order: - **Later refinement (issue #141 Task 4): "unresolved" stopped meaning "not `Typed`".** A row's `Surface` now has five values, and two of them -- `NotWireParameter` (an audited request control such as `-Eradicate`/`-Force`) and `OutsideStandardRequest` (the declaring cmdlet - issues no `Invoke-PfbApiRequest` call at all) -- say the parameter is **not a wire field**, - which is not the same claim as "its wire field could not be found". 34 real parameters were + issues no `Invoke-PfbApiRequest` call at all) -- say the parameter has no field in a + *standard* `Invoke-PfbApiRequest` payload, which is a statement about this resolver's reach + and not about whether the parameter reaches the wire. That distinction is load-bearing, not + pedantry: `Connect-PfbArray -Username`/`-Password` are `OutsideStandardRequest` and yet are + POSTed to `/api/login` by hand (`Public/Connection/Connect-PfbArray.ps1:331`, `:351`), so + rendering the bucket as "not a wire field" would publish a confident falsehood about six + real cmdlets. Neither claim is the same as "its wire field could not be found", which is + what `typedUnresolved` means. 34 real parameters were in that position and each was lowering `confidence` on every endpoint its cmdlet reaches. Only `AttributesOnly` and `TypedUnresolved` populate `unresolvedParameters` now. `Get-PfbParameterCoverageGaps` branches **exhaustively** on `Surface` and throws on a value @@ -461,6 +467,18 @@ Run in this order: ./tools/Build-PfbDeadKeyReport.ps1 ``` +8. **`Compare-PfbInventoryTuple.ps1`** — the odd one out in this list: it generates no + artifact and is not part of a normal run. It is the row-level regression gate for a + change to the `Public/` wire-name resolver in `lib/PfbCmdletParamTools.ps1`, and it is + run **on demand, on a branch that touches that resolver**, before opening the PR. See + "Resolver regression gate" below for why the totals every other script here reports are + not sufficient to catch a resolver regression. + + ```powershell + ./tools/Compare-PfbInventoryTuple.ps1 ` + -DeclarationPath ./tools/inventory-tuple-baselines/issue-141-task4.json + ``` + ## Response-shape drift (`Build-PfbResponseShapeMap.ps1`) Everything above tracks the **request** side — which endpoints, query parameters, and @@ -685,6 +703,66 @@ every candidate and its recommendation — informational only, not consumed at r `ValidateSet` or `ArgumentCompleter` is added to any `Public/` cmdlet by this script. Whether/how to consume it is a deliberate follow-on decision. +## Resolver regression gate (`Compare-PfbInventoryTuple.ps1`) + +A change to the wire-name resolver in `lib/PfbCmdletParamTools.ps1` can **withdraw** a +resolution as easily as add one, and no total shows it. Issue #141 Task 3 raised the +`Typed` count by 61 while silently demoting `Update-PfbBucketAuditFilter -BucketName` from +a confident `bucket_names` to unresolved: the count went up, the report looked better, and +one real parameter got worse. The only thing that caught it was a human diffing rows by +hand in a code review. This script is that diff, made runnable. + +It inventories both sides — a git ref and the working tree — in separate child processes, +each using **its own** copy of `tools/lib/PfbCmdletParamTools.ps1` and its own `Public/`, +so the baseline is resolved by the baseline's resolver rather than re-resolved by the new +one, and a ref that predates a cmdlet is not accused of losing it. It then compares the +`Surface|WireName|WireSurface|Method|Endpoint` tuple per `|` row. Exit +code is 0 when clean and 1 otherwise, so it works as a gate in a script or workflow step. + +Every changed row must be **declared in advance**, in a JSON file under +`tools/inventory-tuple-baselines/`. That is the anti-rubber-stamp rail: a resolver change +that moves 34 rows should be reviewed as 34 specific before/after claims, not as one +summary count that went up. + +```powershell +# Normal use. The declaration file carries the ref it was measured at, so this is +# correct even on a stacked branch whose base is not an ancestor of origin/main. +./tools/Compare-PfbInventoryTuple.ps1 ` + -DeclarationPath ./tools/inventory-tuple-baselines/issue-141-task4.json + +# No declarations: report every row that moved against origin/main. Exits 1 if any did. +./tools/Compare-PfbInventoryTuple.ps1 -BaselineRef origin/main +``` + +The declaration file is an **object, not a bare array** — a bare array is refused rather +than quietly accepted, because an array cannot record the ref its tuples were measured at: + +```jsonc +{ + "baselineRef": "5018f88231878d0a4abe656c2272f5e3dc977ba3", + "declarations": [ + { "key": "Remove-PfbBucket|Eradicate", + "from": "TypedUnresolved||Unresolved||", + "to": "NotWireParameter||Unresolved||" } + ] +} +``` + +A tuple renders `$null` as the empty string — exactly what the script prints for an +undeclared change, so a reviewed row can be pasted straight in. `-BaselineRef` is only +needed to override the file's own ref; passing the wrong one on a stacked branch reports +every intervening improvement as undeclared, and the tempting fix (paste those rows in to +silence it) pre-authorises movement nobody reviewed. + +**Retire the file when the change merges** — move it to +`tools/inventory-tuple-baselines/landed/`, which the script never reads. Once its commits +*are* the baseline, every entry matches nothing and the run goes red with one `STALE-DECL` +per entry (the script prints a `NOTE:` naming this case). That is correct behaviour, not a +bug: a declaration matching nothing is exactly what the unused-declaration rail exists to +catch, and teaching it to tolerate the expected case would also teach it to tolerate the +typo'd key it was built to find. Do not edit a landed file into agreement with the tree +either — that leaves 34 rows pre-authorised against *future* movement. + ## Tests `Tests/PfbSpecTools.Tests.ps1` and `Tests/Build-PfbCapabilityMap.Tests.ps1` cover the From d536572310403ec2185f4da07b9ffb64c74fc868 Mon Sep 17 00:00:00 2001 From: Justin Emerson Date: Wed, 26 Aug 2026 22:10:44 -0700 Subject: [PATCH 15/29] docs(tools): note the residual abstention hazard on the helper resolver Get-PfbCommonQueryParamHelperWireName collapses two different answers into one $null: "no Add-PfbCommonQueryParams call names this parameter" (silence, keep looking) and "two calls disagree about it" (abstention, stop looking). Resolve-PfbParameterWireLanding is safe because it retries on an empty landing set rather than on a $null from any single resolver, but that safety lives at the caller and was documented only there. A future caller that treated this $null as "not my parameter" and fell through to a looser resolver would turn a deliberate abstention into a confident wrong wire name. Recording the hazard where the ambiguity is actually created. No behaviour change. Nothing in the tree reaches the disagreement branch today, and that is measured: only Get-PfbQuotaUser has two helper call sites, both target $queryParams, and the ByParameterName rule has no Name entry, so the distinct (WireName, TargetVariable) count is 1 tree-wide. --- tools/lib/PfbCmdletParamTools.ps1 | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/tools/lib/PfbCmdletParamTools.ps1 b/tools/lib/PfbCmdletParamTools.ps1 index f616933e..4e21ec16 100644 --- a/tools/lib/PfbCmdletParamTools.ps1 +++ b/tools/lib/PfbCmdletParamTools.ps1 @@ -494,6 +494,22 @@ function Get-PfbCommonQueryParamHelperWireName { `$var.ToArray()` call on a bare variable (real: Get-PfbUserGroupQuotaPolicy, which must convert its [List[string]] accumulators to arrays for the helper's [string[]] parameters); any other member call shape stays refused. + + RESIDUAL ABSTENTION HAZARD, for whoever changes a caller. This function collapses two + different answers into one $null: "no Add-PfbCommonQueryParams call names this + parameter" (silence -- keep looking) and "two calls disagree about it" (abstention -- + stop looking, the truth is genuinely undetermined). Resolve-PfbParameterWireLanding + avoids acting on the difference by never using this result as its only evidence: it + collects landings from every resolver and retries on an empty landing set rather than + on a $null from any one of them. A caller that instead treated $null here as "not my + parameter" and fell through to a looser resolver would turn a deliberate abstention + into a confident wrong wire name, which is the exact failure the never-guess contract + exists to prevent. + + Nothing in the tree reaches it today, and that is measured rather than assumed: only + Get-PfbQuotaUser has two helper call sites, both target $queryParams, and the + ByParameterName rule has no Name entry, so the distinct (WireName, TargetVariable) + count is 1 everywhere. It is a live hazard for a FUTURE cmdlet, not a present defect. .OUTPUTS $null, or [PSCustomObject]@{ WireName; TargetVariable } -- same shape as Get-PfbWireNameForParameter. From f11925d2f1959846001ae3657fb27ffeefe864e9 Mon Sep 17 00:00:00 2001 From: Justin Emerson Date: Wed, 26 Aug 2026 22:39:25 -0700 Subject: [PATCH 16/29] fix(tools): correct the abstention note, the recompute check and a temp leak Three defects from the round-1 fix diff, all in what the fix itself added. The RESIDUAL ABSTENTION HAZARD block on Get-PfbCommonQueryParamHelperWireName described the hazard as mitigated, which is backwards in three ways and contradicted the correct account 250 lines away on Resolve-PfbParameterWireLanding ("One abstention remains invisible here"). Resolution is strict tier precedence, not collection from every resolver; the accumulator retry lives in Get-PfbCmdletParameterInventory, not in Resolve-PfbParameterWireLanding; and retrying on an empty landing set is the mechanism by which this abstention ESCAPES, not a guard against it -- an abstaining helper leaves the tier empty, which is precisely the retry trigger. Two comments giving opposite accounts is bad in any file; in the one whose whole thesis is never-guess, the wrong one reads as permission. Rewritten as the recorded limit it is. The measurement that nothing reaches it today, and the warning to future callers, are unchanged. The recompute command added to coverage-baseline.psd1 did not work: ExpectedSkips is nested under winps51 and pwsh7, not at the root. Verbatim it throws on pwsh 7 and silently yields nothing on 5.1 without StrictMode -- the worse direction, since a mitigation for "a running total drifts silently" that returns nothing would confirm any total put to it. Now names the edition, and says why that is not optional. Compare-PfbInventoryTuple.ps1 leaked an empty temp directory on either declaration-validation refusal: moving the declaration read ahead of the archive (correct in itself) left the two new throws outside the try whose finally is the only cleanup. The scratch directory is now created immediately before that try, so nothing thrown during validation has a directory to leak. Verified against a control that the counter can see such a directory: 0 before, 0 after both refusal probes. --- Tests/coverage-baseline.psd1 | 6 ++++-- tools/Compare-PfbInventoryTuple.ps1 | 8 ++++++-- tools/lib/PfbCmdletParamTools.ps1 | 19 ++++++++++++------- 3 files changed, 22 insertions(+), 11 deletions(-) diff --git a/Tests/coverage-baseline.psd1 b/Tests/coverage-baseline.psd1 index cb40fe01..5d069938 100644 --- a/Tests/coverage-baseline.psd1 +++ b/Tests/coverage-baseline.psd1 @@ -232,8 +232,10 @@ # which was wrong in both halves because it was written against the #141 change alone # and never reconciled with #113's, already landed. A running total that is only ever # incremented drifts silently; the check is - # `(Import-PowerShellDataFile Tests/coverage-baseline.psd1).ExpectedSkips.Values | - # Measure-Object -Sum`. + # `(Import-PowerShellDataFile Tests/coverage-baseline.psd1).winps51.ExpectedSkips.Values | + # Measure-Object -Sum`. The edition key is not optional -- ExpectedSkips is nested under + # winps51 and pwsh7 separately, and omitting it throws on pwsh 7 but yields NOTHING on + # 5.1 without StrictMode, which is the direction that would quietly confirm any total. # # Every entry is the same cause: a Describe carrying # -Skip:($PSVersionTable.PSVersion.Major -lt 7), or a whole tooling file gated that way, diff --git a/tools/Compare-PfbInventoryTuple.ps1 b/tools/Compare-PfbInventoryTuple.ps1 index 119414d7..1a6d8169 100644 --- a/tools/Compare-PfbInventoryTuple.ps1 +++ b/tools/Compare-PfbInventoryTuple.ps1 @@ -95,8 +95,6 @@ foreach ($row in (Get-PfbCmdletParameterInventory -PublicDirectory (Join-Path $T '@ $hostExe = [System.Diagnostics.Process]::GetCurrentProcess().MainModule.FileName -$scratch = Join-Path ([System.IO.Path]::GetTempPath()) ("pfb-tuple-" + [guid]::NewGuid().ToString('N')) -New-Item -ItemType Directory -Path $scratch -Force | Out-Null # Windows ships bsdtar as %SystemRoot%\System32\tar.exe, but PATH order decides which `tar` a # bare invocation gets, and under a pwsh launched from Git Bash it gets GNU tar. GNU tar parses @@ -161,6 +159,12 @@ function Read-PfbTupleDump { return @($rows) } +# Created here, not earlier: the only cleanup is the finally below, so anything thrown before +# this point must be thrown before there is a directory to leak. The declaration-validation +# throws above are exactly that case. +$scratch = Join-Path ([System.IO.Path]::GetTempPath()) ("pfb-tuple-" + [guid]::NewGuid().ToString('N')) +New-Item -ItemType Directory -Path $scratch -Force | Out-Null + try { $dumpScript = Join-Path $scratch 'Dump-PfbInventoryTuple.ps1' Set-Content -Path $dumpScript -Value $dumpSource -Encoding UTF8 diff --git a/tools/lib/PfbCmdletParamTools.ps1 b/tools/lib/PfbCmdletParamTools.ps1 index 4e21ec16..1f9c25a8 100644 --- a/tools/lib/PfbCmdletParamTools.ps1 +++ b/tools/lib/PfbCmdletParamTools.ps1 @@ -498,13 +498,18 @@ function Get-PfbCommonQueryParamHelperWireName { RESIDUAL ABSTENTION HAZARD, for whoever changes a caller. This function collapses two different answers into one $null: "no Add-PfbCommonQueryParams call names this parameter" (silence -- keep looking) and "two calls disagree about it" (abstention -- - stop looking, the truth is genuinely undetermined). Resolve-PfbParameterWireLanding - avoids acting on the difference by never using this result as its only evidence: it - collects landings from every resolver and retries on an empty landing set rather than - on a $null from any one of them. A caller that instead treated $null here as "not my - parameter" and fell through to a looser resolver would turn a deliberate abstention - into a confident wrong wire name, which is the exact failure the never-guess contract - exists to prevent. + stop looking, the truth is genuinely undetermined). + + Resolve-PfbParameterWireLanding cannot tell the two apart: this $null collapses to an + empty helper tier and reads as silence -- see its own .DESCRIPTION, "One abstention + remains invisible here". Get-PfbCmdletParameterInventory's accumulator retry then fires + on that empty landing set, so a helper abstention DOES reach a fifth source. That is + deliberate for issue #141 Task 4, which must leave every real-tree resolution tuple + untouched -- it is a RECORDED LIMIT, not a mitigation, and this block is not a licence + to assume the caller is guarding it. A caller that additionally treated $null here as + "not my parameter" and fell through to a looser resolver would compound that into a + confident wrong wire name, which is the exact failure the never-guess contract exists + to prevent. Nothing in the tree reaches it today, and that is measured rather than assumed: only Get-PfbQuotaUser has two helper call sites, both target $queryParams, and the From 339314598e9ff17bfe1bfead722f8097804b6c2e Mon Sep 17 00:00:00 2001 From: Justin Emerson Date: Thu, 27 Aug 2026 09:49:31 -0700 Subject: [PATCH 17/29] feat(reports): classify wrong-surface dead keys Add a `classification` and a `declaredElsewhere` field to every dead-key record in Reports/PfbDeadKeyReport.json (issue #141 Task 5). A dead key is now labelled WRONG-SURFACE when the endpoint declares the key as a request body property under some verb, WRONG-VERB when another verb declares it as a query key, and UNDECLARED when no operation on the endpoint declares it on either surface. `declaredElsewhere` carries the provenance -- the (method, surface) pairs the classification is derived from -- deduplicated and ordered by method then surface ordinally. The declaration index reuses Get-PfbSpecCapabilities for body properties, so the $ref/allOf walk that reaches Alert.flagged is the repo's existing walker rather than a second implementation, and reuses Get-PfbDeclaredQueryKey for query keys, so the index can never disagree with the gate that decided the key was dead. Capability records' Parameters field is deliberately not used: it is not filtered by `in:`, and 630 header-parameter occurrences across 629 of 632 operations would otherwise be read as query declarations. Deadness itself is unchanged: the pre-existing deadKeys and noSurvivingSelector populations are byte-identical on their pre-existing fields, and the 66 parameters previously bucketed as "wire name unresolved" now split 32/28/6 across unresolved, outside-standard-request and not-wire-parameter with no leakage. Reports/PfbDeadKeyReport.json is deliberately NOT regenerated here; the regeneration gate's staleness assertion stays red until Task 6 refreshes the artifact and re-baselines Tests/coverage-baseline.psd1. Co-Authored-By: Claude Opus 5 (1M context) --- Tests/Build-PfbDeadKeyReport.Tests.ps1 | 399 +++++++++++++++++++++++++ tools/Build-PfbDeadKeyReport.ps1 | 234 ++++++++++++++- 2 files changed, 626 insertions(+), 7 deletions(-) diff --git a/Tests/Build-PfbDeadKeyReport.Tests.ps1 b/Tests/Build-PfbDeadKeyReport.Tests.ps1 index 20000aaf..5de02ece 100644 --- a/Tests/Build-PfbDeadKeyReport.Tests.ps1 +++ b/Tests/Build-PfbDeadKeyReport.Tests.ps1 @@ -73,6 +73,13 @@ Describe 'Build-PfbDeadKeyReport regeneration (real spec cache required, PS7 onl finally { Pop-Location } + + # Read the FRESH regeneration, never the committed artifact, for every classification + # assertion below. The committed file is regenerated in a later task, so asserting + # against it would make these tests report on the artifact's age rather than on the + # classifier. + $script:regeneratedReport = Get-Content -LiteralPath $regeneratedPath -Raw | ConvertFrom-Json + $script:regeneratedDeadKeys = @($regeneratedReport.deadKeys) } AfterAll { @@ -145,6 +152,105 @@ Describe 'Build-PfbDeadKeyReport regeneration (real spec cache required, PS7 onl } $firstDifference | Should -Be -1 -Because "regenerating from '$regenWorkRoot' instead of the repo root changed the output at byte $firstDifference -- the generator is resolving something against the working directory" } + + It 'classifies Get-PfbAlert -Flagged as WRONG-SURFACE with PATCH/Body provenance' { + # THE anti-vacuity acceptance for the whole feature (issue #141 Task 5 Step 4). + # `flagged` is a real PATCH /alerts body property, reachable ONLY through + # $ref -> allOf -> $ref. Every synthetic fixture in this file could pass with a + # one-level schema reader; this cannot -- such a reader sees ZERO properties on that + # schema and would publish `classification: UNDECLARED`, a confident false assertion + # that no operation on /alerts declares the key. The sibling It below proves that + # one-level reader really does return zero, so this assertion is not merely "the + # answer we happen to get". + $entry = @($regeneratedDeadKeys | Where-Object { $_.cmdlet -eq 'Get-PfbAlert' -and $_.parameter -eq 'Flagged' }) + @($entry).Count | Should -Be 1 -Because "Get-PfbAlert -Flagged writes the query key 'flagged' on GET alerts, which does not declare it, so it must appear exactly once in deadKeys. Present dead keys for Get-PfbAlert: $(@($regeneratedDeadKeys | Where-Object cmdlet -eq 'Get-PfbAlert' | ForEach-Object { "$($_.parameter)/$($_.wireKey)" }) -join ', ')" + $entry[0].wireKey | Should -Be 'flagged' + $entry[0].method | Should -Be 'GET' + $entry[0].endpoint | Should -Be 'alerts' + $entry[0].classification | Should -Be 'WRONG-SURFACE' -Because "PATCH /alerts declares a body property named 'flagged', so the field exists and the cmdlet is sending it on the wrong surface. UNDECLARED here means the `$ref/allOf walk regressed to a one-level read. declaredElsewhere was: $(@($entry[0].declaredElsewhere | ForEach-Object { "$($_.method)/$($_.surface)" }) -join ', ')" + + $bodySites = @($entry[0].declaredElsewhere | Where-Object { $_.surface -eq 'Body' }) + @($bodySites).Count | Should -BeGreaterThan 0 -Because 'the classification is only meaningful with the provenance that justifies it' + @($bodySites | ForEach-Object { $_.method }) | Should -Contain 'PATCH' -Because "PATCH is the verb whose body declares 'flagged'; a different verb would mean the index is reading the wrong operation" + } + + It 'proves the $ref/allOf body walk is load-bearing: a one-level read of PATCH alerts sees no properties' { + # THE CONTROL for the It above. An assertion that a walker "found flagged" cannot + # distinguish a real allOf resolution from a schema that happened to declare it + # inline -- so measure the cheap wrong implementation on the same input and require + # it to DISAGREE. If this ever stops disagreeing, the spec changed shape and the + # WRONG-SURFACE assertion above has quietly stopped proving anything. + . (Join-Path $repoRoot 'tools/lib/PfbSpecTools.ps1') + $capabilityMap = Get-Content -LiteralPath (Join-Path $repoRoot 'Data/PfbCapabilityMap.json') -Raw | ConvertFrom-Json -Depth 20 + $pinnedVersion = $capabilityMap.generatedFrom | Select-Object -Last 1 + $spec = Get-Content -LiteralPath (Join-Path $specsDirectory "fb$pinnedVersion.json") -Raw | ConvertFrom-Json -Depth 64 + + $mediaSchema = $spec.paths."/api/$pinnedVersion/alerts".patch.requestBody.content.'application/json'.schema + $mediaSchema | Should -Not -BeNullOrEmpty -Because 'the fixture-free control depends on PATCH /alerts having a JSON request body in the pinned spec' + + # The one-level reader: resolve the $ref once, then take .properties -- exactly the + # implementation the plan rejects. + $oneLevel = Resolve-PfbRef -Node $mediaSchema -Spec $spec + $oneLevelNames = @(if ($oneLevel.properties) { $oneLevel.properties.PSObject.Properties.Name } else { @() }) + $oneLevelNames | Should -Not -Contain 'flagged' -Because "a one-level read must MISS 'flagged' for the WRONG-SURFACE assertion to be a real test of allOf resolution. It saw: [$($oneLevelNames -join ', ')]" + + $walked = @(Get-PfbSchemaPropertyNames -Schema $mediaSchema -Spec $spec -MaxDepth 32) + $walked | Should -Contain 'flagged' -Because "Get-PfbSchemaPropertyNames resolves `$ref and allOf, so it must see the property the one-level read missed. It saw: [$($walked -join ', ')]" + } + + It 'classifies New-PfbCertificateSigningRequest -Name as UNDECLARED with an empty provenance array' { + # The counterweight to the Flagged case: same generator, same index, opposite answer. + # POST certificates/certificate-signing-requests is the endpoint's ONLY operation and + # declares zero query keys; its body declares common_name and friends but no 'names'. + # So nothing on the endpoint declares the key on either surface -- and that is the one + # classification that is a positive assertion of absence. + $entry = @($regeneratedDeadKeys | Where-Object { $_.cmdlet -eq 'New-PfbCertificateSigningRequest' -and $_.parameter -eq 'Name' }) + @($entry).Count | Should -Be 1 -Because "New-PfbCertificateSigningRequest -Name writes 'names' on POST certificates/certificate-signing-requests, which declares no query keys at all" + $entry[0].wireKey | Should -Be 'names' + $entry[0].method | Should -Be 'POST' + $entry[0].classification | Should -Be 'UNDECLARED' -Because "nothing on this endpoint declares 'names' on either surface. declaredElsewhere was: $(@($entry[0].declaredElsewhere | ForEach-Object { "$($_.method)/$($_.surface)" }) -join ', ')" + @($entry[0].declaredElsewhere).Count | Should -Be 0 -Because 'UNDECLARED must carry an empty array, never a populated one' + $null -ne $entry[0].declaredElsewhere | Should -BeTrue -Because 'and never null -- a consumer reading null cannot tell "no declaration anywhere" from "this generator did not look"' + } + + It 'gives every real dead key a classification from the closed vocabulary, consistent with its provenance' { + # A whole-population invariant rather than a count pin (counts are re-baselined in a + # later task). It is the assertion that catches a record the classifier skipped + # entirely, which the two named-cmdlet tests above cannot see. + @($regeneratedDeadKeys).Count | Should -BeGreaterThan 0 -Because 'a zero-length population would make every assertion in this It vacuously true -- this is the control, not a smoke test' + + $offenders = [System.Collections.Generic.List[string]]::new() + foreach ($record in $regeneratedDeadKeys) { + $sites = @($record.declaredElsewhere) + $hasBody = @($sites | Where-Object { $_.surface -eq 'Body' }).Count -gt 0 + $hasOtherVerbQuery = @($sites | Where-Object { $_.surface -eq 'Query' -and $_.method -ne $record.method }).Count -gt 0 + $expected = if ($hasBody) { 'WRONG-SURFACE' } elseif ($hasOtherVerbQuery) { 'WRONG-VERB' } else { 'UNDECLARED' } + $identity = "$($record.cmdlet)|$($record.parameter)" + + if ($record.classification -notin @('WRONG-SURFACE', 'WRONG-VERB', 'UNDECLARED')) { + $offenders.Add("$identity has classification '$($record.classification)', outside the closed vocabulary") + continue + } + if ($record.classification -ne $expected) { + $offenders.Add("$identity is '$($record.classification)' but its provenance [$(@($sites | ForEach-Object { "$($_.method)/$($_.surface)" }) -join ', ')] implies '$expected'") + } + # Sorted by method then surface, ORDINALLY, deduplicated. Recomputed here rather + # than trusted: the generator's comparer is an unstable introsort, so a duplicate + # (method, surface) pair would make the artifact's byte order depend on .NET's + # partitioning. + $keys = @($sites | ForEach-Object { "$($_.method)|$($_.surface)" }) + if (@($keys | Select-Object -Unique).Count -ne $keys.Count) { + $offenders.Add("$identity has a duplicated declaredElsewhere entry: [$($keys -join ', ')]") + } + for ($i = 1; $i -lt $keys.Count; $i++) { + if ([string]::Compare($keys[$i - 1], $keys[$i], [System.StringComparison]::Ordinal) -ge 0) { + $offenders.Add("$identity has declaredElsewhere out of ordinal method-then-surface order: [$($keys -join ', ')]") + } + } + } + + @($offenders) -join '; ' | Should -BeNullOrEmpty + } } Describe 'Build-PfbDeadKeyReport classification (synthetic fixture, no spec cache, PS7 only)' -Skip:($PSVersionTable.PSVersion.Major -lt 7) { @@ -180,8 +286,117 @@ Describe 'Build-PfbDeadKeyReport classification (synthetic fixture, no spec cach parameters = [PSCustomObject]@{ NamesParam = [PSCustomObject]@{ name = 'names'; 'in' = 'query' } } + schemas = [PSCustomObject]@{ + # DELIBERATELY TWO LEVELS OF INDIRECTION, mirroring the real Alert schema: + # the operation's requestBody holds a bare $ref to SyntheticSurfacePatch, + # whose ONLY key is allOf, whose branch is a $ref to the schema that + # actually declares 'flagged'. A reader that resolves the first $ref and + # then takes .properties gets an EMPTY LIST, so the wrong-surface fixture + # below would classify UNDECLARED and this whole Describe would pass while + # proving the opposite of what it claims. The real-spec control in the + # sibling Describe covers the same hazard against fb; this covers + # it without the ~50MB cache. + SyntheticSurfacePatch = [PSCustomObject]@{ + allOf = @( + [PSCustomObject]@{ '$ref' = '#/components/schemas/SyntheticSurfaceBase' } + ) + } + SyntheticSurfaceBase = [PSCustomObject]@{ + properties = [PSCustomObject]@{ + # SPELT 'Flagged' WHILE THE CMDLET SENDS 'flagged', on purpose. The + # deadness gate itself uses PowerShell's -contains, which is + # case-INSENSITIVE, so an index that matched case-sensitively would + # be STRICTER than the gate it exists to explain: it would report + # UNDECLARED -- a positive assertion of absence -- about a key the + # same generator would have called declared. This spelling makes + # that regression fail a test instead of publishing a false claim. + Flagged = [PSCustomObject]@{ type = 'boolean' } + other_body_field = [PSCustomObject]@{ type = 'string' } + } + } + SyntheticUndeclaredPost = [PSCustomObject]@{ + properties = [PSCustomObject]@{ + other_field = [PSCustomObject]@{ type = 'string' } + } + } + } } paths = [PSCustomObject]@{ + # WRONG-SURFACE case. GET declares only 'limit', so a GET sending 'flagged' is + # dead. Three other declaration sites exist on the SAME path so the provenance + # list exercises both sort keys and the Body-over-Query priority at once: + # DELETE declares 'flagged' as a query key, and PATCH declares it BOTH as a + # query key and (through the allOf chain above) as a body property. Ordinal + # method-then-surface order is therefore DELETE/Query, PATCH/Body, PATCH/Query. + "/api/$fixtureVersion/synthetic/surface" = [PSCustomObject]@{ + get = [PSCustomObject]@{ + parameters = @( + [PSCustomObject]@{ name = 'limit'; 'in' = 'query' } + ) + } + patch = [PSCustomObject]@{ + parameters = @( + [PSCustomObject]@{ name = 'flagged'; 'in' = 'query' } + ) + requestBody = [PSCustomObject]@{ + content = [PSCustomObject]@{ + 'application/json' = [PSCustomObject]@{ + schema = [PSCustomObject]@{ '$ref' = '#/components/schemas/SyntheticSurfacePatch' } + } + } + } + } + delete = [PSCustomObject]@{ + parameters = @( + # 'FLAGGED' for the same reason SyntheticSurfaceBase spells its + # property 'Flagged': the QUERY half of the index must be exactly as + # case-tolerant as the -contains gate, and the only way to assert + # that is a fixture whose case differs from the key being looked up. + [PSCustomObject]@{ name = 'FLAGGED'; 'in' = 'query' } + ) + } + } + # WRONG-VERB case. Nothing on this path declares a body at all, so the only + # possible provenance is a query declaration under another verb. + "/api/$fixtureVersion/synthetic/verb" = [PSCustomObject]@{ + get = [PSCustomObject]@{ + parameters = @( + [PSCustomObject]@{ name = 'limit'; 'in' = 'query' } + ) + } + delete = [PSCustomObject]@{ + parameters = @( + [PSCustomObject]@{ name = 'destroyed'; 'in' = 'query' } + ) + } + } + # UNDECLARED case. The only operation declares 'limit' as its query key and + # 'other_field' as its only body property, so 'names' appears nowhere. + "/api/$fixtureVersion/synthetic/undeclared" = [PSCustomObject]@{ + post = [PSCustomObject]@{ + parameters = @( + [PSCustomObject]@{ name = 'limit'; 'in' = 'query' } + ) + requestBody = [PSCustomObject]@{ + content = [PSCustomObject]@{ + 'application/json' = [PSCustomObject]@{ + schema = [PSCustomObject]@{ '$ref' = '#/components/schemas/SyntheticUndeclaredPost' } + } + } + } + } + } + # Exists only so the audited-control fixture's OTHER parameter resolves to a + # DECLARED key. Without it that cmdlet's -Name would be skipped as + # 'endpoint/verb absent from spec' and the skip-accounting assertions would be + # measuring the wrong bucket. + "/api/$fixtureVersion/synthetic/allow" = [PSCustomObject]@{ + delete = [PSCustomObject]@{ + parameters = @( + [PSCustomObject]@{ name = 'names'; 'in' = 'query' } + ) + } + } "/api/$fixtureVersion/synthetic/dead" = [PSCustomObject]@{ delete = [PSCustomObject]@{ parameters = @( @@ -273,6 +488,92 @@ function Get-PfbSyntheticContext { $queryParams = @{ 'context_names' = $ContextName } Invoke-PfbApiRequest -Array $Array -Method GET -Endpoint 'synthetic/context' -QueryParams $queryParams } +'@ + + # ---- issue #141 Task 5 fixtures ------------------------------------------------- + # EVERY parameter name below is decoupled from the wire key it writes (-Marked writes + # 'flagged', -Gone writes 'destroyed', -Title writes 'names'), so an implementation that + # guessed the key from the parameter name cannot pass. The classification under test is + # a property of the KEY against the spec, so a coincidental name match would make each + # of these tests unable to tell reading from guessing. + # + # -Unresolvable is the planted non-zero control for the 'wire name unresolved' bucket: + # it is typed, its declaring function DOES issue Invoke-PfbApiRequest, and it appears + # nowhere in the payload, so it is a genuine resolution failure. Without it, an + # assertion that the two NEW buckets are non-zero could not distinguish a working + # classifier from a skip counter that increments everything it sees. + Set-Content -LiteralPath (Join-Path $fixturePublicDirectory 'Get-PfbSyntheticWrongSurface.ps1') -Encoding UTF8 -Value @' +function Get-PfbSyntheticWrongSurface { + [CmdletBinding()] + param( + [Parameter()] [bool]$Marked, + [Parameter()] [string]$Unresolvable, + [Parameter()] [PSCustomObject]$Array + ) + $queryParams = @{ 'flagged' = $Marked } + Invoke-PfbApiRequest -Array $Array -Method GET -Endpoint 'synthetic/surface' -QueryParams $queryParams +} +'@ + + Set-Content -LiteralPath (Join-Path $fixturePublicDirectory 'Get-PfbSyntheticWrongVerb.ps1') -Encoding UTF8 -Value @' +function Get-PfbSyntheticWrongVerb { + [CmdletBinding()] + param( + [Parameter()] [bool]$Gone, + [Parameter()] [PSCustomObject]$Array + ) + $queryParams = @{ 'destroyed' = $Gone } + Invoke-PfbApiRequest -Array $Array -Method GET -Endpoint 'synthetic/verb' -QueryParams $queryParams +} +'@ + + Set-Content -LiteralPath (Join-Path $fixturePublicDirectory 'New-PfbSyntheticUndeclared.ps1') -Encoding UTF8 -Value @' +function New-PfbSyntheticUndeclared { + [CmdletBinding()] + param( + [Parameter()] [string[]]$Title, + [Parameter()] [PSCustomObject]$Array + ) + $queryParams = @{ 'names' = $Title } + Invoke-PfbApiRequest -Array $Array -Method POST -Endpoint 'synthetic/undeclared' -QueryParams $queryParams +} +'@ + + # 'OutsideStandardRequest' fixture: NO Invoke-PfbApiRequest call anywhere in the body, + # which is the whole structural fact Test-PfbFunctionMakesStandardRequest measures + # (tools/lib/PfbCmdletParamTools.ps1:1630). Two parameters, so the bucket it feeds is + # distinguishable from a bucket that happens to be 1. + Set-Content -LiteralPath (Join-Path $fixturePublicDirectory 'Set-PfbSyntheticNoRequest.ps1') -Encoding UTF8 -Value @' +function Set-PfbSyntheticNoRequest { + [CmdletBinding()] + param( + [Parameter()] [string]$Alpha, + [Parameter()] [string]$Beta + ) + $script:PfbSyntheticState = @{ Alpha = $Alpha; Beta = $Beta } +} +'@ + + # 'NotWireParameter' fixture, and it MUST be named Remove-PfbBucket with a parameter + # named Eradicate: the allowlist is keyed on the exact 'Cmdlet|Parameter' identity + # ('{0}|{1}' -f $funcAst.Name, $paramName at tools/lib/PfbCmdletParamTools.ps1:1781), so + # no invented synthetic name can reach that state. It also has to issue a real + # Invoke-PfbApiRequest, because 'OutsideStandardRequest' is tested FIRST in the same + # ladder and would otherwise absorb both parameters and hide this state entirely. + # -Name writes the DECLARED key 'names' on DELETE synthetic/allow, so it is not dead + # and does not perturb the dead-key assertions. + Set-Content -LiteralPath (Join-Path $fixturePublicDirectory 'Remove-PfbBucket.ps1') -Encoding UTF8 -Value @' +function Remove-PfbBucket { + [CmdletBinding()] + param( + [Parameter()] [string[]]$Name, + [Parameter()] [switch]$Eradicate, + [Parameter()] [PSCustomObject]$Array + ) + if (-not $Eradicate) { throw 'refusing to remove without -Eradicate' } + $queryParams = @{ 'names' = $Name } + Invoke-PfbApiRequest -Array $Array -Method DELETE -Endpoint 'synthetic/allow' -QueryParams $queryParams +} '@ $script:syntheticReportPath = Join-Path $fixtureWorkRoot 'synthetic.json' @@ -289,6 +590,14 @@ function Get-PfbSyntheticContext { $script:syntheticNssText = @(@($syntheticReport.noSurvivingSelector) | ForEach-Object { "$($_.cmdlet) $($_.method) $($_.endpoint)" }) -join '; ' + # Rendered once so every skip-accounting failure message below carries the WHOLE + # bucket table. A failure that reports only the bucket it asserted on cannot tell + # "the state was not detected" from "it was counted in the wrong bucket", which is + # precisely the confusion Task 4's two new states exist to remove. + $script:syntheticSkipReasons = $syntheticReport.counts.skipReasons + $script:syntheticSkipText = @(@($syntheticSkipReasons.PSObject.Properties) | ForEach-Object { + "$($_.Name)=$($_.Value)" + }) -join ', ' } AfterAll { @@ -337,4 +646,94 @@ function Get-PfbSyntheticContext { @(@($syntheticReport.noSurvivingSelector) | Where-Object { $_.cmdlet -eq 'Get-PfbSyntheticContext' }) | Should -BeNullOrEmpty -Because "context_names is a fleet-routing key, not a selector, so it must never make a cmdlet look as though it has no surviving selector. Reported groups were: $syntheticNssText" } + + It 'classifies a key declared as a body property under another verb as WRONG-SURFACE' { + # Step 1-2. 'flagged' is dead on GET synthetic/surface (which declares only 'limit'), + # and is declared THREE other ways on the same path: DELETE query, PATCH query, and + # PATCH body through $ref -> allOf -> $ref. Body must win the priority ladder even + # though a wrong-verb QUERY declaration also exists, because "you sent a body field as + # a query parameter" is the actionable diagnosis. + $entry = @(@($syntheticReport.deadKeys) | Where-Object { $_.cmdlet -eq 'Get-PfbSyntheticWrongSurface' -and $_.parameter -eq 'Marked' }) + @($entry).Count | Should -Be 1 -Because "-Marked writes 'flagged' on GET synthetic/surface, which declares only 'limit'. Reported dead keys were: $syntheticDeadKeyText" + $entry[0].wireKey | Should -Be 'flagged' -Because 'the parameter is named Marked, so a key of "marked" would mean the resolver guessed from the name instead of reading the payload literal' + $entry[0].classification | Should -Be 'WRONG-SURFACE' -Because "PATCH synthetic/surface declares 'flagged' as a body property, reachable only through `$ref -> allOf -> `$ref. UNDECLARED here means the fixture's allOf chain was not resolved; WRONG-VERB means Body lost the priority ladder to the DELETE/PATCH query declarations. declaredElsewhere was: $(@($entry[0].declaredElsewhere | ForEach-Object { "$($_.method)/$($_.surface)" }) -join ', ')" + + # Provenance is the whole value of the classification, and it is asserted as an exact + # ORDERED list: deduplicated, and sorted by method then surface ordinally. An + # order-insensitive assertion would let the generator's unstable introsort reorder the + # committed artifact between runs on identical inputs. + $sites = @($entry[0].declaredElsewhere | ForEach-Object { "$($_.method)/$($_.surface)" }) + $sites | Should -Be @('DELETE/Query', 'PATCH/Body', 'PATCH/Query') -Because "the fixture declares exactly those three sites, and both sort keys must be exercised: DELETE before PATCH orders on method, Body before Query orders on surface within PATCH. Got: [$($sites -join ', ')]" + } + + It 'classifies a key declared only as a query key under another verb as WRONG-VERB' { + # Step 1-2, the second arm. Nothing on synthetic/verb declares a request body at all, + # so this fixture cannot be satisfied by a Body site and isolates the WRONG-VERB arm + # from the WRONG-SURFACE one above. + $entry = @(@($syntheticReport.deadKeys) | Where-Object { $_.cmdlet -eq 'Get-PfbSyntheticWrongVerb' -and $_.parameter -eq 'Gone' }) + @($entry).Count | Should -Be 1 -Because "-Gone writes 'destroyed' on GET synthetic/verb, which declares only 'limit'. Reported dead keys were: $syntheticDeadKeyText" + $entry[0].wireKey | Should -Be 'destroyed' -Because 'the parameter is named Gone, so the key can only have come from the payload literal' + $entry[0].classification | Should -Be 'WRONG-VERB' -Because "DELETE synthetic/verb declares 'destroyed' as a query key while GET does not. declaredElsewhere was: $(@($entry[0].declaredElsewhere | ForEach-Object { "$($_.method)/$($_.surface)" }) -join ', ')" + @($entry[0].declaredElsewhere | ForEach-Object { "$($_.method)/$($_.surface)" }) | + Should -Be @('DELETE/Query') -Because 'the single declaration site is the evidence for the verb claim' + } + + It 'classifies a key declared nowhere on the endpoint as UNDECLARED with an empty array' { + # Step 1-2, the third arm, and the one that is a POSITIVE ASSERTION of absence: the + # endpoint's only operation declares 'limit' as its query key and 'other_field' as its + # only body property, so 'names' genuinely appears on neither surface. The empty-array + # assertion matters as much as the classification -- a consumer reading null cannot + # distinguish "no declaration anywhere" from "this generator did not look". + $entry = @(@($syntheticReport.deadKeys) | Where-Object { $_.cmdlet -eq 'New-PfbSyntheticUndeclared' -and $_.parameter -eq 'Title' }) + @($entry).Count | Should -Be 1 -Because "-Title writes 'names' on POST synthetic/undeclared, which declares only 'limit'. Reported dead keys were: $syntheticDeadKeyText" + $entry[0].wireKey | Should -Be 'names' -Because 'the parameter is named Title, so the key can only have come from the payload literal' + $entry[0].classification | Should -Be 'UNDECLARED' -Because "nothing on synthetic/undeclared declares 'names' -- not the POST query keys and not its body. A WRONG-SURFACE answer would mean the index leaked another endpoint's body properties in. declaredElsewhere was: $(@($entry[0].declaredElsewhere | ForEach-Object { "$($_.method)/$($_.surface)" }) -join ', ')" + @($entry[0].declaredElsewhere).Count | Should -Be 0 -Because 'UNDECLARED must carry no provenance' + $null -ne $entry[0].declaredElsewhere | Should -BeTrue -Because 'and an empty array rather than null' + + # THE ANTI-LEAK CONTROL for the index: 'other_field' is a body property of THIS + # endpoint, and 'flagged' is a body property of a DIFFERENT one. If the index were + # keyed too loosely -- on the spec document rather than per (endpoint, method) -- then + # a dead key would find a declaration on some other path and this file's three + # classification arms would all still pass. Asserting that no dead key in the whole + # synthetic population claims a Body site it cannot have is what closes that. + $leaks = @(@($syntheticReport.deadKeys) | Where-Object { + $_.endpoint -ne 'synthetic/surface' -and @($_.declaredElsewhere | Where-Object { $_.surface -eq 'Body' }).Count -gt 0 + } | ForEach-Object { "$($_.cmdlet)|$($_.parameter) on $($_.endpoint)" }) + @($leaks) -join '; ' | Should -BeNullOrEmpty -Because 'synthetic/surface is the only fixture path with a request body, so a Body provenance anywhere else means the declaration index is not keyed per endpoint' + } + + It 'counts a parameter of a function that issues no request as outside standard request, not unresolved' { + # Step 5, first new bucket. Set-PfbSyntheticNoRequest contains no Invoke-PfbApiRequest + # call, so NONE of its parameters can resolve -- and reporting them as "wire name + # unresolved" describes a resolver failure that never happened + # (tools/lib/PfbCmdletParamTools.ps1:1630). Both of its parameters must land in this + # bucket and nowhere else. + $syntheticSkipReasons.'outside standard request' | Should -Be 2 -Because "Set-PfbSyntheticNoRequest declares -Alpha and -Beta and issues no Invoke-PfbApiRequest. Buckets were: $syntheticSkipText" + + # THE CONTROL, per the measure-with-a-control rule: 'wire name unresolved' must be + # provably able to fire in this same run, otherwise the assertion above is + # indistinguishable from a generator that stopped counting unresolved parameters + # altogether. -Unresolvable on Get-PfbSyntheticWrongSurface is the planted non-zero: + # typed, in a function that DOES issue a request, and absent from the payload. + $syntheticSkipReasons.'wire name unresolved' | Should -BeGreaterThan 0 -Because "-Unresolvable is a genuine resolution failure and must still be counted as one; a zero here means the two new states have swallowed the bucket they were split out of. Buckets were: $syntheticSkipText" + + @(@($syntheticReport.deadKeys) | Where-Object { $_.cmdlet -eq 'Set-PfbSyntheticNoRequest' }) | + Should -BeNullOrEmpty -Because 'a parameter with no request to land in can never be a dead key' + } + + It 'counts an audited request control as not wire parameter, not unresolved' { + # Step 5, second new bucket. The allowlist is keyed on the exact 'Cmdlet|Parameter' + # identity, so this fixture has to BE Remove-PfbBucket -Eradicate; see the fixture + # comment. It also has to issue a real request, because 'OutsideStandardRequest' is + # tested first in the same ladder -- if this assertion and the one above ever both + # move together, that ordering is what broke. + $syntheticSkipReasons.'not wire parameter' | Should -Be 1 -Because "Remove-PfbBucket -Eradicate is on the audited allowlist (Get-PfbNotWireParameterAllowlist) and its function does issue Invoke-PfbApiRequest, so it must be counted here and not as 'outside standard request'. Buckets were: $syntheticSkipText" + + # -Name writes the DECLARED key on this endpoint, so the fixture proves the cmdlet was + # inventoried and evaluated rather than skipped wholesale -- without which the count + # above could be explained by the file never being read. + @(@($syntheticReport.deadKeys) | Where-Object { $_.cmdlet -eq 'Remove-PfbBucket' }) | + Should -BeNullOrEmpty -Because "-Name writes the declared key 'names' on DELETE synthetic/allow and -Eradicate is not a wire field at all, so this cmdlet has no dead key. Reported dead keys were: $syntheticDeadKeyText" + } } diff --git a/tools/Build-PfbDeadKeyReport.ps1 b/tools/Build-PfbDeadKeyReport.ps1 index ad8e469e..1f07dbb9 100644 --- a/tools/Build-PfbDeadKeyReport.ps1 +++ b/tools/Build-PfbDeadKeyReport.ps1 @@ -163,16 +163,205 @@ function Get-PfbDeadKeySeverity { } } +function Get-PfbDeadKeyDeclarationIndex { + <# + .SYNOPSIS + One record per (method, normalized endpoint) carrying every query key and every + top-level body property that operation declares. Built ONCE per generator run. + .DESCRIPTION + This index exists only to EXPLAIN a deadness that Get-PfbDeclaredQueryKey has already + proven. It never decides whether a record is dead. + + BUILT ON Get-PfbSpecCapabilities RATHER THAN A BESPOKE WALK, deliberately, for three + measured reasons -- all three are ways a hand-rolled body read returns a plausible ZERO: + + 1. It derives BodyProperties through Get-PfbSchemaPropertyNames, which resolves + $ref AND allOf. PATCH /alerts' body schema is a bare `$ref`; resolving that ONE + level lands on a node whose only key is `allOf`, so a reader that then takes + `.properties` gets an EMPTY LIST -- measured against fb2.28. Alert.flagged is + reachable no other way, so such a reader classifies Get-PfbAlert -Flagged as + UNDECLARED: a confident false assertion published under a field name that reads + as authoritative. The walker returns all 18 properties including `flagged`. + 2. It hops a `type: array` request body onto its `items` element schema (issue #82). + A bespoke `Get-PfbSchemaPropertyNames -Schema $op.requestBody.content..schema` + call has nothing to descend for an array body and silently records zero body + properties. + 3. It reads body schemas at MaxDepth 32, not the helpers' own default of 8. Depth 8 + truncates the fb2.12-2.16 allOf chains (issue #71); a bespoke walk would have to + re-decide that value, and the cheap wrong answer is to accept the default. + + WHAT IT DELIBERATELY DOES *NOT* TAKE FROM THAT FUNCTION IS `Parameters`. That field is + every parameter regardless of `in:` location, not the query ones. Measured on fb2.28: + 630 header-parameter occurrences (`api-token`, `X-Request-ID`) across 629 of the 632 + operations. Using it as the query-declaration set would inject two header names into + almost every operation's declarations and could report a genuinely dead key as + WRONG-VERB. Query keys come from Get-PfbDeclaredQueryKey instead -- the SAME function + that gates deadness -- so the classification's notion of "declared as a query key" is + identical to the gate's by construction and the two can never contradict each other. + + WHY A NULL FROM THAT HELPER SKIPS THE OPERATION, AND WHY THAT LOSES NOTHING. It returns + $null for "this path/verb is not in the spec", which here means only that the + capability record's normalized path is one of the five UNVERSIONED meta paths + (/api/login, /api/logout, /api/api_version, /api/login-banner, /oauth2/1.0/token): + normalizing strips no prefix from them, so re-adding /api// misses. Measured + on fb2.28: exactly those 5 of 632 records, and every versioned path round-trips. No + record can reach classification through such an endpoint, because the deadness gate + below has already called this same helper on the record's own endpoint and required a + non-$null answer -- which proves /api// is a real path key, and a + path key carries all of its own verbs. So the index is complete for every endpoint that + can reach it. Skipping is not a silent gap; it is the honest alternative to recording an + operation whose query declarations this generator cannot read. + + CASE-INSENSITIVE (Ordinal-ignore-case) SETS on purpose. The deadness gate is + `@($declared) -contains $wireName`, and PowerShell's -contains is case-INSENSITIVE. An + ordinal-exact index here would be STRICTER than the gate it explains, so a key the gate + would have called declared could be reported UNDECLARED. Every key on this surface is + lower-case snake_case today, so this changes no current row -- it removes a way for the + explanation to disagree with the finding. + #> + param( + [Parameter(Mandatory)] $Spec, + [Parameter(Mandatory)] [string]$Version + ) + + $index = [System.Collections.Generic.Dictionary[string, System.Collections.Generic.List[object]]]::new( + [System.StringComparer]::OrdinalIgnoreCase) + + foreach ($capability in @(Get-PfbSpecCapabilities -Spec $Spec)) { + $endpointKey = ([string]$capability.Path).TrimStart('/') + $method = ([string]$capability.Method).ToUpperInvariant() + + $queryKeys = Get-PfbDeclaredQueryKey -Spec $Spec -Endpoint $endpointKey -Method $method -Version $Version + if ($null -eq $queryKeys) { continue } + + if (-not $index.ContainsKey($endpointKey)) { + $index[$endpointKey] = [System.Collections.Generic.List[object]]::new() + } + $index[$endpointKey].Add([PSCustomObject]@{ + Method = $method + QueryKeys = [System.Collections.Generic.HashSet[string]]::new( + [string[]]@($queryKeys), [System.StringComparer]::OrdinalIgnoreCase) + BodyKeys = [System.Collections.Generic.HashSet[string]]::new( + [string[]]@($capability.BodyProperties), [System.StringComparer]::OrdinalIgnoreCase) + }) + } + + return $index +} + +function Get-PfbDeadKeyDeclarationSite { + <# + .SYNOPSIS + Every (method, surface) pair on the SAME normalized endpoint that declares $WireKey. + .DESCRIPTION + Returns an empty list when nothing on the endpoint declares the key -- which is the + positive assertion behind UNDECLARED, and is the reason the index above is built with + the repo's real schema walker rather than a one-level read. + + Only the endpoint the record itself resolved to is consulted. A similarly named + endpoint is not a declaration, and neither is an older spec version: the caller passes + the one pinned spec. + #> + param( + [Parameter(Mandatory)] $DeclarationIndex, + [Parameter(Mandatory)] [string]$Endpoint, + [Parameter(Mandatory)] [string]$WireKey + ) + + $sites = [System.Collections.Generic.List[object]]::new() + $endpointKey = $Endpoint.TrimStart('/') + if (-not $DeclarationIndex.ContainsKey($endpointKey)) { return $sites } + + foreach ($operation in $DeclarationIndex[$endpointKey]) { + if ($operation.BodyKeys.Contains($WireKey)) { + $sites.Add([PSCustomObject]@{ Method = $operation.Method; Surface = 'Body' }) + } + if ($operation.QueryKeys.Contains($WireKey)) { + $sites.Add([PSCustomObject]@{ Method = $operation.Method; Surface = 'Query' }) + } + } + + return $sites +} + +function Get-PfbDeadKeyClassification { + <# + .SYNOPSIS + WRONG-SURFACE, WRONG-VERB or UNDECLARED for a key already proven dead on $Method. + .DESCRIPTION + Priority is diagnostic, not arithmetic: a body declaration anywhere on the endpoint is + the most actionable finding (the field exists, the cmdlet is sending it on the wrong + surface), so it outranks a query declaration on another verb. Only when NEITHER exists + is UNDECLARED emitted, and UNDECLARED is a POSITIVE ASSERTION that no operation on the + endpoint declares the key on either surface -- see the index's help for why that + assertion is only safe with a $ref/allOf-aware body reader. + + THE `$_.Method -ne $Method` COMPONENT IS PROVABLY REDUNDANT TODAY, and is kept as a + statement of intent rather than as a working guard. A Query site on the CURRENT method + cannot exist: the index's query keys come from Get-PfbDeclaredQueryKey with the same + endpoint, the same method and the same case-insensitive membership test that just + declared this key dead. Do not read a surviving mutant of that comparison as a missing + test -- it is an equivalent mutant. The ARM itself is live and tested: replacing the + whole condition with $false reds the WRONG-VERB fixture. + #> + param( + [Parameter(Mandatory)] + [AllowEmptyCollection()] + [object[]]$DeclarationSite, + + [Parameter(Mandatory)] [string]$Method + ) + + if (@($DeclarationSite | Where-Object { $_.Surface -eq 'Body' }).Count -gt 0) { + return 'WRONG-SURFACE' + } + if (@($DeclarationSite | Where-Object { $_.Surface -eq 'Query' -and $_.Method -ne $Method }).Count -gt 0) { + return 'WRONG-VERB' + } + return 'UNDECLARED' +} + +# ORDER IS PART OF THE ARTIFACT: this is an [ordered] dictionary serialized verbatim into +# counts.skipReasons, so inserting a key rewrites the JSON. The two issue #141 Task 4 states +# sit immediately after 'wire name unresolved' because that is the bucket they were being +# absorbed into before they existed, and reading them adjacent to it is how a maintainer sees +# the reclassification rather than a count that merely fell. +# +# THE FOUR OLD KEYS ARE ADMISSIONS OF IGNORANCE; THE TWO NEW ONES ARE ANSWERS. 'wire name +# unresolved' means this AST resolver could not find a key that may well exist. 'outside +# standard request' means the declaring function issues no Invoke-PfbApiRequest call at all, +# and 'not wire parameter' means an audited request control with no query/body key. Neither +# says anything about bespoke HTTP: Connect-PfbArray's -Username/-Password are 'outside +# standard request' AND reach the wire, in a login body posted through Invoke-WebRequest. +# Do not retitle these buckets as "not a wire field". $skipReasons = [ordered]@{ 'wire name unresolved' = 0 + 'outside standard request' = 0 + 'not wire parameter' = 0 'body property' = 0 'endpoint/method ambiguous' = 0 'endpoint/verb absent from spec' = 0 } +$declarationIndex = Get-PfbDeadKeyDeclarationIndex -Spec $spec -Version $specVersion $deadKeyRecords = [System.Collections.Generic.List[object]]::new() $evaluatedRecords = [System.Collections.Generic.List[object]]::new() foreach ($record in $inventory) { + # BEFORE the null-WireName test, not after, and that ordering is the whole point of these + # two branches. Both Surface values carry WireName = $null BY CONSTRUCTION -- the + # inventory's Surface ladder tests `if ($wireName) { 'Typed' }` first, so it cannot reach + # either value with a resolved name -- so a null-WireName test placed first swallows every + # one of them into 'wire name unresolved' and the reclassification produces no visible + # movement at all. Tested by fixture in both directions: each state increments only its own + # bucket, and 'wire name unresolved' keeps its own genuinely-unresolved rows. + if ($record.Surface -eq 'OutsideStandardRequest') { + $skipReasons['outside standard request']++ + continue + } + if ($record.Surface -eq 'NotWireParameter') { + $skipReasons['not wire parameter']++ + continue + } if ($null -eq $record.WireName) { $skipReasons['wire name unresolved']++ continue @@ -211,14 +400,33 @@ foreach ($record in $inventory) { continue } + # Classification is strictly ADDITIVE to the finding above: $status decided deadness from + # the current operation's own query declarations, and nothing below can change it. If a + # change here moves which records are dead, it has exceeded its remit. + $declarationSites = Get-PfbDeadKeyDeclarationSite -DeclarationIndex $declarationIndex ` + -Endpoint ([string]$record.Endpoint) -WireKey $wireName + $classification = Get-PfbDeadKeyClassification -DeclarationSite @($declarationSites) -Method $method + + # Deduplicate BEFORE sorting. Sort-PfbDeadKeyRecords is an unstable introsort (see its + # header), so it is deterministic only over unique keys -- and (Method, Surface) is unique + # here precisely because this loop collapses it. One operation can legitimately declare the + # same key on both surfaces, which is why Surface is a sort key and not only a label. + $seenSites = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::Ordinal) + $uniqueSites = [System.Collections.Generic.List[object]]::new() + foreach ($site in $declarationSites) { + if ($seenSites.Add(('{0}|{1}' -f $site.Method, $site.Surface))) { $uniqueSites.Add($site) } + } + $deadKeyRecords.Add([PSCustomObject]@{ - Cmdlet = $record.Cmdlet - Parameter = $record.Parameter - Severity = Get-PfbDeadKeySeverity -Method $method - WireKey = $wireName - Method = $method - Endpoint = $record.Endpoint - Declared = @($declared) + Cmdlet = $record.Cmdlet + Parameter = $record.Parameter + Severity = Get-PfbDeadKeySeverity -Method $method + WireKey = $wireName + Method = $method + Endpoint = $record.Endpoint + Declared = @($declared) + Classification = $classification + DeclaredElsewhere = @(Sort-PfbDeadKeyRecords -Records @($uniqueSites) -Property @('Method', 'Surface')) }) } @@ -307,6 +515,18 @@ $sortedDeadKeys = @(Sort-PfbDeadKeyRecords -Records @($deadKeyRecords) -Property method = $_.Method endpoint = $_.Endpoint declared = @($_.Declared) + # `declared` above stays exactly what it has always been -- the CURRENT + # operation's query-key list. These two are appended so no consumer keyed on + # field order or on the old names moves. + classification = $_.Classification + # ALWAYS an array, EMPTY for UNDECLARED, never $null: a consumer reading null + # cannot tell "no declaration anywhere" from "this generator did not look". + declaredElsewhere = @(@($_.DeclaredElsewhere) | ForEach-Object { + [ordered]@{ + method = $_.Method + surface = $_.Surface + } + }) } }) $sortedNoSurvivingSelector = @(Sort-PfbDeadKeyRecords -Records @($noSurvivingSelectorRecords) -Property @('Cmdlet', 'Method', 'Endpoint') | From 19bed977c5e2f60e363092634aaabc3963ecd34a Mon Sep 17 00:00:00 2001 From: Justin Emerson Date: Thu, 27 Aug 2026 10:52:28 -0700 Subject: [PATCH 18/29] test(reports): cover the endpoint axis of dead-key index case tolerance Review round 1. The endpoint key of the declaration index was an uncovered guard, not an equivalent mutant as previously reported: the deadness gate reaches the spec through PSObject property access on a ConvertFrom-Json object, which is case-insensitive, so a cmdlet whose -Endpoint literal differs in case from the spec path key is gated normally and reaches classification. An ordinal-exact index would then miss it and publish UNDECLARED -- a positive assertion of absence about a key the same generator can see. Add one It driving a mixed-case endpoint fixture (spec /api/9.9/widgets, cmdlet -Endpoint 'Widgets') against a lower-case control (/api/9.9/gadgets, 'gadgets') that is identical in every other respect. Measured: shipped code classifies both WRONG-SURFACE with [PATCH/Body]; with the index dictionary patched to StringComparer::Ordinal the mixed-case record becomes UNDECLARED with an empty provenance while the control is unchanged, so the discrimination is provably the case divergence and not the fixture. Not a live defect -- all 85 real dead-key endpoint literals match a normalized spec path exactly -- so this guards a future ordinal hardening. Also, comments only, no behaviour change: - soften the dedup comment from "cannot be produced" to "is not produced by any spec we pin", and say why the dedup is load-bearing anyway (the unstable introsort makes a duplicate pair reorder the committed artifact with no input change); - record that MaxDepth 32 is inherited from Get-PfbSpecCapabilities' own default rather than pinned by this report; - record the residual of the array-items hop: a Body site inside an array element reads identically to a top-level one; - note that the explicit -MaxDepth 32 in the real-spec control does not exercise the issue #71 truncation, since depth 8 also returns all 18 properties of PATCH /alerts. Co-Authored-By: Claude Opus 5 (1M context) --- Tests/Build-PfbDeadKeyReport.Tests.ps1 | 120 ++++++++++++++++++++++++- tools/Build-PfbDeadKeyReport.ps1 | 28 ++++-- 2 files changed, 141 insertions(+), 7 deletions(-) diff --git a/Tests/Build-PfbDeadKeyReport.Tests.ps1 b/Tests/Build-PfbDeadKeyReport.Tests.ps1 index 5de02ece..e3470e48 100644 --- a/Tests/Build-PfbDeadKeyReport.Tests.ps1 +++ b/Tests/Build-PfbDeadKeyReport.Tests.ps1 @@ -194,6 +194,11 @@ Describe 'Build-PfbDeadKeyReport regeneration (real spec cache required, PS7 onl $oneLevelNames = @(if ($oneLevel.properties) { $oneLevel.properties.PSObject.Properties.Name } else { @() }) $oneLevelNames | Should -Not -Contain 'flagged' -Because "a one-level read must MISS 'flagged' for the WRONG-SURFACE assertion to be a real test of allOf resolution. It saw: [$($oneLevelNames -join ', ')]" + # -MaxDepth 32 states the depth the generator inherits, but this call does NOT exercise + # it: measured, the walker at its SIGNATURE DEFAULT of 8 also returns all 18 properties + # of PATCH /alerts including 'flagged', so 32-vs-8 is unobservable at this call site. + # The issue #71 truncation lives in the fb2.12-2.16 allOf chains, not this one -- do not + # read this line as a demonstration of that hazard. $walked = @(Get-PfbSchemaPropertyNames -Schema $mediaSchema -Spec $spec -MaxDepth 32) $walked | Should -Contain 'flagged' -Because "Get-PfbSchemaPropertyNames resolves `$ref and allOf, so it must see the property the one-level read missed. It saw: [$($walked -join ', ')]" } @@ -314,6 +319,13 @@ Describe 'Build-PfbDeadKeyReport classification (synthetic fixture, no spec cach other_body_field = [PSCustomObject]@{ type = 'string' } } } + # Used by BOTH endpoint-case fixtures below, so the mixed-case case and its + # lowercase control differ in exactly one thing: the case of the endpoint. + SyntheticEndpointPatch = [PSCustomObject]@{ + properties = [PSCustomObject]@{ + archived = [PSCustomObject]@{ type = 'boolean' } + } + } SyntheticUndeclaredPost = [PSCustomObject]@{ properties = [PSCustomObject]@{ other_field = [PSCustomObject]@{ type = 'string' } @@ -386,6 +398,48 @@ Describe 'Build-PfbDeadKeyReport classification (synthetic fixture, no spec cach } } } + # ENDPOINT-CASE PAIR. Both paths are spelt lower-case here; the cmdlets below + # send 'Widgets' (mixed) and 'gadgets' (lower). The declaration index is keyed + # on the endpoint, and the deadness gate reaches the spec through PSObject + # property access on a ConvertFrom-Json object, which is case-INSENSITIVE -- so + # a record whose -Endpoint literal differs in case from the spec path key + # passes the gate and reaches classification. An ordinal-exact endpoint key + # would then miss the index entirely and publish UNDECLARED: an assertion of + # absence about a key this same generator can see. The pair makes that + # difference observable; without the lowercase control, a red could equally + # mean the fixture itself is malformed. + "/api/$fixtureVersion/widgets" = [PSCustomObject]@{ + get = [PSCustomObject]@{ + parameters = @( + [PSCustomObject]@{ name = 'limit'; 'in' = 'query' } + ) + } + patch = [PSCustomObject]@{ + requestBody = [PSCustomObject]@{ + content = [PSCustomObject]@{ + 'application/json' = [PSCustomObject]@{ + schema = [PSCustomObject]@{ '$ref' = '#/components/schemas/SyntheticEndpointPatch' } + } + } + } + } + } + "/api/$fixtureVersion/gadgets" = [PSCustomObject]@{ + get = [PSCustomObject]@{ + parameters = @( + [PSCustomObject]@{ name = 'limit'; 'in' = 'query' } + ) + } + patch = [PSCustomObject]@{ + requestBody = [PSCustomObject]@{ + content = [PSCustomObject]@{ + 'application/json' = [PSCustomObject]@{ + schema = [PSCustomObject]@{ '$ref' = '#/components/schemas/SyntheticEndpointPatch' } + } + } + } + } + } # Exists only so the audited-control fixture's OTHER parameter resolves to a # DECLARED key. Without it that cmdlet's -Name would be skipped as # 'endpoint/verb absent from spec' and the skip-accounting assertions would be @@ -574,6 +628,34 @@ function Remove-PfbBucket { $queryParams = @{ 'names' = $Name } Invoke-PfbApiRequest -Array $Array -Method DELETE -Endpoint 'synthetic/allow' -QueryParams $queryParams } +'@ + + # The endpoint-case pair. -Endpoint 'Widgets' vs the spec's '/api/9.9/widgets' is the + # ONLY difference between these two cmdlets; 'gadgets' is the lowercase control, and it + # must classify identically under shipped code and stay green under an ordinal-exact + # index, so a red on the mixed-case one can only mean the case divergence. + Set-Content -LiteralPath (Join-Path $fixturePublicDirectory 'Get-PfbSyntheticMixedCaseEndpoint.ps1') -Encoding UTF8 -Value @' +function Get-PfbSyntheticMixedCaseEndpoint { + [CmdletBinding()] + param( + [Parameter()] [bool]$Stowed, + [Parameter()] [PSCustomObject]$Array + ) + $queryParams = @{ 'archived' = $Stowed } + Invoke-PfbApiRequest -Array $Array -Method GET -Endpoint 'Widgets' -QueryParams $queryParams +} +'@ + + Set-Content -LiteralPath (Join-Path $fixturePublicDirectory 'Get-PfbSyntheticLowerCaseEndpoint.ps1') -Encoding UTF8 -Value @' +function Get-PfbSyntheticLowerCaseEndpoint { + [CmdletBinding()] + param( + [Parameter()] [bool]$Stowed, + [Parameter()] [PSCustomObject]$Array + ) + $queryParams = @{ 'archived' = $Stowed } + Invoke-PfbApiRequest -Array $Array -Method GET -Endpoint 'gadgets' -QueryParams $queryParams +} '@ $script:syntheticReportPath = Join-Path $fixtureWorkRoot 'synthetic.json' @@ -697,10 +779,44 @@ function Remove-PfbBucket { # a dead key would find a declaration on some other path and this file's three # classification arms would all still pass. Asserting that no dead key in the whole # synthetic population claims a Body site it cannot have is what closes that. + # The three body-bearing fixture paths are listed by name rather than skipped by a + # pattern: any NEW fixture path with a request body must be added here consciously, + # which is the point -- a wildcard would quietly re-open the hole. + $bodyBearing = @('synthetic/surface', 'Widgets', 'gadgets') $leaks = @(@($syntheticReport.deadKeys) | Where-Object { - $_.endpoint -ne 'synthetic/surface' -and @($_.declaredElsewhere | Where-Object { $_.surface -eq 'Body' }).Count -gt 0 + $_.endpoint -notin $bodyBearing -and @($_.declaredElsewhere | Where-Object { $_.surface -eq 'Body' }).Count -gt 0 } | ForEach-Object { "$($_.cmdlet)|$($_.parameter) on $($_.endpoint)" }) - @($leaks) -join '; ' | Should -BeNullOrEmpty -Because 'synthetic/surface is the only fixture path with a request body, so a Body provenance anywhere else means the declaration index is not keyed per endpoint' + @($leaks) -join '; ' | Should -BeNullOrEmpty -Because "only [$($bodyBearing -join ', ')] carry a request body in this fixture, so a Body provenance on any other endpoint means the declaration index is not keyed per endpoint" + } + + It 'matches the declaration index on the endpoint case-insensitively, exactly as the gate does' { + # The endpoint axis of the same argument the key axis already carries: the deadness + # gate reaches the spec through PSObject property access on a ConvertFrom-Json object, + # which is case-INSENSITIVE, so a cmdlet whose -Endpoint literal differs in case from + # the spec path key is still gated normally and still reaches classification. An index + # keyed ordinal-exactly would miss it and publish UNDECLARED -- a positive assertion + # that nothing on the endpoint declares the key, about a key this same generator can + # see one line earlier. That is the strictly worse failure direction, so it is asserted + # rather than assumed. + # + # Not a live defect today: all 85 real dead-key endpoint literals match a normalized + # spec path exactly. This guards a future ordinal hardening, which is a plausible and + # well-intentioned change. + $mixed = @(@($syntheticReport.deadKeys) | Where-Object { $_.cmdlet -eq 'Get-PfbSyntheticMixedCaseEndpoint' }) + @($mixed).Count | Should -Be 1 -Because "-Stowed writes 'archived' on GET Widgets, and the gate resolves '/api/$fixtureVersion/Widgets' against the lower-case spec key, so the key is dead and reaches classification. Reported dead keys were: $syntheticDeadKeyText" + $mixed[0].endpoint | Should -Be 'Widgets' -Because 'the record must keep the literal the cmdlet itself wrote; a normalising rewrite here would hide the divergence this test exists to exercise' + $mixed[0].classification | Should -Be 'WRONG-SURFACE' -Because "PATCH widgets declares 'archived' as a body property. UNDECLARED here means the index is keyed more strictly than the gate. declaredElsewhere was: $(@($mixed[0].declaredElsewhere | ForEach-Object { "$($_.method)/$($_.surface)" }) -join ', ')" + @($mixed[0].declaredElsewhere).Count | Should -Be 1 -Because 'the one PATCH body declaration is the whole provenance' + @($mixed[0].declaredElsewhere | ForEach-Object { "$($_.method)/$($_.surface)" }) | Should -Be @('PATCH/Body') + + # THE CONTROL. Identical fixture in every respect except that its endpoint literal + # matches the spec path case, so it classifies the same way with or without + # case-insensitive endpoint keys. If the assertion above ever reds while this one is + # green, the case divergence is the only remaining explanation. + $lower = @(@($syntheticReport.deadKeys) | Where-Object { $_.cmdlet -eq 'Get-PfbSyntheticLowerCaseEndpoint' }) + @($lower).Count | Should -Be 1 -Because "the control must itself be a dead key, or it controls for nothing. Reported dead keys were: $syntheticDeadKeyText" + $lower[0].classification | Should -Be 'WRONG-SURFACE' -Because 'the control shares the spec shape, key and verb of the mixed-case fixture, so a difference between the two can only come from the endpoint case' + @($lower[0].declaredElsewhere | ForEach-Object { "$($_.method)/$($_.surface)" }) | Should -Be @('PATCH/Body') } It 'counts a parameter of a function that issues no request as outside standard request, not unresolved' { diff --git a/tools/Build-PfbDeadKeyReport.ps1 b/tools/Build-PfbDeadKeyReport.ps1 index 1f07dbb9..b4663967 100644 --- a/tools/Build-PfbDeadKeyReport.ps1 +++ b/tools/Build-PfbDeadKeyReport.ps1 @@ -185,10 +185,19 @@ function Get-PfbDeadKeyDeclarationIndex { 2. It hops a `type: array` request body onto its `items` element schema (issue #82). A bespoke `Get-PfbSchemaPropertyNames -Schema $op.requestBody.content..schema` call has nothing to descend for an array body and silently records zero body - properties. + properties. RESIDUAL, stated because it is a real loss of precision and not a + bug: a Body site found inside an array ELEMENT reads identically to a top-level + one, so `declaredElsewhere` cannot tell "the field belongs on each element of the + posted array" from "the field belongs on the body object". It is the right trade + anyway -- omitting the hop manufactures a false UNDECLARED, an assertion of + absence, which is the worse direction -- and it publishes nothing false today: + measured on fb2.28, ZERO dead keys fall on an array-bodied operation. 3. It reads body schemas at MaxDepth 32, not the helpers' own default of 8. Depth 8 truncates the fb2.12-2.16 allOf chains (issue #71); a bespoke walk would have to - re-decide that value, and the cheap wrong answer is to accept the default. + re-decide that value, and the cheap wrong answer is to accept the default. Note + that 32 is INHERITED, not pinned here: the call below passes no -MaxDepth, so the + value comes from Get-PfbSpecCapabilities' own default (tools/lib/PfbSpecTools.ps1). + Lowering that default would silently lower this report's depth too. WHAT IT DELIBERATELY DOES *NOT* TAKE FROM THAT FUNCTION IS `Parameters`. That field is every parameter regardless of `in:` location, not the query ones. Measured on fb2.28: @@ -408,9 +417,18 @@ foreach ($record in $inventory) { $classification = Get-PfbDeadKeyClassification -DeclarationSite @($declarationSites) -Method $method # Deduplicate BEFORE sorting. Sort-PfbDeadKeyRecords is an unstable introsort (see its - # header), so it is deterministic only over unique keys -- and (Method, Surface) is unique - # here precisely because this loop collapses it. One operation can legitimately declare the - # same key on both surfaces, which is why Surface is a sort key and not only a label. + # header), so it is deterministic only over unique keys, and a duplicate (Method, Surface) + # pair would make the COMMITTED artifact's byte order depend on .NET's partitioning -- a + # diff that changes with no input change. That is why this dedup is load-bearing rather + # than tidiness. + # + # A duplicate pair is not produced by any spec we pin -- measured on fb2.28: 0 duplicate + # (Path, Method) groups across 264 normalized paths, exact-case and case-insensitive -- but + # the claim stops there, and deliberately: the index build adds one entry per capability + # record with no per-method collapse, so any spec whose version-prefixed path keys normalize + # NON-INJECTIVELY onto one endpoint reaches this loop with the same (Method, Surface) twice. + # One operation can also legitimately declare the same key on both surfaces, which is why + # Surface is a sort key and not only a label. $seenSites = [System.Collections.Generic.HashSet[string]]::new([System.StringComparer]::Ordinal) $uniqueSites = [System.Collections.Generic.List[object]]::new() foreach ($site in $declarationSites) { From 3a26ffcf3d45c0a94783a666a42c884db764a246 Mon Sep 17 00:00:00 2001 From: Justin Emerson Date: Thu, 27 Aug 2026 11:29:50 -0700 Subject: [PATCH 19/29] fix(reports): correct the dedup rationale, harden two dead-key test guards Review round 2, comments and tests only -- no classifier behaviour changes. The dedup comment imported the introsort-stability argument from the top-level sorts, where it holds, to a place where it does not: a declaredElsewhere site carries only Method and Surface and the projection emits only those two, so a tie on both sort keys is a tie on the entire serialised record and an unstable sort cannot reorder byte-identical elements. Measured from a planted duplicate with the dedup removed: the two entries serialise to one distinct string. The real consequence is a WRONG ROW -- declaredElsewhere: [DELETE/Query, DELETE/Query] published in a committed artifact -- so the comment now says that, points at the assertion that catches it, and warns off the "add a tie-break property" remedy, which makes the order total and still publishes both rows. Should -Be is case-insensitive for strings, so the endpoint-literal assertion could not fail for the reason it gave: lowercasing the emitted record left the whole file green. It is now Should -BeExactly, and that mutant is KILLED. The anti-leak exclusion list was a hand-maintained literal, whose mechanical response to a red is to append the offending endpoint -- a one-token edit that disables the gate and looks like every legitimate edit around it. It is now derived from the fixture spec (normalized paths whose operations declare a requestBody), so an unjustified widening cannot be written and a fixture path that loses its body cannot leave a stale exclusion behind. Re-planting a document-keyed Body leak reds the assertion with the offender named, and the derivation also proved the old hand-list was wrong in the other direction: it omitted synthetic/undeclared. Finally, the header claimed this file contributes six 5.1 skips. That has been false since the first Task 5 commit; it is sixteen. Co-Authored-By: Claude Opus 5 (1M context) --- Tests/Build-PfbDeadKeyReport.Tests.ps1 | 56 +++++++++++++++++++++----- tools/Build-PfbDeadKeyReport.ps1 | 18 ++++++--- 2 files changed, 58 insertions(+), 16 deletions(-) diff --git a/Tests/Build-PfbDeadKeyReport.Tests.ps1 b/Tests/Build-PfbDeadKeyReport.Tests.ps1 index e3470e48..98c1b5bb 100644 --- a/Tests/Build-PfbDeadKeyReport.Tests.ps1 +++ b/Tests/Build-PfbDeadKeyReport.Tests.ps1 @@ -13,7 +13,10 @@ cache, the real Public/ tree and the committed artifact; synthetic classification needs only a fixture it builds itself. One shared BeforeAll would have let a broken fixture red the two regeneration tests, reporting the real generator as broken when it was fine. - The total It count is unchanged by the split, so 5.1 still contributes exactly six skips. + The split itself moved no It between editions: both Describes are PS7-gated, so 5.1 skips + every It in this file -- SIXTEEN of them today. That figure changes whenever an It is added + here and is consumed by Tests/coverage-baseline.psd1; it read six until issue #141 Task 5 + added ten. WHY EVERY DESCRIBE CARRIES -Skip:($PSVersionTable.PSVersion.Major -lt 7): the generator carries `#Requires -Version 7.0`, so it cannot run on Windows PowerShell 5.1 @@ -39,10 +42,10 @@ Describe 'Build-PfbDeadKeyReport regeneration (real spec cache required, PS7 onl # SPLIT FROM THE SYNTHETIC BLOCK BELOW ON PURPOSE, and the seam is a dependency boundary # rather than a stylistic one: this half needs the real ~50MB tools/specs cache, the real # Public/ tree and the committed artifact; the half below needs a fixture and nothing else. - # Sharing one BeforeAll made a throw anywhere red all six tests, so a broken FIXTURE would - # have reported the real generator as broken. Splitting also stops the synthetic half - # depending on a cache it never reads. Both halves keep the PS7 gate, and the total It - # count is unchanged, so 5.1 still contributes exactly six skips. + # Sharing one BeforeAll made a throw anywhere red every test in the file, so a broken + # FIXTURE would have reported the real generator as broken. Splitting also stops the + # synthetic half depending on a cache it never reads. Both halves keep the PS7 gate, so the + # split moved no It between editions -- 5.1 skips all sixteen (see the file header). BeforeAll { $script:repoRoot = Split-Path -Parent $PSScriptRoot @@ -484,6 +487,27 @@ Describe 'Build-PfbDeadKeyReport classification (synthetic fixture, no spec cach $fixtureSpec | ConvertTo-Json -Depth 20 | Set-Content -LiteralPath (Join-Path $fixtureSpecsDirectory "fb$fixtureVersion.json") -Encoding UTF8 + # DERIVED FROM THE FIXTURE, never hand-listed. The anti-leak assertion below excludes + # the endpoints that legitimately have a Body provenance, and a hand-maintained literal + # would make the mechanical response to a red "append the offending endpoint" -- a + # one-token edit indistinguishable from a legitimate one, which disables the gate + # exactly the way a comment asking for conscious review cannot prevent. Deriving it + # means an unjustified addition is impossible to make, and a fixture path that LOSES + # its request body cannot leave a stale over-broad exclusion behind. + $script:fixtureBodyBearingEndpoints = @( + foreach ($pathProperty in $fixtureSpec.paths.PSObject.Properties) { + $declaresBody = $false + foreach ($operationProperty in $pathProperty.Value.PSObject.Properties) { + if ($operationProperty.Value.PSObject.Properties.Name -contains 'requestBody') { + $declaresBody = $true + } + } + if ($declaresBody) { + $pathProperty.Name -replace ('^/api/' + [regex]::Escape($fixtureVersion) + '/'), '' + } + } + ) + $fixtureCapabilityMapPath = Join-Path $fixtureWorkRoot 'PfbFixtureCapabilityMap.json' ([PSCustomObject]@{ generatedFrom = @($fixtureVersion) } | ConvertTo-Json -Depth 5) | Set-Content -LiteralPath $fixtureCapabilityMapPath -Encoding UTF8 @@ -779,14 +803,21 @@ function Get-PfbSyntheticLowerCaseEndpoint { # a dead key would find a declaration on some other path and this file's three # classification arms would all still pass. Asserting that no dead key in the whole # synthetic population claims a Body site it cannot have is what closes that. - # The three body-bearing fixture paths are listed by name rather than skipped by a - # pattern: any NEW fixture path with a request body must be added here consciously, - # which is the point -- a wildcard would quietly re-open the hole. - $bodyBearing = @('synthetic/surface', 'Widgets', 'gadgets') + # The excluded set is COMPUTED from the fixture spec in BeforeAll (the normalized paths + # whose operations declare a requestBody) rather than written out here, so it cannot be + # widened by hand to silence a red. Its own non-emptiness is asserted first: an empty + # exclusion set would make the assertion below strictly stronger, but an exclusion set + # that silently stopped being derived at all is a fixture defect worth naming. + $bodyBearing = @($fixtureBodyBearingEndpoints) + @($bodyBearing).Count | Should -BeGreaterThan 0 -Because 'the exclusion set is derived from the fixture spec, so an empty one means the derivation broke rather than that the fixture has no bodies' + + # -notin is case-INSENSITIVE, which is deliberate and matches the gate: the derived set + # holds the spec-cased path ('widgets') while a record may carry the cmdlet-cased + # literal ('Widgets'), and those are the same endpoint everywhere else in this file. $leaks = @(@($syntheticReport.deadKeys) | Where-Object { $_.endpoint -notin $bodyBearing -and @($_.declaredElsewhere | Where-Object { $_.surface -eq 'Body' }).Count -gt 0 } | ForEach-Object { "$($_.cmdlet)|$($_.parameter) on $($_.endpoint)" }) - @($leaks) -join '; ' | Should -BeNullOrEmpty -Because "only [$($bodyBearing -join ', ')] carry a request body in this fixture, so a Body provenance on any other endpoint means the declaration index is not keyed per endpoint" + @($leaks) -join '; ' | Should -BeNullOrEmpty -Because "only [$($bodyBearing -join ', ')] declare a request body in this fixture, so a Body provenance on any other endpoint means the declaration index is not keyed per endpoint" } It 'matches the declaration index on the endpoint case-insensitively, exactly as the gate does' { @@ -804,7 +835,10 @@ function Get-PfbSyntheticLowerCaseEndpoint { # well-intentioned change. $mixed = @(@($syntheticReport.deadKeys) | Where-Object { $_.cmdlet -eq 'Get-PfbSyntheticMixedCaseEndpoint' }) @($mixed).Count | Should -Be 1 -Because "-Stowed writes 'archived' on GET Widgets, and the gate resolves '/api/$fixtureVersion/Widgets' against the lower-case spec key, so the key is dead and reaches classification. Reported dead keys were: $syntheticDeadKeyText" - $mixed[0].endpoint | Should -Be 'Widgets' -Because 'the record must keep the literal the cmdlet itself wrote; a normalising rewrite here would hide the divergence this test exists to exercise' + # -BeExactly, NOT -Be. `Should -Be` is CASE-INSENSITIVE for strings, so the -Be form of + # this line could not fail for the reason it gives: measured, lowercasing the emitted + # record to 'widgets' (lookups untouched) left every assertion in this file green. + $mixed[0].endpoint | Should -BeExactly 'Widgets' -Because 'the record must keep the literal the cmdlet itself wrote; a normalising rewrite here would hide the divergence this test exists to exercise, and endpoint is how a reader locates that literal in the source' $mixed[0].classification | Should -Be 'WRONG-SURFACE' -Because "PATCH widgets declares 'archived' as a body property. UNDECLARED here means the index is keyed more strictly than the gate. declaredElsewhere was: $(@($mixed[0].declaredElsewhere | ForEach-Object { "$($_.method)/$($_.surface)" }) -join ', ')" @($mixed[0].declaredElsewhere).Count | Should -Be 1 -Because 'the one PATCH body declaration is the whole provenance' @($mixed[0].declaredElsewhere | ForEach-Object { "$($_.method)/$($_.surface)" }) | Should -Be @('PATCH/Body') diff --git a/tools/Build-PfbDeadKeyReport.ps1 b/tools/Build-PfbDeadKeyReport.ps1 index b4663967..47e0cc37 100644 --- a/tools/Build-PfbDeadKeyReport.ps1 +++ b/tools/Build-PfbDeadKeyReport.ps1 @@ -416,11 +416,19 @@ foreach ($record in $inventory) { -Endpoint ([string]$record.Endpoint) -WireKey $wireName $classification = Get-PfbDeadKeyClassification -DeclarationSite @($declarationSites) -Method $method - # Deduplicate BEFORE sorting. Sort-PfbDeadKeyRecords is an unstable introsort (see its - # header), so it is deterministic only over unique keys, and a duplicate (Method, Surface) - # pair would make the COMMITTED artifact's byte order depend on .NET's partitioning -- a - # diff that changes with no input change. That is why this dedup is load-bearing rather - # than tidiness. + # Deduplicate, and NOT for the reason the top-level sorts carry. The introsort-stability + # argument documented at the head of Sort-PfbDeadKeyRecords does NOT apply here: a site + # object holds only Method and Surface and the projection below emits only those two, so a + # tie on both sort keys is a tie on the ENTIRE serialised record and an unstable sort cannot + # move a byte among byte-identical elements. Measured: with this dedup removed and a + # duplicate planted, the two entries serialise to one distinct string. + # + # What the dedup actually prevents is a WRONG ROW -- `declaredElsewhere: [DELETE/Query, + # DELETE/Query]`, published in a committed artifact, asserting two declaration sites where + # the spec has one. Tests/Build-PfbDeadKeyReport.Tests.ps1:247-249 asserts exactly that + # ("has a duplicated declaredElsewhere entry") and is the pointer to follow on a red here. + # Do NOT reach for the "add a final tie-break property" remedy at the head of + # Sort-PfbDeadKeyRecords: a tie-break makes the order total and still publishes both rows. # # A duplicate pair is not produced by any spec we pin -- measured on fb2.28: 0 duplicate # (Path, Method) groups across 264 normalized paths, exact-case and case-insensitive -- but From b4dbeba020dc104a0bc19428df38d2e353c2d784 Mon Sep 17 00:00:00 2001 From: Justin Emerson Date: Thu, 27 Aug 2026 13:50:06 -0700 Subject: [PATCH 20/29] docs(reports): retract the introsort rationale everywhere it was copied Round-3 comment-only corrections. No executable line changes: all 42 changed lines are comments, both files parse clean, and the scoped run is unchanged at pwsh 7 15/1/0 (inherited staleness only) and WinPS 5.1 0/0/16, container ok. - tools/Build-PfbDeadKeyReport.ps1: fix the cross-reference to the assertion that catches a duplicated declaredElsewhere entry -- the quoted string is at Tests/Build-PfbDeadKeyReport.Tests.ps1:251 and the assertion block is :249-252, not :247-249, which held only comments and an assignment. - tools/Build-PfbDeadKeyReport.ps1: put the anti-tie-break caution at the head of Sort-PfbDeadKeyRecords, where the remedy it contradicts actually lives. A site object carries only Method and Surface, so there is no third property to break a tie with, and a total order would still publish both rows. - Tests/Build-PfbDeadKeyReport.Tests.ps1: retract the falsified introsort claim in the two further places it had been copied to -- the deduplication assertion and the ordered-provenance assertion. Neither ordering depends on sort stability: the provenance sites have distinct sort keys so the comparison never returns 0, and a tie on (method, surface) is a tie on the entire serialised record. Both assertions are correct; only their stated reasons were wrong. The ordered form is justified by the comparer contract it pins instead. - Tests/Build-PfbDeadKeyReport.Tests.ps1: record that the derived exclusion set is strictly larger than the literal it replaced, newly excluding synthetic/undeclared, so a Body leak there is caught only per-record. Co-Authored-By: Claude Opus 5 (1M context) --- Tests/Build-PfbDeadKeyReport.Tests.ps1 | 33 ++++++++++++++++++++++---- tools/Build-PfbDeadKeyReport.ps1 | 9 ++++++- 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/Tests/Build-PfbDeadKeyReport.Tests.ps1 b/Tests/Build-PfbDeadKeyReport.Tests.ps1 index 98c1b5bb..bed15e68 100644 --- a/Tests/Build-PfbDeadKeyReport.Tests.ps1 +++ b/Tests/Build-PfbDeadKeyReport.Tests.ps1 @@ -243,9 +243,16 @@ Describe 'Build-PfbDeadKeyReport regeneration (real spec cache required, PS7 onl $offenders.Add("$identity is '$($record.classification)' but its provenance [$(@($sites | ForEach-Object { "$($_.method)/$($_.surface)" }) -join ', ')] implies '$expected'") } # Sorted by method then surface, ORDINALLY, deduplicated. Recomputed here rather - # than trusted: the generator's comparer is an unstable introsort, so a duplicate + # than trusted. + # + # NOT for byte-order reasons. An earlier version of this comment claimed a duplicate # (method, surface) pair would make the artifact's byte order depend on .NET's - # partitioning. + # introsort partitioning; that was measured false and retracted here and at the dedup + # site in tools/Build-PfbDeadKeyReport.ps1. A site object carries only Method and + # Surface and the projection emits only those two, so a tie on both sort keys is a tie + # on the ENTIRE serialised record -- an unstable sort cannot reorder byte-identical + # elements observably. What a duplicate actually produces is a WRONG ROW, and the + # assertion below is what catches it. $keys = @($sites | ForEach-Object { "$($_.method)|$($_.surface)" }) if (@($keys | Select-Object -Unique).Count -ne $keys.Count) { $offenders.Add("$identity has a duplicated declaredElsewhere entry: [$($keys -join ', ')]") @@ -494,6 +501,15 @@ Describe 'Build-PfbDeadKeyReport classification (synthetic fixture, no spec cach # exactly the way a comment asking for conscious review cannot prevent. Deriving it # means an unjustified addition is impossible to make, and a fixture path that LOSES # its request body cannot leave a stale over-broad exclusion behind. + # + # ONE COST OF THE SWAP, recorded rather than discovered later: the derived set is strictly + # LARGER than the hand-written literal it replaced -- it newly excludes + # 'synthetic/undeclared', which does declare a requestBody and which the literal had + # omitted. So a Body-provenance leak on that endpoint is now invisible to the anti-leak + # assertion below, and is caught only per-record by the classification assertions further + # down in the UNDECLARED test. That compensation is per-record: a SECOND dead key on + # 'synthetic/undeclared' would have neither guard. The derivation is still the right trade + # -- the literal's omission was itself a latent false-red -- but the exclusion did widen. $script:fixtureBodyBearingEndpoints = @( foreach ($pathProperty in $fixtureSpec.paths.PSObject.Properties) { $declaresBody = $false @@ -765,9 +781,16 @@ function Get-PfbSyntheticLowerCaseEndpoint { $entry[0].classification | Should -Be 'WRONG-SURFACE' -Because "PATCH synthetic/surface declares 'flagged' as a body property, reachable only through `$ref -> allOf -> `$ref. UNDECLARED here means the fixture's allOf chain was not resolved; WRONG-VERB means Body lost the priority ladder to the DELETE/PATCH query declarations. declaredElsewhere was: $(@($entry[0].declaredElsewhere | ForEach-Object { "$($_.method)/$($_.surface)" }) -join ', ')" # Provenance is the whole value of the classification, and it is asserted as an exact - # ORDERED list: deduplicated, and sorted by method then surface ordinally. An - # order-insensitive assertion would let the generator's unstable introsort reorder the - # committed artifact between runs on identical inputs. + # ORDERED list: deduplicated, and sorted by method then surface ordinally. + # + # NOT because an unstable sort could reorder it. An earlier version of this comment said + # so; that reasoning is retracted for the same reason as the one at the deduplication + # assertion above. These three sites have DISTINCT sort keys, so the comparison never + # returns 0 and the introsort's output is deterministic; instability manifests only on + # ties, and a tie on (method, surface) is a tie on the entire serialised record. The + # ordered form is asserted because it pins the comparer's actual CONTRACT -- method first, + # then surface, both ordinal -- which an order-insensitive assertion would leave + # unexercised, as the -Because below spells out. $sites = @($entry[0].declaredElsewhere | ForEach-Object { "$($_.method)/$($_.surface)" }) $sites | Should -Be @('DELETE/Query', 'PATCH/Body', 'PATCH/Query') -Because "the fixture declares exactly those three sites, and both sort keys must be exercised: DELETE before PATCH orders on method, Body before Query orders on surface within PATCH. Got: [$($sites -join ', ')]" } diff --git a/tools/Build-PfbDeadKeyReport.ps1 b/tools/Build-PfbDeadKeyReport.ps1 index 47e0cc37..2f6bfea8 100644 --- a/tools/Build-PfbDeadKeyReport.ps1 +++ b/tools/Build-PfbDeadKeyReport.ps1 @@ -74,6 +74,13 @@ $inventory = @(Get-PfbCmdletParameterInventory -PublicDirectory $PublicDirectory # -Property list (WireKey for deadKeys is enough today), here and in the mirrored comparer in # Tests/CommittedDeadKeyReport.Tests.ps1. Do not reach for a "stable sort" instead -- a total # order is what makes the artifact reproducible. +# +# THAT REMEDY IS FOR THESE TOP-LEVEL SORTS ONLY. Do NOT reach for it on a duplicate +# (Method, Surface) pair inside declaredElsewhere. A site object carries only those two +# properties, so there is no third property to break the tie with -- and even a total order there +# would still publish BOTH rows, `declaredElsewhere: [DELETE/Query, DELETE/Query]`, which is a +# wrong row in a committed artifact rather than a byte-order flap. The dedup at the +# declaredElsewhere projection is the fix for that case; its reasoning is recorded beside it. function Sort-PfbDeadKeyRecords { param( [AllowEmptyCollection()] @@ -425,7 +432,7 @@ foreach ($record in $inventory) { # # What the dedup actually prevents is a WRONG ROW -- `declaredElsewhere: [DELETE/Query, # DELETE/Query]`, published in a committed artifact, asserting two declaration sites where - # the spec has one. Tests/Build-PfbDeadKeyReport.Tests.ps1:247-249 asserts exactly that + # the spec has one. Tests/Build-PfbDeadKeyReport.Tests.ps1:249-252 asserts exactly that # ("has a duplicated declaredElsewhere entry") and is the pointer to follow on a red here. # Do NOT reach for the "add a final tie-break property" remedy at the head of # Sort-PfbDeadKeyRecords: a tie-break makes the order total and still publishes both rows. From b89cd5f94e69fd07f7445008372f79b4eda222f4 Mon Sep 17 00:00:00 2001 From: Justin Emerson Date: Thu, 27 Aug 2026 13:53:05 -0700 Subject: [PATCH 21/29] test(reports): discriminate the dead-key declaration scope guards Add two synthetic-fixture declarations of `policy_names` -- a body property on SyntheticEndpointPatch and a query key on DELETE synthetic/allow -- neither of which any fixture record needs declared. They exist so that widening Get-PfbDeadKeyDeclarationSite's Body lookup from the record's own endpoint to a union over the index stops being an equivalent mutant: either widening now hands Remove-PfbSyntheticDeadKey's dead PolicyName a Body provenance on synthetic/dead, which the anti-leak control rejects. Co-Authored-By: Claude Opus 5 (1M context) --- Tests/Build-PfbDeadKeyReport.Tests.ps1 | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Tests/Build-PfbDeadKeyReport.Tests.ps1 b/Tests/Build-PfbDeadKeyReport.Tests.ps1 index bed15e68..ed77773f 100644 --- a/Tests/Build-PfbDeadKeyReport.Tests.ps1 +++ b/Tests/Build-PfbDeadKeyReport.Tests.ps1 @@ -334,6 +334,14 @@ Describe 'Build-PfbDeadKeyReport classification (synthetic fixture, no spec cach SyntheticEndpointPatch = [PSCustomObject]@{ properties = [PSCustomObject]@{ archived = [PSCustomObject]@{ type = 'boolean' } + # A *_names BODY property on an endpoint that no dead key resolves + # to. Nothing in this fixture needs it declared; it exists so that a + # Body lookup widened from the record's own endpoint to a union over + # the whole index -- even one narrowed to *_names keys -- finds + # 'policy_names' here and hands Remove-PfbSyntheticDeadKey's dead + # PolicyName a Body provenance it cannot have. Without this line that + # widening is an equivalent mutant. + policy_names = [PSCustomObject]@{ type = 'array' } } } SyntheticUndeclaredPost = [PSCustomObject]@{ @@ -458,6 +466,13 @@ Describe 'Build-PfbDeadKeyReport classification (synthetic fixture, no spec cach delete = [PSCustomObject]@{ parameters = @( [PSCustomObject]@{ name = 'names'; 'in' = 'query' } + # The QUERY counterpart of the *_names body property above, on a + # DIFFERENT endpoint from the one whose dead PolicyName resolves. It + # closes the sibling widening: a Body site synthesised because SOME + # endpoint declares the key as a query key. Query-declared here and + # nowhere on synthetic/dead, so the unmutated generator must still + # report UNDECLARED with no provenance. + [PSCustomObject]@{ name = 'policy_names'; 'in' = 'query' } ) } } From 866b344d5e9ff6182f5a6368f0217fa22eb34cf5 Mon Sep 17 00:00:00 2001 From: Justin Emerson Date: Thu, 27 Aug 2026 14:04:35 -0700 Subject: [PATCH 22/29] docs(reports): bound the anti-leak residual to the one exempt endpoint Comment-only. An earlier draft of this paragraph, written before the measurement existed, read as though the anti-leak assertion were broadly blind to a scope error. It is not. Measured: with the 'policy_names' body property now in the fixture, widening the Body lookup to a document-wide union over the whole declaration index DOES red the anti-leak assertion, on 'Remove-PfbSyntheticDeadKey|PolicyName on synthetic/dead' -- that endpoint declares no request body, so it is not excluded and the leak surfaces there. Before that fixture property existed the same widening produced zero offenders and the assertion passed while the index was document-keyed. So the residual is exactly one exempt endpoint ('synthetic/undeclared', newly excluded because the derived set is strictly larger than the literal it replaced), not a general weakness. The paragraph now says so. Scoped run unchanged: pwsh 7 15/1/0 with the inherited staleness failure as the only red, WinPS 5.1 0/0/16, container ok both. 12 changed lines, all comments, zero executable; file parses clean. Co-Authored-By: Claude Opus 5 (1M context) --- Tests/Build-PfbDeadKeyReport.Tests.ps1 | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/Tests/Build-PfbDeadKeyReport.Tests.ps1 b/Tests/Build-PfbDeadKeyReport.Tests.ps1 index ed77773f..613abd33 100644 --- a/Tests/Build-PfbDeadKeyReport.Tests.ps1 +++ b/Tests/Build-PfbDeadKeyReport.Tests.ps1 @@ -520,11 +520,21 @@ Describe 'Build-PfbDeadKeyReport classification (synthetic fixture, no spec cach # ONE COST OF THE SWAP, recorded rather than discovered later: the derived set is strictly # LARGER than the hand-written literal it replaced -- it newly excludes # 'synthetic/undeclared', which does declare a requestBody and which the literal had - # omitted. So a Body-provenance leak on that endpoint is now invisible to the anti-leak + # omitted. So a Body-provenance leak on THAT ONE ENDPOINT is invisible to the anti-leak # assertion below, and is caught only per-record by the classification assertions further # down in the UNDECLARED test. That compensation is per-record: a SECOND dead key on # 'synthetic/undeclared' would have neither guard. The derivation is still the right trade # -- the literal's omission was itself a latent false-red -- but the exclusion did widen. + # + # SCOPE OF THAT RESIDUAL, measured rather than assumed, because an earlier draft of this + # comment over-stated it: the exclusion is the ONLY blind spot, and the assertion is not + # broadly toothless. With the 'policy_names' body property below, a Body lookup widened to + # a document-wide union over the whole declaration index DOES red the anti-leak assertion, + # on 'Remove-PfbSyntheticDeadKey|PolicyName on synthetic/dead' -- 'synthetic/dead' declares + # no request body, so it is not excluded and the leak surfaces there. Before that fixture + # property existed, the same widening produced zero offenders and the assertion passed + # while the index was document-keyed. So read this paragraph as "one endpoint is exempt", + # not as "the control cannot see a scope error". $script:fixtureBodyBearingEndpoints = @( foreach ($pathProperty in $fixtureSpec.paths.PSObject.Properties) { $declaresBody = $false From 30138d055e67f3b566360e861c9a2530b46d4a2f Mon Sep 17 00:00:00 2001 From: Justin Emerson Date: Thu, 27 Aug 2026 14:23:37 -0700 Subject: [PATCH 23/29] docs(reports): cite the dedup assertion by string, not by line range Fixes a blocking review finding against my own prior commit, plus two over-broad claims in comments I wrote. Comment-only: 31 changed lines, zero executable, both files parse clean, scoped run unchanged at pwsh 7 15/1/0 (inherited staleness only) and WinPS 5.1 0/0/16, container ok. BLOCKING. b4dbeba set out to fix a stale cross-reference and committed one that was stale on arrival: it cited Tests/Build-PfbDeadKeyReport.Tests.ps1:249-252, derived from the file BEFORE that same commit inserted seven lines of retraction prose above the target. At HEAD those four lines are pure comment; the $keys assignment is :256, the assertion :257-259, the quoted string :258. That is worse than the range it replaced, which at least included the assignment. Rather than re-point the range -- the second stale citation of this same assertion in two rounds -- the pointer is now the quoted string "has a duplicated declaredElsewhere entry", verified unique in that file. A range is correct only if re-derived after the edit that writes it, and a pointer that silently rots is worse than none when the comment bills itself as the thing to follow on a red. Two minors, both mine, both universals generalised from one measurement: - The residual-scope paragraph said the exemption "is the ONLY blind spot". True of what the derivation added; false of the assertion in general. A union narrowed to 'flagged' leaks Body provenance onto 'synthetic/surface', excluded for reasons predating the derivation and caught by the ordered provenance assertion instead. Scoped to "the only blind spot the derivation added", with the counterexample recorded. - The ordered-provenance justification claimed the assertion pins "method first, then surface, both ordinal". The ordering halves are measured -- dropping Surface and swapping the keys each red it -- but ordinality is not pinned: DELETE before PATCH and Body before Query sort identically under ordinal and culture-aware comparison, so a -Culture '' mutant survives. Claim narrowed and the real location of the ordinality argument named. Co-Authored-By: Claude Opus 5 (1M context) --- Tests/Build-PfbDeadKeyReport.Tests.ps1 | 18 ++++++++++++++---- tools/Build-PfbDeadKeyReport.ps1 | 13 +++++++++++-- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/Tests/Build-PfbDeadKeyReport.Tests.ps1 b/Tests/Build-PfbDeadKeyReport.Tests.ps1 index 613abd33..9045c39d 100644 --- a/Tests/Build-PfbDeadKeyReport.Tests.ps1 +++ b/Tests/Build-PfbDeadKeyReport.Tests.ps1 @@ -527,8 +527,11 @@ Describe 'Build-PfbDeadKeyReport classification (synthetic fixture, no spec cach # -- the literal's omission was itself a latent false-red -- but the exclusion did widen. # # SCOPE OF THAT RESIDUAL, measured rather than assumed, because an earlier draft of this - # comment over-stated it: the exclusion is the ONLY blind spot, and the assertion is not - # broadly toothless. With the 'policy_names' body property below, a Body lookup widened to + # comment over-stated it: that exemption is the only blind spot THE DERIVATION ADDED, and + # the assertion is not broadly toothless. It is NOT the assertion's only blind spot in + # general -- a union narrowed to 'flagged' leaks Body provenance onto 'synthetic/surface', + # which is excluded for reasons predating the derivation, and is caught by the ordered + # provenance assertion instead. With the 'policy_names' body property below, a Body lookup widened to # a document-wide union over the whole declaration index DOES red the anti-leak assertion, # on 'Remove-PfbSyntheticDeadKey|PolicyName on synthetic/dead' -- 'synthetic/dead' declares # no request body, so it is not excluded and the leak surfaces there. Before that fixture @@ -814,8 +817,15 @@ function Get-PfbSyntheticLowerCaseEndpoint { # returns 0 and the introsort's output is deterministic; instability manifests only on # ties, and a tie on (method, surface) is a tie on the entire serialised record. The # ordered form is asserted because it pins the comparer's actual CONTRACT -- method first, - # then surface, both ordinal -- which an order-insensitive assertion would leave - # unexercised, as the -Because below spells out. + # then surface -- which an order-insensitive assertion would leave unexercised, as the + # -Because below spells out. Both halves of THAT much are measured: dropping Surface from + # the sort key list, and swapping the two keys, each red this assertion. + # + # What this fixture does NOT pin is ORDINALITY. 'DELETE' before 'PATCH' and 'Body' before + # 'Query' sort identically under ordinal and culture-aware comparison, so a -Culture '' + # mutant would survive here. Ordinality is argued at the head of + # tools/Build-PfbDeadKeyReport.ps1 and asserted in Tests/CommittedDeadKeyReport.Tests.ps1; + # do not read this assertion as covering it. $sites = @($entry[0].declaredElsewhere | ForEach-Object { "$($_.method)/$($_.surface)" }) $sites | Should -Be @('DELETE/Query', 'PATCH/Body', 'PATCH/Query') -Because "the fixture declares exactly those three sites, and both sort keys must be exercised: DELETE before PATCH orders on method, Body before Query orders on surface within PATCH. Got: [$($sites -join ', ')]" } diff --git a/tools/Build-PfbDeadKeyReport.ps1 b/tools/Build-PfbDeadKeyReport.ps1 index 2f6bfea8..cf56b11e 100644 --- a/tools/Build-PfbDeadKeyReport.ps1 +++ b/tools/Build-PfbDeadKeyReport.ps1 @@ -432,8 +432,17 @@ foreach ($record in $inventory) { # # What the dedup actually prevents is a WRONG ROW -- `declaredElsewhere: [DELETE/Query, # DELETE/Query]`, published in a committed artifact, asserting two declaration sites where - # the spec has one. Tests/Build-PfbDeadKeyReport.Tests.ps1:249-252 asserts exactly that - # ("has a duplicated declaredElsewhere entry") and is the pointer to follow on a red here. + # the spec has one. The assertion that catches it is in + # Tests/Build-PfbDeadKeyReport.Tests.ps1 -- search for the string + # "has a duplicated declaredElsewhere entry", which is unique in that file. Follow it on a + # red here. + # + # Deliberately a searchable string and NOT a line number. Two successive attempts to cite + # this same assertion by line range were both stale on arrival: each was derived from the + # file as it stood BEFORE the citing commit's own comment insertions shifted the target + # downward. A range is only correct if it is re-derived after the edit that writes it, and a + # pointer that silently rots is worse than none, because this comment bills itself as the + # thing to follow when the dedup reds. # Do NOT reach for the "add a final tie-break property" remedy at the head of # Sort-PfbDeadKeyRecords: a tie-break makes the order total and still publishes both rows. # From 3890719403ae9f13bab906e31b4d4a0ec9ba50fe Mon Sep 17 00:00:00 2001 From: Justin Emerson Date: Thu, 27 Aug 2026 14:56:04 -0700 Subject: [PATCH 24/29] docs(reports): drop an unrun counterexample and fix the ordinality pointer Round-5 fix, both findings against my own commit 30138d0 and both the same class the commit before it was written to remove: a claim asserted without being executed. Comment-only -- 55 changed lines, zero executable, executable token skeleton unchanged at 2981, file parses clean, scoped run unchanged at pwsh 7 15/1/0 (inherited staleness only) and WinPS 5.1 0/0/16, containers ok. MAJOR 1. The residual-scope paragraph justified its narrowing with a second counterexample -- "a union narrowed to 'flagged' leaks Body provenance onto synthetic/surface" -- that is FALSE. 'Flagged' is a body property of SyntheticSurfaceBase only, reached only by synthetic/surface PATCH, so a union narrowed to it returns the record's OWN endpoint's sites: declaredElsewhere is unchanged, nothing leaks, every assertion stays green. It is an equivalent mutant, which is exactly the hazard the 'policy_names' comment defuses for the *_names case and the reason that property had to be planted. Replaced with the structural statement the measurement supports: the assertion is blind to a Body leak landing on any of the four endpoints the exclusion covers (synthetic/surface, synthetic/undeclared, widgets, gadgets -- verified against the fixture), and the derivation added exactly one of them. The other three were exempt under the hand-written literal too. The only measured positive control is the 'policy_names' one, and the paragraph now says so and says not to restore an unexecuted counterexample. MAJOR 2. The ordinality retraction pointed the reader at Tests/CommittedDeadKeyReport.Tests.ps1 for coverage that does not exist there for this data: that file does not mention declaredElsewhere at all. What it asserts (:507-508, :514-515) is ordinal order for the two TOP-LEVEL sorts only -- deadKeys on (cmdlet, parameter) and noSurvivingSelector on (cmdlet, method, endpoint). The comment now separates ARGUED from ASSERTED, names the top-level scope, and states plainly that no fixture pins ordinality for the declaredElsewhere (method, surface) sort -- nor can one while every real method and both surfaces are same-case ASCII. A gap closed by argument, not assertion. The other half was accurate and is kept: the ordinal-vs -Culture '' argument is at the head of tools/Build-PfbDeadKeyReport.ps1 (:58-59, above Sort-PfbDeadKeyRecords at :84) and governs the shared comparer. Co-Authored-By: Claude Opus 5 (1M context) --- Tests/Build-PfbDeadKeyReport.Tests.ps1 | 55 +++++++++++++++++++------- 1 file changed, 40 insertions(+), 15 deletions(-) diff --git a/Tests/Build-PfbDeadKeyReport.Tests.ps1 b/Tests/Build-PfbDeadKeyReport.Tests.ps1 index 9045c39d..c6bb33a2 100644 --- a/Tests/Build-PfbDeadKeyReport.Tests.ps1 +++ b/Tests/Build-PfbDeadKeyReport.Tests.ps1 @@ -526,18 +526,32 @@ Describe 'Build-PfbDeadKeyReport classification (synthetic fixture, no spec cach # 'synthetic/undeclared' would have neither guard. The derivation is still the right trade # -- the literal's omission was itself a latent false-red -- but the exclusion did widen. # - # SCOPE OF THAT RESIDUAL, measured rather than assumed, because an earlier draft of this - # comment over-stated it: that exemption is the only blind spot THE DERIVATION ADDED, and - # the assertion is not broadly toothless. It is NOT the assertion's only blind spot in - # general -- a union narrowed to 'flagged' leaks Body provenance onto 'synthetic/surface', - # which is excluded for reasons predating the derivation, and is caught by the ordered - # provenance assertion instead. With the 'policy_names' body property below, a Body lookup widened to - # a document-wide union over the whole declaration index DOES red the anti-leak assertion, - # on 'Remove-PfbSyntheticDeadKey|PolicyName on synthetic/dead' -- 'synthetic/dead' declares - # no request body, so it is not excluded and the leak surfaces there. Before that fixture - # property existed, the same widening produced zero offenders and the assertion passed - # while the index was document-keyed. So read this paragraph as "one endpoint is exempt", - # not as "the control cannot see a scope error". + # SCOPE OF THAT RESIDUAL, stated at exactly the strength the measurement supports, because + # this paragraph has now over-stated it in BOTH directions. The true statement is + # structural, not about any one key: the assertion is blind to a Body-provenance leak that + # lands on any endpoint the exclusion covers -- all four of them, every endpoint declaring + # a requestBody -- and the derivation ADDED exactly one of those four + # ('synthetic/undeclared'). The other three, 'synthetic/surface' included, were exempt + # under the hand-written literal too, so the derivation widened the blind spot by one + # endpoint and did not create it. + # + # An earlier draft of this paragraph tried to make that concrete with a second + # counterexample -- a union narrowed to 'flagged' -- and that example was FALSE, invented + # rather than run. 'Flagged' is a body property of SyntheticSurfaceBase only, reached only + # by 'synthetic/surface' PATCH, so a union narrowed to it returns the record's OWN + # endpoint's sites: declaredElsewhere is unchanged, nothing leaks, and every assertion + # stays green. It is an equivalent mutant -- precisely the hazard the 'policy_names' + # comment below defuses for the *_names case, and the reason it had to be planted there. + # Do not restore a counterexample here that has not been executed. + # + # The one measured positive control is that 'policy_names' body property: with it in the + # fixture, a Body lookup widened to a document-wide union over the whole declaration index + # DOES red the anti-leak assertion, on + # 'Remove-PfbSyntheticDeadKey|PolicyName on synthetic/dead' -- 'synthetic/dead' declares no + # request body, so it is not excluded and the leak surfaces there. Before that property + # existed the same widening produced zero offenders and the assertion passed while the + # index was document-keyed. So read this paragraph as "four endpoints are exempt, one of + # them newly", not as "the control cannot see a scope error". $script:fixtureBodyBearingEndpoints = @( foreach ($pathProperty in $fixtureSpec.paths.PSObject.Properties) { $declaresBody = $false @@ -823,9 +837,20 @@ function Get-PfbSyntheticLowerCaseEndpoint { # # What this fixture does NOT pin is ORDINALITY. 'DELETE' before 'PATCH' and 'Body' before # 'Query' sort identically under ordinal and culture-aware comparison, so a -Culture '' - # mutant would survive here. Ordinality is argued at the head of - # tools/Build-PfbDeadKeyReport.ps1 and asserted in Tests/CommittedDeadKeyReport.Tests.ps1; - # do not read this assertion as covering it. + # mutant would survive here. Ordinality is ARGUED at the head of + # tools/Build-PfbDeadKeyReport.ps1, above Sort-PfbDeadKeyRecords, and that argument governs + # the shared comparer this sort uses. It is ASSERTED against the committed artifact in + # Tests/CommittedDeadKeyReport.Tests.ps1 -- but only for the two TOP-LEVEL sorts, deadKeys + # on (cmdlet, parameter) and noSurvivingSelector on (cmdlet, method, endpoint). That file + # does not mention declaredElsewhere at all. + # + # So no fixture in this repo pins ordinality for the declaredElsewhere (method, surface) + # sort, and none can while the values stay as they are: every real method (DELETE, GET, + # PATCH, POST, PUT) and both surfaces (Body, Query) are same-case ASCII, which sorts + # identically under either comparer. Pinning it would take a fixture whose method or + # surface values differ in case or in non-ASCII collation -- neither of which the generator + # can produce -- so this is a gap that is closed by argument, not by assertion. Do not read + # either this assertion or the committed-artifact one as covering it. $sites = @($entry[0].declaredElsewhere | ForEach-Object { "$($_.method)/$($_.surface)" }) $sites | Should -Be @('DELETE/Query', 'PATCH/Body', 'PATCH/Query') -Because "the fixture declares exactly those three sites, and both sort keys must be exercised: DELETE before PATCH orders on method, Body before Query orders on surface within PATCH. Got: [$($sites -join ', ')]" } From 94d71e11c87f520be7611d41bfc93d8a32cab18f Mon Sep 17 00:00:00 2001 From: Justin Emerson Date: Thu, 27 Aug 2026 15:12:54 -0700 Subject: [PATCH 25/29] docs(reports): point the equivalent-mutant cross-reference the right way Last Minor from the round-5 verification, and it is the same class one more time: a direction word carried over from deleted text and re-asserted without being checked, inside the very paragraph whose subject is not restating unchecked things. The 'policy_names' comment is ABOVE, at :337-344 on the SyntheticEndpointPatch schema, not below. Names the schema so the pointer does not depend on relative position at all. Comment-only: 3 changed lines, zero executable, executable token skeleton unchanged at 2981, parses clean. Scoped run unchanged -- pwsh 7 15/1/0 (inherited staleness only), WinPS 5.1 0/0/16, containers ok. Co-Authored-By: Claude Opus 5 (1M context) --- Tests/Build-PfbDeadKeyReport.Tests.ps1 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Tests/Build-PfbDeadKeyReport.Tests.ps1 b/Tests/Build-PfbDeadKeyReport.Tests.ps1 index c6bb33a2..e07b1292 100644 --- a/Tests/Build-PfbDeadKeyReport.Tests.ps1 +++ b/Tests/Build-PfbDeadKeyReport.Tests.ps1 @@ -541,7 +541,8 @@ Describe 'Build-PfbDeadKeyReport classification (synthetic fixture, no spec cach # by 'synthetic/surface' PATCH, so a union narrowed to it returns the record's OWN # endpoint's sites: declaredElsewhere is unchanged, nothing leaks, and every assertion # stays green. It is an equivalent mutant -- precisely the hazard the 'policy_names' - # comment below defuses for the *_names case, and the reason it had to be planted there. + # comment ABOVE defuses for the *_names case, on the SyntheticEndpointPatch schema, and the + # reason that property had to be planted there. # Do not restore a counterexample here that has not been executed. # # The one measured positive control is that 'policy_names' body property: with it in the From 8472011adf770d230b8c15dc324539207be2d346 Mon Sep 17 00:00:00 2001 From: Justin Emerson Date: Thu, 27 Aug 2026 16:24:58 -0700 Subject: [PATCH 26/29] chore(reports): record expanded wire-name coverage Task 6 of issue #141. Regenerates the seven artifacts the resolver work moved, re-baselines three tripwires and adds two skip-reason keys, retires Task 4's tuple declaration, and pins the measured 5.1 skip count. REGENERATION. Through scripts/Assert-PfbDerivedArtifacts.ps1, never a bare generator. Seven of eleven checked artifacts were stale; PfbValueEnumMap.json and PfbValueEnumReconciliation.md held, as expected. All eleven now report up to date. PfbDeadKeyReport.json goes 31,597 -> 40,242 normalised characters, which clears the inherited staleness failure Tasks 4 and 5 were forbidden from fixing: Build-PfbDeadKeyReport.Tests.ps1 is now 16/0/0 on pwsh 7. CONTROL FOR THE STALENESS. main (76f0c95) regenerates 11/11 up to date, so all seven movements are attributable to this branch and none is pre-existing debt. SEMANTIC MOVEMENT, verified by regenerating rather than by checking that the arithmetic closes -- the plan previously carried a paired off-by-one that reconciled just as correctly: keysEvaluated 1779, parametersInventoried 2168, dead keys 85, noSurvivingSelector 7, skip reasons 32 / 28 / 6 / 309 / 14 / 0. Classification census UNDECLARED 71, WRONG-VERB 13, WRONG-SURFACE 1. Diff against main's committed report is +2 and -0. No previously-reported record was lost. THREE GATE MOVEMENTS, each justified beside its pin: - baselineDeadKeyCount 83 -> 85. Raising a monotone gate is against the file's own stated discipline, so the justification is written in: #141 changes no cmdlet, it teaches the resolver three assignment shapes it had been skipping, and two of the newly-evaluated parameters were dead all along -- Get-PfbAlert|Flagged|flagged|GET|alerts and New-PfbCertificateSigningRequest|Name|names|POST|certificates/certificate-signing-requests. Pre-existing module defects made visible, not introduced. - baselineNoSurvivingSelectorCount 6 -> 7, with the CSR identity added to the allowlist. CSR has one selector-shaped query key and the operation declares zero. - 'wire name unresolved' 127 -> 32. Lowering, and the failure mode here is leaving it high: 34 of the 66 null-WireName rows are now separately accounted for, so 127 would carry 95 rows of slack. TWO NEW VOCABULARY KEYS, 'outside standard request' = 28 and 'not wire parameter' = 6. Required rather than optional: the scan treats an unknown reason as an offender and separately asserts it visited every reason. Both are ceilinged rather than unceilinged because, unlike 'body property', each names a population the resolver positively classified rather than failed to read. PIPELINE SELECTOR -- the brief said this artifact must not move and it does. Diagnosed rather than copied: probe pairs are 1247 on both sides while candidates move 629 -> 647. The generator's code and probe population are unchanged; its candidate set depends on how many parameters resolve to a wire name, which is what this issue improved. Two consequences, both real: - Build-PfbPipelineSelectorMap headline pin 264 -> 266 findings, 101 -> 102 pairs, with probePairs left pinned at 1247 as the discriminator. - Rail A gains one waiver: Get-PfbUserGroupQuotaPolicy|Name, Family scope, 2 producers. This is a LIVE WIRE-CORRECTNESS DEFECT that #141 reveals -- the parameter stringifies a nested join item to names=@{context=; member=; policy=} on GET /user-group-quota-policies/ file-systems and /members. Same root cause as the existing Get-PfbTlsPolicy and Get-PfbWormPolicy entries. Waived against #141 following the register's own convention of naming the revealing issue; a fix issue is owed. STEP 6b. tools/inventory-tuple-baselines/issue-141-task4.json git mv'd to landed/ (R100, byte-identical). The brief's rationale for this was wrong in both directions and is corrected in the task report: the file is NOT currently stale (it validates CLEAN, exit 0, 34/34 declared), and a run with no -DeclarationPath is NOT clean (97 undeclared changes against origin/main, which is the gate working as designed on a branch that moves 97 tuples). The move is still right, on the authority of the file's own retirement note and the script's documented trigger: staleness fires once these commits ARE the baseline, i.e. at merge, and this is the PR that merges them. Retirement is safe because the script has zero auto-discovery -- -DeclarationPath is explicit-only -- so landed/ can never be read implicitly. COVERAGE BASELINE. Build-PfbDeadKeyReport.Tests.ps1 6 -> 16, measured on Windows PowerShell 5.1 for that file alone and read out of the runner's child winps51.json rather than its Write-Host summary: 0/0/16, container ok. No headroom added; these entries are exact. The tree-wide total is only measurable by a full-suite run, which is CI's. TESTS. Plan Step 7 list, both editions: pwsh 7 581/0/0, WinPS 5.1 418/0/163, containers ok on both. CONSTRAINTS. No change under Public/, Private/, PureStorageFlashBladePowerShell.psd1 or .psm1 -- checked by explicit filename, because a *.psd1 pathspec also matches Tests/coverage-baseline.psd1 and reads as a false breach. No version bump, no CHANGELOG edit. Co-Authored-By: Claude Opus 5 (1M context) --- Reports/PfbApiDriftReport.json | 9513 +++++++++++------ Reports/PfbApiDriftReport.md | 211 +- Reports/PfbDeadKeyReport.json | 463 +- Reports/PfbFieldCmdletMap.json | 1026 +- Reports/PfbFieldCmdletMapping.md | 142 +- Reports/PfbPipelineSelectorMap.json | 268 +- Reports/PfbPipelineSelectorMap.md | 20 +- Tests/Build-PfbPipelineSelectorMap.Tests.ps1 | 16 +- Tests/CommittedDeadKeyReport.Tests.ps1 | 78 +- Tests/Fixtures/PfbSelectorWaivers.psd1 | 28 +- Tests/coverage-baseline.psd1 | 11 +- .../{ => landed}/issue-141-task4.json | 0 12 files changed, 7611 insertions(+), 4165 deletions(-) rename tools/inventory-tuple-baselines/{ => landed}/issue-141-task4.json (100%) diff --git a/Reports/PfbApiDriftReport.json b/Reports/PfbApiDriftReport.json index eb67e4b5..3e5b0442 100644 --- a/Reports/PfbApiDriftReport.json +++ b/Reports/PfbApiDriftReport.json @@ -644,17 +644,10 @@ "missingBodyProperties": [], "readOnlyFields": [], "confidence": { - "level": "partial", - "unresolvedParameters": [ - { - "parameter": "Force", - "surface": "TypedUnresolved", - "file": "Public/FileSystem/Remove-PfbFileSystemSession.ps1", - "line": 59 - } - ], + "level": "high", + "unresolvedParameters": [], "escapeHatchOnly": [], - "caveat": "one or more parameters could not be traced to a wire name and have no -Attributes escape hatch; lists reflect typed-parameter coverage only, not full wire reachability" + "caveat": "" }, "annotations": [] }, @@ -951,37 +944,15 @@ ], "missingQueryParameters": [ "file_system_ids", - "file_system_names", - "gids", - "group_names", "names" ], "missingBodyProperties": [], "readOnlyFields": [], "confidence": { - "level": "partial", - "unresolvedParameters": [ - { - "parameter": "FileSystemName", - "surface": "TypedUnresolved", - "file": "Public/Quota/Remove-PfbQuotaGroup.ps1", - "line": 35 - }, - { - "parameter": "GroupId", - "surface": "TypedUnresolved", - "file": "Public/Quota/Remove-PfbQuotaGroup.ps1", - "line": 37 - }, - { - "parameter": "GroupName", - "surface": "TypedUnresolved", - "file": "Public/Quota/Remove-PfbQuotaGroup.ps1", - "line": 36 - } - ], + "level": "high", + "unresolvedParameters": [], "escapeHatchOnly": [], - "caveat": "one or more parameters could not be traced to a wire name and have no -Attributes escape hatch; lists reflect typed-parameter coverage only, not full wire reachability" + "caveat": "" }, "annotations": [] }, @@ -992,31 +963,16 @@ ], "missingQueryParameters": [ "file_system_ids", - "file_system_names", "names", - "uids", - "user_names" + "uids" ], "missingBodyProperties": [], "readOnlyFields": [], "confidence": { - "level": "partial", - "unresolvedParameters": [ - { - "parameter": "FileSystemName", - "surface": "TypedUnresolved", - "file": "Public/Quota/Remove-PfbQuotaUser.ps1", - "line": 25 - }, - { - "parameter": "UserName", - "surface": "TypedUnresolved", - "file": "Public/Quota/Remove-PfbQuotaUser.ps1", - "line": 26 - } - ], + "level": "high", + "unresolvedParameters": [], "escapeHatchOnly": [], - "caveat": "one or more parameters could not be traced to a wire name and have no -Attributes escape hatch; lists reflect typed-parameter coverage only, not full wire reachability" + "caveat": "" }, "annotations": [] }, @@ -1031,17 +987,10 @@ "missingBodyProperties": [], "readOnlyFields": [], "confidence": { - "level": "partial", - "unresolvedParameters": [ - { - "parameter": "Eradicate", - "surface": "TypedUnresolved", - "file": "Public/Server/Remove-PfbServer.ps1", - "line": 35 - } - ], + "level": "high", + "unresolvedParameters": [], "escapeHatchOnly": [], - "caveat": "one or more parameters could not be traced to a wire name and have no -Attributes escape hatch; lists reflect typed-parameter coverage only, not full wire reachability" + "caveat": "" }, "annotations": [] }, @@ -4335,30 +4284,15 @@ "Get-PfbUserGroupQuotaPolicy" ], "missingQueryParameters": [ - "allow_errors", - "ids", - "names" + "allow_errors" ], "missingBodyProperties": [], "readOnlyFields": [], "confidence": { - "level": "partial", - "unresolvedParameters": [ - { - "parameter": "Id", - "surface": "TypedUnresolved", - "file": "Public/Policy/Get-PfbUserGroupQuotaPolicy.ps1", - "line": 36 - }, - { - "parameter": "Name", - "surface": "TypedUnresolved", - "file": "Public/Policy/Get-PfbUserGroupQuotaPolicy.ps1", - "line": 33 - } - ], + "level": "high", + "unresolvedParameters": [], "escapeHatchOnly": [], - "caveat": "one or more parameters could not be traced to a wire name and have no -Attributes escape hatch; lists reflect typed-parameter coverage only, not full wire reachability" + "caveat": "" }, "annotations": [] }, @@ -4615,27 +4549,16 @@ "Update-PfbAlertWatcher" ], "missingQueryParameters": [], - "missingBodyProperties": [ - "enabled" - ], + "missingBodyProperties": [], "readOnlyFields": [ "id", "name" ], "confidence": { - "level": "partial", - "unresolvedParameters": [ - { - "parameter": "Enabled", - "surface": "AttributesOnly", - "file": "Public/Alert/Update-PfbAlertWatcher.ps1", - "line": 32 - } - ], - "escapeHatchOnly": [ - "Enabled" - ], - "caveat": "body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability" + "level": "high", + "unresolvedParameters": [], + "escapeHatchOnly": [], + "caveat": "" }, "annotations": [] }, @@ -4645,9 +4568,7 @@ "Update-PfbAlert" ], "missingQueryParameters": [], - "missingBodyProperties": [ - "flagged" - ], + "missingBodyProperties": [], "readOnlyFields": [ "action", "code", @@ -4668,19 +4589,10 @@ "variables" ], "confidence": { - "level": "partial", - "unresolvedParameters": [ - { - "parameter": "Flagged", - "surface": "AttributesOnly", - "file": "Public/Alert/Update-PfbAlert.ps1", - "line": 26 - } - ], - "escapeHatchOnly": [ - "Flagged" - ], - "caveat": "body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability" + "level": "high", + "unresolvedParameters": [], + "escapeHatchOnly": [], + "caveat": "" }, "annotations": [] }, @@ -4976,14 +4888,128 @@ ], "missingQueryParameters": [], "missingBodyProperties": [ - "add_log_targets", - "control_type", - "enabled", - "location", - "log_targets", - "name", - "remove_log_targets", - "rules" + { + "name": "add_log_targets", + "type": "array", + "format": null, + "specRequired": false, + "synopsis": "The log targets which will be added to the existing `log_targets` list for the audit policy.", + "suggestedPowerShellType": "[object[]]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Policy/Update-PfbAuditFileSystemPolicy.ps1", + "paramBlockLine": 46, + "payloadVariable": "body", + "assignmentStyle": "index", + "hasAttributes": true + } + }, + { + "name": "control_type", + "type": "string", + "format": null, + "specRequired": false, + "synopsis": "Specifies the evaluation mode for auditing in this policy.", + "suggestedPowerShellType": "[string]", + "enumValues": [ + "policy", + "sacl" + ], + "enumStatus": "matched", + "target": { + "file": "Public/Policy/Update-PfbAuditFileSystemPolicy.ps1", + "paramBlockLine": 46, + "payloadVariable": "body", + "assignmentStyle": "index", + "hasAttributes": true + } + }, + { + "name": "location", + "type": null, + "format": null, + "specRequired": false, + "synopsis": "Reference to the array where the policy is defined.", + "suggestedPowerShellType": "[object]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Policy/Update-PfbAuditFileSystemPolicy.ps1", + "paramBlockLine": 46, + "payloadVariable": "body", + "assignmentStyle": "index", + "hasAttributes": true + } + }, + { + "name": "log_targets", + "type": "array", + "format": null, + "specRequired": false, + "synopsis": "List of targets which will be utilized for audit log storage.", + "suggestedPowerShellType": "[object[]]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Policy/Update-PfbAuditFileSystemPolicy.ps1", + "paramBlockLine": 46, + "payloadVariable": "body", + "assignmentStyle": "index", + "hasAttributes": true + } + }, + { + "name": "name", + "type": "string", + "format": null, + "specRequired": false, + "synopsis": "A user-specified name.", + "suggestedPowerShellType": "[string]", + "enumValues": [], + "enumStatus": "not-found-in-resource", + "target": { + "file": "Public/Policy/Update-PfbAuditFileSystemPolicy.ps1", + "paramBlockLine": 46, + "payloadVariable": "body", + "assignmentStyle": "index", + "hasAttributes": true + } + }, + { + "name": "remove_log_targets", + "type": "array", + "format": null, + "specRequired": false, + "synopsis": "The log targets which will be removed from the existing `log_targets` list for the audit policy.", + "suggestedPowerShellType": "[object[]]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Policy/Update-PfbAuditFileSystemPolicy.ps1", + "paramBlockLine": 46, + "payloadVariable": "body", + "assignmentStyle": "index", + "hasAttributes": true + } + }, + { + "name": "rules", + "type": "array", + "format": null, + "specRequired": false, + "synopsis": "All of the rules that are part of this policy.", + "suggestedPowerShellType": "[object[]]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Policy/Update-PfbAuditFileSystemPolicy.ps1", + "paramBlockLine": 46, + "payloadVariable": "body", + "assignmentStyle": "index", + "hasAttributes": true + } + } ], "readOnlyFields": [ "id", @@ -4992,19 +5018,10 @@ "realms" ], "confidence": { - "level": "partial", - "unresolvedParameters": [ - { - "parameter": "Enabled", - "surface": "AttributesOnly", - "file": "Public/Policy/Update-PfbAuditFileSystemPolicy.ps1", - "line": 40 - } - ], - "escapeHatchOnly": [ - "Enabled" - ], - "caveat": "body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability" + "level": "high", + "unresolvedParameters": [], + "escapeHatchOnly": [], + "caveat": "" }, "annotations": [] }, @@ -5015,33 +5032,103 @@ ], "missingQueryParameters": [], "missingBodyProperties": [ - "add_log_targets", - "enabled", - "location", - "log_targets", - "name", - "remove_log_targets" - ], - "readOnlyFields": [ - "id", - "is_local", - "policy_type", - "realms" - ], - "confidence": { - "level": "partial", - "unresolvedParameters": [ - { - "parameter": "Enabled", - "surface": "AttributesOnly", + { + "name": "add_log_targets", + "type": "array", + "format": null, + "specRequired": false, + "synopsis": "The log targets which will be added to the existing `log_targets` list for the audit policy.", + "suggestedPowerShellType": "[object[]]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { "file": "Public/Policy/Update-PfbAuditObjectStorePolicy.ps1", - "line": 40 + "paramBlockLine": 46, + "payloadVariable": "body", + "assignmentStyle": "index", + "hasAttributes": true } - ], - "escapeHatchOnly": [ - "Enabled" - ], - "caveat": "body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability" + }, + { + "name": "location", + "type": null, + "format": null, + "specRequired": false, + "synopsis": "Reference to the array where the policy is defined.", + "suggestedPowerShellType": "[object]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Policy/Update-PfbAuditObjectStorePolicy.ps1", + "paramBlockLine": 46, + "payloadVariable": "body", + "assignmentStyle": "index", + "hasAttributes": true + } + }, + { + "name": "log_targets", + "type": "array", + "format": null, + "specRequired": false, + "synopsis": "List of targets which will be utilized for audit log storage.", + "suggestedPowerShellType": "[object[]]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Policy/Update-PfbAuditObjectStorePolicy.ps1", + "paramBlockLine": 46, + "payloadVariable": "body", + "assignmentStyle": "index", + "hasAttributes": true + } + }, + { + "name": "name", + "type": "string", + "format": null, + "specRequired": false, + "synopsis": "A user-specified name.", + "suggestedPowerShellType": "[string]", + "enumValues": [], + "enumStatus": "not-found-in-resource", + "target": { + "file": "Public/Policy/Update-PfbAuditObjectStorePolicy.ps1", + "paramBlockLine": 46, + "payloadVariable": "body", + "assignmentStyle": "index", + "hasAttributes": true + } + }, + { + "name": "remove_log_targets", + "type": "array", + "format": null, + "specRequired": false, + "synopsis": "The log targets which will be removed from the existing `log_targets` list for the audit policy.", + "suggestedPowerShellType": "[object[]]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Policy/Update-PfbAuditObjectStorePolicy.ps1", + "paramBlockLine": 46, + "payloadVariable": "body", + "assignmentStyle": "index", + "hasAttributes": true + } + } + ], + "readOnlyFields": [ + "id", + "is_local", + "policy_type", + "realms" + ], + "confidence": { + "level": "high", + "unresolvedParameters": [], + "escapeHatchOnly": [], + "caveat": "" }, "annotations": [] }, @@ -5056,36 +5143,162 @@ "ignore_usage" ], "missingBodyProperties": [ - "destroyed", - "eradication_config", - "hard_limit_enabled", - "object_lock_config", - "public_access_config", - "qos_policy", - "retention_lock", - "storage_class" + { + "name": "eradication_config", + "type": null, + "format": null, + "specRequired": false, + "synopsis": null, + "suggestedPowerShellType": "[object]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Bucket/Remove-PfbBucket.ps1", + "paramBlockLine": 31, + "payloadVariable": "body", + "assignmentStyle": "literal", + "hasAttributes": false + } + }, + { + "name": "hard_limit_enabled", + "type": "boolean", + "format": null, + "specRequired": false, + "synopsis": "If set to `true`, the bucket's size, as defined by `quota_limit`, is used as a hard limit quota.", + "suggestedPowerShellType": "[bool]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Bucket/Remove-PfbBucket.ps1", + "paramBlockLine": 31, + "payloadVariable": "body", + "assignmentStyle": "literal", + "hasAttributes": false + } + }, + { + "name": "object_lock_config", + "type": null, + "format": null, + "specRequired": false, + "synopsis": null, + "suggestedPowerShellType": "[object]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Bucket/Remove-PfbBucket.ps1", + "paramBlockLine": 31, + "payloadVariable": "body", + "assignmentStyle": "literal", + "hasAttributes": false + } + }, + { + "name": "public_access_config", + "type": null, + "format": null, + "specRequired": false, + "synopsis": null, + "suggestedPowerShellType": "[object]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Bucket/Remove-PfbBucket.ps1", + "paramBlockLine": 31, + "payloadVariable": "body", + "assignmentStyle": "literal", + "hasAttributes": false + } + }, + { + "name": "qos_policy", + "type": null, + "format": null, + "specRequired": false, + "synopsis": "The QoS policy for the bucket defines the performance controls that can be applied to the aggregate performance of all the clients accessing the bucket.", + "suggestedPowerShellType": "[object]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Bucket/Remove-PfbBucket.ps1", + "paramBlockLine": 31, + "payloadVariable": "body", + "assignmentStyle": "literal", + "hasAttributes": false + } + }, + { + "name": "retention_lock", + "type": "string", + "format": null, + "specRequired": false, + "synopsis": "If set to `ratcheted`, then `object_lock_config.default_retention_mode` cannot be changed if set to `compliance`.", + "suggestedPowerShellType": "[string]", + "enumValues": [ + "unlocked", + "ratcheted" + ], + "enumStatus": "matched", + "target": { + "file": "Public/Bucket/Remove-PfbBucket.ps1", + "paramBlockLine": 31, + "payloadVariable": "body", + "assignmentStyle": "literal", + "hasAttributes": false + } + }, + { + "name": "storage_class", + "type": null, + "format": null, + "specRequired": false, + "synopsis": null, + "suggestedPowerShellType": "[object]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Bucket/Remove-PfbBucket.ps1", + "paramBlockLine": 31, + "payloadVariable": "body", + "assignmentStyle": "literal", + "hasAttributes": false + } + } + ], + "readOnlyFields": [], + "confidence": { + "level": "high", + "unresolvedParameters": [], + "escapeHatchOnly": [], + "caveat": "" + }, + "annotations": [] + }, + { + "endpoint": "PATCH /buckets/audit-filters", + "cmdlets": [ + "Update-PfbBucketAuditFilter" + ], + "missingQueryParameters": [ + "bucket_names" ], + "missingBodyProperties": [], "readOnlyFields": [], "confidence": { "level": "partial", "unresolvedParameters": [ { - "parameter": "Destroyed", + "parameter": "BucketName", "surface": "AttributesOnly", - "file": "Public/Bucket/Update-PfbBucket.ps1", - "line": 37 - }, - { - "parameter": "Eradicate", - "surface": "TypedUnresolved", - "file": "Public/Bucket/Remove-PfbBucket.ps1", - "line": 27 + "file": "Public/Bucket/Update-PfbBucketAuditFilter.ps1", + "line": 78 } ], "escapeHatchOnly": [ - "Destroyed" + "BucketName" ], - "caveat": "body reachable via -Attributes for some parameters and untraceable for others; lists reflect typed-parameter coverage only, not full wire reachability" + "caveat": "body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability" }, "annotations": [] }, @@ -5119,8 +5332,23 @@ ], "missingQueryParameters": [], "missingBodyProperties": [ - "enabled", - "location" + { + "name": "location", + "type": null, + "format": null, + "specRequired": false, + "synopsis": "Reference to the array where the policy is defined.", + "suggestedPowerShellType": "[object]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/DataEviction/Update-PfbDataEvictionPolicy.ps1", + "paramBlockLine": 38, + "payloadVariable": "body", + "assignmentStyle": "index", + "hasAttributes": false + } + } ], "readOnlyFields": [ "context", @@ -5130,17 +5358,10 @@ "realms" ], "confidence": { - "level": "partial", - "unresolvedParameters": [ - { - "parameter": "Enabled", - "surface": "TypedUnresolved", - "file": "Public/DataEviction/Update-PfbDataEvictionPolicy.ps1", - "line": 37 - } - ], + "level": "high", + "unresolvedParameters": [], "escapeHatchOnly": [], - "caveat": "one or more parameters could not be traced to a wire name and have no -Attributes escape hatch; lists reflect typed-parameter coverage only, not full wire reachability" + "caveat": "" }, "annotations": [] }, @@ -5150,20 +5371,179 @@ "Update-PfbDirectoryService" ], "missingQueryParameters": [ - "ids", - "names" + "ids" ], "missingBodyProperties": [ - "base_dn", - "bind_password", - "bind_user", - "ca_certificate", - "ca_certificate_group", - "enabled", - "management", - "nfs", - "smb", - "uris" + { + "name": "base_dn", + "type": "string", + "format": null, + "specRequired": false, + "synopsis": "Base of the Distinguished Name (DN) of the directory service groups.", + "suggestedPowerShellType": "[string]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/DirectoryService/Update-PfbDirectoryService.ps1", + "paramBlockLine": 34, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true + } + }, + { + "name": "bind_password", + "type": "string", + "format": null, + "specRequired": false, + "synopsis": "Obfuscated password used to query the directory.", + "suggestedPowerShellType": "[string]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/DirectoryService/Update-PfbDirectoryService.ps1", + "paramBlockLine": 34, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true + } + }, + { + "name": "bind_user", + "type": "string", + "format": null, + "specRequired": false, + "synopsis": "Username used to query the directory.", + "suggestedPowerShellType": "[string]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/DirectoryService/Update-PfbDirectoryService.ps1", + "paramBlockLine": 34, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true + } + }, + { + "name": "ca_certificate", + "type": null, + "format": null, + "specRequired": false, + "synopsis": "Reference (ID, name, and resource type) of the Certificate Authority (CA) that signed the certificates of the configured servers, which is used to validate the authenticity of the servers.", + "suggestedPowerShellType": "[object]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/DirectoryService/Update-PfbDirectoryService.ps1", + "paramBlockLine": 34, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true + } + }, + { + "name": "ca_certificate_group", + "type": null, + "format": null, + "specRequired": false, + "synopsis": "Reference (ID, name, and resource type) of a certificate group containing CA certificates that can be used to validate the authenticity of the configured servers.", + "suggestedPowerShellType": "[object]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/DirectoryService/Update-PfbDirectoryService.ps1", + "paramBlockLine": 34, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true + } + }, + { + "name": "enabled", + "type": "boolean", + "format": null, + "specRequired": false, + "synopsis": "Is the directory service enabled or not?", + "suggestedPowerShellType": "[bool]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/DirectoryService/Update-PfbDirectoryService.ps1", + "paramBlockLine": 34, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true + } + }, + { + "name": "management", + "type": null, + "format": null, + "specRequired": false, + "synopsis": null, + "suggestedPowerShellType": "[object]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/DirectoryService/Update-PfbDirectoryService.ps1", + "paramBlockLine": 34, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true + } + }, + { + "name": "nfs", + "type": null, + "format": null, + "specRequired": false, + "synopsis": null, + "suggestedPowerShellType": "[object]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/DirectoryService/Update-PfbDirectoryService.ps1", + "paramBlockLine": 34, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true + } + }, + { + "name": "smb", + "type": null, + "format": null, + "specRequired": false, + "synopsis": null, + "suggestedPowerShellType": "[object]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/DirectoryService/Update-PfbDirectoryService.ps1", + "paramBlockLine": 34, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true + } + }, + { + "name": "uris", + "type": "array", + "format": null, + "specRequired": false, + "synopsis": "List of URIs for the configured directory servers.", + "suggestedPowerShellType": "[string[]]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/DirectoryService/Update-PfbDirectoryService.ps1", + "paramBlockLine": 34, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true + } + } ], "readOnlyFields": [ "id", @@ -5171,19 +5551,10 @@ "services" ], "confidence": { - "level": "partial", - "unresolvedParameters": [ - { - "parameter": "Name", - "surface": "AttributesOnly", - "file": "Public/DirectoryService/Update-PfbDirectoryService.ps1", - "line": 32 - } - ], - "escapeHatchOnly": [ - "Name" - ], - "caveat": "body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability" + "level": "high", + "unresolvedParameters": [], + "escapeHatchOnly": [], + "caveat": "" }, "annotations": [] }, @@ -5280,11 +5651,91 @@ "latest_replica" ], "missingBodyProperties": [ - "destroyed", - "name", - "owner", - "policy", - "source" + { + "name": "destroyed", + "type": "boolean", + "format": null, + "specRequired": false, + "synopsis": "Is the file system snapshot destroyed?", + "suggestedPowerShellType": "[bool]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/FileSystemSnapshot/Remove-PfbFileSystemSnapshot.ps1", + "paramBlockLine": 28, + "payloadVariable": "body", + "assignmentStyle": "literal", + "hasAttributes": false + } + }, + { + "name": "name", + "type": "string", + "format": null, + "specRequired": false, + "synopsis": "A user-specified name.", + "suggestedPowerShellType": "[string]", + "enumValues": [], + "enumStatus": "not-found-in-resource", + "target": { + "file": "Public/FileSystemSnapshot/Remove-PfbFileSystemSnapshot.ps1", + "paramBlockLine": 28, + "payloadVariable": "body", + "assignmentStyle": "literal", + "hasAttributes": false + } + }, + { + "name": "owner", + "type": null, + "format": null, + "specRequired": false, + "synopsis": "A reference to the file system that owns this snapshot.", + "suggestedPowerShellType": "[object]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/FileSystemSnapshot/Remove-PfbFileSystemSnapshot.ps1", + "paramBlockLine": 28, + "payloadVariable": "body", + "assignmentStyle": "literal", + "hasAttributes": false + } + }, + { + "name": "policy", + "type": null, + "format": null, + "specRequired": false, + "synopsis": "A reference to the associated policy that drives the behavior of the snapshot.", + "suggestedPowerShellType": "[object]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/FileSystemSnapshot/Remove-PfbFileSystemSnapshot.ps1", + "paramBlockLine": 28, + "payloadVariable": "body", + "assignmentStyle": "literal", + "hasAttributes": false + } + }, + { + "name": "source", + "type": null, + "format": null, + "specRequired": false, + "synopsis": "A reference to the file system that was the source of the data in this snapshot.", + "suggestedPowerShellType": "[object]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/FileSystemSnapshot/Remove-PfbFileSystemSnapshot.ps1", + "paramBlockLine": 28, + "payloadVariable": "body", + "assignmentStyle": "literal", + "hasAttributes": false + } + } ], "readOnlyFields": [ "context", @@ -5296,17 +5747,10 @@ "time_remaining" ], "confidence": { - "level": "partial", - "unresolvedParameters": [ - { - "parameter": "Eradicate", - "surface": "TypedUnresolved", - "file": "Public/FileSystemSnapshot/Remove-PfbFileSystemSnapshot.ps1", - "line": 24 - } - ], - "escapeHatchOnly": [], - "caveat": "one or more parameters could not be traced to a wire name and have no -Attributes escape hatch; lists reflect typed-parameter coverage only, not full wire reachability" + "level": "high", + "unresolvedParameters": [], + "escapeHatchOnly": [], + "caveat": "" }, "annotations": [] }, @@ -5325,11 +5769,8 @@ "abort_quiesce", "default_group_quota", "default_user_quota", - "destroyed", "fast_remove_directory_enabled", "group_ownership", - "hard_limit_enabled", - "http", "multi_protocol", "name", "nfs", @@ -5352,30 +5793,6 @@ "confidence": { "level": "partial", "unresolvedParameters": [ - { - "parameter": "Destroyed", - "surface": "AttributesOnly", - "file": "Public/FileSystem/Update-PfbFileSystem.ps1", - "line": 93 - }, - { - "parameter": "Eradicate", - "surface": "TypedUnresolved", - "file": "Public/FileSystem/Remove-PfbFileSystem.ps1", - "line": 38 - }, - { - "parameter": "HardLimitEnabled", - "surface": "AttributesOnly", - "file": "Public/FileSystem/Update-PfbFileSystem.ps1", - "line": 69 - }, - { - "parameter": "HttpEnabled", - "surface": "AttributesOnly", - "file": "Public/FileSystem/Update-PfbFileSystem.ps1", - "line": 90 - }, { "parameter": "NfsEnabled", "surface": "AttributesOnly", @@ -5414,9 +5831,6 @@ } ], "escapeHatchOnly": [ - "Destroyed", - "HardLimitEnabled", - "HttpEnabled", "NfsEnabled", "NfsExportPolicy", "NfsRules", @@ -5424,7 +5838,7 @@ "SmbEnabled", "SmbSharePolicy" ], - "caveat": "body reachable via -Attributes for some parameters and untraceable for others; lists reflect typed-parameter coverage only, not full wire reachability" + "caveat": "body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability" }, "annotations": [] }, @@ -5619,10 +6033,57 @@ "versions" ], "missingBodyProperties": [ - "enabled", - "location", - "name", - "rules" + { + "name": "location", + "type": null, + "format": null, + "specRequired": false, + "synopsis": "Reference to the array where the policy is defined.", + "suggestedPowerShellType": "[object]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Policy/Update-PfbNetworkAccessPolicy.ps1", + "paramBlockLine": 48, + "payloadVariable": "body", + "assignmentStyle": "index", + "hasAttributes": true + } + }, + { + "name": "name", + "type": "string", + "format": null, + "specRequired": false, + "synopsis": "A user-specified name.", + "suggestedPowerShellType": "[string]", + "enumValues": [], + "enumStatus": "not-found-in-resource", + "target": { + "file": "Public/Policy/Update-PfbNetworkAccessPolicy.ps1", + "paramBlockLine": 48, + "payloadVariable": "body", + "assignmentStyle": "index", + "hasAttributes": true + } + }, + { + "name": "rules", + "type": "array", + "format": null, + "specRequired": false, + "synopsis": "All of the rules that are part of this policy.", + "suggestedPowerShellType": "[object[]]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Policy/Update-PfbNetworkAccessPolicy.ps1", + "paramBlockLine": 48, + "payloadVariable": "body", + "assignmentStyle": "index", + "hasAttributes": true + } + } ], "readOnlyFields": [ "id", @@ -5632,19 +6093,10 @@ "version" ], "confidence": { - "level": "partial", - "unresolvedParameters": [ - { - "parameter": "Enabled", - "surface": "AttributesOnly", - "file": "Public/Policy/Update-PfbNetworkAccessPolicy.ps1", - "line": 42 - } - ], - "escapeHatchOnly": [ - "Enabled" - ], - "caveat": "body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability" + "level": "high", + "unresolvedParameters": [], + "escapeHatchOnly": [], + "caveat": "" }, "annotations": [] }, @@ -5798,10 +6250,57 @@ "versions" ], "missingBodyProperties": [ - "enabled", - "location", - "name", - "rules" + { + "name": "location", + "type": null, + "format": null, + "specRequired": false, + "synopsis": "Reference to the array where the policy is defined.", + "suggestedPowerShellType": "[object]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Policy/Update-PfbNfsExportPolicy.ps1", + "paramBlockLine": 48, + "payloadVariable": "body", + "assignmentStyle": "index", + "hasAttributes": true + } + }, + { + "name": "name", + "type": "string", + "format": null, + "specRequired": false, + "synopsis": "A user-specified name.", + "suggestedPowerShellType": "[string]", + "enumValues": [], + "enumStatus": "not-found-in-resource", + "target": { + "file": "Public/Policy/Update-PfbNfsExportPolicy.ps1", + "paramBlockLine": 48, + "payloadVariable": "body", + "assignmentStyle": "index", + "hasAttributes": true + } + }, + { + "name": "rules", + "type": "array", + "format": null, + "specRequired": false, + "synopsis": "All of the rules that are part of this policy.", + "suggestedPowerShellType": "[object[]]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Policy/Update-PfbNfsExportPolicy.ps1", + "paramBlockLine": 48, + "payloadVariable": "body", + "assignmentStyle": "index", + "hasAttributes": true + } + } ], "readOnlyFields": [ "id", @@ -5811,19 +6310,10 @@ "version" ], "confidence": { - "level": "partial", - "unresolvedParameters": [ - { - "parameter": "Enabled", - "surface": "AttributesOnly", - "file": "Public/Policy/Update-PfbNfsExportPolicy.ps1", - "line": 42 - } - ], - "escapeHatchOnly": [ - "Enabled" - ], - "caveat": "body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability" + "level": "high", + "unresolvedParameters": [], + "escapeHatchOnly": [], + "caveat": "" }, "annotations": [] }, @@ -6571,10 +7061,57 @@ "destroy_snapshots" ], "missingBodyProperties": [ - "add_rules", - "enabled", - "location", - "remove_rules" + { + "name": "add_rules", + "type": "array", + "format": null, + "specRequired": false, + "synopsis": null, + "suggestedPowerShellType": "[object[]]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Policy/Update-PfbPolicy.ps1", + "paramBlockLine": 37, + "payloadVariable": "body", + "assignmentStyle": "index", + "hasAttributes": true + } + }, + { + "name": "location", + "type": null, + "format": null, + "specRequired": false, + "synopsis": "Reference to the array where the policy is defined.", + "suggestedPowerShellType": "[object]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Policy/Update-PfbPolicy.ps1", + "paramBlockLine": 37, + "payloadVariable": "body", + "assignmentStyle": "index", + "hasAttributes": true + } + }, + { + "name": "remove_rules", + "type": "array", + "format": null, + "specRequired": false, + "synopsis": null, + "suggestedPowerShellType": "[object[]]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Policy/Update-PfbPolicy.ps1", + "paramBlockLine": 37, + "payloadVariable": "body", + "assignmentStyle": "index", + "hasAttributes": true + } + } ], "readOnlyFields": [ "id", @@ -6585,19 +7122,10 @@ "retention_lock" ], "confidence": { - "level": "partial", - "unresolvedParameters": [ - { - "parameter": "Enabled", - "surface": "AttributesOnly", - "file": "Public/Policy/Update-PfbPolicy.ps1", - "line": 28 - } - ], - "escapeHatchOnly": [ - "Enabled" - ], - "caveat": "body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability" + "level": "high", + "unresolvedParameters": [], + "escapeHatchOnly": [], + "caveat": "" }, "annotations": [] }, @@ -6630,9 +7158,6 @@ ], "missingQueryParameters": [ "file_system_ids", - "file_system_names", - "gids", - "group_names", "names" ], "missingBodyProperties": [], @@ -6640,33 +7165,10 @@ "name" ], "confidence": { - "level": "partial", - "unresolvedParameters": [ - { - "parameter": "FileSystemName", - "surface": "AttributesOnly", - "file": "Public/Quota/Update-PfbQuotaGroup.ps1", - "line": 40 - }, - { - "parameter": "GroupId", - "surface": "AttributesOnly", - "file": "Public/Quota/Update-PfbQuotaGroup.ps1", - "line": 42 - }, - { - "parameter": "GroupName", - "surface": "AttributesOnly", - "file": "Public/Quota/Update-PfbQuotaGroup.ps1", - "line": 41 - } - ], - "escapeHatchOnly": [ - "FileSystemName", - "GroupId", - "GroupName" - ], - "caveat": "body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability" + "level": "high", + "unresolvedParameters": [], + "escapeHatchOnly": [], + "caveat": "" }, "annotations": [] }, @@ -6731,36 +7233,18 @@ ], "missingQueryParameters": [ "file_system_ids", - "file_system_names", "names", - "uids", - "user_names" + "uids" ], "missingBodyProperties": [], "readOnlyFields": [ "name" ], "confidence": { - "level": "partial", - "unresolvedParameters": [ - { - "parameter": "FileSystemName", - "surface": "AttributesOnly", - "file": "Public/Quota/Update-PfbQuotaUser.ps1", - "line": 30 - }, - { - "parameter": "UserName", - "surface": "AttributesOnly", - "file": "Public/Quota/Update-PfbQuotaUser.ps1", - "line": 31 - } - ], - "escapeHatchOnly": [ - "FileSystemName", - "UserName" - ], - "caveat": "body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability" + "level": "high", + "unresolvedParameters": [], + "escapeHatchOnly": [], + "caveat": "" }, "annotations": [] }, @@ -6823,33 +7307,49 @@ ], "missingQueryParameters": [], "missingBodyProperties": [ - "default_inbound_tls_policy", - "destroyed", - "name" + { + "name": "default_inbound_tls_policy", + "type": null, + "format": null, + "specRequired": false, + "synopsis": "The default TLS policy governing inbound traffic from clients accessing the accessing the realm's network interfaces.", + "suggestedPowerShellType": "[object]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Realm/Remove-PfbRealm.ps1", + "paramBlockLine": 36, + "payloadVariable": "body", + "assignmentStyle": "literal", + "hasAttributes": false + } + }, + { + "name": "name", + "type": "string", + "format": null, + "specRequired": false, + "synopsis": "A user-specified name.", + "suggestedPowerShellType": "[string]", + "enumValues": [], + "enumStatus": "not-found-in-resource", + "target": { + "file": "Public/Realm/Remove-PfbRealm.ps1", + "paramBlockLine": 36, + "payloadVariable": "body", + "assignmentStyle": "literal", + "hasAttributes": false + } + } ], "readOnlyFields": [ "id" ], "confidence": { - "level": "partial", - "unresolvedParameters": [ - { - "parameter": "Destroyed", - "surface": "AttributesOnly", - "file": "Public/Realm/Update-PfbRealm.ps1", - "line": 31 - }, - { - "parameter": "Eradicate", - "surface": "TypedUnresolved", - "file": "Public/Realm/Remove-PfbRealm.ps1", - "line": 32 - } - ], - "escapeHatchOnly": [ - "Destroyed" - ], - "caveat": "body reachable via -Attributes for some parameters and untraceable for others; lists reflect typed-parameter coverage only, not full wire reachability" + "level": "high", + "unresolvedParameters": [], + "escapeHatchOnly": [], + "caveat": "" }, "annotations": [] }, @@ -6879,25 +7379,47 @@ ], "missingQueryParameters": [], "missingBodyProperties": [ - "enabled", - "name", - "rules" + { + "name": "name", + "type": "string", + "format": null, + "specRequired": false, + "synopsis": "New name of the S3 export policy.", + "suggestedPowerShellType": "[string]", + "enumValues": [], + "enumStatus": "not-found-in-resource", + "target": { + "file": "Public/Policy/Update-PfbS3ExportPolicy.ps1", + "paramBlockLine": 48, + "payloadVariable": "body", + "assignmentStyle": "index", + "hasAttributes": true + } + }, + { + "name": "rules", + "type": "array", + "format": null, + "specRequired": false, + "synopsis": null, + "suggestedPowerShellType": "[object[]]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Policy/Update-PfbS3ExportPolicy.ps1", + "paramBlockLine": 48, + "payloadVariable": "body", + "assignmentStyle": "index", + "hasAttributes": true + } + } ], "readOnlyFields": [], "confidence": { - "level": "partial", - "unresolvedParameters": [ - { - "parameter": "Enabled", - "surface": "AttributesOnly", - "file": "Public/Policy/Update-PfbS3ExportPolicy.ps1", - "line": 42 - } - ], - "escapeHatchOnly": [ - "Enabled" - ], - "caveat": "body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability" + "level": "high", + "unresolvedParameters": [], + "escapeHatchOnly": [], + "caveat": "" }, "annotations": [] }, @@ -6979,11 +7501,74 @@ ], "missingQueryParameters": [], "missingBodyProperties": [ - "access_based_enumeration_enabled", - "enabled", - "location", - "name", - "rules" + { + "name": "access_based_enumeration_enabled", + "type": "boolean", + "format": null, + "specRequired": false, + "synopsis": "If set to `true`, enables access based enumeration on the policy.", + "suggestedPowerShellType": "[bool]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Policy/Update-PfbSmbClientPolicy.ps1", + "paramBlockLine": 48, + "payloadVariable": "body", + "assignmentStyle": "index", + "hasAttributes": true + } + }, + { + "name": "location", + "type": null, + "format": null, + "specRequired": false, + "synopsis": "Reference to the array where the policy is defined.", + "suggestedPowerShellType": "[object]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Policy/Update-PfbSmbClientPolicy.ps1", + "paramBlockLine": 48, + "payloadVariable": "body", + "assignmentStyle": "index", + "hasAttributes": true + } + }, + { + "name": "name", + "type": "string", + "format": null, + "specRequired": false, + "synopsis": "A user-specified name.", + "suggestedPowerShellType": "[string]", + "enumValues": [], + "enumStatus": "not-found-in-resource", + "target": { + "file": "Public/Policy/Update-PfbSmbClientPolicy.ps1", + "paramBlockLine": 48, + "payloadVariable": "body", + "assignmentStyle": "index", + "hasAttributes": true + } + }, + { + "name": "rules", + "type": "array", + "format": null, + "specRequired": false, + "synopsis": "All of the rules that are part of this policy.", + "suggestedPowerShellType": "[object[]]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Policy/Update-PfbSmbClientPolicy.ps1", + "paramBlockLine": 48, + "payloadVariable": "body", + "assignmentStyle": "index", + "hasAttributes": true + } + } ], "readOnlyFields": [ "id", @@ -6993,19 +7578,10 @@ "version" ], "confidence": { - "level": "partial", - "unresolvedParameters": [ - { - "parameter": "Enabled", - "surface": "AttributesOnly", - "file": "Public/Policy/Update-PfbSmbClientPolicy.ps1", - "line": 42 - } - ], - "escapeHatchOnly": [ - "Enabled" - ], - "caveat": "body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability" + "level": "high", + "unresolvedParameters": [], + "escapeHatchOnly": [], + "caveat": "" }, "annotations": [] }, @@ -7135,10 +7711,57 @@ ], "missingQueryParameters": [], "missingBodyProperties": [ - "enabled", - "location", - "name", - "rules" + { + "name": "location", + "type": null, + "format": null, + "specRequired": false, + "synopsis": "Reference to the array where the policy is defined.", + "suggestedPowerShellType": "[object]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Policy/Update-PfbSmbSharePolicy.ps1", + "paramBlockLine": 48, + "payloadVariable": "body", + "assignmentStyle": "index", + "hasAttributes": true + } + }, + { + "name": "name", + "type": "string", + "format": null, + "specRequired": false, + "synopsis": "A user-specified name.", + "suggestedPowerShellType": "[string]", + "enumValues": [], + "enumStatus": "not-found-in-resource", + "target": { + "file": "Public/Policy/Update-PfbSmbSharePolicy.ps1", + "paramBlockLine": 48, + "payloadVariable": "body", + "assignmentStyle": "index", + "hasAttributes": true + } + }, + { + "name": "rules", + "type": "array", + "format": null, + "specRequired": false, + "synopsis": "All of the rules that are part of this policy.", + "suggestedPowerShellType": "[object[]]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Policy/Update-PfbSmbSharePolicy.ps1", + "paramBlockLine": 48, + "payloadVariable": "body", + "assignmentStyle": "index", + "hasAttributes": true + } + } ], "readOnlyFields": [ "id", @@ -7147,19 +7770,10 @@ "realms" ], "confidence": { - "level": "partial", - "unresolvedParameters": [ - { - "parameter": "Enabled", - "surface": "AttributesOnly", - "file": "Public/Policy/Update-PfbSmbSharePolicy.ps1", - "line": 42 - } - ], - "escapeHatchOnly": [ - "Enabled" - ], - "caveat": "body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability" + "level": "high", + "unresolvedParameters": [], + "escapeHatchOnly": [], + "caveat": "" }, "annotations": [] }, @@ -7760,31 +8374,37 @@ ], "missingQueryParameters": [], "missingBodyProperties": [ - "enabled", - "name" - ], - "readOnlyFields": [ - "context", - "id", + { + "name": "name", + "type": "string", + "format": null, + "specRequired": false, + "synopsis": "A user-specified name.", + "suggestedPowerShellType": "[string]", + "enumValues": [], + "enumStatus": "not-found-in-resource", + "target": { + "file": "Public/Policy/Update-PfbUserGroupQuotaPolicy.ps1", + "paramBlockLine": 42, + "payloadVariable": "body", + "assignmentStyle": "index", + "hasAttributes": true + } + } + ], + "readOnlyFields": [ + "context", + "id", "is_local", "policy_type", "realms", "version" ], "confidence": { - "level": "partial", - "unresolvedParameters": [ - { - "parameter": "Enabled", - "surface": "AttributesOnly", - "file": "Public/Policy/Update-PfbUserGroupQuotaPolicy.ps1", - "line": 36 - } - ], - "escapeHatchOnly": [ - "Enabled" - ], - "caveat": "body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability" + "level": "high", + "unresolvedParameters": [], + "escapeHatchOnly": [], + "caveat": "" }, "annotations": [] }, @@ -7807,31 +8427,6 @@ }, "annotations": [] }, - { - "endpoint": "PATCH /workloads", - "cmdlets": [ - "Update-PfbWorkload" - ], - "missingQueryParameters": [], - "missingBodyProperties": [ - "destroyed" - ], - "readOnlyFields": [], - "confidence": { - "level": "partial", - "unresolvedParameters": [ - { - "parameter": "Destroyed", - "surface": "TypedUnresolved", - "file": "Public/Workloads/Update-PfbWorkload.ps1", - "line": 36 - } - ], - "escapeHatchOnly": [], - "caveat": "one or more parameters could not be traced to a wire name and have no -Attributes escape hatch; lists reflect typed-parameter coverage only, not full wire reachability" - }, - "annotations": [] - }, { "endpoint": "PATCH /worm-data-policies", "cmdlets": [ @@ -7861,496 +8456,232 @@ "New-PfbActiveDirectory" ], "missingQueryParameters": [ - "join_existing_account", - "names" - ], - "missingBodyProperties": [ - "ca_certificate", - "ca_certificate_group", - "computer_name", - "directory_servers", - "domain", - "encryption_types", - "fqdns", - "global_catalog_servers", - "join_ou", - "kerberos_servers", - "password", - "service_principal_names", - "user" - ], - "readOnlyFields": [], - "confidence": { - "level": "partial", - "unresolvedParameters": [ - { - "parameter": "Name", - "surface": "AttributesOnly", - "file": "Public/DirectoryService/New-PfbActiveDirectory.ps1", - "line": 40 - } - ], - "escapeHatchOnly": [ - "Name" - ], - "caveat": "body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability" - }, - "annotations": [] - }, - { - "endpoint": "POST /api-clients", - "cmdlets": [ - "New-PfbApiClient" + "join_existing_account" ], - "missingQueryParameters": [], "missingBodyProperties": [ { - "name": "access_policies", - "type": "array", + "name": "ca_certificate", + "type": null, "format": null, "specRequired": false, - "synopsis": "The access policies allowed for ID Tokens issued by this API client.", - "suggestedPowerShellType": "[object[]]", + "synopsis": "Reference (ID, name, and resource type) of the Certificate Authority (CA) that signed the certificates of the configured servers, which is used to validate the authenticity of the servers.", + "suggestedPowerShellType": "[object]", "enumValues": [], "enumStatus": "no-spec-enum-found", "target": { - "file": "Public/Admin/New-PfbApiClient.ps1", - "paramBlockLine": 58, - "payloadVariable": "body", - "assignmentStyle": "literal", + "file": "Public/DirectoryService/New-PfbActiveDirectory.ps1", + "paramBlockLine": 42, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", "hasAttributes": true } }, { - "name": "access_token_ttl_in_ms", - "type": "integer", - "format": "int64", + "name": "ca_certificate_group", + "type": null, + "format": null, "specRequired": false, - "synopsis": "The TTL (Time To Live) duration for which the exchanged access token is valid.", - "suggestedPowerShellType": "[long]", + "synopsis": "Reference (ID, name, and resource type) of a certificate group containing CA certificates that can be used to validate the authenticity of the configured servers.", + "suggestedPowerShellType": "[object]", "enumValues": [], "enumStatus": "no-spec-enum-found", "target": { - "file": "Public/Admin/New-PfbApiClient.ps1", - "paramBlockLine": 58, - "payloadVariable": "body", - "assignmentStyle": "literal", + "file": "Public/DirectoryService/New-PfbActiveDirectory.ps1", + "paramBlockLine": 42, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", "hasAttributes": true } }, { - "name": "issuer", + "name": "computer_name", "type": "string", "format": null, "specRequired": false, - "synopsis": "The name of the identity provider that will be issuing ID Tokens for this API client.", + "synopsis": "The common name of the computer account to be created in the Active Directory domain.", "suggestedPowerShellType": "[string]", "enumValues": [], "enumStatus": "no-spec-enum-found", "target": { - "file": "Public/Admin/New-PfbApiClient.ps1", - "paramBlockLine": 58, - "payloadVariable": "body", - "assignmentStyle": "literal", + "file": "Public/DirectoryService/New-PfbActiveDirectory.ps1", + "paramBlockLine": 42, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", "hasAttributes": true } - } - ], - "readOnlyFields": [], - "confidence": { - "level": "high", - "unresolvedParameters": [], - "escapeHatchOnly": [], - "caveat": "" - }, - "annotations": [] - }, - { - "endpoint": "POST /array-connections", - "cmdlets": [ - "New-PfbArrayConnection" - ], - "missingQueryParameters": [], - "missingBodyProperties": [], - "readOnlyFields": [ - "context", - "id", - "os", - "status", - "type", - "version" - ], - "confidence": { - "level": "high", - "unresolvedParameters": [], - "escapeHatchOnly": [], - "caveat": "" - }, - "annotations": [] - }, - { - "endpoint": "POST /arrays/erasures", - "cmdlets": [ - "New-PfbArrayErasure" - ], - "missingQueryParameters": [ - "eradicate_all_data", - "preserve_configuration_data", - "skip_phonehome_check" - ], - "missingBodyProperties": [], - "readOnlyFields": [], - "confidence": { - "level": "high", - "unresolvedParameters": [], - "escapeHatchOnly": [], - "caveat": "" - }, - "annotations": [] - }, - { - "endpoint": "POST /audit-file-systems-policies", - "cmdlets": [ - "New-PfbAuditFileSystemPolicy" - ], - "missingQueryParameters": [], - "missingBodyProperties": [ - "control_type", - "enabled", - "location", - "log_targets", - "name", - "rules" - ], - "readOnlyFields": [ - "id", - "is_local", - "policy_type", - "realms" - ], - "confidence": { - "level": "partial", - "unresolvedParameters": [ - { - "parameter": "Enabled", - "surface": "AttributesOnly", - "file": "Public/Policy/New-PfbAuditFileSystemPolicy.ps1", - "line": 35 - } - ], - "escapeHatchOnly": [ - "Enabled" - ], - "caveat": "body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability" - }, - "annotations": [] - }, - { - "endpoint": "POST /audit-object-store-policies", - "cmdlets": [ - "New-PfbAuditObjectStorePolicy" - ], - "missingQueryParameters": [], - "missingBodyProperties": [ - "enabled", - "location", - "log_targets", - "name" - ], - "readOnlyFields": [ - "id", - "is_local", - "policy_type", - "realms" - ], - "confidence": { - "level": "partial", - "unresolvedParameters": [ - { - "parameter": "Enabled", - "surface": "AttributesOnly", - "file": "Public/Policy/New-PfbAuditObjectStorePolicy.ps1", - "line": 35 - } - ], - "escapeHatchOnly": [ - "Enabled" - ], - "caveat": "body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability" - }, - "annotations": [] - }, - { - "endpoint": "POST /buckets", - "cmdlets": [ - "New-PfbBucket" - ], - "missingQueryParameters": [], - "missingBodyProperties": [ + }, { - "name": "bucket_type", - "type": "string", + "name": "directory_servers", + "type": "array", "format": null, "specRequired": false, - "synopsis": "The bucket type for the bucket.", - "suggestedPowerShellType": "[string]", - "enumValues": [ - "classic", - "multi-site-writable" - ], - "enumStatus": "matched", + "synopsis": "A list of directory servers that will be used for lookups related to user authorization.", + "suggestedPowerShellType": "[string[]]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", "target": { - "file": "Public/Bucket/New-PfbBucket.ps1", - "paramBlockLine": 39, - "payloadVariable": "body", - "assignmentStyle": "index", + "file": "Public/DirectoryService/New-PfbActiveDirectory.ps1", + "paramBlockLine": 42, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", "hasAttributes": true } }, { - "name": "eradication_config", - "type": null, + "name": "domain", + "type": "string", "format": null, - "specRequired": false, - "synopsis": null, - "suggestedPowerShellType": "[object]", + "specRequired": true, + "synopsis": "The Active Directory domain to join.", + "suggestedPowerShellType": "[string]", "enumValues": [], "enumStatus": "no-spec-enum-found", "target": { - "file": "Public/Bucket/New-PfbBucket.ps1", - "paramBlockLine": 39, - "payloadVariable": "body", - "assignmentStyle": "index", + "file": "Public/DirectoryService/New-PfbActiveDirectory.ps1", + "paramBlockLine": 42, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", "hasAttributes": true } }, { - "name": "hard_limit_enabled", - "type": "boolean", + "name": "encryption_types", + "type": "array", "format": null, "specRequired": false, - "synopsis": "If set to `true`, the bucket's size, as defined by `quota_limit`, is used as a hard limit quota.", - "suggestedPowerShellType": "[bool]", - "enumValues": [], - "enumStatus": "no-spec-enum-found", + "synopsis": "The encryption types that will be supported for use by clients for Kerberos authentication.", + "suggestedPowerShellType": "[string[]]", + "enumValues": [ + "aes256-cts-hmac-sha1-96", + "aes128-cts-hmac-sha1-96", + "arcfour-hmac" + ], + "enumStatus": "matched", "target": { - "file": "Public/Bucket/New-PfbBucket.ps1", - "paramBlockLine": 39, - "payloadVariable": "body", - "assignmentStyle": "index", + "file": "Public/DirectoryService/New-PfbActiveDirectory.ps1", + "paramBlockLine": 42, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", "hasAttributes": true } }, { - "name": "object_lock_config", - "type": null, + "name": "fqdns", + "type": "array", "format": null, "specRequired": false, - "synopsis": null, - "suggestedPowerShellType": "[object]", + "synopsis": "A list of fully qualified domain names to use to register service principal names for the machine account.", + "suggestedPowerShellType": "[string[]]", "enumValues": [], "enumStatus": "no-spec-enum-found", "target": { - "file": "Public/Bucket/New-PfbBucket.ps1", - "paramBlockLine": 39, - "payloadVariable": "body", - "assignmentStyle": "index", + "file": "Public/DirectoryService/New-PfbActiveDirectory.ps1", + "paramBlockLine": 42, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", "hasAttributes": true } }, { - "name": "retention_lock", - "type": "string", + "name": "global_catalog_servers", + "type": "array", "format": null, "specRequired": false, - "synopsis": "If set to `ratcheted`, then `object_lock_config.default_retention_mode` cannot be changed if set to `compliance`.", - "suggestedPowerShellType": "[string]", - "enumValues": [ - "unlocked", - "ratcheted" - ], - "enumStatus": "matched", + "synopsis": "A list of global catalog servers that will be used for lookups related to user authorization.", + "suggestedPowerShellType": "[string[]]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", "target": { - "file": "Public/Bucket/New-PfbBucket.ps1", - "paramBlockLine": 39, - "payloadVariable": "body", - "assignmentStyle": "index", + "file": "Public/DirectoryService/New-PfbActiveDirectory.ps1", + "paramBlockLine": 42, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", "hasAttributes": true } - } - ], - "readOnlyFields": [], - "confidence": { - "level": "high", - "unresolvedParameters": [], - "escapeHatchOnly": [], - "caveat": "" - }, - "annotations": [] - }, - { - "endpoint": "POST /buckets/audit-filters", - "cmdlets": [ - "New-PfbBucketAuditFilter" - ], - "missingQueryParameters": [ - "bucket_ids", - "names" - ], - "missingBodyProperties": [ - "actions", - "s3_prefixes" - ], - "readOnlyFields": [], - "confidence": { - "level": "partial", - "unresolvedParameters": [ - { - "parameter": "Name", - "surface": "AttributesOnly", - "file": "Public/Bucket/New-PfbBucketAuditFilter.ps1", - "line": 44 - } - ], - "escapeHatchOnly": [ - "Name" - ], - "caveat": "body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability" - }, - "annotations": [] - }, - { - "endpoint": "POST /buckets/bucket-access-policies", - "cmdlets": [ - "New-PfbBucketAccessPolicy" - ], - "missingQueryParameters": [ - "bucket_ids" - ], - "missingBodyProperties": [ + }, { - "name": "rules", - "type": "array", + "name": "join_ou", + "type": "string", "format": null, "specRequired": false, - "synopsis": null, - "suggestedPowerShellType": "[object[]]", + "synopsis": "The relative distinguished name of the organizational unit in which the computer account should be created when joining the domain.", + "suggestedPowerShellType": "[string]", "enumValues": [], "enumStatus": "no-spec-enum-found", "target": { - "file": "Public/Bucket/New-PfbBucketAccessPolicy.ps1", - "paramBlockLine": 53, - "payloadVariable": null, - "assignmentStyle": null, - "hasAttributes": false + "file": "Public/DirectoryService/New-PfbActiveDirectory.ps1", + "paramBlockLine": 42, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true } - } - ], - "readOnlyFields": [], - "confidence": { - "level": "high", - "unresolvedParameters": [], - "escapeHatchOnly": [], - "caveat": "" - }, - "annotations": [] - }, - { - "endpoint": "POST /buckets/bucket-access-policies/rules", - "cmdlets": [ - "New-PfbBucketAccessPolicyRule" - ], - "missingQueryParameters": [ - "bucket_ids" - ], - "missingBodyProperties": [ + }, { - "name": "actions", + "name": "kerberos_servers", "type": "array", "format": null, "specRequired": false, - "synopsis": "The list of actions granted by this rule.", + "synopsis": "A list of key distribution servers to use for Kerberos protocol.", "suggestedPowerShellType": "[string[]]", "enumValues": [], - "enumStatus": "not-found-in-resource", + "enumStatus": "no-spec-enum-found", "target": { - "file": "Public/Bucket/New-PfbBucketAccessPolicyRule.ps1", - "paramBlockLine": 82, + "file": "Public/DirectoryService/New-PfbActiveDirectory.ps1", + "paramBlockLine": 42, "payloadVariable": "Attributes", "assignmentStyle": "attributesOnly", "hasAttributes": true } }, { - "name": "principals", - "type": null, + "name": "password", + "type": "string", "format": null, - "specRequired": false, - "synopsis": "The principals to which this rule applies.", - "suggestedPowerShellType": "[object]", + "specRequired": true, + "synopsis": "The login password of the user with privileges to create the computer account in the domain.", + "suggestedPowerShellType": "[string]", "enumValues": [], "enumStatus": "no-spec-enum-found", "target": { - "file": "Public/Bucket/New-PfbBucketAccessPolicyRule.ps1", - "paramBlockLine": 82, + "file": "Public/DirectoryService/New-PfbActiveDirectory.ps1", + "paramBlockLine": 42, "payloadVariable": "Attributes", "assignmentStyle": "attributesOnly", "hasAttributes": true } }, { - "name": "resources", + "name": "service_principal_names", "type": "array", "format": null, "specRequired": false, - "synopsis": "The list of resources which this rule applies to.", + "synopsis": "A list of service principal names to register for the machine account, which can be used for the creation of keys for Kerberos authentication.", "suggestedPowerShellType": "[string[]]", "enumValues": [], "enumStatus": "no-spec-enum-found", "target": { - "file": "Public/Bucket/New-PfbBucketAccessPolicyRule.ps1", - "paramBlockLine": 82, + "file": "Public/DirectoryService/New-PfbActiveDirectory.ps1", + "paramBlockLine": 42, "payloadVariable": "Attributes", "assignmentStyle": "attributesOnly", "hasAttributes": true } - } - ], - "readOnlyFields": [ - "effect" - ], - "confidence": { - "level": "high", - "unresolvedParameters": [], - "escapeHatchOnly": [], - "caveat": "" - }, - "annotations": [] - }, - { - "endpoint": "POST /buckets/cross-origin-resource-sharing-policies", - "cmdlets": [ - "New-PfbBucketCorsPolicy" - ], - "missingQueryParameters": [ - "bucket_ids" - ], - "missingBodyProperties": [ + }, { - "name": "rules", - "type": "array", + "name": "user", + "type": "string", "format": null, - "specRequired": false, - "synopsis": null, - "suggestedPowerShellType": "[object[]]", + "specRequired": true, + "synopsis": "The login name of the user with privileges to create the computer account in the domain.", + "suggestedPowerShellType": "[string]", "enumValues": [], "enumStatus": "no-spec-enum-found", "target": { - "file": "Public/Bucket/New-PfbBucketCorsPolicy.ps1", - "paramBlockLine": 53, - "payloadVariable": null, - "assignmentStyle": null, - "hasAttributes": false + "file": "Public/DirectoryService/New-PfbActiveDirectory.ps1", + "paramBlockLine": 42, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true } } ], @@ -8364,62 +8695,60 @@ "annotations": [] }, { - "endpoint": "POST /buckets/cross-origin-resource-sharing-policies/rules", + "endpoint": "POST /api-clients", "cmdlets": [ - "New-PfbBucketCorsPolicyRule" - ], - "missingQueryParameters": [ - "bucket_ids" + "New-PfbApiClient" ], + "missingQueryParameters": [], "missingBodyProperties": [ { - "name": "allowed_headers", + "name": "access_policies", "type": "array", "format": null, "specRequired": false, - "synopsis": "A list of headers that are permitted to be included in cross-origin requests to access a bucket.", - "suggestedPowerShellType": "[string[]]", - "enumValues": [], + "synopsis": "The access policies allowed for ID Tokens issued by this API client.", + "suggestedPowerShellType": "[object[]]", + "enumValues": [], "enumStatus": "no-spec-enum-found", "target": { - "file": "Public/Bucket/New-PfbBucketCorsPolicyRule.ps1", - "paramBlockLine": 85, - "payloadVariable": "Attributes", - "assignmentStyle": "attributesOnly", + "file": "Public/Admin/New-PfbApiClient.ps1", + "paramBlockLine": 58, + "payloadVariable": "body", + "assignmentStyle": "literal", "hasAttributes": true } }, { - "name": "allowed_methods", - "type": "array", - "format": null, + "name": "access_token_ttl_in_ms", + "type": "integer", + "format": "int64", "specRequired": false, - "synopsis": "A list of HTTP methods that are permitted for cross-origin requests to access a bucket.", - "suggestedPowerShellType": "[string[]]", + "synopsis": "The TTL (Time To Live) duration for which the exchanged access token is valid.", + "suggestedPowerShellType": "[long]", "enumValues": [], "enumStatus": "no-spec-enum-found", "target": { - "file": "Public/Bucket/New-PfbBucketCorsPolicyRule.ps1", - "paramBlockLine": 85, - "payloadVariable": "Attributes", - "assignmentStyle": "attributesOnly", + "file": "Public/Admin/New-PfbApiClient.ps1", + "paramBlockLine": 58, + "payloadVariable": "body", + "assignmentStyle": "literal", "hasAttributes": true } }, { - "name": "allowed_origins", - "type": "array", + "name": "issuer", + "type": "string", "format": null, "specRequired": false, - "synopsis": "A list of origins (domains) that are permitted to make cross-origin requests to access a bucket.", - "suggestedPowerShellType": "[string[]]", + "synopsis": "The name of the identity provider that will be issuing ID Tokens for this API client.", + "suggestedPowerShellType": "[string]", "enumValues": [], "enumStatus": "no-spec-enum-found", "target": { - "file": "Public/Bucket/New-PfbBucketCorsPolicyRule.ps1", - "paramBlockLine": 85, - "payloadVariable": "Attributes", - "assignmentStyle": "attributesOnly", + "file": "Public/Admin/New-PfbApiClient.ps1", + "paramBlockLine": 58, + "payloadVariable": "body", + "assignmentStyle": "literal", "hasAttributes": true } } @@ -8434,296 +8763,331 @@ "annotations": [] }, { - "endpoint": "POST /certificates", + "endpoint": "POST /array-connections", "cmdlets": [ - "New-PfbCertificate" + "New-PfbArrayConnection" + ], + "missingQueryParameters": [], + "missingBodyProperties": [], + "readOnlyFields": [ + "context", + "id", + "os", + "status", + "type", + "version" + ], + "confidence": { + "level": "high", + "unresolvedParameters": [], + "escapeHatchOnly": [], + "caveat": "" + }, + "annotations": [] + }, + { + "endpoint": "POST /arrays/erasures", + "cmdlets": [ + "New-PfbArrayErasure" + ], + "missingQueryParameters": [ + "eradicate_all_data", + "preserve_configuration_data", + "skip_phonehome_check" + ], + "missingBodyProperties": [], + "readOnlyFields": [], + "confidence": { + "level": "high", + "unresolvedParameters": [], + "escapeHatchOnly": [], + "caveat": "" + }, + "annotations": [] + }, + { + "endpoint": "POST /audit-file-systems-policies", + "cmdlets": [ + "New-PfbAuditFileSystemPolicy" ], "missingQueryParameters": [], "missingBodyProperties": [ { - "name": "certificate", - "type": "string", - "format": null, - "specRequired": false, - "synopsis": "The text of the certificate.", - "suggestedPowerShellType": "[string]", - "enumValues": [], - "enumStatus": "no-spec-enum-found", - "target": { - "file": "Public/Certificate/New-PfbCertificate.ps1", - "paramBlockLine": 36, - "payloadVariable": "Attributes", - "assignmentStyle": "attributesOnly", - "hasAttributes": true - } - }, - { - "name": "certificate_type", + "name": "control_type", "type": "string", "format": null, "specRequired": false, - "synopsis": "The type of certificate.", + "synopsis": "Specifies the evaluation mode for auditing in this policy.", "suggestedPowerShellType": "[string]", "enumValues": [ - "appliance", - "external" + "policy", + "sacl" ], "enumStatus": "matched", "target": { - "file": "Public/Certificate/New-PfbCertificate.ps1", - "paramBlockLine": 36, - "payloadVariable": "Attributes", - "assignmentStyle": "attributesOnly", + "file": "Public/Policy/New-PfbAuditFileSystemPolicy.ps1", + "paramBlockLine": 41, + "payloadVariable": "body", + "assignmentStyle": "index", "hasAttributes": true } }, { - "name": "common_name", - "type": "string", + "name": "location", + "type": null, "format": null, "specRequired": false, - "synopsis": "The common name field listed in the certificate.", - "suggestedPowerShellType": "[string]", + "synopsis": "Reference to the array where the policy is defined.", + "suggestedPowerShellType": "[object]", "enumValues": [], "enumStatus": "no-spec-enum-found", "target": { - "file": "Public/Certificate/New-PfbCertificate.ps1", - "paramBlockLine": 36, - "payloadVariable": "Attributes", - "assignmentStyle": "attributesOnly", + "file": "Public/Policy/New-PfbAuditFileSystemPolicy.ps1", + "paramBlockLine": 41, + "payloadVariable": "body", + "assignmentStyle": "index", "hasAttributes": true } }, { - "name": "country", - "type": "string", + "name": "log_targets", + "type": "array", "format": null, "specRequired": false, - "synopsis": "The country field listed in the certificate.", - "suggestedPowerShellType": "[string]", - "enumValues": [], - "enumStatus": "no-spec-enum-found", - "target": { - "file": "Public/Certificate/New-PfbCertificate.ps1", - "paramBlockLine": 36, - "payloadVariable": "Attributes", - "assignmentStyle": "attributesOnly", - "hasAttributes": true - } - }, - { - "name": "days", - "type": "integer", - "format": "int32", - "specRequired": false, - "synopsis": "The number of days that the self-signed certificate is valid.", - "suggestedPowerShellType": "[int]", + "synopsis": "List of targets which will be utilized for audit log storage.", + "suggestedPowerShellType": "[object[]]", "enumValues": [], "enumStatus": "no-spec-enum-found", "target": { - "file": "Public/Certificate/New-PfbCertificate.ps1", - "paramBlockLine": 36, - "payloadVariable": "Attributes", - "assignmentStyle": "attributesOnly", + "file": "Public/Policy/New-PfbAuditFileSystemPolicy.ps1", + "paramBlockLine": 41, + "payloadVariable": "body", + "assignmentStyle": "index", "hasAttributes": true } }, { - "name": "email", + "name": "name", "type": "string", "format": null, "specRequired": false, - "synopsis": "The email field listed in the certificate.", + "synopsis": "A user-specified name.", "suggestedPowerShellType": "[string]", "enumValues": [], - "enumStatus": "no-spec-enum-found", + "enumStatus": "not-found-in-resource", "target": { - "file": "Public/Certificate/New-PfbCertificate.ps1", - "paramBlockLine": 36, - "payloadVariable": "Attributes", - "assignmentStyle": "attributesOnly", + "file": "Public/Policy/New-PfbAuditFileSystemPolicy.ps1", + "paramBlockLine": 41, + "payloadVariable": "body", + "assignmentStyle": "index", "hasAttributes": true } }, { - "name": "intermediate_certificate", - "type": "string", + "name": "rules", + "type": "array", "format": null, "specRequired": false, - "synopsis": "Intermediate certificate chains.", - "suggestedPowerShellType": "[string]", + "synopsis": "All of the rules that are part of this policy.", + "suggestedPowerShellType": "[object[]]", "enumValues": [], "enumStatus": "no-spec-enum-found", "target": { - "file": "Public/Certificate/New-PfbCertificate.ps1", - "paramBlockLine": 36, - "payloadVariable": "Attributes", - "assignmentStyle": "attributesOnly", + "file": "Public/Policy/New-PfbAuditFileSystemPolicy.ps1", + "paramBlockLine": 41, + "payloadVariable": "body", + "assignmentStyle": "index", "hasAttributes": true } - }, + } + ], + "readOnlyFields": [ + "id", + "is_local", + "policy_type", + "realms" + ], + "confidence": { + "level": "high", + "unresolvedParameters": [], + "escapeHatchOnly": [], + "caveat": "" + }, + "annotations": [] + }, + { + "endpoint": "POST /audit-object-store-policies", + "cmdlets": [ + "New-PfbAuditObjectStorePolicy" + ], + "missingQueryParameters": [], + "missingBodyProperties": [ { - "name": "key_algorithm", - "type": "string", + "name": "location", + "type": null, "format": null, "specRequired": false, - "synopsis": "The key algorithm used to generate the certificate.", - "suggestedPowerShellType": "[string]", + "synopsis": "Reference to the array where the policy is defined.", + "suggestedPowerShellType": "[object]", "enumValues": [], "enumStatus": "no-spec-enum-found", "target": { - "file": "Public/Certificate/New-PfbCertificate.ps1", - "paramBlockLine": 36, - "payloadVariable": "Attributes", - "assignmentStyle": "attributesOnly", + "file": "Public/Policy/New-PfbAuditObjectStorePolicy.ps1", + "paramBlockLine": 41, + "payloadVariable": "body", + "assignmentStyle": "index", "hasAttributes": true } }, { - "name": "key_size", - "type": "integer", - "format": "int32", + "name": "log_targets", + "type": "array", + "format": null, "specRequired": false, - "synopsis": "The size (in bits) of the private key for the certificate.", - "suggestedPowerShellType": "[int]", + "synopsis": "List of targets which will be utilized for audit log storage.", + "suggestedPowerShellType": "[object[]]", "enumValues": [], "enumStatus": "no-spec-enum-found", "target": { - "file": "Public/Certificate/New-PfbCertificate.ps1", - "paramBlockLine": 36, - "payloadVariable": "Attributes", - "assignmentStyle": "attributesOnly", + "file": "Public/Policy/New-PfbAuditObjectStorePolicy.ps1", + "paramBlockLine": 41, + "payloadVariable": "body", + "assignmentStyle": "index", "hasAttributes": true } }, { - "name": "locality", + "name": "name", "type": "string", "format": null, "specRequired": false, - "synopsis": "The locality field listed in the certificate.", + "synopsis": "A user-specified name.", "suggestedPowerShellType": "[string]", "enumValues": [], - "enumStatus": "no-spec-enum-found", + "enumStatus": "not-found-in-resource", "target": { - "file": "Public/Certificate/New-PfbCertificate.ps1", - "paramBlockLine": 36, - "payloadVariable": "Attributes", - "assignmentStyle": "attributesOnly", + "file": "Public/Policy/New-PfbAuditObjectStorePolicy.ps1", + "paramBlockLine": 41, + "payloadVariable": "body", + "assignmentStyle": "index", "hasAttributes": true } - }, + } + ], + "readOnlyFields": [ + "id", + "is_local", + "policy_type", + "realms" + ], + "confidence": { + "level": "high", + "unresolvedParameters": [], + "escapeHatchOnly": [], + "caveat": "" + }, + "annotations": [] + }, + { + "endpoint": "POST /buckets", + "cmdlets": [ + "New-PfbBucket" + ], + "missingQueryParameters": [], + "missingBodyProperties": [ { - "name": "organization", + "name": "bucket_type", "type": "string", "format": null, "specRequired": false, - "synopsis": "The organization field listed in the certificate.", + "synopsis": "The bucket type for the bucket.", "suggestedPowerShellType": "[string]", - "enumValues": [], - "enumStatus": "no-spec-enum-found", + "enumValues": [ + "classic", + "multi-site-writable" + ], + "enumStatus": "matched", "target": { - "file": "Public/Certificate/New-PfbCertificate.ps1", - "paramBlockLine": 36, - "payloadVariable": "Attributes", - "assignmentStyle": "attributesOnly", + "file": "Public/Bucket/New-PfbBucket.ps1", + "paramBlockLine": 39, + "payloadVariable": "body", + "assignmentStyle": "index", "hasAttributes": true } }, { - "name": "organizational_unit", - "type": "string", + "name": "eradication_config", + "type": null, "format": null, "specRequired": false, - "synopsis": "The organizational unit field listed in the certificate.", - "suggestedPowerShellType": "[string]", + "synopsis": null, + "suggestedPowerShellType": "[object]", "enumValues": [], "enumStatus": "no-spec-enum-found", "target": { - "file": "Public/Certificate/New-PfbCertificate.ps1", - "paramBlockLine": 36, - "payloadVariable": "Attributes", - "assignmentStyle": "attributesOnly", + "file": "Public/Bucket/New-PfbBucket.ps1", + "paramBlockLine": 39, + "payloadVariable": "body", + "assignmentStyle": "index", "hasAttributes": true } }, { - "name": "passphrase", - "type": "string", + "name": "hard_limit_enabled", + "type": "boolean", "format": null, "specRequired": false, - "synopsis": "The passphrase used to encrypt `private_key`.", - "suggestedPowerShellType": "[string]", + "synopsis": "If set to `true`, the bucket's size, as defined by `quota_limit`, is used as a hard limit quota.", + "suggestedPowerShellType": "[bool]", "enumValues": [], "enumStatus": "no-spec-enum-found", "target": { - "file": "Public/Certificate/New-PfbCertificate.ps1", - "paramBlockLine": 36, - "payloadVariable": "Attributes", - "assignmentStyle": "attributesOnly", + "file": "Public/Bucket/New-PfbBucket.ps1", + "paramBlockLine": 39, + "payloadVariable": "body", + "assignmentStyle": "index", "hasAttributes": true } }, { - "name": "private_key", - "type": "string", + "name": "object_lock_config", + "type": null, "format": null, "specRequired": false, - "synopsis": "The text of the private key.", - "suggestedPowerShellType": "[string]", + "synopsis": null, + "suggestedPowerShellType": "[object]", "enumValues": [], "enumStatus": "no-spec-enum-found", "target": { - "file": "Public/Certificate/New-PfbCertificate.ps1", - "paramBlockLine": 36, - "payloadVariable": "Attributes", - "assignmentStyle": "attributesOnly", + "file": "Public/Bucket/New-PfbBucket.ps1", + "paramBlockLine": 39, + "payloadVariable": "body", + "assignmentStyle": "index", "hasAttributes": true } }, { - "name": "state", + "name": "retention_lock", "type": "string", "format": null, "specRequired": false, - "synopsis": "The state/province field listed in the certificate.", + "synopsis": "If set to `ratcheted`, then `object_lock_config.default_retention_mode` cannot be changed if set to `compliance`.", "suggestedPowerShellType": "[string]", - "enumValues": [], - "enumStatus": "not-found-in-resource", - "target": { - "file": "Public/Certificate/New-PfbCertificate.ps1", - "paramBlockLine": 36, - "payloadVariable": "Attributes", - "assignmentStyle": "attributesOnly", - "hasAttributes": true - } - }, - { - "name": "subject_alternative_names", - "type": "array", - "format": null, - "specRequired": false, - "synopsis": "The alternative names that are secured by this certificate.", - "suggestedPowerShellType": "[string[]]", - "enumValues": [], - "enumStatus": "no-spec-enum-found", + "enumValues": [ + "unlocked", + "ratcheted" + ], + "enumStatus": "matched", "target": { - "file": "Public/Certificate/New-PfbCertificate.ps1", - "paramBlockLine": 36, - "payloadVariable": "Attributes", - "assignmentStyle": "attributesOnly", + "file": "Public/Bucket/New-PfbBucket.ps1", + "paramBlockLine": 39, + "payloadVariable": "body", + "assignmentStyle": "index", "hasAttributes": true } } ], - "readOnlyFields": [ - "issued_by", - "issued_to", - "realms", - "status", - "valid_from", - "valid_to" - ], + "readOnlyFields": [], "confidence": { "level": "high", "unresolvedParameters": [], @@ -8733,21 +9097,17 @@ "annotations": [] }, { - "endpoint": "POST /certificates/certificate-signing-requests", + "endpoint": "POST /buckets/audit-filters", "cmdlets": [ - "New-PfbCertificateSigningRequest" + "New-PfbBucketAuditFilter" + ], + "missingQueryParameters": [ + "bucket_ids", + "names" ], - "missingQueryParameters": [], "missingBodyProperties": [ - "certificate", - "common_name", - "country", - "email", - "locality", - "organization", - "organizational_unit", - "state", - "subject_alternative_names" + "actions", + "s3_prefixes" ], "readOnlyFields": [], "confidence": { @@ -8756,8 +9116,8 @@ { "parameter": "Name", "surface": "AttributesOnly", - "file": "Public/Certificate/New-PfbCertificateSigningRequest.ps1", - "line": 29 + "file": "Public/Bucket/New-PfbBucketAuditFilter.ps1", + "line": 44 } ], "escapeHatchOnly": [ @@ -8768,47 +9128,32 @@ "annotations": [] }, { - "endpoint": "POST /data-eviction-policies", + "endpoint": "POST /buckets/bucket-access-policies", "cmdlets": [ - "New-PfbDataEvictionPolicy" - ], - "missingQueryParameters": [], - "missingBodyProperties": [ - "enabled", - "location", - "name" + "New-PfbBucketAccessPolicy" ], - "readOnlyFields": [ - "id", - "is_local", - "policy_type", - "realms" + "missingQueryParameters": [ + "bucket_ids" ], - "confidence": { - "level": "partial", - "unresolvedParameters": [ - { - "parameter": "Disabled", - "surface": "TypedUnresolved", - "file": "Public/DataEviction/New-PfbDataEvictionPolicy.ps1", - "line": 31 + "missingBodyProperties": [ + { + "name": "rules", + "type": "array", + "format": null, + "specRequired": false, + "synopsis": null, + "suggestedPowerShellType": "[object[]]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Bucket/New-PfbBucketAccessPolicy.ps1", + "paramBlockLine": 53, + "payloadVariable": null, + "assignmentStyle": null, + "hasAttributes": false } - ], - "escapeHatchOnly": [], - "caveat": "one or more parameters could not be traced to a wire name and have no -Attributes escape hatch; lists reflect typed-parameter coverage only, not full wire reachability" - }, - "annotations": [] - }, - { - "endpoint": "POST /directory-services/local/groups", - "cmdlets": [ - "New-PfbLocalGroup" - ], - "missingQueryParameters": [ - "local_directory_service_ids", - "local_directory_service_names" + } ], - "missingBodyProperties": [], "readOnlyFields": [], "confidence": { "level": "high", @@ -8819,111 +9164,69 @@ "annotations": [] }, { - "endpoint": "POST /directory-services/local/groups/members", + "endpoint": "POST /buckets/bucket-access-policies/rules", "cmdlets": [ - "New-PfbLocalGroupMember" + "New-PfbBucketAccessPolicyRule" ], "missingQueryParameters": [ - "group_gids", - "group_sids", - "local_directory_service_ids" - ], - "missingBodyProperties": [ - "members" - ], - "readOnlyFields": [], - "confidence": { - "level": "partial", - "unresolvedParameters": [ - { - "parameter": "Member", - "surface": "TypedUnresolved", - "file": "Public/DirectoryService/New-PfbLocalGroupMember.ps1", - "line": 35 - } - ], - "escapeHatchOnly": [], - "caveat": "one or more parameters could not be traced to a wire name and have no -Attributes escape hatch; lists reflect typed-parameter coverage only, not full wire reachability" - }, - "annotations": [] - }, - { - "endpoint": "POST /directory-services/roles", - "cmdlets": [ - "New-PfbDirectoryServiceRole" + "bucket_ids" ], - "missingQueryParameters": [], "missingBodyProperties": [ { - "name": "group", - "type": "string", + "name": "actions", + "type": "array", "format": null, "specRequired": false, - "synopsis": "Common Name (CN) of the directory service group containing users with authority level of the specified role name.", - "suggestedPowerShellType": "[string]", + "synopsis": "The list of actions granted by this rule.", + "suggestedPowerShellType": "[string[]]", "enumValues": [], - "enumStatus": "no-spec-enum-found", + "enumStatus": "not-found-in-resource", "target": { - "file": "Public/DirectoryService/New-PfbDirectoryServiceRole.ps1", - "paramBlockLine": 33, - "payloadVariable": "body", - "assignmentStyle": "unknown", + "file": "Public/Bucket/New-PfbBucketAccessPolicyRule.ps1", + "paramBlockLine": 82, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", "hasAttributes": true } }, { - "name": "group_base", - "type": "string", + "name": "principals", + "type": null, "format": null, "specRequired": false, - "synopsis": "Specifies where the configured group is located in the directory tree.", - "suggestedPowerShellType": "[string]", + "synopsis": "The principals to which this rule applies.", + "suggestedPowerShellType": "[object]", "enumValues": [], "enumStatus": "no-spec-enum-found", "target": { - "file": "Public/DirectoryService/New-PfbDirectoryServiceRole.ps1", - "paramBlockLine": 33, - "payloadVariable": "body", - "assignmentStyle": "unknown", + "file": "Public/Bucket/New-PfbBucketAccessPolicyRule.ps1", + "paramBlockLine": 82, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", "hasAttributes": true } }, { - "name": "management_access_policies", + "name": "resources", "type": "array", "format": null, "specRequired": false, - "synopsis": "List of management access policies associated with the directory service role.", - "suggestedPowerShellType": "[object[]]", - "enumValues": [], - "enumStatus": "no-spec-enum-found", - "target": { - "file": "Public/DirectoryService/New-PfbDirectoryServiceRole.ps1", - "paramBlockLine": 33, - "payloadVariable": "body", - "assignmentStyle": "unknown", - "hasAttributes": true - } - }, - { - "name": "role", - "type": null, - "format": null, - "specRequired": false, - "synopsis": "Deprecated.", - "suggestedPowerShellType": "[object]", + "synopsis": "The list of resources which this rule applies to.", + "suggestedPowerShellType": "[string[]]", "enumValues": [], "enumStatus": "no-spec-enum-found", "target": { - "file": "Public/DirectoryService/New-PfbDirectoryServiceRole.ps1", - "paramBlockLine": 33, - "payloadVariable": "body", - "assignmentStyle": "unknown", + "file": "Public/Bucket/New-PfbBucketAccessPolicyRule.ps1", + "paramBlockLine": 82, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", "hasAttributes": true } } ], - "readOnlyFields": [], + "readOnlyFields": [ + "effect" + ], "confidence": { "level": "high", "unresolvedParameters": [], @@ -8933,49 +9236,32 @@ "annotations": [] }, { - "endpoint": "POST /dns", + "endpoint": "POST /buckets/cross-origin-resource-sharing-policies", "cmdlets": [ - "New-PfbDns" + "New-PfbBucketCorsPolicy" ], "missingQueryParameters": [ - "names" + "bucket_ids" ], "missingBodyProperties": [ - "ca_certificate", - "ca_certificate_group", - "domain", - "nameservers", - "services", - "sources" - ], - "readOnlyFields": [], - "confidence": { - "level": "partial", - "unresolvedParameters": [ - { - "parameter": "Name", - "surface": "AttributesOnly", - "file": "Public/Network/New-PfbDns.ps1", - "line": 29 + { + "name": "rules", + "type": "array", + "format": null, + "specRequired": false, + "synopsis": null, + "suggestedPowerShellType": "[object[]]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Bucket/New-PfbBucketCorsPolicy.ps1", + "paramBlockLine": 53, + "payloadVariable": null, + "assignmentStyle": null, + "hasAttributes": false } - ], - "escapeHatchOnly": [ - "Name" - ], - "caveat": "body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability" - }, - "annotations": [] - }, - { - "endpoint": "POST /file-system-exports", - "cmdlets": [ - "New-PfbFileSystemExport" - ], - "missingQueryParameters": [ - "member_ids", - "policy_ids" + } ], - "missingBodyProperties": [], "readOnlyFields": [], "confidence": { "level": "high", @@ -8986,127 +9272,1726 @@ "annotations": [] }, { - "endpoint": "POST /file-system-replica-links", + "endpoint": "POST /buckets/cross-origin-resource-sharing-policies/rules", "cmdlets": [ - "New-PfbFileSystemReplicaLink" + "New-PfbBucketCorsPolicyRule" ], "missingQueryParameters": [ - "local_file_system_ids" + "bucket_ids" ], "missingBodyProperties": [ { - "name": "direction", - "type": null, + "name": "allowed_headers", + "type": "array", "format": null, "specRequired": false, - "synopsis": null, - "suggestedPowerShellType": "[object]", - "enumValues": [ - "inbound", - "outbound" - ], - "enumStatus": "matched", + "synopsis": "A list of headers that are permitted to be included in cross-origin requests to access a bucket.", + "suggestedPowerShellType": "[string[]]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", "target": { - "file": "Public/Replication/New-PfbFileSystemReplicaLink.ps1", + "file": "Public/Bucket/New-PfbBucketCorsPolicyRule.ps1", "paramBlockLine": 85, - "payloadVariable": "body", - "assignmentStyle": "unknown", - "hasAttributes": false + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true } }, { - "name": "link_type", - "type": "string", + "name": "allowed_methods", + "type": "array", "format": null, "specRequired": false, - "synopsis": "Type of the replica link.", - "suggestedPowerShellType": "[string]", + "synopsis": "A list of HTTP methods that are permitted for cross-origin requests to access a bucket.", + "suggestedPowerShellType": "[string[]]", "enumValues": [], "enumStatus": "no-spec-enum-found", "target": { - "file": "Public/Replication/New-PfbFileSystemReplicaLink.ps1", + "file": "Public/Bucket/New-PfbBucketCorsPolicyRule.ps1", "paramBlockLine": 85, - "payloadVariable": "body", - "assignmentStyle": "unknown", - "hasAttributes": false + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true } }, { - "name": "local_file_system", - "type": null, + "name": "allowed_origins", + "type": "array", "format": null, "specRequired": false, - "synopsis": "Reference to a local file system.", - "suggestedPowerShellType": "[object]", + "synopsis": "A list of origins (domains) that are permitted to make cross-origin requests to access a bucket.", + "suggestedPowerShellType": "[string[]]", "enumValues": [], "enumStatus": "no-spec-enum-found", "target": { - "file": "Public/Replication/New-PfbFileSystemReplicaLink.ps1", + "file": "Public/Bucket/New-PfbBucketCorsPolicyRule.ps1", "paramBlockLine": 85, - "payloadVariable": "body", - "assignmentStyle": "unknown", - "hasAttributes": false + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true } - }, + } + ], + "readOnlyFields": [], + "confidence": { + "level": "high", + "unresolvedParameters": [], + "escapeHatchOnly": [], + "caveat": "" + }, + "annotations": [] + }, + { + "endpoint": "POST /certificates", + "cmdlets": [ + "New-PfbCertificate" + ], + "missingQueryParameters": [], + "missingBodyProperties": [ { - "name": "policies", - "type": "array", + "name": "certificate", + "type": "string", "format": null, "specRequired": false, - "synopsis": null, - "suggestedPowerShellType": "[object[]]", + "synopsis": "The text of the certificate.", + "suggestedPowerShellType": "[string]", "enumValues": [], "enumStatus": "no-spec-enum-found", "target": { - "file": "Public/Replication/New-PfbFileSystemReplicaLink.ps1", - "paramBlockLine": 85, - "payloadVariable": "body", - "assignmentStyle": "unknown", - "hasAttributes": false + "file": "Public/Certificate/New-PfbCertificate.ps1", + "paramBlockLine": 36, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true } }, { - "name": "remote", - "type": null, + "name": "certificate_type", + "type": "string", "format": null, "specRequired": false, - "synopsis": "Reference to a remote array or realm.", - "suggestedPowerShellType": "[object]", + "synopsis": "The type of certificate.", + "suggestedPowerShellType": "[string]", + "enumValues": [ + "appliance", + "external" + ], + "enumStatus": "matched", + "target": { + "file": "Public/Certificate/New-PfbCertificate.ps1", + "paramBlockLine": 36, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true + } + }, + { + "name": "common_name", + "type": "string", + "format": null, + "specRequired": false, + "synopsis": "The common name field listed in the certificate.", + "suggestedPowerShellType": "[string]", "enumValues": [], "enumStatus": "no-spec-enum-found", "target": { - "file": "Public/Replication/New-PfbFileSystemReplicaLink.ps1", - "paramBlockLine": 85, - "payloadVariable": "body", - "assignmentStyle": "unknown", - "hasAttributes": false + "file": "Public/Certificate/New-PfbCertificate.ps1", + "paramBlockLine": 36, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true } }, { - "name": "remote_file_system", - "type": null, + "name": "country", + "type": "string", "format": null, "specRequired": false, - "synopsis": "Reference to a remote file system.", - "suggestedPowerShellType": "[object]", + "synopsis": "The country field listed in the certificate.", + "suggestedPowerShellType": "[string]", "enumValues": [], "enumStatus": "no-spec-enum-found", "target": { - "file": "Public/Replication/New-PfbFileSystemReplicaLink.ps1", - "paramBlockLine": 85, - "payloadVariable": "body", - "assignmentStyle": "unknown", + "file": "Public/Certificate/New-PfbCertificate.ps1", + "paramBlockLine": 36, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true + } + }, + { + "name": "days", + "type": "integer", + "format": "int32", + "specRequired": false, + "synopsis": "The number of days that the self-signed certificate is valid.", + "suggestedPowerShellType": "[int]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Certificate/New-PfbCertificate.ps1", + "paramBlockLine": 36, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true + } + }, + { + "name": "email", + "type": "string", + "format": null, + "specRequired": false, + "synopsis": "The email field listed in the certificate.", + "suggestedPowerShellType": "[string]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Certificate/New-PfbCertificate.ps1", + "paramBlockLine": 36, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true + } + }, + { + "name": "intermediate_certificate", + "type": "string", + "format": null, + "specRequired": false, + "synopsis": "Intermediate certificate chains.", + "suggestedPowerShellType": "[string]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Certificate/New-PfbCertificate.ps1", + "paramBlockLine": 36, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true + } + }, + { + "name": "key_algorithm", + "type": "string", + "format": null, + "specRequired": false, + "synopsis": "The key algorithm used to generate the certificate.", + "suggestedPowerShellType": "[string]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Certificate/New-PfbCertificate.ps1", + "paramBlockLine": 36, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true + } + }, + { + "name": "key_size", + "type": "integer", + "format": "int32", + "specRequired": false, + "synopsis": "The size (in bits) of the private key for the certificate.", + "suggestedPowerShellType": "[int]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Certificate/New-PfbCertificate.ps1", + "paramBlockLine": 36, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true + } + }, + { + "name": "locality", + "type": "string", + "format": null, + "specRequired": false, + "synopsis": "The locality field listed in the certificate.", + "suggestedPowerShellType": "[string]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Certificate/New-PfbCertificate.ps1", + "paramBlockLine": 36, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true + } + }, + { + "name": "organization", + "type": "string", + "format": null, + "specRequired": false, + "synopsis": "The organization field listed in the certificate.", + "suggestedPowerShellType": "[string]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Certificate/New-PfbCertificate.ps1", + "paramBlockLine": 36, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true + } + }, + { + "name": "organizational_unit", + "type": "string", + "format": null, + "specRequired": false, + "synopsis": "The organizational unit field listed in the certificate.", + "suggestedPowerShellType": "[string]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Certificate/New-PfbCertificate.ps1", + "paramBlockLine": 36, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true + } + }, + { + "name": "passphrase", + "type": "string", + "format": null, + "specRequired": false, + "synopsis": "The passphrase used to encrypt `private_key`.", + "suggestedPowerShellType": "[string]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Certificate/New-PfbCertificate.ps1", + "paramBlockLine": 36, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true + } + }, + { + "name": "private_key", + "type": "string", + "format": null, + "specRequired": false, + "synopsis": "The text of the private key.", + "suggestedPowerShellType": "[string]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Certificate/New-PfbCertificate.ps1", + "paramBlockLine": 36, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true + } + }, + { + "name": "state", + "type": "string", + "format": null, + "specRequired": false, + "synopsis": "The state/province field listed in the certificate.", + "suggestedPowerShellType": "[string]", + "enumValues": [], + "enumStatus": "not-found-in-resource", + "target": { + "file": "Public/Certificate/New-PfbCertificate.ps1", + "paramBlockLine": 36, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true + } + }, + { + "name": "subject_alternative_names", + "type": "array", + "format": null, + "specRequired": false, + "synopsis": "The alternative names that are secured by this certificate.", + "suggestedPowerShellType": "[string[]]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Certificate/New-PfbCertificate.ps1", + "paramBlockLine": 36, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true + } + } + ], + "readOnlyFields": [ + "issued_by", + "issued_to", + "realms", + "status", + "valid_from", + "valid_to" + ], + "confidence": { + "level": "high", + "unresolvedParameters": [], + "escapeHatchOnly": [], + "caveat": "" + }, + "annotations": [] + }, + { + "endpoint": "POST /certificates/certificate-signing-requests", + "cmdlets": [ + "New-PfbCertificateSigningRequest" + ], + "missingQueryParameters": [], + "missingBodyProperties": [ + { + "name": "certificate", + "type": null, + "format": null, + "specRequired": false, + "synopsis": "The certificate object whose private key will be used for generating the CSR, and whose certificate will be overwritten if the CSR is signed and imported back into FlashBlade.", + "suggestedPowerShellType": "[object]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Certificate/New-PfbCertificateSigningRequest.ps1", + "paramBlockLine": 31, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true + } + }, + { + "name": "common_name", + "type": "string", + "format": null, + "specRequired": false, + "synopsis": "The common name field to be listed in the certificate.", + "suggestedPowerShellType": "[string]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Certificate/New-PfbCertificateSigningRequest.ps1", + "paramBlockLine": 31, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true + } + }, + { + "name": "country", + "type": "string", + "format": null, + "specRequired": false, + "synopsis": "Two-letter country (ISO) code to be listed in the certificate.", + "suggestedPowerShellType": "[string]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Certificate/New-PfbCertificateSigningRequest.ps1", + "paramBlockLine": 31, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true + } + }, + { + "name": "email", + "type": "string", + "format": null, + "specRequired": false, + "synopsis": "The email field to be listed in the certificate.", + "suggestedPowerShellType": "[string]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Certificate/New-PfbCertificateSigningRequest.ps1", + "paramBlockLine": 31, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true + } + }, + { + "name": "locality", + "type": "string", + "format": null, + "specRequired": false, + "synopsis": "The locality field to be listed in the certificate.", + "suggestedPowerShellType": "[string]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Certificate/New-PfbCertificateSigningRequest.ps1", + "paramBlockLine": 31, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true + } + }, + { + "name": "organization", + "type": "string", + "format": null, + "specRequired": false, + "synopsis": "The organization field to be listed in the certificate.", + "suggestedPowerShellType": "[string]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Certificate/New-PfbCertificateSigningRequest.ps1", + "paramBlockLine": 31, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true + } + }, + { + "name": "organizational_unit", + "type": "string", + "format": null, + "specRequired": false, + "synopsis": "The organizational unit field to be listed in the certificate.", + "suggestedPowerShellType": "[string]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Certificate/New-PfbCertificateSigningRequest.ps1", + "paramBlockLine": 31, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true + } + }, + { + "name": "state", + "type": "string", + "format": null, + "specRequired": false, + "synopsis": "The state/province field to be listed in the certificate.", + "suggestedPowerShellType": "[string]", + "enumValues": [], + "enumStatus": "not-found-in-resource", + "target": { + "file": "Public/Certificate/New-PfbCertificateSigningRequest.ps1", + "paramBlockLine": 31, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true + } + }, + { + "name": "subject_alternative_names", + "type": "array", + "format": null, + "specRequired": false, + "synopsis": "The alternative names that are secured by this certificate.", + "suggestedPowerShellType": "[string[]]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Certificate/New-PfbCertificateSigningRequest.ps1", + "paramBlockLine": 31, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true + } + } + ], + "readOnlyFields": [], + "confidence": { + "level": "high", + "unresolvedParameters": [], + "escapeHatchOnly": [], + "caveat": "" + }, + "annotations": [] + }, + { + "endpoint": "POST /data-eviction-policies", + "cmdlets": [ + "New-PfbDataEvictionPolicy" + ], + "missingQueryParameters": [], + "missingBodyProperties": [ + "enabled", + "location", + "name" + ], + "readOnlyFields": [ + "id", + "is_local", + "policy_type", + "realms" + ], + "confidence": { + "level": "partial", + "unresolvedParameters": [ + { + "parameter": "Disabled", + "surface": "TypedUnresolved", + "file": "Public/DataEviction/New-PfbDataEvictionPolicy.ps1", + "line": 31 + } + ], + "escapeHatchOnly": [], + "caveat": "one or more parameters could not be traced to a wire name and have no -Attributes escape hatch; lists reflect typed-parameter coverage only, not full wire reachability" + }, + "annotations": [] + }, + { + "endpoint": "POST /directory-services/local/groups", + "cmdlets": [ + "New-PfbLocalGroup" + ], + "missingQueryParameters": [ + "local_directory_service_ids", + "local_directory_service_names" + ], + "missingBodyProperties": [], + "readOnlyFields": [], + "confidence": { + "level": "high", + "unresolvedParameters": [], + "escapeHatchOnly": [], + "caveat": "" + }, + "annotations": [] + }, + { + "endpoint": "POST /directory-services/local/groups/members", + "cmdlets": [ + "New-PfbLocalGroupMember" + ], + "missingQueryParameters": [ + "group_gids", + "group_sids", + "local_directory_service_ids" + ], + "missingBodyProperties": [ + "members" + ], + "readOnlyFields": [], + "confidence": { + "level": "partial", + "unresolvedParameters": [ + { + "parameter": "Member", + "surface": "TypedUnresolved", + "file": "Public/DirectoryService/New-PfbLocalGroupMember.ps1", + "line": 35 + } + ], + "escapeHatchOnly": [], + "caveat": "one or more parameters could not be traced to a wire name and have no -Attributes escape hatch; lists reflect typed-parameter coverage only, not full wire reachability" + }, + "annotations": [] + }, + { + "endpoint": "POST /directory-services/roles", + "cmdlets": [ + "New-PfbDirectoryServiceRole" + ], + "missingQueryParameters": [], + "missingBodyProperties": [ + { + "name": "group", + "type": "string", + "format": null, + "specRequired": false, + "synopsis": "Common Name (CN) of the directory service group containing users with authority level of the specified role name.", + "suggestedPowerShellType": "[string]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/DirectoryService/New-PfbDirectoryServiceRole.ps1", + "paramBlockLine": 33, + "payloadVariable": "body", + "assignmentStyle": "unknown", + "hasAttributes": true + } + }, + { + "name": "group_base", + "type": "string", + "format": null, + "specRequired": false, + "synopsis": "Specifies where the configured group is located in the directory tree.", + "suggestedPowerShellType": "[string]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/DirectoryService/New-PfbDirectoryServiceRole.ps1", + "paramBlockLine": 33, + "payloadVariable": "body", + "assignmentStyle": "unknown", + "hasAttributes": true + } + }, + { + "name": "management_access_policies", + "type": "array", + "format": null, + "specRequired": false, + "synopsis": "List of management access policies associated with the directory service role.", + "suggestedPowerShellType": "[object[]]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/DirectoryService/New-PfbDirectoryServiceRole.ps1", + "paramBlockLine": 33, + "payloadVariable": "body", + "assignmentStyle": "unknown", + "hasAttributes": true + } + }, + { + "name": "role", + "type": null, + "format": null, + "specRequired": false, + "synopsis": "Deprecated.", + "suggestedPowerShellType": "[object]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/DirectoryService/New-PfbDirectoryServiceRole.ps1", + "paramBlockLine": 33, + "payloadVariable": "body", + "assignmentStyle": "unknown", + "hasAttributes": true + } + } + ], + "readOnlyFields": [], + "confidence": { + "level": "high", + "unresolvedParameters": [], + "escapeHatchOnly": [], + "caveat": "" + }, + "annotations": [] + }, + { + "endpoint": "POST /dns", + "cmdlets": [ + "New-PfbDns" + ], + "missingQueryParameters": [], + "missingBodyProperties": [ + { + "name": "ca_certificate", + "type": null, + "format": null, + "specRequired": false, + "synopsis": "A reference to the `certificate` to use for validating nameservers with https connections.", + "suggestedPowerShellType": "[object]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Network/New-PfbDns.ps1", + "paramBlockLine": 31, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true + } + }, + { + "name": "ca_certificate_group", + "type": null, + "format": null, + "specRequired": false, + "synopsis": "A reference to the `certificate group` to use for validating nameservers with https connections.", + "suggestedPowerShellType": "[object]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Network/New-PfbDns.ps1", + "paramBlockLine": 31, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true + } + }, + { + "name": "domain", + "type": "string", + "format": null, + "specRequired": false, + "synopsis": "Domain suffix to be appended by the appliance when performing DNS lookups.", + "suggestedPowerShellType": "[string]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Network/New-PfbDns.ps1", + "paramBlockLine": 31, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true + } + }, + { + "name": "nameservers", + "type": "array", + "format": null, + "specRequired": false, + "synopsis": "The list of DNS servers either in form of IP addresses or https endpoints.", + "suggestedPowerShellType": "[string[]]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Network/New-PfbDns.ps1", + "paramBlockLine": 31, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true + } + }, + { + "name": "services", + "type": "array", + "format": null, + "specRequired": false, + "synopsis": "The list of services utilizing the DNS configuration.", + "suggestedPowerShellType": "[string[]]", + "enumValues": [], + "enumStatus": "not-found-in-resource", + "target": { + "file": "Public/Network/New-PfbDns.ps1", + "paramBlockLine": 31, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true + } + }, + { + "name": "sources", + "type": "array", + "format": null, + "specRequired": false, + "synopsis": "The network interfaces used for communication with the DNS server.", + "suggestedPowerShellType": "[object[]]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Network/New-PfbDns.ps1", + "paramBlockLine": 31, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true + } + } + ], + "readOnlyFields": [], + "confidence": { + "level": "high", + "unresolvedParameters": [], + "escapeHatchOnly": [], + "caveat": "" + }, + "annotations": [] + }, + { + "endpoint": "POST /file-system-exports", + "cmdlets": [ + "New-PfbFileSystemExport" + ], + "missingQueryParameters": [ + "member_ids", + "policy_ids" + ], + "missingBodyProperties": [], + "readOnlyFields": [], + "confidence": { + "level": "high", + "unresolvedParameters": [], + "escapeHatchOnly": [], + "caveat": "" + }, + "annotations": [] + }, + { + "endpoint": "POST /file-system-replica-links", + "cmdlets": [ + "New-PfbFileSystemReplicaLink" + ], + "missingQueryParameters": [ + "local_file_system_ids" + ], + "missingBodyProperties": [ + { + "name": "direction", + "type": null, + "format": null, + "specRequired": false, + "synopsis": null, + "suggestedPowerShellType": "[object]", + "enumValues": [ + "inbound", + "outbound" + ], + "enumStatus": "matched", + "target": { + "file": "Public/Replication/New-PfbFileSystemReplicaLink.ps1", + "paramBlockLine": 85, + "payloadVariable": "body", + "assignmentStyle": "unknown", + "hasAttributes": false + } + }, + { + "name": "link_type", + "type": "string", + "format": null, + "specRequired": false, + "synopsis": "Type of the replica link.", + "suggestedPowerShellType": "[string]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Replication/New-PfbFileSystemReplicaLink.ps1", + "paramBlockLine": 85, + "payloadVariable": "body", + "assignmentStyle": "unknown", + "hasAttributes": false + } + }, + { + "name": "local_file_system", + "type": null, + "format": null, + "specRequired": false, + "synopsis": "Reference to a local file system.", + "suggestedPowerShellType": "[object]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Replication/New-PfbFileSystemReplicaLink.ps1", + "paramBlockLine": 85, + "payloadVariable": "body", + "assignmentStyle": "unknown", + "hasAttributes": false + } + }, + { + "name": "policies", + "type": "array", + "format": null, + "specRequired": false, + "synopsis": null, + "suggestedPowerShellType": "[object[]]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Replication/New-PfbFileSystemReplicaLink.ps1", + "paramBlockLine": 85, + "payloadVariable": "body", + "assignmentStyle": "unknown", + "hasAttributes": false + } + }, + { + "name": "remote", + "type": null, + "format": null, + "specRequired": false, + "synopsis": "Reference to a remote array or realm.", + "suggestedPowerShellType": "[object]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Replication/New-PfbFileSystemReplicaLink.ps1", + "paramBlockLine": 85, + "payloadVariable": "body", + "assignmentStyle": "unknown", + "hasAttributes": false + } + }, + { + "name": "remote_file_system", + "type": null, + "format": null, + "specRequired": false, + "synopsis": "Reference to a remote file system.", + "suggestedPowerShellType": "[object]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Replication/New-PfbFileSystemReplicaLink.ps1", + "paramBlockLine": 85, + "payloadVariable": "body", + "assignmentStyle": "unknown", "hasAttributes": false } } ], "readOnlyFields": [ - "context", + "context", + "id", + "lag", + "recovery_point", + "status", + "status_details" + ], + "confidence": { + "level": "high", + "unresolvedParameters": [], + "escapeHatchOnly": [], + "caveat": "" + }, + "annotations": [] + }, + { + "endpoint": "POST /file-system-snapshots", + "cmdlets": [ + "New-PfbFileSystemSnapshot" + ], + "missingQueryParameters": [ + "source_ids", + "source_names" + ], + "missingBodyProperties": [], + "readOnlyFields": [], + "confidence": { + "level": "partial", + "unresolvedParameters": [ + { + "parameter": "SourceName", + "surface": "TypedUnresolved", + "file": "Public/FileSystemSnapshot/New-PfbFileSystemSnapshot.ps1", + "line": 36 + } + ], + "escapeHatchOnly": [], + "caveat": "one or more parameters could not be traced to a wire name and have no -Attributes escape hatch; lists reflect typed-parameter coverage only, not full wire reachability" + }, + "annotations": [] + }, + { + "endpoint": "POST /file-systems", + "cmdlets": [ + "New-PfbFileSystem" + ], + "missingQueryParameters": [ + "default_exports", + "discard_non_snapshotted_data", + "include_snapshot", + "overwrite", + "policy_ids", + "policy_names" + ], + "missingBodyProperties": [ + "eradication_config", + "hard_limit_enabled", + "http", + "multi_protocol", + "nfs", + "node_group", + "smb", + "snapshot_directory_enabled", + "workload" + ], + "readOnlyFields": [ + "requested_promotion_state" + ], + "confidence": { + "level": "partial", + "unresolvedParameters": [ + { + "parameter": "DefaultExports", + "surface": "AttributesOnly", + "file": "Public/FileSystem/New-PfbFileSystem.ps1", + "line": 208 + }, + { + "parameter": "HardLimit", + "surface": "AttributesOnly", + "file": "Public/FileSystem/New-PfbFileSystem.ps1", + "line": 139 + }, + { + "parameter": "Http", + "surface": "AttributesOnly", + "file": "Public/FileSystem/New-PfbFileSystem.ps1", + "line": 175 + }, + { + "parameter": "MultiProtocolAccessControlStyle", + "surface": "AttributesOnly", + "file": "Public/FileSystem/New-PfbFileSystem.ps1", + "line": 178 + }, + { + "parameter": "Nfs", + "surface": "AttributesOnly", + "file": "Public/FileSystem/New-PfbFileSystem.ps1", + "line": 148 + }, + { + "parameter": "NfsExportPolicy", + "surface": "AttributesOnly", + "file": "Public/FileSystem/New-PfbFileSystem.ps1", + "line": 160 + }, + { + "parameter": "NfsRules", + "surface": "AttributesOnly", + "file": "Public/FileSystem/New-PfbFileSystem.ps1", + "line": 157 + }, + { + "parameter": "NfsV3", + "surface": "AttributesOnly", + "file": "Public/FileSystem/New-PfbFileSystem.ps1", + "line": 151 + }, + { + "parameter": "NfsV41", + "surface": "AttributesOnly", + "file": "Public/FileSystem/New-PfbFileSystem.ps1", + "line": 154 + }, + { + "parameter": "SafeguardAcls", + "surface": "AttributesOnly", + "file": "Public/FileSystem/New-PfbFileSystem.ps1", + "line": 182 + }, + { + "parameter": "Smb", + "surface": "AttributesOnly", + "file": "Public/FileSystem/New-PfbFileSystem.ps1", + "line": 163 + }, + { + "parameter": "SmbClientPolicy", + "surface": "AttributesOnly", + "file": "Public/FileSystem/New-PfbFileSystem.ps1", + "line": 169 + }, + { + "parameter": "SmbContinuousAvailabilityEnabled", + "surface": "AttributesOnly", + "file": "Public/FileSystem/New-PfbFileSystem.ps1", + "line": 172 + }, + { + "parameter": "SmbSharePolicy", + "surface": "AttributesOnly", + "file": "Public/FileSystem/New-PfbFileSystem.ps1", + "line": 166 + }, + { + "parameter": "SnapshotDirectoryEnabled", + "surface": "AttributesOnly", + "file": "Public/FileSystem/New-PfbFileSystem.ps1", + "line": 185 + } + ], + "escapeHatchOnly": [ + "DefaultExports", + "HardLimit", + "Http", + "MultiProtocolAccessControlStyle", + "Nfs", + "NfsExportPolicy", + "NfsRules", + "NfsV3", + "NfsV41", + "SafeguardAcls", + "Smb", + "SmbClientPolicy", + "SmbContinuousAvailabilityEnabled", + "SmbSharePolicy", + "SnapshotDirectoryEnabled" + ], + "caveat": "body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability" + }, + "annotations": [] + }, + { + "endpoint": "POST /keytabs", + "cmdlets": [ + "New-PfbKeytab" + ], + "missingQueryParameters": [ + "name_prefixes" + ], + "missingBodyProperties": [ + { + "name": "source", + "type": null, + "format": null, + "specRequired": false, + "synopsis": "A reference to the Active Directory configuration for the computer account whose keys will be rotated in order to create new keytabs for all of its registered service principal names.", + "suggestedPowerShellType": "[object]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Misc/New-PfbKeytab.ps1", + "paramBlockLine": 26, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true + } + } + ], + "readOnlyFields": [], + "confidence": { + "level": "high", + "unresolvedParameters": [], + "escapeHatchOnly": [], + "caveat": "" + }, + "annotations": [] + }, + { + "endpoint": "POST /keytabs/upload", + "cmdlets": [ + "New-PfbKeytabUpload" + ], + "missingQueryParameters": [ + "name_prefixes" + ], + "missingBodyProperties": [ + { + "name": "keytab_file", + "type": null, + "format": null, + "specRequired": true, + "synopsis": null, + "suggestedPowerShellType": "[object]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Misc/New-PfbKeytabUpload.ps1", + "paramBlockLine": 28, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true + } + } + ], + "readOnlyFields": [], + "confidence": { + "level": "high", + "unresolvedParameters": [], + "escapeHatchOnly": [], + "caveat": "" + }, + "annotations": [] + }, + { + "endpoint": "POST /legal-holds", + "cmdlets": [ + "New-PfbLegalHold" + ], + "missingQueryParameters": [], + "missingBodyProperties": [ + { + "name": "description", + "type": "string", + "format": null, + "specRequired": false, + "synopsis": "The description of the legal hold instance.", + "suggestedPowerShellType": "[string]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Misc/New-PfbLegalHold.ps1", + "paramBlockLine": 36, + "payloadVariable": "body", + "assignmentStyle": "unknown", + "hasAttributes": true + } + } + ], + "readOnlyFields": [ + "id", + "name", + "realms" + ], + "confidence": { + "level": "high", + "unresolvedParameters": [], + "escapeHatchOnly": [], + "caveat": "" + }, + "annotations": [] + }, + { + "endpoint": "POST /lifecycle-rules", + "cmdlets": [ + "New-PfbLifecycleRule" + ], + "missingQueryParameters": [ + "confirm_date" + ], + "missingBodyProperties": [ + { + "name": "abort_incomplete_multipart_uploads_after", + "type": "integer", + "format": "int64", + "specRequired": false, + "synopsis": "Duration of time after which incomplete multipart uploads will be aborted.", + "suggestedPowerShellType": "[long]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Misc/New-PfbLifecycleRule.ps1", + "paramBlockLine": 33, + "payloadVariable": "body", + "assignmentStyle": "index", + "hasAttributes": true + } + }, + { + "name": "keep_current_version_for", + "type": "integer", + "format": "int64", + "specRequired": false, + "synopsis": "Time after which current versions will be marked expired.", + "suggestedPowerShellType": "[long]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Misc/New-PfbLifecycleRule.ps1", + "paramBlockLine": 33, + "payloadVariable": "body", + "assignmentStyle": "index", + "hasAttributes": true + } + }, + { + "name": "keep_current_version_until", + "type": "integer", + "format": "int64", + "specRequired": false, + "synopsis": "Time after which current versions will be marked expired.", + "suggestedPowerShellType": "[long]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Misc/New-PfbLifecycleRule.ps1", + "paramBlockLine": 33, + "payloadVariable": "body", + "assignmentStyle": "index", + "hasAttributes": true + } + }, + { + "name": "keep_previous_version_for", + "type": "integer", + "format": "int64", + "specRequired": false, + "synopsis": "Time after which previous versions will be marked expired.", + "suggestedPowerShellType": "[long]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Misc/New-PfbLifecycleRule.ps1", + "paramBlockLine": 33, + "payloadVariable": "body", + "assignmentStyle": "index", + "hasAttributes": true + } + }, + { + "name": "prefix", + "type": "string", + "format": null, + "specRequired": false, + "synopsis": "Object key prefix identifying one or more objects in the bucket.", + "suggestedPowerShellType": "[string]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Misc/New-PfbLifecycleRule.ps1", + "paramBlockLine": 33, + "payloadVariable": "body", + "assignmentStyle": "index", + "hasAttributes": true + } + }, + { + "name": "rule_id", + "type": "string", + "format": null, + "specRequired": false, + "synopsis": "Identifier for the rule that is unique to the bucket that it applies to.", + "suggestedPowerShellType": "[string]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Misc/New-PfbLifecycleRule.ps1", + "paramBlockLine": 33, + "payloadVariable": "body", + "assignmentStyle": "index", + "hasAttributes": true + } + } + ], + "readOnlyFields": [], + "confidence": { + "level": "high", + "unresolvedParameters": [], + "escapeHatchOnly": [], + "caveat": "" + }, + "annotations": [] + }, + { + "endpoint": "POST /link-aggregation-groups", + "cmdlets": [ + "New-PfbLag" + ], + "missingQueryParameters": [], + "missingBodyProperties": [ + { + "name": "ports", + "type": "array", + "format": null, + "specRequired": false, + "synopsis": "Ports associated with the LAG.", + "suggestedPowerShellType": "[object[]]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Misc/New-PfbLag.ps1", + "paramBlockLine": 31, + "payloadVariable": "body", + "assignmentStyle": "unknown", + "hasAttributes": true + } + } + ], + "readOnlyFields": [ + "id", + "lag_speed", + "mac_address", + "name", + "port_speed", + "status" + ], + "confidence": { + "level": "high", + "unresolvedParameters": [], + "escapeHatchOnly": [], + "caveat": "" + }, + "annotations": [] + }, + { + "endpoint": "POST /log-targets/file-systems", + "cmdlets": [ + "New-PfbLogTargetFileSystem" + ], + "missingQueryParameters": [], + "missingBodyProperties": [ + { + "name": "file_system", + "type": null, + "format": null, + "specRequired": false, + "synopsis": "The target filesystem where audit logs will be stored.", + "suggestedPowerShellType": "[object]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Monitoring/New-PfbLogTargetFileSystem.ps1", + "paramBlockLine": 44, + "payloadVariable": "body", + "assignmentStyle": "unknown", + "hasAttributes": true + } + }, + { + "name": "keep_for", + "type": "integer", + "format": "int64", + "specRequired": false, + "synopsis": "Specifies the period that audit logs are retained before they are deleted, in milliseconds.", + "suggestedPowerShellType": "[long]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Monitoring/New-PfbLogTargetFileSystem.ps1", + "paramBlockLine": 44, + "payloadVariable": "body", + "assignmentStyle": "unknown", + "hasAttributes": true + } + }, + { + "name": "keep_size", + "type": "integer", + "format": "int64", + "specRequired": false, + "synopsis": "Specifies the maximum size of audit logs to be retained.", + "suggestedPowerShellType": "[long]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Monitoring/New-PfbLogTargetFileSystem.ps1", + "paramBlockLine": 44, + "payloadVariable": "body", + "assignmentStyle": "unknown", + "hasAttributes": true + } + }, + { + "name": "name", + "type": "string", + "format": null, + "specRequired": false, + "synopsis": "A user-specified name.", + "suggestedPowerShellType": "[string]", + "enumValues": [], + "enumStatus": "not-found-in-resource", + "target": { + "file": "Public/Monitoring/New-PfbLogTargetFileSystem.ps1", + "paramBlockLine": 44, + "payloadVariable": "body", + "assignmentStyle": "unknown", + "hasAttributes": true + } + } + ], + "readOnlyFields": [ + "id" + ], + "confidence": { + "level": "high", + "unresolvedParameters": [], + "escapeHatchOnly": [], + "caveat": "" + }, + "annotations": [] + }, + { + "endpoint": "POST /log-targets/object-store", + "cmdlets": [ + "New-PfbLogTargetObjectStore" + ], + "missingQueryParameters": [], + "missingBodyProperties": [ + { + "name": "bucket", + "type": null, + "format": null, + "specRequired": false, + "synopsis": "Reference to the bucket where audit logs will be stored.", + "suggestedPowerShellType": "[object]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Monitoring/New-PfbLogTargetObjectStore.ps1", + "paramBlockLine": 36, + "payloadVariable": "body", + "assignmentStyle": "unknown", + "hasAttributes": true + } + }, + { + "name": "log_name_prefix", + "type": null, + "format": null, + "specRequired": false, + "synopsis": "The prefix of the audit log object.", + "suggestedPowerShellType": "[object]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Monitoring/New-PfbLogTargetObjectStore.ps1", + "paramBlockLine": 36, + "payloadVariable": "body", + "assignmentStyle": "unknown", + "hasAttributes": true + } + }, + { + "name": "log_rotate", + "type": null, + "format": null, + "specRequired": false, + "synopsis": "The threshold after which the audit log object will be rotated.", + "suggestedPowerShellType": "[object]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Monitoring/New-PfbLogTargetObjectStore.ps1", + "paramBlockLine": 36, + "payloadVariable": "body", + "assignmentStyle": "unknown", + "hasAttributes": true + } + }, + { + "name": "name", + "type": "string", + "format": null, + "specRequired": false, + "synopsis": "A user-specified name.", + "suggestedPowerShellType": "[string]", + "enumValues": [], + "enumStatus": "not-found-in-resource", + "target": { + "file": "Public/Monitoring/New-PfbLogTargetObjectStore.ps1", + "paramBlockLine": 36, + "payloadVariable": "body", + "assignmentStyle": "unknown", + "hasAttributes": true + } + } + ], + "readOnlyFields": [ + "id" + ], + "confidence": { + "level": "high", + "unresolvedParameters": [], + "escapeHatchOnly": [], + "caveat": "" + }, + "annotations": [] + }, + { + "endpoint": "POST /maintenance-windows", + "cmdlets": [ + "New-PfbMaintenanceWindow" + ], + "missingQueryParameters": [ + "names" + ], + "missingBodyProperties": [ + { + "name": "timeout", + "type": "integer", + "format": "int32", + "specRequired": false, + "synopsis": "Duration of a maintenance window measured in milliseconds.", + "suggestedPowerShellType": "[int]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Misc/New-PfbMaintenanceWindow.ps1", + "paramBlockLine": 28, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true + } + } + ], + "readOnlyFields": [], + "confidence": { + "level": "high", + "unresolvedParameters": [], + "escapeHatchOnly": [], + "caveat": "" + }, + "annotations": [] + }, + { + "endpoint": "POST /management-access-policies", + "cmdlets": [ + "New-PfbManagementAccessPolicy" + ], + "missingQueryParameters": [], + "missingBodyProperties": [ + { + "name": "aggregation_strategy", + "type": "string", + "format": null, + "specRequired": false, + "synopsis": "When this is set to `least-common-permissions`, any users to whom this policy applies can receive no access rights exceeding those defined in this policy's capability and resource.", + "suggestedPowerShellType": "[string]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Admin/New-PfbManagementAccessPolicy.ps1", + "paramBlockLine": 32, + "payloadVariable": "body", + "assignmentStyle": "unknown", + "hasAttributes": true + } + }, + { + "name": "enabled", + "type": "boolean", + "format": null, + "specRequired": false, + "synopsis": "If `true`, the policy is enabled.", + "suggestedPowerShellType": "[bool]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Admin/New-PfbManagementAccessPolicy.ps1", + "paramBlockLine": 32, + "payloadVariable": "body", + "assignmentStyle": "unknown", + "hasAttributes": true + } + }, + { + "name": "location", + "type": null, + "format": null, + "specRequired": false, + "synopsis": "Reference to the array where the policy is defined.", + "suggestedPowerShellType": "[object]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Admin/New-PfbManagementAccessPolicy.ps1", + "paramBlockLine": 32, + "payloadVariable": "body", + "assignmentStyle": "unknown", + "hasAttributes": true + } + }, + { + "name": "name", + "type": "string", + "format": null, + "specRequired": false, + "synopsis": "A user-specified name.", + "suggestedPowerShellType": "[string]", + "enumValues": [], + "enumStatus": "not-found-in-resource", + "target": { + "file": "Public/Admin/New-PfbManagementAccessPolicy.ps1", + "paramBlockLine": 32, + "payloadVariable": "body", + "assignmentStyle": "unknown", + "hasAttributes": true + } + }, + { + "name": "rules", + "type": "array", + "format": null, + "specRequired": false, + "synopsis": "All of the rules that are part of this policy.", + "suggestedPowerShellType": "[object[]]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Admin/New-PfbManagementAccessPolicy.ps1", + "paramBlockLine": 32, + "payloadVariable": "body", + "assignmentStyle": "unknown", + "hasAttributes": true + } + } + ], + "readOnlyFields": [ "id", - "lag", - "recovery_point", - "status", - "status_details" + "is_local", + "policy_type", + "realms" ], "confidence": { "level": "high", @@ -9114,247 +10999,65 @@ "escapeHatchOnly": [], "caveat": "" }, - "annotations": [] + "annotations": [ + { + "matchType": "endpoint", + "match": "management-access-policies", + "kind": "liveTestingHazard", + "note": "POST/PATCH/DELETE return 403 regardless of account; not an implementation bug", + "reference": null + } + ] }, { - "endpoint": "POST /file-system-snapshots", + "endpoint": "POST /network-access-policies/rules", "cmdlets": [ - "New-PfbFileSystemSnapshot" - ], - "missingQueryParameters": [ - "source_ids", - "source_names" + "New-PfbNetworkAccessRule" ], + "missingQueryParameters": [], "missingBodyProperties": [], - "readOnlyFields": [], - "confidence": { - "level": "partial", - "unresolvedParameters": [ - { - "parameter": "SourceName", - "surface": "TypedUnresolved", - "file": "Public/FileSystemSnapshot/New-PfbFileSystemSnapshot.ps1", - "line": 36 - } - ], - "escapeHatchOnly": [], - "caveat": "one or more parameters could not be traced to a wire name and have no -Attributes escape hatch; lists reflect typed-parameter coverage only, not full wire reachability" - }, - "annotations": [] - }, - { - "endpoint": "POST /file-systems", - "cmdlets": [ - "New-PfbFileSystem" - ], - "missingQueryParameters": [ - "default_exports", - "discard_non_snapshotted_data", - "include_snapshot", - "overwrite", - "policy_ids", - "policy_names" - ], - "missingBodyProperties": [ - "eradication_config", - "fast_remove_directory_enabled", - "hard_limit_enabled", - "http", - "multi_protocol", - "nfs", - "node_group", - "smb", - "snapshot_directory_enabled", - "workload", - "writable" - ], "readOnlyFields": [ - "requested_promotion_state" - ], - "confidence": { - "level": "partial", - "unresolvedParameters": [ - { - "parameter": "DefaultExports", - "surface": "AttributesOnly", - "file": "Public/FileSystem/New-PfbFileSystem.ps1", - "line": 208 - }, - { - "parameter": "FastRemoveDirectoryEnabled", - "surface": "AttributesOnly", - "file": "Public/FileSystem/New-PfbFileSystem.ps1", - "line": 188 - }, - { - "parameter": "HardLimit", - "surface": "AttributesOnly", - "file": "Public/FileSystem/New-PfbFileSystem.ps1", - "line": 139 - }, - { - "parameter": "Http", - "surface": "AttributesOnly", - "file": "Public/FileSystem/New-PfbFileSystem.ps1", - "line": 175 - }, - { - "parameter": "MultiProtocolAccessControlStyle", - "surface": "AttributesOnly", - "file": "Public/FileSystem/New-PfbFileSystem.ps1", - "line": 178 - }, - { - "parameter": "Nfs", - "surface": "AttributesOnly", - "file": "Public/FileSystem/New-PfbFileSystem.ps1", - "line": 148 - }, - { - "parameter": "NfsExportPolicy", - "surface": "AttributesOnly", - "file": "Public/FileSystem/New-PfbFileSystem.ps1", - "line": 160 - }, - { - "parameter": "NfsRules", - "surface": "AttributesOnly", - "file": "Public/FileSystem/New-PfbFileSystem.ps1", - "line": 157 - }, - { - "parameter": "NfsV3", - "surface": "AttributesOnly", - "file": "Public/FileSystem/New-PfbFileSystem.ps1", - "line": 151 - }, - { - "parameter": "NfsV41", - "surface": "AttributesOnly", - "file": "Public/FileSystem/New-PfbFileSystem.ps1", - "line": 154 - }, - { - "parameter": "SafeguardAcls", - "surface": "AttributesOnly", - "file": "Public/FileSystem/New-PfbFileSystem.ps1", - "line": 182 - }, - { - "parameter": "Smb", - "surface": "AttributesOnly", - "file": "Public/FileSystem/New-PfbFileSystem.ps1", - "line": 163 - }, - { - "parameter": "SmbClientPolicy", - "surface": "AttributesOnly", - "file": "Public/FileSystem/New-PfbFileSystem.ps1", - "line": 169 - }, - { - "parameter": "SmbContinuousAvailabilityEnabled", - "surface": "AttributesOnly", - "file": "Public/FileSystem/New-PfbFileSystem.ps1", - "line": 172 - }, - { - "parameter": "SmbSharePolicy", - "surface": "AttributesOnly", - "file": "Public/FileSystem/New-PfbFileSystem.ps1", - "line": 166 - }, - { - "parameter": "SnapshotDirectoryEnabled", - "surface": "AttributesOnly", - "file": "Public/FileSystem/New-PfbFileSystem.ps1", - "line": 185 - }, - { - "parameter": "Writable", - "surface": "AttributesOnly", - "file": "Public/FileSystem/New-PfbFileSystem.ps1", - "line": 195 - } - ], - "escapeHatchOnly": [ - "DefaultExports", - "FastRemoveDirectoryEnabled", - "HardLimit", - "Http", - "MultiProtocolAccessControlStyle", - "Nfs", - "NfsExportPolicy", - "NfsRules", - "NfsV3", - "NfsV41", - "SafeguardAcls", - "Smb", - "SmbClientPolicy", - "SmbContinuousAvailabilityEnabled", - "SmbSharePolicy", - "SnapshotDirectoryEnabled", - "Writable" - ], - "caveat": "body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability" - }, - "annotations": [] - }, - { - "endpoint": "POST /fleets", - "cmdlets": [ - "New-PfbFleet" - ], - "missingQueryParameters": [ - "names" - ], - "missingBodyProperties": [], - "readOnlyFields": [], + "id", + "name" + ], "confidence": { - "level": "partial", - "unresolvedParameters": [ - { - "parameter": "Name", - "surface": "AttributesOnly", - "file": "Public/Replication/New-PfbFleet.ps1", - "line": 30 - } - ], - "escapeHatchOnly": [ - "Name" - ], - "caveat": "body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability" + "level": "high", + "unresolvedParameters": [], + "escapeHatchOnly": [], + "caveat": "" }, "annotations": [] }, { - "endpoint": "POST /keytabs", + "endpoint": "POST /network-interfaces", "cmdlets": [ - "New-PfbKeytab" - ], - "missingQueryParameters": [ - "name_prefixes" + "New-PfbNetworkInterface" ], + "missingQueryParameters": [], "missingBodyProperties": [ { - "name": "source", - "type": null, + "name": "rdma_enabled", + "type": "boolean", "format": null, "specRequired": false, - "synopsis": "A reference to the Active Directory configuration for the computer account whose keys will be rotated in order to create new keytabs for all of its registered service principal names.", - "suggestedPowerShellType": "[object]", + "synopsis": "If `true` indicated that RDMA is enabled on the network interface.", + "suggestedPowerShellType": "[bool]", "enumValues": [], "enumStatus": "no-spec-enum-found", "target": { - "file": "Public/Misc/New-PfbKeytab.ps1", - "paramBlockLine": 26, - "payloadVariable": "Attributes", - "assignmentStyle": "attributesOnly", + "file": "Public/Network/New-PfbNetworkInterface.ps1", + "paramBlockLine": 66, + "payloadVariable": "body", + "assignmentStyle": "index", "hasAttributes": true } } ], - "readOnlyFields": [], + "readOnlyFields": [ + "id", + "name", + "realms" + ], "confidence": { "level": "high", "unresolvedParameters": [], @@ -9364,33 +11067,70 @@ "annotations": [] }, { - "endpoint": "POST /keytabs/upload", + "endpoint": "POST /nfs-export-policies", "cmdlets": [ - "New-PfbKeytabUpload" - ], - "missingQueryParameters": [ - "name_prefixes" + "New-PfbNfsExportPolicy" ], + "missingQueryParameters": [], "missingBodyProperties": [ { - "name": "keytab_file", + "name": "location", "type": null, "format": null, - "specRequired": true, - "synopsis": null, + "specRequired": false, + "synopsis": "Reference to the array where the policy is defined.", "suggestedPowerShellType": "[object]", "enumValues": [], "enumStatus": "no-spec-enum-found", "target": { - "file": "Public/Misc/New-PfbKeytabUpload.ps1", - "paramBlockLine": 28, - "payloadVariable": "Attributes", - "assignmentStyle": "attributesOnly", + "file": "Public/Policy/New-PfbNfsExportPolicy.ps1", + "paramBlockLine": 42, + "payloadVariable": "body", + "assignmentStyle": "index", + "hasAttributes": true + } + }, + { + "name": "name", + "type": "string", + "format": null, + "specRequired": false, + "synopsis": "A user-specified name.", + "suggestedPowerShellType": "[string]", + "enumValues": [], + "enumStatus": "not-found-in-resource", + "target": { + "file": "Public/Policy/New-PfbNfsExportPolicy.ps1", + "paramBlockLine": 42, + "payloadVariable": "body", + "assignmentStyle": "index", + "hasAttributes": true + } + }, + { + "name": "rules", + "type": "array", + "format": null, + "specRequired": false, + "synopsis": "All of the rules that are part of this policy.", + "suggestedPowerShellType": "[object[]]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Policy/New-PfbNfsExportPolicy.ps1", + "paramBlockLine": 42, + "payloadVariable": "body", + "assignmentStyle": "index", "hasAttributes": true } } ], - "readOnlyFields": [], + "readOnlyFields": [ + "id", + "is_local", + "policy_type", + "realms" + ], "confidence": { "level": "high", "unresolvedParameters": [], @@ -9400,35 +11140,54 @@ "annotations": [] }, { - "endpoint": "POST /legal-holds", + "endpoint": "POST /nfs-export-policies/rules", "cmdlets": [ - "New-PfbLegalHold" + "New-PfbNfsExportRule" ], "missingQueryParameters": [], + "missingBodyProperties": [], + "readOnlyFields": [ + "context", + "id", + "name", + "policy_version" + ], + "confidence": { + "level": "high", + "unresolvedParameters": [], + "escapeHatchOnly": [], + "caveat": "" + }, + "annotations": [] + }, + { + "endpoint": "POST /object-store-access-keys", + "cmdlets": [ + "New-PfbObjectStoreAccessKey" + ], + "missingQueryParameters": [ + "names" + ], "missingBodyProperties": [ { - "name": "description", + "name": "secret_access_key", "type": "string", "format": null, "specRequired": false, - "synopsis": "The description of the legal hold instance.", + "synopsis": "The secret access key to import from another FlashBlade.", "suggestedPowerShellType": "[string]", "enumValues": [], "enumStatus": "no-spec-enum-found", "target": { - "file": "Public/Misc/New-PfbLegalHold.ps1", - "paramBlockLine": 36, + "file": "Public/ObjectStore/New-PfbObjectStoreAccessKey.ps1", + "paramBlockLine": 17, "payloadVariable": "body", - "assignmentStyle": "unknown", - "hasAttributes": true + "assignmentStyle": "literal", + "hasAttributes": false } } ], - "readOnlyFields": [ - "id", - "name", - "realms" - ], + "readOnlyFields": [], "confidence": { "level": "high", "unresolvedParameters": [], @@ -9438,113 +11197,174 @@ "annotations": [] }, { - "endpoint": "POST /lifecycle-rules", + "endpoint": "POST /object-store-access-policies", "cmdlets": [ - "New-PfbLifecycleRule" + "New-PfbObjectStoreAccessPolicy" ], "missingQueryParameters": [ - "confirm_date" + "enforce_action_restrictions" ], "missingBodyProperties": [ { - "name": "abort_incomplete_multipart_uploads_after", - "type": "integer", - "format": "int64", + "name": "description", + "type": "string", + "format": null, "specRequired": false, - "synopsis": "Duration of time after which incomplete multipart uploads will be aborted.", - "suggestedPowerShellType": "[long]", + "synopsis": "A description of the policy, optionally specified when the policy is created.", + "suggestedPowerShellType": "[string]", "enumValues": [], "enumStatus": "no-spec-enum-found", "target": { - "file": "Public/Misc/New-PfbLifecycleRule.ps1", - "paramBlockLine": 33, + "file": "Public/ObjectStore/New-PfbObjectStoreAccessPolicy.ps1", + "paramBlockLine": 58, "payloadVariable": "body", - "assignmentStyle": "index", + "assignmentStyle": "unknown", "hasAttributes": true } }, { - "name": "keep_current_version_for", - "type": "integer", - "format": "int64", + "name": "rules", + "type": "array", + "format": null, "specRequired": false, - "synopsis": "Time after which current versions will be marked expired.", - "suggestedPowerShellType": "[long]", + "synopsis": null, + "suggestedPowerShellType": "[object[]]", "enumValues": [], "enumStatus": "no-spec-enum-found", "target": { - "file": "Public/Misc/New-PfbLifecycleRule.ps1", - "paramBlockLine": 33, + "file": "Public/ObjectStore/New-PfbObjectStoreAccessPolicy.ps1", + "paramBlockLine": 58, "payloadVariable": "body", - "assignmentStyle": "index", + "assignmentStyle": "unknown", "hasAttributes": true } - }, - { - "name": "keep_current_version_until", - "type": "integer", - "format": "int64", + } + ], + "readOnlyFields": [], + "confidence": { + "level": "high", + "unresolvedParameters": [], + "escapeHatchOnly": [], + "caveat": "" + }, + "annotations": [] + }, + { + "endpoint": "POST /object-store-access-policies/object-store-roles", + "cmdlets": [ + "New-PfbObjectStoreAccessPolicyRole" + ], + "missingQueryParameters": [ + "member_ids", + "policy_ids" + ], + "missingBodyProperties": [], + "readOnlyFields": [], + "confidence": { + "level": "high", + "unresolvedParameters": [], + "escapeHatchOnly": [], + "caveat": "" + }, + "annotations": [] + }, + { + "endpoint": "POST /object-store-access-policies/object-store-users", + "cmdlets": [ + "New-PfbObjectStoreAccessPolicyUser" + ], + "missingQueryParameters": [ + "member_ids", + "policy_ids" + ], + "missingBodyProperties": [], + "readOnlyFields": [], + "confidence": { + "level": "high", + "unresolvedParameters": [], + "escapeHatchOnly": [], + "caveat": "" + }, + "annotations": [] + }, + { + "endpoint": "POST /object-store-access-policies/rules", + "cmdlets": [ + "New-PfbObjectStoreAccessPolicyRule" + ], + "missingQueryParameters": [ + "enforce_action_restrictions", + "policy_ids" + ], + "missingBodyProperties": [ + { + "name": "actions", + "type": "array", + "format": null, "specRequired": false, - "synopsis": "Time after which current versions will be marked expired.", - "suggestedPowerShellType": "[long]", + "synopsis": "The list of actions granted by this rule.", + "suggestedPowerShellType": "[string[]]", "enumValues": [], - "enumStatus": "no-spec-enum-found", + "enumStatus": "not-found-in-resource", "target": { - "file": "Public/Misc/New-PfbLifecycleRule.ps1", - "paramBlockLine": 33, + "file": "Public/ObjectStore/New-PfbObjectStoreAccessPolicyRule.ps1", + "paramBlockLine": 51, "payloadVariable": "body", - "assignmentStyle": "index", + "assignmentStyle": "unknown", "hasAttributes": true } }, { - "name": "keep_previous_version_for", - "type": "integer", - "format": "int64", + "name": "conditions", + "type": null, + "format": null, "specRequired": false, - "synopsis": "Time after which previous versions will be marked expired.", - "suggestedPowerShellType": "[long]", + "synopsis": "Conditions used to limit the scope which this rule applies to.", + "suggestedPowerShellType": "[object]", "enumValues": [], "enumStatus": "no-spec-enum-found", "target": { - "file": "Public/Misc/New-PfbLifecycleRule.ps1", - "paramBlockLine": 33, + "file": "Public/ObjectStore/New-PfbObjectStoreAccessPolicyRule.ps1", + "paramBlockLine": 51, "payloadVariable": "body", - "assignmentStyle": "index", + "assignmentStyle": "unknown", "hasAttributes": true } }, { - "name": "prefix", + "name": "effect", "type": "string", "format": null, "specRequired": false, - "synopsis": "Object key prefix identifying one or more objects in the bucket.", + "synopsis": "Effect of this rule.", "suggestedPowerShellType": "[string]", - "enumValues": [], - "enumStatus": "no-spec-enum-found", + "enumValues": [ + "allow", + "deny" + ], + "enumStatus": "matched", "target": { - "file": "Public/Misc/New-PfbLifecycleRule.ps1", - "paramBlockLine": 33, + "file": "Public/ObjectStore/New-PfbObjectStoreAccessPolicyRule.ps1", + "paramBlockLine": 51, "payloadVariable": "body", - "assignmentStyle": "index", + "assignmentStyle": "unknown", "hasAttributes": true } }, { - "name": "rule_id", - "type": "string", + "name": "resources", + "type": "array", "format": null, "specRequired": false, - "synopsis": "Identifier for the rule that is unique to the bucket that it applies to.", - "suggestedPowerShellType": "[string]", + "synopsis": "The list of resources which this rule applies to.", + "suggestedPowerShellType": "[string[]]", "enumValues": [], "enumStatus": "no-spec-enum-found", "target": { - "file": "Public/Misc/New-PfbLifecycleRule.ps1", - "paramBlockLine": 33, + "file": "Public/ObjectStore/New-PfbObjectStoreAccessPolicyRule.ps1", + "paramBlockLine": 51, "payloadVariable": "body", - "assignmentStyle": "index", + "assignmentStyle": "unknown", "hasAttributes": true } } @@ -9559,120 +11379,82 @@ "annotations": [] }, { - "endpoint": "POST /link-aggregation-groups", - "cmdlets": [ - "New-PfbLag" - ], - "missingQueryParameters": [ - "names" - ], - "missingBodyProperties": [ - "ports" - ], - "readOnlyFields": [ - "id", - "lag_speed", - "mac_address", - "name", - "port_speed", - "status" - ], - "confidence": { - "level": "partial", - "unresolvedParameters": [ - { - "parameter": "Name", - "surface": "AttributesOnly", - "file": "Public/Misc/New-PfbLag.ps1", - "line": 29 - } - ], - "escapeHatchOnly": [ - "Name" - ], - "caveat": "body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability" - }, - "annotations": [] - }, - { - "endpoint": "POST /log-targets/file-systems", + "endpoint": "POST /object-store-accounts", "cmdlets": [ - "New-PfbLogTargetFileSystem" + "New-PfbObjectStoreAccount" ], "missingQueryParameters": [], "missingBodyProperties": [ { - "name": "file_system", - "type": null, + "name": "account_exports", + "type": "array", "format": null, "specRequired": false, - "synopsis": "The target filesystem where audit logs will be stored.", - "suggestedPowerShellType": "[object]", + "synopsis": "A list of exports to be created for the account.", + "suggestedPowerShellType": "[object[]]", "enumValues": [], "enumStatus": "no-spec-enum-found", "target": { - "file": "Public/Monitoring/New-PfbLogTargetFileSystem.ps1", - "paramBlockLine": 44, - "payloadVariable": "body", - "assignmentStyle": "unknown", - "hasAttributes": true + "file": "Public/ObjectStore/New-PfbObjectStoreAccount.ps1", + "paramBlockLine": 17, + "payloadVariable": null, + "assignmentStyle": null, + "hasAttributes": false } }, { - "name": "keep_for", - "type": "integer", - "format": "int64", + "name": "bucket_defaults", + "type": null, + "format": null, "specRequired": false, - "synopsis": "Specifies the period that audit logs are retained before they are deleted, in milliseconds.", - "suggestedPowerShellType": "[long]", + "synopsis": "Default settings to be applied to newly created buckets associated with this account.", + "suggestedPowerShellType": "[object]", "enumValues": [], "enumStatus": "no-spec-enum-found", "target": { - "file": "Public/Monitoring/New-PfbLogTargetFileSystem.ps1", - "paramBlockLine": 44, - "payloadVariable": "body", - "assignmentStyle": "unknown", - "hasAttributes": true + "file": "Public/ObjectStore/New-PfbObjectStoreAccount.ps1", + "paramBlockLine": 17, + "payloadVariable": null, + "assignmentStyle": null, + "hasAttributes": false } }, { - "name": "keep_size", - "type": "integer", - "format": "int64", + "name": "hard_limit_enabled", + "type": "boolean", + "format": null, "specRequired": false, - "synopsis": "Specifies the maximum size of audit logs to be retained.", - "suggestedPowerShellType": "[long]", + "synopsis": "If set to `true`, the account's size, as defined by `quota_limit`, is used as a hard limit quota.", + "suggestedPowerShellType": "[bool]", "enumValues": [], "enumStatus": "no-spec-enum-found", "target": { - "file": "Public/Monitoring/New-PfbLogTargetFileSystem.ps1", - "paramBlockLine": 44, - "payloadVariable": "body", - "assignmentStyle": "unknown", - "hasAttributes": true + "file": "Public/ObjectStore/New-PfbObjectStoreAccount.ps1", + "paramBlockLine": 17, + "payloadVariable": null, + "assignmentStyle": null, + "hasAttributes": false } }, { - "name": "name", + "name": "quota_limit", "type": "string", "format": null, "specRequired": false, - "synopsis": "A user-specified name.", + "synopsis": "The effective quota limit to be applied against the size of the account, displayed in bytes.", "suggestedPowerShellType": "[string]", "enumValues": [], - "enumStatus": "not-found-in-resource", + "enumStatus": "no-spec-enum-found", "target": { - "file": "Public/Monitoring/New-PfbLogTargetFileSystem.ps1", - "paramBlockLine": 44, - "payloadVariable": "body", - "assignmentStyle": "unknown", - "hasAttributes": true + "file": "Public/ObjectStore/New-PfbObjectStoreAccount.ps1", + "paramBlockLine": 17, + "payloadVariable": null, + "assignmentStyle": null, + "hasAttributes": false } } ], - "readOnlyFields": [ - "id" - ], + "readOnlyFields": [], "confidence": { "level": "high", "unresolvedParameters": [], @@ -9682,84 +11464,82 @@ "annotations": [] }, { - "endpoint": "POST /log-targets/object-store", + "endpoint": "POST /object-store-remote-credentials", "cmdlets": [ - "New-PfbLogTargetObjectStore" + "New-PfbObjectStoreRemoteCredential" ], "missingQueryParameters": [], "missingBodyProperties": [ { - "name": "bucket", - "type": null, + "name": "access_key_id", + "type": "string", "format": null, "specRequired": false, - "synopsis": "Reference to the bucket where audit logs will be stored.", - "suggestedPowerShellType": "[object]", + "synopsis": "Access Key ID to be used when connecting to a remote object store.", + "suggestedPowerShellType": "[string]", "enumValues": [], "enumStatus": "no-spec-enum-found", "target": { - "file": "Public/Monitoring/New-PfbLogTargetObjectStore.ps1", - "paramBlockLine": 36, + "file": "Public/ObjectStore/New-PfbObjectStoreRemoteCredential.ps1", + "paramBlockLine": 39, "payloadVariable": "body", "assignmentStyle": "unknown", "hasAttributes": true } }, { - "name": "log_name_prefix", - "type": null, + "name": "secret_access_key", + "type": "string", "format": null, "specRequired": false, - "synopsis": "The prefix of the audit log object.", - "suggestedPowerShellType": "[object]", + "synopsis": "Secret Access Key to be used when connecting to a remote object store.", + "suggestedPowerShellType": "[string]", "enumValues": [], "enumStatus": "no-spec-enum-found", "target": { - "file": "Public/Monitoring/New-PfbLogTargetObjectStore.ps1", - "paramBlockLine": 36, + "file": "Public/ObjectStore/New-PfbObjectStoreRemoteCredential.ps1", + "paramBlockLine": 39, "payloadVariable": "body", "assignmentStyle": "unknown", "hasAttributes": true } - }, + } + ], + "readOnlyFields": [], + "confidence": { + "level": "high", + "unresolvedParameters": [], + "escapeHatchOnly": [], + "caveat": "" + }, + "annotations": [] + }, + { + "endpoint": "POST /object-store-roles", + "cmdlets": [ + "New-PfbObjectStoreRole" + ], + "missingQueryParameters": [], + "missingBodyProperties": [ { - "name": "log_rotate", - "type": null, + "name": "max_session_duration", + "type": "integer", "format": null, "specRequired": false, - "synopsis": "The threshold after which the audit log object will be rotated.", - "suggestedPowerShellType": "[object]", + "synopsis": "Maximum session duration in milliseconds.", + "suggestedPowerShellType": "[int]", "enumValues": [], "enumStatus": "no-spec-enum-found", "target": { - "file": "Public/Monitoring/New-PfbLogTargetObjectStore.ps1", - "paramBlockLine": 36, - "payloadVariable": "body", - "assignmentStyle": "unknown", - "hasAttributes": true - } - }, - { - "name": "name", - "type": "string", - "format": null, - "specRequired": false, - "synopsis": "A user-specified name.", - "suggestedPowerShellType": "[string]", - "enumValues": [], - "enumStatus": "not-found-in-resource", - "target": { - "file": "Public/Monitoring/New-PfbLogTargetObjectStore.ps1", - "paramBlockLine": 36, + "file": "Public/ObjectStore/New-PfbObjectStoreRole.ps1", + "paramBlockLine": 37, "payloadVariable": "body", "assignmentStyle": "unknown", "hasAttributes": true } } ], - "readOnlyFields": [ - "id" - ], + "readOnlyFields": [], "confidence": { "level": "high", "unresolvedParameters": [], @@ -9769,32 +11549,16 @@ "annotations": [] }, { - "endpoint": "POST /maintenance-windows", + "endpoint": "POST /object-store-roles/object-store-access-policies", "cmdlets": [ - "New-PfbMaintenanceWindow" + "New-PfbObjectStoreRoleAccessPolicy" ], "missingQueryParameters": [ - "names" - ], - "missingBodyProperties": [ - { - "name": "timeout", - "type": "integer", - "format": "int32", - "specRequired": false, - "synopsis": "Duration of a maintenance window measured in milliseconds.", - "suggestedPowerShellType": "[int]", - "enumValues": [], - "enumStatus": "no-spec-enum-found", - "target": { - "file": "Public/Misc/New-PfbMaintenanceWindow.ps1", - "paramBlockLine": 28, - "payloadVariable": "Attributes", - "assignmentStyle": "attributesOnly", - "hasAttributes": true - } - } + "member_ids", + "policy_ids", + "policy_names" ], + "missingBodyProperties": [], "readOnlyFields": [], "confidence": { "level": "high", @@ -9805,92 +11569,79 @@ "annotations": [] }, { - "endpoint": "POST /management-access-policies", + "endpoint": "POST /object-store-roles/object-store-trust-policies/rules", "cmdlets": [ - "New-PfbManagementAccessPolicy" + "New-PfbObjectStoreTrustPolicyRule" + ], + "missingQueryParameters": [ + "names", + "role_ids", + "role_names" ], - "missingQueryParameters": [], "missingBodyProperties": [ { - "name": "aggregation_strategy", - "type": "string", + "name": "actions", + "type": "array", "format": null, "specRequired": false, - "synopsis": "When this is set to `least-common-permissions`, any users to whom this policy applies can receive no access rights exceeding those defined in this policy's capability and resource.", - "suggestedPowerShellType": "[string]", + "synopsis": "The list of role-assumption actions granted by this rule to the respective role.", + "suggestedPowerShellType": "[string[]]", "enumValues": [], - "enumStatus": "no-spec-enum-found", + "enumStatus": "not-found-in-resource", "target": { - "file": "Public/Admin/New-PfbManagementAccessPolicy.ps1", - "paramBlockLine": 32, + "file": "Public/ObjectStore/New-PfbObjectStoreTrustPolicyRule.ps1", + "paramBlockLine": 45, "payloadVariable": "body", "assignmentStyle": "unknown", "hasAttributes": true } }, { - "name": "enabled", - "type": "boolean", + "name": "conditions", + "type": "array", "format": null, "specRequired": false, - "synopsis": "If `true`, the policy is enabled.", - "suggestedPowerShellType": "[bool]", + "synopsis": "Conditions used to limit the scope which this rule applies to.", + "suggestedPowerShellType": "[object[]]", "enumValues": [], "enumStatus": "no-spec-enum-found", "target": { - "file": "Public/Admin/New-PfbManagementAccessPolicy.ps1", - "paramBlockLine": 32, + "file": "Public/ObjectStore/New-PfbObjectStoreTrustPolicyRule.ps1", + "paramBlockLine": 45, "payloadVariable": "body", "assignmentStyle": "unknown", "hasAttributes": true } }, { - "name": "location", + "name": "policy", "type": null, "format": null, "specRequired": false, - "synopsis": "Reference to the array where the policy is defined.", + "synopsis": "The policy to which this rule belongs.", "suggestedPowerShellType": "[object]", "enumValues": [], "enumStatus": "no-spec-enum-found", "target": { - "file": "Public/Admin/New-PfbManagementAccessPolicy.ps1", - "paramBlockLine": 32, - "payloadVariable": "body", - "assignmentStyle": "unknown", - "hasAttributes": true - } - }, - { - "name": "name", - "type": "string", - "format": null, - "specRequired": false, - "synopsis": "A user-specified name.", - "suggestedPowerShellType": "[string]", - "enumValues": [], - "enumStatus": "not-found-in-resource", - "target": { - "file": "Public/Admin/New-PfbManagementAccessPolicy.ps1", - "paramBlockLine": 32, + "file": "Public/ObjectStore/New-PfbObjectStoreTrustPolicyRule.ps1", + "paramBlockLine": 45, "payloadVariable": "body", "assignmentStyle": "unknown", "hasAttributes": true } }, { - "name": "rules", + "name": "principals", "type": "array", "format": null, "specRequired": false, - "synopsis": "All of the rules that are part of this policy.", + "synopsis": "List of Identity Providers", "suggestedPowerShellType": "[object[]]", "enumValues": [], "enumStatus": "no-spec-enum-found", "target": { - "file": "Public/Admin/New-PfbManagementAccessPolicy.ps1", - "paramBlockLine": 32, + "file": "Public/ObjectStore/New-PfbObjectStoreTrustPolicyRule.ps1", + "paramBlockLine": 45, "payloadVariable": "body", "assignmentStyle": "unknown", "hasAttributes": true @@ -9898,10 +11649,7 @@ } ], "readOnlyFields": [ - "id", - "is_local", - "policy_type", - "realms" + "effect" ], "confidence": { "level": "high", @@ -9909,27 +11657,18 @@ "escapeHatchOnly": [], "caveat": "" }, - "annotations": [ - { - "matchType": "endpoint", - "match": "management-access-policies", - "kind": "liveTestingHazard", - "note": "POST/PATCH/DELETE return 403 regardless of account; not an implementation bug", - "reference": null - } - ] + "annotations": [] }, { - "endpoint": "POST /network-access-policies/rules", + "endpoint": "POST /object-store-users", "cmdlets": [ - "New-PfbNetworkAccessRule" + "New-PfbObjectStoreUser" ], - "missingQueryParameters": [], - "missingBodyProperties": [], - "readOnlyFields": [ - "id", - "name" + "missingQueryParameters": [ + "full_access" ], + "missingBodyProperties": [], + "readOnlyFields": [], "confidence": { "level": "high", "unresolvedParameters": [], @@ -9939,35 +11678,16 @@ "annotations": [] }, { - "endpoint": "POST /network-interfaces", + "endpoint": "POST /object-store-users/object-store-access-policies", "cmdlets": [ - "New-PfbNetworkInterface" - ], - "missingQueryParameters": [], - "missingBodyProperties": [ - { - "name": "rdma_enabled", - "type": "boolean", - "format": null, - "specRequired": false, - "synopsis": "If `true` indicated that RDMA is enabled on the network interface.", - "suggestedPowerShellType": "[bool]", - "enumValues": [], - "enumStatus": "no-spec-enum-found", - "target": { - "file": "Public/Network/New-PfbNetworkInterface.ps1", - "paramBlockLine": 66, - "payloadVariable": "body", - "assignmentStyle": "index", - "hasAttributes": true - } - } + "New-PfbObjectStoreUserAccessPolicy" ], - "readOnlyFields": [ - "id", - "name", - "realms" + "missingQueryParameters": [ + "member_ids", + "policy_ids" ], + "missingBodyProperties": [], + "readOnlyFields": [], "confidence": { "level": "high", "unresolvedParameters": [], @@ -9977,52 +11697,35 @@ "annotations": [] }, { - "endpoint": "POST /nfs-export-policies", + "endpoint": "POST /object-store-virtual-hosts", "cmdlets": [ - "New-PfbNfsExportPolicy" + "New-PfbObjectStoreVirtualHost" ], "missingQueryParameters": [], "missingBodyProperties": [ - "enabled", - "location", - "name", - "rules" - ], - "readOnlyFields": [ - "id", - "is_local", - "policy_type", - "realms" - ], - "confidence": { - "level": "partial", - "unresolvedParameters": [ - { - "parameter": "Enabled", - "surface": "AttributesOnly", - "file": "Public/Policy/New-PfbNfsExportPolicy.ps1", - "line": 36 + { + "name": "attached_servers", + "type": "array", + "format": null, + "specRequired": false, + "synopsis": "A list of servers which are allowed to use this virtual host.", + "suggestedPowerShellType": "[object[]]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/ObjectStore/New-PfbObjectStoreVirtualHost.ps1", + "paramBlockLine": 42, + "payloadVariable": "body", + "assignmentStyle": "index", + "hasAttributes": true } - ], - "escapeHatchOnly": [ - "Enabled" - ], - "caveat": "body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability" - }, - "annotations": [] - }, - { - "endpoint": "POST /nfs-export-policies/rules", - "cmdlets": [ - "New-PfbNfsExportRule" + } ], - "missingQueryParameters": [], - "missingBodyProperties": [], "readOnlyFields": [ "context", "id", "name", - "policy_version" + "realms" ], "confidence": { "level": "high", @@ -10033,60 +11736,54 @@ "annotations": [] }, { - "endpoint": "POST /node-groups", - "cmdlets": [ - "New-PfbNodeGroup" - ], - "missingQueryParameters": [ - "names" - ], - "missingBodyProperties": [], - "readOnlyFields": [], - "confidence": { - "level": "partial", - "unresolvedParameters": [ - { - "parameter": "Name", - "surface": "AttributesOnly", - "file": "Public/Node/New-PfbNodeGroup.ps1", - "line": 30 - } - ], - "escapeHatchOnly": [ - "Name" - ], - "caveat": "body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability" - }, - "annotations": [] - }, - { - "endpoint": "POST /object-store-access-keys", + "endpoint": "POST /policies", "cmdlets": [ - "New-PfbObjectStoreAccessKey" - ], - "missingQueryParameters": [ - "names" + "New-PfbPolicy" ], + "missingQueryParameters": [], "missingBodyProperties": [ { - "name": "secret_access_key", + "name": "location", + "type": null, + "format": null, + "specRequired": false, + "synopsis": "Reference to the array where the policy is defined.", + "suggestedPowerShellType": "[object]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Policy/New-PfbPolicy.ps1", + "paramBlockLine": 32, + "payloadVariable": "body", + "assignmentStyle": "index", + "hasAttributes": true + } + }, + { + "name": "name", "type": "string", "format": null, "specRequired": false, - "synopsis": "The secret access key to import from another FlashBlade.", + "synopsis": "A user-specified name.", "suggestedPowerShellType": "[string]", "enumValues": [], - "enumStatus": "no-spec-enum-found", + "enumStatus": "not-found-in-resource", "target": { - "file": "Public/ObjectStore/New-PfbObjectStoreAccessKey.ps1", - "paramBlockLine": 17, + "file": "Public/Policy/New-PfbPolicy.ps1", + "paramBlockLine": 32, "payloadVariable": "body", - "assignmentStyle": "literal", - "hasAttributes": false + "assignmentStyle": "index", + "hasAttributes": true } } ], - "readOnlyFields": [], + "readOnlyFields": [ + "id", + "is_local", + "policy_type", + "realms", + "retention_lock" + ], "confidence": { "level": "high", "unresolvedParameters": [], @@ -10096,172 +11793,284 @@ "annotations": [] }, { - "endpoint": "POST /object-store-access-policies", + "endpoint": "POST /presets/workload", "cmdlets": [ - "New-PfbObjectStoreAccessPolicy" - ], - "missingQueryParameters": [ - "enforce_action_restrictions" + "New-PfbPresetWorkload" ], + "missingQueryParameters": [], "missingBodyProperties": [ { "name": "description", "type": "string", "format": null, "specRequired": false, - "synopsis": "A description of the policy, optionally specified when the policy is created.", + "synopsis": "A brief description of the workload the preset will configure.", "suggestedPowerShellType": "[string]", "enumValues": [], "enumStatus": "no-spec-enum-found", "target": { - "file": "Public/ObjectStore/New-PfbObjectStoreAccessPolicy.ps1", - "paramBlockLine": 58, - "payloadVariable": "body", - "assignmentStyle": "unknown", + "file": "Public/Presets/New-PfbPresetWorkload.ps1", + "paramBlockLine": 51, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", "hasAttributes": true } }, { - "name": "rules", + "name": "directory_configurations", + "type": "array", + "format": null, + "specRequired": false, + "synopsis": "The file systems and managed directories that will be provisioned by the preset.", + "suggestedPowerShellType": "[object[]]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Presets/New-PfbPresetWorkload.ps1", + "paramBlockLine": 51, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true + } + }, + { + "name": "export_configurations", + "type": null, + "format": null, + "specRequired": false, + "synopsis": "NFS, SMB, and SMB share policy configuration to be specified in file system and directory exports.", + "suggestedPowerShellType": "[object]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Presets/New-PfbPresetWorkload.ps1", + "paramBlockLine": 51, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true + } + }, + { + "name": "parameters", + "type": "array", + "format": null, + "specRequired": false, + "synopsis": "The parameters to prompt the user when they deploy workloads from the preset.", + "suggestedPowerShellType": "[object[]]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Presets/New-PfbPresetWorkload.ps1", + "paramBlockLine": 51, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true + } + }, + { + "name": "periodic_replication_configurations", + "type": "array", + "format": null, + "specRequired": false, + "synopsis": "The periodic replication configurations that can be applied to storage resources (such as volumes) within the preset.", + "suggestedPowerShellType": "[object[]]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Presets/New-PfbPresetWorkload.ps1", + "paramBlockLine": 51, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true + } + }, + { + "name": "placement_configurations", + "type": "array", + "format": null, + "specRequired": true, + "synopsis": "The placement configurations that can be applied to storage resources (such as volumes) within the preset.", + "suggestedPowerShellType": "[object[]]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Presets/New-PfbPresetWorkload.ps1", + "paramBlockLine": 51, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true + } + }, + { + "name": "platform_features", "type": "array", "format": null, "specRequired": false, "synopsis": null, "suggestedPowerShellType": "[object[]]", + "enumValues": [ + "fa_block", + "fa_file", + "fb_file" + ], + "enumStatus": "matched", + "target": { + "file": "Public/Presets/New-PfbPresetWorkload.ps1", + "paramBlockLine": 51, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true + } + }, + { + "name": "qos_configurations", + "type": "array", + "format": null, + "specRequired": false, + "synopsis": "The QoS configurations that can be applied to storage resources (such as volumes) within the preset.", + "suggestedPowerShellType": "[object[]]", "enumValues": [], "enumStatus": "no-spec-enum-found", "target": { - "file": "Public/ObjectStore/New-PfbObjectStoreAccessPolicy.ps1", - "paramBlockLine": 58, - "payloadVariable": "body", - "assignmentStyle": "unknown", + "file": "Public/Presets/New-PfbPresetWorkload.ps1", + "paramBlockLine": 51, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", "hasAttributes": true } - } - ], - "readOnlyFields": [], - "confidence": { - "level": "high", - "unresolvedParameters": [], - "escapeHatchOnly": [], - "caveat": "" - }, - "annotations": [] - }, - { - "endpoint": "POST /object-store-access-policies/object-store-roles", - "cmdlets": [ - "New-PfbObjectStoreAccessPolicyRole" - ], - "missingQueryParameters": [ - "member_ids", - "policy_ids" - ], - "missingBodyProperties": [], - "readOnlyFields": [], - "confidence": { - "level": "high", - "unresolvedParameters": [], - "escapeHatchOnly": [], - "caveat": "" - }, - "annotations": [] - }, - { - "endpoint": "POST /object-store-access-policies/object-store-users", - "cmdlets": [ - "New-PfbObjectStoreAccessPolicyUser" - ], - "missingQueryParameters": [ - "member_ids", - "policy_ids" - ], - "missingBodyProperties": [], - "readOnlyFields": [], - "confidence": { - "level": "high", - "unresolvedParameters": [], - "escapeHatchOnly": [], - "caveat": "" - }, - "annotations": [] - }, - { - "endpoint": "POST /object-store-access-policies/rules", - "cmdlets": [ - "New-PfbObjectStoreAccessPolicyRule" - ], - "missingQueryParameters": [ - "enforce_action_restrictions", - "policy_ids" - ], - "missingBodyProperties": [ + }, + { + "name": "quota_configurations", + "type": "array", + "format": null, + "specRequired": false, + "synopsis": "The quota configurations that can be applied to storage resources (such as file systems and directories) within the preset.", + "suggestedPowerShellType": "[object[]]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Presets/New-PfbPresetWorkload.ps1", + "paramBlockLine": 51, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true + } + }, + { + "name": "snapshot_configurations", + "type": "array", + "format": null, + "specRequired": false, + "synopsis": "The snapshot configurations that can be applied to storage resources (such as volumes) within the preset.", + "suggestedPowerShellType": "[object[]]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Presets/New-PfbPresetWorkload.ps1", + "paramBlockLine": 51, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true + } + }, { - "name": "actions", + "name": "volume_configurations", "type": "array", "format": null, "specRequired": false, - "synopsis": "The list of actions granted by this rule.", - "suggestedPowerShellType": "[string[]]", + "synopsis": "The volumes that will be provisioned by the preset.", + "suggestedPowerShellType": "[object[]]", "enumValues": [], - "enumStatus": "not-found-in-resource", + "enumStatus": "no-spec-enum-found", "target": { - "file": "Public/ObjectStore/New-PfbObjectStoreAccessPolicyRule.ps1", + "file": "Public/Presets/New-PfbPresetWorkload.ps1", "paramBlockLine": 51, - "payloadVariable": "body", - "assignmentStyle": "unknown", + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", "hasAttributes": true } }, { - "name": "conditions", - "type": null, + "name": "workload_tags", + "type": "array", "format": null, "specRequired": false, - "synopsis": "Conditions used to limit the scope which this rule applies to.", - "suggestedPowerShellType": "[object]", + "synopsis": "The tags that will be associated with workloads provisioned by the preset.", + "suggestedPowerShellType": "[object[]]", "enumValues": [], "enumStatus": "no-spec-enum-found", "target": { - "file": "Public/ObjectStore/New-PfbObjectStoreAccessPolicyRule.ps1", + "file": "Public/Presets/New-PfbPresetWorkload.ps1", "paramBlockLine": 51, - "payloadVariable": "body", - "assignmentStyle": "unknown", + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", "hasAttributes": true } }, { - "name": "effect", + "name": "workload_type", "type": "string", "format": null, - "specRequired": false, - "synopsis": "Effect of this rule.", + "specRequired": true, + "synopsis": "The type of workload the preset will configure.", "suggestedPowerShellType": "[string]", "enumValues": [ - "allow", - "deny" + "Clarity", + "Epic", + "Exchange", + "File", + "MsSQL", + "MySQL", + "Oracle", + "PostgreSQL", + "SAP-Hana", + "SAP", + "VDI", + "VSI", + "Wfs", + "Zerto", + "Custom" ], "enumStatus": "matched", "target": { - "file": "Public/ObjectStore/New-PfbObjectStoreAccessPolicyRule.ps1", + "file": "Public/Presets/New-PfbPresetWorkload.ps1", "paramBlockLine": 51, - "payloadVariable": "body", - "assignmentStyle": "unknown", + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", "hasAttributes": true } - }, + } + ], + "readOnlyFields": [ + "revision" + ], + "confidence": { + "level": "high", + "unresolvedParameters": [], + "escapeHatchOnly": [], + "caveat": "" + }, + "annotations": [] + }, + { + "endpoint": "POST /public-keys", + "cmdlets": [ + "New-PfbPublicKey" + ], + "missingQueryParameters": [], + "missingBodyProperties": [ { - "name": "resources", - "type": "array", + "name": "public_key", + "type": "string", "format": null, "specRequired": false, - "synopsis": "The list of resources which this rule applies to.", - "suggestedPowerShellType": "[string[]]", + "synopsis": "The text of the public key.", + "suggestedPowerShellType": "[string]", "enumValues": [], "enumStatus": "no-spec-enum-found", "target": { - "file": "Public/ObjectStore/New-PfbObjectStoreAccessPolicyRule.ps1", - "paramBlockLine": 51, + "file": "Public/Admin/New-PfbPublicKey.ps1", + "paramBlockLine": 37, "payloadVariable": "body", "assignmentStyle": "unknown", "hasAttributes": true @@ -10278,82 +12087,105 @@ "annotations": [] }, { - "endpoint": "POST /object-store-accounts", + "endpoint": "POST /qos-policies", "cmdlets": [ - "New-PfbObjectStoreAccount" + "New-PfbQosPolicy" ], "missingQueryParameters": [], "missingBodyProperties": [ { - "name": "account_exports", - "type": "array", + "name": "enabled", + "type": "boolean", "format": null, "specRequired": false, - "synopsis": "A list of exports to be created for the account.", - "suggestedPowerShellType": "[object[]]", + "synopsis": "If `true`, the policy is enabled.", + "suggestedPowerShellType": "[bool]", "enumValues": [], "enumStatus": "no-spec-enum-found", "target": { - "file": "Public/ObjectStore/New-PfbObjectStoreAccount.ps1", - "paramBlockLine": 17, - "payloadVariable": null, - "assignmentStyle": null, - "hasAttributes": false + "file": "Public/Policy/New-PfbQosPolicy.ps1", + "paramBlockLine": 33, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true } }, { - "name": "bucket_defaults", + "name": "location", "type": null, "format": null, "specRequired": false, - "synopsis": "Default settings to be applied to newly created buckets associated with this account.", + "synopsis": "Reference to the array where the policy is defined.", "suggestedPowerShellType": "[object]", "enumValues": [], "enumStatus": "no-spec-enum-found", "target": { - "file": "Public/ObjectStore/New-PfbObjectStoreAccount.ps1", - "paramBlockLine": 17, - "payloadVariable": null, - "assignmentStyle": null, - "hasAttributes": false + "file": "Public/Policy/New-PfbQosPolicy.ps1", + "paramBlockLine": 33, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true } }, { - "name": "hard_limit_enabled", - "type": "boolean", - "format": null, + "name": "max_total_bytes_per_sec", + "type": "integer", + "format": "int64", "specRequired": false, - "synopsis": "If set to `true`, the account's size, as defined by `quota_limit`, is used as a hard limit quota.", - "suggestedPowerShellType": "[bool]", + "synopsis": "The maximum allowed bytes/s totaled across all the clients.", + "suggestedPowerShellType": "[long]", "enumValues": [], "enumStatus": "no-spec-enum-found", "target": { - "file": "Public/ObjectStore/New-PfbObjectStoreAccount.ps1", - "paramBlockLine": 17, - "payloadVariable": null, - "assignmentStyle": null, - "hasAttributes": false + "file": "Public/Policy/New-PfbQosPolicy.ps1", + "paramBlockLine": 33, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true } }, { - "name": "quota_limit", + "name": "max_total_ops_per_sec", + "type": "integer", + "format": "int64", + "specRequired": false, + "synopsis": "The maximum allowed operations/s totaled across all the clients.", + "suggestedPowerShellType": "[long]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Policy/New-PfbQosPolicy.ps1", + "paramBlockLine": 33, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true + } + }, + { + "name": "name", "type": "string", "format": null, "specRequired": false, - "synopsis": "The effective quota limit to be applied against the size of the account, displayed in bytes.", + "synopsis": "A user-specified name.", "suggestedPowerShellType": "[string]", "enumValues": [], - "enumStatus": "no-spec-enum-found", + "enumStatus": "not-found-in-resource", "target": { - "file": "Public/ObjectStore/New-PfbObjectStoreAccount.ps1", - "paramBlockLine": 17, - "payloadVariable": null, - "assignmentStyle": null, - "hasAttributes": false + "file": "Public/Policy/New-PfbQosPolicy.ps1", + "paramBlockLine": 33, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true } } ], - "readOnlyFields": [], + "readOnlyFields": [ + "context", + "id", + "is_local", + "policy_type", + "realms" + ], "confidence": { "level": "high", "unresolvedParameters": [], @@ -10363,47 +12195,54 @@ "annotations": [] }, { - "endpoint": "POST /object-store-remote-credentials", + "endpoint": "POST /quotas/groups", "cmdlets": [ - "New-PfbObjectStoreRemoteCredential" + "New-PfbQuotaGroup" ], - "missingQueryParameters": [], - "missingBodyProperties": [ - { - "name": "access_key_id", - "type": "string", - "format": null, - "specRequired": false, - "synopsis": "Access Key ID to be used when connecting to a remote object store.", - "suggestedPowerShellType": "[string]", - "enumValues": [], - "enumStatus": "no-spec-enum-found", - "target": { - "file": "Public/ObjectStore/New-PfbObjectStoreRemoteCredential.ps1", - "paramBlockLine": 39, - "payloadVariable": "body", - "assignmentStyle": "unknown", - "hasAttributes": true - } - }, - { - "name": "secret_access_key", - "type": "string", - "format": null, - "specRequired": false, - "synopsis": "Secret Access Key to be used when connecting to a remote object store.", - "suggestedPowerShellType": "[string]", - "enumValues": [], - "enumStatus": "no-spec-enum-found", - "target": { - "file": "Public/ObjectStore/New-PfbObjectStoreRemoteCredential.ps1", - "paramBlockLine": 39, - "payloadVariable": "body", - "assignmentStyle": "unknown", - "hasAttributes": true - } - } + "missingQueryParameters": [ + "file_system_ids" + ], + "missingBodyProperties": [], + "readOnlyFields": [ + "name" + ], + "confidence": { + "level": "high", + "unresolvedParameters": [], + "escapeHatchOnly": [], + "caveat": "" + }, + "annotations": [] + }, + { + "endpoint": "POST /quotas/users", + "cmdlets": [ + "New-PfbQuotaUser" + ], + "missingQueryParameters": [ + "file_system_ids" + ], + "missingBodyProperties": [], + "readOnlyFields": [ + "name" + ], + "confidence": { + "level": "high", + "unresolvedParameters": [], + "escapeHatchOnly": [], + "caveat": "" + }, + "annotations": [] + }, + { + "endpoint": "POST /realms", + "cmdlets": [ + "New-PfbRealm" + ], + "missingQueryParameters": [ + "without_default_access_list" ], + "missingBodyProperties": [], "readOnlyFields": [], "confidence": { "level": "high", @@ -10414,26 +12253,26 @@ "annotations": [] }, { - "endpoint": "POST /object-store-roles", + "endpoint": "POST /s3-export-policies", "cmdlets": [ - "New-PfbObjectStoreRole" + "New-PfbS3ExportPolicy" ], "missingQueryParameters": [], "missingBodyProperties": [ { - "name": "max_session_duration", - "type": "integer", + "name": "rules", + "type": "array", "format": null, "specRequired": false, - "synopsis": "Maximum session duration in milliseconds.", - "suggestedPowerShellType": "[int]", + "synopsis": null, + "suggestedPowerShellType": "[object[]]", "enumValues": [], "enumStatus": "no-spec-enum-found", "target": { - "file": "Public/ObjectStore/New-PfbObjectStoreRole.ps1", - "paramBlockLine": 37, + "file": "Public/Policy/New-PfbS3ExportPolicy.ps1", + "paramBlockLine": 42, "payloadVariable": "body", - "assignmentStyle": "unknown", + "assignmentStyle": "index", "hasAttributes": true } } @@ -10448,107 +12287,114 @@ "annotations": [] }, { - "endpoint": "POST /object-store-roles/object-store-access-policies", + "endpoint": "POST /servers", "cmdlets": [ - "New-PfbObjectStoreRoleAccessPolicy" + "New-PfbServer" ], "missingQueryParameters": [ - "member_ids", - "policy_ids", - "policy_names" + "create_ds", + "create_local_directory_service" ], "missingBodyProperties": [], "readOnlyFields": [], "confidence": { - "level": "high", - "unresolvedParameters": [], - "escapeHatchOnly": [], - "caveat": "" + "level": "partial", + "unresolvedParameters": [ + { + "parameter": "CreateDirectoryService", + "surface": "AttributesOnly", + "file": "Public/Server/New-PfbServer.ps1", + "line": 40 + } + ], + "escapeHatchOnly": [ + "CreateDirectoryService" + ], + "caveat": "body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability" }, "annotations": [] }, { - "endpoint": "POST /object-store-roles/object-store-trust-policies/rules", + "endpoint": "POST /smb-client-policies", "cmdlets": [ - "New-PfbObjectStoreTrustPolicyRule" - ], - "missingQueryParameters": [ - "names", - "role_ids", - "role_names" + "New-PfbSmbClientPolicy" ], + "missingQueryParameters": [], "missingBodyProperties": [ { - "name": "actions", - "type": "array", + "name": "access_based_enumeration_enabled", + "type": "boolean", "format": null, "specRequired": false, - "synopsis": "The list of role-assumption actions granted by this rule to the respective role.", - "suggestedPowerShellType": "[string[]]", + "synopsis": "If set to `true`, enables access based enumeration on the policy.", + "suggestedPowerShellType": "[bool]", "enumValues": [], - "enumStatus": "not-found-in-resource", + "enumStatus": "no-spec-enum-found", "target": { - "file": "Public/ObjectStore/New-PfbObjectStoreTrustPolicyRule.ps1", - "paramBlockLine": 45, + "file": "Public/Policy/New-PfbSmbClientPolicy.ps1", + "paramBlockLine": 42, "payloadVariable": "body", - "assignmentStyle": "unknown", + "assignmentStyle": "index", "hasAttributes": true } }, { - "name": "conditions", - "type": "array", + "name": "location", + "type": null, "format": null, "specRequired": false, - "synopsis": "Conditions used to limit the scope which this rule applies to.", - "suggestedPowerShellType": "[object[]]", + "synopsis": "Reference to the array where the policy is defined.", + "suggestedPowerShellType": "[object]", "enumValues": [], "enumStatus": "no-spec-enum-found", "target": { - "file": "Public/ObjectStore/New-PfbObjectStoreTrustPolicyRule.ps1", - "paramBlockLine": 45, + "file": "Public/Policy/New-PfbSmbClientPolicy.ps1", + "paramBlockLine": 42, "payloadVariable": "body", - "assignmentStyle": "unknown", + "assignmentStyle": "index", "hasAttributes": true } }, { - "name": "policy", - "type": null, + "name": "name", + "type": "string", "format": null, "specRequired": false, - "synopsis": "The policy to which this rule belongs.", - "suggestedPowerShellType": "[object]", + "synopsis": "A user-specified name.", + "suggestedPowerShellType": "[string]", "enumValues": [], - "enumStatus": "no-spec-enum-found", + "enumStatus": "not-found-in-resource", "target": { - "file": "Public/ObjectStore/New-PfbObjectStoreTrustPolicyRule.ps1", - "paramBlockLine": 45, + "file": "Public/Policy/New-PfbSmbClientPolicy.ps1", + "paramBlockLine": 42, "payloadVariable": "body", - "assignmentStyle": "unknown", + "assignmentStyle": "index", "hasAttributes": true } }, { - "name": "principals", + "name": "rules", "type": "array", "format": null, "specRequired": false, - "synopsis": "List of Identity Providers", + "synopsis": "All of the rules that are part of this policy.", "suggestedPowerShellType": "[object[]]", "enumValues": [], "enumStatus": "no-spec-enum-found", "target": { - "file": "Public/ObjectStore/New-PfbObjectStoreTrustPolicyRule.ps1", - "paramBlockLine": 45, + "file": "Public/Policy/New-PfbSmbClientPolicy.ps1", + "paramBlockLine": 42, "payloadVariable": "body", - "assignmentStyle": "unknown", + "assignmentStyle": "index", "hasAttributes": true } } ], "readOnlyFields": [ - "effect" + "id", + "is_local", + "policy_type", + "realms" ], "confidence": { "level": "high", @@ -10559,34 +12405,16 @@ "annotations": [] }, { - "endpoint": "POST /object-store-users", + "endpoint": "POST /smb-client-policies/rules", "cmdlets": [ - "New-PfbObjectStoreUser" - ], - "missingQueryParameters": [ - "full_access" + "New-PfbSmbClientRule" ], + "missingQueryParameters": [], "missingBodyProperties": [], - "readOnlyFields": [], - "confidence": { - "level": "high", - "unresolvedParameters": [], - "escapeHatchOnly": [], - "caveat": "" - }, - "annotations": [] - }, - { - "endpoint": "POST /object-store-users/object-store-access-policies", - "cmdlets": [ - "New-PfbObjectStoreUserAccessPolicy" - ], - "missingQueryParameters": [ - "member_ids", - "policy_ids" + "readOnlyFields": [ + "id", + "name" ], - "missingBodyProperties": [], - "readOnlyFields": [], "confidence": { "level": "high", "unresolvedParameters": [], @@ -10596,23 +12424,57 @@ "annotations": [] }, { - "endpoint": "POST /object-store-virtual-hosts", + "endpoint": "POST /smb-share-policies", "cmdlets": [ - "New-PfbObjectStoreVirtualHost" + "New-PfbSmbSharePolicy" ], "missingQueryParameters": [], "missingBodyProperties": [ { - "name": "attached_servers", + "name": "location", + "type": null, + "format": null, + "specRequired": false, + "synopsis": "Reference to the array where the policy is defined.", + "suggestedPowerShellType": "[object]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Policy/New-PfbSmbSharePolicy.ps1", + "paramBlockLine": 42, + "payloadVariable": "body", + "assignmentStyle": "index", + "hasAttributes": true + } + }, + { + "name": "name", + "type": "string", + "format": null, + "specRequired": false, + "synopsis": "A user-specified name.", + "suggestedPowerShellType": "[string]", + "enumValues": [], + "enumStatus": "not-found-in-resource", + "target": { + "file": "Public/Policy/New-PfbSmbSharePolicy.ps1", + "paramBlockLine": 42, + "payloadVariable": "body", + "assignmentStyle": "index", + "hasAttributes": true + } + }, + { + "name": "rules", "type": "array", "format": null, "specRequired": false, - "synopsis": "A list of servers which are allowed to use this virtual host.", + "synopsis": "All of the rules that are part of this policy.", "suggestedPowerShellType": "[object[]]", "enumValues": [], "enumStatus": "no-spec-enum-found", "target": { - "file": "Public/ObjectStore/New-PfbObjectStoreVirtualHost.ps1", + "file": "Public/Policy/New-PfbSmbSharePolicy.ps1", "paramBlockLine": 42, "payloadVariable": "body", "assignmentStyle": "index", @@ -10621,9 +12483,9 @@ } ], "readOnlyFields": [ - "context", "id", - "name", + "is_local", + "policy_type", "realms" ], "confidence": { @@ -10635,283 +12497,238 @@ "annotations": [] }, { - "endpoint": "POST /policies", + "endpoint": "POST /smb-share-policies/rules", "cmdlets": [ - "New-PfbPolicy" + "New-PfbSmbShareRule" ], "missingQueryParameters": [], - "missingBodyProperties": [ - "enabled", - "location", - "name" - ], + "missingBodyProperties": [], "readOnlyFields": [ "id", - "is_local", - "policy_type", - "realms", - "retention_lock" + "name" ], "confidence": { - "level": "partial", - "unresolvedParameters": [ - { - "parameter": "Enabled", - "surface": "AttributesOnly", - "file": "Public/Policy/New-PfbPolicy.ps1", - "line": 23 - } - ], - "escapeHatchOnly": [ - "Enabled" - ], - "caveat": "body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability" + "level": "high", + "unresolvedParameters": [], + "escapeHatchOnly": [], + "caveat": "" }, "annotations": [] }, { - "endpoint": "POST /presets/workload", + "endpoint": "POST /snmp-managers", "cmdlets": [ - "New-PfbPresetWorkload" + "New-PfbSnmpManager" ], "missingQueryParameters": [], - "missingBodyProperties": [ - { - "name": "description", - "type": "string", - "format": null, - "specRequired": false, - "synopsis": "A brief description of the workload the preset will configure.", - "suggestedPowerShellType": "[string]", - "enumValues": [], - "enumStatus": "no-spec-enum-found", - "target": { - "file": "Public/Presets/New-PfbPresetWorkload.ps1", - "paramBlockLine": 51, - "payloadVariable": "Attributes", - "assignmentStyle": "attributesOnly", - "hasAttributes": true - } - }, + "missingBodyProperties": [ { - "name": "directory_configurations", - "type": "array", + "name": "host", + "type": "string", "format": null, "specRequired": false, - "synopsis": "The file systems and managed directories that will be provisioned by the preset.", - "suggestedPowerShellType": "[object[]]", + "synopsis": "DNS hostname or IP address of a computer that hosts an SNMP manager to which Purity is to send trap messages when it generates alerts.", + "suggestedPowerShellType": "[string]", "enumValues": [], "enumStatus": "no-spec-enum-found", "target": { - "file": "Public/Presets/New-PfbPresetWorkload.ps1", - "paramBlockLine": 51, + "file": "Public/Monitoring/New-PfbSnmpManager.ps1", + "paramBlockLine": 35, "payloadVariable": "Attributes", "assignmentStyle": "attributesOnly", "hasAttributes": true } }, { - "name": "export_configurations", - "type": null, + "name": "notification", + "type": "string", "format": null, "specRequired": false, - "synopsis": "NFS, SMB, and SMB share policy configuration to be specified in file system and directory exports.", - "suggestedPowerShellType": "[object]", - "enumValues": [], - "enumStatus": "no-spec-enum-found", + "synopsis": "The type of notification the agent will send.", + "suggestedPowerShellType": "[string]", + "enumValues": [ + "inform", + "trap" + ], + "enumStatus": "matched", "target": { - "file": "Public/Presets/New-PfbPresetWorkload.ps1", - "paramBlockLine": 51, + "file": "Public/Monitoring/New-PfbSnmpManager.ps1", + "paramBlockLine": 35, "payloadVariable": "Attributes", "assignmentStyle": "attributesOnly", "hasAttributes": true } }, { - "name": "parameters", - "type": "array", + "name": "v2c", + "type": null, "format": null, "specRequired": false, - "synopsis": "The parameters to prompt the user when they deploy workloads from the preset.", - "suggestedPowerShellType": "[object[]]", + "synopsis": null, + "suggestedPowerShellType": "[object]", "enumValues": [], "enumStatus": "no-spec-enum-found", "target": { - "file": "Public/Presets/New-PfbPresetWorkload.ps1", - "paramBlockLine": 51, + "file": "Public/Monitoring/New-PfbSnmpManager.ps1", + "paramBlockLine": 35, "payloadVariable": "Attributes", "assignmentStyle": "attributesOnly", "hasAttributes": true } }, { - "name": "periodic_replication_configurations", - "type": "array", + "name": "v3", + "type": null, "format": null, "specRequired": false, - "synopsis": "The periodic replication configurations that can be applied to storage resources (such as volumes) within the preset.", - "suggestedPowerShellType": "[object[]]", - "enumValues": [], - "enumStatus": "no-spec-enum-found", - "target": { - "file": "Public/Presets/New-PfbPresetWorkload.ps1", - "paramBlockLine": 51, - "payloadVariable": "Attributes", - "assignmentStyle": "attributesOnly", - "hasAttributes": true - } - }, - { - "name": "placement_configurations", - "type": "array", - "format": null, - "specRequired": true, - "synopsis": "The placement configurations that can be applied to storage resources (such as volumes) within the preset.", - "suggestedPowerShellType": "[object[]]", + "synopsis": null, + "suggestedPowerShellType": "[object]", "enumValues": [], "enumStatus": "no-spec-enum-found", "target": { - "file": "Public/Presets/New-PfbPresetWorkload.ps1", - "paramBlockLine": 51, + "file": "Public/Monitoring/New-PfbSnmpManager.ps1", + "paramBlockLine": 35, "payloadVariable": "Attributes", "assignmentStyle": "attributesOnly", "hasAttributes": true } }, { - "name": "platform_features", - "type": "array", + "name": "version", + "type": "string", "format": null, "specRequired": false, - "synopsis": null, - "suggestedPowerShellType": "[object[]]", + "synopsis": "Version of the SNMP protocol to be used by Purity in communications with the specified manager.", + "suggestedPowerShellType": "[string]", "enumValues": [ - "fa_block", - "fa_file", - "fb_file" + "v2c", + "v3" ], "enumStatus": "matched", "target": { - "file": "Public/Presets/New-PfbPresetWorkload.ps1", - "paramBlockLine": 51, + "file": "Public/Monitoring/New-PfbSnmpManager.ps1", + "paramBlockLine": 35, "payloadVariable": "Attributes", "assignmentStyle": "attributesOnly", "hasAttributes": true } - }, + } + ], + "readOnlyFields": [], + "confidence": { + "level": "high", + "unresolvedParameters": [], + "escapeHatchOnly": [], + "caveat": "" + }, + "annotations": [] + }, + { + "endpoint": "POST /software-check", + "cmdlets": [ + "New-PfbSoftwareCheck" + ], + "missingQueryParameters": [ + "software_names", + "software_versions" + ], + "missingBodyProperties": [], + "readOnlyFields": [], + "confidence": { + "level": "high", + "unresolvedParameters": [], + "escapeHatchOnly": [], + "caveat": "" + }, + "annotations": [] + }, + { + "endpoint": "POST /ssh-certificate-authority-policies", + "cmdlets": [ + "New-PfbSshCaPolicy" + ], + "missingQueryParameters": [], + "missingBodyProperties": [ { - "name": "qos_configurations", - "type": "array", + "name": "enabled", + "type": "boolean", "format": null, "specRequired": false, - "synopsis": "The QoS configurations that can be applied to storage resources (such as volumes) within the preset.", - "suggestedPowerShellType": "[object[]]", + "synopsis": "If `true`, the policy is enabled.", + "suggestedPowerShellType": "[bool]", "enumValues": [], "enumStatus": "no-spec-enum-found", "target": { - "file": "Public/Presets/New-PfbPresetWorkload.ps1", - "paramBlockLine": 51, + "file": "Public/Policy/New-PfbSshCaPolicy.ps1", + "paramBlockLine": 40, "payloadVariable": "Attributes", "assignmentStyle": "attributesOnly", "hasAttributes": true } }, { - "name": "quota_configurations", - "type": "array", + "name": "location", + "type": null, "format": null, "specRequired": false, - "synopsis": "The quota configurations that can be applied to storage resources (such as file systems and directories) within the preset.", - "suggestedPowerShellType": "[object[]]", + "synopsis": "Reference to the array where the policy is defined.", + "suggestedPowerShellType": "[object]", "enumValues": [], "enumStatus": "no-spec-enum-found", "target": { - "file": "Public/Presets/New-PfbPresetWorkload.ps1", - "paramBlockLine": 51, + "file": "Public/Policy/New-PfbSshCaPolicy.ps1", + "paramBlockLine": 40, "payloadVariable": "Attributes", "assignmentStyle": "attributesOnly", "hasAttributes": true } }, { - "name": "snapshot_configurations", - "type": "array", + "name": "name", + "type": "string", "format": null, "specRequired": false, - "synopsis": "The snapshot configurations that can be applied to storage resources (such as volumes) within the preset.", - "suggestedPowerShellType": "[object[]]", + "synopsis": "A user-specified name.", + "suggestedPowerShellType": "[string]", "enumValues": [], - "enumStatus": "no-spec-enum-found", + "enumStatus": "not-found-in-resource", "target": { - "file": "Public/Presets/New-PfbPresetWorkload.ps1", - "paramBlockLine": 51, + "file": "Public/Policy/New-PfbSshCaPolicy.ps1", + "paramBlockLine": 40, "payloadVariable": "Attributes", "assignmentStyle": "attributesOnly", "hasAttributes": true } }, { - "name": "volume_configurations", - "type": "array", + "name": "signing_authority", + "type": null, "format": null, "specRequired": false, - "synopsis": "The volumes that will be provisioned by the preset.", - "suggestedPowerShellType": "[object[]]", + "synopsis": "A reference to the authority that will digitally sign user SSH certificates that will be used to access the system.", + "suggestedPowerShellType": "[object]", "enumValues": [], "enumStatus": "no-spec-enum-found", "target": { - "file": "Public/Presets/New-PfbPresetWorkload.ps1", - "paramBlockLine": 51, + "file": "Public/Policy/New-PfbSshCaPolicy.ps1", + "paramBlockLine": 40, "payloadVariable": "Attributes", "assignmentStyle": "attributesOnly", "hasAttributes": true } }, { - "name": "workload_tags", + "name": "static_authorized_principals", "type": "array", "format": null, "specRequired": false, - "synopsis": "The tags that will be associated with workloads provisioned by the preset.", - "suggestedPowerShellType": "[object[]]", + "synopsis": "If not specified - users affected by this policy can only log into the system when they present an SSH certificate containing their own username as a principle.", + "suggestedPowerShellType": "[string[]]", "enumValues": [], "enumStatus": "no-spec-enum-found", "target": { - "file": "Public/Presets/New-PfbPresetWorkload.ps1", - "paramBlockLine": 51, - "payloadVariable": "Attributes", - "assignmentStyle": "attributesOnly", - "hasAttributes": true - } - }, - { - "name": "workload_type", - "type": "string", - "format": null, - "specRequired": true, - "synopsis": "The type of workload the preset will configure.", - "suggestedPowerShellType": "[string]", - "enumValues": [ - "Clarity", - "Epic", - "Exchange", - "File", - "MsSQL", - "MySQL", - "Oracle", - "PostgreSQL", - "SAP-Hana", - "SAP", - "VDI", - "VSI", - "Wfs", - "Zerto", - "Custom" - ], - "enumStatus": "matched", - "target": { - "file": "Public/Presets/New-PfbPresetWorkload.ps1", - "paramBlockLine": 51, + "file": "Public/Policy/New-PfbSshCaPolicy.ps1", + "paramBlockLine": 40, "payloadVariable": "Attributes", "assignmentStyle": "attributesOnly", "hasAttributes": true @@ -10919,7 +12736,10 @@ } ], "readOnlyFields": [ - "revision" + "id", + "is_local", + "policy_type", + "realms" ], "confidence": { "level": "high", @@ -10930,31 +12750,67 @@ "annotations": [] }, { - "endpoint": "POST /public-keys", + "endpoint": "POST /sso/oidc/idps", "cmdlets": [ - "New-PfbPublicKey" + "New-PfbOidcIdp" ], "missingQueryParameters": [], "missingBodyProperties": [ { - "name": "public_key", - "type": "string", + "name": "enabled", + "type": "boolean", "format": null, "specRequired": false, - "synopsis": "The text of the public key.", - "suggestedPowerShellType": "[string]", + "synopsis": "If set to `true`, the OIDC SSO configuration is enabled.", + "suggestedPowerShellType": "[bool]", "enumValues": [], "enumStatus": "no-spec-enum-found", "target": { - "file": "Public/Admin/New-PfbPublicKey.ps1", - "paramBlockLine": 37, + "file": "Public/Admin/New-PfbOidcIdp.ps1", + "paramBlockLine": 39, + "payloadVariable": "body", + "assignmentStyle": "unknown", + "hasAttributes": true + } + }, + { + "name": "idp", + "type": null, + "format": null, + "specRequired": false, + "synopsis": null, + "suggestedPowerShellType": "[object]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Admin/New-PfbOidcIdp.ps1", + "paramBlockLine": 39, + "payloadVariable": "body", + "assignmentStyle": "unknown", + "hasAttributes": true + } + }, + { + "name": "services", + "type": "array", + "format": null, + "specRequired": false, + "synopsis": "Services that the OIDC SSO authentication is used for.", + "suggestedPowerShellType": "[string[]]", + "enumValues": [], + "enumStatus": "not-found-in-resource", + "target": { + "file": "Public/Admin/New-PfbOidcIdp.ps1", + "paramBlockLine": 39, "payloadVariable": "body", "assignmentStyle": "unknown", "hasAttributes": true } } ], - "readOnlyFields": [], + "readOnlyFields": [ + "prn" + ], "confidence": { "level": "high", "unresolvedParameters": [], @@ -10964,325 +12820,134 @@ "annotations": [] }, { - "endpoint": "POST /qos-policies", + "endpoint": "POST /sso/saml2/idps", "cmdlets": [ - "New-PfbQosPolicy" + "New-PfbSaml2Idp" ], "missingQueryParameters": [], "missingBodyProperties": [ { - "name": "enabled", - "type": "boolean", + "name": "array_url", + "type": "string", "format": null, "specRequired": false, - "synopsis": "If `true`, the policy is enabled.", - "suggestedPowerShellType": "[bool]", + "synopsis": "The URL of the array.", + "suggestedPowerShellType": "[string]", "enumValues": [], "enumStatus": "no-spec-enum-found", "target": { - "file": "Public/Policy/New-PfbQosPolicy.ps1", - "paramBlockLine": 33, - "payloadVariable": "Attributes", - "assignmentStyle": "attributesOnly", + "file": "Public/Admin/New-PfbSaml2Idp.ps1", + "paramBlockLine": 39, + "payloadVariable": "body", + "assignmentStyle": "unknown", "hasAttributes": true } }, { - "name": "location", - "type": null, + "name": "binding", + "type": "string", "format": null, "specRequired": false, - "synopsis": "Reference to the array where the policy is defined.", - "suggestedPowerShellType": "[object]", + "synopsis": "SAML2 binding to use for the request from Flashblade to the Identity Provider.", + "suggestedPowerShellType": "[string]", "enumValues": [], "enumStatus": "no-spec-enum-found", "target": { - "file": "Public/Policy/New-PfbQosPolicy.ps1", - "paramBlockLine": 33, - "payloadVariable": "Attributes", - "assignmentStyle": "attributesOnly", + "file": "Public/Admin/New-PfbSaml2Idp.ps1", + "paramBlockLine": 39, + "payloadVariable": "body", + "assignmentStyle": "unknown", "hasAttributes": true } }, { - "name": "max_total_bytes_per_sec", - "type": "integer", - "format": "int64", + "name": "enabled", + "type": "boolean", + "format": null, "specRequired": false, - "synopsis": "The maximum allowed bytes/s totaled across all the clients.", - "suggestedPowerShellType": "[long]", + "synopsis": "If set to `true`, the SAML2 SSO configuration is enabled.", + "suggestedPowerShellType": "[bool]", "enumValues": [], "enumStatus": "no-spec-enum-found", "target": { - "file": "Public/Policy/New-PfbQosPolicy.ps1", - "paramBlockLine": 33, - "payloadVariable": "Attributes", - "assignmentStyle": "attributesOnly", + "file": "Public/Admin/New-PfbSaml2Idp.ps1", + "paramBlockLine": 39, + "payloadVariable": "body", + "assignmentStyle": "unknown", "hasAttributes": true } }, { - "name": "max_total_ops_per_sec", - "type": "integer", - "format": "int64", + "name": "idp", + "type": null, + "format": null, "specRequired": false, - "synopsis": "The maximum allowed operations/s totaled across all the clients.", - "suggestedPowerShellType": "[long]", + "synopsis": null, + "suggestedPowerShellType": "[object]", "enumValues": [], "enumStatus": "no-spec-enum-found", "target": { - "file": "Public/Policy/New-PfbQosPolicy.ps1", - "paramBlockLine": 33, - "payloadVariable": "Attributes", - "assignmentStyle": "attributesOnly", + "file": "Public/Admin/New-PfbSaml2Idp.ps1", + "paramBlockLine": 39, + "payloadVariable": "body", + "assignmentStyle": "unknown", "hasAttributes": true } }, { - "name": "name", - "type": "string", + "name": "management", + "type": null, "format": null, "specRequired": false, - "synopsis": "A user-specified name.", - "suggestedPowerShellType": "[string]", + "synopsis": null, + "suggestedPowerShellType": "[object]", "enumValues": [], - "enumStatus": "not-found-in-resource", + "enumStatus": "no-spec-enum-found", "target": { - "file": "Public/Policy/New-PfbQosPolicy.ps1", - "paramBlockLine": 33, - "payloadVariable": "Attributes", - "assignmentStyle": "attributesOnly", + "file": "Public/Admin/New-PfbSaml2Idp.ps1", + "paramBlockLine": 39, + "payloadVariable": "body", + "assignmentStyle": "unknown", "hasAttributes": true } - } - ], - "readOnlyFields": [ - "context", - "id", - "is_local", - "policy_type", - "realms" - ], - "confidence": { - "level": "high", - "unresolvedParameters": [], - "escapeHatchOnly": [], - "caveat": "" - }, - "annotations": [] - }, - { - "endpoint": "POST /quotas/groups", - "cmdlets": [ - "New-PfbQuotaGroup" - ], - "missingQueryParameters": [ - "file_system_ids", - "file_system_names", - "gids", - "group_names" - ], - "missingBodyProperties": [], - "readOnlyFields": [ - "name" - ], - "confidence": { - "level": "partial", - "unresolvedParameters": [ - { - "parameter": "FileSystemName", - "surface": "AttributesOnly", - "file": "Public/Quota/New-PfbQuotaGroup.ps1", - "line": 40 - }, - { - "parameter": "GroupId", - "surface": "AttributesOnly", - "file": "Public/Quota/New-PfbQuotaGroup.ps1", - "line": 42 - }, - { - "parameter": "GroupName", - "surface": "AttributesOnly", - "file": "Public/Quota/New-PfbQuotaGroup.ps1", - "line": 41 - } - ], - "escapeHatchOnly": [ - "FileSystemName", - "GroupId", - "GroupName" - ], - "caveat": "body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability" - }, - "annotations": [] - }, - { - "endpoint": "POST /quotas/users", - "cmdlets": [ - "New-PfbQuotaUser" - ], - "missingQueryParameters": [ - "file_system_ids", - "file_system_names", - "uids", - "user_names" - ], - "missingBodyProperties": [], - "readOnlyFields": [ - "name" - ], - "confidence": { - "level": "partial", - "unresolvedParameters": [ - { - "parameter": "FileSystemName", - "surface": "AttributesOnly", - "file": "Public/Quota/New-PfbQuotaUser.ps1", - "line": 42 - }, - { - "parameter": "UserId", - "surface": "AttributesOnly", - "file": "Public/Quota/New-PfbQuotaUser.ps1", - "line": 44 - }, - { - "parameter": "UserName", - "surface": "AttributesOnly", - "file": "Public/Quota/New-PfbQuotaUser.ps1", - "line": 43 - } - ], - "escapeHatchOnly": [ - "FileSystemName", - "UserId", - "UserName" - ], - "caveat": "body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability" - }, - "annotations": [] - }, - { - "endpoint": "POST /realms", - "cmdlets": [ - "New-PfbRealm" - ], - "missingQueryParameters": [ - "without_default_access_list" - ], - "missingBodyProperties": [], - "readOnlyFields": [], - "confidence": { - "level": "high", - "unresolvedParameters": [], - "escapeHatchOnly": [], - "caveat": "" - }, - "annotations": [] - }, - { - "endpoint": "POST /s3-export-policies", - "cmdlets": [ - "New-PfbS3ExportPolicy" - ], - "missingQueryParameters": [], - "missingBodyProperties": [ - "enabled", - "rules" - ], - "readOnlyFields": [], - "confidence": { - "level": "partial", - "unresolvedParameters": [ - { - "parameter": "Enabled", - "surface": "AttributesOnly", - "file": "Public/Policy/New-PfbS3ExportPolicy.ps1", - "line": 36 - } - ], - "escapeHatchOnly": [ - "Enabled" - ], - "caveat": "body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability" - }, - "annotations": [] - }, - { - "endpoint": "POST /servers", - "cmdlets": [ - "New-PfbServer" - ], - "missingQueryParameters": [ - "create_ds", - "create_local_directory_service" - ], - "missingBodyProperties": [], - "readOnlyFields": [], - "confidence": { - "level": "partial", - "unresolvedParameters": [ - { - "parameter": "CreateDirectoryService", - "surface": "AttributesOnly", - "file": "Public/Server/New-PfbServer.ps1", - "line": 40 + }, + { + "name": "services", + "type": "array", + "format": null, + "specRequired": false, + "synopsis": "Services that the SAML2 SSO authentication is used for.", + "suggestedPowerShellType": "[string[]]", + "enumValues": [], + "enumStatus": "not-found-in-resource", + "target": { + "file": "Public/Admin/New-PfbSaml2Idp.ps1", + "paramBlockLine": 39, + "payloadVariable": "body", + "assignmentStyle": "unknown", + "hasAttributes": true } - ], - "escapeHatchOnly": [ - "CreateDirectoryService" - ], - "caveat": "body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability" - }, - "annotations": [] - }, - { - "endpoint": "POST /smb-client-policies", - "cmdlets": [ - "New-PfbSmbClientPolicy" - ], - "missingQueryParameters": [], - "missingBodyProperties": [ - "access_based_enumeration_enabled", - "enabled", - "location", - "name", - "rules" - ], - "readOnlyFields": [ - "id", - "is_local", - "policy_type", - "realms" - ], - "confidence": { - "level": "partial", - "unresolvedParameters": [ - { - "parameter": "Enabled", - "surface": "AttributesOnly", - "file": "Public/Policy/New-PfbSmbClientPolicy.ps1", - "line": 36 + }, + { + "name": "sp", + "type": null, + "format": null, + "specRequired": false, + "synopsis": null, + "suggestedPowerShellType": "[object]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Admin/New-PfbSaml2Idp.ps1", + "paramBlockLine": 39, + "payloadVariable": "body", + "assignmentStyle": "unknown", + "hasAttributes": true } - ], - "escapeHatchOnly": [ - "Enabled" - ], - "caveat": "body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability" - }, - "annotations": [] - }, - { - "endpoint": "POST /smb-client-policies/rules", - "cmdlets": [ - "New-PfbSmbClientRule" + } ], - "missingQueryParameters": [], - "missingBodyProperties": [], "readOnlyFields": [ - "id", - "name" + "prn" ], "confidence": { "level": "high", @@ -11293,16 +12958,97 @@ "annotations": [] }, { - "endpoint": "POST /smb-share-policies", + "endpoint": "POST /storage-class-tiering-policies", "cmdlets": [ - "New-PfbSmbSharePolicy" + "New-PfbStorageClassTieringPolicy" ], "missingQueryParameters": [], "missingBodyProperties": [ - "enabled", - "location", - "name", - "rules" + { + "name": "archival_rules", + "type": "array", + "format": null, + "specRequired": false, + "synopsis": "The list of archival rules for this policy.", + "suggestedPowerShellType": "[object[]]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Policy/New-PfbStorageClassTieringPolicy.ps1", + "paramBlockLine": 32, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true + } + }, + { + "name": "enabled", + "type": "boolean", + "format": null, + "specRequired": false, + "synopsis": "If `true`, the policy is enabled.", + "suggestedPowerShellType": "[bool]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Policy/New-PfbStorageClassTieringPolicy.ps1", + "paramBlockLine": 32, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true + } + }, + { + "name": "location", + "type": null, + "format": null, + "specRequired": false, + "synopsis": "Reference to the array where the policy is defined.", + "suggestedPowerShellType": "[object]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Policy/New-PfbStorageClassTieringPolicy.ps1", + "paramBlockLine": 32, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true + } + }, + { + "name": "name", + "type": "string", + "format": null, + "specRequired": false, + "synopsis": "A user-specified name.", + "suggestedPowerShellType": "[string]", + "enumValues": [], + "enumStatus": "not-found-in-resource", + "target": { + "file": "Public/Policy/New-PfbStorageClassTieringPolicy.ps1", + "paramBlockLine": 32, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true + } + }, + { + "name": "retrieval_rules", + "type": "array", + "format": null, + "specRequired": false, + "synopsis": "The list of retrieval rules for this policy.", + "suggestedPowerShellType": "[object[]]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Policy/New-PfbStorageClassTieringPolicy.ps1", + "paramBlockLine": 32, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true + } + } ], "readOnlyFields": [ "id", @@ -11310,34 +13056,6 @@ "policy_type", "realms" ], - "confidence": { - "level": "partial", - "unresolvedParameters": [ - { - "parameter": "Enabled", - "surface": "AttributesOnly", - "file": "Public/Policy/New-PfbSmbSharePolicy.ps1", - "line": 36 - } - ], - "escapeHatchOnly": [ - "Enabled" - ], - "caveat": "body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability" - }, - "annotations": [] - }, - { - "endpoint": "POST /smb-share-policies/rules", - "cmdlets": [ - "New-PfbSmbShareRule" - ], - "missingQueryParameters": [], - "missingBodyProperties": [], - "readOnlyFields": [ - "id", - "name" - ], "confidence": { "level": "high", "unresolvedParameters": [], @@ -11347,49 +13065,19 @@ "annotations": [] }, { - "endpoint": "POST /snmp-managers", - "cmdlets": [ - "New-PfbSnmpManager" - ], - "missingQueryParameters": [ - "names" - ], - "missingBodyProperties": [ - "host", - "notification", - "v2c", - "v3", - "version" - ], - "readOnlyFields": [], - "confidence": { - "level": "partial", - "unresolvedParameters": [ - { - "parameter": "Name", - "surface": "AttributesOnly", - "file": "Public/Monitoring/New-PfbSnmpManager.ps1", - "line": 33 - } - ], - "escapeHatchOnly": [ - "Name" - ], - "caveat": "body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability" - }, - "annotations": [] - }, - { - "endpoint": "POST /software-check", + "endpoint": "POST /subnets", "cmdlets": [ - "New-PfbSoftwareCheck" - ], - "missingQueryParameters": [ - "software_names", - "software_versions" + "New-PfbSubnet" ], + "missingQueryParameters": [], "missingBodyProperties": [], - "readOnlyFields": [], + "readOnlyFields": [ + "enabled", + "id", + "interfaces", + "name", + "services" + ], "confidence": { "level": "high", "unresolvedParameters": [], @@ -11399,105 +13087,87 @@ "annotations": [] }, { - "endpoint": "POST /ssh-certificate-authority-policies", + "endpoint": "POST /support-diagnostics", "cmdlets": [ - "New-PfbSshCaPolicy" + "New-PfbSupportDiagnostics" ], "missingQueryParameters": [ - "names" - ], - "missingBodyProperties": [ - "enabled", - "location", - "name", - "signing_authority", - "static_authorized_principals" - ], - "readOnlyFields": [ - "id", - "is_local", - "policy_type", - "realms" - ], - "confidence": { - "level": "partial", - "unresolvedParameters": [ - { - "parameter": "Name", - "surface": "AttributesOnly", - "file": "Public/Policy/New-PfbSshCaPolicy.ps1", - "line": 38 - } - ], - "escapeHatchOnly": [ - "Name" - ], - "caveat": "body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability" + "analysis_period_end_time", + "analysis_period_start_time" + ], + "missingBodyProperties": [], + "readOnlyFields": [], + "confidence": { + "level": "high", + "unresolvedParameters": [], + "escapeHatchOnly": [], + "caveat": "" }, "annotations": [] }, { - "endpoint": "POST /sso/oidc/idps", + "endpoint": "POST /syslog-servers", "cmdlets": [ - "New-PfbOidcIdp" + "New-PfbSyslogServer" ], "missingQueryParameters": [], "missingBodyProperties": [ { - "name": "enabled", - "type": "boolean", + "name": "services", + "type": "array", "format": null, "specRequired": false, - "synopsis": "If set to `true`, the OIDC SSO configuration is enabled.", - "suggestedPowerShellType": "[bool]", - "enumValues": [], - "enumStatus": "no-spec-enum-found", + "synopsis": "Valid values are `data-audit` and `management`.", + "suggestedPowerShellType": "[string[]]", + "enumValues": [ + "data-audit", + "management" + ], + "enumStatus": "matched", "target": { - "file": "Public/Admin/New-PfbOidcIdp.ps1", - "paramBlockLine": 39, - "payloadVariable": "body", - "assignmentStyle": "unknown", + "file": "Public/Monitoring/New-PfbSyslogServer.ps1", + "paramBlockLine": 34, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", "hasAttributes": true } }, { - "name": "idp", - "type": null, + "name": "sources", + "type": "array", "format": null, "specRequired": false, - "synopsis": null, - "suggestedPowerShellType": "[object]", + "synopsis": "The network interfaces used for communication with the syslog server.", + "suggestedPowerShellType": "[object[]]", "enumValues": [], "enumStatus": "no-spec-enum-found", "target": { - "file": "Public/Admin/New-PfbOidcIdp.ps1", - "paramBlockLine": 39, - "payloadVariable": "body", - "assignmentStyle": "unknown", + "file": "Public/Monitoring/New-PfbSyslogServer.ps1", + "paramBlockLine": 34, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", "hasAttributes": true } }, { - "name": "services", - "type": "array", + "name": "uri", + "type": "string", "format": null, "specRequired": false, - "synopsis": "Services that the OIDC SSO authentication is used for.", - "suggestedPowerShellType": "[string[]]", + "synopsis": "The URI of the syslog server in the format PROTOCOL://HOSTNAME:PORT.", + "suggestedPowerShellType": "[string]", "enumValues": [], - "enumStatus": "not-found-in-resource", + "enumStatus": "no-spec-enum-found", "target": { - "file": "Public/Admin/New-PfbOidcIdp.ps1", - "paramBlockLine": 39, - "payloadVariable": "body", - "assignmentStyle": "unknown", + "file": "Public/Monitoring/New-PfbSyslogServer.ps1", + "paramBlockLine": 34, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", "hasAttributes": true } } ], - "readOnlyFields": [ - "prn" - ], + "readOnlyFields": [], "confidence": { "level": "high", "unresolvedParameters": [], @@ -11507,43 +13177,94 @@ "annotations": [] }, { - "endpoint": "POST /sso/saml2/idps", + "endpoint": "POST /targets", "cmdlets": [ - "New-PfbSaml2Idp" + "New-PfbTarget" ], "missingQueryParameters": [], "missingBodyProperties": [ { - "name": "array_url", + "name": "address", "type": "string", "format": null, "specRequired": false, - "synopsis": "The URL of the array.", + "synopsis": "IP address or FQDN of the target system.", "suggestedPowerShellType": "[string]", "enumValues": [], "enumStatus": "no-spec-enum-found", "target": { - "file": "Public/Admin/New-PfbSaml2Idp.ps1", - "paramBlockLine": 39, - "payloadVariable": "body", - "assignmentStyle": "unknown", + "file": "Public/Replication/New-PfbTarget.ps1", + "paramBlockLine": 33, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true + } + } + ], + "readOnlyFields": [], + "confidence": { + "level": "high", + "unresolvedParameters": [], + "escapeHatchOnly": [], + "caveat": "" + }, + "annotations": [] + }, + { + "endpoint": "POST /tls-policies", + "cmdlets": [ + "New-PfbTlsPolicy" + ], + "missingQueryParameters": [], + "missingBodyProperties": [ + { + "name": "appliance_certificate", + "type": null, + "format": null, + "specRequired": false, + "synopsis": "A reference to a certificate that will be presented as the server certificate in TLS negotiations with any clients that connect to appliance network addresses to which this policy applies.", + "suggestedPowerShellType": "[object]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Policy/New-PfbTlsPolicy.ps1", + "paramBlockLine": 31, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", "hasAttributes": true } }, { - "name": "binding", - "type": "string", + "name": "client_certificates_required", + "type": "boolean", "format": null, "specRequired": false, - "synopsis": "SAML2 binding to use for the request from Flashblade to the Identity Provider.", - "suggestedPowerShellType": "[string]", + "synopsis": "If `true`, then all clients negotiating TLS connections with network interfaces to which this policy applies will be required to provide their client certificates during TLS negotiation.", + "suggestedPowerShellType": "[bool]", "enumValues": [], "enumStatus": "no-spec-enum-found", "target": { - "file": "Public/Admin/New-PfbSaml2Idp.ps1", - "paramBlockLine": 39, - "payloadVariable": "body", - "assignmentStyle": "unknown", + "file": "Public/Policy/New-PfbTlsPolicy.ps1", + "paramBlockLine": 31, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true + } + }, + { + "name": "disabled_tls_ciphers", + "type": "array", + "format": null, + "specRequired": false, + "synopsis": "If specified, disables the specific TLS ciphers.", + "suggestedPowerShellType": "[string[]]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Policy/New-PfbTlsPolicy.ps1", + "paramBlockLine": 31, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", "hasAttributes": true } }, @@ -11552,150 +13273,127 @@ "type": "boolean", "format": null, "specRequired": false, - "synopsis": "If set to `true`, the SAML2 SSO configuration is enabled.", + "synopsis": "If `true`, the policy is enabled.", "suggestedPowerShellType": "[bool]", "enumValues": [], "enumStatus": "no-spec-enum-found", "target": { - "file": "Public/Admin/New-PfbSaml2Idp.ps1", - "paramBlockLine": 39, - "payloadVariable": "body", - "assignmentStyle": "unknown", + "file": "Public/Policy/New-PfbTlsPolicy.ps1", + "paramBlockLine": 31, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", "hasAttributes": true } }, { - "name": "idp", - "type": null, + "name": "enabled_tls_ciphers", + "type": "array", "format": null, "specRequired": false, - "synopsis": null, - "suggestedPowerShellType": "[object]", + "synopsis": "If specified, enables only the specified TLS ciphers.", + "suggestedPowerShellType": "[string[]]", "enumValues": [], "enumStatus": "no-spec-enum-found", "target": { - "file": "Public/Admin/New-PfbSaml2Idp.ps1", - "paramBlockLine": 39, - "payloadVariable": "body", - "assignmentStyle": "unknown", + "file": "Public/Policy/New-PfbTlsPolicy.ps1", + "paramBlockLine": 31, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", "hasAttributes": true } }, { - "name": "management", + "name": "location", "type": null, "format": null, "specRequired": false, - "synopsis": null, + "synopsis": "Reference to the array where the policy is defined.", "suggestedPowerShellType": "[object]", "enumValues": [], "enumStatus": "no-spec-enum-found", "target": { - "file": "Public/Admin/New-PfbSaml2Idp.ps1", - "paramBlockLine": 39, - "payloadVariable": "body", - "assignmentStyle": "unknown", + "file": "Public/Policy/New-PfbTlsPolicy.ps1", + "paramBlockLine": 31, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", "hasAttributes": true } }, { - "name": "services", - "type": "array", + "name": "min_tls_version", + "type": "string", "format": null, "specRequired": false, - "synopsis": "Services that the SAML2 SSO authentication is used for.", - "suggestedPowerShellType": "[string[]]", + "synopsis": "The minimum TLS version that will be allowed for inbound connections on IPs to which this policy applies.", + "suggestedPowerShellType": "[string]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Policy/New-PfbTlsPolicy.ps1", + "paramBlockLine": 31, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true + } + }, + { + "name": "name", + "type": "string", + "format": null, + "specRequired": false, + "synopsis": "A user-specified name.", + "suggestedPowerShellType": "[string]", "enumValues": [], "enumStatus": "not-found-in-resource", "target": { - "file": "Public/Admin/New-PfbSaml2Idp.ps1", - "paramBlockLine": 39, - "payloadVariable": "body", - "assignmentStyle": "unknown", + "file": "Public/Policy/New-PfbTlsPolicy.ps1", + "paramBlockLine": 31, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", "hasAttributes": true } }, { - "name": "sp", + "name": "trusted_client_certificate_authority", "type": null, "format": null, "specRequired": false, - "synopsis": null, + "synopsis": "A reference to a certificate or certificate group.", "suggestedPowerShellType": "[object]", "enumValues": [], "enumStatus": "no-spec-enum-found", "target": { - "file": "Public/Admin/New-PfbSaml2Idp.ps1", - "paramBlockLine": 39, - "payloadVariable": "body", - "assignmentStyle": "unknown", + "file": "Public/Policy/New-PfbTlsPolicy.ps1", + "paramBlockLine": 31, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true + } + }, + { + "name": "verify_client_certificate_trust", + "type": "boolean", + "format": null, + "specRequired": false, + "synopsis": "If `true`, then any certificate presented by a client in TLS negotiation will undergo strict trust verification using the certificate(s) referenced by `trusted_client_certificate_authority`.", + "suggestedPowerShellType": "[bool]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Policy/New-PfbTlsPolicy.ps1", + "paramBlockLine": 31, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", "hasAttributes": true } } ], - "readOnlyFields": [ - "prn" - ], - "confidence": { - "level": "high", - "unresolvedParameters": [], - "escapeHatchOnly": [], - "caveat": "" - }, - "annotations": [] - }, - { - "endpoint": "POST /storage-class-tiering-policies", - "cmdlets": [ - "New-PfbStorageClassTieringPolicy" - ], - "missingQueryParameters": [ - "names" - ], - "missingBodyProperties": [ - "archival_rules", - "enabled", - "location", - "name", - "retrieval_rules" - ], "readOnlyFields": [ "id", "is_local", "policy_type", "realms" ], - "confidence": { - "level": "partial", - "unresolvedParameters": [ - { - "parameter": "Name", - "surface": "AttributesOnly", - "file": "Public/Policy/New-PfbStorageClassTieringPolicy.ps1", - "line": 30 - } - ], - "escapeHatchOnly": [ - "Name" - ], - "caveat": "body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability" - }, - "annotations": [] - }, - { - "endpoint": "POST /subnets", - "cmdlets": [ - "New-PfbSubnet" - ], - "missingQueryParameters": [], - "missingBodyProperties": [], - "readOnlyFields": [ - "enabled", - "id", - "interfaces", - "name", - "services" - ], "confidence": { "level": "high", "unresolvedParameters": [], @@ -11705,16 +13403,36 @@ "annotations": [] }, { - "endpoint": "POST /support-diagnostics", + "endpoint": "POST /user-group-quota-policies", "cmdlets": [ - "New-PfbSupportDiagnostics" + "New-PfbUserGroupQuotaPolicy" ], - "missingQueryParameters": [ - "analysis_period_end_time", - "analysis_period_start_time" + "missingQueryParameters": [], + "missingBodyProperties": [ + { + "name": "name", + "type": "string", + "format": null, + "specRequired": false, + "synopsis": "A user-specified name.", + "suggestedPowerShellType": "[string]", + "enumValues": [], + "enumStatus": "not-found-in-resource", + "target": { + "file": "Public/Policy/New-PfbUserGroupQuotaPolicy.ps1", + "paramBlockLine": 53, + "payloadVariable": "body", + "assignmentStyle": "index", + "hasAttributes": true + } + } + ], + "readOnlyFields": [ + "id", + "is_local", + "policy_type", + "realms" ], - "missingBodyProperties": [], - "readOnlyFields": [], "confidence": { "level": "high", "unresolvedParameters": [], @@ -11724,224 +13442,174 @@ "annotations": [] }, { - "endpoint": "POST /syslog-servers", - "cmdlets": [ - "New-PfbSyslogServer" - ], - "missingQueryParameters": [ - "names" - ], - "missingBodyProperties": [ - "services", - "sources", - "uri" - ], - "readOnlyFields": [], - "confidence": { - "level": "partial", - "unresolvedParameters": [ - { - "parameter": "Name", - "surface": "AttributesOnly", - "file": "Public/Monitoring/New-PfbSyslogServer.ps1", - "line": 32 - } - ], - "escapeHatchOnly": [ - "Name" - ], - "caveat": "body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability" - }, - "annotations": [] - }, - { - "endpoint": "POST /targets", - "cmdlets": [ - "New-PfbTarget" - ], - "missingQueryParameters": [ - "names" - ], - "missingBodyProperties": [ - "address" - ], - "readOnlyFields": [], - "confidence": { - "level": "partial", - "unresolvedParameters": [ - { - "parameter": "Name", - "surface": "AttributesOnly", - "file": "Public/Replication/New-PfbTarget.ps1", - "line": 31 - } - ], - "escapeHatchOnly": [ - "Name" - ], - "caveat": "body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability" - }, - "annotations": [] - }, - { - "endpoint": "POST /tls-policies", + "endpoint": "POST /workloads/placement-recommendations", "cmdlets": [ - "New-PfbTlsPolicy" - ], - "missingQueryParameters": [ - "names" + "New-PfbWorkloadPlacementRecommendation" ], + "missingQueryParameters": [], "missingBodyProperties": [ - "appliance_certificate", - "client_certificates_required", - "disabled_tls_ciphers", - "enabled", - "enabled_tls_ciphers", - "location", - "min_tls_version", - "name", - "trusted_client_certificate_authority", - "verify_client_certificate_trust" + "additional_constraints", + "parameters", + "preset", + "projection_months", + "recommendation_engine", + "results_limit" ], "readOnlyFields": [ + "context", + "created", + "expires", "id", - "is_local", - "policy_type", - "realms" + "more_results_available", + "name", + "progress", + "results", + "status" ], "confidence": { "level": "partial", "unresolvedParameters": [ { - "parameter": "Name", - "surface": "AttributesOnly", - "file": "Public/Policy/New-PfbTlsPolicy.ps1", + "parameter": "Inputs", + "surface": "TypedUnresolved", + "file": "Public/Workloads/New-PfbWorkloadPlacementRecommendation.ps1", "line": 29 } ], - "escapeHatchOnly": [ - "Name" - ], - "caveat": "body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability" + "escapeHatchOnly": [], + "caveat": "one or more parameters could not be traced to a wire name and have no -Attributes escape hatch; lists reflect typed-parameter coverage only, not full wire reachability" }, "annotations": [] }, { - "endpoint": "POST /user-group-quota-policies", + "endpoint": "POST /worm-data-policies", "cmdlets": [ - "New-PfbUserGroupQuotaPolicy" + "New-PfbWormPolicy" ], "missingQueryParameters": [], "missingBodyProperties": [ - "enabled", - "name" - ], - "readOnlyFields": [ - "id", - "is_local", - "policy_type", - "realms" - ], - "confidence": { - "level": "partial", - "unresolvedParameters": [ - { - "parameter": "Enabled", - "surface": "AttributesOnly", - "file": "Public/Policy/New-PfbUserGroupQuotaPolicy.ps1", - "line": 49 + { + "name": "default_retention", + "type": "integer", + "format": "int64", + "specRequired": false, + "synopsis": "Default retention period, in milliseconds.", + "suggestedPowerShellType": "[long]", + "enumValues": [], + "enumStatus": "not-found-in-resource", + "target": { + "file": "Public/Policy/New-PfbWormPolicy.ps1", + "paramBlockLine": 32, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true + } + }, + { + "name": "enabled", + "type": "boolean", + "format": null, + "specRequired": false, + "synopsis": "If `true`, the policy is enabled.", + "suggestedPowerShellType": "[bool]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Policy/New-PfbWormPolicy.ps1", + "paramBlockLine": 32, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true + } + }, + { + "name": "location", + "type": null, + "format": null, + "specRequired": false, + "synopsis": "Reference to the array where the policy is defined.", + "suggestedPowerShellType": "[object]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Policy/New-PfbWormPolicy.ps1", + "paramBlockLine": 32, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true + } + }, + { + "name": "max_retention", + "type": "integer", + "format": "int64", + "specRequired": false, + "synopsis": "Maximum retention period, in milliseconds.", + "suggestedPowerShellType": "[long]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Policy/New-PfbWormPolicy.ps1", + "paramBlockLine": 32, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true + } + }, + { + "name": "min_retention", + "type": "integer", + "format": "int64", + "specRequired": false, + "synopsis": "Minimum retention period, in milliseconds.", + "suggestedPowerShellType": "[long]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Policy/New-PfbWormPolicy.ps1", + "paramBlockLine": 32, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true } - ], - "escapeHatchOnly": [ - "Enabled" - ], - "caveat": "body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability" - }, - "annotations": [] - }, - { - "endpoint": "POST /user-group-quota-policies/rules", - "cmdlets": [ - "New-PfbUserGroupQuotaPolicyRule" - ], - "missingQueryParameters": [], - "missingBodyProperties": [ - "enforced" - ], - "readOnlyFields": [], - "confidence": { - "level": "partial", - "unresolvedParameters": [ - { - "parameter": "Enforced", - "surface": "AttributesOnly", - "file": "Public/Policy/New-PfbUserGroupQuotaPolicyRule.ps1", - "line": 57 + }, + { + "name": "mode", + "type": "string", + "format": null, + "specRequired": false, + "synopsis": "The type of the retention lock.", + "suggestedPowerShellType": "[string]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Policy/New-PfbWormPolicy.ps1", + "paramBlockLine": 32, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true } - ], - "escapeHatchOnly": [ - "Enforced" - ], - "caveat": "body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability" - }, - "annotations": [] - }, - { - "endpoint": "POST /workloads/placement-recommendations", - "cmdlets": [ - "New-PfbWorkloadPlacementRecommendation" - ], - "missingQueryParameters": [], - "missingBodyProperties": [ - "additional_constraints", - "parameters", - "preset", - "projection_months", - "recommendation_engine", - "results_limit" - ], - "readOnlyFields": [ - "context", - "created", - "expires", - "id", - "more_results_available", - "name", - "progress", - "results", - "status" - ], - "confidence": { - "level": "partial", - "unresolvedParameters": [ - { - "parameter": "Inputs", - "surface": "TypedUnresolved", - "file": "Public/Workloads/New-PfbWorkloadPlacementRecommendation.ps1", - "line": 29 + }, + { + "name": "retention_lock", + "type": "string", + "format": null, + "specRequired": false, + "synopsis": "If set to `locked`, then the value of retention attributes or policy attributes are not allowed to change.", + "suggestedPowerShellType": "[string]", + "enumValues": [ + "unlocked", + "locked" + ], + "enumStatus": "matched", + "target": { + "file": "Public/Policy/New-PfbWormPolicy.ps1", + "paramBlockLine": 32, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true } - ], - "escapeHatchOnly": [], - "caveat": "one or more parameters could not be traced to a wire name and have no -Attributes escape hatch; lists reflect typed-parameter coverage only, not full wire reachability" - }, - "annotations": [] - }, - { - "endpoint": "POST /worm-data-policies", - "cmdlets": [ - "New-PfbWormPolicy" - ], - "missingQueryParameters": [ - "names" - ], - "missingBodyProperties": [ - "default_retention", - "enabled", - "location", - "max_retention", - "min_retention", - "mode", - "retention_lock" + } ], "readOnlyFields": [ "context", @@ -11952,19 +13620,10 @@ "realms" ], "confidence": { - "level": "partial", - "unresolvedParameters": [ - { - "parameter": "Name", - "surface": "AttributesOnly", - "file": "Public/Policy/New-PfbWormPolicy.ps1", - "line": 30 - } - ], - "escapeHatchOnly": [ - "Name" - ], - "caveat": "body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability" + "level": "high", + "unresolvedParameters": [], + "escapeHatchOnly": [], + "caveat": "" }, "annotations": [] }, @@ -12280,8 +13939,8 @@ "systemicGaps": [ { "name": "allow_errors", - "endpointCount": 118, - "queryEndpointCount": 118, + "endpointCount": 119, + "queryEndpointCount": 119, "bodyEndpointCount": 0, "endpoints": [ "GET /active-directory/test", @@ -12394,6 +14053,7 @@ "GET /targets", "GET /usage/groups", "GET /usage/users", + "GET /user-group-quota-policies", "GET /user-group-quota-policies/file-systems", "GET /user-group-quota-policies/members", "GET /user-group-quota-policies/rules", @@ -12415,8 +14075,8 @@ }, { "name": "ids", - "endpointCount": 38, - "queryEndpointCount": 38, + "endpointCount": 39, + "queryEndpointCount": 39, "bodyEndpointCount": 0, "endpoints": [ "DELETE /network-access-policies/rules", @@ -12451,6 +14111,7 @@ "GET /support", "GET /syslog-servers/settings", "GET /targets/performance/replication", + "PATCH /directory-services", "PATCH /network-access-policies/rules", "PATCH /nfs-export-policies/rules", "PATCH /password-policies", @@ -12497,12 +14158,50 @@ ], "annotations": [] }, + { + "name": "name", + "endpointCount": 27, + "queryEndpointCount": 0, + "bodyEndpointCount": 27, + "endpoints": [ + "PATCH /arrays", + "PATCH /audit-file-systems-policies", + "PATCH /audit-object-store-policies", + "PATCH /file-system-snapshots", + "PATCH /network-access-policies", + "PATCH /nfs-export-policies", + "PATCH /password-policies", + "PATCH /realms", + "PATCH /s3-export-policies", + "PATCH /smb-client-policies", + "PATCH /smb-share-policies", + "PATCH /user-group-quota-policies", + "POST /audit-file-systems-policies", + "POST /audit-object-store-policies", + "POST /log-targets/file-systems", + "POST /log-targets/object-store", + "POST /management-access-policies", + "POST /nfs-export-policies", + "POST /policies", + "POST /qos-policies", + "POST /smb-client-policies", + "POST /smb-share-policies", + "POST /ssh-certificate-authority-policies", + "POST /storage-class-tiering-policies", + "POST /tls-policies", + "POST /user-group-quota-policies", + "PUT /presets/workload" + ], + "annotations": [] + }, { "name": "names", - "endpointCount": 23, - "queryEndpointCount": 23, + "endpointCount": 27, + "queryEndpointCount": 27, "bodyEndpointCount": 0, "endpoints": [ + "DELETE /quotas/groups", + "DELETE /quotas/users", "GET /arrays/clients/performance", "GET /arrays/clients/s3-specific-performance", "GET /arrays/supported-time-zones", @@ -12522,6 +14221,8 @@ "GET /software-check", "GET /syslog-servers/settings", "PATCH /password-policies", + "PATCH /quotas/groups", + "PATCH /quotas/users", "PATCH /syslog-servers/settings", "POST /maintenance-windows", "POST /object-store-access-keys", @@ -12529,6 +14230,36 @@ ], "annotations": [] }, + { + "name": "location", + "endpointCount": 21, + "queryEndpointCount": 0, + "bodyEndpointCount": 21, + "endpoints": [ + "PATCH /audit-file-systems-policies", + "PATCH /audit-object-store-policies", + "PATCH /data-eviction-policies", + "PATCH /network-access-policies", + "PATCH /nfs-export-policies", + "PATCH /password-policies", + "PATCH /policies", + "PATCH /smb-client-policies", + "PATCH /smb-share-policies", + "POST /audit-file-systems-policies", + "POST /audit-object-store-policies", + "POST /management-access-policies", + "POST /nfs-export-policies", + "POST /policies", + "POST /qos-policies", + "POST /smb-client-policies", + "POST /smb-share-policies", + "POST /ssh-certificate-authority-policies", + "POST /storage-class-tiering-policies", + "POST /tls-policies", + "POST /worm-data-policies" + ], + "annotations": [] + }, { "name": "total_only", "endpointCount": 17, @@ -12580,6 +14311,53 @@ ], "annotations": [] }, + { + "name": "rules", + "endpointCount": 15, + "queryEndpointCount": 0, + "bodyEndpointCount": 15, + "endpoints": [ + "PATCH /audit-file-systems-policies", + "PATCH /network-access-policies", + "PATCH /nfs-export-policies", + "PATCH /s3-export-policies", + "PATCH /smb-client-policies", + "PATCH /smb-share-policies", + "POST /audit-file-systems-policies", + "POST /buckets/bucket-access-policies", + "POST /buckets/cross-origin-resource-sharing-policies", + "POST /management-access-policies", + "POST /nfs-export-policies", + "POST /object-store-access-policies", + "POST /s3-export-policies", + "POST /smb-client-policies", + "POST /smb-share-policies" + ], + "annotations": [] + }, + { + "name": "file_system_ids", + "endpointCount": 14, + "queryEndpointCount": 14, + "bodyEndpointCount": 0, + "endpoints": [ + "DELETE /file-systems/locks", + "DELETE /quotas/groups", + "DELETE /quotas/users", + "GET /file-systems/locks", + "GET /file-systems/open-files", + "GET /legal-holds/held-entities", + "GET /quotas/groups", + "GET /quotas/users", + "GET /usage/groups", + "GET /usage/users", + "PATCH /quotas/groups", + "PATCH /quotas/users", + "POST /quotas/groups", + "POST /quotas/users" + ], + "annotations": [] + }, { "name": "member_ids", "endpointCount": 14, @@ -12603,6 +14381,26 @@ ], "annotations": [] }, + { + "name": "enabled", + "endpointCount": 11, + "queryEndpointCount": 0, + "bodyEndpointCount": 11, + "endpoints": [ + "PATCH /directory-services", + "PATCH /password-policies", + "PATCH /rapid-data-locking", + "POST /management-access-policies", + "POST /qos-policies", + "POST /ssh-certificate-authority-policies", + "POST /sso/oidc/idps", + "POST /sso/saml2/idps", + "POST /storage-class-tiering-policies", + "POST /tls-policies", + "POST /worm-data-policies" + ], + "annotations": [] + }, { "name": "bucket_ids", "endpointCount": 10, @@ -12641,84 +14439,53 @@ "annotations": [] }, { - "name": "file_system_ids", - "endpointCount": 8, - "queryEndpointCount": 8, - "bodyEndpointCount": 0, - "endpoints": [ - "DELETE /file-systems/locks", - "GET /file-systems/locks", - "GET /file-systems/open-files", - "GET /legal-holds/held-entities", - "GET /quotas/groups", - "GET /quotas/users", - "GET /usage/groups", - "GET /usage/users" - ], - "annotations": [] - }, - { - "name": "local_file_system_ids", - "endpointCount": 8, - "queryEndpointCount": 8, - "bodyEndpointCount": 0, - "endpoints": [ - "DELETE /file-system-replica-links", - "DELETE /file-system-replica-links/policies", - "DELETE /policies/file-system-replica-links", - "GET /file-system-replica-links", - "GET /file-system-replica-links/policies", - "GET /policies-all/members", - "GET /policies/file-system-replica-links", - "POST /file-system-replica-links" - ], - "annotations": [] - }, - { - "name": "limit", - "endpointCount": 7, - "queryEndpointCount": 7, + "name": "versions", + "endpointCount": 9, + "queryEndpointCount": 9, "bodyEndpointCount": 0, "endpoints": [ - "GET /active-directory", - "GET /active-directory/test", - "GET /directory-services", - "GET /directory-services/test", - "GET /snmp-agents", - "GET /snmp-managers/test", - "GET /sso/saml2/idps/test" + "DELETE /network-access-policies/rules", + "DELETE /nfs-export-policies", + "DELETE /nfs-export-policies/rules", + "DELETE /smb-client-policies/rules", + "PATCH /network-access-policies", + "PATCH /network-access-policies/rules", + "PATCH /nfs-export-policies", + "PATCH /nfs-export-policies/rules", + "PATCH /smb-client-policies/rules" ], "annotations": [] }, - { - "name": "name", - "endpointCount": 7, - "queryEndpointCount": 0, - "bodyEndpointCount": 7, - "endpoints": [ - "PATCH /arrays", - "PATCH /password-policies", - "POST /log-targets/file-systems", - "POST /log-targets/object-store", - "POST /management-access-policies", - "POST /qos-policies", - "PUT /presets/workload" + { + "name": "local_file_system_ids", + "endpointCount": 8, + "queryEndpointCount": 8, + "bodyEndpointCount": 0, + "endpoints": [ + "DELETE /file-system-replica-links", + "DELETE /file-system-replica-links/policies", + "DELETE /policies/file-system-replica-links", + "GET /file-system-replica-links", + "GET /file-system-replica-links/policies", + "GET /policies-all/members", + "GET /policies/file-system-replica-links", + "POST /file-system-replica-links" ], "annotations": [] }, { - "name": "versions", + "name": "limit", "endpointCount": 7, "queryEndpointCount": 7, "bodyEndpointCount": 0, "endpoints": [ - "DELETE /network-access-policies/rules", - "DELETE /nfs-export-policies", - "DELETE /nfs-export-policies/rules", - "DELETE /smb-client-policies/rules", - "PATCH /network-access-policies/rules", - "PATCH /nfs-export-policies/rules", - "PATCH /smb-client-policies/rules" + "GET /active-directory", + "GET /active-directory/test", + "GET /directory-services", + "GET /directory-services/test", + "GET /snmp-agents", + "GET /snmp-managers/test", + "GET /sso/saml2/idps/test" ], "annotations": [] }, @@ -12737,21 +14504,6 @@ ], "annotations": [] }, - { - "name": "enabled", - "endpointCount": 6, - "queryEndpointCount": 0, - "bodyEndpointCount": 6, - "endpoints": [ - "PATCH /password-policies", - "PATCH /rapid-data-locking", - "POST /management-access-policies", - "POST /qos-policies", - "POST /sso/oidc/idps", - "POST /sso/saml2/idps" - ], - "annotations": [] - }, { "name": "filter", "endpointCount": 6, @@ -12767,6 +14519,21 @@ ], "annotations": [] }, + { + "name": "policy", + "endpointCount": 6, + "queryEndpointCount": 0, + "bodyEndpointCount": 6, + "endpoints": [ + "PATCH /file-system-snapshots", + "PATCH /network-access-policies/rules", + "PATCH /object-store-roles/object-store-trust-policies/rules", + "PATCH /smb-client-policies/rules", + "PATCH /smb-share-policies/rules", + "POST /object-store-roles/object-store-trust-policies/rules" + ], + "annotations": [] + }, { "name": "role_ids", "endpointCount": 6, @@ -12797,6 +14564,21 @@ ], "annotations": [] }, + { + "name": "user_names", + "endpointCount": 6, + "queryEndpointCount": 6, + "bodyEndpointCount": 0, + "endpoints": [ + "DELETE /file-systems/sessions", + "GET /file-systems/open-files", + "GET /file-systems/sessions", + "GET /file-systems/users/performance", + "GET /quotas/users", + "GET /usage/users" + ], + "annotations": [] + }, { "name": "workload_ids", "endpointCount": 6, @@ -12827,6 +14609,20 @@ ], "annotations": [] }, + { + "name": "client_names", + "endpointCount": 5, + "queryEndpointCount": 5, + "bodyEndpointCount": 0, + "endpoints": [ + "DELETE /file-systems/locks", + "DELETE /file-systems/sessions", + "GET /file-systems/locks", + "GET /file-systems/open-files", + "GET /file-systems/sessions" + ], + "annotations": [] + }, { "name": "gids", "endpointCount": 5, @@ -12855,20 +14651,6 @@ ], "annotations": [] }, - { - "name": "policy", - "endpointCount": 5, - "queryEndpointCount": 0, - "bodyEndpointCount": 5, - "endpoints": [ - "PATCH /network-access-policies/rules", - "PATCH /object-store-roles/object-store-trust-policies/rules", - "PATCH /smb-client-policies/rules", - "PATCH /smb-share-policies/rules", - "POST /object-store-roles/object-store-trust-policies/rules" - ], - "annotations": [] - }, { "name": "remote_file_system_ids", "endpointCount": 5, @@ -12898,29 +14680,42 @@ "annotations": [] }, { - "name": "user_names", + "name": "uids", "endpointCount": 5, "queryEndpointCount": 5, "bodyEndpointCount": 0, "endpoints": [ - "GET /file-systems/open-files", - "GET /file-systems/sessions", + "DELETE /quotas/users", "GET /file-systems/users/performance", "GET /quotas/users", - "GET /usage/users" + "GET /usage/users", + "PATCH /quotas/users" ], "annotations": [] }, { - "name": "client_names", + "name": "ca_certificate", "endpointCount": 4, - "queryEndpointCount": 4, - "bodyEndpointCount": 0, + "queryEndpointCount": 0, + "bodyEndpointCount": 4, "endpoints": [ - "DELETE /file-systems/locks", - "GET /file-systems/locks", - "GET /file-systems/open-files", - "GET /file-systems/sessions" + "PATCH /directory-services", + "PATCH /syslog-servers/settings", + "POST /active-directory", + "POST /dns" + ], + "annotations": [] + }, + { + "name": "ca_certificate_group", + "endpointCount": 4, + "queryEndpointCount": 0, + "bodyEndpointCount": 4, + "endpoints": [ + "PATCH /directory-services", + "PATCH /syslog-servers/settings", + "POST /active-directory", + "POST /dns" ], "annotations": [] }, @@ -12989,6 +14784,19 @@ ], "annotations": [] }, + { + "name": "log_targets", + "endpointCount": 4, + "queryEndpointCount": 0, + "bodyEndpointCount": 4, + "endpoints": [ + "PATCH /audit-file-systems-policies", + "PATCH /audit-object-store-policies", + "POST /audit-file-systems-policies", + "POST /audit-object-store-policies" + ], + "annotations": [] + }, { "name": "member_types", "endpointCount": 4, @@ -13042,15 +14850,15 @@ "annotations": [] }, { - "name": "rules", + "name": "services", "endpointCount": 4, "queryEndpointCount": 0, "bodyEndpointCount": 4, "endpoints": [ - "POST /buckets/bucket-access-policies", - "POST /buckets/cross-origin-resource-sharing-policies", - "POST /management-access-policies", - "POST /object-store-access-policies" + "POST /dns", + "POST /sso/oidc/idps", + "POST /sso/saml2/idps", + "POST /syslog-servers" ], "annotations": [] }, @@ -13139,6 +14947,18 @@ ], "annotations": [] }, + { + "name": "eradication_config", + "endpointCount": 3, + "queryEndpointCount": 0, + "bodyEndpointCount": 3, + "endpoints": [ + "PATCH /arrays", + "PATCH /buckets", + "POST /buckets" + ], + "annotations": [] + }, { "name": "group_names", "endpointCount": 3, @@ -13151,6 +14971,18 @@ ], "annotations": [] }, + { + "name": "hard_limit_enabled", + "endpointCount": 3, + "queryEndpointCount": 0, + "bodyEndpointCount": 3, + "endpoints": [ + "PATCH /buckets", + "POST /buckets", + "POST /object-store-accounts" + ], + "annotations": [] + }, { "name": "index", "endpointCount": 3, @@ -13199,18 +15031,6 @@ ], "annotations": [] }, - { - "name": "location", - "endpointCount": 3, - "queryEndpointCount": 0, - "bodyEndpointCount": 3, - "endpoints": [ - "PATCH /password-policies", - "POST /management-access-policies", - "POST /qos-policies" - ], - "annotations": [] - }, { "name": "principals", "endpointCount": 3, @@ -13235,6 +15055,18 @@ ], "annotations": [] }, + { + "name": "retention_lock", + "endpointCount": 3, + "queryEndpointCount": 0, + "bodyEndpointCount": 3, + "endpoints": [ + "PATCH /buckets", + "POST /buckets", + "POST /worm-data-policies" + ], + "annotations": [] + }, { "name": "role", "endpointCount": 3, @@ -13260,14 +15092,35 @@ "annotations": [] }, { - "name": "uids", - "endpointCount": 3, - "queryEndpointCount": 3, - "bodyEndpointCount": 0, + "name": "access_based_enumeration_enabled", + "endpointCount": 2, + "queryEndpointCount": 0, + "bodyEndpointCount": 2, "endpoints": [ - "GET /file-systems/users/performance", - "GET /quotas/users", - "GET /usage/users" + "PATCH /smb-client-policies", + "POST /smb-client-policies" + ], + "annotations": [] + }, + { + "name": "add_log_targets", + "endpointCount": 2, + "queryEndpointCount": 0, + "bodyEndpointCount": 2, + "endpoints": [ + "PATCH /audit-file-systems-policies", + "PATCH /audit-object-store-policies" + ], + "annotations": [] + }, + { + "name": "certificate", + "endpointCount": 2, + "queryEndpointCount": 0, + "bodyEndpointCount": 2, + "endpoints": [ + "POST /certificates", + "POST /certificates/certificate-signing-requests" ], "annotations": [] }, @@ -13277,19 +15130,74 @@ "queryEndpointCount": 2, "bodyEndpointCount": 0, "endpoints": [ - "DELETE /certificates/certificate-groups", - "GET /certificates/certificate-groups" + "DELETE /certificates/certificate-groups", + "GET /certificates/certificate-groups" + ], + "annotations": [] + }, + { + "name": "common_name", + "endpointCount": 2, + "queryEndpointCount": 0, + "bodyEndpointCount": 2, + "endpoints": [ + "POST /certificates", + "POST /certificates/certificate-signing-requests" + ], + "annotations": [] + }, + { + "name": "component_name", + "endpointCount": 2, + "queryEndpointCount": 2, + "bodyEndpointCount": 0, + "endpoints": [ + "GET /network-interfaces/ping", + "GET /network-interfaces/trace" + ], + "annotations": [] + }, + { + "name": "control_type", + "endpointCount": 2, + "queryEndpointCount": 0, + "bodyEndpointCount": 2, + "endpoints": [ + "PATCH /audit-file-systems-policies", + "POST /audit-file-systems-policies" + ], + "annotations": [] + }, + { + "name": "country", + "endpointCount": 2, + "queryEndpointCount": 0, + "bodyEndpointCount": 2, + "endpoints": [ + "POST /certificates", + "POST /certificates/certificate-signing-requests" + ], + "annotations": [] + }, + { + "name": "default_inbound_tls_policy", + "endpointCount": 2, + "queryEndpointCount": 0, + "bodyEndpointCount": 2, + "endpoints": [ + "PATCH /arrays", + "PATCH /realms" ], "annotations": [] }, { - "name": "component_name", + "name": "destroyed", "endpointCount": 2, - "queryEndpointCount": 2, - "bodyEndpointCount": 0, + "queryEndpointCount": 1, + "bodyEndpointCount": 1, "endpoints": [ - "GET /network-interfaces/ping", - "GET /network-interfaces/trace" + "GET /directory-services/local/directory-services", + "PATCH /file-system-snapshots" ], "annotations": [] }, @@ -13305,24 +15213,35 @@ "annotations": [] }, { - "name": "eradicate_all_data", + "name": "domain", "endpointCount": 2, - "queryEndpointCount": 2, - "bodyEndpointCount": 0, + "queryEndpointCount": 0, + "bodyEndpointCount": 2, "endpoints": [ - "PATCH /arrays/erasures", - "POST /arrays/erasures" + "POST /active-directory", + "POST /dns" ], "annotations": [] }, { - "name": "eradication_config", + "name": "email", "endpointCount": 2, "queryEndpointCount": 0, "bodyEndpointCount": 2, "endpoints": [ - "PATCH /arrays", - "POST /buckets" + "POST /certificates", + "POST /certificates/certificate-signing-requests" + ], + "annotations": [] + }, + { + "name": "eradicate_all_data", + "endpointCount": 2, + "queryEndpointCount": 2, + "bodyEndpointCount": 0, + "endpoints": [ + "PATCH /arrays/erasures", + "POST /arrays/erasures" ], "annotations": [] }, @@ -13359,17 +15278,6 @@ ], "annotations": [] }, - { - "name": "hard_limit_enabled", - "endpointCount": 2, - "queryEndpointCount": 0, - "bodyEndpointCount": 2, - "endpoints": [ - "POST /buckets", - "POST /object-store-accounts" - ], - "annotations": [] - }, { "name": "idp", "endpointCount": 2, @@ -13392,6 +15300,17 @@ ], "annotations": [] }, + { + "name": "locality", + "endpointCount": 2, + "queryEndpointCount": 0, + "bodyEndpointCount": 2, + "endpoints": [ + "POST /certificates", + "POST /certificates/certificate-signing-requests" + ], + "annotations": [] + }, { "name": "lockout_duration", "endpointCount": 2, @@ -13403,6 +15322,17 @@ ], "annotations": [] }, + { + "name": "management", + "endpointCount": 2, + "queryEndpointCount": 0, + "bodyEndpointCount": 2, + "endpoints": [ + "PATCH /directory-services", + "POST /sso/saml2/idps" + ], + "annotations": [] + }, { "name": "max_login_attempts", "endpointCount": 2, @@ -13502,6 +15432,39 @@ ], "annotations": [] }, + { + "name": "object_lock_config", + "endpointCount": 2, + "queryEndpointCount": 0, + "bodyEndpointCount": 2, + "endpoints": [ + "PATCH /buckets", + "POST /buckets" + ], + "annotations": [] + }, + { + "name": "organization", + "endpointCount": 2, + "queryEndpointCount": 0, + "bodyEndpointCount": 2, + "endpoints": [ + "POST /certificates", + "POST /certificates/certificate-signing-requests" + ], + "annotations": [] + }, + { + "name": "organizational_unit", + "endpointCount": 2, + "queryEndpointCount": 0, + "bodyEndpointCount": 2, + "endpoints": [ + "POST /certificates", + "POST /certificates/certificate-signing-requests" + ], + "annotations": [] + }, { "name": "parameters", "endpointCount": 2, @@ -13579,6 +15542,17 @@ ], "annotations": [] }, + { + "name": "remove_log_targets", + "endpointCount": 2, + "queryEndpointCount": 0, + "bodyEndpointCount": 2, + "endpoints": [ + "PATCH /audit-file-systems-policies", + "PATCH /audit-object-store-policies" + ], + "annotations": [] + }, { "name": "resolve_hostname", "endpointCount": 2, @@ -13601,17 +15575,6 @@ ], "annotations": [] }, - { - "name": "services", - "endpointCount": 2, - "queryEndpointCount": 0, - "bodyEndpointCount": 2, - "endpoints": [ - "POST /sso/oidc/idps", - "POST /sso/saml2/idps" - ], - "annotations": [] - }, { "name": "sids", "endpointCount": 2, @@ -13656,6 +15619,83 @@ ], "annotations": [] }, + { + "name": "source", + "endpointCount": 2, + "queryEndpointCount": 0, + "bodyEndpointCount": 2, + "endpoints": [ + "PATCH /file-system-snapshots", + "POST /keytabs" + ], + "annotations": [] + }, + { + "name": "sources", + "endpointCount": 2, + "queryEndpointCount": 0, + "bodyEndpointCount": 2, + "endpoints": [ + "POST /dns", + "POST /syslog-servers" + ], + "annotations": [] + }, + { + "name": "state", + "endpointCount": 2, + "queryEndpointCount": 0, + "bodyEndpointCount": 2, + "endpoints": [ + "POST /certificates", + "POST /certificates/certificate-signing-requests" + ], + "annotations": [] + }, + { + "name": "subject_alternative_names", + "endpointCount": 2, + "queryEndpointCount": 0, + "bodyEndpointCount": 2, + "endpoints": [ + "POST /certificates", + "POST /certificates/certificate-signing-requests" + ], + "annotations": [] + }, + { + "name": "v2c", + "endpointCount": 2, + "queryEndpointCount": 0, + "bodyEndpointCount": 2, + "endpoints": [ + "PATCH /snmp-agents", + "POST /snmp-managers" + ], + "annotations": [] + }, + { + "name": "v3", + "endpointCount": 2, + "queryEndpointCount": 0, + "bodyEndpointCount": 2, + "endpoints": [ + "PATCH /snmp-agents", + "POST /snmp-managers" + ], + "annotations": [] + }, + { + "name": "version", + "endpointCount": 2, + "queryEndpointCount": 0, + "bodyEndpointCount": 2, + "endpoints": [ + "PATCH /snmp-agents", + "POST /snmp-managers" + ], + "annotations": [] + }, { "name": "volume_configurations", "endpointCount": 2, @@ -13749,6 +15789,26 @@ ], "annotations": [] }, + { + "name": "add_rules", + "endpointCount": 1, + "queryEndpointCount": 0, + "bodyEndpointCount": 1, + "endpoints": [ + "PATCH /policies" + ], + "annotations": [] + }, + { + "name": "address", + "endpointCount": 1, + "queryEndpointCount": 0, + "bodyEndpointCount": 1, + "endpoints": [ + "POST /targets" + ], + "annotations": [] + }, { "name": "aggregation_strategy", "endpointCount": 1, @@ -13829,6 +15889,26 @@ ], "annotations": [] }, + { + "name": "appliance_certificate", + "endpointCount": 1, + "queryEndpointCount": 0, + "bodyEndpointCount": 1, + "endpoints": [ + "POST /tls-policies" + ], + "annotations": [] + }, + { + "name": "archival_rules", + "endpointCount": 1, + "queryEndpointCount": 0, + "bodyEndpointCount": 1, + "endpoints": [ + "POST /storage-class-tiering-policies" + ], + "annotations": [] + }, { "name": "array_url", "endpointCount": 1, @@ -13870,72 +15950,92 @@ "annotations": [] }, { - "name": "binding", + "name": "base_dn", "endpointCount": 1, "queryEndpointCount": 0, "bodyEndpointCount": 1, "endpoints": [ - "POST /sso/saml2/idps" + "PATCH /directory-services" ], "annotations": [] }, { - "name": "bucket", + "name": "bind_password", "endpointCount": 1, "queryEndpointCount": 0, "bodyEndpointCount": 1, "endpoints": [ - "POST /log-targets/object-store" + "PATCH /directory-services" ], "annotations": [] }, { - "name": "bucket_defaults", + "name": "bind_user", "endpointCount": 1, "queryEndpointCount": 0, "bodyEndpointCount": 1, "endpoints": [ - "POST /object-store-accounts" + "PATCH /directory-services" ], "annotations": [] }, { - "name": "bucket_type", + "name": "binding", "endpointCount": 1, "queryEndpointCount": 0, "bodyEndpointCount": 1, "endpoints": [ - "POST /buckets" + "POST /sso/saml2/idps" ], "annotations": [] }, { - "name": "ca_certificate", + "name": "bucket", "endpointCount": 1, "queryEndpointCount": 0, "bodyEndpointCount": 1, "endpoints": [ - "PATCH /syslog-servers/settings" + "POST /log-targets/object-store" ], "annotations": [] }, { - "name": "ca_certificate_group", + "name": "bucket_defaults", "endpointCount": 1, "queryEndpointCount": 0, "bodyEndpointCount": 1, "endpoints": [ - "PATCH /syslog-servers/settings" + "POST /object-store-accounts" ], "annotations": [] }, { - "name": "certificate", + "name": "bucket_type", "endpointCount": 1, "queryEndpointCount": 0, "bodyEndpointCount": 1, "endpoints": [ - "POST /certificates" + "POST /buckets" + ], + "annotations": [] + }, + { + "name": "cancel_in_progress_storage_class_transition", + "endpointCount": 1, + "queryEndpointCount": 1, + "bodyEndpointCount": 0, + "endpoints": [ + "PATCH /buckets" + ], + "annotations": [] + }, + { + "name": "cascade_delete", + "endpointCount": 1, + "queryEndpointCount": 1, + "bodyEndpointCount": 0, + "endpoints": [ + "DELETE /servers" ], "annotations": [] }, @@ -13970,12 +16070,22 @@ "annotations": [] }, { - "name": "common_name", + "name": "client_certificates_required", + "endpointCount": 1, + "queryEndpointCount": 0, + "bodyEndpointCount": 1, + "endpoints": [ + "POST /tls-policies" + ], + "annotations": [] + }, + { + "name": "computer_name", "endpointCount": 1, "queryEndpointCount": 0, "bodyEndpointCount": 1, "endpoints": [ - "POST /certificates" + "POST /active-directory" ], "annotations": [] }, @@ -13999,16 +16109,6 @@ ], "annotations": [] }, - { - "name": "country", - "endpointCount": 1, - "queryEndpointCount": 0, - "bodyEndpointCount": 1, - "endpoints": [ - "POST /certificates" - ], - "annotations": [] - }, { "name": "current_state", "endpointCount": 1, @@ -14030,12 +16130,12 @@ "annotations": [] }, { - "name": "default_inbound_tls_policy", + "name": "default_retention", "endpointCount": 1, "queryEndpointCount": 0, "bodyEndpointCount": 1, "endpoints": [ - "PATCH /arrays" + "POST /worm-data-policies" ], "annotations": [] }, @@ -14050,12 +16150,12 @@ "annotations": [] }, { - "name": "destroyed", + "name": "destroy_snapshots", "endpointCount": 1, "queryEndpointCount": 1, "bodyEndpointCount": 0, "endpoints": [ - "GET /directory-services/local/directory-services" + "PATCH /policies" ], "annotations": [] }, @@ -14079,6 +16179,26 @@ ], "annotations": [] }, + { + "name": "directory_servers", + "endpointCount": 1, + "queryEndpointCount": 0, + "bodyEndpointCount": 1, + "endpoints": [ + "POST /active-directory" + ], + "annotations": [] + }, + { + "name": "disabled_tls_ciphers", + "endpointCount": 1, + "queryEndpointCount": 0, + "bodyEndpointCount": 1, + "endpoints": [ + "POST /tls-policies" + ], + "annotations": [] + }, { "name": "discover_mtu", "endpointCount": 1, @@ -14089,6 +16209,16 @@ ], "annotations": [] }, + { + "name": "disruptive", + "endpointCount": 1, + "queryEndpointCount": 1, + "bodyEndpointCount": 0, + "endpoints": [ + "DELETE /file-systems/sessions" + ], + "annotations": [] + }, { "name": "edge_agent_update_enabled", "endpointCount": 1, @@ -14120,12 +16250,12 @@ "annotations": [] }, { - "name": "email", + "name": "enabled_tls_ciphers", "endpointCount": 1, "queryEndpointCount": 0, "bodyEndpointCount": 1, "endpoints": [ - "POST /certificates" + "POST /tls-policies" ], "annotations": [] }, @@ -14139,6 +16269,16 @@ ], "annotations": [] }, + { + "name": "encryption_types", + "endpointCount": 1, + "queryEndpointCount": 0, + "bodyEndpointCount": 1, + "endpoints": [ + "POST /active-directory" + ], + "annotations": [] + }, { "name": "enforce_dictionary_check", "endpointCount": 1, @@ -14219,6 +16359,16 @@ ], "annotations": [] }, + { + "name": "fqdns", + "endpointCount": 1, + "queryEndpointCount": 0, + "bodyEndpointCount": 1, + "endpoints": [ + "POST /active-directory" + ], + "annotations": [] + }, { "name": "fragment_packet", "endpointCount": 1, @@ -14249,6 +16399,16 @@ ], "annotations": [] }, + { + "name": "global_catalog_servers", + "endpointCount": 1, + "queryEndpointCount": 0, + "bodyEndpointCount": 1, + "endpoints": [ + "POST /active-directory" + ], + "annotations": [] + }, { "name": "group", "endpointCount": 1, @@ -14269,6 +16429,16 @@ ], "annotations": [] }, + { + "name": "host", + "endpointCount": 1, + "queryEndpointCount": 0, + "bodyEndpointCount": 1, + "endpoints": [ + "POST /snmp-managers" + ], + "annotations": [] + }, { "name": "idle_timeout", "endpointCount": 1, @@ -14279,6 +16449,16 @@ ], "annotations": [] }, + { + "name": "ignore_usage", + "endpointCount": 1, + "queryEndpointCount": 1, + "bodyEndpointCount": 0, + "endpoints": [ + "PATCH /buckets" + ], + "annotations": [] + }, { "name": "interfaces", "endpointCount": 1, @@ -14309,6 +16489,26 @@ ], "annotations": [] }, + { + "name": "join_existing_account", + "endpointCount": 1, + "queryEndpointCount": 1, + "bodyEndpointCount": 0, + "endpoints": [ + "POST /active-directory" + ], + "annotations": [] + }, + { + "name": "join_ou", + "endpointCount": 1, + "queryEndpointCount": 0, + "bodyEndpointCount": 1, + "endpoints": [ + "POST /active-directory" + ], + "annotations": [] + }, { "name": "keep_current_version_for", "endpointCount": 1, @@ -14359,6 +16559,16 @@ ], "annotations": [] }, + { + "name": "kerberos_servers", + "endpointCount": 1, + "queryEndpointCount": 0, + "bodyEndpointCount": 1, + "endpoints": [ + "POST /active-directory" + ], + "annotations": [] + }, { "name": "key_algorithm", "endpointCount": 1, @@ -14419,6 +16629,16 @@ ], "annotations": [] }, + { + "name": "latest_replica", + "endpointCount": 1, + "queryEndpointCount": 1, + "bodyEndpointCount": 0, + "endpoints": [ + "PATCH /file-system-snapshots" + ], + "annotations": [] + }, { "name": "link_type", "endpointCount": 1, @@ -14479,16 +16699,6 @@ ], "annotations": [] }, - { - "name": "locality", - "endpointCount": 1, - "queryEndpointCount": 0, - "bodyEndpointCount": 1, - "endpoints": [ - "POST /certificates" - ], - "annotations": [] - }, { "name": "log_name_prefix", "endpointCount": 1, @@ -14510,32 +16720,32 @@ "annotations": [] }, { - "name": "management", + "name": "management_access_policies", "endpointCount": 1, "queryEndpointCount": 0, "bodyEndpointCount": 1, "endpoints": [ - "POST /sso/saml2/idps" + "POST /directory-services/roles" ], "annotations": [] }, { - "name": "management_access_policies", + "name": "max_password_age", "endpointCount": 1, "queryEndpointCount": 0, "bodyEndpointCount": 1, "endpoints": [ - "POST /directory-services/roles" + "PATCH /password-policies" ], "annotations": [] }, { - "name": "max_password_age", + "name": "max_retention", "endpointCount": 1, "queryEndpointCount": 0, "bodyEndpointCount": 1, "endpoints": [ - "PATCH /password-policies" + "POST /worm-data-policies" ], "annotations": [] }, @@ -14609,6 +16819,46 @@ ], "annotations": [] }, + { + "name": "min_retention", + "endpointCount": 1, + "queryEndpointCount": 0, + "bodyEndpointCount": 1, + "endpoints": [ + "POST /worm-data-policies" + ], + "annotations": [] + }, + { + "name": "min_tls_version", + "endpointCount": 1, + "queryEndpointCount": 0, + "bodyEndpointCount": 1, + "endpoints": [ + "POST /tls-policies" + ], + "annotations": [] + }, + { + "name": "mode", + "endpointCount": 1, + "queryEndpointCount": 0, + "bodyEndpointCount": 1, + "endpoints": [ + "POST /worm-data-policies" + ], + "annotations": [] + }, + { + "name": "nameservers", + "endpointCount": 1, + "queryEndpointCount": 0, + "bodyEndpointCount": 1, + "endpoints": [ + "POST /dns" + ], + "annotations": [] + }, { "name": "network_access_policy", "endpointCount": 1, @@ -14620,42 +16870,42 @@ "annotations": [] }, { - "name": "ntp_servers", + "name": "nfs", "endpointCount": 1, "queryEndpointCount": 0, "bodyEndpointCount": 1, "endpoints": [ - "PATCH /arrays" + "PATCH /directory-services" ], "annotations": [] }, { - "name": "object_lock_config", + "name": "notification", "endpointCount": 1, "queryEndpointCount": 0, "bodyEndpointCount": 1, "endpoints": [ - "POST /buckets" + "POST /snmp-managers" ], "annotations": [] }, { - "name": "organization", + "name": "ntp_servers", "endpointCount": 1, "queryEndpointCount": 0, "bodyEndpointCount": 1, "endpoints": [ - "POST /certificates" + "PATCH /arrays" ], "annotations": [] }, { - "name": "organizational_unit", + "name": "owner", "endpointCount": 1, "queryEndpointCount": 0, "bodyEndpointCount": 1, "endpoints": [ - "POST /certificates" + "PATCH /file-system-snapshots" ], "annotations": [] }, @@ -14679,6 +16929,16 @@ ], "annotations": [] }, + { + "name": "password", + "endpointCount": 1, + "queryEndpointCount": 0, + "bodyEndpointCount": 1, + "endpoints": [ + "POST /active-directory" + ], + "annotations": [] + }, { "name": "password_history", "endpointCount": 1, @@ -14719,6 +16979,16 @@ ], "annotations": [] }, + { + "name": "ports", + "endpointCount": 1, + "queryEndpointCount": 0, + "bodyEndpointCount": 1, + "endpoints": [ + "POST /link-aggregation-groups" + ], + "annotations": [] + }, { "name": "prefix", "endpointCount": 1, @@ -14799,6 +17069,16 @@ ], "annotations": [] }, + { + "name": "public_access_config", + "endpointCount": 1, + "queryEndpointCount": 0, + "bodyEndpointCount": 1, + "endpoints": [ + "PATCH /buckets" + ], + "annotations": [] + }, { "name": "public_key", "endpointCount": 1, @@ -14819,6 +17099,16 @@ ], "annotations": [] }, + { + "name": "qos_policy", + "endpointCount": 1, + "queryEndpointCount": 0, + "bodyEndpointCount": 1, + "endpoints": [ + "PATCH /buckets" + ], + "annotations": [] + }, { "name": "quota_limit", "endpointCount": 1, @@ -14939,6 +17229,16 @@ ], "annotations": [] }, + { + "name": "remove_rules", + "endpointCount": 1, + "queryEndpointCount": 0, + "bodyEndpointCount": 1, + "endpoints": [ + "PATCH /policies" + ], + "annotations": [] + }, { "name": "required_transport_security", "endpointCount": 1, @@ -14950,12 +17250,12 @@ "annotations": [] }, { - "name": "retention_lock", + "name": "retrieval_rules", "endpointCount": 1, "queryEndpointCount": 0, "bodyEndpointCount": 1, "endpoints": [ - "POST /buckets" + "POST /storage-class-tiering-policies" ], "annotations": [] }, @@ -14989,6 +17289,16 @@ ], "annotations": [] }, + { + "name": "service_principal_names", + "endpointCount": 1, + "queryEndpointCount": 0, + "bodyEndpointCount": 1, + "endpoints": [ + "POST /active-directory" + ], + "annotations": [] + }, { "name": "session_names", "endpointCount": 1, @@ -15019,6 +17329,16 @@ ], "annotations": [] }, + { + "name": "signing_authority", + "endpointCount": 1, + "queryEndpointCount": 0, + "bodyEndpointCount": 1, + "endpoints": [ + "POST /ssh-certificate-authority-policies" + ], + "annotations": [] + }, { "name": "skip_phonehome_check", "endpointCount": 1, @@ -15030,12 +17350,12 @@ "annotations": [] }, { - "name": "source", + "name": "smb", "endpointCount": 1, "queryEndpointCount": 0, "bodyEndpointCount": 1, "endpoints": [ - "POST /keytabs" + "PATCH /directory-services" ], "annotations": [] }, @@ -15050,22 +17370,22 @@ "annotations": [] }, { - "name": "state", + "name": "static_authorized_principals", "endpointCount": 1, "queryEndpointCount": 0, "bodyEndpointCount": 1, "endpoints": [ - "POST /certificates" + "POST /ssh-certificate-authority-policies" ], "annotations": [] }, { - "name": "subject_alternative_names", + "name": "storage_class", "endpointCount": 1, "queryEndpointCount": 0, "bodyEndpointCount": 1, "endpoints": [ - "POST /certificates" + "PATCH /buckets" ], "annotations": [] }, @@ -15089,6 +17409,16 @@ ], "annotations": [] }, + { + "name": "trusted_client_certificate_authority", + "endpointCount": 1, + "queryEndpointCount": 0, + "bodyEndpointCount": 1, + "endpoints": [ + "POST /tls-policies" + ], + "annotations": [] + }, { "name": "type", "endpointCount": 1, @@ -15100,42 +17430,52 @@ "annotations": [] }, { - "name": "unreachable", + "name": "unreachable", + "endpointCount": 1, + "queryEndpointCount": 1, + "bodyEndpointCount": 0, + "endpoints": [ + "DELETE /fleets/members" + ], + "annotations": [] + }, + { + "name": "uri", "endpointCount": 1, - "queryEndpointCount": 1, - "bodyEndpointCount": 0, + "queryEndpointCount": 0, + "bodyEndpointCount": 1, "endpoints": [ - "DELETE /fleets/members" + "POST /syslog-servers" ], "annotations": [] }, { - "name": "v2c", + "name": "uris", "endpointCount": 1, "queryEndpointCount": 0, "bodyEndpointCount": 1, "endpoints": [ - "PATCH /snmp-agents" + "PATCH /directory-services" ], "annotations": [] }, { - "name": "v3", + "name": "user", "endpointCount": 1, "queryEndpointCount": 0, "bodyEndpointCount": 1, "endpoints": [ - "PATCH /snmp-agents" + "POST /active-directory" ], "annotations": [] }, { - "name": "version", + "name": "verify_client_certificate_trust", "endpointCount": 1, "queryEndpointCount": 0, "bodyEndpointCount": 1, "endpoints": [ - "PATCH /snmp-agents" + "POST /tls-policies" ], "annotations": [] }, @@ -15153,7 +17493,7 @@ "conventionStrength": [ { "name": "names", - "cmdletCount": 301, + "cmdletCount": 316, "cmdlets": [ "Get-PfbActiveDirectory", "Get-PfbAdmin", @@ -15265,10 +17605,12 @@ "Get-PfbTarget", "Get-PfbTargetPerformanceReplication", "Get-PfbTlsPolicy", + "Get-PfbUserGroupQuotaPolicy", "Get-PfbUserGroupQuotaPolicyRule", "Get-PfbWorkload", "Get-PfbWorkloadPlacementRecommendation", "Get-PfbWormPolicy", + "New-PfbActiveDirectory", "New-PfbAlertWatcher", "New-PfbApiClient", "New-PfbAuditFileSystemPolicy", @@ -15278,9 +17620,13 @@ "New-PfbBucketCorsPolicyRule", "New-PfbCertificate", "New-PfbCertificateGroup", + "New-PfbCertificateSigningRequest", "New-PfbDataEvictionPolicy", "New-PfbDirectoryServiceRole", + "New-PfbDns", "New-PfbFileSystem", + "New-PfbFleet", + "New-PfbLag", "New-PfbLegalHold", "New-PfbLegalHoldEntity", "New-PfbLocalDirectoryService", @@ -15291,6 +17637,7 @@ "New-PfbNetworkInterface", "New-PfbNfsExportPolicy", "New-PfbNlmReclamation", + "New-PfbNodeGroup", "New-PfbObjectStoreAccessPolicy", "New-PfbObjectStoreAccessPolicyRule", "New-PfbObjectStoreAccount", @@ -15310,9 +17657,16 @@ "New-PfbServer", "New-PfbSmbClientPolicy", "New-PfbSmbSharePolicy", + "New-PfbSnmpManager", + "New-PfbSshCaPolicy", + "New-PfbStorageClassTieringPolicy", "New-PfbSubnet", + "New-PfbSyslogServer", + "New-PfbTarget", + "New-PfbTlsPolicy", "New-PfbUserGroupQuotaPolicy", "New-PfbWorkload", + "New-PfbWormPolicy", "Remove-PfbActiveDirectory", "Remove-PfbAdminCache", "Remove-PfbAlertWatcher", @@ -15403,6 +17757,7 @@ "Update-PfbBucketAuditFilter", "Update-PfbCertificate", "Update-PfbDataEvictionPolicy", + "Update-PfbDirectoryService", "Update-PfbDirectoryServiceRole", "Update-PfbDns", "Update-PfbFileSystem", @@ -15460,7 +17815,7 @@ }, { "name": "ids", - "cmdletCount": 220, + "cmdletCount": 221, "cmdlets": [ "Get-PfbAdmin", "Get-PfbAdminCache", @@ -15549,6 +17904,7 @@ "Get-PfbSyslogServer", "Get-PfbTarget", "Get-PfbTlsPolicy", + "Get-PfbUserGroupQuotaPolicy", "Get-PfbUserGroupQuotaPolicyRule", "Get-PfbWorkload", "Get-PfbWorkloadPlacementRecommendation", @@ -16591,6 +18947,41 @@ "Remove-PfbUserGroupQuotaPolicyFileSystem" ] }, + { + "name": "enabled", + "cmdletCount": 29, + "cmdlets": [ + "New-PfbAuditFileSystemPolicy", + "New-PfbAuditObjectStorePolicy", + "New-PfbNfsExportPolicy", + "New-PfbPolicy", + "New-PfbS3ExportPolicy", + "New-PfbSmbClientPolicy", + "New-PfbSmbSharePolicy", + "New-PfbUserGroupQuotaPolicy", + "Update-PfbAlertWatcher", + "Update-PfbApiClient", + "Update-PfbAuditFileSystemPolicy", + "Update-PfbAuditObjectStorePolicy", + "Update-PfbDataEvictionPolicy", + "Update-PfbLifecycleRule", + "Update-PfbManagementAccessPolicy", + "Update-PfbNetworkAccessPolicy", + "Update-PfbNfsExportPolicy", + "Update-PfbOidcIdp", + "Update-PfbPolicy", + "Update-PfbQosPolicy", + "Update-PfbS3ExportPolicy", + "Update-PfbSaml2Idp", + "Update-PfbSmbClientPolicy", + "Update-PfbSmbSharePolicy", + "Update-PfbSshCaPolicy", + "Update-PfbStorageClassTieringPolicy", + "Update-PfbTlsPolicy", + "Update-PfbUserGroupQuotaPolicy", + "Update-PfbWormPolicy" + ] + }, { "name": "name", "cmdletCount": 20, @@ -16617,6 +19008,31 @@ "Update-PfbWorkload" ] }, + { + "name": "file_system_names", + "cmdletCount": 19, + "cmdlets": [ + "Get-PfbFileSystemGroup", + "Get-PfbFileSystemGroupPerformance", + "Get-PfbFileSystemGroupQuota", + "Get-PfbFileSystemUser", + "Get-PfbFileSystemUserPerformance", + "Get-PfbFileSystemUserQuota", + "Get-PfbQuotaGroup", + "Get-PfbQuotaUser", + "Get-PfbUsageGroup", + "Get-PfbUsageUser", + "New-PfbLegalHoldEntity", + "New-PfbQuotaGroup", + "New-PfbQuotaUser", + "New-PfbUserGroupQuotaPolicy", + "Remove-PfbQuotaGroup", + "Remove-PfbQuotaUser", + "Update-PfbLegalHoldEntity", + "Update-PfbQuotaGroup", + "Update-PfbQuotaUser" + ] + }, { "name": "end_time", "cmdletCount": 17, @@ -16665,7 +19081,7 @@ }, { "name": "bucket_names", - "cmdletCount": 16, + "cmdletCount": 15, "cmdlets": [ "Get-PfbBucketAccessPolicy", "Get-PfbBucketAccessPolicyRule", @@ -16681,7 +19097,6 @@ "Remove-PfbBucketAccessPolicy", "Remove-PfbBucketAuditFilter", "Remove-PfbBucketCorsPolicy", - "Update-PfbBucketAuditFilter", "Update-PfbLifecycleRule" ] }, @@ -16706,25 +19121,6 @@ "Get-PfbTargetPerformanceReplication" ] }, - { - "name": "file_system_names", - "cmdletCount": 13, - "cmdlets": [ - "Get-PfbFileSystemGroup", - "Get-PfbFileSystemGroupPerformance", - "Get-PfbFileSystemGroupQuota", - "Get-PfbFileSystemUser", - "Get-PfbFileSystemUserPerformance", - "Get-PfbFileSystemUserQuota", - "Get-PfbQuotaGroup", - "Get-PfbQuotaUser", - "Get-PfbUsageGroup", - "Get-PfbUsageUser", - "New-PfbLegalHoldEntity", - "New-PfbUserGroupQuotaPolicy", - "Update-PfbLegalHoldEntity" - ] - }, { "name": "total_only", "cmdletCount": 12, @@ -16744,19 +19140,34 @@ ] }, { - "name": "enabled", + "name": "group_names", "cmdletCount": 10, "cmdlets": [ - "Update-PfbApiClient", - "Update-PfbLifecycleRule", - "Update-PfbManagementAccessPolicy", - "Update-PfbOidcIdp", - "Update-PfbQosPolicy", - "Update-PfbSaml2Idp", - "Update-PfbSshCaPolicy", - "Update-PfbStorageClassTieringPolicy", - "Update-PfbTlsPolicy", - "Update-PfbWormPolicy" + "Get-PfbFileSystemGroup", + "Get-PfbFileSystemGroupQuota", + "Get-PfbLocalGroupMember", + "Get-PfbNodeGroupNode", + "New-PfbLocalGroupMember", + "New-PfbQuotaGroup", + "Remove-PfbLocalGroupMember", + "Remove-PfbNodeGroupNode", + "Remove-PfbQuotaGroup", + "Update-PfbQuotaGroup" + ] + }, + { + "name": "destroyed", + "cmdletCount": 9, + "cmdlets": [ + "Get-PfbBucket", + "Get-PfbFileSystem", + "Get-PfbFileSystemSnapshot", + "Get-PfbRealm", + "Get-PfbWorkload", + "Update-PfbBucket", + "Update-PfbFileSystem", + "Update-PfbRealm", + "Update-PfbWorkload" ] }, { @@ -16802,19 +19213,6 @@ "Update-PfbWormPolicy" ] }, - { - "name": "group_names", - "cmdletCount": 7, - "cmdlets": [ - "Get-PfbFileSystemGroup", - "Get-PfbFileSystemGroupQuota", - "Get-PfbLocalGroupMember", - "Get-PfbNodeGroupNode", - "New-PfbLocalGroupMember", - "Remove-PfbLocalGroupMember", - "Remove-PfbNodeGroupNode" - ] - }, { "name": "ca_certificate_group", "cmdletCount": 6, @@ -16840,14 +19238,25 @@ ] }, { - "name": "destroyed", + "name": "gids", "cmdletCount": 5, "cmdlets": [ - "Get-PfbBucket", - "Get-PfbFileSystem", - "Get-PfbFileSystemSnapshot", - "Get-PfbRealm", - "Get-PfbWorkload" + "Get-PfbFileSystemGroup", + "Get-PfbFileSystemGroupQuota", + "New-PfbQuotaGroup", + "Remove-PfbQuotaGroup", + "Update-PfbQuotaGroup" + ] + }, + { + "name": "ignore_usage", + "cmdletCount": 5, + "cmdlets": [ + "New-PfbFileSystemUserGroupQuotaPolicy", + "New-PfbUserGroupQuotaPolicyFileSystem", + "New-PfbUserGroupQuotaPolicyRule", + "Update-PfbUserGroupQuotaPolicy", + "Update-PfbUserGroupQuotaPolicyRule" ] }, { @@ -16872,6 +19281,17 @@ "Update-PfbUserGroupQuotaPolicy" ] }, + { + "name": "user_names", + "cmdletCount": 5, + "cmdlets": [ + "Get-PfbFileSystemUser", + "Get-PfbFileSystemUserQuota", + "New-PfbQuotaUser", + "Remove-PfbQuotaUser", + "Update-PfbQuotaUser" + ] + }, { "name": "versions", "cmdletCount": 5, @@ -16913,6 +19333,15 @@ "New-PfbNetworkInterface" ] }, + { + "name": "address", + "cmdletCount": 3, + "cmdlets": [ + "New-PfbNetworkInterface", + "Update-PfbNetworkInterface", + "Update-PfbTarget" + ] + }, { "name": "attached_servers", "cmdletCount": 3, @@ -17030,6 +19459,15 @@ "New-PfbFileSystem" ] }, + { + "name": "uids", + "cmdletCount": 3, + "cmdlets": [ + "Get-PfbFileSystemUser", + "Get-PfbFileSystemUserQuota", + "New-PfbQuotaUser" + ] + }, { "name": "actions", "cmdletCount": 2, @@ -17054,6 +19492,14 @@ "New-PfbCertificateCertificateGroup" ] }, + { + "name": "domain", + "cmdletCount": 2, + "cmdlets": [ + "New-PfbLocalDirectoryService", + "Update-PfbDns" + ] + }, { "name": "effect", "cmdletCount": 2, @@ -17070,14 +19516,6 @@ "Update-PfbCertificate" ] }, - { - "name": "gids", - "cmdletCount": 2, - "cmdlets": [ - "Get-PfbFileSystemGroup", - "Get-PfbFileSystemGroupQuota" - ] - }, { "name": "idp", "cmdletCount": 2, @@ -17151,19 +19589,11 @@ ] }, { - "name": "uids", - "cmdletCount": 2, - "cmdlets": [ - "Get-PfbFileSystemUser", - "Get-PfbFileSystemUserQuota" - ] - }, - { - "name": "user_names", + "name": "sources", "cmdletCount": 2, "cmdlets": [ - "Get-PfbFileSystemUser", - "Get-PfbFileSystemUserQuota" + "Update-PfbDns", + "Update-PfbSyslogServer" ] }, { @@ -17208,6 +19638,20 @@ "New-PfbNfsExportRule" ] }, + { + "name": "appliance_certificate", + "cmdletCount": 1, + "cmdlets": [ + "Update-PfbTlsPolicy" + ] + }, + { + "name": "archival_rules", + "cmdletCount": 1, + "cmdlets": [ + "Update-PfbStorageClassTieringPolicy" + ] + }, { "name": "array_url", "cmdletCount": 1, @@ -17244,59 +19688,101 @@ ] }, { - "name": "certificate_type", + "name": "certificate_type", + "cmdletCount": 1, + "cmdlets": [ + "Update-PfbCertificate" + ] + }, + { + "name": "change", + "cmdletCount": 1, + "cmdlets": [ + "New-PfbSmbShareRule" + ] + }, + { + "name": "client_certificates_required", + "cmdletCount": 1, + "cmdlets": [ + "Update-PfbTlsPolicy" + ] + }, + { + "name": "common_name", + "cmdletCount": 1, + "cmdlets": [ + "Update-PfbCertificate" + ] + }, + { + "name": "confirm_date", + "cmdletCount": 1, + "cmdlets": [ + "Update-PfbLifecycleRule" + ] + }, + { + "name": "country", + "cmdletCount": 1, + "cmdlets": [ + "Update-PfbCertificate" + ] + }, + { + "name": "days", "cmdletCount": 1, "cmdlets": [ "Update-PfbCertificate" ] }, { - "name": "change", + "name": "default_retention", "cmdletCount": 1, "cmdlets": [ - "New-PfbSmbShareRule" + "Update-PfbWormPolicy" ] }, { - "name": "common_name", + "name": "description", "cmdletCount": 1, "cmdlets": [ - "Update-PfbCertificate" + "Update-PfbLegalHold" ] }, { - "name": "confirm_date", + "name": "directory_servers", "cmdletCount": 1, "cmdlets": [ - "Update-PfbLifecycleRule" + "Update-PfbActiveDirectory" ] }, { - "name": "country", + "name": "disabled_tls_ciphers", "cmdletCount": 1, "cmdlets": [ - "Update-PfbCertificate" + "Update-PfbTlsPolicy" ] }, { - "name": "days", + "name": "enabled_tls_ciphers", "cmdletCount": 1, "cmdlets": [ - "Update-PfbCertificate" + "Update-PfbTlsPolicy" ] }, { - "name": "description", + "name": "encryption", "cmdletCount": 1, "cmdlets": [ - "Update-PfbLegalHold" + "New-PfbSmbClientRule" ] }, { - "name": "encryption", + "name": "encryption_types", "cmdletCount": 1, "cmdlets": [ - "New-PfbSmbClientRule" + "Update-PfbActiveDirectory" ] }, { @@ -17327,6 +19813,13 @@ "New-PfbFleetMember" ] }, + { + "name": "fqdns", + "cmdletCount": 1, + "cmdlets": [ + "Update-PfbActiveDirectory" + ] + }, { "name": "full_control", "cmdletCount": 1, @@ -17334,6 +19827,13 @@ "New-PfbSmbShareRule" ] }, + { + "name": "global_catalog_servers", + "cmdletCount": 1, + "cmdlets": [ + "Update-PfbActiveDirectory" + ] + }, { "name": "group", "cmdletCount": 1, @@ -17348,6 +19848,20 @@ "Update-PfbDirectoryServiceRole" ] }, + { + "name": "hard_limit_enabled", + "cmdletCount": 1, + "cmdlets": [ + "Update-PfbFileSystem" + ] + }, + { + "name": "host", + "cmdletCount": 1, + "cmdlets": [ + "Update-PfbSnmpManager" + ] + }, { "name": "interfaces", "cmdletCount": 1, @@ -17362,6 +19876,13 @@ "Update-PfbCertificate" ] }, + { + "name": "join_ou", + "cmdletCount": 1, + "cmdlets": [ + "Update-PfbActiveDirectory" + ] + }, { "name": "keep_current_version_for", "cmdletCount": 1, @@ -17390,6 +19911,13 @@ "Update-PfbLifecycleRule" ] }, + { + "name": "kerberos_servers", + "cmdletCount": 1, + "cmdlets": [ + "Update-PfbActiveDirectory" + ] + }, { "name": "key_algorithm", "cmdletCount": 1, @@ -17446,6 +19974,13 @@ "Update-PfbAdmin" ] }, + { + "name": "max_retention", + "cmdletCount": 1, + "cmdlets": [ + "Update-PfbWormPolicy" + ] + }, { "name": "max_role", "cmdletCount": 1, @@ -17474,6 +20009,27 @@ "Update-PfbQosPolicy" ] }, + { + "name": "min_retention", + "cmdletCount": 1, + "cmdlets": [ + "Update-PfbWormPolicy" + ] + }, + { + "name": "min_tls_version", + "cmdletCount": 1, + "cmdlets": [ + "Update-PfbTlsPolicy" + ] + }, + { + "name": "mode", + "cmdletCount": 1, + "cmdlets": [ + "Update-PfbWormPolicy" + ] + }, { "name": "names_or_owner_names", "cmdletCount": 1, @@ -17481,6 +20037,13 @@ "Get-PfbFileSystemReplicaLinkTransfer" ] }, + { + "name": "nameservers", + "cmdletCount": 1, + "cmdlets": [ + "Update-PfbDns" + ] + }, { "name": "node_group_ids", "cmdletCount": 1, @@ -17509,6 +20072,13 @@ "New-PfbNodeGroupNode" ] }, + { + "name": "notification", + "cmdletCount": 1, + "cmdlets": [ + "Update-PfbSnmpManager" + ] + }, { "name": "organization", "cmdletCount": 1, @@ -17537,6 +20107,20 @@ "Update-PfbCertificate" ] }, + { + "name": "password", + "cmdletCount": 1, + "cmdlets": [ + "Update-PfbAdmin" + ] + }, + { + "name": "ports", + "cmdletCount": 1, + "cmdlets": [ + "Update-PfbLag" + ] + }, { "name": "principal", "cmdletCount": 1, @@ -17558,6 +20142,13 @@ "Get-PfbArrayPerformance" ] }, + { + "name": "qos_policy", + "cmdletCount": 1, + "cmdlets": [ + "New-PfbFileSystem" + ] + }, { "name": "rdma_enabled", "cmdletCount": 1, @@ -17600,6 +20191,13 @@ "Update-PfbWormPolicy" ] }, + { + "name": "retrieval_rules", + "cmdletCount": 1, + "cmdlets": [ + "Update-PfbStorageClassTieringPolicy" + ] + }, { "name": "secret_access_key", "cmdletCount": 1, @@ -17621,6 +20219,20 @@ "New-PfbNfsExportRule" ] }, + { + "name": "service_principal_names", + "cmdletCount": 1, + "cmdlets": [ + "Update-PfbActiveDirectory" + ] + }, + { + "name": "signing_authority", + "cmdletCount": 1, + "cmdlets": [ + "Update-PfbSshCaPolicy" + ] + }, { "name": "sp", "cmdletCount": 1, @@ -17635,6 +20247,13 @@ "Update-PfbCertificate" ] }, + { + "name": "static_authorized_principals", + "cmdletCount": 1, + "cmdlets": [ + "Update-PfbSshCaPolicy" + ] + }, { "name": "subject_alternative_names", "cmdletCount": 1, @@ -17649,6 +20268,34 @@ "New-PfbApiToken" ] }, + { + "name": "trusted_client_certificate_authority", + "cmdletCount": 1, + "cmdlets": [ + "Update-PfbTlsPolicy" + ] + }, + { + "name": "uri", + "cmdletCount": 1, + "cmdlets": [ + "Update-PfbSyslogServer" + ] + }, + { + "name": "uris", + "cmdletCount": 1, + "cmdlets": [ + "Update-PfbKmip" + ] + }, + { + "name": "user", + "cmdletCount": 1, + "cmdlets": [ + "New-PfbObjectStoreAccessKey" + ] + }, { "name": "v2c", "cmdletCount": 1, @@ -17663,6 +20310,13 @@ "Update-PfbSnmpManager" ] }, + { + "name": "verify_client_certificate_trust", + "cmdletCount": 1, + "cmdlets": [ + "Update-PfbTlsPolicy" + ] + }, { "name": "version", "cmdletCount": 1, @@ -17670,6 +20324,11 @@ "Update-PfbSnmpManager" ] }, + { + "name": "access_based_enumeration_enabled", + "cmdletCount": 0, + "cmdlets": [] + }, { "name": "access_policies", "cmdletCount": 0, @@ -17685,6 +20344,16 @@ "cmdletCount": 0, "cmdlets": [] }, + { + "name": "add_log_targets", + "cmdletCount": 0, + "cmdlets": [] + }, + { + "name": "add_rules", + "cmdletCount": 0, + "cmdlets": [] + }, { "name": "allow_errors", "cmdletCount": 0, @@ -17720,6 +20389,21 @@ "cmdletCount": 0, "cmdlets": [] }, + { + "name": "base_dn", + "cmdletCount": 0, + "cmdlets": [] + }, + { + "name": "bind_password", + "cmdletCount": 0, + "cmdlets": [] + }, + { + "name": "bind_user", + "cmdletCount": 0, + "cmdlets": [] + }, { "name": "bucket_defaults", "cmdletCount": 0, @@ -17730,6 +20414,16 @@ "cmdletCount": 0, "cmdlets": [] }, + { + "name": "cancel_in_progress_storage_class_transition", + "cmdletCount": 0, + "cmdlets": [] + }, + { + "name": "cascade_delete", + "cmdletCount": 0, + "cmdlets": [] + }, { "name": "client_names", "cmdletCount": 0, @@ -17740,6 +20434,11 @@ "cmdletCount": 0, "cmdlets": [] }, + { + "name": "computer_name", + "cmdletCount": 0, + "cmdlets": [] + }, { "name": "conditions", "cmdletCount": 0, @@ -17750,6 +20449,11 @@ "cmdletCount": 0, "cmdlets": [] }, + { + "name": "control_type", + "cmdletCount": 0, + "cmdlets": [] + }, { "name": "current_state", "cmdletCount": 0, @@ -17765,6 +20469,11 @@ "cmdletCount": 0, "cmdlets": [] }, + { + "name": "destroy_snapshots", + "cmdletCount": 0, + "cmdlets": [] + }, { "name": "direct_notifications_enabled", "cmdletCount": 0, @@ -17785,6 +20494,11 @@ "cmdletCount": 0, "cmdlets": [] }, + { + "name": "disruptive", + "cmdletCount": 0, + "cmdlets": [] + }, { "name": "edge_agent_update_enabled", "cmdletCount": 0, @@ -17860,11 +20574,6 @@ "cmdletCount": 0, "cmdlets": [] }, - { - "name": "hard_limit_enabled", - "cmdletCount": 0, - "cmdlets": [] - }, { "name": "idle_timeout", "cmdletCount": 0, @@ -17885,6 +20594,11 @@ "cmdletCount": 0, "cmdlets": [] }, + { + "name": "join_existing_account", + "cmdletCount": 0, + "cmdlets": [] + }, { "name": "keytab_file", "cmdletCount": 0, @@ -17905,6 +20619,11 @@ "cmdletCount": 0, "cmdlets": [] }, + { + "name": "latest_replica", + "cmdletCount": 0, + "cmdlets": [] + }, { "name": "link_type", "cmdletCount": 0, @@ -17945,6 +20664,11 @@ "cmdletCount": 0, "cmdlets": [] }, + { + "name": "log_targets", + "cmdletCount": 0, + "cmdlets": [] + }, { "name": "max_login_attempts", "cmdletCount": 0, @@ -17990,6 +20714,11 @@ "cmdletCount": 0, "cmdlets": [] }, + { + "name": "nfs", + "cmdletCount": 0, + "cmdlets": [] + }, { "name": "ntp_servers", "cmdletCount": 0, @@ -18000,6 +20729,11 @@ "cmdletCount": 0, "cmdlets": [] }, + { + "name": "owner", + "cmdletCount": 0, + "cmdlets": [] + }, { "name": "owner_ids", "cmdletCount": 0, @@ -18060,6 +20794,11 @@ "cmdletCount": 0, "cmdlets": [] }, + { + "name": "public_access_config", + "cmdletCount": 0, + "cmdlets": [] + }, { "name": "purity_defined", "cmdletCount": 0, @@ -18110,6 +20849,16 @@ "cmdletCount": 0, "cmdlets": [] }, + { + "name": "remove_log_targets", + "cmdletCount": 0, + "cmdlets": [] + }, + { + "name": "remove_rules", + "cmdletCount": 0, + "cmdlets": [] + }, { "name": "resolve_hostname", "cmdletCount": 0, @@ -18150,6 +20899,11 @@ "cmdletCount": 0, "cmdlets": [] }, + { + "name": "smb", + "cmdletCount": 0, + "cmdlets": [] + }, { "name": "snapshot_configurations", "cmdletCount": 0, @@ -18165,6 +20919,11 @@ "cmdletCount": 0, "cmdlets": [] }, + { + "name": "storage_class", + "cmdletCount": 0, + "cmdlets": [] + }, { "name": "storage_class_names", "cmdletCount": 0, diff --git a/Reports/PfbApiDriftReport.md b/Reports/PfbApiDriftReport.md index abf3e084..d9a43986 100644 --- a/Reports/PfbApiDriftReport.md +++ b/Reports/PfbApiDriftReport.md @@ -21,13 +21,13 @@ This report accepts **false positives in order to eliminate false negatives**. A ## Summary - Uncovered endpoints: 95 -- Endpoints with parameter gaps: 358 -- Missing body properties (addable): 424 -- Missing query parameters (addable): 569 +- Endpoints with parameter gaps: 355 +- Missing body properties (addable): 395 +- Missing query parameters (addable): 539 - Read-only body fields (not addable -- see the Read-only fields section below): 384 - Phantom fields silently excluded (accumulated in the capability map, absent from the newest analysed spec): 40 -- Partial-confidence endpoints (see `How to read this report` above, and each row's marker in the Parameter gaps table): 58 -- Systemic gaps (distinct field names collapsed across high-confidence endpoints, detailed below): 240 +- Partial-confidence endpoints (see `How to read this report` above, and each row's marker in the Parameter gaps table): 11 +- Systemic gaps (distinct field names collapsed across high-confidence endpoints, detailed below): 297 - ValidateSet drift: 0 - New ValidateSet candidates: 2 - Context cardinality signal disagreements (fb2.28): 9 @@ -39,35 +39,35 @@ This report accepts **false positives in order to eliminate false negatives**. A One finding per distinct wire field name, collapsed across every endpoint where a high-confidence gap exists (decision 7) -- turns hundreds of per-endpoint rows into a handful of real, actionable decisions. "Cmdlets already using this name" is decision 8's convention-strength ranking: a high count means closing the remaining gaps for this name is a mechanical batch fix; zero means no established convention exists to extend at all -- closing it is an architectural decision, not a mechanical one. -Showing the top 25 of 240 findings by endpoint count -- the full list is in the JSON manifest's `systemicGaps`, nothing is dropped there. +Showing the top 25 of 297 findings by endpoint count -- the full list is in the JSON manifest's `systemicGaps`, nothing is dropped there. | Field name | Endpoints | Query | Body | Cmdlets already using this name | Annotation | |---|---|---|---|---|---| -| `allow_errors` | 118 | 118 | 0 | 0 | not yet implemented; deferred to Phase 2 | -| `ids` | 38 | 38 | 0 | 220 | | +| `allow_errors` | 119 | 119 | 0 | 0 | not yet implemented; deferred to Phase 2 | +| `ids` | 39 | 39 | 0 | 221 | | | `sort` | 28 | 28 | 0 | 179 | | -| `names` | 23 | 23 | 0 | 301 | | +| `name` | 27 | 0 | 27 | 20 | | +| `names` | 27 | 27 | 0 | 316 | | +| `location` | 21 | 0 | 21 | 8 | | | `total_only` | 17 | 17 | 0 | 12 | | | `policy_ids` | 16 | 16 | 0 | 97 | | +| `rules` | 15 | 0 | 15 | 5 | | +| `file_system_ids` | 14 | 14 | 0 | 9 | | | `member_ids` | 14 | 14 | 0 | 81 | | +| `enabled` | 11 | 0 | 11 | 29 | | | `bucket_ids` | 10 | 10 | 0 | 8 | | | `policy_names` | 9 | 9 | 0 | 114 | | -| `file_system_ids` | 8 | 8 | 0 | 9 | | +| `versions` | 9 | 9 | 0 | 5 | | | `local_file_system_ids` | 8 | 8 | 0 | 2 | | | `limit` | 7 | 7 | 0 | 199 | | -| `name` | 7 | 0 | 7 | 20 | | -| `versions` | 7 | 7 | 0 | 5 | | | `actions` | 6 | 0 | 6 | 2 | | -| `enabled` | 6 | 0 | 6 | 10 | | | `filter` | 6 | 6 | 0 | 201 | | +| `policy` | 6 | 0 | 6 | 3 | | | `role_ids` | 6 | 6 | 0 | 2 | | | `role_names` | 6 | 6 | 0 | 4 | | +| `user_names` | 6 | 6 | 0 | 5 | | | `workload_ids` | 6 | 6 | 0 | 0 | | | `workload_names` | 6 | 6 | 0 | 0 | | -| `gids` | 5 | 5 | 0 | 2 | | -| `local_file_system_names` | 5 | 5 | 0 | 5 | | -| `policy` | 5 | 0 | 5 | 3 | | -| `remote_file_system_ids` | 5 | 5 | 0 | 0 | | ## Parameter gaps @@ -84,7 +84,7 @@ Endpoints an existing cmdlet already calls, where the capability map knows of a | `DELETE /file-system-replica-links` | Remove-PfbFileSystemReplicaLink | local_file_system_ids, remote_file_system_ids | | `high` | | | `DELETE /file-system-replica-links/policies` | Remove-PfbFileSystemReplicaLinkPolicy | local_file_system_ids, local_file_system_names | | `high` | | | `DELETE /file-systems/locks` | Remove-PfbFileLock | client_names, file_system_ids, file_system_names, inodes, paths, recursive | | `high` | | -| `DELETE /file-systems/sessions` | Remove-PfbFileSystemSession | client_names, disruptive, user_names | | `partial` -- /!\ 1 unresolved param (see Partial-confidence detail below) | | +| `DELETE /file-systems/sessions` | Remove-PfbFileSystemSession | client_names, disruptive, user_names | | `high` | | | `DELETE /fleets/members` | Remove-PfbFleetMember | member_ids, unreachable | | `high` | | | `DELETE /lifecycle-rules` | Remove-PfbLifecycleRule | bucket_ids, bucket_names | | `high` | | | `DELETE /network-access-policies/rules` | Remove-PfbNetworkAccessRule | ids, versions | | `high` | | @@ -100,9 +100,9 @@ Endpoints an existing cmdlet already calls, where the capability map knows of a | `DELETE /object-store-users/object-store-access-policies` | Remove-PfbObjectStoreUserAccessPolicy | member_ids, policy_ids | | `high` | | | `DELETE /policies/file-system-replica-links` | Remove-PfbPolicyFileSystemReplicaLink | local_file_system_ids, local_file_system_names | | `high` | | | `DELETE /qos-policies/members` | Remove-PfbQosPolicyMember | member_types | | `high` | | -| `DELETE /quotas/groups` | Remove-PfbQuotaGroup | file_system_ids, file_system_names, gids, group_names, names | | `partial` -- /!\ 3 unresolved params (see Partial-confidence detail below) | | -| `DELETE /quotas/users` | Remove-PfbQuotaUser | file_system_ids, file_system_names, names, uids, user_names | | `partial` -- /!\ 2 unresolved params (see Partial-confidence detail below) | | -| `DELETE /servers` | Remove-PfbServer | cascade_delete | | `partial` -- /!\ 1 unresolved param (see Partial-confidence detail below) | | +| `DELETE /quotas/groups` | Remove-PfbQuotaGroup | file_system_ids, names | | `high` | | +| `DELETE /quotas/users` | Remove-PfbQuotaUser | file_system_ids, names, uids | | `high` | | +| `DELETE /servers` | Remove-PfbServer | cascade_delete | | `high` | | | `DELETE /smb-client-policies/rules` | Remove-PfbSmbClientRule | ids, versions | | `high` | | | `DELETE /smb-share-policies/rules` | Remove-PfbSmbShareRule | ids | | `high` | | | `GET /active-directory` | Get-PfbActiveDirectory | ids, limit, sort | | `high` | | @@ -272,7 +272,7 @@ Endpoints an existing cmdlet already calls, where the capability map knows of a | `GET /tls-policies/members` | Get-PfbTlsPolicyMember | sort | | `high` | | | `GET /usage/groups` | Get-PfbUsageGroup | allow_errors, file_system_ids, gids, group_names | | `high` | | | `GET /usage/users` | Get-PfbUsageUser | allow_errors, file_system_ids, uids, user_names | | `high` | | -| `GET /user-group-quota-policies` | Get-PfbUserGroupQuotaPolicy | allow_errors, ids, names | | `partial` -- /!\ 2 unresolved params (see Partial-confidence detail below) | | +| `GET /user-group-quota-policies` | Get-PfbUserGroupQuotaPolicy | allow_errors | | `high` | | | `GET /user-group-quota-policies/file-systems` | Get-PfbUserGroupQuotaPolicyFileSystem | allow_errors | | `high` | | | `GET /user-group-quota-policies/members` | Get-PfbUserGroupQuotaPolicyMember | allow_errors | | `high` | | | `GET /user-group-quota-policies/rules` | Get-PfbUserGroupQuotaPolicyRule | allow_errors | | `high` | | @@ -283,24 +283,25 @@ Endpoints an existing cmdlet already calls, where the capability map knows of a | `GET /worm-data-policies/members` | Get-PfbWormPolicyMember | allow_errors, sort | | `high` | | | `PATCH /admins` | Update-PfbAdmin | | role | `high` | | | `PATCH /admins/settings` | Update-PfbAdminSetting | | lockout_duration, max_login_attempts, min_password_length | `high` | | -| `PATCH /alert-watchers` | Update-PfbAlertWatcher | | enabled | `partial` -- /!\ 1 unresolved param (see Partial-confidence detail below) | | -| `PATCH /alerts` | Update-PfbAlert | | flagged | `partial` -- /!\ 1 unresolved param (see Partial-confidence detail below) | | +| `PATCH /alert-watchers` | Update-PfbAlertWatcher | | | `high` | | +| `PATCH /alerts` | Update-PfbAlert | | | `high` | | | `PATCH /api-clients` | Update-PfbApiClient | | max_role | `high` | | | `PATCH /array-connections` | Update-PfbArrayConnection | | | `high` | | | `PATCH /arrays` | Update-PfbArray | | banner, default_inbound_tls_policy, eradication_config, idle_timeout, name, network_access_policy, ntp_servers, time_zone | `high` | | | `PATCH /arrays/erasures` | Update-PfbArrayErasure | delete_sanitization_certificate, eradicate_all_data, finalize | | `high` | | | `PATCH /arrays/eula` | Update-PfbArrayEula | | signature | `high` | | -| `PATCH /audit-file-systems-policies` | Update-PfbAuditFileSystemPolicy | | add_log_targets, control_type, enabled, location, log_targets, name, remove_log_targets, rules | `partial` -- /!\ 1 unresolved param (see Partial-confidence detail below) | | -| `PATCH /audit-object-store-policies` | Update-PfbAuditObjectStorePolicy | | add_log_targets, enabled, location, log_targets, name, remove_log_targets | `partial` -- /!\ 1 unresolved param (see Partial-confidence detail below) | | -| `PATCH /buckets` | Remove-PfbBucket, Update-PfbBucket | cancel_in_progress_storage_class_transition, ignore_usage | destroyed, eradication_config, hard_limit_enabled, object_lock_config, public_access_config, qos_policy, retention_lock, storage_class | `partial` -- /!\ 2 unresolved params (see Partial-confidence detail below) | | +| `PATCH /audit-file-systems-policies` | Update-PfbAuditFileSystemPolicy | | add_log_targets, control_type, location, log_targets, name, remove_log_targets, rules | `high` | | +| `PATCH /audit-object-store-policies` | Update-PfbAuditObjectStorePolicy | | add_log_targets, location, log_targets, name, remove_log_targets | `high` | | +| `PATCH /buckets` | Remove-PfbBucket, Update-PfbBucket | cancel_in_progress_storage_class_transition, ignore_usage | eradication_config, hard_limit_enabled, object_lock_config, public_access_config, qos_policy, retention_lock, storage_class | `high` | | +| `PATCH /buckets/audit-filters` | Update-PfbBucketAuditFilter | bucket_names | | `partial` -- /!\ 1 unresolved param (see Partial-confidence detail below) | | | `PATCH /certificates` | Update-PfbCertificate | | | `high` | | -| `PATCH /data-eviction-policies` | Update-PfbDataEvictionPolicy | | enabled, location | `partial` -- /!\ 1 unresolved param (see Partial-confidence detail below) | | -| `PATCH /directory-services` | Update-PfbDirectoryService | ids, names | base_dn, bind_password, bind_user, ca_certificate, ca_certificate_group, enabled, management, nfs, smb, uris | `partial` -- /!\ 1 unresolved param (see Partial-confidence detail below) | | +| `PATCH /data-eviction-policies` | Update-PfbDataEvictionPolicy | | location | `high` | | +| `PATCH /directory-services` | Update-PfbDirectoryService | ids | base_dn, bind_password, bind_user, ca_certificate, ca_certificate_group, enabled, management, nfs, smb, uris | `high` | | | `PATCH /directory-services/roles` | Update-PfbDirectoryServiceRole | role_ids, role_names | role | `high` | | | `PATCH /dns` | Update-PfbDns | | | `high` | | | `PATCH /file-system-exports` | Update-PfbFileSystemExport | | | `high` | | -| `PATCH /file-system-snapshots` | Remove-PfbFileSystemSnapshot | latest_replica | destroyed, name, owner, policy, source | `partial` -- /!\ 1 unresolved param (see Partial-confidence detail below) | | -| `PATCH /file-systems` | Remove-PfbFileSystem, Update-PfbFileSystem | cancel_in_progress_storage_class_transition, discard_detailed_permissions, ignore_usage | abort_quiesce, default_group_quota, default_user_quota, destroyed, fast_remove_directory_enabled, group_ownership, hard_limit_enabled, http, multi_protocol, name, nfs, qos_policy, quiesce, skip_quiesce, smb, snapshot_directory_enabled, source, storage_class, workload, writable | `partial` -- /!\ 10 unresolved params (see Partial-confidence detail below) | | +| `PATCH /file-system-snapshots` | Remove-PfbFileSystemSnapshot | latest_replica | destroyed, name, owner, policy, source | `high` | | +| `PATCH /file-systems` | Remove-PfbFileSystem, Update-PfbFileSystem | cancel_in_progress_storage_class_transition, discard_detailed_permissions, ignore_usage | abort_quiesce, default_group_quota, default_user_quota, fast_remove_directory_enabled, group_ownership, multi_protocol, name, nfs, qos_policy, quiesce, skip_quiesce, smb, snapshot_directory_enabled, source, storage_class, workload, writable | `partial` -- /!\ 6 unresolved params (see Partial-confidence detail below) | | | `PATCH /hardware` | Update-PfbHardware | | | `high` | | | `PATCH /hardware-connectors` | Update-PfbHardwareConnector | | | `high` | | | `PATCH /kmip` | Update-PfbKmip | | | `high` | | @@ -309,10 +310,10 @@ Endpoints an existing cmdlet already calls, where the capability map knows of a | `PATCH /log-targets/object-store` | Update-PfbLogTargetObjectStore | | | `high` | | | `PATCH /logs-async` | Update-PfbAsyncLog | | | `high` | | | `PATCH /management-access-policies` | Update-PfbManagementAccessPolicy | | | `high` | POST/PATCH/DELETE return 403 regardless of account; not an implementation bug | -| `PATCH /network-access-policies` | Update-PfbNetworkAccessPolicy | versions | enabled, location, name, rules | `partial` -- /!\ 1 unresolved param (see Partial-confidence detail below) | | +| `PATCH /network-access-policies` | Update-PfbNetworkAccessPolicy | versions | location, name, rules | `high` | | | `PATCH /network-access-policies/rules` | Update-PfbNetworkAccessRule | before_rule_id, before_rule_name, ids, versions | client, effect, index, interfaces, policy | `high` | | | `PATCH /network-interfaces/connectors` | Update-PfbNetworkInterfaceConnector | | | `high` | | -| `PATCH /nfs-export-policies` | Update-PfbNfsExportPolicy | versions | enabled, location, name, rules | `partial` -- /!\ 1 unresolved param (see Partial-confidence detail below) | | +| `PATCH /nfs-export-policies` | Update-PfbNfsExportPolicy | versions | location, name, rules | `high` | | | `PATCH /nfs-export-policies/rules` | Update-PfbNfsExportRule | before_rule_id, before_rule_name, ids, versions | access, anongid, anonuid, atime, client, fileid_32bit, index, permission, required_transport_security, secure, security | `high` | | | `PATCH /nodes` | Update-PfbNode | | | `high` | | | `PATCH /object-store-access-policies/rules` | Update-PfbObjectStoreAccessPolicyRule | enforce_action_restrictions, policy_ids, policy_names | actions, conditions, effect, resources | `high` | | @@ -321,19 +322,19 @@ Endpoints an existing cmdlet already calls, where the capability map knows of a | `PATCH /object-store-roles/object-store-trust-policies/rules` | Update-PfbObjectStoreTrustPolicyRule | indices, policy_names, role_ids, role_names | actions, conditions, policy, principals | `high` | | | `PATCH /object-store-virtual-hosts` | Update-PfbObjectStoreVirtualHost | | | `high` | | | `PATCH /password-policies` | Update-PfbPasswordPolicy | ids, names | enabled, enforce_dictionary_check, enforce_username_check, location, lockout_duration, max_login_attempts, max_password_age, min_character_groups, min_characters_per_group, min_password_age, min_password_length, name, password_history | `high` | | -| `PATCH /policies` | Update-PfbPolicy | destroy_snapshots | add_rules, enabled, location, remove_rules | `partial` -- /!\ 1 unresolved param (see Partial-confidence detail below) | | +| `PATCH /policies` | Update-PfbPolicy | destroy_snapshots | add_rules, location, remove_rules | `high` | | | `PATCH /qos-policies` | Update-PfbQosPolicy | | | `high` | | -| `PATCH /quotas/groups` | Update-PfbQuotaGroup | file_system_ids, file_system_names, gids, group_names, names | | `partial` -- /!\ 3 unresolved params (see Partial-confidence detail below) | | +| `PATCH /quotas/groups` | Update-PfbQuotaGroup | file_system_ids, names | | `high` | | | `PATCH /quotas/settings` | Update-PfbQuotaSettings | | contact, direct_notifications_enabled | `high` | | -| `PATCH /quotas/users` | Update-PfbQuotaUser | file_system_ids, file_system_names, names, uids, user_names | | `partial` -- /!\ 2 unresolved params (see Partial-confidence detail below) | | +| `PATCH /quotas/users` | Update-PfbQuotaUser | file_system_ids, names, uids | | `high` | | | `PATCH /rapid-data-locking` | Update-PfbRapidDataLocking | | enabled, kmip_server | `high` | | -| `PATCH /realms` | Remove-PfbRealm, Update-PfbRealm | | default_inbound_tls_policy, destroyed, name | `partial` -- /!\ 2 unresolved params (see Partial-confidence detail below) | | +| `PATCH /realms` | Remove-PfbRealm, Update-PfbRealm | | default_inbound_tls_policy, name | `high` | | | `PATCH /realms/defaults` | Update-PfbRealmDefaults | | | `high` | | -| `PATCH /s3-export-policies` | Update-PfbS3ExportPolicy | | enabled, name, rules | `partial` -- /!\ 1 unresolved param (see Partial-confidence detail below) | | +| `PATCH /s3-export-policies` | Update-PfbS3ExportPolicy | | name, rules | `high` | | | `PATCH /s3-export-policies/rules` | Update-PfbS3ExportRule | policy_ids, policy_names | actions, effect, resources | `high` | | -| `PATCH /smb-client-policies` | Update-PfbSmbClientPolicy | | access_based_enumeration_enabled, enabled, location, name, rules | `partial` -- /!\ 1 unresolved param (see Partial-confidence detail below) | | +| `PATCH /smb-client-policies` | Update-PfbSmbClientPolicy | | access_based_enumeration_enabled, location, name, rules | `high` | | | `PATCH /smb-client-policies/rules` | Update-PfbSmbClientRule | before_rule_id, before_rule_name, ids, versions | client, encryption, index, permission, policy | `high` | | -| `PATCH /smb-share-policies` | Update-PfbSmbSharePolicy | | enabled, location, name, rules | `partial` -- /!\ 1 unresolved param (see Partial-confidence detail below) | | +| `PATCH /smb-share-policies` | Update-PfbSmbSharePolicy | | location, name, rules | `high` | | | `PATCH /smb-share-policies/rules` | Update-PfbSmbShareRule | ids, policy_ids, policy_names | change, full_control, policy, principal, read | `high` | | | `PATCH /smtp-servers` | Update-PfbSmtpServer | | | `high` | | | `PATCH /snmp-agents` | Update-PfbSnmpAgent | | v2c, v3, version | `high` | | @@ -348,16 +349,15 @@ Endpoints an existing cmdlet already calls, where the capability map knows of a | `PATCH /syslog-servers/settings` | Update-PfbSyslogServerSettings | ids, names | ca_certificate, ca_certificate_group | `high` | | | `PATCH /targets` | Update-PfbTarget | | | `high` | | | `PATCH /tls-policies` | Update-PfbTlsPolicy | | | `high` | | -| `PATCH /user-group-quota-policies` | Update-PfbUserGroupQuotaPolicy | | enabled, name | `partial` -- /!\ 1 unresolved param (see Partial-confidence detail below) | | +| `PATCH /user-group-quota-policies` | Update-PfbUserGroupQuotaPolicy | | name | `high` | | | `PATCH /user-group-quota-policies/rules` | Update-PfbUserGroupQuotaPolicyRule | | | `high` | | -| `PATCH /workloads` | Update-PfbWorkload | | destroyed | `partial` -- /!\ 1 unresolved param (see Partial-confidence detail below) | | | `PATCH /worm-data-policies` | Update-PfbWormPolicy | | | `high` | | -| `POST /active-directory` | New-PfbActiveDirectory | join_existing_account, names | ca_certificate, ca_certificate_group, computer_name, directory_servers, domain, encryption_types, fqdns, global_catalog_servers, join_ou, kerberos_servers, password, service_principal_names, user | `partial` -- /!\ 1 unresolved param (see Partial-confidence detail below) | | +| `POST /active-directory` | New-PfbActiveDirectory | join_existing_account | ca_certificate, ca_certificate_group, computer_name, directory_servers, domain, encryption_types, fqdns, global_catalog_servers, join_ou, kerberos_servers, password, service_principal_names, user | `high` | | | `POST /api-clients` | New-PfbApiClient | | access_policies, access_token_ttl_in_ms, issuer | `high` | | | `POST /array-connections` | New-PfbArrayConnection | | | `high` | | | `POST /arrays/erasures` | New-PfbArrayErasure | eradicate_all_data, preserve_configuration_data, skip_phonehome_check | | `high` | | -| `POST /audit-file-systems-policies` | New-PfbAuditFileSystemPolicy | | control_type, enabled, location, log_targets, name, rules | `partial` -- /!\ 1 unresolved param (see Partial-confidence detail below) | | -| `POST /audit-object-store-policies` | New-PfbAuditObjectStorePolicy | | enabled, location, log_targets, name | `partial` -- /!\ 1 unresolved param (see Partial-confidence detail below) | | +| `POST /audit-file-systems-policies` | New-PfbAuditFileSystemPolicy | | control_type, location, log_targets, name, rules | `high` | | +| `POST /audit-object-store-policies` | New-PfbAuditObjectStorePolicy | | location, log_targets, name | `high` | | | `POST /buckets` | New-PfbBucket | | bucket_type, eradication_config, hard_limit_enabled, object_lock_config, retention_lock | `high` | | | `POST /buckets/audit-filters` | New-PfbBucketAuditFilter | bucket_ids, names | actions, s3_prefixes | `partial` -- /!\ 1 unresolved param (see Partial-confidence detail below) | | | `POST /buckets/bucket-access-policies` | New-PfbBucketAccessPolicy | bucket_ids | rules | `high` | | @@ -365,31 +365,29 @@ Endpoints an existing cmdlet already calls, where the capability map knows of a | `POST /buckets/cross-origin-resource-sharing-policies` | New-PfbBucketCorsPolicy | bucket_ids | rules | `high` | | | `POST /buckets/cross-origin-resource-sharing-policies/rules` | New-PfbBucketCorsPolicyRule | bucket_ids | allowed_headers, allowed_methods, allowed_origins | `high` | | | `POST /certificates` | New-PfbCertificate | | certificate, certificate_type, common_name, country, days, email, intermediate_certificate, key_algorithm, key_size, locality, organization, organizational_unit, passphrase, private_key, state, subject_alternative_names | `high` | | -| `POST /certificates/certificate-signing-requests` | New-PfbCertificateSigningRequest | | certificate, common_name, country, email, locality, organization, organizational_unit, state, subject_alternative_names | `partial` -- /!\ 1 unresolved param (see Partial-confidence detail below) | | +| `POST /certificates/certificate-signing-requests` | New-PfbCertificateSigningRequest | | certificate, common_name, country, email, locality, organization, organizational_unit, state, subject_alternative_names | `high` | | | `POST /data-eviction-policies` | New-PfbDataEvictionPolicy | | enabled, location, name | `partial` -- /!\ 1 unresolved param (see Partial-confidence detail below) | | | `POST /directory-services/local/groups` | New-PfbLocalGroup | local_directory_service_ids, local_directory_service_names | | `high` | | | `POST /directory-services/local/groups/members` | New-PfbLocalGroupMember | group_gids, group_sids, local_directory_service_ids | members | `partial` -- /!\ 1 unresolved param (see Partial-confidence detail below) | | | `POST /directory-services/roles` | New-PfbDirectoryServiceRole | | group, group_base, management_access_policies, role | `high` | | -| `POST /dns` | New-PfbDns | names | ca_certificate, ca_certificate_group, domain, nameservers, services, sources | `partial` -- /!\ 1 unresolved param (see Partial-confidence detail below) | | +| `POST /dns` | New-PfbDns | | ca_certificate, ca_certificate_group, domain, nameservers, services, sources | `high` | | | `POST /file-system-exports` | New-PfbFileSystemExport | member_ids, policy_ids | | `high` | | | `POST /file-system-replica-links` | New-PfbFileSystemReplicaLink | local_file_system_ids | direction, link_type, local_file_system, policies, remote, remote_file_system | `high` | | | `POST /file-system-snapshots` | New-PfbFileSystemSnapshot | source_ids, source_names | | `partial` -- /!\ 1 unresolved param (see Partial-confidence detail below) | | -| `POST /file-systems` | New-PfbFileSystem | default_exports, discard_non_snapshotted_data, include_snapshot, overwrite, policy_ids, policy_names | eradication_config, fast_remove_directory_enabled, hard_limit_enabled, http, multi_protocol, nfs, node_group, smb, snapshot_directory_enabled, workload, writable | `partial` -- /!\ 17 unresolved params (see Partial-confidence detail below) | | -| `POST /fleets` | New-PfbFleet | names | | `partial` -- /!\ 1 unresolved param (see Partial-confidence detail below) | | +| `POST /file-systems` | New-PfbFileSystem | default_exports, discard_non_snapshotted_data, include_snapshot, overwrite, policy_ids, policy_names | eradication_config, hard_limit_enabled, http, multi_protocol, nfs, node_group, smb, snapshot_directory_enabled, workload | `partial` -- /!\ 15 unresolved params (see Partial-confidence detail below) | | | `POST /keytabs` | New-PfbKeytab | name_prefixes | source | `high` | | | `POST /keytabs/upload` | New-PfbKeytabUpload | name_prefixes | keytab_file | `high` | | | `POST /legal-holds` | New-PfbLegalHold | | description | `high` | | | `POST /lifecycle-rules` | New-PfbLifecycleRule | confirm_date | abort_incomplete_multipart_uploads_after, keep_current_version_for, keep_current_version_until, keep_previous_version_for, prefix, rule_id | `high` | | -| `POST /link-aggregation-groups` | New-PfbLag | names | ports | `partial` -- /!\ 1 unresolved param (see Partial-confidence detail below) | | +| `POST /link-aggregation-groups` | New-PfbLag | | ports | `high` | | | `POST /log-targets/file-systems` | New-PfbLogTargetFileSystem | | file_system, keep_for, keep_size, name | `high` | | | `POST /log-targets/object-store` | New-PfbLogTargetObjectStore | | bucket, log_name_prefix, log_rotate, name | `high` | | | `POST /maintenance-windows` | New-PfbMaintenanceWindow | names | timeout | `high` | | | `POST /management-access-policies` | New-PfbManagementAccessPolicy | | aggregation_strategy, enabled, location, name, rules | `high` | POST/PATCH/DELETE return 403 regardless of account; not an implementation bug | | `POST /network-access-policies/rules` | New-PfbNetworkAccessRule | | | `high` | | | `POST /network-interfaces` | New-PfbNetworkInterface | | rdma_enabled | `high` | | -| `POST /nfs-export-policies` | New-PfbNfsExportPolicy | | enabled, location, name, rules | `partial` -- /!\ 1 unresolved param (see Partial-confidence detail below) | | +| `POST /nfs-export-policies` | New-PfbNfsExportPolicy | | location, name, rules | `high` | | | `POST /nfs-export-policies/rules` | New-PfbNfsExportRule | | | `high` | | -| `POST /node-groups` | New-PfbNodeGroup | names | | `partial` -- /!\ 1 unresolved param (see Partial-confidence detail below) | | | `POST /object-store-access-keys` | New-PfbObjectStoreAccessKey | names | secret_access_key | `high` | | | `POST /object-store-access-policies` | New-PfbObjectStoreAccessPolicy | enforce_action_restrictions | description, rules | `high` | | | `POST /object-store-access-policies/object-store-roles` | New-PfbObjectStoreAccessPolicyRole | member_ids, policy_ids | | `high` | | @@ -403,34 +401,33 @@ Endpoints an existing cmdlet already calls, where the capability map knows of a | `POST /object-store-users` | New-PfbObjectStoreUser | full_access | | `high` | | | `POST /object-store-users/object-store-access-policies` | New-PfbObjectStoreUserAccessPolicy | member_ids, policy_ids | | `high` | | | `POST /object-store-virtual-hosts` | New-PfbObjectStoreVirtualHost | | attached_servers | `high` | | -| `POST /policies` | New-PfbPolicy | | enabled, location, name | `partial` -- /!\ 1 unresolved param (see Partial-confidence detail below) | | +| `POST /policies` | New-PfbPolicy | | location, name | `high` | | | `POST /presets/workload` | New-PfbPresetWorkload | | description, directory_configurations, export_configurations, parameters, periodic_replication_configurations, placement_configurations, platform_features, qos_configurations, quota_configurations, snapshot_configurations, volume_configurations, workload_tags, workload_type | `high` | | | `POST /public-keys` | New-PfbPublicKey | | public_key | `high` | | | `POST /qos-policies` | New-PfbQosPolicy | | enabled, location, max_total_bytes_per_sec, max_total_ops_per_sec, name | `high` | | -| `POST /quotas/groups` | New-PfbQuotaGroup | file_system_ids, file_system_names, gids, group_names | | `partial` -- /!\ 3 unresolved params (see Partial-confidence detail below) | | -| `POST /quotas/users` | New-PfbQuotaUser | file_system_ids, file_system_names, uids, user_names | | `partial` -- /!\ 3 unresolved params (see Partial-confidence detail below) | | +| `POST /quotas/groups` | New-PfbQuotaGroup | file_system_ids | | `high` | | +| `POST /quotas/users` | New-PfbQuotaUser | file_system_ids | | `high` | | | `POST /realms` | New-PfbRealm | without_default_access_list | | `high` | | -| `POST /s3-export-policies` | New-PfbS3ExportPolicy | | enabled, rules | `partial` -- /!\ 1 unresolved param (see Partial-confidence detail below) | | +| `POST /s3-export-policies` | New-PfbS3ExportPolicy | | rules | `high` | | | `POST /servers` | New-PfbServer | create_ds, create_local_directory_service | | `partial` -- /!\ 1 unresolved param (see Partial-confidence detail below) | | -| `POST /smb-client-policies` | New-PfbSmbClientPolicy | | access_based_enumeration_enabled, enabled, location, name, rules | `partial` -- /!\ 1 unresolved param (see Partial-confidence detail below) | | +| `POST /smb-client-policies` | New-PfbSmbClientPolicy | | access_based_enumeration_enabled, location, name, rules | `high` | | | `POST /smb-client-policies/rules` | New-PfbSmbClientRule | | | `high` | | -| `POST /smb-share-policies` | New-PfbSmbSharePolicy | | enabled, location, name, rules | `partial` -- /!\ 1 unresolved param (see Partial-confidence detail below) | | +| `POST /smb-share-policies` | New-PfbSmbSharePolicy | | location, name, rules | `high` | | | `POST /smb-share-policies/rules` | New-PfbSmbShareRule | | | `high` | | -| `POST /snmp-managers` | New-PfbSnmpManager | names | host, notification, v2c, v3, version | `partial` -- /!\ 1 unresolved param (see Partial-confidence detail below) | | +| `POST /snmp-managers` | New-PfbSnmpManager | | host, notification, v2c, v3, version | `high` | | | `POST /software-check` | New-PfbSoftwareCheck | software_names, software_versions | | `high` | | -| `POST /ssh-certificate-authority-policies` | New-PfbSshCaPolicy | names | enabled, location, name, signing_authority, static_authorized_principals | `partial` -- /!\ 1 unresolved param (see Partial-confidence detail below) | | +| `POST /ssh-certificate-authority-policies` | New-PfbSshCaPolicy | | enabled, location, name, signing_authority, static_authorized_principals | `high` | | | `POST /sso/oidc/idps` | New-PfbOidcIdp | | enabled, idp, services | `high` | | | `POST /sso/saml2/idps` | New-PfbSaml2Idp | | array_url, binding, enabled, idp, management, services, sp | `high` | | -| `POST /storage-class-tiering-policies` | New-PfbStorageClassTieringPolicy | names | archival_rules, enabled, location, name, retrieval_rules | `partial` -- /!\ 1 unresolved param (see Partial-confidence detail below) | | +| `POST /storage-class-tiering-policies` | New-PfbStorageClassTieringPolicy | | archival_rules, enabled, location, name, retrieval_rules | `high` | | | `POST /subnets` | New-PfbSubnet | | | `high` | | | `POST /support-diagnostics` | New-PfbSupportDiagnostics | analysis_period_end_time, analysis_period_start_time | | `high` | | -| `POST /syslog-servers` | New-PfbSyslogServer | names | services, sources, uri | `partial` -- /!\ 1 unresolved param (see Partial-confidence detail below) | | -| `POST /targets` | New-PfbTarget | names | address | `partial` -- /!\ 1 unresolved param (see Partial-confidence detail below) | | -| `POST /tls-policies` | New-PfbTlsPolicy | names | appliance_certificate, client_certificates_required, disabled_tls_ciphers, enabled, enabled_tls_ciphers, location, min_tls_version, name, trusted_client_certificate_authority, verify_client_certificate_trust | `partial` -- /!\ 1 unresolved param (see Partial-confidence detail below) | | -| `POST /user-group-quota-policies` | New-PfbUserGroupQuotaPolicy | | enabled, name | `partial` -- /!\ 1 unresolved param (see Partial-confidence detail below) | | -| `POST /user-group-quota-policies/rules` | New-PfbUserGroupQuotaPolicyRule | | enforced | `partial` -- /!\ 1 unresolved param (see Partial-confidence detail below) | | +| `POST /syslog-servers` | New-PfbSyslogServer | | services, sources, uri | `high` | | +| `POST /targets` | New-PfbTarget | | address | `high` | | +| `POST /tls-policies` | New-PfbTlsPolicy | | appliance_certificate, client_certificates_required, disabled_tls_ciphers, enabled, enabled_tls_ciphers, location, min_tls_version, name, trusted_client_certificate_authority, verify_client_certificate_trust | `high` | | +| `POST /user-group-quota-policies` | New-PfbUserGroupQuotaPolicy | | name | `high` | | | `POST /workloads/placement-recommendations` | New-PfbWorkloadPlacementRecommendation | | additional_constraints, parameters, preset, projection_months, recommendation_engine, results_limit | `partial` -- /!\ 1 unresolved param (see Partial-confidence detail below) | | -| `POST /worm-data-policies` | New-PfbWormPolicy | names | default_retention, enabled, location, max_retention, min_retention, mode, retention_lock | `partial` -- /!\ 1 unresolved param (see Partial-confidence detail below) | | +| `POST /worm-data-policies` | New-PfbWormPolicy | | default_retention, enabled, location, max_retention, min_retention, mode, retention_lock | `high` | | | `PUT /presets/workload` | Set-PfbPresetWorkload | | description, directory_configurations, export_configurations, name, parameters, periodic_replication_configurations, placement_configurations, platform_features, qos_configurations, quota_configurations, snapshot_configurations, volume_configurations, workload_tags, workload_type | `high` | | | `PUT /workloads/tags/batch` | Set-PfbWorkloadTag | | copyable, key, namespace, resource, value | `partial` -- /!\ 1 unresolved param (see Partial-confidence detail below) | | @@ -440,61 +437,19 @@ Per the decision-6 procedure above: open each parameter at its `file:line` and f | Endpoint | Parameter | Surface | File:Line | Caveat | |---|---|---|---|---| -| `DELETE /file-systems/sessions` | `-Force` | TypedUnresolved | `Public/FileSystem/Remove-PfbFileSystemSession.ps1:59` | one or more parameters could not be traced to a wire name and have no -Attributes escape hatch; lists reflect typed-parameter coverage only, not full wire reachability | -| `DELETE /quotas/groups` | `-FileSystemName` | TypedUnresolved | `Public/Quota/Remove-PfbQuotaGroup.ps1:35` | one or more parameters could not be traced to a wire name and have no -Attributes escape hatch; lists reflect typed-parameter coverage only, not full wire reachability | -| `DELETE /quotas/groups` | `-GroupId` | TypedUnresolved | `Public/Quota/Remove-PfbQuotaGroup.ps1:37` | one or more parameters could not be traced to a wire name and have no -Attributes escape hatch; lists reflect typed-parameter coverage only, not full wire reachability | -| `DELETE /quotas/groups` | `-GroupName` | TypedUnresolved | `Public/Quota/Remove-PfbQuotaGroup.ps1:36` | one or more parameters could not be traced to a wire name and have no -Attributes escape hatch; lists reflect typed-parameter coverage only, not full wire reachability | -| `DELETE /quotas/users` | `-FileSystemName` | TypedUnresolved | `Public/Quota/Remove-PfbQuotaUser.ps1:25` | one or more parameters could not be traced to a wire name and have no -Attributes escape hatch; lists reflect typed-parameter coverage only, not full wire reachability | -| `DELETE /quotas/users` | `-UserName` | TypedUnresolved | `Public/Quota/Remove-PfbQuotaUser.ps1:26` | one or more parameters could not be traced to a wire name and have no -Attributes escape hatch; lists reflect typed-parameter coverage only, not full wire reachability | -| `DELETE /servers` | `-Eradicate` | TypedUnresolved | `Public/Server/Remove-PfbServer.ps1:35` | one or more parameters could not be traced to a wire name and have no -Attributes escape hatch; lists reflect typed-parameter coverage only, not full wire reachability | | `GET /arrays` | `-Endpoint` | TypedUnresolved | `Public/Connection/Test-PfbConnection.ps1:31` | one or more parameters could not be traced to a wire name and have no -Attributes escape hatch; lists reflect typed-parameter coverage only, not full wire reachability | -| `GET /user-group-quota-policies` | `-Id` | TypedUnresolved | `Public/Policy/Get-PfbUserGroupQuotaPolicy.ps1:36` | one or more parameters could not be traced to a wire name and have no -Attributes escape hatch; lists reflect typed-parameter coverage only, not full wire reachability | -| `GET /user-group-quota-policies` | `-Name` | TypedUnresolved | `Public/Policy/Get-PfbUserGroupQuotaPolicy.ps1:33` | one or more parameters could not be traced to a wire name and have no -Attributes escape hatch; lists reflect typed-parameter coverage only, not full wire reachability | -| `PATCH /alert-watchers` | `-Enabled` | AttributesOnly | `Public/Alert/Update-PfbAlertWatcher.ps1:32` | body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability | -| `PATCH /alerts` | `-Flagged` | AttributesOnly | `Public/Alert/Update-PfbAlert.ps1:26` | body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability | -| `PATCH /audit-file-systems-policies` | `-Enabled` | AttributesOnly | `Public/Policy/Update-PfbAuditFileSystemPolicy.ps1:40` | body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability | -| `PATCH /audit-object-store-policies` | `-Enabled` | AttributesOnly | `Public/Policy/Update-PfbAuditObjectStorePolicy.ps1:40` | body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability | -| `PATCH /buckets` | `-Destroyed` | AttributesOnly | `Public/Bucket/Update-PfbBucket.ps1:37` | body reachable via -Attributes for some parameters and untraceable for others; lists reflect typed-parameter coverage only, not full wire reachability | -| `PATCH /buckets` | `-Eradicate` | TypedUnresolved | `Public/Bucket/Remove-PfbBucket.ps1:27` | body reachable via -Attributes for some parameters and untraceable for others; lists reflect typed-parameter coverage only, not full wire reachability | -| `PATCH /data-eviction-policies` | `-Enabled` | TypedUnresolved | `Public/DataEviction/Update-PfbDataEvictionPolicy.ps1:37` | one or more parameters could not be traced to a wire name and have no -Attributes escape hatch; lists reflect typed-parameter coverage only, not full wire reachability | -| `PATCH /directory-services` | `-Name` | AttributesOnly | `Public/DirectoryService/Update-PfbDirectoryService.ps1:32` | body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability | -| `PATCH /file-system-snapshots` | `-Eradicate` | TypedUnresolved | `Public/FileSystemSnapshot/Remove-PfbFileSystemSnapshot.ps1:24` | one or more parameters could not be traced to a wire name and have no -Attributes escape hatch; lists reflect typed-parameter coverage only, not full wire reachability | -| `PATCH /file-systems` | `-Destroyed` | AttributesOnly | `Public/FileSystem/Update-PfbFileSystem.ps1:93` | body reachable via -Attributes for some parameters and untraceable for others; lists reflect typed-parameter coverage only, not full wire reachability | -| `PATCH /file-systems` | `-Eradicate` | TypedUnresolved | `Public/FileSystem/Remove-PfbFileSystem.ps1:38` | body reachable via -Attributes for some parameters and untraceable for others; lists reflect typed-parameter coverage only, not full wire reachability | -| `PATCH /file-systems` | `-HardLimitEnabled` | AttributesOnly | `Public/FileSystem/Update-PfbFileSystem.ps1:69` | body reachable via -Attributes for some parameters and untraceable for others; lists reflect typed-parameter coverage only, not full wire reachability | -| `PATCH /file-systems` | `-HttpEnabled` | AttributesOnly | `Public/FileSystem/Update-PfbFileSystem.ps1:90` | body reachable via -Attributes for some parameters and untraceable for others; lists reflect typed-parameter coverage only, not full wire reachability | -| `PATCH /file-systems` | `-NfsEnabled` | AttributesOnly | `Public/FileSystem/Update-PfbFileSystem.ps1:72` | body reachable via -Attributes for some parameters and untraceable for others; lists reflect typed-parameter coverage only, not full wire reachability | -| `PATCH /file-systems` | `-NfsExportPolicy` | AttributesOnly | `Public/FileSystem/Update-PfbFileSystem.ps1:78` | body reachable via -Attributes for some parameters and untraceable for others; lists reflect typed-parameter coverage only, not full wire reachability | -| `PATCH /file-systems` | `-NfsRules` | AttributesOnly | `Public/FileSystem/Update-PfbFileSystem.ps1:75` | body reachable via -Attributes for some parameters and untraceable for others; lists reflect typed-parameter coverage only, not full wire reachability | -| `PATCH /file-systems` | `-SmbClientPolicy` | AttributesOnly | `Public/FileSystem/Update-PfbFileSystem.ps1:87` | body reachable via -Attributes for some parameters and untraceable for others; lists reflect typed-parameter coverage only, not full wire reachability | -| `PATCH /file-systems` | `-SmbEnabled` | AttributesOnly | `Public/FileSystem/Update-PfbFileSystem.ps1:81` | body reachable via -Attributes for some parameters and untraceable for others; lists reflect typed-parameter coverage only, not full wire reachability | -| `PATCH /file-systems` | `-SmbSharePolicy` | AttributesOnly | `Public/FileSystem/Update-PfbFileSystem.ps1:84` | body reachable via -Attributes for some parameters and untraceable for others; lists reflect typed-parameter coverage only, not full wire reachability | -| `PATCH /network-access-policies` | `-Enabled` | AttributesOnly | `Public/Policy/Update-PfbNetworkAccessPolicy.ps1:42` | body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability | -| `PATCH /nfs-export-policies` | `-Enabled` | AttributesOnly | `Public/Policy/Update-PfbNfsExportPolicy.ps1:42` | body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability | -| `PATCH /policies` | `-Enabled` | AttributesOnly | `Public/Policy/Update-PfbPolicy.ps1:28` | body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability | -| `PATCH /quotas/groups` | `-FileSystemName` | AttributesOnly | `Public/Quota/Update-PfbQuotaGroup.ps1:40` | body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability | -| `PATCH /quotas/groups` | `-GroupId` | AttributesOnly | `Public/Quota/Update-PfbQuotaGroup.ps1:42` | body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability | -| `PATCH /quotas/groups` | `-GroupName` | AttributesOnly | `Public/Quota/Update-PfbQuotaGroup.ps1:41` | body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability | -| `PATCH /quotas/users` | `-FileSystemName` | AttributesOnly | `Public/Quota/Update-PfbQuotaUser.ps1:30` | body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability | -| `PATCH /quotas/users` | `-UserName` | AttributesOnly | `Public/Quota/Update-PfbQuotaUser.ps1:31` | body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability | -| `PATCH /realms` | `-Destroyed` | AttributesOnly | `Public/Realm/Update-PfbRealm.ps1:31` | body reachable via -Attributes for some parameters and untraceable for others; lists reflect typed-parameter coverage only, not full wire reachability | -| `PATCH /realms` | `-Eradicate` | TypedUnresolved | `Public/Realm/Remove-PfbRealm.ps1:32` | body reachable via -Attributes for some parameters and untraceable for others; lists reflect typed-parameter coverage only, not full wire reachability | -| `PATCH /s3-export-policies` | `-Enabled` | AttributesOnly | `Public/Policy/Update-PfbS3ExportPolicy.ps1:42` | body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability | -| `PATCH /smb-client-policies` | `-Enabled` | AttributesOnly | `Public/Policy/Update-PfbSmbClientPolicy.ps1:42` | body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability | -| `PATCH /smb-share-policies` | `-Enabled` | AttributesOnly | `Public/Policy/Update-PfbSmbSharePolicy.ps1:42` | body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability | -| `PATCH /user-group-quota-policies` | `-Enabled` | AttributesOnly | `Public/Policy/Update-PfbUserGroupQuotaPolicy.ps1:36` | body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability | -| `PATCH /workloads` | `-Destroyed` | TypedUnresolved | `Public/Workloads/Update-PfbWorkload.ps1:36` | one or more parameters could not be traced to a wire name and have no -Attributes escape hatch; lists reflect typed-parameter coverage only, not full wire reachability | -| `POST /active-directory` | `-Name` | AttributesOnly | `Public/DirectoryService/New-PfbActiveDirectory.ps1:40` | body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability | -| `POST /audit-file-systems-policies` | `-Enabled` | AttributesOnly | `Public/Policy/New-PfbAuditFileSystemPolicy.ps1:35` | body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability | -| `POST /audit-object-store-policies` | `-Enabled` | AttributesOnly | `Public/Policy/New-PfbAuditObjectStorePolicy.ps1:35` | body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability | +| `PATCH /buckets/audit-filters` | `-BucketName` | AttributesOnly | `Public/Bucket/Update-PfbBucketAuditFilter.ps1:78` | body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability | +| `PATCH /file-systems` | `-NfsEnabled` | AttributesOnly | `Public/FileSystem/Update-PfbFileSystem.ps1:72` | body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability | +| `PATCH /file-systems` | `-NfsExportPolicy` | AttributesOnly | `Public/FileSystem/Update-PfbFileSystem.ps1:78` | body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability | +| `PATCH /file-systems` | `-NfsRules` | AttributesOnly | `Public/FileSystem/Update-PfbFileSystem.ps1:75` | body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability | +| `PATCH /file-systems` | `-SmbClientPolicy` | AttributesOnly | `Public/FileSystem/Update-PfbFileSystem.ps1:87` | body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability | +| `PATCH /file-systems` | `-SmbEnabled` | AttributesOnly | `Public/FileSystem/Update-PfbFileSystem.ps1:81` | body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability | +| `PATCH /file-systems` | `-SmbSharePolicy` | AttributesOnly | `Public/FileSystem/Update-PfbFileSystem.ps1:84` | body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability | | `POST /buckets/audit-filters` | `-Name` | AttributesOnly | `Public/Bucket/New-PfbBucketAuditFilter.ps1:44` | body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability | -| `POST /certificates/certificate-signing-requests` | `-Name` | AttributesOnly | `Public/Certificate/New-PfbCertificateSigningRequest.ps1:29` | body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability | | `POST /data-eviction-policies` | `-Disabled` | TypedUnresolved | `Public/DataEviction/New-PfbDataEvictionPolicy.ps1:31` | one or more parameters could not be traced to a wire name and have no -Attributes escape hatch; lists reflect typed-parameter coverage only, not full wire reachability | | `POST /directory-services/local/groups/members` | `-Member` | TypedUnresolved | `Public/DirectoryService/New-PfbLocalGroupMember.ps1:35` | one or more parameters could not be traced to a wire name and have no -Attributes escape hatch; lists reflect typed-parameter coverage only, not full wire reachability | -| `POST /dns` | `-Name` | AttributesOnly | `Public/Network/New-PfbDns.ps1:29` | body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability | | `POST /file-system-snapshots` | `-SourceName` | TypedUnresolved | `Public/FileSystemSnapshot/New-PfbFileSystemSnapshot.ps1:36` | one or more parameters could not be traced to a wire name and have no -Attributes escape hatch; lists reflect typed-parameter coverage only, not full wire reachability | | `POST /file-systems` | `-DefaultExports` | AttributesOnly | `Public/FileSystem/New-PfbFileSystem.ps1:208` | body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability | -| `POST /file-systems` | `-FastRemoveDirectoryEnabled` | AttributesOnly | `Public/FileSystem/New-PfbFileSystem.ps1:188` | body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability | | `POST /file-systems` | `-HardLimit` | AttributesOnly | `Public/FileSystem/New-PfbFileSystem.ps1:139` | body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability | | `POST /file-systems` | `-Http` | AttributesOnly | `Public/FileSystem/New-PfbFileSystem.ps1:175` | body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability | | `POST /file-systems` | `-MultiProtocolAccessControlStyle` | AttributesOnly | `Public/FileSystem/New-PfbFileSystem.ps1:178` | body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability | @@ -509,32 +464,8 @@ Per the decision-6 procedure above: open each parameter at its `file:line` and f | `POST /file-systems` | `-SmbContinuousAvailabilityEnabled` | AttributesOnly | `Public/FileSystem/New-PfbFileSystem.ps1:172` | body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability | | `POST /file-systems` | `-SmbSharePolicy` | AttributesOnly | `Public/FileSystem/New-PfbFileSystem.ps1:166` | body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability | | `POST /file-systems` | `-SnapshotDirectoryEnabled` | AttributesOnly | `Public/FileSystem/New-PfbFileSystem.ps1:185` | body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability | -| `POST /file-systems` | `-Writable` | AttributesOnly | `Public/FileSystem/New-PfbFileSystem.ps1:195` | body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability | -| `POST /fleets` | `-Name` | AttributesOnly | `Public/Replication/New-PfbFleet.ps1:30` | body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability | -| `POST /link-aggregation-groups` | `-Name` | AttributesOnly | `Public/Misc/New-PfbLag.ps1:29` | body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability | -| `POST /nfs-export-policies` | `-Enabled` | AttributesOnly | `Public/Policy/New-PfbNfsExportPolicy.ps1:36` | body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability | -| `POST /node-groups` | `-Name` | AttributesOnly | `Public/Node/New-PfbNodeGroup.ps1:30` | body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability | -| `POST /policies` | `-Enabled` | AttributesOnly | `Public/Policy/New-PfbPolicy.ps1:23` | body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability | -| `POST /quotas/groups` | `-FileSystemName` | AttributesOnly | `Public/Quota/New-PfbQuotaGroup.ps1:40` | body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability | -| `POST /quotas/groups` | `-GroupId` | AttributesOnly | `Public/Quota/New-PfbQuotaGroup.ps1:42` | body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability | -| `POST /quotas/groups` | `-GroupName` | AttributesOnly | `Public/Quota/New-PfbQuotaGroup.ps1:41` | body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability | -| `POST /quotas/users` | `-FileSystemName` | AttributesOnly | `Public/Quota/New-PfbQuotaUser.ps1:42` | body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability | -| `POST /quotas/users` | `-UserId` | AttributesOnly | `Public/Quota/New-PfbQuotaUser.ps1:44` | body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability | -| `POST /quotas/users` | `-UserName` | AttributesOnly | `Public/Quota/New-PfbQuotaUser.ps1:43` | body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability | -| `POST /s3-export-policies` | `-Enabled` | AttributesOnly | `Public/Policy/New-PfbS3ExportPolicy.ps1:36` | body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability | | `POST /servers` | `-CreateDirectoryService` | AttributesOnly | `Public/Server/New-PfbServer.ps1:40` | body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability | -| `POST /smb-client-policies` | `-Enabled` | AttributesOnly | `Public/Policy/New-PfbSmbClientPolicy.ps1:36` | body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability | -| `POST /smb-share-policies` | `-Enabled` | AttributesOnly | `Public/Policy/New-PfbSmbSharePolicy.ps1:36` | body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability | -| `POST /snmp-managers` | `-Name` | AttributesOnly | `Public/Monitoring/New-PfbSnmpManager.ps1:33` | body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability | -| `POST /ssh-certificate-authority-policies` | `-Name` | AttributesOnly | `Public/Policy/New-PfbSshCaPolicy.ps1:38` | body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability | -| `POST /storage-class-tiering-policies` | `-Name` | AttributesOnly | `Public/Policy/New-PfbStorageClassTieringPolicy.ps1:30` | body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability | -| `POST /syslog-servers` | `-Name` | AttributesOnly | `Public/Monitoring/New-PfbSyslogServer.ps1:32` | body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability | -| `POST /targets` | `-Name` | AttributesOnly | `Public/Replication/New-PfbTarget.ps1:31` | body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability | -| `POST /tls-policies` | `-Name` | AttributesOnly | `Public/Policy/New-PfbTlsPolicy.ps1:29` | body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability | -| `POST /user-group-quota-policies` | `-Enabled` | AttributesOnly | `Public/Policy/New-PfbUserGroupQuotaPolicy.ps1:49` | body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability | -| `POST /user-group-quota-policies/rules` | `-Enforced` | AttributesOnly | `Public/Policy/New-PfbUserGroupQuotaPolicyRule.ps1:57` | body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability | | `POST /workloads/placement-recommendations` | `-Inputs` | TypedUnresolved | `Public/Workloads/New-PfbWorkloadPlacementRecommendation.ps1:29` | one or more parameters could not be traced to a wire name and have no -Attributes escape hatch; lists reflect typed-parameter coverage only, not full wire reachability | -| `POST /worm-data-policies` | `-Name` | AttributesOnly | `Public/Policy/New-PfbWormPolicy.ps1:30` | body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability | | `PUT /workloads/tags/batch` | `-Tags` | TypedUnresolved | `Public/Workloads/Set-PfbWorkloadTag.ps1:30` | one or more parameters could not be traced to a wire name and have no -Attributes escape hatch; lists reflect typed-parameter coverage only, not full wire reachability | ## Read-only fields (not addressable) diff --git a/Reports/PfbDeadKeyReport.json b/Reports/PfbDeadKeyReport.json index 84eb0438..3e75eafd 100644 --- a/Reports/PfbDeadKeyReport.json +++ b/Reports/PfbDeadKeyReport.json @@ -2,17 +2,43 @@ "specVersion": "2.28", "counts": { "parametersInventoried": 2168, - "keysEvaluated": 1747, - "ok": 1664, - "deadKey": 83, + "keysEvaluated": 1779, + "ok": 1694, + "deadKey": 85, "skipReasons": { - "wire name unresolved": 127, - "body property": 280, + "wire name unresolved": 32, + "outside standard request": 28, + "not wire parameter": 6, + "body property": 309, "endpoint/method ambiguous": 14, "endpoint/verb absent from spec": 0 } }, "deadKeys": [ + { + "severity": "WRONG-RESULTS", + "cmdlet": "Get-PfbAlert", + "parameter": "Flagged", + "wireKey": "flagged", + "method": "GET", + "endpoint": "alerts", + "declared": [ + "continuation_token", + "filter", + "ids", + "limit", + "names", + "offset", + "sort" + ], + "classification": "WRONG-SURFACE", + "declaredElsewhere": [ + { + "method": "PATCH", + "surface": "Body" + } + ] + }, { "severity": "WRONG-RESULTS", "cmdlet": "Get-PfbArrayClientPerformance", @@ -27,7 +53,9 @@ "protocol", "sort", "total_only" - ] + ], + "classification": "UNDECLARED", + "declaredElsewhere": [] }, { "severity": "WRONG-RESULTS", @@ -43,7 +71,9 @@ "protocol", "sort", "total_only" - ] + ], + "classification": "UNDECLARED", + "declaredElsewhere": [] }, { "severity": "WRONG-RESULTS", @@ -59,7 +89,9 @@ "protocol", "sort", "total_only" - ] + ], + "classification": "UNDECLARED", + "declaredElsewhere": [] }, { "severity": "WRONG-RESULTS", @@ -74,7 +106,9 @@ "names", "sort", "total_only" - ] + ], + "classification": "UNDECLARED", + "declaredElsewhere": [] }, { "severity": "WRONG-RESULTS", @@ -89,7 +123,9 @@ "names", "sort", "total_only" - ] + ], + "classification": "UNDECLARED", + "declaredElsewhere": [] }, { "severity": "WRONG-RESULTS", @@ -104,7 +140,9 @@ "names", "sort", "total_only" - ] + ], + "classification": "UNDECLARED", + "declaredElsewhere": [] }, { "severity": "WRONG-RESULTS", @@ -113,7 +151,9 @@ "wireKey": "filter", "method": "GET", "endpoint": "arrays/erasures", - "declared": [] + "declared": [], + "classification": "UNDECLARED", + "declaredElsewhere": [] }, { "severity": "WRONG-RESULTS", @@ -122,7 +162,9 @@ "wireKey": "limit", "method": "GET", "endpoint": "arrays/erasures", - "declared": [] + "declared": [], + "classification": "UNDECLARED", + "declaredElsewhere": [] }, { "severity": "WRONG-RESULTS", @@ -131,7 +173,9 @@ "wireKey": "sort", "method": "GET", "endpoint": "arrays/erasures", - "declared": [] + "declared": [], + "classification": "UNDECLARED", + "declaredElsewhere": [] }, { "severity": "WRONG-RESULTS", @@ -146,7 +190,9 @@ "end_time", "resolution", "start_time" - ] + ], + "classification": "UNDECLARED", + "declaredElsewhere": [] }, { "severity": "WRONG-RESULTS", @@ -161,7 +207,9 @@ "end_time", "resolution", "start_time" - ] + ], + "classification": "UNDECLARED", + "declaredElsewhere": [] }, { "severity": "WRONG-RESULTS", @@ -176,7 +224,9 @@ "end_time", "resolution", "start_time" - ] + ], + "classification": "UNDECLARED", + "declaredElsewhere": [] }, { "severity": "WRONG-RESULTS", @@ -191,7 +241,9 @@ "end_time", "resolution", "start_time" - ] + ], + "classification": "UNDECLARED", + "declaredElsewhere": [] }, { "severity": "WRONG-RESULTS", @@ -206,7 +258,9 @@ "end_time", "resolution", "start_time" - ] + ], + "classification": "UNDECLARED", + "declaredElsewhere": [] }, { "severity": "WRONG-RESULTS", @@ -221,7 +275,9 @@ "end_time", "resolution", "start_time" - ] + ], + "classification": "UNDECLARED", + "declaredElsewhere": [] }, { "severity": "WRONG-RESULTS", @@ -237,7 +293,9 @@ "resolution", "start_time", "type" - ] + ], + "classification": "UNDECLARED", + "declaredElsewhere": [] }, { "severity": "WRONG-RESULTS", @@ -253,7 +311,9 @@ "resolution", "start_time", "type" - ] + ], + "classification": "UNDECLARED", + "declaredElsewhere": [] }, { "severity": "WRONG-RESULTS", @@ -269,7 +329,9 @@ "resolution", "start_time", "type" - ] + ], + "classification": "UNDECLARED", + "declaredElsewhere": [] }, { "severity": "WRONG-RESULTS", @@ -284,7 +346,9 @@ "end_time", "resolution", "start_time" - ] + ], + "classification": "UNDECLARED", + "declaredElsewhere": [] }, { "severity": "WRONG-RESULTS", @@ -299,7 +363,9 @@ "end_time", "resolution", "start_time" - ] + ], + "classification": "UNDECLARED", + "declaredElsewhere": [] }, { "severity": "WRONG-RESULTS", @@ -314,7 +380,9 @@ "end_time", "resolution", "start_time" - ] + ], + "classification": "UNDECLARED", + "declaredElsewhere": [] }, { "severity": "WRONG-RESULTS", @@ -325,7 +393,9 @@ "endpoint": "logs-async/download", "declared": [ "names" - ] + ], + "classification": "UNDECLARED", + "declaredElsewhere": [] }, { "severity": "WRONG-RESULTS", @@ -346,7 +416,9 @@ "limit", "names", "paths" - ] + ], + "classification": "UNDECLARED", + "declaredElsewhere": [] }, { "severity": "WRONG-RESULTS", @@ -367,7 +439,9 @@ "limit", "names", "paths" - ] + ], + "classification": "UNDECLARED", + "declaredElsewhere": [] }, { "severity": "WRONG-RESULTS", @@ -382,7 +456,9 @@ "continuation_token", "filter", "limit" - ] + ], + "classification": "UNDECLARED", + "declaredElsewhere": [] }, { "severity": "WRONG-RESULTS", @@ -397,7 +473,9 @@ "continuation_token", "filter", "limit" - ] + ], + "classification": "UNDECLARED", + "declaredElsewhere": [] }, { "severity": "WRONG-RESULTS", @@ -412,7 +490,9 @@ "continuation_token", "filter", "limit" - ] + ], + "classification": "UNDECLARED", + "declaredElsewhere": [] }, { "severity": "WRONG-RESULTS", @@ -431,7 +511,9 @@ "names", "sort", "total_only" - ] + ], + "classification": "UNDECLARED", + "declaredElsewhere": [] }, { "severity": "WRONG-RESULTS", @@ -450,7 +532,9 @@ "names", "sort", "total_only" - ] + ], + "classification": "UNDECLARED", + "declaredElsewhere": [] }, { "severity": "WRONG-RESULTS", @@ -469,7 +553,9 @@ "names", "sort", "total_only" - ] + ], + "classification": "UNDECLARED", + "declaredElsewhere": [] }, { "severity": "WRONG-RESULTS", @@ -487,7 +573,9 @@ "names", "protocols", "user_names" - ] + ], + "classification": "UNDECLARED", + "declaredElsewhere": [] }, { "severity": "WRONG-RESULTS", @@ -505,7 +593,9 @@ "names", "protocols", "user_names" - ] + ], + "classification": "UNDECLARED", + "declaredElsewhere": [] }, { "severity": "WRONG-RESULTS", @@ -527,6 +617,17 @@ "owner_ids", "sort", "total_only" + ], + "classification": "WRONG-VERB", + "declaredElsewhere": [ + { + "method": "DELETE", + "surface": "Query" + }, + { + "method": "PATCH", + "surface": "Query" + } ] }, { @@ -549,6 +650,13 @@ "owner_ids", "sort", "total_only" + ], + "classification": "WRONG-VERB", + "declaredElsewhere": [ + { + "method": "POST", + "surface": "Query" + } ] }, { @@ -569,6 +677,13 @@ "offset", "sort", "total_only" + ], + "classification": "WRONG-VERB", + "declaredElsewhere": [ + { + "method": "DELETE", + "surface": "Query" + } ] }, { @@ -588,7 +703,9 @@ "total_only", "uids", "user_names" - ] + ], + "classification": "UNDECLARED", + "declaredElsewhere": [] }, { "severity": "WRONG-RESULTS", @@ -607,7 +724,9 @@ "total_only", "uids", "user_names" - ] + ], + "classification": "UNDECLARED", + "declaredElsewhere": [] }, { "severity": "WRONG-RESULTS", @@ -626,7 +745,9 @@ "total_only", "uids", "user_names" - ] + ], + "classification": "UNDECLARED", + "declaredElsewhere": [] }, { "severity": "WRONG-RESULTS", @@ -638,7 +759,9 @@ "declared": [ "keytab_ids", "keytab_names" - ] + ], + "classification": "UNDECLARED", + "declaredElsewhere": [] }, { "severity": "WRONG-RESULTS", @@ -650,7 +773,9 @@ "declared": [ "keytab_ids", "keytab_names" - ] + ], + "classification": "UNDECLARED", + "declaredElsewhere": [] }, { "severity": "WRONG-RESULTS", @@ -662,7 +787,9 @@ "declared": [ "keytab_ids", "keytab_names" - ] + ], + "classification": "UNDECLARED", + "declaredElsewhere": [] }, { "severity": "WRONG-RESULTS", @@ -674,7 +801,9 @@ "declared": [ "keytab_ids", "keytab_names" - ] + ], + "classification": "UNDECLARED", + "declaredElsewhere": [] }, { "severity": "WRONG-RESULTS", @@ -686,7 +815,9 @@ "declared": [ "keytab_ids", "keytab_names" - ] + ], + "classification": "UNDECLARED", + "declaredElsewhere": [] }, { "severity": "WRONG-RESULTS", @@ -703,7 +834,9 @@ "limit", "names", "paths" - ] + ], + "classification": "UNDECLARED", + "declaredElsewhere": [] }, { "severity": "WRONG-RESULTS", @@ -720,7 +853,9 @@ "limit", "names", "paths" - ] + ], + "classification": "UNDECLARED", + "declaredElsewhere": [] }, { "severity": "WRONG-RESULTS", @@ -737,7 +872,9 @@ "limit", "names", "paths" - ] + ], + "classification": "UNDECLARED", + "declaredElsewhere": [] }, { "severity": "WRONG-RESULTS", @@ -754,7 +891,9 @@ "limit", "names", "paths" - ] + ], + "classification": "UNDECLARED", + "declaredElsewhere": [] }, { "severity": "WRONG-RESULTS", @@ -771,7 +910,9 @@ "limit", "names", "paths" - ] + ], + "classification": "UNDECLARED", + "declaredElsewhere": [] }, { "severity": "WRONG-RESULTS", @@ -788,7 +929,9 @@ "limit", "names", "paths" - ] + ], + "classification": "UNDECLARED", + "declaredElsewhere": [] }, { "severity": "WRONG-RESULTS", @@ -800,7 +943,9 @@ "declared": [ "end_time", "start_time" - ] + ], + "classification": "UNDECLARED", + "declaredElsewhere": [] }, { "severity": "WRONG-RESULTS", @@ -812,7 +957,9 @@ "declared": [ "end_time", "start_time" - ] + ], + "classification": "UNDECLARED", + "declaredElsewhere": [] }, { "severity": "WRONG-RESULTS", @@ -824,7 +971,9 @@ "declared": [ "end_time", "start_time" - ] + ], + "classification": "UNDECLARED", + "declaredElsewhere": [] }, { "severity": "WRONG-RESULTS", @@ -843,7 +992,9 @@ "node_names", "offset", "sort" - ] + ], + "classification": "UNDECLARED", + "declaredElsewhere": [] }, { "severity": "WRONG-RESULTS", @@ -862,7 +1013,9 @@ "node_names", "offset", "sort" - ] + ], + "classification": "UNDECLARED", + "declaredElsewhere": [] }, { "severity": "WRONG-RESULTS", @@ -881,7 +1034,9 @@ "node_names", "offset", "sort" - ] + ], + "classification": "UNDECLARED", + "declaredElsewhere": [] }, { "severity": "WRONG-RESULTS", @@ -900,7 +1055,9 @@ "node_names", "offset", "sort" - ] + ], + "classification": "UNDECLARED", + "declaredElsewhere": [] }, { "severity": "WRONG-RESULTS", @@ -918,7 +1075,9 @@ "names", "offset", "sort" - ] + ], + "classification": "UNDECLARED", + "declaredElsewhere": [] }, { "severity": "WRONG-RESULTS", @@ -939,7 +1098,9 @@ "policy_ids", "policy_names", "sort" - ] + ], + "classification": "UNDECLARED", + "declaredElsewhere": [] }, { "severity": "WRONG-RESULTS", @@ -960,7 +1121,9 @@ "policy_ids", "policy_names", "sort" - ] + ], + "classification": "UNDECLARED", + "declaredElsewhere": [] }, { "severity": "WRONG-RESULTS", @@ -972,7 +1135,9 @@ "declared": [ "ids", "names" - ] + ], + "classification": "UNDECLARED", + "declaredElsewhere": [] }, { "severity": "WRONG-RESULTS", @@ -984,7 +1149,9 @@ "declared": [ "ids", "names" - ] + ], + "classification": "UNDECLARED", + "declaredElsewhere": [] }, { "severity": "WRONG-RESULTS", @@ -996,7 +1163,9 @@ "declared": [ "ids", "names" - ] + ], + "classification": "UNDECLARED", + "declaredElsewhere": [] }, { "severity": "WRONG-RESULTS", @@ -1015,6 +1184,17 @@ "names", "offset", "sort" + ], + "classification": "WRONG-VERB", + "declaredElsewhere": [ + { + "method": "DELETE", + "surface": "Query" + }, + { + "method": "POST", + "surface": "Query" + } ] }, { @@ -1034,6 +1214,17 @@ "names", "offset", "sort" + ], + "classification": "WRONG-VERB", + "declaredElsewhere": [ + { + "method": "DELETE", + "surface": "Query" + }, + { + "method": "POST", + "surface": "Query" + } ] }, { @@ -1051,7 +1242,9 @@ "ids", "limit", "names" - ] + ], + "classification": "UNDECLARED", + "declaredElsewhere": [] }, { "severity": "WRONG-RESULTS", @@ -1068,7 +1261,9 @@ "ids", "limit", "names" - ] + ], + "classification": "UNDECLARED", + "declaredElsewhere": [] }, { "severity": "WRONG-RESULTS", @@ -1084,7 +1279,9 @@ "ids", "limit", "names" - ] + ], + "classification": "UNDECLARED", + "declaredElsewhere": [] }, { "severity": "WRONG-RESULTS", @@ -1100,7 +1297,20 @@ "ids", "limit", "names" - ] + ], + "classification": "UNDECLARED", + "declaredElsewhere": [] + }, + { + "severity": "CREATE", + "cmdlet": "New-PfbCertificateSigningRequest", + "parameter": "Name", + "wireKey": "names", + "method": "POST", + "endpoint": "certificates/certificate-signing-requests", + "declared": [], + "classification": "UNDECLARED", + "declaredElsewhere": [] }, { "severity": "CREATE", @@ -1111,7 +1321,9 @@ "endpoint": "file-systems/locks/nlm-reclamations", "declared": [ "context_names" - ] + ], + "classification": "UNDECLARED", + "declaredElsewhere": [] }, { "severity": "CREATE", @@ -1126,7 +1338,9 @@ "member_names", "policy_ids", "policy_names" - ] + ], + "classification": "UNDECLARED", + "declaredElsewhere": [] }, { "severity": "DESTRUCTIVE", @@ -1144,7 +1358,9 @@ "names", "paths", "recursive" - ] + ], + "classification": "UNDECLARED", + "declaredElsewhere": [] }, { "severity": "DESTRUCTIVE", @@ -1157,6 +1373,17 @@ "member_ids", "member_names", "unreachable" + ], + "classification": "WRONG-VERB", + "declaredElsewhere": [ + { + "method": "GET", + "surface": "Query" + }, + { + "method": "POST", + "surface": "Query" + } ] }, { @@ -1173,6 +1400,13 @@ "local_directory_service_names", "names", "sids" + ], + "classification": "WRONG-VERB", + "declaredElsewhere": [ + { + "method": "GET", + "surface": "Query" + } ] }, { @@ -1186,6 +1420,17 @@ "ids", "names", "versions" + ], + "classification": "WRONG-VERB", + "declaredElsewhere": [ + { + "method": "GET", + "surface": "Query" + }, + { + "method": "POST", + "surface": "Query" + } ] }, { @@ -1199,6 +1444,17 @@ "ids", "names", "versions" + ], + "classification": "WRONG-VERB", + "declaredElsewhere": [ + { + "method": "GET", + "surface": "Query" + }, + { + "method": "POST", + "surface": "Query" + } ] }, { @@ -1213,6 +1469,17 @@ "ids", "names", "versions" + ], + "classification": "WRONG-VERB", + "declaredElsewhere": [ + { + "method": "GET", + "surface": "Query" + }, + { + "method": "POST", + "surface": "Query" + } ] }, { @@ -1227,6 +1494,17 @@ "ids", "names", "versions" + ], + "classification": "WRONG-VERB", + "declaredElsewhere": [ + { + "method": "GET", + "surface": "Query" + }, + { + "method": "POST", + "surface": "Query" + } ] }, { @@ -1241,7 +1519,9 @@ "node_group_names", "node_ids", "node_names" - ] + ], + "classification": "UNDECLARED", + "declaredElsewhere": [] }, { "severity": "DESTRUCTIVE", @@ -1255,7 +1535,9 @@ "node_group_names", "node_ids", "node_names" - ] + ], + "classification": "UNDECLARED", + "declaredElsewhere": [] }, { "severity": "DESTRUCTIVE", @@ -1267,7 +1549,9 @@ "declared": [ "context_names", "names" - ] + ], + "classification": "UNDECLARED", + "declaredElsewhere": [] }, { "severity": "DESTRUCTIVE", @@ -1282,7 +1566,9 @@ "member_names", "policy_ids", "policy_names" - ] + ], + "classification": "UNDECLARED", + "declaredElsewhere": [] }, { "severity": "DESTRUCTIVE", @@ -1296,6 +1582,17 @@ "ids", "names", "versions" + ], + "classification": "WRONG-VERB", + "declaredElsewhere": [ + { + "method": "GET", + "surface": "Query" + }, + { + "method": "POST", + "surface": "Query" + } ] }, { @@ -1310,6 +1607,17 @@ "ids", "names", "versions" + ], + "classification": "WRONG-VERB", + "declaredElsewhere": [ + { + "method": "GET", + "surface": "Query" + }, + { + "method": "POST", + "surface": "Query" + } ] } ], @@ -1334,6 +1642,11 @@ "method": "GET", "endpoint": "node-groups/nodes" }, + { + "cmdlet": "New-PfbCertificateSigningRequest", + "method": "POST", + "endpoint": "certificates/certificate-signing-requests" + }, { "cmdlet": "New-PfbNlmReclamation", "method": "POST", diff --git a/Reports/PfbFieldCmdletMap.json b/Reports/PfbFieldCmdletMap.json index 5cb5e742..6e51065a 100644 --- a/Reports/PfbFieldCmdletMap.json +++ b/Reports/PfbFieldCmdletMap.json @@ -352,6 +352,16 @@ "stableSinceOldestVersion": null, "recommendation": null }, + { + "cmdlet": "Get-PfbAlert", + "parameter": "Flagged", + "wireName": "flagged", + "status": "no-spec-enum-found", + "matchedKey": null, + "specValues": null, + "stableSinceOldestVersion": null, + "recommendation": null + }, { "cmdlet": "Get-PfbAlert", "parameter": "Id", @@ -10617,6 +10627,16 @@ "stableSinceOldestVersion": null, "recommendation": null }, + { + "cmdlet": "Get-PfbUserGroupQuotaPolicy", + "parameter": "Id", + "wireName": "ids", + "status": "no-spec-enum-found", + "matchedKey": null, + "specValues": null, + "stableSinceOldestVersion": null, + "recommendation": null + }, { "cmdlet": "Get-PfbUserGroupQuotaPolicy", "parameter": "Limit", @@ -10627,6 +10647,16 @@ "stableSinceOldestVersion": null, "recommendation": null }, + { + "cmdlet": "Get-PfbUserGroupQuotaPolicy", + "parameter": "Name", + "wireName": "names", + "status": "no-spec-enum-found", + "matchedKey": null, + "specValues": null, + "stableSinceOldestVersion": null, + "recommendation": null + }, { "cmdlet": "Get-PfbUserGroupQuotaPolicy", "parameter": "Sort", @@ -11157,6 +11187,16 @@ "stableSinceOldestVersion": null, "recommendation": null }, + { + "cmdlet": "New-PfbActiveDirectory", + "parameter": "Name", + "wireName": "names", + "status": "no-spec-enum-found", + "matchedKey": null, + "specValues": null, + "stableSinceOldestVersion": null, + "recommendation": null + }, { "cmdlet": "New-PfbAdminManagementAccessPolicy", "parameter": "MemberId", @@ -11417,6 +11457,16 @@ "stableSinceOldestVersion": null, "recommendation": null }, + { + "cmdlet": "New-PfbAuditFileSystemPolicy", + "parameter": "Enabled", + "wireName": "enabled", + "status": "no-spec-enum-found", + "matchedKey": null, + "specValues": null, + "stableSinceOldestVersion": null, + "recommendation": null + }, { "cmdlet": "New-PfbAuditFileSystemPolicy", "parameter": "Name", @@ -11467,6 +11517,16 @@ "stableSinceOldestVersion": null, "recommendation": null }, + { + "cmdlet": "New-PfbAuditObjectStorePolicy", + "parameter": "Enabled", + "wireName": "enabled", + "status": "no-spec-enum-found", + "matchedKey": null, + "specValues": null, + "stableSinceOldestVersion": null, + "recommendation": null + }, { "cmdlet": "New-PfbAuditObjectStorePolicy", "parameter": "Name", @@ -11697,6 +11757,16 @@ "stableSinceOldestVersion": null, "recommendation": null }, + { + "cmdlet": "New-PfbCertificateSigningRequest", + "parameter": "Name", + "wireName": "names", + "status": "no-spec-enum-found", + "matchedKey": null, + "specValues": null, + "stableSinceOldestVersion": null, + "recommendation": null + }, { "cmdlet": "New-PfbDataEvictionPolicy", "parameter": "KeepSize", @@ -11767,6 +11837,16 @@ "stableSinceOldestVersion": null, "recommendation": null }, + { + "cmdlet": "New-PfbDns", + "parameter": "Name", + "wireName": "names", + "status": "no-spec-enum-found", + "matchedKey": null, + "specValues": null, + "stableSinceOldestVersion": null, + "recommendation": null + }, { "cmdlet": "New-PfbFileSystem", "parameter": "DefaultGroupQuota", @@ -11787,6 +11867,16 @@ "stableSinceOldestVersion": null, "recommendation": null }, + { + "cmdlet": "New-PfbFileSystem", + "parameter": "FastRemoveDirectoryEnabled", + "wireName": "fast_remove_directory_enabled", + "status": "no-spec-enum-found", + "matchedKey": null, + "specValues": null, + "stableSinceOldestVersion": null, + "recommendation": null + }, { "cmdlet": "New-PfbFileSystem", "parameter": "Name", @@ -11827,6 +11917,16 @@ "stableSinceOldestVersion": null, "recommendation": null }, + { + "cmdlet": "New-PfbFileSystem", + "parameter": "Writable", + "wireName": "writable", + "status": "no-spec-enum-found", + "matchedKey": null, + "specValues": null, + "stableSinceOldestVersion": null, + "recommendation": null + }, { "cmdlet": "New-PfbFileSystemAuditPolicy", "parameter": "MemberId", @@ -12177,6 +12277,16 @@ "stableSinceOldestVersion": null, "recommendation": null }, + { + "cmdlet": "New-PfbFleet", + "parameter": "Name", + "wireName": "names", + "status": "no-spec-enum-found", + "matchedKey": null, + "specValues": null, + "stableSinceOldestVersion": null, + "recommendation": null + }, { "cmdlet": "New-PfbFleetMember", "parameter": "FleetId", @@ -12207,6 +12317,16 @@ "stableSinceOldestVersion": null, "recommendation": null }, + { + "cmdlet": "New-PfbLag", + "parameter": "Name", + "wireName": "names", + "status": "no-spec-enum-found", + "matchedKey": null, + "specValues": null, + "stableSinceOldestVersion": null, + "recommendation": null + }, { "cmdlet": "New-PfbLegalHold", "parameter": "Name", @@ -12607,6 +12727,16 @@ "stableSinceOldestVersion": null, "recommendation": null }, + { + "cmdlet": "New-PfbNfsExportPolicy", + "parameter": "Enabled", + "wireName": "enabled", + "status": "no-spec-enum-found", + "matchedKey": null, + "specValues": null, + "stableSinceOldestVersion": null, + "recommendation": null + }, { "cmdlet": "New-PfbNfsExportPolicy", "parameter": "Name", @@ -12767,6 +12897,16 @@ "stableSinceOldestVersion": null, "recommendation": null }, + { + "cmdlet": "New-PfbNodeGroup", + "parameter": "Name", + "wireName": "names", + "status": "no-spec-enum-found", + "matchedKey": null, + "specValues": null, + "stableSinceOldestVersion": null, + "recommendation": null + }, { "cmdlet": "New-PfbNodeGroupNode", "parameter": "GroupId", @@ -13077,6 +13217,16 @@ "stableSinceOldestVersion": null, "recommendation": null }, + { + "cmdlet": "New-PfbPolicy", + "parameter": "Enabled", + "wireName": "enabled", + "status": "no-spec-enum-found", + "matchedKey": null, + "specValues": null, + "stableSinceOldestVersion": null, + "recommendation": null + }, { "cmdlet": "New-PfbPolicy", "parameter": "Name", @@ -13297,6 +13447,36 @@ "stableSinceOldestVersion": null, "recommendation": null }, + { + "cmdlet": "New-PfbQuotaGroup", + "parameter": "FileSystemName", + "wireName": "file_system_names", + "status": "no-spec-enum-found", + "matchedKey": null, + "specValues": null, + "stableSinceOldestVersion": null, + "recommendation": null + }, + { + "cmdlet": "New-PfbQuotaGroup", + "parameter": "GroupId", + "wireName": "gids", + "status": "no-spec-enum-found", + "matchedKey": null, + "specValues": null, + "stableSinceOldestVersion": null, + "recommendation": null + }, + { + "cmdlet": "New-PfbQuotaGroup", + "parameter": "GroupName", + "wireName": "group_names", + "status": "no-spec-enum-found", + "matchedKey": null, + "specValues": null, + "stableSinceOldestVersion": null, + "recommendation": null + }, { "cmdlet": "New-PfbQuotaGroup", "parameter": "Quota", @@ -13307,6 +13487,16 @@ "stableSinceOldestVersion": null, "recommendation": null }, + { + "cmdlet": "New-PfbQuotaUser", + "parameter": "FileSystemName", + "wireName": "file_system_names", + "status": "no-spec-enum-found", + "matchedKey": null, + "specValues": null, + "stableSinceOldestVersion": null, + "recommendation": null + }, { "cmdlet": "New-PfbQuotaUser", "parameter": "Quota", @@ -13317,6 +13507,26 @@ "stableSinceOldestVersion": null, "recommendation": null }, + { + "cmdlet": "New-PfbQuotaUser", + "parameter": "UserId", + "wireName": "uids", + "status": "no-spec-enum-found", + "matchedKey": null, + "specValues": null, + "stableSinceOldestVersion": null, + "recommendation": null + }, + { + "cmdlet": "New-PfbQuotaUser", + "parameter": "UserName", + "wireName": "user_names", + "status": "no-spec-enum-found", + "matchedKey": null, + "specValues": null, + "stableSinceOldestVersion": null, + "recommendation": null + }, { "cmdlet": "New-PfbRealm", "parameter": "Name", @@ -13327,6 +13537,16 @@ "stableSinceOldestVersion": null, "recommendation": null }, + { + "cmdlet": "New-PfbS3ExportPolicy", + "parameter": "Enabled", + "wireName": "enabled", + "status": "no-spec-enum-found", + "matchedKey": null, + "specValues": null, + "stableSinceOldestVersion": null, + "recommendation": null + }, { "cmdlet": "New-PfbS3ExportPolicy", "parameter": "Name", @@ -13437,6 +13657,16 @@ "stableSinceOldestVersion": null, "recommendation": null }, + { + "cmdlet": "New-PfbSmbClientPolicy", + "parameter": "Enabled", + "wireName": "enabled", + "status": "no-spec-enum-found", + "matchedKey": null, + "specValues": null, + "stableSinceOldestVersion": null, + "recommendation": null + }, { "cmdlet": "New-PfbSmbClientPolicy", "parameter": "Name", @@ -13517,6 +13747,16 @@ "stableSinceOldestVersion": null, "recommendation": null }, + { + "cmdlet": "New-PfbSmbSharePolicy", + "parameter": "Enabled", + "wireName": "enabled", + "status": "no-spec-enum-found", + "matchedKey": null, + "specValues": null, + "stableSinceOldestVersion": null, + "recommendation": null + }, { "cmdlet": "New-PfbSmbSharePolicy", "parameter": "Name", @@ -13557,6 +13797,26 @@ "stableSinceOldestVersion": null, "recommendation": null }, + { + "cmdlet": "New-PfbSnmpManager", + "parameter": "Name", + "wireName": "names", + "status": "no-spec-enum-found", + "matchedKey": null, + "specValues": null, + "stableSinceOldestVersion": null, + "recommendation": null + }, + { + "cmdlet": "New-PfbSshCaPolicy", + "parameter": "Name", + "wireName": "names", + "status": "no-spec-enum-found", + "matchedKey": null, + "specValues": null, + "stableSinceOldestVersion": null, + "recommendation": null + }, { "cmdlet": "New-PfbSshCaPolicyAdmin", "parameter": "MemberId", @@ -13638,8 +13898,18 @@ "recommendation": null }, { - "cmdlet": "New-PfbSubnet", - "parameter": "Gateway", + "cmdlet": "New-PfbStorageClassTieringPolicy", + "parameter": "Name", + "wireName": "names", + "status": "no-spec-enum-found", + "matchedKey": null, + "specValues": null, + "stableSinceOldestVersion": null, + "recommendation": null + }, + { + "cmdlet": "New-PfbSubnet", + "parameter": "Gateway", "wireName": "gateway", "status": "no-spec-enum-found", "matchedKey": null, @@ -13697,6 +13967,46 @@ "stableSinceOldestVersion": null, "recommendation": null }, + { + "cmdlet": "New-PfbSyslogServer", + "parameter": "Name", + "wireName": "names", + "status": "no-spec-enum-found", + "matchedKey": null, + "specValues": null, + "stableSinceOldestVersion": null, + "recommendation": null + }, + { + "cmdlet": "New-PfbTarget", + "parameter": "Name", + "wireName": "names", + "status": "no-spec-enum-found", + "matchedKey": null, + "specValues": null, + "stableSinceOldestVersion": null, + "recommendation": null + }, + { + "cmdlet": "New-PfbTlsPolicy", + "parameter": "Name", + "wireName": "names", + "status": "no-spec-enum-found", + "matchedKey": null, + "specValues": null, + "stableSinceOldestVersion": null, + "recommendation": null + }, + { + "cmdlet": "New-PfbUserGroupQuotaPolicy", + "parameter": "Enabled", + "wireName": "enabled", + "status": "no-spec-enum-found", + "matchedKey": null, + "specValues": null, + "stableSinceOldestVersion": null, + "recommendation": null + }, { "cmdlet": "New-PfbUserGroupQuotaPolicy", "parameter": "FileSystemId", @@ -13807,6 +14117,16 @@ "stableSinceOldestVersion": null, "recommendation": null }, + { + "cmdlet": "New-PfbUserGroupQuotaPolicyRule", + "parameter": "Enforced", + "wireName": "enforced", + "status": "no-spec-enum-found", + "matchedKey": null, + "specValues": null, + "stableSinceOldestVersion": null, + "recommendation": null + }, { "cmdlet": "New-PfbUserGroupQuotaPolicyRule", "parameter": "IgnoreUsage", @@ -13927,6 +14247,16 @@ "stableSinceOldestVersion": null, "recommendation": null }, + { + "cmdlet": "New-PfbWormPolicy", + "parameter": "Name", + "wireName": "names", + "status": "no-spec-enum-found", + "matchedKey": null, + "specValues": null, + "stableSinceOldestVersion": null, + "recommendation": null + }, { "cmdlet": "Remove-PfbActiveDirectory", "parameter": "Id", @@ -16037,6 +16367,56 @@ "stableSinceOldestVersion": null, "recommendation": null }, + { + "cmdlet": "Remove-PfbQuotaGroup", + "parameter": "FileSystemName", + "wireName": "file_system_names", + "status": "no-spec-enum-found", + "matchedKey": null, + "specValues": null, + "stableSinceOldestVersion": null, + "recommendation": null + }, + { + "cmdlet": "Remove-PfbQuotaGroup", + "parameter": "GroupId", + "wireName": "gids", + "status": "no-spec-enum-found", + "matchedKey": null, + "specValues": null, + "stableSinceOldestVersion": null, + "recommendation": null + }, + { + "cmdlet": "Remove-PfbQuotaGroup", + "parameter": "GroupName", + "wireName": "group_names", + "status": "no-spec-enum-found", + "matchedKey": null, + "specValues": null, + "stableSinceOldestVersion": null, + "recommendation": null + }, + { + "cmdlet": "Remove-PfbQuotaUser", + "parameter": "FileSystemName", + "wireName": "file_system_names", + "status": "no-spec-enum-found", + "matchedKey": null, + "specValues": null, + "stableSinceOldestVersion": null, + "recommendation": null + }, + { + "cmdlet": "Remove-PfbQuotaUser", + "parameter": "UserName", + "wireName": "user_names", + "status": "no-spec-enum-found", + "matchedKey": null, + "specValues": null, + "stableSinceOldestVersion": null, + "recommendation": null + }, { "cmdlet": "Remove-PfbRealm", "parameter": "Id", @@ -16997,6 +17377,16 @@ "stableSinceOldestVersion": null, "recommendation": null }, + { + "cmdlet": "Update-PfbAlert", + "parameter": "Flagged", + "wireName": "flagged", + "status": "no-spec-enum-found", + "matchedKey": null, + "specValues": null, + "stableSinceOldestVersion": null, + "recommendation": null + }, { "cmdlet": "Update-PfbAlert", "parameter": "Id", @@ -17017,6 +17407,16 @@ "stableSinceOldestVersion": null, "recommendation": null }, + { + "cmdlet": "Update-PfbAlertWatcher", + "parameter": "Enabled", + "wireName": "enabled", + "status": "no-spec-enum-found", + "matchedKey": null, + "specValues": null, + "stableSinceOldestVersion": null, + "recommendation": null + }, { "cmdlet": "Update-PfbAlertWatcher", "parameter": "Id", @@ -17187,6 +17587,16 @@ "stableSinceOldestVersion": null, "recommendation": null }, + { + "cmdlet": "Update-PfbAuditFileSystemPolicy", + "parameter": "Enabled", + "wireName": "enabled", + "status": "no-spec-enum-found", + "matchedKey": null, + "specValues": null, + "stableSinceOldestVersion": null, + "recommendation": null + }, { "cmdlet": "Update-PfbAuditFileSystemPolicy", "parameter": "Id", @@ -17207,6 +17617,16 @@ "stableSinceOldestVersion": null, "recommendation": null }, + { + "cmdlet": "Update-PfbAuditObjectStorePolicy", + "parameter": "Enabled", + "wireName": "enabled", + "status": "no-spec-enum-found", + "matchedKey": null, + "specValues": null, + "stableSinceOldestVersion": null, + "recommendation": null + }, { "cmdlet": "Update-PfbAuditObjectStorePolicy", "parameter": "Id", @@ -17227,6 +17647,16 @@ "stableSinceOldestVersion": null, "recommendation": null }, + { + "cmdlet": "Update-PfbBucket", + "parameter": "Destroyed", + "wireName": "destroyed", + "status": "no-spec-enum-found", + "matchedKey": null, + "specValues": null, + "stableSinceOldestVersion": null, + "recommendation": null + }, { "cmdlet": "Update-PfbBucket", "parameter": "Id", @@ -17277,16 +17707,6 @@ "stableSinceOldestVersion": null, "recommendation": null }, - { - "cmdlet": "Update-PfbBucketAuditFilter", - "parameter": "BucketName", - "wireName": "bucket_names", - "status": "no-spec-enum-found", - "matchedKey": null, - "specValues": null, - "stableSinceOldestVersion": null, - "recommendation": null - }, { "cmdlet": "Update-PfbBucketAuditFilter", "parameter": "Name", @@ -17487,6 +17907,16 @@ "stableSinceOldestVersion": null, "recommendation": null }, + { + "cmdlet": "Update-PfbDataEvictionPolicy", + "parameter": "Enabled", + "wireName": "enabled", + "status": "no-spec-enum-found", + "matchedKey": null, + "specValues": null, + "stableSinceOldestVersion": null, + "recommendation": null + }, { "cmdlet": "Update-PfbDataEvictionPolicy", "parameter": "Id", @@ -17527,6 +17957,16 @@ "stableSinceOldestVersion": null, "recommendation": null }, + { + "cmdlet": "Update-PfbDirectoryService", + "parameter": "Name", + "wireName": "names", + "status": "no-spec-enum-found", + "matchedKey": null, + "specValues": null, + "stableSinceOldestVersion": null, + "recommendation": null + }, { "cmdlet": "Update-PfbDirectoryServiceRole", "parameter": "Group", @@ -17657,6 +18097,16 @@ "stableSinceOldestVersion": null, "recommendation": null }, + { + "cmdlet": "Update-PfbFileSystem", + "parameter": "Destroyed", + "wireName": "destroyed", + "status": "no-spec-enum-found", + "matchedKey": null, + "specValues": null, + "stableSinceOldestVersion": null, + "recommendation": null + }, { "cmdlet": "Update-PfbFileSystem", "parameter": "DiscardNonSnapshottedData", @@ -17667,6 +18117,26 @@ "stableSinceOldestVersion": null, "recommendation": null }, + { + "cmdlet": "Update-PfbFileSystem", + "parameter": "HardLimitEnabled", + "wireName": "hard_limit_enabled", + "status": "no-spec-enum-found", + "matchedKey": null, + "specValues": null, + "stableSinceOldestVersion": null, + "recommendation": null + }, + { + "cmdlet": "Update-PfbFileSystem", + "parameter": "HttpEnabled", + "wireName": "http", + "status": "no-spec-enum-found", + "matchedKey": null, + "specValues": null, + "stableSinceOldestVersion": null, + "recommendation": null + }, { "cmdlet": "Update-PfbFileSystem", "parameter": "Id", @@ -18387,6 +18857,16 @@ "stableSinceOldestVersion": null, "recommendation": null }, + { + "cmdlet": "Update-PfbNetworkAccessPolicy", + "parameter": "Enabled", + "wireName": "enabled", + "status": "no-spec-enum-found", + "matchedKey": null, + "specValues": null, + "stableSinceOldestVersion": null, + "recommendation": null + }, { "cmdlet": "Update-PfbNetworkAccessPolicy", "parameter": "Id", @@ -18537,6 +19017,16 @@ "stableSinceOldestVersion": null, "recommendation": null }, + { + "cmdlet": "Update-PfbNfsExportPolicy", + "parameter": "Enabled", + "wireName": "enabled", + "status": "no-spec-enum-found", + "matchedKey": null, + "specValues": null, + "stableSinceOldestVersion": null, + "recommendation": null + }, { "cmdlet": "Update-PfbNfsExportPolicy", "parameter": "Id", @@ -18947,6 +19437,16 @@ "stableSinceOldestVersion": null, "recommendation": null }, + { + "cmdlet": "Update-PfbPolicy", + "parameter": "Enabled", + "wireName": "enabled", + "status": "no-spec-enum-found", + "matchedKey": null, + "specValues": null, + "stableSinceOldestVersion": null, + "recommendation": null + }, { "cmdlet": "Update-PfbPolicy", "parameter": "Id", @@ -19079,8 +19579,48 @@ }, { "cmdlet": "Update-PfbQuotaGroup", - "parameter": "Quota", - "wireName": "quota", + "parameter": "FileSystemName", + "wireName": "file_system_names", + "status": "no-spec-enum-found", + "matchedKey": null, + "specValues": null, + "stableSinceOldestVersion": null, + "recommendation": null + }, + { + "cmdlet": "Update-PfbQuotaGroup", + "parameter": "GroupId", + "wireName": "gids", + "status": "no-spec-enum-found", + "matchedKey": null, + "specValues": null, + "stableSinceOldestVersion": null, + "recommendation": null + }, + { + "cmdlet": "Update-PfbQuotaGroup", + "parameter": "GroupName", + "wireName": "group_names", + "status": "no-spec-enum-found", + "matchedKey": null, + "specValues": null, + "stableSinceOldestVersion": null, + "recommendation": null + }, + { + "cmdlet": "Update-PfbQuotaGroup", + "parameter": "Quota", + "wireName": "quota", + "status": "no-spec-enum-found", + "matchedKey": null, + "specValues": null, + "stableSinceOldestVersion": null, + "recommendation": null + }, + { + "cmdlet": "Update-PfbQuotaUser", + "parameter": "FileSystemName", + "wireName": "file_system_names", "status": "no-spec-enum-found", "matchedKey": null, "specValues": null, @@ -19097,6 +19637,26 @@ "stableSinceOldestVersion": null, "recommendation": null }, + { + "cmdlet": "Update-PfbQuotaUser", + "parameter": "UserName", + "wireName": "user_names", + "status": "no-spec-enum-found", + "matchedKey": null, + "specValues": null, + "stableSinceOldestVersion": null, + "recommendation": null + }, + { + "cmdlet": "Update-PfbRealm", + "parameter": "Destroyed", + "wireName": "destroyed", + "status": "no-spec-enum-found", + "matchedKey": null, + "specValues": null, + "stableSinceOldestVersion": null, + "recommendation": null + }, { "cmdlet": "Update-PfbRealm", "parameter": "Id", @@ -19147,6 +19707,16 @@ "stableSinceOldestVersion": null, "recommendation": null }, + { + "cmdlet": "Update-PfbS3ExportPolicy", + "parameter": "Enabled", + "wireName": "enabled", + "status": "no-spec-enum-found", + "matchedKey": null, + "specValues": null, + "stableSinceOldestVersion": null, + "recommendation": null + }, { "cmdlet": "Update-PfbS3ExportPolicy", "parameter": "Id", @@ -19317,6 +19887,16 @@ "stableSinceOldestVersion": null, "recommendation": null }, + { + "cmdlet": "Update-PfbSmbClientPolicy", + "parameter": "Enabled", + "wireName": "enabled", + "status": "no-spec-enum-found", + "matchedKey": null, + "specValues": null, + "stableSinceOldestVersion": null, + "recommendation": null + }, { "cmdlet": "Update-PfbSmbClientPolicy", "parameter": "Id", @@ -19347,6 +19927,16 @@ "stableSinceOldestVersion": null, "recommendation": null }, + { + "cmdlet": "Update-PfbSmbSharePolicy", + "parameter": "Enabled", + "wireName": "enabled", + "status": "no-spec-enum-found", + "matchedKey": null, + "specValues": null, + "stableSinceOldestVersion": null, + "recommendation": null + }, { "cmdlet": "Update-PfbSmbSharePolicy", "parameter": "Id", @@ -19889,6 +20479,16 @@ "stableSinceOldestVersion": null, "recommendation": null }, + { + "cmdlet": "Update-PfbUserGroupQuotaPolicy", + "parameter": "Enabled", + "wireName": "enabled", + "status": "no-spec-enum-found", + "matchedKey": null, + "specValues": null, + "stableSinceOldestVersion": null, + "recommendation": null + }, { "cmdlet": "Update-PfbUserGroupQuotaPolicy", "parameter": "Id", @@ -19989,6 +20589,16 @@ "stableSinceOldestVersion": null, "recommendation": null }, + { + "cmdlet": "Update-PfbWorkload", + "parameter": "Destroyed", + "wireName": "destroyed", + "status": "no-spec-enum-found", + "matchedKey": null, + "specValues": null, + "stableSinceOldestVersion": null, + "recommendation": null + }, { "cmdlet": "Update-PfbWorkload", "parameter": "Id", @@ -20101,38 +20711,14 @@ } ], "attributesOnly": [ - { - "cmdlet": "New-PfbActiveDirectory", - "parameter": "Name" - }, - { - "cmdlet": "New-PfbAuditFileSystemPolicy", - "parameter": "Enabled" - }, - { - "cmdlet": "New-PfbAuditObjectStorePolicy", - "parameter": "Enabled" - }, { "cmdlet": "New-PfbBucketAuditFilter", "parameter": "Name" }, - { - "cmdlet": "New-PfbCertificateSigningRequest", - "parameter": "Name" - }, - { - "cmdlet": "New-PfbDns", - "parameter": "Name" - }, { "cmdlet": "New-PfbFileSystem", "parameter": "DefaultExports" }, - { - "cmdlet": "New-PfbFileSystem", - "parameter": "FastRemoveDirectoryEnabled" - }, { "cmdlet": "New-PfbFileSystem", "parameter": "HardLimit" @@ -20189,141 +20775,13 @@ "cmdlet": "New-PfbFileSystem", "parameter": "SnapshotDirectoryEnabled" }, - { - "cmdlet": "New-PfbFileSystem", - "parameter": "Writable" - }, - { - "cmdlet": "New-PfbFleet", - "parameter": "Name" - }, - { - "cmdlet": "New-PfbLag", - "parameter": "Name" - }, - { - "cmdlet": "New-PfbNfsExportPolicy", - "parameter": "Enabled" - }, - { - "cmdlet": "New-PfbNodeGroup", - "parameter": "Name" - }, - { - "cmdlet": "New-PfbPolicy", - "parameter": "Enabled" - }, - { - "cmdlet": "New-PfbQuotaGroup", - "parameter": "FileSystemName" - }, - { - "cmdlet": "New-PfbQuotaGroup", - "parameter": "GroupId" - }, - { - "cmdlet": "New-PfbQuotaGroup", - "parameter": "GroupName" - }, - { - "cmdlet": "New-PfbQuotaUser", - "parameter": "FileSystemName" - }, - { - "cmdlet": "New-PfbQuotaUser", - "parameter": "UserId" - }, - { - "cmdlet": "New-PfbQuotaUser", - "parameter": "UserName" - }, - { - "cmdlet": "New-PfbS3ExportPolicy", - "parameter": "Enabled" - }, { "cmdlet": "New-PfbServer", "parameter": "CreateDirectoryService" }, { - "cmdlet": "New-PfbSmbClientPolicy", - "parameter": "Enabled" - }, - { - "cmdlet": "New-PfbSmbSharePolicy", - "parameter": "Enabled" - }, - { - "cmdlet": "New-PfbSnmpManager", - "parameter": "Name" - }, - { - "cmdlet": "New-PfbSshCaPolicy", - "parameter": "Name" - }, - { - "cmdlet": "New-PfbStorageClassTieringPolicy", - "parameter": "Name" - }, - { - "cmdlet": "New-PfbSyslogServer", - "parameter": "Name" - }, - { - "cmdlet": "New-PfbTarget", - "parameter": "Name" - }, - { - "cmdlet": "New-PfbTlsPolicy", - "parameter": "Name" - }, - { - "cmdlet": "New-PfbUserGroupQuotaPolicy", - "parameter": "Enabled" - }, - { - "cmdlet": "New-PfbUserGroupQuotaPolicyRule", - "parameter": "Enforced" - }, - { - "cmdlet": "New-PfbWormPolicy", - "parameter": "Name" - }, - { - "cmdlet": "Update-PfbAlert", - "parameter": "Flagged" - }, - { - "cmdlet": "Update-PfbAlertWatcher", - "parameter": "Enabled" - }, - { - "cmdlet": "Update-PfbAuditFileSystemPolicy", - "parameter": "Enabled" - }, - { - "cmdlet": "Update-PfbAuditObjectStorePolicy", - "parameter": "Enabled" - }, - { - "cmdlet": "Update-PfbBucket", - "parameter": "Destroyed" - }, - { - "cmdlet": "Update-PfbDirectoryService", - "parameter": "Name" - }, - { - "cmdlet": "Update-PfbFileSystem", - "parameter": "Destroyed" - }, - { - "cmdlet": "Update-PfbFileSystem", - "parameter": "HardLimitEnabled" - }, - { - "cmdlet": "Update-PfbFileSystem", - "parameter": "HttpEnabled" + "cmdlet": "Update-PfbBucketAuditFilter", + "parameter": "BucketName" }, { "cmdlet": "Update-PfbFileSystem", @@ -20348,268 +20806,212 @@ { "cmdlet": "Update-PfbFileSystem", "parameter": "SmbSharePolicy" - }, - { - "cmdlet": "Update-PfbNetworkAccessPolicy", - "parameter": "Enabled" - }, - { - "cmdlet": "Update-PfbNfsExportPolicy", - "parameter": "Enabled" - }, - { - "cmdlet": "Update-PfbPolicy", - "parameter": "Enabled" - }, - { - "cmdlet": "Update-PfbQuotaGroup", - "parameter": "FileSystemName" - }, - { - "cmdlet": "Update-PfbQuotaGroup", - "parameter": "GroupId" - }, + } + ], + "typedUnresolved": [ { - "cmdlet": "Update-PfbQuotaGroup", - "parameter": "GroupName" + "cmdlet": "Get-PfbHardwareTemperature", + "parameter": "Limit" }, { - "cmdlet": "Update-PfbQuotaUser", - "parameter": "FileSystemName" + "cmdlet": "New-PfbDataEvictionPolicy", + "parameter": "Disabled" }, { - "cmdlet": "Update-PfbQuotaUser", - "parameter": "UserName" + "cmdlet": "New-PfbFileSystemSnapshot", + "parameter": "SourceName" }, { - "cmdlet": "Update-PfbRealm", - "parameter": "Destroyed" + "cmdlet": "New-PfbFleetMember", + "parameter": "FleetKey" }, { - "cmdlet": "Update-PfbS3ExportPolicy", - "parameter": "Enabled" + "cmdlet": "New-PfbLocalGroupMember", + "parameter": "Member" }, { - "cmdlet": "Update-PfbSmbClientPolicy", - "parameter": "Enabled" + "cmdlet": "New-PfbWorkloadPlacementRecommendation", + "parameter": "Inputs" }, { - "cmdlet": "Update-PfbSmbSharePolicy", - "parameter": "Enabled" + "cmdlet": "Set-PfbWorkloadTag", + "parameter": "Tags" }, { - "cmdlet": "Update-PfbUserGroupQuotaPolicy", - "parameter": "Enabled" + "cmdlet": "Test-PfbConnection", + "parameter": "Endpoint" } ], - "typedUnresolved": [ + "notApplicable": [ { "cmdlet": "Connect-PfbArray", - "parameter": "AllArrays" + "parameter": "AllArrays", + "surface": "OutsideStandardRequest" }, { "cmdlet": "Connect-PfbArray", - "parameter": "ApiToken" + "parameter": "ApiToken", + "surface": "OutsideStandardRequest" }, { "cmdlet": "Connect-PfbArray", - "parameter": "ApiVersion" + "parameter": "ApiVersion", + "surface": "OutsideStandardRequest" }, { "cmdlet": "Connect-PfbArray", - "parameter": "ClientId" + "parameter": "ClientId", + "surface": "OutsideStandardRequest" }, { "cmdlet": "Connect-PfbArray", - "parameter": "Context" + "parameter": "Context", + "surface": "OutsideStandardRequest" }, { "cmdlet": "Connect-PfbArray", - "parameter": "Credential" + "parameter": "Credential", + "surface": "OutsideStandardRequest" }, { "cmdlet": "Connect-PfbArray", - "parameter": "Endpoint" + "parameter": "Endpoint", + "surface": "OutsideStandardRequest" }, { "cmdlet": "Connect-PfbArray", - "parameter": "HttpTimeout" + "parameter": "HttpTimeout", + "surface": "OutsideStandardRequest" }, { "cmdlet": "Connect-PfbArray", - "parameter": "IgnoreCertificateError" + "parameter": "IgnoreCertificateError", + "surface": "OutsideStandardRequest" }, { "cmdlet": "Connect-PfbArray", - "parameter": "Issuer" + "parameter": "Issuer", + "surface": "OutsideStandardRequest" }, { "cmdlet": "Connect-PfbArray", - "parameter": "KeyId" + "parameter": "KeyId", + "surface": "OutsideStandardRequest" }, { "cmdlet": "Connect-PfbArray", - "parameter": "Kind" + "parameter": "Kind", + "surface": "OutsideStandardRequest" }, { "cmdlet": "Connect-PfbArray", - "parameter": "Password" + "parameter": "Password", + "surface": "OutsideStandardRequest" }, { "cmdlet": "Connect-PfbArray", - "parameter": "PrivateKeyFile" + "parameter": "PrivateKeyFile", + "surface": "OutsideStandardRequest" }, { "cmdlet": "Connect-PfbArray", - "parameter": "PrivateKeyPassword" + "parameter": "PrivateKeyPassword", + "surface": "OutsideStandardRequest" }, { "cmdlet": "Connect-PfbArray", - "parameter": "Username" - }, - { - "cmdlet": "Get-PfbAlert", - "parameter": "Flagged" + "parameter": "Username", + "surface": "OutsideStandardRequest" }, { "cmdlet": "Get-PfbApiVersion", - "parameter": "Endpoint" + "parameter": "Endpoint", + "surface": "OutsideStandardRequest" }, { "cmdlet": "Get-PfbApiVersion", - "parameter": "IgnoreCertificateError" + "parameter": "IgnoreCertificateError", + "surface": "OutsideStandardRequest" }, { "cmdlet": "Get-PfbConnection", - "parameter": "Endpoint" - }, - { - "cmdlet": "Get-PfbHardwareTemperature", - "parameter": "Limit" - }, - { - "cmdlet": "Get-PfbUserGroupQuotaPolicy", - "parameter": "Id" - }, - { - "cmdlet": "Get-PfbUserGroupQuotaPolicy", - "parameter": "Name" + "parameter": "Endpoint", + "surface": "OutsideStandardRequest" }, { "cmdlet": "Invoke-PfbInContext", - "parameter": "AllArrays" + "parameter": "AllArrays", + "surface": "OutsideStandardRequest" }, { "cmdlet": "Invoke-PfbInContext", - "parameter": "Context" + "parameter": "Context", + "surface": "OutsideStandardRequest" }, { "cmdlet": "Invoke-PfbInContext", - "parameter": "Kind" + "parameter": "Kind", + "surface": "OutsideStandardRequest" }, { "cmdlet": "Invoke-PfbInContext", - "parameter": "ScriptBlock" - }, - { - "cmdlet": "New-PfbDataEvictionPolicy", - "parameter": "Disabled" - }, - { - "cmdlet": "New-PfbFileSystemSnapshot", - "parameter": "SourceName" - }, - { - "cmdlet": "New-PfbFleetMember", - "parameter": "FleetKey" - }, - { - "cmdlet": "New-PfbLocalGroupMember", - "parameter": "Member" - }, - { - "cmdlet": "New-PfbWorkloadPlacementRecommendation", - "parameter": "Inputs" + "parameter": "ScriptBlock", + "surface": "OutsideStandardRequest" }, { "cmdlet": "Remove-PfbBucket", - "parameter": "Eradicate" + "parameter": "Eradicate", + "surface": "NotWireParameter" }, { "cmdlet": "Remove-PfbFileSystem", - "parameter": "Eradicate" + "parameter": "Eradicate", + "surface": "NotWireParameter" }, { "cmdlet": "Remove-PfbFileSystemSession", - "parameter": "Force" + "parameter": "Force", + "surface": "NotWireParameter" }, { "cmdlet": "Remove-PfbFileSystemSnapshot", - "parameter": "Eradicate" - }, - { - "cmdlet": "Remove-PfbQuotaGroup", - "parameter": "FileSystemName" - }, - { - "cmdlet": "Remove-PfbQuotaGroup", - "parameter": "GroupId" - }, - { - "cmdlet": "Remove-PfbQuotaGroup", - "parameter": "GroupName" - }, - { - "cmdlet": "Remove-PfbQuotaUser", - "parameter": "FileSystemName" - }, - { - "cmdlet": "Remove-PfbQuotaUser", - "parameter": "UserName" + "parameter": "Eradicate", + "surface": "NotWireParameter" }, { "cmdlet": "Remove-PfbRealm", - "parameter": "Eradicate" + "parameter": "Eradicate", + "surface": "NotWireParameter" }, { "cmdlet": "Remove-PfbServer", - "parameter": "Eradicate" + "parameter": "Eradicate", + "surface": "NotWireParameter" }, { "cmdlet": "Set-PfbContext", - "parameter": "AllArrays" + "parameter": "AllArrays", + "surface": "OutsideStandardRequest" }, { "cmdlet": "Set-PfbContext", - "parameter": "AllowErrors" + "parameter": "AllowErrors", + "surface": "OutsideStandardRequest" }, { "cmdlet": "Set-PfbContext", - "parameter": "Context" + "parameter": "Context", + "surface": "OutsideStandardRequest" }, { "cmdlet": "Set-PfbContext", - "parameter": "Kind" + "parameter": "Kind", + "surface": "OutsideStandardRequest" }, { "cmdlet": "Set-PfbCredential", - "parameter": "Credential" - }, - { - "cmdlet": "Set-PfbWorkloadTag", - "parameter": "Tags" - }, - { - "cmdlet": "Test-PfbConnection", - "parameter": "Endpoint" - }, - { - "cmdlet": "Update-PfbDataEvictionPolicy", - "parameter": "Enabled" - }, - { - "cmdlet": "Update-PfbWorkload", - "parameter": "Destroyed" + "parameter": "Credential", + "surface": "OutsideStandardRequest" } ] } diff --git a/Reports/PfbFieldCmdletMapping.md b/Reports/PfbFieldCmdletMapping.md index 516aac59..781d9dff 100644 --- a/Reports/PfbFieldCmdletMapping.md +++ b/Reports/PfbFieldCmdletMapping.md @@ -9,7 +9,7 @@ Reporting only -- no `Public/` cmdlet is edited by this script. Every `matched` - matched: 2 - collision: 1 - not-found-in-resource: 29 -- no-spec-enum-found: 1974 +- no-spec-enum-found: 2035 | Cmdlet | Parameter | Wire name | Status | Spec values | Recommendation | |---|---|---|---|---|---| @@ -46,16 +46,10 @@ Reporting only -- no `Public/` cmdlet is edited by this script. Every `matched` | `Update-PfbWorkload` | `-NewName` | name | not-found-in-resource | | | | `Update-PfbWormPolicy` | `-DefaultRetention` | default_retention | not-found-in-resource | | | -## Attributes-only parameters (no typed field to attach either mechanism to): 75 +## Attributes-only parameters (no typed field to attach either mechanism to): 24 -- `New-PfbActiveDirectory -Name` -- `New-PfbAuditFileSystemPolicy -Enabled` -- `New-PfbAuditObjectStorePolicy -Enabled` - `New-PfbBucketAuditFilter -Name` -- `New-PfbCertificateSigningRequest -Name` -- `New-PfbDns -Name` - `New-PfbFileSystem -DefaultExports` -- `New-PfbFileSystem -FastRemoveDirectoryEnabled` - `New-PfbFileSystem -HardLimit` - `New-PfbFileSystem -Http` - `New-PfbFileSystem -MultiProtocolAccessControlStyle` @@ -70,112 +64,62 @@ Reporting only -- no `Public/` cmdlet is edited by this script. Every `matched` - `New-PfbFileSystem -SmbContinuousAvailabilityEnabled` - `New-PfbFileSystem -SmbSharePolicy` - `New-PfbFileSystem -SnapshotDirectoryEnabled` -- `New-PfbFileSystem -Writable` -- `New-PfbFleet -Name` -- `New-PfbLag -Name` -- `New-PfbNfsExportPolicy -Enabled` -- `New-PfbNodeGroup -Name` -- `New-PfbPolicy -Enabled` -- `New-PfbQuotaGroup -FileSystemName` -- `New-PfbQuotaGroup -GroupId` -- `New-PfbQuotaGroup -GroupName` -- `New-PfbQuotaUser -FileSystemName` -- `New-PfbQuotaUser -UserId` -- `New-PfbQuotaUser -UserName` -- `New-PfbS3ExportPolicy -Enabled` - `New-PfbServer -CreateDirectoryService` -- `New-PfbSmbClientPolicy -Enabled` -- `New-PfbSmbSharePolicy -Enabled` -- `New-PfbSnmpManager -Name` -- `New-PfbSshCaPolicy -Name` -- `New-PfbStorageClassTieringPolicy -Name` -- `New-PfbSyslogServer -Name` -- `New-PfbTarget -Name` -- `New-PfbTlsPolicy -Name` -- `New-PfbUserGroupQuotaPolicy -Enabled` -- `New-PfbUserGroupQuotaPolicyRule -Enforced` -- `New-PfbWormPolicy -Name` -- `Update-PfbAlert -Flagged` -- `Update-PfbAlertWatcher -Enabled` -- `Update-PfbAuditFileSystemPolicy -Enabled` -- `Update-PfbAuditObjectStorePolicy -Enabled` -- `Update-PfbBucket -Destroyed` -- `Update-PfbDirectoryService -Name` -- `Update-PfbFileSystem -Destroyed` -- `Update-PfbFileSystem -HardLimitEnabled` -- `Update-PfbFileSystem -HttpEnabled` +- `Update-PfbBucketAuditFilter -BucketName` - `Update-PfbFileSystem -NfsEnabled` - `Update-PfbFileSystem -NfsExportPolicy` - `Update-PfbFileSystem -NfsRules` - `Update-PfbFileSystem -SmbClientPolicy` - `Update-PfbFileSystem -SmbEnabled` - `Update-PfbFileSystem -SmbSharePolicy` -- `Update-PfbNetworkAccessPolicy -Enabled` -- `Update-PfbNfsExportPolicy -Enabled` -- `Update-PfbPolicy -Enabled` -- `Update-PfbQuotaGroup -FileSystemName` -- `Update-PfbQuotaGroup -GroupId` -- `Update-PfbQuotaGroup -GroupName` -- `Update-PfbQuotaUser -FileSystemName` -- `Update-PfbQuotaUser -UserName` -- `Update-PfbRealm -Destroyed` -- `Update-PfbS3ExportPolicy -Enabled` -- `Update-PfbSmbClientPolicy -Enabled` -- `Update-PfbSmbSharePolicy -Enabled` -- `Update-PfbUserGroupQuotaPolicy -Enabled` -## Typed but unresolved wire name (needs manual inspection): 52 +## Typed but unresolved wire name (needs manual inspection): 8 -- `Connect-PfbArray -AllArrays` -- `Connect-PfbArray -ApiToken` -- `Connect-PfbArray -ApiVersion` -- `Connect-PfbArray -ClientId` -- `Connect-PfbArray -Context` -- `Connect-PfbArray -Credential` -- `Connect-PfbArray -Endpoint` -- `Connect-PfbArray -HttpTimeout` -- `Connect-PfbArray -IgnoreCertificateError` -- `Connect-PfbArray -Issuer` -- `Connect-PfbArray -KeyId` -- `Connect-PfbArray -Kind` -- `Connect-PfbArray -Password` -- `Connect-PfbArray -PrivateKeyFile` -- `Connect-PfbArray -PrivateKeyPassword` -- `Connect-PfbArray -Username` -- `Get-PfbAlert -Flagged` -- `Get-PfbApiVersion -Endpoint` -- `Get-PfbApiVersion -IgnoreCertificateError` -- `Get-PfbConnection -Endpoint` - `Get-PfbHardwareTemperature -Limit` -- `Get-PfbUserGroupQuotaPolicy -Id` -- `Get-PfbUserGroupQuotaPolicy -Name` -- `Invoke-PfbInContext -AllArrays` -- `Invoke-PfbInContext -Context` -- `Invoke-PfbInContext -Kind` -- `Invoke-PfbInContext -ScriptBlock` - `New-PfbDataEvictionPolicy -Disabled` - `New-PfbFileSystemSnapshot -SourceName` - `New-PfbFleetMember -FleetKey` - `New-PfbLocalGroupMember -Member` - `New-PfbWorkloadPlacementRecommendation -Inputs` -- `Remove-PfbBucket -Eradicate` -- `Remove-PfbFileSystem -Eradicate` -- `Remove-PfbFileSystemSession -Force` -- `Remove-PfbFileSystemSnapshot -Eradicate` -- `Remove-PfbQuotaGroup -FileSystemName` -- `Remove-PfbQuotaGroup -GroupId` -- `Remove-PfbQuotaGroup -GroupName` -- `Remove-PfbQuotaUser -FileSystemName` -- `Remove-PfbQuotaUser -UserName` -- `Remove-PfbRealm -Eradicate` -- `Remove-PfbServer -Eradicate` -- `Set-PfbContext -AllArrays` -- `Set-PfbContext -AllowErrors` -- `Set-PfbContext -Context` -- `Set-PfbContext -Kind` -- `Set-PfbCredential -Credential` - `Set-PfbWorkloadTag -Tags` - `Test-PfbConnection -Endpoint` -- `Update-PfbDataEvictionPolicy -Enabled` -- `Update-PfbWorkload -Destroyed` + +## Outside this resolver's reach (no standard-request field to inspect): 34 + +Listed separately from the section above on purpose: neither is a standard-request field whose wire name went unresolved. `NotWireParameter` is an audited request control (`-Eradicate`, `-Force`) with no query or body key. `OutsideStandardRequest` means the declaring cmdlet issues no `Invoke-PfbApiRequest` call, so this resolver cannot see its payload -- it does **not** mean the parameter has no wire effect; `Connect-PfbArray -Username`/`-Password`, for example, reach `/api/login` through bespoke HTTP. + +- `Connect-PfbArray -AllArrays` (OutsideStandardRequest) +- `Connect-PfbArray -ApiToken` (OutsideStandardRequest) +- `Connect-PfbArray -ApiVersion` (OutsideStandardRequest) +- `Connect-PfbArray -ClientId` (OutsideStandardRequest) +- `Connect-PfbArray -Context` (OutsideStandardRequest) +- `Connect-PfbArray -Credential` (OutsideStandardRequest) +- `Connect-PfbArray -Endpoint` (OutsideStandardRequest) +- `Connect-PfbArray -HttpTimeout` (OutsideStandardRequest) +- `Connect-PfbArray -IgnoreCertificateError` (OutsideStandardRequest) +- `Connect-PfbArray -Issuer` (OutsideStandardRequest) +- `Connect-PfbArray -KeyId` (OutsideStandardRequest) +- `Connect-PfbArray -Kind` (OutsideStandardRequest) +- `Connect-PfbArray -Password` (OutsideStandardRequest) +- `Connect-PfbArray -PrivateKeyFile` (OutsideStandardRequest) +- `Connect-PfbArray -PrivateKeyPassword` (OutsideStandardRequest) +- `Connect-PfbArray -Username` (OutsideStandardRequest) +- `Get-PfbApiVersion -Endpoint` (OutsideStandardRequest) +- `Get-PfbApiVersion -IgnoreCertificateError` (OutsideStandardRequest) +- `Get-PfbConnection -Endpoint` (OutsideStandardRequest) +- `Invoke-PfbInContext -AllArrays` (OutsideStandardRequest) +- `Invoke-PfbInContext -Context` (OutsideStandardRequest) +- `Invoke-PfbInContext -Kind` (OutsideStandardRequest) +- `Invoke-PfbInContext -ScriptBlock` (OutsideStandardRequest) +- `Remove-PfbBucket -Eradicate` (NotWireParameter) +- `Remove-PfbFileSystem -Eradicate` (NotWireParameter) +- `Remove-PfbFileSystemSession -Force` (NotWireParameter) +- `Remove-PfbFileSystemSnapshot -Eradicate` (NotWireParameter) +- `Remove-PfbRealm -Eradicate` (NotWireParameter) +- `Remove-PfbServer -Eradicate` (NotWireParameter) +- `Set-PfbContext -AllArrays` (OutsideStandardRequest) +- `Set-PfbContext -AllowErrors` (OutsideStandardRequest) +- `Set-PfbContext -Context` (OutsideStandardRequest) +- `Set-PfbContext -Kind` (OutsideStandardRequest) +- `Set-PfbCredential -Credential` (OutsideStandardRequest) diff --git a/Reports/PfbPipelineSelectorMap.json b/Reports/PfbPipelineSelectorMap.json index f314a139..942aca5c 100644 --- a/Reports/PfbPipelineSelectorMap.json +++ b/Reports/PfbPipelineSelectorMap.json @@ -8,12 +8,12 @@ ], "totals": { "probePairs": 1247, - "evaluatedPairs": 1213, - "candidatePairs": 629, - "candidateRate": 0.5185, - "findings": 264, - "findingPairs": 101, - "confirmationRate": 0.4197, + "evaluatedPairs": 1233, + "candidatePairs": 647, + "candidateRate": 0.5247, + "findings": 266, + "findingPairs": 102, + "confirmationRate": 0.4111, "controlLeakage": 0, "assistedRows": 212 }, @@ -24,7 +24,7 @@ }, { "Outcome": "Bound", - "Count": 577 + "Count": 579 }, { "Outcome": "CmdletError", @@ -32,7 +32,7 @@ }, { "Outcome": "Coerced", - "Count": 264 + "Count": 266 }, { "Outcome": "Guarded", @@ -40,7 +40,7 @@ }, { "Outcome": "NoSelector", - "Count": 47 + "Count": 43 }, { "Outcome": "Unbindable", @@ -50,15 +50,15 @@ "gateBreakdown": [ { "Gate": "Candidate", - "Count": 629 + "Count": 647 }, { "Gate": "Matched", - "Count": 584 + "Count": 586 }, { "Gate": "SelectorUnresolved", - "Count": 34 + "Count": 14 } ], "results": [ @@ -21139,14 +21139,14 @@ "Producer": "GET /user-group-quota-policies", "IsPrimary": true, "FromExample": false, - "WireName": null, + "WireName": "names", "IsCandidate": false, - "Gate": "SelectorUnresolved", + "Gate": "Matched", "ValueFromPipeline": true, - "Outcome": "NoSelector", - "Evidence": "the parameter resolves to no wire key, so nothing can be attributed to it", - "BoundWireKey": null, - "BoundValue": null, + "Outcome": "Bound", + "Evidence": "names=PROBE-name (from property 'name')", + "BoundWireKey": "names", + "BoundValue": "PROBE-name", "ErrorKind": null, "FilledParameter": [], "ProbeProperties": [ @@ -21181,14 +21181,14 @@ "Producer": "GET /user-group-quota-policies/file-systems", "IsPrimary": false, "FromExample": false, - "WireName": null, - "IsCandidate": false, - "Gate": "SelectorUnresolved", + "WireName": "names", + "IsCandidate": true, + "Gate": "Candidate", "ValueFromPipeline": true, - "Outcome": "NoSelector", - "Evidence": "the parameter resolves to no wire key, so nothing can be attributed to it", - "BoundWireKey": null, - "BoundValue": null, + "Outcome": "Coerced", + "Evidence": "names=@{context=; member=; policy=}", + "BoundWireKey": "names", + "BoundValue": "@{context=; member=; policy=}", "ErrorKind": null, "FilledParameter": [], "ProbeProperties": [ @@ -21209,14 +21209,14 @@ "Producer": "GET /user-group-quota-policies/members", "IsPrimary": false, "FromExample": false, - "WireName": null, - "IsCandidate": false, - "Gate": "SelectorUnresolved", + "WireName": "names", + "IsCandidate": true, + "Gate": "Candidate", "ValueFromPipeline": true, - "Outcome": "NoSelector", - "Evidence": "the parameter resolves to no wire key, so nothing can be attributed to it", - "BoundWireKey": null, - "BoundValue": null, + "Outcome": "Coerced", + "Evidence": "names=@{context=; member=; policy=}", + "BoundWireKey": "names", + "BoundValue": "@{context=; member=; policy=}", "ErrorKind": null, "FilledParameter": [], "ProbeProperties": [ @@ -21237,14 +21237,14 @@ "Producer": "GET /user-group-quota-policies/rules", "IsPrimary": false, "FromExample": false, - "WireName": null, + "WireName": "names", "IsCandidate": false, - "Gate": "SelectorUnresolved", + "Gate": "Matched", "ValueFromPipeline": true, - "Outcome": "NoSelector", - "Evidence": "the parameter resolves to no wire key, so nothing can be attributed to it", - "BoundWireKey": null, - "BoundValue": null, + "Outcome": "Bound", + "Evidence": "names=PROBE-name (from property 'name')", + "BoundWireKey": "names", + "BoundValue": "PROBE-name", "ErrorKind": null, "FilledParameter": [], "ProbeProperties": [ @@ -37069,9 +37069,9 @@ "Producer": "GET /quotas/groups", "IsPrimary": true, "FromExample": true, - "WireName": null, - "IsCandidate": false, - "Gate": "SelectorUnresolved", + "WireName": "file_system_names", + "IsCandidate": true, + "Gate": "Candidate", "ValueFromPipeline": false, "Outcome": "Unbindable", "Evidence": "The input object cannot be bound to any parameters for the command either because the command does not take pipeline input or the input and its properties do not match any of the parameters that take pipeline input.", @@ -37108,9 +37108,9 @@ "Producer": "GET /quotas/settings", "IsPrimary": false, "FromExample": false, - "WireName": null, - "IsCandidate": false, - "Gate": "SelectorUnresolved", + "WireName": "file_system_names", + "IsCandidate": true, + "Gate": "Candidate", "ValueFromPipeline": false, "Outcome": "Unbindable", "Evidence": "The input object cannot be bound to any parameters for the command either because the command does not take pipeline input or the input and its properties do not match any of the parameters that take pipeline input.", @@ -37141,9 +37141,9 @@ "Producer": "GET /quotas/users", "IsPrimary": false, "FromExample": false, - "WireName": null, - "IsCandidate": false, - "Gate": "SelectorUnresolved", + "WireName": "file_system_names", + "IsCandidate": true, + "Gate": "Candidate", "ValueFromPipeline": false, "Outcome": "Unbindable", "Evidence": "The input object cannot be bound to any parameters for the command either because the command does not take pipeline input or the input and its properties do not match any of the parameters that take pipeline input.", @@ -37180,9 +37180,9 @@ "Producer": "GET /quotas/groups", "IsPrimary": true, "FromExample": true, - "WireName": null, - "IsCandidate": false, - "Gate": "SelectorUnresolved", + "WireName": "group_names", + "IsCandidate": true, + "Gate": "Candidate", "ValueFromPipeline": false, "Outcome": "Unbindable", "Evidence": "The input object cannot be bound to any parameters for the command either because the command does not take pipeline input or the input and its properties do not match any of the parameters that take pipeline input.", @@ -37219,9 +37219,9 @@ "Producer": "GET /quotas/settings", "IsPrimary": false, "FromExample": false, - "WireName": null, - "IsCandidate": false, - "Gate": "SelectorUnresolved", + "WireName": "group_names", + "IsCandidate": true, + "Gate": "Candidate", "ValueFromPipeline": false, "Outcome": "Unbindable", "Evidence": "The input object cannot be bound to any parameters for the command either because the command does not take pipeline input or the input and its properties do not match any of the parameters that take pipeline input.", @@ -37252,9 +37252,9 @@ "Producer": "GET /quotas/users", "IsPrimary": false, "FromExample": false, - "WireName": null, - "IsCandidate": false, - "Gate": "SelectorUnresolved", + "WireName": "group_names", + "IsCandidate": true, + "Gate": "Candidate", "ValueFromPipeline": false, "Outcome": "Unbindable", "Evidence": "The input object cannot be bound to any parameters for the command either because the command does not take pipeline input or the input and its properties do not match any of the parameters that take pipeline input.", @@ -37291,9 +37291,9 @@ "Producer": "GET /quotas/groups", "IsPrimary": false, "FromExample": false, - "WireName": null, - "IsCandidate": false, - "Gate": "SelectorUnresolved", + "WireName": "file_system_names", + "IsCandidate": true, + "Gate": "Candidate", "ValueFromPipeline": false, "Outcome": "Unbindable", "Evidence": "The input object cannot be bound to any parameters for the command either because the command does not take pipeline input or the input and its properties do not match any of the parameters that take pipeline input.", @@ -37330,9 +37330,9 @@ "Producer": "GET /quotas/settings", "IsPrimary": false, "FromExample": false, - "WireName": null, - "IsCandidate": false, - "Gate": "SelectorUnresolved", + "WireName": "file_system_names", + "IsCandidate": true, + "Gate": "Candidate", "ValueFromPipeline": false, "Outcome": "Unbindable", "Evidence": "The input object cannot be bound to any parameters for the command either because the command does not take pipeline input or the input and its properties do not match any of the parameters that take pipeline input.", @@ -37363,9 +37363,9 @@ "Producer": "GET /quotas/users", "IsPrimary": true, "FromExample": true, - "WireName": null, - "IsCandidate": false, - "Gate": "SelectorUnresolved", + "WireName": "file_system_names", + "IsCandidate": true, + "Gate": "Candidate", "ValueFromPipeline": false, "Outcome": "Unbindable", "Evidence": "The input object cannot be bound to any parameters for the command either because the command does not take pipeline input or the input and its properties do not match any of the parameters that take pipeline input.", @@ -37402,9 +37402,9 @@ "Producer": "GET /quotas/groups", "IsPrimary": false, "FromExample": false, - "WireName": null, - "IsCandidate": false, - "Gate": "SelectorUnresolved", + "WireName": "user_names", + "IsCandidate": true, + "Gate": "Candidate", "ValueFromPipeline": false, "Outcome": "Unbindable", "Evidence": "The input object cannot be bound to any parameters for the command either because the command does not take pipeline input or the input and its properties do not match any of the parameters that take pipeline input.", @@ -37441,9 +37441,9 @@ "Producer": "GET /quotas/settings", "IsPrimary": false, "FromExample": false, - "WireName": null, - "IsCandidate": false, - "Gate": "SelectorUnresolved", + "WireName": "user_names", + "IsCandidate": true, + "Gate": "Candidate", "ValueFromPipeline": false, "Outcome": "Unbindable", "Evidence": "The input object cannot be bound to any parameters for the command either because the command does not take pipeline input or the input and its properties do not match any of the parameters that take pipeline input.", @@ -37474,9 +37474,9 @@ "Producer": "GET /quotas/users", "IsPrimary": true, "FromExample": true, - "WireName": null, - "IsCandidate": false, - "Gate": "SelectorUnresolved", + "WireName": "user_names", + "IsCandidate": true, + "Gate": "Candidate", "ValueFromPipeline": false, "Outcome": "Unbindable", "Evidence": "The input object cannot be bound to any parameters for the command either because the command does not take pipeline input or the input and its properties do not match any of the parameters that take pipeline input.", @@ -40789,9 +40789,9 @@ "Producer": "GET /buckets", "IsPrimary": false, "FromExample": false, - "WireName": "bucket_names", - "IsCandidate": true, - "Gate": "Candidate", + "WireName": null, + "IsCandidate": false, + "Gate": "SelectorUnresolved", "ValueFromPipeline": false, "Outcome": "Unbindable", "Evidence": "The input object cannot be bound to any parameters for the command either because the command does not take pipeline input or the input and its properties do not match any of the parameters that take pipeline input.", @@ -40855,9 +40855,9 @@ "Producer": "GET /buckets/audit-filters", "IsPrimary": true, "FromExample": false, - "WireName": "bucket_names", - "IsCandidate": true, - "Gate": "Candidate", + "WireName": null, + "IsCandidate": false, + "Gate": "SelectorUnresolved", "ValueFromPipeline": false, "Outcome": "Unbindable", "Evidence": "The input object cannot be bound to any parameters for the command either because the command does not take pipeline input or the input and its properties do not match any of the parameters that take pipeline input.", @@ -40889,9 +40889,9 @@ "Producer": "GET /buckets/bucket-access-policies", "IsPrimary": false, "FromExample": false, - "WireName": "bucket_names", - "IsCandidate": true, - "Gate": "Candidate", + "WireName": null, + "IsCandidate": false, + "Gate": "SelectorUnresolved", "ValueFromPipeline": false, "Outcome": "Unbindable", "Evidence": "The input object cannot be bound to any parameters for the command either because the command does not take pipeline input or the input and its properties do not match any of the parameters that take pipeline input.", @@ -40933,9 +40933,9 @@ "Producer": "GET /buckets/bucket-access-policies/rules", "IsPrimary": false, "FromExample": false, - "WireName": "bucket_names", - "IsCandidate": true, - "Gate": "Candidate", + "WireName": null, + "IsCandidate": false, + "Gate": "SelectorUnresolved", "ValueFromPipeline": false, "Outcome": "Unbindable", "Evidence": "The input object cannot be bound to any parameters for the command either because the command does not take pipeline input or the input and its properties do not match any of the parameters that take pipeline input.", @@ -40971,9 +40971,9 @@ "Producer": "GET /buckets/cross-origin-resource-sharing-policies", "IsPrimary": false, "FromExample": false, - "WireName": "bucket_names", - "IsCandidate": true, - "Gate": "Candidate", + "WireName": null, + "IsCandidate": false, + "Gate": "SelectorUnresolved", "ValueFromPipeline": false, "Outcome": "Unbindable", "Evidence": "The input object cannot be bound to any parameters for the command either because the command does not take pipeline input or the input and its properties do not match any of the parameters that take pipeline input.", @@ -41015,9 +41015,9 @@ "Producer": "GET /buckets/cross-origin-resource-sharing-policies/rules", "IsPrimary": false, "FromExample": false, - "WireName": "bucket_names", - "IsCandidate": true, - "Gate": "Candidate", + "WireName": null, + "IsCandidate": false, + "Gate": "SelectorUnresolved", "ValueFromPipeline": false, "Outcome": "Unbindable", "Evidence": "The input object cannot be bound to any parameters for the command either because the command does not take pipeline input or the input and its properties do not match any of the parameters that take pipeline input.", @@ -41051,9 +41051,9 @@ "Producer": "GET /buckets/performance", "IsPrimary": false, "FromExample": false, - "WireName": "bucket_names", - "IsCandidate": true, - "Gate": "Candidate", + "WireName": null, + "IsCandidate": false, + "Gate": "SelectorUnresolved", "ValueFromPipeline": false, "Outcome": "Unbindable", "Evidence": "The input object cannot be bound to any parameters for the command either because the command does not take pipeline input or the input and its properties do not match any of the parameters that take pipeline input.", @@ -41107,9 +41107,9 @@ "Producer": "GET /buckets/s3-specific-performance", "IsPrimary": false, "FromExample": false, - "WireName": "bucket_names", - "IsCandidate": true, - "Gate": "Candidate", + "WireName": null, + "IsCandidate": false, + "Gate": "SelectorUnresolved", "ValueFromPipeline": false, "Outcome": "Unbindable", "Evidence": "The input object cannot be bound to any parameters for the command either because the command does not take pipeline input or the input and its properties do not match any of the parameters that take pipeline input.", @@ -45456,9 +45456,9 @@ "Producer": "GET /quotas/groups", "IsPrimary": true, "FromExample": true, - "WireName": null, - "IsCandidate": false, - "Gate": "SelectorUnresolved", + "WireName": "file_system_names", + "IsCandidate": true, + "Gate": "Candidate", "ValueFromPipeline": false, "Outcome": "Unbindable", "Evidence": "The input object cannot be bound to any parameters for the command either because the command does not take pipeline input or the input and its properties do not match any of the parameters that take pipeline input.", @@ -45495,9 +45495,9 @@ "Producer": "GET /quotas/settings", "IsPrimary": false, "FromExample": false, - "WireName": null, - "IsCandidate": false, - "Gate": "SelectorUnresolved", + "WireName": "file_system_names", + "IsCandidate": true, + "Gate": "Candidate", "ValueFromPipeline": false, "Outcome": "Unbindable", "Evidence": "The input object cannot be bound to any parameters for the command either because the command does not take pipeline input or the input and its properties do not match any of the parameters that take pipeline input.", @@ -45528,9 +45528,9 @@ "Producer": "GET /quotas/users", "IsPrimary": false, "FromExample": false, - "WireName": null, - "IsCandidate": false, - "Gate": "SelectorUnresolved", + "WireName": "file_system_names", + "IsCandidate": true, + "Gate": "Candidate", "ValueFromPipeline": false, "Outcome": "Unbindable", "Evidence": "The input object cannot be bound to any parameters for the command either because the command does not take pipeline input or the input and its properties do not match any of the parameters that take pipeline input.", @@ -45567,9 +45567,9 @@ "Producer": "GET /quotas/groups", "IsPrimary": true, "FromExample": true, - "WireName": null, - "IsCandidate": false, - "Gate": "SelectorUnresolved", + "WireName": "group_names", + "IsCandidate": true, + "Gate": "Candidate", "ValueFromPipeline": false, "Outcome": "Unbindable", "Evidence": "The input object cannot be bound to any parameters for the command either because the command does not take pipeline input or the input and its properties do not match any of the parameters that take pipeline input.", @@ -45606,9 +45606,9 @@ "Producer": "GET /quotas/settings", "IsPrimary": false, "FromExample": false, - "WireName": null, - "IsCandidate": false, - "Gate": "SelectorUnresolved", + "WireName": "group_names", + "IsCandidate": true, + "Gate": "Candidate", "ValueFromPipeline": false, "Outcome": "Unbindable", "Evidence": "The input object cannot be bound to any parameters for the command either because the command does not take pipeline input or the input and its properties do not match any of the parameters that take pipeline input.", @@ -45639,9 +45639,9 @@ "Producer": "GET /quotas/users", "IsPrimary": false, "FromExample": false, - "WireName": null, - "IsCandidate": false, - "Gate": "SelectorUnresolved", + "WireName": "group_names", + "IsCandidate": true, + "Gate": "Candidate", "ValueFromPipeline": false, "Outcome": "Unbindable", "Evidence": "The input object cannot be bound to any parameters for the command either because the command does not take pipeline input or the input and its properties do not match any of the parameters that take pipeline input.", @@ -45678,9 +45678,9 @@ "Producer": "GET /quotas/groups", "IsPrimary": false, "FromExample": false, - "WireName": null, - "IsCandidate": false, - "Gate": "SelectorUnresolved", + "WireName": "file_system_names", + "IsCandidate": true, + "Gate": "Candidate", "ValueFromPipeline": false, "Outcome": "Unbindable", "Evidence": "The input object cannot be bound to any parameters for the command either because the command does not take pipeline input or the input and its properties do not match any of the parameters that take pipeline input.", @@ -45717,9 +45717,9 @@ "Producer": "GET /quotas/settings", "IsPrimary": false, "FromExample": false, - "WireName": null, - "IsCandidate": false, - "Gate": "SelectorUnresolved", + "WireName": "file_system_names", + "IsCandidate": true, + "Gate": "Candidate", "ValueFromPipeline": false, "Outcome": "Unbindable", "Evidence": "The input object cannot be bound to any parameters for the command either because the command does not take pipeline input or the input and its properties do not match any of the parameters that take pipeline input.", @@ -45750,9 +45750,9 @@ "Producer": "GET /quotas/users", "IsPrimary": true, "FromExample": true, - "WireName": null, - "IsCandidate": false, - "Gate": "SelectorUnresolved", + "WireName": "file_system_names", + "IsCandidate": true, + "Gate": "Candidate", "ValueFromPipeline": false, "Outcome": "Unbindable", "Evidence": "The input object cannot be bound to any parameters for the command either because the command does not take pipeline input or the input and its properties do not match any of the parameters that take pipeline input.", @@ -45789,9 +45789,9 @@ "Producer": "GET /quotas/groups", "IsPrimary": false, "FromExample": false, - "WireName": null, - "IsCandidate": false, - "Gate": "SelectorUnresolved", + "WireName": "user_names", + "IsCandidate": true, + "Gate": "Candidate", "ValueFromPipeline": false, "Outcome": "Unbindable", "Evidence": "The input object cannot be bound to any parameters for the command either because the command does not take pipeline input or the input and its properties do not match any of the parameters that take pipeline input.", @@ -45828,9 +45828,9 @@ "Producer": "GET /quotas/settings", "IsPrimary": false, "FromExample": false, - "WireName": null, - "IsCandidate": false, - "Gate": "SelectorUnresolved", + "WireName": "user_names", + "IsCandidate": true, + "Gate": "Candidate", "ValueFromPipeline": false, "Outcome": "Unbindable", "Evidence": "The input object cannot be bound to any parameters for the command either because the command does not take pipeline input or the input and its properties do not match any of the parameters that take pipeline input.", @@ -45861,9 +45861,9 @@ "Producer": "GET /quotas/users", "IsPrimary": true, "FromExample": true, - "WireName": null, - "IsCandidate": false, - "Gate": "SelectorUnresolved", + "WireName": "user_names", + "IsCandidate": true, + "Gate": "Candidate", "ValueFromPipeline": false, "Outcome": "Unbindable", "Evidence": "The input object cannot be bound to any parameters for the command either because the command does not take pipeline input or the input and its properties do not match any of the parameters that take pipeline input.", diff --git a/Reports/PfbPipelineSelectorMap.md b/Reports/PfbPipelineSelectorMap.md index d607852a..29665917 100644 --- a/Reports/PfbPipelineSelectorMap.md +++ b/Reports/PfbPipelineSelectorMap.md @@ -11,12 +11,12 @@ no request leaves the machine, and nothing here is inferred from pattern-matchin | Metric | Value | |---|---:| | `probePairs` | 1247 | -| `evaluatedPairs` | 1213 | -| `candidatePairs` | 629 | -| `candidateRate` | 0.5185 | -| `findings` | 264 | -| `findingPairs` | 101 | -| `confirmationRate` | 0.4197 | +| `evaluatedPairs` | 1233 | +| `candidatePairs` | 647 | +| `candidateRate` | 0.5247 | +| `findings` | 266 | +| `findingPairs` | 102 | +| `confirmationRate` | 0.4111 | | `controlLeakage` | 0 | | `assistedRows` | 212 | @@ -28,11 +28,11 @@ defect appears once per producing endpoint, so rows always exceed pairs. | Outcome | Rows | Finding? | |---|---:|---| | `BindError` | 4 | triage -- the harness never invoked, the only unmeasured outcome | -| `Bound` | 577 | no -- the selector bound as intended | +| `Bound` | 579 | no -- the selector bound as intended | | `CmdletError` | 6 | no -- the cmdlet threw before any request was built | -| `Coerced` | 264 | **yes** -- a stringified object reached the wire | +| `Coerced` | 266 | **yes** -- a stringified object reached the wire | | `Guarded` | 116 | no -- a #64/#90 coercion guard fired | -| `NoSelector` | 47 | no -- reported observation | +| `NoSelector` | 43 | no -- reported observation | | `Unbindable` | 233 | no -- PowerShell declined to bind this probe object at all. Note that pass 4 is ByPropertyName WITH coercion, so a ByPropertyName-only parameter whose alias matches an object-valued property CAN still coerce; this outcome is not a structural immunity | ## Findings @@ -203,6 +203,8 @@ Ordered with primary-producer rows first: those are the chains a user would most | `Get-PfbSyslogServer` | `Name` | `GET /syslog-servers/test` | | `names=@{component_address=PROBE-component_address; component_name=PROBE-component_name; description=PROBE-description; destination=PROBE-destination; enabled=PROBE-enabled; resource=; result_details=PROBE-result_details; success=PROBE-success; test_type=PROBE-test_type}` | | `Get-PfbTlsPolicy` | `Name` | `GET /tls-policies/members` | | `names=@{member=; policy=}` | | `Get-PfbTlsPolicy` | `Name` | `GET /tls-policies/network-interfaces` | | `names=@{member=; policy=}` | +| `Get-PfbUserGroupQuotaPolicy` | `Name` | `GET /user-group-quota-policies/file-systems` | | `names=@{context=; member=; policy=}` | +| `Get-PfbUserGroupQuotaPolicy` | `Name` | `GET /user-group-quota-policies/members` | | `names=@{context=; member=; policy=}` | | `Get-PfbUserGroupQuotaPolicyRule` | `PolicyName` | `GET /user-group-quota-policies` | | `policy_names=@{context=; enabled=PROBE-enabled; id=PROBE-id; is_local=PROBE-is_local; location=; name=PROBE-name; policy_type=PROBE-policy_type; realms=System.Object[]; rules=System.Object[]; version=PROBE-version}` | | `Get-PfbUserGroupQuotaPolicyRule` | `PolicyName` | `GET /user-group-quota-policies/file-systems` | | `policy_names=@{context=; member=; policy=}` | | `Get-PfbUserGroupQuotaPolicyRule` | `PolicyName` | `GET /user-group-quota-policies/members` | | `policy_names=@{context=; member=; policy=}` | diff --git a/Tests/Build-PfbPipelineSelectorMap.Tests.ps1 b/Tests/Build-PfbPipelineSelectorMap.Tests.ps1 index 215f18ed..b91061b8 100644 --- a/Tests/Build-PfbPipelineSelectorMap.Tests.ps1 +++ b/Tests/Build-PfbPipelineSelectorMap.Tests.ps1 @@ -97,15 +97,27 @@ Describe 'Build-PfbPipelineSelectorMap' { # which Rail A pins from the opposite direction -- a waiver # for a pair that no longer coerces fails there -- so the # two figures cannot drift apart quietly. + # + # Re-baselined again at issue #141: findings 264 -> 266, pairs 101 -> 102. The one added + # pair is Get-PfbUserGroupQuotaPolicy|Name on two family endpoints, and it is NOT new + # module debt -- #141 changed no cmdlet. It taught the wire-name resolver assignment + # shapes it had been skipping, so that parameter resolved to a wire name for the first + # time and entered the candidate set. + # + # THE MEASUREMENT THAT MAKES THAT MORE THAN AN ASSERTION, and the reason probePairs is + # pinned separately below: probePairs stayed at 1247 across the change while candidates + # moved 629 -> 647. The probe population is unchanged; only how many of it resolve well + # enough to be probed moved. Had probePairs moved too, the generator's own input would + # have changed and this re-baseline would need a different argument entirely. $report = Get-Content $script:reportPath -Raw | ConvertFrom-Json $report.totals.probePairs | Should -Be 1247 - $report.totals.findings | Should -Be 264 + $report.totals.findings | Should -Be 266 $pairs = @($report.results | Where-Object { $_.Outcome -in @('Coerced', 'WrongScalar') } | ForEach-Object { "$($_.Cmdlet)/$($_.Parameter)" } | Sort-Object -Unique) - $pairs.Count | Should -Be 101 + $pairs.Count | Should -Be 102 } It 'records only BindError as unmeasured' { diff --git a/Tests/CommittedDeadKeyReport.Tests.ps1 b/Tests/CommittedDeadKeyReport.Tests.ps1 index 241dbadd..454cdd33 100644 --- a/Tests/CommittedDeadKeyReport.Tests.ps1 +++ b/Tests/CommittedDeadKeyReport.Tests.ps1 @@ -116,6 +116,11 @@ BeforeAll { 'Get-PfbKeytabDownload|GET|keytabs/download' 'Get-PfbLegalHoldEntity|GET|legal-holds/held-entities' 'Get-PfbNodeGroupNode|GET|node-groups/nodes' + # ADDED by issue #141. CSR has exactly one selector-shaped query key, 'names', and + # POST certificates/certificate-signing-requests declares zero query keys -- so no + # selector survives. Visible only now because #141 taught the resolver the assignment + # shape that writes this key; the operation's spec has always declared none. + 'New-PfbCertificateSigningRequest|POST|certificates/certificate-signing-requests' 'New-PfbNlmReclamation|POST|file-systems/locks/nlm-reclamations' 'Remove-PfbNodeGroupNode|DELETE|node-groups/nodes' ) @@ -132,8 +137,32 @@ BeforeAll { # pin: dead keys legitimately fall as fixes land, and a pin would red every such fix and # make the gate a tax on doing the right thing. The cost of the ceiling is precisely the # slack being closed here, so closing it promptly is the whole discipline. - $script:baselineDeadKeyCount = 83 - $script:baselineNoSurvivingSelectorCount = 6 + # + # RAISED 83 -> 85 by issue #141, against everything the paragraph above says, so the raise has + # to earn the exception rather than assert it. It earns it this way: #141 changed no cmdlet. It + # taught the wire-name resolver three assignment shapes it had been skipping silently, so 126 + # parameters that were never evaluated are now evaluated. Two of them turn out to have been + # dead all along: + # + # Get-PfbAlert|Flagged|flagged|GET|alerts + # -- GET /alerts declares no 'flagged' query key. PATCH /alerts declares it as a BODY + # property, which is why this is the single WRONG-SURFACE record in the report. + # New-PfbCertificateSigningRequest|Name|names|POST|certificates/certificate-signing-requests + # -- the operation declares zero query keys, so 'names' cannot be among them. It is also + # the new $baselineNoSurvivingSelector entry above, for the same underlying reason. + # + # Both are PRE-EXISTING module defects that #141 makes visible; it introduces neither. Measured + # against main's committed report, the diff is +2 and -0 -- no previously-reported record was + # lost, which is the other half of the claim and the half a passing test would not show. + # + # That is the ONLY shape of argument that justifies raising this gate: the detector improved + # and the defect was already there. A raise because new code sends a new dead key is precisely + # the regression this ceiling exists to catch, and must never be cleared this way. + # + # Re-lower on either fix. Both defects are tracked as the follow-up issue named in the #141 + # plan; fixing them returns this to 83. + $script:baselineDeadKeyCount = 85 + $script:baselineNoSurvivingSelectorCount = 7 $script:baselineSkipReasons = @{ # New-PfbBucketAuditFilter|Name was introduced by 9d08ecc as a new parameter, so # nothing that was evaluable stopped being evaluated. The parameter demonstrably @@ -154,7 +183,30 @@ BeforeAll { # means the resolver cannot see it, not that it goes nowhere). Absent either, a growing # count is the coverage regression this ceiling exists to catch -- do not bump it to # clear a red. - 'wire name unresolved' = 127 + # LOWERED 127 -> 32 by issue #141, and lowering is the direction this ceiling calls better, + # so the risk here is the opposite one: leaving it high. The bucket fell because #141 split + # what used to be one undifferentiated "cannot resolve" population. The inventory still has + # 66 rows with no WireName, but 34 of them are now explicitly and SEPARATELY accounted for + # as 28 'outside standard request' + 6 'not wire parameter' below. Left at 127 this ceiling + # would carry 95 rows of slack and reproduce exactly the failure the note above describes: + # reporting safety it is no longer providing. + # + # Never raise this bucket to clear a red. The two conditions in the note above still apply + # to any raise, and neither is satisfied by a reclassification. + 'wire name unresolved' = 32 + # NEW vocabulary, added by issue #141, and both are required rather than optional: the scan + # below treats an unknown skip reason as an offender AND asserts it visited every reason + # ($scanned -eq $skipReasonCount). Omit either key and the gate reds for a confusing + # reason; get the vocabulary wrong in the other direction and the scan covers less than the + # report contains while still passing. + # + # These two are ceilinged rather than unceilinged because they are NOT the 'body property' + # case. Both name a population the resolver has positively CLASSIFIED -- a parameter that + # does not travel in a standard request, or is not a wire parameter at all -- rather than + # one it failed to read. Growth in either therefore does mean rows left the evaluated set, + # which is what a ceiling is for. + 'outside standard request' = 28 + 'not wire parameter' = 6 'endpoint/method ambiguous' = 14 'endpoint/verb absent from spec' = 0 } @@ -173,7 +225,7 @@ BeforeAll { # Reconciliation still catches arithmetic inconsistency, but it does NOT catch a realistic # reclassification that moves one record from keysEvaluated into this bucket while preserving # the total. The keysEvaluated floor is the remaining coverage-collapse check, with deliberate - # headroom from its measured 1757. This reason is unceilinged because that weaker watch is the + # headroom from its measured 1779. This reason is unceilinged because that weaker watch is the # accepted cost of avoiding false reds on legitimate body-surface work. $script:baselineUnceilingedSkipReasons = @('body property') } @@ -310,8 +362,8 @@ Describe 'Committed dead-key report (REGRESSION guard, no spec cache required)' It 'keeps the inventory covered: parametersInventoried and keysEvaluated stay above their floors' { # THE COVERAGE-COLLAPSE GUARD. Without it every other assertion in this file can be - # satisfied by a report that simply stopped looking: cut parametersInventoried 2174 -> - # 900 and keysEvaluated 1757 -> 700, and a third of the dead keys and a third of the + # satisfied by a report that simply stopped looking: cut parametersInventoried 2168 -> + # 900 and keysEvaluated 1779 -> 700, and a third of the dead keys and a third of the # groups vanish from the gate's view with every ceiling and every allowlist still # cleared. That was reproduced against this file before this test existed -- all six # tests passed. A gate reporting safety it does not provide is worse than no gate. @@ -326,15 +378,19 @@ Describe 'Committed dead-key report (REGRESSION guard, no spec cache required)' # floor of 20 against 29 published specs: this asserts "the mechanism still ran over # the corpus", and the real figures move with ordinary cmdlet churn and with genuine # reclassification. Pinning them would turn every unrelated cmdlet addition into a red - # build. Measured at the baseline commit (specVersion 2.28): parametersInventoried - # 2174, keysEvaluated 1757 -- so the headroom below is 174 and 157 respectively, and - # the 900/700 collapse misses by a wide margin. + # build. Measured against the committed artifact at issue #141 (specVersion 2.28): + # parametersInventoried 2168, keysEvaluated 1779 -- so the headroom below is 168 and 179 + # respectively, and the 900/700 collapse misses by a wide margin. (Both figures were + # 2174 / 1757 when this comment was written; parametersInventoried has since settled at + # 2168 through ordinary cmdlet churn, and keysEvaluated ROSE because #141 taught the + # resolver three assignment shapes it had been skipping. The floors are unchanged -- + # keysEvaluated rising is the direction they exist to protect.) # # deadKey and the skip counts are deliberately NOT floored. Those must be free to fall # to zero; that is the whole monotone design, and flooring them would recreate the bug # this file was sent back for. - $committedReport.counts.parametersInventoried | Should -BeGreaterOrEqual 2000 -Because "the AST inventory must still be walking the whole of Public/: it reported $($committedReport.counts.parametersInventoried) parameters against a measured 2174. A large drop is a coverage collapse, not an improvement -- the keys that disappeared were not proven safe, they stopped being looked at." - $committedReport.counts.keysEvaluated | Should -BeGreaterOrEqual 1600 -Because "the classifier must still be evaluating the bulk of the inventory: it reported $($committedReport.counts.keysEvaluated) evaluated keys against a measured 1757. Every key that stops being evaluated leaves the gate's view silently." + $committedReport.counts.parametersInventoried | Should -BeGreaterOrEqual 2000 -Because "the AST inventory must still be walking the whole of Public/: it reported $($committedReport.counts.parametersInventoried) parameters against a measured 2168. A large drop is a coverage collapse, not an improvement -- the keys that disappeared were not proven safe, they stopped being looked at." + $committedReport.counts.keysEvaluated | Should -BeGreaterOrEqual 1600 -Because "the classifier must still be evaluating the bulk of the inventory: it reported $($committedReport.counts.keysEvaluated) evaluated keys against a measured 1779. Every key that stops being evaluated leaves the gate's view silently." # THE RECONCILIATION, both halves -- and the two halves are NOT of equal strength. Said # plainly, because an earlier version of this comment overclaimed the first one: # diff --git a/Tests/Fixtures/PfbSelectorWaivers.psd1 b/Tests/Fixtures/PfbSelectorWaivers.psd1 index 243363f8..1b8da489 100644 --- a/Tests/Fixtures/PfbSelectorWaivers.psd1 +++ b/Tests/Fixtures/PfbSelectorWaivers.psd1 @@ -7,11 +7,18 @@ its waiver rather than leave a licence behind for the next reintroduction. ONE ENTRY PER (Cmdlet, Parameter) PAIR, NOT PER PRODUCING ENDPOINT. The current report's - 264 finding rows are producer multiplicity over 101 real defects; keying by triple would be - 264 entries against psd1's hard 500-element cap for a single collection literal, and would + 266 finding rows are producer multiplicity over 102 real defects; keying by triple would be + 266 entries against psd1's hard 500-element cap for a single collection literal, and would list the same defect up to a dozen times. (The pre-fix audit measured 389 rows over 127 pairs; the guards and renames delivered for #90 account for the reduction.) + 264 rows / 101 pairs -> 266 / 102 at issue #141, and the added pair is NOT new module debt. + #141 changed no cmdlet; it taught the wire-name resolver assignment shapes it had been + skipping, so Get-PfbUserGroupQuotaPolicy|Name entered the probe candidate set for the first + time and reproduced a defect that was always there. Candidates moved 629 -> 647 while probe + pairs stayed at 1247, which is the measurement that distinguishes "the rail can see more" + from "the module does more". + Scope and Producers are LOAD-BEARING, not annotation -- pair-level keying is otherwise blind to where a coercion happens. Rail A fails if a Family-scoped waiver's pair starts coercing on its PRIMARY producer (the chain a user would obviously write), and fails if a @@ -21,9 +28,13 @@ Family = only against another endpoint in the same resource family), Issue, Producers (how many endpoints reproduce it), Why. - Issue is #90 for every entry because the fix issue does not exist yet -- #90 delivers the - audit and this rail, and the split issue is filed after the PR exists. Re-pointing these at - that issue is a follow-up commit. + Issue is #90 for every entry originating in that audit, because the fix issue does not exist + yet -- #90 delivers the audit and this rail, and the split issue is filed after the PR + exists. Re-pointing these at that issue is a follow-up commit. The one #141 entry follows + the same convention for the same reason: it names the issue that REVEALED the defect, not a + fix issue, and it is owed the same re-pointing. It is called out here rather than left for a + reader to notice, because "Issue is #90 for every entry" was true until #141 and a stale + absolute like that is how a register stops being read. Clusters below are the audit report's root-cause clusters (issue-90-audit-report.md, 3.3), not cosmetic grouping: each cluster is one fix, not N. @@ -180,6 +191,13 @@ Why = 'Primary producer binds -Name correctly; one family endpoint coerces, GET /syslog-servers/test -- the /test endpoint returns a test-result item, not a resource, so it has no name.' } @{ Cmdlet = 'Get-PfbTlsPolicy'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 2 Why = 'Primary producer binds -Name correctly; 2 family endpoints coerce, e.g. GET /tls-policies/members -- that join item returns its endpoints as objects (member, policy) and carries no name.' } + # NEW at issue #141, and new only to the RAIL -- not to the module. -Name was invisible + # here until #141 taught the wire-name resolver the assignment shape that writes it + # (inventory row moved TypedUnresolved -> Typed|names|Query|GET|user-group-quota-policies), + # so the pair entered the probe candidate set and immediately reproduced the same + # nested-join-item defect as the two entries above it. Nothing about the cmdlet changed. + @{ Cmdlet = 'Get-PfbUserGroupQuotaPolicy'; Parameter = 'Name'; Scope = 'Family'; Issue = '#141'; Producers = 2 + Why = 'Primary producer binds -Name correctly; 2 family endpoints coerce, GET /user-group-quota-policies/file-systems and /members -- both join items return their endpoints as objects (context, member, policy) and carry no name, so a name-shaped selector cannot bind by property name and is stringified to names=@{context=; member=; policy=}. Same root cause as the Get-PfbTlsPolicy and Get-PfbWormPolicy entries: one API design decision, one fix.' } @{ Cmdlet = 'Get-PfbWorkload'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 1 Why = 'Primary producer binds -Name correctly; one family endpoint coerces, GET /workloads/tags -- its items carry no name (context, copyable, key, namespace, resource, value).' } @{ Cmdlet = 'Get-PfbWormPolicy'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 1 diff --git a/Tests/coverage-baseline.psd1 b/Tests/coverage-baseline.psd1 index 5d069938..6d3b670d 100644 --- a/Tests/coverage-baseline.psd1 +++ b/Tests/coverage-baseline.psd1 @@ -265,7 +265,16 @@ 'Build-PfbFieldCmdletMap.Tests.ps1' = 26 'PfbPipelineSelectorRail.Tests.ps1' = 11 'Build-PfbResponseShapeMap.Tests.ps1' = 9 - 'Build-PfbDeadKeyReport.Tests.ps1' = 6 + # 6 -> 16 for issue #141 Task 5: ten new Its across the file's two existing + # PS7-gated Describes -- the dead-key declaration classification work (declaration + # index, the wrong-surface/wrong-verb priority ladder, case tolerance on both the + # endpoint and the key axis, and the provenance list's dedup and ordering). No new + # Describe and no gate change, so every added It inherits the file's existing + # -Skip:($PSVersionTable.PSVersion.Major -lt 7) and lands on the 5.1 skip count. + # Measured on Windows PowerShell 5.1 for this file alone, read out of the runner's + # child winps51.json rather than its Write-Host summary: 0 passed / 0 failed / + # 16 skipped, container ok. No headroom added -- these entries are exact. + 'Build-PfbDeadKeyReport.Tests.ps1' = 16 # Mixed files: some Describes PS7-gated, others deliberately ungated so they # execute on both legs. The passing halves are what several RequiredDescribes # entries above are asserting, so these two numbers moving in opposite directions diff --git a/tools/inventory-tuple-baselines/issue-141-task4.json b/tools/inventory-tuple-baselines/landed/issue-141-task4.json similarity index 100% rename from tools/inventory-tuple-baselines/issue-141-task4.json rename to tools/inventory-tuple-baselines/landed/issue-141-task4.json From 21d7068ca3dcc26fb03270fbcb14da9e59159f25 Mon Sep 17 00:00:00 2001 From: Justin Emerson Date: Thu, 27 Aug 2026 17:15:57 -0700 Subject: [PATCH 27/29] docs(tools): follow through on the bookkeeping this task invalidated Review fixes for 8472011. Three Major, two Minor, all of them bookkeeping that the previous commit either edited or invalidated and did not carry through. Comment- and documentation-only: executable token skeletons byte-identical for all three .ps1 files (1304/1015/609), every file parses clean, and no module file is touched. MAJOR 1. Tests/coverage-baseline.psd1 reconciliation note said "TWO entries have moved ... 297 + 4 + 11 = 312". 8472011 moved a third (Build-PfbDeadKeyReport 6 -> 16) and left the note alone. Recomputed from the map with the check the note itself prescribes rather than incremented by hand: 19 entries, sum 322. The paragraph immediately below exists because an earlier revision made this exact error; it has now been made twice by the same mechanism, and the note says so. MAJOR 2. The Step 6b git mv left four documented invocations pointing at a path that no longer exists -- Compare-PfbInventoryTuple.ps1 .DESCRIPTION and .EXAMPLE, and two runnable blocks in tools/README.md. The .EXAMPLE is the documented way to run the gate, so anyone copying it got a file-not-found and would reasonably conclude the gate was broken. The three runnable sites now use .json; the prose site points at landed/ and says the file is a shape to read, not a file to pass. MAJOR 3. The rationale given for retiring the declaration file pre-merge is withdrawn. It claimed the authority of the file's own retirement note, and that note -- with Compare-PfbInventoryTuple.ps1:56 -- says retire it WHEN THE CHANGE MERGES, which is the opposite. The move is kept on narrower and honest grounds: the plan mandates it in this task, this is the PR that merges those commits, and the script has zero auto-discovery so landed/ can never be read implicitly. What is given up is stated rather than glossed -- the file was a live, passing gate at the moment of retirement (CLEAN, 34/34 declared, exit 0) and a reviewer who wants it must now run it explicitly against landed/. That sentence belongs in the PR body. MINOR 4. CommittedDeadKeyReport.Tests.ps1 attributed the whole keysEvaluated delta to #141. Measured against main's committed artifact: churn took 2174/1757 -> 2168/1747, and #141 then raised keysEvaluated 1747 -> 1779. The old text credited #141 with +22 where it earned +32 against a base that had fallen. MINOR 5. PfbPipelineSelectorRail.Tests.ps1 still carried 264/101 in three places while the identical prose in the waiver fixture had been updated to 266/102, so the two disagreed. Now 266 / 102 / "101 of the 102". NOT FIXED, deliberately: CommittedDeadKeyReport.Tests.ps1's "this collection has only 18 entries" is wrong (noSurvivingSelector has 7) but is pre-existing -- the identical line is at origin/main:289 -- so it is flagged for the whole-branch review rather than pulled into this task's scope. Scoped run over the six affected files, both editions: pwsh 7 83/0/0, WinPS 5.1 23/0/60, containers ok. Co-Authored-By: Claude Opus 5 (1M context) --- Tests/CommittedDeadKeyReport.Tests.ps1 | 9 ++++++--- Tests/PfbPipelineSelectorRail.Tests.ps1 | 6 +++--- Tests/coverage-baseline.psd1 | 13 +++++++++---- tools/Compare-PfbInventoryTuple.ps1 | 6 ++++-- tools/README.md | 4 ++-- 5 files changed, 24 insertions(+), 14 deletions(-) diff --git a/Tests/CommittedDeadKeyReport.Tests.ps1 b/Tests/CommittedDeadKeyReport.Tests.ps1 index 454cdd33..4eeab938 100644 --- a/Tests/CommittedDeadKeyReport.Tests.ps1 +++ b/Tests/CommittedDeadKeyReport.Tests.ps1 @@ -381,9 +381,12 @@ Describe 'Committed dead-key report (REGRESSION guard, no spec cache required)' # build. Measured against the committed artifact at issue #141 (specVersion 2.28): # parametersInventoried 2168, keysEvaluated 1779 -- so the headroom below is 168 and 179 # respectively, and the 900/700 collapse misses by a wide margin. (Both figures were - # 2174 / 1757 when this comment was written; parametersInventoried has since settled at - # 2168 through ordinary cmdlet churn, and keysEvaluated ROSE because #141 taught the - # resolver three assignment shapes it had been skipping. The floors are unchanged -- + # 2174 / 1757 when this comment was written, and BOTH moved in two steps, not one: + # ordinary cmdlet churn took parametersInventoried 2174 -> 2168 and keysEvaluated + # 1757 -> 1747 -- those are main's committed figures, measured, not inferred -- and then + # #141 raised keysEvaluated 1747 -> 1779 by teaching the resolver three assignment shapes + # it had been skipping. Attributing the whole keysEvaluated delta to #141 would credit it + # with +22 when it earned +32 against a base that had fallen. The floors are unchanged; # keysEvaluated rising is the direction they exist to protect.) # # deadKey and the skip counts are deliberately NOT floored. Those must be free to fall diff --git a/Tests/PfbPipelineSelectorRail.Tests.ps1 b/Tests/PfbPipelineSelectorRail.Tests.ps1 index 32746382..c7d95c9f 100644 --- a/Tests/PfbPipelineSelectorRail.Tests.ps1 +++ b/Tests/PfbPipelineSelectorRail.Tests.ps1 @@ -16,8 +16,8 @@ object is rebuilt from the report's own ProbeProperties/ProbeTypes fields instead. Waivers are keyed by (cmdlet, parameter) PAIR, not by (cmdlet, parameter, producer) - triple. The 264 finding rows are producer multiplicity over 101 real defects; a - triple-keyed file would be 264 entries against psd1's hard 500-element parse cap for a + triple. The 266 finding rows are producer multiplicity over 102 real defects; a + triple-keyed file would be 266 entries against psd1's hard 500-element parse cap for a single collection literal, and would list the same defect up to a dozen times. The rail still names the producing endpoint in its failure text as evidence. @@ -186,7 +186,7 @@ Describe 'Rail A - no unwaived selector coercion' -Skip:($PSVersionTable.PSVersi } It 'no Family-scoped waiver has escalated onto its primary producer' { - # A pair-keyed waiver is blind to WHERE the coercion happens, and 100 of the 101 are + # A pair-keyed waiver is blind to WHERE the coercion happens, and 101 of the 102 are # waived precisely because the obvious chain -- the cmdlet's own base-path GET -- is # safe. Without this, a change that breaks property-name binding on, say, # Remove-PfbFileSystem would turn `Get-PfbFileSystem | Remove-PfbFileSystem` into a diff --git a/Tests/coverage-baseline.psd1 b/Tests/coverage-baseline.psd1 index 6d3b670d..04fd96d7 100644 --- a/Tests/coverage-baseline.psd1 +++ b/Tests/coverage-baseline.psd1 @@ -222,10 +222,15 @@ # the gate asserts that reconciliation on every run, so a container the walk misses is # a red rather than a quietly smaller number. # - # Since seeding, TWO entries have moved, and the arithmetic below accounts for both: - # PfbApiDriftTools.Tests.ps1 8 -> 12 for issue #113 (+4), and - # Build-PfbFieldCmdletMap.Tests.ps1 15 -> 26 for issue #141 Task 4 (+11). Each carries - # its own note at its entry. 297 + 4 + 11 = 312, which is what the entries sum to. + # Since seeding, THREE entries have moved, and the arithmetic below accounts for all + # three: PfbApiDriftTools.Tests.ps1 8 -> 12 for issue #113 (+4), + # Build-PfbFieldCmdletMap.Tests.ps1 15 -> 26 for issue #141 Task 4 (+11), and + # Build-PfbDeadKeyReport.Tests.ps1 6 -> 16 for issue #141 Task 5 (+10). Each carries + # its own note at its entry. 297 + 4 + 11 + 10 = 322, which is what the entries sum to. + # + # The third entry was added by #141 Task 6 while this note still said TWO -- i.e. the + # exact failure the paragraph below describes, committed by the same mechanism one + # revision later. Recomputed from the map, not incremented by hand: 19 entries, 322. # # Recompute this total from the map itself rather than adjusting it by the delta in # hand -- an earlier revision of this note said "one entry has moved ... sum to 307", diff --git a/tools/Compare-PfbInventoryTuple.ps1 b/tools/Compare-PfbInventoryTuple.ps1 index 1a6d8169..f3a4efa3 100644 --- a/tools/Compare-PfbInventoryTuple.ps1 +++ b/tools/Compare-PfbInventoryTuple.ps1 @@ -46,7 +46,9 @@ string -- exactly what this script prints for an undeclared change, so a reviewed change can be pasted straight in. A bare array is REFUSED rather than quietly accepted, because an array cannot carry the ref it was measured at and that omission is the defect described under - -BaselineRef. tools/inventory-tuple-baselines/issue-141-task4.json is the worked example. + -BaselineRef. tools/inventory-tuple-baselines/landed/issue-141-task4.json is the worked + example -- read it for the shape; it lives under landed/ because it has been retired, so it + is no longer a file to pass to -DeclarationPath. RETIREMENT, and why it is a documented step rather than a softer rail. A declaration file describes a change that has not landed yet. Once its commits ARE the baseline, every entry @@ -60,7 +62,7 @@ # Normal use. The ref comes from the declaration file, so this is correct even on a stacked # branch whose base is not an ancestor of origin/main. ./tools/Compare-PfbInventoryTuple.ps1 ` - -DeclarationPath ./tools/inventory-tuple-baselines/issue-141-task4.json + -DeclarationPath ./tools/inventory-tuple-baselines/.json .EXAMPLE # No declarations: report every row that moved against origin/main. Exits 1 if any did. ./tools/Compare-PfbInventoryTuple.ps1 -BaselineRef origin/main diff --git a/tools/README.md b/tools/README.md index b3f13f5c..161cc3cd 100644 --- a/tools/README.md +++ b/tools/README.md @@ -476,7 +476,7 @@ Run in this order: ```powershell ./tools/Compare-PfbInventoryTuple.ps1 ` - -DeclarationPath ./tools/inventory-tuple-baselines/issue-141-task4.json + -DeclarationPath ./tools/inventory-tuple-baselines/.json ``` ## Response-shape drift (`Build-PfbResponseShapeMap.ps1`) @@ -728,7 +728,7 @@ summary count that went up. # Normal use. The declaration file carries the ref it was measured at, so this is # correct even on a stacked branch whose base is not an ancestor of origin/main. ./tools/Compare-PfbInventoryTuple.ps1 ` - -DeclarationPath ./tools/inventory-tuple-baselines/issue-141-task4.json + -DeclarationPath ./tools/inventory-tuple-baselines/.json # No declarations: report every row that moved against origin/main. Exits 1 if any did. ./tools/Compare-PfbInventoryTuple.ps1 -BaselineRef origin/main From dbddb2297f983e422ffb3b60f6d2538e611bd6d0 Mon Sep 17 00:00:00 2001 From: Justin Emerson Date: Thu, 27 Aug 2026 17:43:02 -0700 Subject: [PATCH 28/29] docs: correct three superseded figures the whole-branch review found Whole-branch final review of 21d7068: APPROVED, no Blocking, no defect in code, artifacts, gates or the exemption. Every load-bearing numeric claim held on independent measurement. What it did find was prose arguing from figures a later task changed -- the failure mode its cross-task item exists to catch. Comment- and doc-only; executable skeletons byte-identical for both .ps1 files. MAJOR. tools/Compare-PfbInventoryTuple.ps1 .DESCRIPTION credited Task 3 with raising the Typed count by 61. Task 3's own delta is +29 (2073 -> 2102); 61 is the cumulative branch figure, which the tuple gate confirms independently -- 62 rows entered Typed, 1 left, 2102 - 62 + 1 = 2041 on main. The narrative was right and only the number was misattributed, but this docstring is the authoritative explanation of why the gate exists. The plan's Completion Condition 8 carried the same class of error and is fixed in the plan file (not in this repo): three of its nine values were the superseded 65 / 31 / 1,780 triple that Task 6 Step 4 had already retracted 175 lines earlier, so the branch failed its own definition of done while being correct. Acceptance criteria are what a reader checks the artifact against, so that one could have driven someone to "fix" the artifact toward the wrong numbers. MINOR. Tests/CommittedDeadKeyReport.Tests.ps1: "this collection has only 18 entries" describes noSurvivingSelector, which holds 7. Pre-existing -- byte-identical at origin/main:289 -- and I deferred it once as out of scope. Fixing it now, because the reviewer's argument is better than mine was: this commit raises baselineNoSurvivingSelectorCount 6 -> 7 four lines above it in the same BeforeAll, and Task 6 already swept this exact file for prose arguing from contradicted figures. Not unsafe either way; the assertion beneath it is an equality against input size, not against 18. MINOR. The Compare-PfbInventoryTuple docstring and the plan gave opposite instructions for the retired declaration file -- "no longer a file to pass to -DeclarationPath" against a plan sentence telling a reviewer to pass exactly that path. Softened to "not a file any ROUTINE run should pass; pass it explicitly only to reproduce Task 4's evidence", and it now records the sharper reason retiring it pre-merge was right, which the review supplied: its baselineRef is an intermediate feature-branch commit, and this repo squash-merges, so after merge the ref may be unreachable and a future run would fail on ref RESOLUTION rather than the 34 STALE-DECL Step 6b predicted. Verification, re-run after these edits -- scoped 10-file list, both editions: pwsh 7 581/0/0, WinPS 5.1 418/0/163, containers ok, TotalCount 581 on both legs, so the 5.1 leg discovered the identical set rather than being a smaller run wearing a green label. Co-Authored-By: Claude Opus 5 (1M context) --- Tests/CommittedDeadKeyReport.Tests.ps1 | 2 +- tools/Compare-PfbInventoryTuple.ps1 | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Tests/CommittedDeadKeyReport.Tests.ps1 b/Tests/CommittedDeadKeyReport.Tests.ps1 index 4eeab938..d71ae3f3 100644 --- a/Tests/CommittedDeadKeyReport.Tests.ps1 +++ b/Tests/CommittedDeadKeyReport.Tests.ps1 @@ -338,7 +338,7 @@ Describe 'Committed dead-key report (REGRESSION guard, no spec cache required)' # strictly worse than a new DESTRUCTIVE dead key alongside a surviving selector. # # Same visit-everything reasoning as the test above, and here the payoff is sharper: - # this collection has only 18 entries, so "somebody fixed all of them" is one PR. Any + # this collection has only 7 entries, so "somebody fixed all of them" is one PR. Any # non-emptiness floor -- on this collection or on deadKeys -- would make the gate red on # precisely its own success. An equality against the input size does not. $scanInput = @($committedReport.noSurvivingSelector) diff --git a/tools/Compare-PfbInventoryTuple.ps1 b/tools/Compare-PfbInventoryTuple.ps1 index f3a4efa3..eec96d1f 100644 --- a/tools/Compare-PfbInventoryTuple.ps1 +++ b/tools/Compare-PfbInventoryTuple.ps1 @@ -6,7 +6,7 @@ working tree -- and fails unless every move was declared in advance. .DESCRIPTION A resolver change can WITHDRAW a resolution as easily as add one, and no total shows it. - Issue #141 Task 3 raised the Typed count by 61 while silently demoting + Issue #141 Task 3 raised the Typed count by 29 (61 across #141 as a whole) while silently demoting Update-PfbBucketAuditFilter -BucketName from a confident 'bucket_names' to unresolved; the only thing that caught it was a human diffing rows by hand in a code review. This script is that diff, made runnable and repeatable. @@ -47,8 +47,10 @@ be pasted straight in. A bare array is REFUSED rather than quietly accepted, because an array cannot carry the ref it was measured at and that omission is the defect described under -BaselineRef. tools/inventory-tuple-baselines/landed/issue-141-task4.json is the worked - example -- read it for the shape; it lives under landed/ because it has been retired, so it - is no longer a file to pass to -DeclarationPath. + example -- read it for the shape. It lives under landed/ because it has been retired, so it is + not a file any ROUTINE run should pass; pass it explicitly only to reproduce Task 4's evidence, + and note its baselineRef is an intermediate feature-branch commit that a squash merge may leave + unreachable, in which case the run fails on ref resolution rather than on STALE-DECL. RETIREMENT, and why it is a documented step rather than a softer rail. A declaration file describes a change that has not landed yet. Once its commits ARE the baseline, every entry From c566c78c498a86dafa2e64df6aaf3deca7ef94e5 Mon Sep 17 00:00:00 2001 From: Justin Emerson Date: Thu, 27 Aug 2026 20:12:04 -0700 Subject: [PATCH 29/29] fix(tools): do not read a defaulting fallback arm as a second wire name A cmdlet may write the same wire key from a later arm of one if/elseif chain as a convenience default derived from a different parameter. Update-PfbBucketAuditFilter does this: -Name sets names, and an elseif sets names from -BucketName so a -BucketName-only caller need not restate the value. The resolver counted that arm as a landing of -BucketName, so -BucketName appeared to land both bucket_names and names, the arbitration abstained, and PATCH /buckets/audit-filters lost parser traceability and dropped to partial confidence -- tripping the issue #31 guard in CI. Test-PfbIsDefaultingAliasAssignment flags an index assignment whose key and target variable are already written by an earlier sibling clause of the same if/elseif chain from an expression that does not mention this parameter. Flagged landings are dropped only when the parameter still has an unflagged landing of its own. That proviso is load-bearing: New-PfbFleetMember writes members from a FleetKey arm and again from a -Members elseif, but neither defaults the other and -Members has no other landing, so an unconditional drop deletes its only evidence and relocates the same regression onto POST /fleets/members. Measured against the previous commit: keysEvaluated 1779 -> 1780, resolved ok 1694 -> 1695, wire-name-unresolved 32 -> 31, dead keys unchanged at 85, field map 2067 -> 2068 entries (the single added entry being Update-PfbBucketAuditFilter|BucketName|bucket_names). Selector findings are unchanged at 266 rows over 102 pairs with probePairs still 1247 and control leakage still 0; SelectorUnresolved falls 14 -> 6. A whole-module sweep now reports zero wire-landing abstentions, where the same sweep on the previous commit reports exactly one. Also re-points the 102 selector waivers off closed #90: 64 to #152 (the join-item class), 37 to #153 (items with no name for unrelated reasons), and 1 to #123, which already tracks that pair against a different, upstream blocker. Co-Authored-By: Claude Opus 5 (1M context) --- Reports/PfbApiDriftReport.json | 30 +--- Reports/PfbApiDriftReport.md | 8 +- Reports/PfbDeadKeyReport.json | 6 +- Reports/PfbFieldCmdletMap.json | 14 +- Reports/PfbFieldCmdletMapping.md | 5 +- Reports/PfbPipelineSelectorMap.json | 60 +++---- Reports/PfbPipelineSelectorMap.md | 8 +- Tests/Fixtures/PfbSelectorWaivers.psd1 | 235 +++++++++++++------------ Tests/PfbCmdletParamTools.Tests.ps1 | 104 +++++++++++ tools/lib/PfbCmdletParamTools.ps1 | 142 ++++++++++++++- 10 files changed, 422 insertions(+), 190 deletions(-) diff --git a/Reports/PfbApiDriftReport.json b/Reports/PfbApiDriftReport.json index 3e5b0442..96b8428f 100644 --- a/Reports/PfbApiDriftReport.json +++ b/Reports/PfbApiDriftReport.json @@ -5275,33 +5275,6 @@ }, "annotations": [] }, - { - "endpoint": "PATCH /buckets/audit-filters", - "cmdlets": [ - "Update-PfbBucketAuditFilter" - ], - "missingQueryParameters": [ - "bucket_names" - ], - "missingBodyProperties": [], - "readOnlyFields": [], - "confidence": { - "level": "partial", - "unresolvedParameters": [ - { - "parameter": "BucketName", - "surface": "AttributesOnly", - "file": "Public/Bucket/Update-PfbBucketAuditFilter.ps1", - "line": 78 - } - ], - "escapeHatchOnly": [ - "BucketName" - ], - "caveat": "body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability" - }, - "annotations": [] - }, { "endpoint": "PATCH /certificates", "cmdlets": [ @@ -19081,7 +19054,7 @@ }, { "name": "bucket_names", - "cmdletCount": 15, + "cmdletCount": 16, "cmdlets": [ "Get-PfbBucketAccessPolicy", "Get-PfbBucketAccessPolicyRule", @@ -19097,6 +19070,7 @@ "Remove-PfbBucketAccessPolicy", "Remove-PfbBucketAuditFilter", "Remove-PfbBucketCorsPolicy", + "Update-PfbBucketAuditFilter", "Update-PfbLifecycleRule" ] }, diff --git a/Reports/PfbApiDriftReport.md b/Reports/PfbApiDriftReport.md index d9a43986..46740ce5 100644 --- a/Reports/PfbApiDriftReport.md +++ b/Reports/PfbApiDriftReport.md @@ -21,12 +21,12 @@ This report accepts **false positives in order to eliminate false negatives**. A ## Summary - Uncovered endpoints: 95 -- Endpoints with parameter gaps: 355 +- Endpoints with parameter gaps: 354 - Missing body properties (addable): 395 -- Missing query parameters (addable): 539 +- Missing query parameters (addable): 538 - Read-only body fields (not addable -- see the Read-only fields section below): 384 - Phantom fields silently excluded (accumulated in the capability map, absent from the newest analysed spec): 40 -- Partial-confidence endpoints (see `How to read this report` above, and each row's marker in the Parameter gaps table): 11 +- Partial-confidence endpoints (see `How to read this report` above, and each row's marker in the Parameter gaps table): 10 - Systemic gaps (distinct field names collapsed across high-confidence endpoints, detailed below): 297 - ValidateSet drift: 0 - New ValidateSet candidates: 2 @@ -293,7 +293,6 @@ Endpoints an existing cmdlet already calls, where the capability map knows of a | `PATCH /audit-file-systems-policies` | Update-PfbAuditFileSystemPolicy | | add_log_targets, control_type, location, log_targets, name, remove_log_targets, rules | `high` | | | `PATCH /audit-object-store-policies` | Update-PfbAuditObjectStorePolicy | | add_log_targets, location, log_targets, name, remove_log_targets | `high` | | | `PATCH /buckets` | Remove-PfbBucket, Update-PfbBucket | cancel_in_progress_storage_class_transition, ignore_usage | eradication_config, hard_limit_enabled, object_lock_config, public_access_config, qos_policy, retention_lock, storage_class | `high` | | -| `PATCH /buckets/audit-filters` | Update-PfbBucketAuditFilter | bucket_names | | `partial` -- /!\ 1 unresolved param (see Partial-confidence detail below) | | | `PATCH /certificates` | Update-PfbCertificate | | | `high` | | | `PATCH /data-eviction-policies` | Update-PfbDataEvictionPolicy | | location | `high` | | | `PATCH /directory-services` | Update-PfbDirectoryService | ids | base_dn, bind_password, bind_user, ca_certificate, ca_certificate_group, enabled, management, nfs, smb, uris | `high` | | @@ -438,7 +437,6 @@ Per the decision-6 procedure above: open each parameter at its `file:line` and f | Endpoint | Parameter | Surface | File:Line | Caveat | |---|---|---|---|---| | `GET /arrays` | `-Endpoint` | TypedUnresolved | `Public/Connection/Test-PfbConnection.ps1:31` | one or more parameters could not be traced to a wire name and have no -Attributes escape hatch; lists reflect typed-parameter coverage only, not full wire reachability | -| `PATCH /buckets/audit-filters` | `-BucketName` | AttributesOnly | `Public/Bucket/Update-PfbBucketAuditFilter.ps1:78` | body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability | | `PATCH /file-systems` | `-NfsEnabled` | AttributesOnly | `Public/FileSystem/Update-PfbFileSystem.ps1:72` | body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability | | `PATCH /file-systems` | `-NfsExportPolicy` | AttributesOnly | `Public/FileSystem/Update-PfbFileSystem.ps1:78` | body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability | | `PATCH /file-systems` | `-NfsRules` | AttributesOnly | `Public/FileSystem/Update-PfbFileSystem.ps1:75` | body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability | diff --git a/Reports/PfbDeadKeyReport.json b/Reports/PfbDeadKeyReport.json index 3e75eafd..12059790 100644 --- a/Reports/PfbDeadKeyReport.json +++ b/Reports/PfbDeadKeyReport.json @@ -2,11 +2,11 @@ "specVersion": "2.28", "counts": { "parametersInventoried": 2168, - "keysEvaluated": 1779, - "ok": 1694, + "keysEvaluated": 1780, + "ok": 1695, "deadKey": 85, "skipReasons": { - "wire name unresolved": 32, + "wire name unresolved": 31, "outside standard request": 28, "not wire parameter": 6, "body property": 309, diff --git a/Reports/PfbFieldCmdletMap.json b/Reports/PfbFieldCmdletMap.json index 6e51065a..ce3391e8 100644 --- a/Reports/PfbFieldCmdletMap.json +++ b/Reports/PfbFieldCmdletMap.json @@ -17707,6 +17707,16 @@ "stableSinceOldestVersion": null, "recommendation": null }, + { + "cmdlet": "Update-PfbBucketAuditFilter", + "parameter": "BucketName", + "wireName": "bucket_names", + "status": "no-spec-enum-found", + "matchedKey": null, + "specValues": null, + "stableSinceOldestVersion": null, + "recommendation": null + }, { "cmdlet": "Update-PfbBucketAuditFilter", "parameter": "Name", @@ -20779,10 +20789,6 @@ "cmdlet": "New-PfbServer", "parameter": "CreateDirectoryService" }, - { - "cmdlet": "Update-PfbBucketAuditFilter", - "parameter": "BucketName" - }, { "cmdlet": "Update-PfbFileSystem", "parameter": "NfsEnabled" diff --git a/Reports/PfbFieldCmdletMapping.md b/Reports/PfbFieldCmdletMapping.md index 781d9dff..7acceb10 100644 --- a/Reports/PfbFieldCmdletMapping.md +++ b/Reports/PfbFieldCmdletMapping.md @@ -9,7 +9,7 @@ Reporting only -- no `Public/` cmdlet is edited by this script. Every `matched` - matched: 2 - collision: 1 - not-found-in-resource: 29 -- no-spec-enum-found: 2035 +- no-spec-enum-found: 2036 | Cmdlet | Parameter | Wire name | Status | Spec values | Recommendation | |---|---|---|---|---|---| @@ -46,7 +46,7 @@ Reporting only -- no `Public/` cmdlet is edited by this script. Every `matched` | `Update-PfbWorkload` | `-NewName` | name | not-found-in-resource | | | | `Update-PfbWormPolicy` | `-DefaultRetention` | default_retention | not-found-in-resource | | | -## Attributes-only parameters (no typed field to attach either mechanism to): 24 +## Attributes-only parameters (no typed field to attach either mechanism to): 23 - `New-PfbBucketAuditFilter -Name` - `New-PfbFileSystem -DefaultExports` @@ -65,7 +65,6 @@ Reporting only -- no `Public/` cmdlet is edited by this script. Every `matched` - `New-PfbFileSystem -SmbSharePolicy` - `New-PfbFileSystem -SnapshotDirectoryEnabled` - `New-PfbServer -CreateDirectoryService` -- `Update-PfbBucketAuditFilter -BucketName` - `Update-PfbFileSystem -NfsEnabled` - `Update-PfbFileSystem -NfsExportPolicy` - `Update-PfbFileSystem -NfsRules` diff --git a/Reports/PfbPipelineSelectorMap.json b/Reports/PfbPipelineSelectorMap.json index 942aca5c..f0afa76c 100644 --- a/Reports/PfbPipelineSelectorMap.json +++ b/Reports/PfbPipelineSelectorMap.json @@ -8,12 +8,12 @@ ], "totals": { "probePairs": 1247, - "evaluatedPairs": 1233, - "candidatePairs": 647, - "candidateRate": 0.5247, + "evaluatedPairs": 1241, + "candidatePairs": 655, + "candidateRate": 0.5278, "findings": 266, "findingPairs": 102, - "confirmationRate": 0.4111, + "confirmationRate": 0.4061, "controlLeakage": 0, "assistedRows": 212 }, @@ -50,7 +50,7 @@ "gateBreakdown": [ { "Gate": "Candidate", - "Count": 647 + "Count": 655 }, { "Gate": "Matched", @@ -58,7 +58,7 @@ }, { "Gate": "SelectorUnresolved", - "Count": 14 + "Count": 6 } ], "results": [ @@ -40789,9 +40789,9 @@ "Producer": "GET /buckets", "IsPrimary": false, "FromExample": false, - "WireName": null, - "IsCandidate": false, - "Gate": "SelectorUnresolved", + "WireName": "bucket_names", + "IsCandidate": true, + "Gate": "Candidate", "ValueFromPipeline": false, "Outcome": "Unbindable", "Evidence": "The input object cannot be bound to any parameters for the command either because the command does not take pipeline input or the input and its properties do not match any of the parameters that take pipeline input.", @@ -40855,9 +40855,9 @@ "Producer": "GET /buckets/audit-filters", "IsPrimary": true, "FromExample": false, - "WireName": null, - "IsCandidate": false, - "Gate": "SelectorUnresolved", + "WireName": "bucket_names", + "IsCandidate": true, + "Gate": "Candidate", "ValueFromPipeline": false, "Outcome": "Unbindable", "Evidence": "The input object cannot be bound to any parameters for the command either because the command does not take pipeline input or the input and its properties do not match any of the parameters that take pipeline input.", @@ -40889,9 +40889,9 @@ "Producer": "GET /buckets/bucket-access-policies", "IsPrimary": false, "FromExample": false, - "WireName": null, - "IsCandidate": false, - "Gate": "SelectorUnresolved", + "WireName": "bucket_names", + "IsCandidate": true, + "Gate": "Candidate", "ValueFromPipeline": false, "Outcome": "Unbindable", "Evidence": "The input object cannot be bound to any parameters for the command either because the command does not take pipeline input or the input and its properties do not match any of the parameters that take pipeline input.", @@ -40933,9 +40933,9 @@ "Producer": "GET /buckets/bucket-access-policies/rules", "IsPrimary": false, "FromExample": false, - "WireName": null, - "IsCandidate": false, - "Gate": "SelectorUnresolved", + "WireName": "bucket_names", + "IsCandidate": true, + "Gate": "Candidate", "ValueFromPipeline": false, "Outcome": "Unbindable", "Evidence": "The input object cannot be bound to any parameters for the command either because the command does not take pipeline input or the input and its properties do not match any of the parameters that take pipeline input.", @@ -40971,9 +40971,9 @@ "Producer": "GET /buckets/cross-origin-resource-sharing-policies", "IsPrimary": false, "FromExample": false, - "WireName": null, - "IsCandidate": false, - "Gate": "SelectorUnresolved", + "WireName": "bucket_names", + "IsCandidate": true, + "Gate": "Candidate", "ValueFromPipeline": false, "Outcome": "Unbindable", "Evidence": "The input object cannot be bound to any parameters for the command either because the command does not take pipeline input or the input and its properties do not match any of the parameters that take pipeline input.", @@ -41015,9 +41015,9 @@ "Producer": "GET /buckets/cross-origin-resource-sharing-policies/rules", "IsPrimary": false, "FromExample": false, - "WireName": null, - "IsCandidate": false, - "Gate": "SelectorUnresolved", + "WireName": "bucket_names", + "IsCandidate": true, + "Gate": "Candidate", "ValueFromPipeline": false, "Outcome": "Unbindable", "Evidence": "The input object cannot be bound to any parameters for the command either because the command does not take pipeline input or the input and its properties do not match any of the parameters that take pipeline input.", @@ -41051,9 +41051,9 @@ "Producer": "GET /buckets/performance", "IsPrimary": false, "FromExample": false, - "WireName": null, - "IsCandidate": false, - "Gate": "SelectorUnresolved", + "WireName": "bucket_names", + "IsCandidate": true, + "Gate": "Candidate", "ValueFromPipeline": false, "Outcome": "Unbindable", "Evidence": "The input object cannot be bound to any parameters for the command either because the command does not take pipeline input or the input and its properties do not match any of the parameters that take pipeline input.", @@ -41107,9 +41107,9 @@ "Producer": "GET /buckets/s3-specific-performance", "IsPrimary": false, "FromExample": false, - "WireName": null, - "IsCandidate": false, - "Gate": "SelectorUnresolved", + "WireName": "bucket_names", + "IsCandidate": true, + "Gate": "Candidate", "ValueFromPipeline": false, "Outcome": "Unbindable", "Evidence": "The input object cannot be bound to any parameters for the command either because the command does not take pipeline input or the input and its properties do not match any of the parameters that take pipeline input.", diff --git a/Reports/PfbPipelineSelectorMap.md b/Reports/PfbPipelineSelectorMap.md index 29665917..52a4e2ae 100644 --- a/Reports/PfbPipelineSelectorMap.md +++ b/Reports/PfbPipelineSelectorMap.md @@ -11,12 +11,12 @@ no request leaves the machine, and nothing here is inferred from pattern-matchin | Metric | Value | |---|---:| | `probePairs` | 1247 | -| `evaluatedPairs` | 1233 | -| `candidatePairs` | 647 | -| `candidateRate` | 0.5247 | +| `evaluatedPairs` | 1241 | +| `candidatePairs` | 655 | +| `candidateRate` | 0.5278 | | `findings` | 266 | | `findingPairs` | 102 | -| `confirmationRate` | 0.4111 | +| `confirmationRate` | 0.4061 | | `controlLeakage` | 0 | | `assistedRows` | 212 | diff --git a/Tests/Fixtures/PfbSelectorWaivers.psd1 b/Tests/Fixtures/PfbSelectorWaivers.psd1 index 1b8da489..0c75e2ea 100644 --- a/Tests/Fixtures/PfbSelectorWaivers.psd1 +++ b/Tests/Fixtures/PfbSelectorWaivers.psd1 @@ -1,5 +1,6 @@ <# - Accepted pipeline-selector findings for issue #90 -- the debt register Rail A enforces. + Accepted pipeline-selector findings -- the debt register Rail A enforces. Found by the #90 + audit; tracked for fix in #152 / #153 / #123 (see the Issue field note below). Rail A (Tests/PfbPipelineSelectorRail.Tests.ps1) re-probes every pair in Reports/PfbPipelineSelectorMap.json and fails on any Coerced or WrongScalar selector not @@ -15,7 +16,7 @@ 264 rows / 101 pairs -> 266 / 102 at issue #141, and the added pair is NOT new module debt. #141 changed no cmdlet; it taught the wire-name resolver assignment shapes it had been skipping, so Get-PfbUserGroupQuotaPolicy|Name entered the probe candidate set for the first - time and reproduced a defect that was always there. Candidates moved 629 -> 647 while probe + time and reproduced a defect that was always there. Candidates moved 629 -> 655 while probe pairs stayed at 1247, which is the measurement that distinguishes "the rail can see more" from "the module does more". @@ -28,13 +29,25 @@ Family = only against another endpoint in the same resource family), Issue, Producers (how many endpoints reproduce it), Why. - Issue is #90 for every entry originating in that audit, because the fix issue does not exist - yet -- #90 delivers the audit and this rail, and the split issue is filed after the PR - exists. Re-pointing these at that issue is a follow-up commit. The one #141 entry follows - the same convention for the same reason: it names the issue that REVEALED the defect, not a - fix issue, and it is owed the same re-pointing. It is called out here rather than left for a - reader to notice, because "Issue is #90 for every entry" was true until #141 and a stale - absolute like that is how a register stops being read. + Issue now names a FIX issue, not the audit that found the defect. Every entry pointed at + #90 (and the one at #141) until issue #141's PR; #90 is closed, and pointing a live register + at a closed issue is how it stops being read. The 102 pairs split by root cause, measured + from Reports/PfbPipelineSelectorMap.json rather than assigned by hand: + + #152 (64) -- the join-item class. A family endpoint returns join records whose members are + objects (member, policy, usually context) and which carry no name, so a name-shaped + selector cannot bind by property name. One coherent cause; plausibly one fix for all. + #153 (37) -- items with no name for unrelated reasons: alert/hardware records keyed on + component_name, @{group=; member=} membership items, realm/object-store associations. + Deliberately NOT merged into #152 -- there is no shared structure to key a fix on. + #123 (1) -- Get-PfbUserGroupQuotaPolicyRule|PolicyName. Structurally a member of the #152 + class, but it already has its own issue and a different blocker: the array honours a + policy_names key the published OpenAPI omits, so the fix is upstream, not here. + + 64 + 37 + 1 = 102, which is every entry; the split is total and has no residue. #152 counts + that pair in its 65-pair class because the class is defined by root cause, while the waiver + points at #123 because that is where the fix is tracked. Both numbers are right; they answer + different questions. Clusters below are the audit report's root-cause clusters (issue-90-audit-report.md, 3.3), not cosmetic grouping: each cluster is one fix, not N. @@ -48,7 +61,7 @@ # policy_name/member_name/role_name string, so a name-shaped selector can never bind by # property name. One API design decision repeated across roughly a dozen endpoint # families: fix it as one change, not sixteen. - @{ Cmdlet = 'Get-PfbUserGroupQuotaPolicyRule'; Parameter = 'PolicyName'; Scope = 'Primary'; Issue = '#90'; Producers = 4 + @{ Cmdlet = 'Get-PfbUserGroupQuotaPolicyRule'; Parameter = 'PolicyName'; Scope = 'Primary'; Issue = '#123'; Producers = 4 Why = 'GET /user-group-quota-policies/rules returns policy as a nested object, never a flat policy_name, so policy_names receives the stringified rule item. Waived rather than fixed because the array honours a policy_names key the published OpenAPI omits, and the published spec governs.' } # === Cluster 2 -- sub-resources that have no name at all (0 pairs still waived here; the @@ -81,212 +94,212 @@ # -- every pipeline chain this module advertises works. "Family endpoints coerce" is a # count of PRODUCING ENDPOINTS for that pair; the named one is an example, and the # mechanism described belongs to it. - @{ Cmdlet = 'Get-PfbActiveDirectory'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 1 + @{ Cmdlet = 'Get-PfbActiveDirectory'; Parameter = 'Name'; Scope = 'Family'; Issue = '#153'; Producers = 1 Why = 'Primary producer binds -Name correctly; one family endpoint coerces, GET /active-directory/test -- the /test endpoint returns a test-result item, not a resource, so it has no name.' } - @{ Cmdlet = 'Get-PfbAdmin'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 5 + @{ Cmdlet = 'Get-PfbAdmin'; Parameter = 'Name'; Scope = 'Family'; Issue = '#152'; Producers = 5 Why = 'Primary producer binds -Name correctly; 5 family endpoints coerce, e.g. GET /admins/api-tokens -- its items carry no name (admin, api_token, context).' } - @{ Cmdlet = 'Get-PfbAdminCache'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 5 + @{ Cmdlet = 'Get-PfbAdminCache'; Parameter = 'Name'; Scope = 'Family'; Issue = '#152'; Producers = 5 Why = 'Primary producer binds -Name correctly; 5 family endpoints coerce, e.g. GET /admins/api-tokens -- its items carry no name (admin, api_token, context).' } - @{ Cmdlet = 'Get-PfbAlertWatcher'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 1 + @{ Cmdlet = 'Get-PfbAlertWatcher'; Parameter = 'Name'; Scope = 'Family'; Issue = '#153'; Producers = 1 Why = 'Primary producer binds -Name correctly; one family endpoint coerces, GET /alert-watchers/test -- the /test endpoint returns a test-result item, not a resource, so it has no name.' } - @{ Cmdlet = 'Get-PfbAuditFileSystemPolicy'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 1 + @{ Cmdlet = 'Get-PfbAuditFileSystemPolicy'; Parameter = 'Name'; Scope = 'Family'; Issue = '#152'; Producers = 1 Why = 'Primary producer binds -Name correctly; one family endpoint coerces, GET /audit-file-systems-policies/members -- that join item returns its endpoints as objects (context, member, policy) and carries no name.' } - @{ Cmdlet = 'Get-PfbAuditObjectStorePolicy'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 1 + @{ Cmdlet = 'Get-PfbAuditObjectStorePolicy'; Parameter = 'Name'; Scope = 'Family'; Issue = '#152'; Producers = 1 Why = 'Primary producer binds -Name correctly; one family endpoint coerces, GET /audit-object-store-policies/members -- that join item returns its endpoints as objects (context, member, policy) and carries no name.' } - @{ Cmdlet = 'Get-PfbCertificate'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 1 + @{ Cmdlet = 'Get-PfbCertificate'; Parameter = 'Name'; Scope = 'Family'; Issue = '#153'; Producers = 1 Why = 'Primary producer binds -Name correctly; one family endpoint coerces, GET /certificates/certificate-groups -- that join item returns its endpoints as objects (group, member) and carries no name.' } - @{ Cmdlet = 'Get-PfbCertificateGroup'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 1 + @{ Cmdlet = 'Get-PfbCertificateGroup'; Parameter = 'Name'; Scope = 'Family'; Issue = '#153'; Producers = 1 Why = 'Primary producer binds -Name correctly; one family endpoint coerces, GET /certificate-groups/certificates -- that join item returns its endpoints as objects (group, member) and carries no name.' } - @{ Cmdlet = 'Get-PfbCertificateGroupUse'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 1 + @{ Cmdlet = 'Get-PfbCertificateGroupUse'; Parameter = 'Name'; Scope = 'Family'; Issue = '#153'; Producers = 1 Why = 'Primary producer binds -Name correctly; one family endpoint coerces, GET /certificate-groups/certificates -- that join item returns its endpoints as objects (group, member) and carries no name.' } - @{ Cmdlet = 'Get-PfbCertificateUse'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 1 + @{ Cmdlet = 'Get-PfbCertificateUse'; Parameter = 'Name'; Scope = 'Family'; Issue = '#153'; Producers = 1 Why = 'Primary producer binds -Name correctly; one family endpoint coerces, GET /certificates/certificate-groups -- that join item returns its endpoints as objects (group, member) and carries no name.' } - @{ Cmdlet = 'Get-PfbDataEvictionPolicy'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 2 + @{ Cmdlet = 'Get-PfbDataEvictionPolicy'; Parameter = 'Name'; Scope = 'Family'; Issue = '#152'; Producers = 2 Why = 'Primary producer binds -Name correctly; 2 family endpoints coerce, e.g. GET /data-eviction-policies/file-systems -- that join item returns its endpoints as objects (context, member, policy) and carries no name.' } - @{ Cmdlet = 'Get-PfbDirectoryService'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 4 + @{ Cmdlet = 'Get-PfbDirectoryService'; Parameter = 'Name'; Scope = 'Family'; Issue = '#152'; Producers = 4 Why = 'Primary producer binds -Name correctly; 4 family endpoints coerce, e.g. GET /directory-services/local/groups/members -- its items carry no name (context, group, group_gid, is_primary_group, local_directory_service, member, member_id, realms, server).' } - @{ Cmdlet = 'Get-PfbDirectoryServiceRole'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 4 + @{ Cmdlet = 'Get-PfbDirectoryServiceRole'; Parameter = 'Name'; Scope = 'Family'; Issue = '#152'; Producers = 4 Why = 'Primary producer binds -Name correctly; 4 family endpoints coerce, e.g. GET /directory-services/local/groups/members -- its items carry no name (context, group, group_gid, is_primary_group, local_directory_service, member, member_id, realms, server).' } - @{ Cmdlet = 'Get-PfbFileLock'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 9 + @{ Cmdlet = 'Get-PfbFileLock'; Parameter = 'Name'; Scope = 'Family'; Issue = '#152'; Producers = 9 Why = 'Primary producer binds -Name correctly; 9 family endpoints coerce, e.g. GET /file-systems/audit-policies -- that join item returns its endpoints as objects (context, member, policy) and carries no name.' } - @{ Cmdlet = 'Get-PfbFileLockClient'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 9 + @{ Cmdlet = 'Get-PfbFileLockClient'; Parameter = 'Name'; Scope = 'Family'; Issue = '#152'; Producers = 9 Why = 'Primary producer binds -Name correctly; 9 family endpoints coerce, e.g. GET /file-systems/audit-policies -- that join item returns its endpoints as objects (context, member, policy) and carries no name.' } - @{ Cmdlet = 'Get-PfbFileSystem'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 9 + @{ Cmdlet = 'Get-PfbFileSystem'; Parameter = 'Name'; Scope = 'Family'; Issue = '#152'; Producers = 9 Why = 'Primary producer binds -Name correctly; 9 family endpoints coerce, e.g. GET /file-systems/audit-policies -- that join item returns its endpoints as objects (context, member, policy) and carries no name.' } - @{ Cmdlet = 'Get-PfbFileSystemGroupPerformance'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 9 + @{ Cmdlet = 'Get-PfbFileSystemGroupPerformance'; Parameter = 'Name'; Scope = 'Family'; Issue = '#152'; Producers = 9 Why = 'Primary producer binds -Name correctly; 9 family endpoints coerce, e.g. GET /file-systems/audit-policies -- that join item returns its endpoints as objects (context, member, policy) and carries no name.' } - @{ Cmdlet = 'Get-PfbFileSystemSession'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 9 + @{ Cmdlet = 'Get-PfbFileSystemSession'; Parameter = 'Name'; Scope = 'Family'; Issue = '#152'; Producers = 9 Why = 'Primary producer binds -Name correctly; 9 family endpoints coerce, e.g. GET /file-systems/audit-policies -- that join item returns its endpoints as objects (context, member, policy) and carries no name.' } - @{ Cmdlet = 'Get-PfbFileSystemSnapshot'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 1 + @{ Cmdlet = 'Get-PfbFileSystemSnapshot'; Parameter = 'Name'; Scope = 'Family'; Issue = '#152'; Producers = 1 Why = 'Primary producer binds -Name correctly; one family endpoint coerces, GET /file-system-snapshots/policies -- that join item returns its endpoints as objects (context, member, policy) and carries no name.' } - @{ Cmdlet = 'Get-PfbFileSystemSnapshotTransfer'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 1 + @{ Cmdlet = 'Get-PfbFileSystemSnapshotTransfer'; Parameter = 'Name'; Scope = 'Family'; Issue = '#152'; Producers = 1 Why = 'Primary producer binds -Name correctly; one family endpoint coerces, GET /file-system-snapshots/policies -- that join item returns its endpoints as objects (context, member, policy) and carries no name.' } - @{ Cmdlet = 'Get-PfbFileSystemStorageClass'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 9 + @{ Cmdlet = 'Get-PfbFileSystemStorageClass'; Parameter = 'Name'; Scope = 'Family'; Issue = '#152'; Producers = 9 Why = 'Primary producer binds -Name correctly; 9 family endpoints coerce, e.g. GET /file-systems/audit-policies -- that join item returns its endpoints as objects (context, member, policy) and carries no name.' } - @{ Cmdlet = 'Get-PfbFileSystemUserPerformance'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 9 + @{ Cmdlet = 'Get-PfbFileSystemUserPerformance'; Parameter = 'Name'; Scope = 'Family'; Issue = '#152'; Producers = 9 Why = 'Primary producer binds -Name correctly; 9 family endpoints coerce, e.g. GET /file-systems/audit-policies -- that join item returns its endpoints as objects (context, member, policy) and carries no name.' } - @{ Cmdlet = 'Get-PfbFleet'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 2 + @{ Cmdlet = 'Get-PfbFleet'; Parameter = 'Name'; Scope = 'Family'; Issue = '#153'; Producers = 2 Why = 'Primary producer binds -Name correctly; 2 family endpoints coerce, e.g. GET /fleets/fleet-key -- its items carry no name (created, expires, fleet_key).' } - @{ Cmdlet = 'Get-PfbKmip'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 1 + @{ Cmdlet = 'Get-PfbKmip'; Parameter = 'Name'; Scope = 'Family'; Issue = '#153'; Producers = 1 Why = 'Primary producer binds -Name correctly; one family endpoint coerces, GET /kmip/test -- the /test endpoint returns a test-result item, not a resource, so it has no name.' } - @{ Cmdlet = 'Get-PfbLegalHold'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 1 + @{ Cmdlet = 'Get-PfbLegalHold'; Parameter = 'Name'; Scope = 'Family'; Issue = '#153'; Producers = 1 Why = 'Primary producer binds -Name correctly; one family endpoint coerces, GET /legal-holds/held-entities -- its items carry no name (file_system, legal_hold, path, status).' } - @{ Cmdlet = 'Get-PfbLocalDirectoryService'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 4 + @{ Cmdlet = 'Get-PfbLocalDirectoryService'; Parameter = 'Name'; Scope = 'Family'; Issue = '#152'; Producers = 4 Why = 'Primary producer binds -Name correctly; 4 family endpoints coerce, e.g. GET /directory-services/local/groups/members -- its items carry no name (context, group, group_gid, is_primary_group, local_directory_service, member, member_id, realms, server).' } - @{ Cmdlet = 'Get-PfbLocalGroup'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 4 + @{ Cmdlet = 'Get-PfbLocalGroup'; Parameter = 'Name'; Scope = 'Family'; Issue = '#152'; Producers = 4 Why = 'Primary producer binds -Name correctly; 4 family endpoints coerce, e.g. GET /directory-services/local/groups/members -- its items carry no name (context, group, group_gid, is_primary_group, local_directory_service, member, member_id, realms, server).' } - @{ Cmdlet = 'Get-PfbManagementAccessPolicy'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 3 + @{ Cmdlet = 'Get-PfbManagementAccessPolicy'; Parameter = 'Name'; Scope = 'Family'; Issue = '#152'; Producers = 3 Why = 'Primary producer binds -Name correctly; 3 family endpoints coerce, e.g. GET /management-access-policies/admins -- that join item returns its endpoints as objects (context, member, policy) and carries no name.' } - @{ Cmdlet = 'Get-PfbNetworkAccessPolicy'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 1 + @{ Cmdlet = 'Get-PfbNetworkAccessPolicy'; Parameter = 'Name'; Scope = 'Family'; Issue = '#152'; Producers = 1 Why = 'Primary producer binds -Name correctly; one family endpoint coerces, GET /network-access-policies/members -- that join item returns its endpoints as objects (member, policy) and carries no name.' } - @{ Cmdlet = 'Get-PfbNetworkInterface'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 5 + @{ Cmdlet = 'Get-PfbNetworkInterface'; Parameter = 'Name'; Scope = 'Family'; Issue = '#152'; Producers = 5 Why = 'Primary producer binds -Name correctly; 5 family endpoints coerce, e.g. GET /network-interfaces/neighbors -- its items carry no name (initial_ttl_in_sec, local_port, neighbor_chassis, neighbor_port).' } - @{ Cmdlet = 'Get-PfbNetworkInterfaceConnector'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 5 + @{ Cmdlet = 'Get-PfbNetworkInterfaceConnector'; Parameter = 'Name'; Scope = 'Family'; Issue = '#152'; Producers = 5 Why = 'Primary producer binds -Name correctly; 5 family endpoints coerce, e.g. GET /network-interfaces/neighbors -- its items carry no name (initial_ttl_in_sec, local_port, neighbor_chassis, neighbor_port).' } - @{ Cmdlet = 'Get-PfbNetworkInterfaceConnectorPerformance'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 5 + @{ Cmdlet = 'Get-PfbNetworkInterfaceConnectorPerformance'; Parameter = 'Name'; Scope = 'Family'; Issue = '#152'; Producers = 5 Why = 'Primary producer binds -Name correctly; 5 family endpoints coerce, e.g. GET /network-interfaces/neighbors -- its items carry no name (initial_ttl_in_sec, local_port, neighbor_chassis, neighbor_port).' } - @{ Cmdlet = 'Get-PfbNetworkInterfaceConnectorSettings'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 5 + @{ Cmdlet = 'Get-PfbNetworkInterfaceConnectorSettings'; Parameter = 'Name'; Scope = 'Family'; Issue = '#152'; Producers = 5 Why = 'Primary producer binds -Name correctly; 5 family endpoints coerce, e.g. GET /network-interfaces/neighbors -- its items carry no name (initial_ttl_in_sec, local_port, neighbor_chassis, neighbor_port).' } - @{ Cmdlet = 'Get-PfbNodeGroup'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 1 + @{ Cmdlet = 'Get-PfbNodeGroup'; Parameter = 'Name'; Scope = 'Family'; Issue = '#153'; Producers = 1 Why = 'Primary producer binds -Name correctly; one family endpoint coerces, GET /node-groups/nodes -- that join item returns its endpoints as objects (group, member) and carries no name.' } - @{ Cmdlet = 'Get-PfbNodeGroupUse'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 1 + @{ Cmdlet = 'Get-PfbNodeGroupUse'; Parameter = 'Name'; Scope = 'Family'; Issue = '#153'; Producers = 1 Why = 'Primary producer binds -Name correctly; one family endpoint coerces, GET /node-groups/nodes -- that join item returns its endpoints as objects (group, member) and carries no name.' } - @{ Cmdlet = 'Get-PfbObjectStoreAccessPolicy'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 2 + @{ Cmdlet = 'Get-PfbObjectStoreAccessPolicy'; Parameter = 'Name'; Scope = 'Family'; Issue = '#152'; Producers = 2 Why = 'Primary producer binds -Name correctly; 2 family endpoints coerce, e.g. GET /object-store-access-policies/object-store-roles -- that join item returns its endpoints as objects (context, member, policy) and carries no name.' } - @{ Cmdlet = 'Get-PfbObjectStoreRole'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 1 + @{ Cmdlet = 'Get-PfbObjectStoreRole'; Parameter = 'Name'; Scope = 'Family'; Issue = '#152'; Producers = 1 Why = 'Primary producer binds -Name correctly; one family endpoint coerces, GET /object-store-roles/object-store-access-policies -- that join item returns its endpoints as objects (context, member, policy) and carries no name.' } - @{ Cmdlet = 'Get-PfbObjectStoreUser'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 1 + @{ Cmdlet = 'Get-PfbObjectStoreUser'; Parameter = 'Name'; Scope = 'Family'; Issue = '#152'; Producers = 1 Why = 'Primary producer binds -Name correctly; one family endpoint coerces, GET /object-store-users/object-store-access-policies -- that join item returns its endpoints as objects (context, member, policy) and carries no name.' } - @{ Cmdlet = 'Get-PfbOidcIdp'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 1 + @{ Cmdlet = 'Get-PfbOidcIdp'; Parameter = 'Name'; Scope = 'Family'; Issue = '#153'; Producers = 1 Why = 'Primary producer binds -Name correctly; one family endpoint coerces, GET /sso/saml2/idps/test -- the /test endpoint returns a test-result item, not a resource, so it has no name.' } - @{ Cmdlet = 'Get-PfbPolicy'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 4 + @{ Cmdlet = 'Get-PfbPolicy'; Parameter = 'Name'; Scope = 'Family'; Issue = '#152'; Producers = 4 Why = 'Primary producer binds -Name correctly; 4 family endpoints coerce, e.g. GET /policies/file-system-replica-links -- that join item returns its endpoints as objects (context, link, member, policy) and carries no name.' } - @{ Cmdlet = 'Get-PfbPolicyAll'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 1 + @{ Cmdlet = 'Get-PfbPolicyAll'; Parameter = 'Name'; Scope = 'Family'; Issue = '#152'; Producers = 1 Why = 'Primary producer binds -Name correctly; one family endpoint coerces, GET /policies-all/members -- that join item returns its endpoints as objects (context, link, member, policy) and carries no name.' } - @{ Cmdlet = 'Get-PfbQosPolicy'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 3 + @{ Cmdlet = 'Get-PfbQosPolicy'; Parameter = 'Name'; Scope = 'Family'; Issue = '#152'; Producers = 3 Why = 'Primary producer binds -Name correctly; 3 family endpoints coerce, e.g. GET /qos-policies/buckets -- that join item returns its endpoints as objects (context, member, policy) and carries no name.' } - @{ Cmdlet = 'Get-PfbRealm'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 1 + @{ Cmdlet = 'Get-PfbRealm'; Parameter = 'Name'; Scope = 'Family'; Issue = '#153'; Producers = 1 Why = 'Primary producer binds -Name correctly; one family endpoint coerces, GET /realms/defaults -- its items carry no name (context, object_store, realm).' } - @{ Cmdlet = 'Get-PfbRealmSpace'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 1 + @{ Cmdlet = 'Get-PfbRealmSpace'; Parameter = 'Name'; Scope = 'Family'; Issue = '#153'; Producers = 1 Why = 'Primary producer binds -Name correctly; one family endpoint coerces, GET /realms/defaults -- its items carry no name (context, object_store, realm).' } - @{ Cmdlet = 'Get-PfbRealmStorageClass'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 1 + @{ Cmdlet = 'Get-PfbRealmStorageClass'; Parameter = 'Name'; Scope = 'Family'; Issue = '#153'; Producers = 1 Why = 'Primary producer binds -Name correctly; one family endpoint coerces, GET /realms/defaults -- its items carry no name (context, object_store, realm).' } - @{ Cmdlet = 'Get-PfbResiliencyGroup'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 1 + @{ Cmdlet = 'Get-PfbResiliencyGroup'; Parameter = 'Name'; Scope = 'Family'; Issue = '#153'; Producers = 1 Why = 'Primary producer binds -Name correctly; one family endpoint coerces, GET /resiliency-groups/members -- that join item returns its endpoints as objects (group, member) and carries no name.' } - @{ Cmdlet = 'Get-PfbSaml2Idp'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 1 + @{ Cmdlet = 'Get-PfbSaml2Idp'; Parameter = 'Name'; Scope = 'Family'; Issue = '#153'; Producers = 1 Why = 'Primary producer binds -Name correctly; one family endpoint coerces, GET /sso/saml2/idps/test -- the /test endpoint returns a test-result item, not a resource, so it has no name.' } - @{ Cmdlet = 'Get-PfbSnmpManager'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 1 + @{ Cmdlet = 'Get-PfbSnmpManager'; Parameter = 'Name'; Scope = 'Family'; Issue = '#153'; Producers = 1 Why = 'Primary producer binds -Name correctly; one family endpoint coerces, GET /snmp-managers/test -- the /test endpoint returns a test-result item, not a resource, so it has no name.' } - @{ Cmdlet = 'Get-PfbSshCaPolicy'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 3 + @{ Cmdlet = 'Get-PfbSshCaPolicy'; Parameter = 'Name'; Scope = 'Family'; Issue = '#152'; Producers = 3 Why = 'Primary producer binds -Name correctly; 3 family endpoints coerce, e.g. GET /ssh-certificate-authority-policies/admins -- that join item returns its endpoints as objects (context, member, policy) and carries no name.' } - @{ Cmdlet = 'Get-PfbStorageClassTieringPolicy'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 1 + @{ Cmdlet = 'Get-PfbStorageClassTieringPolicy'; Parameter = 'Name'; Scope = 'Family'; Issue = '#152'; Producers = 1 Why = 'Primary producer binds -Name correctly; one family endpoint coerces, GET /storage-class-tiering-policies/members -- that join item returns its endpoints as objects (member, policy) and carries no name.' } - @{ Cmdlet = 'Get-PfbSupport'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 2 + @{ Cmdlet = 'Get-PfbSupport'; Parameter = 'Name'; Scope = 'Family'; Issue = '#153'; Producers = 2 Why = 'Primary producer binds -Name correctly; 2 family endpoints coerce, e.g. GET /support/system-manifest -- its items carry no name (context, system-manifest).' } - @{ Cmdlet = 'Get-PfbSupportDiagnostics'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 1 + @{ Cmdlet = 'Get-PfbSupportDiagnostics'; Parameter = 'Name'; Scope = 'Family'; Issue = '#153'; Producers = 1 Why = 'Primary producer binds -Name correctly; one family endpoint coerces, GET /support-diagnostics/settings -- its items carry no name (last_updated, version).' } - @{ Cmdlet = 'Get-PfbSupportDiagnosticsDetails'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 1 + @{ Cmdlet = 'Get-PfbSupportDiagnosticsDetails'; Parameter = 'Name'; Scope = 'Family'; Issue = '#153'; Producers = 1 Why = 'Primary producer binds -Name correctly; one family endpoint coerces, GET /support-diagnostics/settings -- its items carry no name (last_updated, version).' } - @{ Cmdlet = 'Get-PfbSyslogServer'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 1 + @{ Cmdlet = 'Get-PfbSyslogServer'; Parameter = 'Name'; Scope = 'Family'; Issue = '#153'; Producers = 1 Why = 'Primary producer binds -Name correctly; one family endpoint coerces, GET /syslog-servers/test -- the /test endpoint returns a test-result item, not a resource, so it has no name.' } - @{ Cmdlet = 'Get-PfbTlsPolicy'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 2 + @{ Cmdlet = 'Get-PfbTlsPolicy'; Parameter = 'Name'; Scope = 'Family'; Issue = '#152'; Producers = 2 Why = 'Primary producer binds -Name correctly; 2 family endpoints coerce, e.g. GET /tls-policies/members -- that join item returns its endpoints as objects (member, policy) and carries no name.' } # NEW at issue #141, and new only to the RAIL -- not to the module. -Name was invisible # here until #141 taught the wire-name resolver the assignment shape that writes it # (inventory row moved TypedUnresolved -> Typed|names|Query|GET|user-group-quota-policies), # so the pair entered the probe candidate set and immediately reproduced the same # nested-join-item defect as the two entries above it. Nothing about the cmdlet changed. - @{ Cmdlet = 'Get-PfbUserGroupQuotaPolicy'; Parameter = 'Name'; Scope = 'Family'; Issue = '#141'; Producers = 2 + @{ Cmdlet = 'Get-PfbUserGroupQuotaPolicy'; Parameter = 'Name'; Scope = 'Family'; Issue = '#152'; Producers = 2 Why = 'Primary producer binds -Name correctly; 2 family endpoints coerce, GET /user-group-quota-policies/file-systems and /members -- both join items return their endpoints as objects (context, member, policy) and carry no name, so a name-shaped selector cannot bind by property name and is stringified to names=@{context=; member=; policy=}. Same root cause as the Get-PfbTlsPolicy and Get-PfbWormPolicy entries: one API design decision, one fix.' } - @{ Cmdlet = 'Get-PfbWorkload'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 1 + @{ Cmdlet = 'Get-PfbWorkload'; Parameter = 'Name'; Scope = 'Family'; Issue = '#153'; Producers = 1 Why = 'Primary producer binds -Name correctly; one family endpoint coerces, GET /workloads/tags -- its items carry no name (context, copyable, key, namespace, resource, value).' } - @{ Cmdlet = 'Get-PfbWormPolicy'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 1 + @{ Cmdlet = 'Get-PfbWormPolicy'; Parameter = 'Name'; Scope = 'Family'; Issue = '#152'; Producers = 1 Why = 'Primary producer binds -Name correctly; one family endpoint coerces, GET /worm-data-policies/members -- that join item returns its endpoints as objects (context, member, policy) and carries no name.' } - @{ Cmdlet = 'Remove-PfbActiveDirectory'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 1 + @{ Cmdlet = 'Remove-PfbActiveDirectory'; Parameter = 'Name'; Scope = 'Family'; Issue = '#153'; Producers = 1 Why = 'Primary producer binds -Name correctly; one family endpoint coerces, GET /active-directory/test -- the /test endpoint returns a test-result item, not a resource, so it has no name.' } - @{ Cmdlet = 'Remove-PfbAdminCache'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 5 + @{ Cmdlet = 'Remove-PfbAdminCache'; Parameter = 'Name'; Scope = 'Family'; Issue = '#152'; Producers = 5 Why = 'Primary producer binds -Name correctly; 5 family endpoints coerce, e.g. GET /admins/api-tokens -- its items carry no name (admin, api_token, context).' } - @{ Cmdlet = 'Remove-PfbAlertWatcher'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 1 + @{ Cmdlet = 'Remove-PfbAlertWatcher'; Parameter = 'Name'; Scope = 'Family'; Issue = '#153'; Producers = 1 Why = 'Primary producer binds -Name correctly; one family endpoint coerces, GET /alert-watchers/test -- the /test endpoint returns a test-result item, not a resource, so it has no name.' } - @{ Cmdlet = 'Remove-PfbAuditFileSystemPolicy'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 1 + @{ Cmdlet = 'Remove-PfbAuditFileSystemPolicy'; Parameter = 'Name'; Scope = 'Family'; Issue = '#152'; Producers = 1 Why = 'Primary producer binds -Name correctly; one family endpoint coerces, GET /audit-file-systems-policies/members -- that join item returns its endpoints as objects (context, member, policy) and carries no name.' } - @{ Cmdlet = 'Remove-PfbAuditObjectStorePolicy'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 1 + @{ Cmdlet = 'Remove-PfbAuditObjectStorePolicy'; Parameter = 'Name'; Scope = 'Family'; Issue = '#152'; Producers = 1 Why = 'Primary producer binds -Name correctly; one family endpoint coerces, GET /audit-object-store-policies/members -- that join item returns its endpoints as objects (context, member, policy) and carries no name.' } - @{ Cmdlet = 'Remove-PfbCertificate'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 1 + @{ Cmdlet = 'Remove-PfbCertificate'; Parameter = 'Name'; Scope = 'Family'; Issue = '#153'; Producers = 1 Why = 'Primary producer binds -Name correctly; one family endpoint coerces, GET /certificates/certificate-groups -- that join item returns its endpoints as objects (group, member) and carries no name.' } - @{ Cmdlet = 'Remove-PfbCertificateGroup'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 1 + @{ Cmdlet = 'Remove-PfbCertificateGroup'; Parameter = 'Name'; Scope = 'Family'; Issue = '#153'; Producers = 1 Why = 'Primary producer binds -Name correctly; one family endpoint coerces, GET /certificate-groups/certificates -- that join item returns its endpoints as objects (group, member) and carries no name.' } - @{ Cmdlet = 'Remove-PfbDataEvictionPolicy'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 2 + @{ Cmdlet = 'Remove-PfbDataEvictionPolicy'; Parameter = 'Name'; Scope = 'Family'; Issue = '#152'; Producers = 2 Why = 'Primary producer binds -Name correctly; 2 family endpoints coerce, e.g. GET /data-eviction-policies/file-systems -- that join item returns its endpoints as objects (context, member, policy) and carries no name.' } - @{ Cmdlet = 'Remove-PfbDirectoryServiceRole'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 4 + @{ Cmdlet = 'Remove-PfbDirectoryServiceRole'; Parameter = 'Name'; Scope = 'Family'; Issue = '#152'; Producers = 4 Why = 'Primary producer binds -Name correctly; 4 family endpoints coerce, e.g. GET /directory-services/local/groups/members -- its items carry no name (context, group, group_gid, is_primary_group, local_directory_service, member, member_id, realms, server).' } - @{ Cmdlet = 'Remove-PfbFileLock'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 9 + @{ Cmdlet = 'Remove-PfbFileLock'; Parameter = 'Name'; Scope = 'Family'; Issue = '#152'; Producers = 9 Why = 'Primary producer binds -Name correctly; 9 family endpoints coerce, e.g. GET /file-systems/audit-policies -- that join item returns its endpoints as objects (context, member, policy) and carries no name.' } - @{ Cmdlet = 'Remove-PfbFileSystem'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 9 + @{ Cmdlet = 'Remove-PfbFileSystem'; Parameter = 'Name'; Scope = 'Family'; Issue = '#152'; Producers = 9 Why = 'Primary producer binds -Name correctly; 9 family endpoints coerce, e.g. GET /file-systems/audit-policies -- that join item returns its endpoints as objects (context, member, policy) and carries no name.' } - @{ Cmdlet = 'Remove-PfbFileSystemSession'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 9 + @{ Cmdlet = 'Remove-PfbFileSystemSession'; Parameter = 'Name'; Scope = 'Family'; Issue = '#152'; Producers = 9 Why = 'Primary producer binds -Name correctly; 9 family endpoints coerce, e.g. GET /file-systems/audit-policies -- that join item returns its endpoints as objects (context, member, policy) and carries no name.' } - @{ Cmdlet = 'Remove-PfbFileSystemSnapshot'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 1 + @{ Cmdlet = 'Remove-PfbFileSystemSnapshot'; Parameter = 'Name'; Scope = 'Family'; Issue = '#152'; Producers = 1 Why = 'Primary producer binds -Name correctly; one family endpoint coerces, GET /file-system-snapshots/policies -- that join item returns its endpoints as objects (context, member, policy) and carries no name.' } - @{ Cmdlet = 'Remove-PfbFileSystemSnapshotTransfer'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 1 + @{ Cmdlet = 'Remove-PfbFileSystemSnapshotTransfer'; Parameter = 'Name'; Scope = 'Family'; Issue = '#152'; Producers = 1 Why = 'Primary producer binds -Name correctly; one family endpoint coerces, GET /file-system-snapshots/policies -- that join item returns its endpoints as objects (context, member, policy) and carries no name.' } - @{ Cmdlet = 'Remove-PfbFleet'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 2 + @{ Cmdlet = 'Remove-PfbFleet'; Parameter = 'Name'; Scope = 'Family'; Issue = '#153'; Producers = 2 Why = 'Primary producer binds -Name correctly; 2 family endpoints coerce, e.g. GET /fleets/fleet-key -- its items carry no name (created, expires, fleet_key).' } - @{ Cmdlet = 'Remove-PfbLegalHold'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 1 + @{ Cmdlet = 'Remove-PfbLegalHold'; Parameter = 'Name'; Scope = 'Family'; Issue = '#153'; Producers = 1 Why = 'Primary producer binds -Name correctly; one family endpoint coerces, GET /legal-holds/held-entities -- its items carry no name (file_system, legal_hold, path, status).' } - @{ Cmdlet = 'Remove-PfbLocalGroup'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 4 + @{ Cmdlet = 'Remove-PfbLocalGroup'; Parameter = 'Name'; Scope = 'Family'; Issue = '#152'; Producers = 4 Why = 'Primary producer binds -Name correctly; 4 family endpoints coerce, e.g. GET /directory-services/local/groups/members -- its items carry no name (context, group, group_gid, is_primary_group, local_directory_service, member, member_id, realms, server).' } - @{ Cmdlet = 'Remove-PfbManagementAccessPolicy'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 3 + @{ Cmdlet = 'Remove-PfbManagementAccessPolicy'; Parameter = 'Name'; Scope = 'Family'; Issue = '#152'; Producers = 3 Why = 'Primary producer binds -Name correctly; 3 family endpoints coerce, e.g. GET /management-access-policies/admins -- that join item returns its endpoints as objects (context, member, policy) and carries no name.' } - @{ Cmdlet = 'Remove-PfbNetworkAccessRule'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 1 + @{ Cmdlet = 'Remove-PfbNetworkAccessRule'; Parameter = 'Name'; Scope = 'Family'; Issue = '#152'; Producers = 1 Why = 'Primary producer binds -Name correctly; one family endpoint coerces, GET /network-access-policies/members -- that join item returns its endpoints as objects (member, policy) and carries no name.' } - @{ Cmdlet = 'Remove-PfbNetworkInterface'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 5 + @{ Cmdlet = 'Remove-PfbNetworkInterface'; Parameter = 'Name'; Scope = 'Family'; Issue = '#152'; Producers = 5 Why = 'Primary producer binds -Name correctly; 5 family endpoints coerce, e.g. GET /network-interfaces/neighbors -- its items carry no name (initial_ttl_in_sec, local_port, neighbor_chassis, neighbor_port).' } - @{ Cmdlet = 'Remove-PfbNodeGroup'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 1 + @{ Cmdlet = 'Remove-PfbNodeGroup'; Parameter = 'Name'; Scope = 'Family'; Issue = '#153'; Producers = 1 Why = 'Primary producer binds -Name correctly; one family endpoint coerces, GET /node-groups/nodes -- that join item returns its endpoints as objects (group, member) and carries no name.' } - @{ Cmdlet = 'Remove-PfbObjectStoreAccessPolicy'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 2 + @{ Cmdlet = 'Remove-PfbObjectStoreAccessPolicy'; Parameter = 'Name'; Scope = 'Family'; Issue = '#152'; Producers = 2 Why = 'Primary producer binds -Name correctly; 2 family endpoints coerce, e.g. GET /object-store-access-policies/object-store-roles -- that join item returns its endpoints as objects (context, member, policy) and carries no name.' } - @{ Cmdlet = 'Remove-PfbObjectStoreAccessPolicyRule'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 2 + @{ Cmdlet = 'Remove-PfbObjectStoreAccessPolicyRule'; Parameter = 'Name'; Scope = 'Family'; Issue = '#152'; Producers = 2 Why = 'Primary producer binds -Name correctly; 2 family endpoints coerce, e.g. GET /object-store-access-policies/object-store-roles -- that join item returns its endpoints as objects (context, member, policy) and carries no name.' } - @{ Cmdlet = 'Remove-PfbObjectStoreRole'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 1 + @{ Cmdlet = 'Remove-PfbObjectStoreRole'; Parameter = 'Name'; Scope = 'Family'; Issue = '#152'; Producers = 1 Why = 'Primary producer binds -Name correctly; one family endpoint coerces, GET /object-store-roles/object-store-access-policies -- that join item returns its endpoints as objects (context, member, policy) and carries no name.' } - @{ Cmdlet = 'Remove-PfbObjectStoreTrustPolicyRule'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 1 + @{ Cmdlet = 'Remove-PfbObjectStoreTrustPolicyRule'; Parameter = 'Name'; Scope = 'Family'; Issue = '#152'; Producers = 1 Why = 'Primary producer binds -Name correctly; one family endpoint coerces, GET /object-store-roles/object-store-access-policies -- that join item returns its endpoints as objects (context, member, policy) and carries no name.' } - @{ Cmdlet = 'Remove-PfbObjectStoreUser'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 1 + @{ Cmdlet = 'Remove-PfbObjectStoreUser'; Parameter = 'Name'; Scope = 'Family'; Issue = '#152'; Producers = 1 Why = 'Primary producer binds -Name correctly; one family endpoint coerces, GET /object-store-users/object-store-access-policies -- that join item returns its endpoints as objects (context, member, policy) and carries no name.' } - @{ Cmdlet = 'Remove-PfbOidcIdp'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 1 + @{ Cmdlet = 'Remove-PfbOidcIdp'; Parameter = 'Name'; Scope = 'Family'; Issue = '#153'; Producers = 1 Why = 'Primary producer binds -Name correctly; one family endpoint coerces, GET /sso/saml2/idps/test -- the /test endpoint returns a test-result item, not a resource, so it has no name.' } - @{ Cmdlet = 'Remove-PfbPolicy'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 4 + @{ Cmdlet = 'Remove-PfbPolicy'; Parameter = 'Name'; Scope = 'Family'; Issue = '#152'; Producers = 4 Why = 'Primary producer binds -Name correctly; 4 family endpoints coerce, e.g. GET /policies/file-system-replica-links -- that join item returns its endpoints as objects (context, link, member, policy) and carries no name.' } - @{ Cmdlet = 'Remove-PfbQosPolicy'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 3 + @{ Cmdlet = 'Remove-PfbQosPolicy'; Parameter = 'Name'; Scope = 'Family'; Issue = '#152'; Producers = 3 Why = 'Primary producer binds -Name correctly; 3 family endpoints coerce, e.g. GET /qos-policies/buckets -- that join item returns its endpoints as objects (context, member, policy) and carries no name.' } - @{ Cmdlet = 'Remove-PfbRealm'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 1 + @{ Cmdlet = 'Remove-PfbRealm'; Parameter = 'Name'; Scope = 'Family'; Issue = '#153'; Producers = 1 Why = 'Primary producer binds -Name correctly; one family endpoint coerces, GET /realms/defaults -- its items carry no name (context, object_store, realm).' } - @{ Cmdlet = 'Remove-PfbSaml2Idp'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 1 + @{ Cmdlet = 'Remove-PfbSaml2Idp'; Parameter = 'Name'; Scope = 'Family'; Issue = '#153'; Producers = 1 Why = 'Primary producer binds -Name correctly; one family endpoint coerces, GET /sso/saml2/idps/test -- the /test endpoint returns a test-result item, not a resource, so it has no name.' } - @{ Cmdlet = 'Remove-PfbSnmpManager'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 1 + @{ Cmdlet = 'Remove-PfbSnmpManager'; Parameter = 'Name'; Scope = 'Family'; Issue = '#153'; Producers = 1 Why = 'Primary producer binds -Name correctly; one family endpoint coerces, GET /snmp-managers/test -- the /test endpoint returns a test-result item, not a resource, so it has no name.' } - @{ Cmdlet = 'Remove-PfbSshCaPolicy'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 3 + @{ Cmdlet = 'Remove-PfbSshCaPolicy'; Parameter = 'Name'; Scope = 'Family'; Issue = '#152'; Producers = 3 Why = 'Primary producer binds -Name correctly; 3 family endpoints coerce, e.g. GET /ssh-certificate-authority-policies/admins -- that join item returns its endpoints as objects (context, member, policy) and carries no name.' } - @{ Cmdlet = 'Remove-PfbStorageClassTieringPolicy'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 1 + @{ Cmdlet = 'Remove-PfbStorageClassTieringPolicy'; Parameter = 'Name'; Scope = 'Family'; Issue = '#152'; Producers = 1 Why = 'Primary producer binds -Name correctly; one family endpoint coerces, GET /storage-class-tiering-policies/members -- that join item returns its endpoints as objects (member, policy) and carries no name.' } - @{ Cmdlet = 'Remove-PfbSyslogServer'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 1 + @{ Cmdlet = 'Remove-PfbSyslogServer'; Parameter = 'Name'; Scope = 'Family'; Issue = '#153'; Producers = 1 Why = 'Primary producer binds -Name correctly; one family endpoint coerces, GET /syslog-servers/test -- the /test endpoint returns a test-result item, not a resource, so it has no name.' } - @{ Cmdlet = 'Remove-PfbTlsPolicy'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 2 + @{ Cmdlet = 'Remove-PfbTlsPolicy'; Parameter = 'Name'; Scope = 'Family'; Issue = '#152'; Producers = 2 Why = 'Primary producer binds -Name correctly; 2 family endpoints coerce, e.g. GET /tls-policies/members -- that join item returns its endpoints as objects (member, policy) and carries no name.' } - @{ Cmdlet = 'Remove-PfbUserGroupQuotaPolicy'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 2 + @{ Cmdlet = 'Remove-PfbUserGroupQuotaPolicy'; Parameter = 'Name'; Scope = 'Family'; Issue = '#152'; Producers = 2 Why = 'Primary producer binds -Name correctly; 2 family endpoints coerce, e.g. GET /user-group-quota-policies/file-systems -- that join item returns its endpoints as objects (context, member, policy) and carries no name.' } - @{ Cmdlet = 'Remove-PfbWorkload'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 1 + @{ Cmdlet = 'Remove-PfbWorkload'; Parameter = 'Name'; Scope = 'Family'; Issue = '#153'; Producers = 1 Why = 'Primary producer binds -Name correctly; one family endpoint coerces, GET /workloads/tags -- its items carry no name (context, copyable, key, namespace, resource, value).' } - @{ Cmdlet = 'Remove-PfbWormPolicy'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 1 + @{ Cmdlet = 'Remove-PfbWormPolicy'; Parameter = 'Name'; Scope = 'Family'; Issue = '#152'; Producers = 1 Why = 'Primary producer binds -Name correctly; one family endpoint coerces, GET /worm-data-policies/members -- that join item returns its endpoints as objects (context, member, policy) and carries no name.' } - @{ Cmdlet = 'Update-PfbKmip'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 1 + @{ Cmdlet = 'Update-PfbKmip'; Parameter = 'Name'; Scope = 'Family'; Issue = '#153'; Producers = 1 Why = 'Primary producer binds -Name correctly; one family endpoint coerces, GET /kmip/test -- the /test endpoint returns a test-result item, not a resource, so it has no name.' } - @{ Cmdlet = 'Update-PfbObjectStoreAccessPolicyRule'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 2 + @{ Cmdlet = 'Update-PfbObjectStoreAccessPolicyRule'; Parameter = 'Name'; Scope = 'Family'; Issue = '#152'; Producers = 2 Why = 'Primary producer binds -Name correctly; 2 family endpoints coerce, e.g. GET /object-store-access-policies/object-store-roles -- that join item returns its endpoints as objects (context, member, policy) and carries no name.' } - @{ Cmdlet = 'Update-PfbObjectStoreRole'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 1 + @{ Cmdlet = 'Update-PfbObjectStoreRole'; Parameter = 'Name'; Scope = 'Family'; Issue = '#152'; Producers = 1 Why = 'Primary producer binds -Name correctly; one family endpoint coerces, GET /object-store-roles/object-store-access-policies -- that join item returns its endpoints as objects (context, member, policy) and carries no name.' } - @{ Cmdlet = 'Update-PfbObjectStoreTrustPolicyRule'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 1 + @{ Cmdlet = 'Update-PfbObjectStoreTrustPolicyRule'; Parameter = 'Name'; Scope = 'Family'; Issue = '#152'; Producers = 1 Why = 'Primary producer binds -Name correctly; one family endpoint coerces, GET /object-store-roles/object-store-access-policies -- that join item returns its endpoints as objects (context, member, policy) and carries no name.' } ) } diff --git a/Tests/PfbCmdletParamTools.Tests.ps1 b/Tests/PfbCmdletParamTools.Tests.ps1 index 1c101ebb..866f7775 100644 --- a/Tests/PfbCmdletParamTools.Tests.ps1 +++ b/Tests/PfbCmdletParamTools.Tests.ps1 @@ -3368,3 +3368,107 @@ Describe 'Compare-PfbInventoryTupleSet: the row-level regression gate (issue #14 $result.IsClean | Should -BeFalse } } + +Describe 'Test-PfbIsDefaultingAliasAssignment: a fallback arm is not a second wire name (issue #141 Task 6)' { + <# + Some cmdlets write the SAME wire key from a later arm of one if/elseif chain, as a + convenience default derived from a DIFFERENT parameter. Before this rule the resolver + counted that arm as a landing of the parameter named in the arm's condition, so the + parameter appeared to land two different keys, the arbitration abstained, and the + endpoint lost parser traceability -- PATCH /buckets/audit-filters fell to `partial` + confidence and tripped the issue #31 guard. + + The rule flags such an arm, and the caller drops flagged landings ONLY when the + parameter still has an unflagged landing of its own. That proviso is the whole safety + of it: New-PfbFleetMember writes `members` from an earlier arm built out of -FleetKey + and again from an elseif built out of -Members, but neither arm defaults the other and + -Members has no other landing, so dropping it would delete that parameter's only + evidence and relocate the same regression onto POST /fleets/members. + #> + + It 'flags an arm whose earlier sibling writes the same key from another parameter' { + $ast = Get-PfbRoleFixtureAst @( + 'function Set-FixtureThing {' + ' [CmdletBinding()]' + ' param([string]$Alpha, [string]$Beta, [PSCustomObject]$Array)' + ' $queryParams = @{}' + ' if ($Alpha) { $queryParams[''alpha''] = $Alpha }' + ' if ($PSBoundParameters.ContainsKey(''Beta'')) { $queryParams[''names''] = $Beta }' + ' elseif ($Alpha) { $queryParams[''names''] = $Alpha }' + ' Invoke-PfbApiRequest -Array $Array -Method PATCH -Endpoint ''widgets'' -QueryParams $queryParams' + '}' + ) + # -Alpha keeps its own unflagged landing, so the flagged 'names' arm is dropped. + $wire = Get-PfbWireNameForParameter -FunctionAst $ast -ParameterName 'Alpha' + $wire.WireName | Should -Be 'alpha' -Because 'the elseif arm defaults names= from -Alpha rather than naming it' + } + + It 'does not flag the primary arm of the chain' { + $ast = Get-PfbRoleFixtureAst @( + 'function Set-FixtureThing {' + ' [CmdletBinding()]' + ' param([string]$Beta, [PSCustomObject]$Array)' + ' $queryParams = @{}' + ' if ($Beta) { $queryParams[''names''] = $Beta }' + ' Invoke-PfbApiRequest -Array $Array -Method PATCH -Endpoint ''widgets'' -QueryParams $queryParams' + '}' + ) + $wire = Get-PfbWireNameForParameter -FunctionAst $ast -ParameterName 'Beta' + $wire.WireName | Should -Be 'names' + } + + It 'keeps a flagged landing when it is the parameter''s ONLY landing (New-PfbFleetMember shape)' { + # The regression this proviso exists to prevent: without it -Members resolves to + # nothing and POST /fleets/members loses parser traceability. + $ast = Get-PfbRoleFixtureAst @( + 'function New-FixtureMember {' + ' [CmdletBinding()]' + ' param([string]$FleetKey, [object[]]$Members, [PSCustomObject]$Array)' + ' $body = @{}' + ' if ($PSCmdlet.ParameterSetName -eq ''FleetKey'') {' + ' $body[''members''] = @(@{ key = $FleetKey })' + ' }' + ' elseif ($PSBoundParameters.ContainsKey(''Members'')) {' + ' $body[''members''] = @($Members)' + ' }' + ' Invoke-PfbApiRequest -Array $Array -Method POST -Endpoint ''fleets/members'' -Body $body' + '}' + ) + $wire = Get-PfbWireNameForParameter -FunctionAst $ast -ParameterName 'Members' + $wire | Should -Not -BeNullOrEmpty -Because 'dropping the only landing relocates the regression' + $wire.WireName | Should -Be 'members' + } + + It 'leaves a genuine two-key ambiguity abstaining -- independent ifs are not a chain' { + $ast = Get-PfbRoleFixtureAst @( + 'function Set-FixtureThing {' + ' [CmdletBinding()]' + ' param([string]$Alpha, [PSCustomObject]$Array)' + ' $queryParams = @{}' + ' if ($Alpha) { $queryParams[''alpha''] = $Alpha }' + ' if ($Alpha) { $queryParams[''names''] = $Alpha }' + ' Invoke-PfbApiRequest -Array $Array -Method PATCH -Endpoint ''widgets'' -QueryParams $queryParams' + '}' + ) + $wire = Get-PfbWireNameForParameter -FunctionAst $ast -ParameterName 'Alpha' + $wire.WireName | Should -BeNullOrEmpty -Because 'two sibling-less ifs are real ambiguity, not a defaulting alias' + } + + It 'resolves the real - to ' -ForEach @( + @{ Cmdlet = 'Update-PfbBucketAuditFilter'; Path = 'Public/Bucket/Update-PfbBucketAuditFilter.ps1'; Parameter = 'BucketName'; Expected = 'bucket_names' } + @{ Cmdlet = 'Update-PfbBucketAuditFilter'; Path = 'Public/Bucket/Update-PfbBucketAuditFilter.ps1'; Parameter = 'Name'; Expected = 'names' } + @{ Cmdlet = 'New-PfbFleetMember'; Path = 'Public/Replication/New-PfbFleetMember.ps1'; Parameter = 'Members'; Expected = 'members' } + ) { + $repoRoot = Split-Path -Parent $PSScriptRoot + $file = Join-Path $repoRoot $Path + $tokens = $null + $parseErrors = $null + $fileAst = [System.Management.Automation.Language.Parser]::ParseFile($file, [ref]$tokens, [ref]$parseErrors) + @($parseErrors).Count | Should -Be 0 + $funcAst = $fileAst.FindAll({ param($n) $n -is [System.Management.Automation.Language.FunctionDefinitionAst] }, $true) | + Where-Object { $_.Name -eq $Cmdlet } | Select-Object -First 1 + $funcAst | Should -Not -BeNullOrEmpty + $wire = Get-PfbWireNameForParameter -FunctionAst $funcAst -ParameterName $Parameter + $wire.WireName | Should -Be $Expected + } +} diff --git a/tools/lib/PfbCmdletParamTools.ps1 b/tools/lib/PfbCmdletParamTools.ps1 index 1f9c25a8..b45a4ee4 100644 --- a/tools/lib/PfbCmdletParamTools.ps1 +++ b/tools/lib/PfbCmdletParamTools.ps1 @@ -723,6 +723,118 @@ function New-PfbWireLanding { } } +function Test-PfbIsDefaultingAliasAssignment { + <# + .SYNOPSIS + True when an assignment supplies ANOTHER parameter's wire key as a fallback default, + rather than landing this parameter's own identity. + .DESCRIPTION + Arbitration treats two disagreeing keys for one parameter as an ambiguity and + abstains. That is right when both keys really are candidate names for the parameter, + and wrong when one of them is a documented convenience default for a DIFFERENT + parameter that happens to reuse this parameter's value. + + Update-PfbBucketAuditFilter is the case that forced this. -BucketName lands in + 'bucket_names' at its own unconditional `if`, and ALSO appears as the fallback arm of + a separate if/elseif chain whose primary arm assigns 'names' from -Name: + + if ($BucketName) { $queryParams['bucket_names'] = $BucketName } + ... + if ($PSBoundParameters.ContainsKey('Name')) { $queryParams['names'] = $Name -join ',' } + elseif ($BucketName) { $queryParams['names'] = $BucketName } + + 'names' is -Name's key; the elseif only spares a -BucketName-only caller from + restating the same value. Counting it as a second landing of -BucketName made the two + keys disagree, the arbitration abstain, and PATCH /buckets/audit-filters drop to + partial confidence -- which Tests/Issue31.DriftConfidence.Tests.ps1 catches, because a + parser-untraceable write endpoint is one the drift report can no longer see gaps on. + + The test is structural, not a name list: the same key must be assigned to the same + target variable in an EARLIER sibling clause of the SAME if/elseif chain, from an + expression that does not mention this parameter. Earlier matters -- the primary arm + owns the key and the fallback defers to it. Restricting it to one chain matters too: + two independent `if` blocks assigning one key from two parameters is a genuine + ambiguity and must still abstain. + .OUTPUTS + [bool] + #> + [CmdletBinding()] + param( + [Parameter(Mandatory)] + [System.Management.Automation.Language.AssignmentStatementAst]$Assignment, + + [Parameter(Mandatory)] + [string]$ParameterName, + + [Parameter(Mandatory)] + [string]$WireName, + + [Parameter(Mandatory)] + [string]$TargetVariable + ) + + # Walk up to the statement block this assignment sits in that is a clause of an if + # statement. Anything else -- a bare block, a loop body, the function body -- has no + # sibling arms and therefore cannot be a defaulting fallback. + $node = $Assignment + $block = $null + while ($node.Parent) { + if ($node -is [System.Management.Automation.Language.StatementBlockAst] -and + $node.Parent -is [System.Management.Automation.Language.IfStatementAst]) { + $block = $node + break + } + $node = $node.Parent + } + if (-not $block) { return $false } + + $ifStatement = $block.Parent + + # Which arm are we in? Clauses are ordered as written; the else clause is last. + $myIndex = -1 + for ($i = 0; $i -lt $ifStatement.Clauses.Count; $i++) { + if ([object]::ReferenceEquals($ifStatement.Clauses[$i].Item2, $block)) { $myIndex = $i; break } + } + if ($myIndex -lt 0) { + if ([object]::ReferenceEquals($ifStatement.ElseClause, $block)) { $myIndex = $ifStatement.Clauses.Count } + else { return $false } + } + if ($myIndex -eq 0) { return $false } # the primary arm owns its key by definition + + foreach ($earlier in 0..($myIndex - 1)) { + $earlierBlock = $ifStatement.Clauses[$earlier].Item2 + $siblings = $earlierBlock.FindAll({ + param($n) + $n -is [System.Management.Automation.Language.AssignmentStatementAst] -and + $n.Left -is [System.Management.Automation.Language.IndexExpressionAst] + }, $true) + + foreach ($sibling in $siblings) { + $sibTarget = $sibling.Left.Target -as [System.Management.Automation.Language.VariableExpressionAst] + $sibKey = $sibling.Left.Index -as [System.Management.Automation.Language.StringConstantExpressionAst] + if (-not $sibTarget -or -not $sibKey) { continue } + if (-not [string]::Equals($sibKey.Value, $WireName, [System.StringComparison]::Ordinal)) { continue } + if (-not [string]::Equals($sibTarget.VariablePath.UserPath, $TargetVariable, [System.StringComparison]::Ordinal)) { continue } + + # The earlier arm must assign this key from something OTHER than our parameter. + # If it mentions our parameter too, both arms are landing the same parameter and + # this is one landing, not an alias. + $mentions = $sibling.Right.FindAll({ + param($n) $n -is [System.Management.Automation.Language.VariableExpressionAst] + }, $true) + $mentionsOurs = $false + foreach ($m in $mentions) { + if ([string]::Equals($m.VariablePath.UserPath, $ParameterName, [System.StringComparison]::OrdinalIgnoreCase)) { + $mentionsOurs = $true; break + } + } + if (-not $mentionsOurs) { return $true } + } + } + + return $false +} + function Resolve-PfbParameterWireLanding { <# .SYNOPSIS @@ -802,7 +914,14 @@ function Resolve-PfbParameterWireLanding { if (Test-PfbWireValueIsParameter -ValueAst $assign.Right -ParameterName $ParameterName -IsBooleanLikeParameter:$IsBooleanLikeParameter) { $landing = New-PfbWireLanding -FunctionAst $FunctionAst -WireName $keyExpr.Value -TargetVariable $targetVar.VariablePath.UserPath - if ($landing) { $landings.Add($landing) } + if ($landing) { + # TAG, do not drop -- see the alias filter below and + # Test-PfbIsDefaultingAliasAssignment. + Add-Member -InputObject $landing -NotePropertyName 'IsDefaultingAlias' -NotePropertyValue ( + [bool](Test-PfbIsDefaultingAliasAssignment -Assignment $assign -ParameterName $ParameterName -WireName $keyExpr.Value -TargetVariable $targetVar.VariablePath.UserPath) + ) -Force + $landings.Add($landing) + } } } @@ -813,7 +932,26 @@ function Resolve-PfbParameterWireLanding { # answer -- asking `if ($literalMatch)` instead would read an abstention as a miss and # fall through, which is the whole failure this resolver exists to prevent. $tierLandings = $null - if ($landings.Count -gt 0) { $tierLandings = $landings.ToArray() } + if ($landings.Count -gt 0) { + # A landing tagged IsDefaultingAlias is dropped ONLY when the parameter still has an + # unflagged landing of its own. That proviso is the whole safety of the rule. + # + # Update-PfbBucketAuditFilter: -BucketName holds 'bucket_names' unconditionally AND + # appears in the elseif that defaults -Name's 'names'. Dropping the flagged one leaves + # 'bucket_names' -- the parameter's real identity -- and the abstention correctly + # disappears. + # + # New-PfbFleetMember is why the proviso exists. -Members lands 'members' in the elseif + # of a chain whose first arm builds the same key from -FleetKey. That is structurally + # identical to the alias shape, but neither arm defaults the other: they are two + # alternative constructions, and -Members has NO other landing. Dropping it would + # delete the parameter's only evidence and push POST /fleets/members to partial + # confidence -- the very regression this whole change set out to repair, just moved to + # a different endpoint. Keeping every landing when all of them are flagged makes the + # rule a tie-breaker rather than a deletion. + $unflagged = @($landings | Where-Object { -not $_.IsDefaultingAlias }) + $tierLandings = if ($unflagged.Count -gt 0) { $unflagged } else { $landings.ToArray() } + } # Second idiom: the whole hashtable is built as a LITERAL initializer rather than keyed # into afterwards -- `$queryParams = @{ 'names' = $Name }`, the dominant shape across