diff --git a/Reports/PfbApiDriftReport.json b/Reports/PfbApiDriftReport.json index eb67e4b5..96b8428f 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,135 @@ "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": "partial", - "unresolvedParameters": [ - { - "parameter": "Destroyed", - "surface": "AttributesOnly", - "file": "Public/Bucket/Update-PfbBucket.ps1", - "line": 37 - }, - { - "parameter": "Eradicate", - "surface": "TypedUnresolved", - "file": "Public/Bucket/Remove-PfbBucket.ps1", - "line": 27 - } - ], - "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": [] }, @@ -5119,8 +5305,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 +5331,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,40 +5344,190 @@ "Update-PfbDirectoryService" ], "missingQueryParameters": [ - "ids", - "names" + "ids" ], "missingBodyProperties": [ - "base_dn", - "bind_password", - "bind_user", - "ca_certificate", - "ca_certificate_group", - "enabled", - "management", - "nfs", - "smb", - "uris" - ], - "readOnlyFields": [ - "id", + { + "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", "name", "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 +5624,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 +5720,10 @@ "time_remaining" ], "confidence": { - "level": "partial", - "unresolvedParameters": [ - { - "parameter": "Eradicate", - "surface": "TypedUnresolved", - "file": "Public/FileSystemSnapshot/Remove-PfbFileSystemSnapshot.ps1", - "line": 24 - } - ], + "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": [] }, @@ -5325,11 +5742,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 +5766,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 +5804,6 @@ } ], "escapeHatchOnly": [ - "Destroyed", - "HardLimitEnabled", - "HttpEnabled", "NfsEnabled", "NfsExportPolicy", "NfsRules", @@ -5424,7 +5811,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 +6006,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 +6066,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 +6223,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 +6283,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 +7034,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 +7095,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 +7131,6 @@ ], "missingQueryParameters": [ "file_system_ids", - "file_system_names", - "gids", - "group_names", "names" ], "missingBodyProperties": [], @@ -6640,33 +7138,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 +7206,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 +7280,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 +7352,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 +7474,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 +7551,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 +7684,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 +7743,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 +8347,37 @@ ], "missingQueryParameters": [], "missingBodyProperties": [ - "enabled", - "name" - ], - "readOnlyFields": [ - "context", - "id", - "is_local", - "policy_type", - "realms", + { + "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 +8400,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,144 +8429,235 @@ "New-PfbActiveDirectory" ], "missingQueryParameters": [ - "join_existing_account", - "names" + "join_existing_account" ], "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", + { + "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/New-PfbActiveDirectory.ps1", - "line": 40 + "paramBlockLine": 42, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true } - ], - "escapeHatchOnly": [ - "Name" - ], - "caveat": "body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability" - }, - "annotations": [] - }, - { - "endpoint": "POST /api-clients", - "cmdlets": [ - "New-PfbApiClient" - ], - "missingQueryParameters": [], - "missingBodyProperties": [ + }, { - "name": "access_policies", + "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/New-PfbActiveDirectory.ps1", + "paramBlockLine": 42, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true + } + }, + { + "name": "computer_name", + "type": "string", + "format": null, + "specRequired": false, + "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/DirectoryService/New-PfbActiveDirectory.ps1", + "paramBlockLine": 42, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true + } + }, + { + "name": "directory_servers", "type": "array", "format": null, "specRequired": false, - "synopsis": "The access policies allowed for ID Tokens issued by this API client.", - "suggestedPowerShellType": "[object[]]", + "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/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": "domain", + "type": "string", + "format": null, + "specRequired": true, + "synopsis": "The Active Directory domain to join.", + "suggestedPowerShellType": "[string]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/DirectoryService/New-PfbActiveDirectory.ps1", + "paramBlockLine": 42, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true + } + }, + { + "name": "encryption_types", + "type": "array", + "format": null, "specRequired": false, - "synopsis": "The TTL (Time To Live) duration for which the exchanged access token is valid.", - "suggestedPowerShellType": "[long]", + "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/DirectoryService/New-PfbActiveDirectory.ps1", + "paramBlockLine": 42, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true + } + }, + { + "name": "fqdns", + "type": "array", + "format": null, + "specRequired": false, + "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/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": "global_catalog_servers", + "type": "array", + "format": null, + "specRequired": false, + "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/DirectoryService/New-PfbActiveDirectory.ps1", + "paramBlockLine": 42, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true + } + }, + { + "name": "join_ou", "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 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/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" + }, + { + "name": "kerberos_servers", + "type": "array", + "format": null, + "specRequired": false, + "synopsis": "A list of key distribution servers to use for Kerberos protocol.", + "suggestedPowerShellType": "[string[]]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/DirectoryService/New-PfbActiveDirectory.ps1", + "paramBlockLine": 42, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true + } + }, + { + "name": "password", + "type": "string", + "format": null, + "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/DirectoryService/New-PfbActiveDirectory.ps1", + "paramBlockLine": 42, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true + } + }, + { + "name": "service_principal_names", + "type": "array", + "format": null, + "specRequired": false, + "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/DirectoryService/New-PfbActiveDirectory.ps1", + "paramBlockLine": 42, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true + } + }, + { + "name": "user", + "type": "string", + "format": null, + "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/DirectoryService/New-PfbActiveDirectory.ps1", + "paramBlockLine": 42, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true + } + } ], - "missingBodyProperties": [], "readOnlyFields": [], "confidence": { "level": "high", @@ -8009,178 +8668,400 @@ "annotations": [] }, { - "endpoint": "POST /audit-file-systems-policies", + "endpoint": "POST /api-clients", "cmdlets": [ - "New-PfbAuditFileSystemPolicy" + "New-PfbApiClient" ], "missingQueryParameters": [], "missingBodyProperties": [ - "control_type", - "enabled", - "location", - "log_targets", - "name", - "rules" - ], - "readOnlyFields": [ - "id", - "is_local", - "policy_type", - "realms" + { + "name": "access_policies", + "type": "array", + "format": null, + "specRequired": false, + "synopsis": "The access policies allowed for ID Tokens issued by this API client.", + "suggestedPowerShellType": "[object[]]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Admin/New-PfbApiClient.ps1", + "paramBlockLine": 58, + "payloadVariable": "body", + "assignmentStyle": "literal", + "hasAttributes": true + } + }, + { + "name": "access_token_ttl_in_ms", + "type": "integer", + "format": "int64", + "specRequired": false, + "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/Admin/New-PfbApiClient.ps1", + "paramBlockLine": 58, + "payloadVariable": "body", + "assignmentStyle": "literal", + "hasAttributes": true + } + }, + { + "name": "issuer", + "type": "string", + "format": null, + "specRequired": false, + "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/Admin/New-PfbApiClient.ps1", + "paramBlockLine": 58, + "payloadVariable": "body", + "assignmentStyle": "literal", + "hasAttributes": true + } + } ], + "readOnlyFields": [], "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" + "level": "high", + "unresolvedParameters": [], + "escapeHatchOnly": [], + "caveat": "" }, "annotations": [] }, { - "endpoint": "POST /audit-object-store-policies", + "endpoint": "POST /array-connections", "cmdlets": [ - "New-PfbAuditObjectStorePolicy" + "New-PfbArrayConnection" ], "missingQueryParameters": [], - "missingBodyProperties": [ - "enabled", - "location", - "log_targets", - "name" - ], + "missingBodyProperties": [], "readOnlyFields": [ + "context", "id", - "is_local", - "policy_type", - "realms" + "os", + "status", + "type", + "version" ], "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" + "level": "high", + "unresolvedParameters": [], + "escapeHatchOnly": [], + "caveat": "" }, "annotations": [] }, { - "endpoint": "POST /buckets", + "endpoint": "POST /arrays/erasures", "cmdlets": [ - "New-PfbBucket" + "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": "bucket_type", + "name": "control_type", "type": "string", "format": null, "specRequired": false, - "synopsis": "The bucket type for the bucket.", + "synopsis": "Specifies the evaluation mode for auditing in this policy.", "suggestedPowerShellType": "[string]", "enumValues": [ - "classic", - "multi-site-writable" + "policy", + "sacl" ], "enumStatus": "matched", "target": { - "file": "Public/Bucket/New-PfbBucket.ps1", - "paramBlockLine": 39, + "file": "Public/Policy/New-PfbAuditFileSystemPolicy.ps1", + "paramBlockLine": 41, "payloadVariable": "body", "assignmentStyle": "index", "hasAttributes": true } }, { - "name": "eradication_config", + "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/Bucket/New-PfbBucket.ps1", - "paramBlockLine": 39, + "file": "Public/Policy/New-PfbAuditFileSystemPolicy.ps1", + "paramBlockLine": 41, "payloadVariable": "body", "assignmentStyle": "index", "hasAttributes": true } }, { - "name": "hard_limit_enabled", - "type": "boolean", + "name": "log_targets", + "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]", + "synopsis": "List of targets which will be utilized for audit log storage.", + "suggestedPowerShellType": "[object[]]", "enumValues": [], "enumStatus": "no-spec-enum-found", "target": { - "file": "Public/Bucket/New-PfbBucket.ps1", - "paramBlockLine": 39, + "file": "Public/Policy/New-PfbAuditFileSystemPolicy.ps1", + "paramBlockLine": 41, "payloadVariable": "body", "assignmentStyle": "index", "hasAttributes": true } }, { - "name": "object_lock_config", - "type": null, + "name": "name", + "type": "string", "format": null, "specRequired": false, - "synopsis": null, - "suggestedPowerShellType": "[object]", + "synopsis": "A user-specified name.", + "suggestedPowerShellType": "[string]", "enumValues": [], - "enumStatus": "no-spec-enum-found", + "enumStatus": "not-found-in-resource", "target": { - "file": "Public/Bucket/New-PfbBucket.ps1", - "paramBlockLine": 39, + "file": "Public/Policy/New-PfbAuditFileSystemPolicy.ps1", + "paramBlockLine": 41, "payloadVariable": "body", "assignmentStyle": "index", "hasAttributes": true } }, { - "name": "retention_lock", - "type": "string", + "name": "rules", + "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": "All of the rules that are part of this policy.", + "suggestedPowerShellType": "[object[]]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", "target": { - "file": "Public/Bucket/New-PfbBucket.ps1", - "paramBlockLine": 39, + "file": "Public/Policy/New-PfbAuditFileSystemPolicy.ps1", + "paramBlockLine": 41, "payloadVariable": "body", "assignmentStyle": "index", "hasAttributes": true } } ], - "readOnlyFields": [], - "confidence": { + "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": "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-PfbAuditObjectStorePolicy.ps1", + "paramBlockLine": 41, + "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/New-PfbAuditObjectStorePolicy.ps1", + "paramBlockLine": 41, + "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-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": "bucket_type", + "type": "string", + "format": null, + "specRequired": false, + "synopsis": "The bucket type for the bucket.", + "suggestedPowerShellType": "[string]", + "enumValues": [ + "classic", + "multi-site-writable" + ], + "enumStatus": "matched", + "target": { + "file": "Public/Bucket/New-PfbBucket.ps1", + "paramBlockLine": 39, + "payloadVariable": "body", + "assignmentStyle": "index", + "hasAttributes": true + } + }, + { + "name": "eradication_config", + "type": null, + "format": null, + "specRequired": false, + "synopsis": null, + "suggestedPowerShellType": "[object]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Bucket/New-PfbBucket.ps1", + "paramBlockLine": 39, + "payloadVariable": "body", + "assignmentStyle": "index", + "hasAttributes": true + } + }, + { + "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/New-PfbBucket.ps1", + "paramBlockLine": 39, + "payloadVariable": "body", + "assignmentStyle": "index", + "hasAttributes": true + } + }, + { + "name": "object_lock_config", + "type": null, + "format": null, + "specRequired": false, + "synopsis": null, + "suggestedPowerShellType": "[object]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Bucket/New-PfbBucket.ps1", + "paramBlockLine": 39, + "payloadVariable": "body", + "assignmentStyle": "index", + "hasAttributes": true + } + }, + { + "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/New-PfbBucket.ps1", + "paramBlockLine": 39, + "payloadVariable": "body", + "assignmentStyle": "index", + "hasAttributes": true + } + } + ], + "readOnlyFields": [], + "confidence": { "level": "high", "unresolvedParameters": [], "escapeHatchOnly": [], @@ -8739,62 +9620,197 @@ ], "missingQueryParameters": [], "missingBodyProperties": [ - "certificate", - "common_name", - "country", - "email", - "locality", - "organization", - "organizational_unit", - "state", - "subject_alternative_names" - ], - "readOnlyFields": [], - "confidence": { - "level": "partial", - "unresolvedParameters": [ - { - "parameter": "Name", - "surface": "AttributesOnly", + { + "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", - "line": 29 + "paramBlockLine": 31, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true } - ], - "escapeHatchOnly": [ - "Name" - ], - "caveat": "body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability" - }, - "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 + }, + { + "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 } - ], - "escapeHatchOnly": [], + }, + { + "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": [] @@ -8937,128 +9953,213 @@ "cmdlets": [ "New-PfbDns" ], - "missingQueryParameters": [ - "names" - ], - "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 - } - ], - "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", - "unresolvedParameters": [], - "escapeHatchOnly": [], - "caveat": "" - }, - "annotations": [] - }, - { - "endpoint": "POST /file-system-replica-links", - "cmdlets": [ - "New-PfbFileSystemReplicaLink" - ], - "missingQueryParameters": [ - "local_file_system_ids" - ], + "missingQueryParameters": [], "missingBodyProperties": [ { - "name": "direction", + "name": "ca_certificate", "type": null, "format": null, "specRequired": false, - "synopsis": null, + "synopsis": "A reference to the `certificate` to use for validating nameservers with https connections.", "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 + "file": "Public/Network/New-PfbDns.ps1", + "paramBlockLine": 31, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true } }, { - "name": "local_file_system", + "name": "ca_certificate_group", "type": null, "format": null, "specRequired": false, - "synopsis": "Reference to a local file system.", + "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/Replication/New-PfbFileSystemReplicaLink.ps1", - "paramBlockLine": 85, - "payloadVariable": "body", - "assignmentStyle": "unknown", - "hasAttributes": false + "file": "Public/Network/New-PfbDns.ps1", + "paramBlockLine": 31, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true } }, { - "name": "policies", - "type": "array", + "name": "domain", + "type": "string", "format": null, "specRequired": false, - "synopsis": null, - "suggestedPowerShellType": "[object[]]", + "synopsis": "Domain suffix to be appended by the appliance when performing DNS lookups.", + "suggestedPowerShellType": "[string]", "enumValues": [], "enumStatus": "no-spec-enum-found", "target": { - "file": "Public/Replication/New-PfbFileSystemReplicaLink.ps1", + "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", @@ -9157,7 +10258,6 @@ ], "missingBodyProperties": [ "eradication_config", - "fast_remove_directory_enabled", "hard_limit_enabled", "http", "multi_protocol", @@ -9165,8 +10265,7 @@ "node_group", "smb", "snapshot_directory_enabled", - "workload", - "writable" + "workload" ], "readOnlyFields": [ "requested_promotion_state" @@ -9180,12 +10279,6 @@ "file": "Public/FileSystem/New-PfbFileSystem.ps1", "line": 208 }, - { - "parameter": "FastRemoveDirectoryEnabled", - "surface": "AttributesOnly", - "file": "Public/FileSystem/New-PfbFileSystem.ps1", - "line": 188 - }, { "parameter": "HardLimit", "surface": "AttributesOnly", @@ -9269,17 +10362,10 @@ "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", @@ -9293,35 +10379,7 @@ "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": [], - "confidence": { - "level": "partial", - "unresolvedParameters": [ - { - "parameter": "Name", - "surface": "AttributesOnly", - "file": "Public/Replication/New-PfbFleet.ps1", - "line": 30 - } - ], - "escapeHatchOnly": [ - "Name" + "SnapshotDirectoryEnabled" ], "caveat": "body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability" }, @@ -9563,11 +10621,25 @@ "cmdlets": [ "New-PfbLag" ], - "missingQueryParameters": [ - "names" - ], + "missingQueryParameters": [], "missingBodyProperties": [ - "ports" + { + "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", @@ -9578,19 +10650,10 @@ "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" + "level": "high", + "unresolvedParameters": [], + "escapeHatchOnly": [], + "caveat": "" }, "annotations": [] }, @@ -9983,31 +11046,69 @@ ], "missingQueryParameters": [], "missingBodyProperties": [ - "enabled", - "location", - "name", - "rules" - ], - "readOnlyFields": [ - "id", - "is_local", - "policy_type", - "realms" - ], - "confidence": { - "level": "partial", - "unresolvedParameters": [ - { - "parameter": "Enabled", - "surface": "AttributesOnly", + { + "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-PfbNfsExportPolicy.ps1", - "line": 36 - } - ], - "escapeHatchOnly": [ - "Enabled" - ], - "caveat": "body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability" + "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": [ + "id", + "is_local", + "policy_type", + "realms" + ], + "confidence": { + "level": "high", + "unresolvedParameters": [], + "escapeHatchOnly": [], + "caveat": "" }, "annotations": [] }, @@ -10032,33 +11133,6 @@ }, "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", "cmdlets": [ @@ -10641,9 +11715,40 @@ ], "missingQueryParameters": [], "missingBodyProperties": [ - "enabled", - "location", - "name" + { + "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": "A user-specified name.", + "suggestedPowerShellType": "[string]", + "enumValues": [], + "enumStatus": "not-found-in-resource", + "target": { + "file": "Public/Policy/New-PfbPolicy.ps1", + "paramBlockLine": 32, + "payloadVariable": "body", + "assignmentStyle": "index", + "hasAttributes": true + } + } ], "readOnlyFields": [ "id", @@ -10653,19 +11758,10 @@ "retention_lock" ], "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": [] }, @@ -11077,43 +12173,17 @@ "New-PfbQuotaGroup" ], "missingQueryParameters": [ - "file_system_ids", - "file_system_names", - "gids", - "group_names" + "file_system_ids" ], "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" + "level": "high", + "unresolvedParameters": [], + "escapeHatchOnly": [], + "caveat": "" }, "annotations": [] }, @@ -11123,43 +12193,17 @@ "New-PfbQuotaUser" ], "missingQueryParameters": [ - "file_system_ids", - "file_system_names", - "uids", - "user_names" + "file_system_ids" ], "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" + "level": "high", + "unresolvedParameters": [], + "escapeHatchOnly": [], + "caveat": "" }, "annotations": [] }, @@ -11188,24 +12232,30 @@ ], "missingQueryParameters": [], "missingBodyProperties": [ - "enabled", - "rules" + { + "name": "rules", + "type": "array", + "format": null, + "specRequired": false, + "synopsis": null, + "suggestedPowerShellType": "[object[]]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Policy/New-PfbS3ExportPolicy.ps1", + "paramBlockLine": 42, + "payloadVariable": "body", + "assignmentStyle": "index", + "hasAttributes": true + } + } ], "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" + "level": "high", + "unresolvedParameters": [], + "escapeHatchOnly": [], + "caveat": "" }, "annotations": [] }, @@ -11244,65 +12294,74 @@ ], "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", + { + "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/New-PfbSmbClientPolicy.ps1", - "line": 36 + "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 /smb-client-policies/rules", - "cmdlets": [ - "New-PfbSmbClientRule" - ], - "missingQueryParameters": [], - "missingBodyProperties": [], - "readOnlyFields": [ - "id", - "name" - ], - "confidence": { - "level": "high", - "unresolvedParameters": [], - "escapeHatchOnly": [], - "caveat": "" - }, - "annotations": [] - }, - { - "endpoint": "POST /smb-share-policies", - "cmdlets": [ - "New-PfbSmbSharePolicy" - ], - "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/New-PfbSmbClientPolicy.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-PfbSmbClientPolicy.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-PfbSmbClientPolicy.ps1", + "paramBlockLine": 42, + "payloadVariable": "body", + "assignmentStyle": "index", + "hasAttributes": true + } + } ], "readOnlyFields": [ "id", @@ -11311,26 +12370,17 @@ "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" + "level": "high", + "unresolvedParameters": [], + "escapeHatchOnly": [], + "caveat": "" }, "annotations": [] }, { - "endpoint": "POST /smb-share-policies/rules", + "endpoint": "POST /smb-client-policies/rules", "cmdlets": [ - "New-PfbSmbShareRule" + "New-PfbSmbClientRule" ], "missingQueryParameters": [], "missingBodyProperties": [], @@ -11347,156 +12397,69 @@ "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", - "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": [ - "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" - }, - "annotations": [] - }, - { - "endpoint": "POST /sso/oidc/idps", + "endpoint": "POST /smb-share-policies", "cmdlets": [ - "New-PfbOidcIdp" + "New-PfbSmbSharePolicy" ], "missingQueryParameters": [], "missingBodyProperties": [ { - "name": "enabled", - "type": "boolean", + "name": "location", + "type": null, "format": null, "specRequired": false, - "synopsis": "If set to `true`, the OIDC SSO configuration is enabled.", - "suggestedPowerShellType": "[bool]", + "synopsis": "Reference to the array where the policy is defined.", + "suggestedPowerShellType": "[object]", "enumValues": [], "enumStatus": "no-spec-enum-found", "target": { - "file": "Public/Admin/New-PfbOidcIdp.ps1", - "paramBlockLine": 39, + "file": "Public/Policy/New-PfbSmbSharePolicy.ps1", + "paramBlockLine": 42, "payloadVariable": "body", - "assignmentStyle": "unknown", + "assignmentStyle": "index", "hasAttributes": true } }, { - "name": "idp", - "type": null, + "name": "name", + "type": "string", "format": null, "specRequired": false, - "synopsis": null, - "suggestedPowerShellType": "[object]", + "synopsis": "A user-specified name.", + "suggestedPowerShellType": "[string]", "enumValues": [], - "enumStatus": "no-spec-enum-found", + "enumStatus": "not-found-in-resource", "target": { - "file": "Public/Admin/New-PfbOidcIdp.ps1", - "paramBlockLine": 39, + "file": "Public/Policy/New-PfbSmbSharePolicy.ps1", + "paramBlockLine": 42, "payloadVariable": "body", - "assignmentStyle": "unknown", + "assignmentStyle": "index", "hasAttributes": true } }, { - "name": "services", + "name": "rules", "type": "array", "format": null, "specRequired": false, - "synopsis": "Services that the OIDC SSO authentication is used for.", - "suggestedPowerShellType": "[string[]]", + "synopsis": "All of the rules that are part of this policy.", + "suggestedPowerShellType": "[object[]]", "enumValues": [], - "enumStatus": "not-found-in-resource", + "enumStatus": "no-spec-enum-found", "target": { - "file": "Public/Admin/New-PfbOidcIdp.ps1", - "paramBlockLine": 39, + "file": "Public/Policy/New-PfbSmbSharePolicy.ps1", + "paramBlockLine": 42, "payloadVariable": "body", - "assignmentStyle": "unknown", + "assignmentStyle": "index", "hasAttributes": true } } ], "readOnlyFields": [ - "prn" + "id", + "is_local", + "policy_type", + "realms" ], "confidence": { "level": "high", @@ -11507,108 +12470,276 @@ "annotations": [] }, { - "endpoint": "POST /sso/saml2/idps", + "endpoint": "POST /smb-share-policies/rules", "cmdlets": [ - "New-PfbSaml2Idp" + "New-PfbSmbShareRule" + ], + "missingQueryParameters": [], + "missingBodyProperties": [], + "readOnlyFields": [ + "id", + "name" + ], + "confidence": { + "level": "high", + "unresolvedParameters": [], + "escapeHatchOnly": [], + "caveat": "" + }, + "annotations": [] + }, + { + "endpoint": "POST /snmp-managers", + "cmdlets": [ + "New-PfbSnmpManager" ], "missingQueryParameters": [], "missingBodyProperties": [ { - "name": "array_url", + "name": "host", "type": "string", "format": null, "specRequired": false, - "synopsis": "The URL of the array.", + "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/Admin/New-PfbSaml2Idp.ps1", - "paramBlockLine": 39, - "payloadVariable": "body", - "assignmentStyle": "unknown", + "file": "Public/Monitoring/New-PfbSnmpManager.ps1", + "paramBlockLine": 35, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", "hasAttributes": true } }, { - "name": "binding", + "name": "notification", "type": "string", "format": null, "specRequired": false, - "synopsis": "SAML2 binding to use for the request from Flashblade to the Identity Provider.", + "synopsis": "The type of notification the agent will send.", "suggestedPowerShellType": "[string]", + "enumValues": [ + "inform", + "trap" + ], + "enumStatus": "matched", + "target": { + "file": "Public/Monitoring/New-PfbSnmpManager.ps1", + "paramBlockLine": 35, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true + } + }, + { + "name": "v2c", + "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", + "file": "Public/Monitoring/New-PfbSnmpManager.ps1", + "paramBlockLine": 35, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true + } + }, + { + "name": "v3", + "type": null, + "format": null, + "specRequired": false, + "synopsis": null, + "suggestedPowerShellType": "[object]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Monitoring/New-PfbSnmpManager.ps1", + "paramBlockLine": 35, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", "hasAttributes": true } }, + { + "name": "version", + "type": "string", + "format": null, + "specRequired": false, + "synopsis": "Version of the SNMP protocol to be used by Purity in communications with the specified manager.", + "suggestedPowerShellType": "[string]", + "enumValues": [ + "v2c", + "v3" + ], + "enumStatus": "matched", + "target": { + "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": "enabled", "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-PfbSshCaPolicy.ps1", + "paramBlockLine": 40, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", "hasAttributes": true } }, { - "name": "idp", + "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-PfbSshCaPolicy.ps1", + "paramBlockLine": 40, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", "hasAttributes": true } }, { - "name": "management", + "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-PfbSshCaPolicy.ps1", + "paramBlockLine": 40, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true + } + }, + { + "name": "signing_authority", "type": null, "format": null, "specRequired": false, - "synopsis": null, + "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/Admin/New-PfbSaml2Idp.ps1", - "paramBlockLine": 39, - "payloadVariable": "body", - "assignmentStyle": "unknown", + "file": "Public/Policy/New-PfbSshCaPolicy.ps1", + "paramBlockLine": 40, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", "hasAttributes": true } }, { - "name": "services", + "name": "static_authorized_principals", "type": "array", "format": null, "specRequired": false, - "synopsis": "Services that the SAML2 SSO authentication is used for.", + "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": "not-found-in-resource", + "enumStatus": "no-spec-enum-found", "target": { - "file": "Public/Admin/New-PfbSaml2Idp.ps1", + "file": "Public/Policy/New-PfbSshCaPolicy.ps1", + "paramBlockLine": 40, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true + } + } + ], + "readOnlyFields": [ + "id", + "is_local", + "policy_type", + "realms" + ], + "confidence": { + "level": "high", + "unresolvedParameters": [], + "escapeHatchOnly": [], + "caveat": "" + }, + "annotations": [] + }, + { + "endpoint": "POST /sso/oidc/idps", + "cmdlets": [ + "New-PfbOidcIdp" + ], + "missingQueryParameters": [], + "missingBodyProperties": [ + { + "name": "enabled", + "type": "boolean", + "format": null, + "specRequired": false, + "synopsis": "If set to `true`, the OIDC SSO configuration is enabled.", + "suggestedPowerShellType": "[bool]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Admin/New-PfbOidcIdp.ps1", "paramBlockLine": 39, "payloadVariable": "body", "assignmentStyle": "unknown", @@ -11616,7 +12747,7 @@ } }, { - "name": "sp", + "name": "idp", "type": null, "format": null, "specRequired": false, @@ -11625,7 +12756,24 @@ "enumValues": [], "enumStatus": "no-spec-enum-found", "target": { - "file": "Public/Admin/New-PfbSaml2Idp.ps1", + "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", @@ -11645,44 +12793,251 @@ "annotations": [] }, { - "endpoint": "POST /storage-class-tiering-policies", + "endpoint": "POST /sso/saml2/idps", "cmdlets": [ - "New-PfbStorageClassTieringPolicy" - ], - "missingQueryParameters": [ - "names" + "New-PfbSaml2Idp" ], + "missingQueryParameters": [], "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 + { + "name": "array_url", + "type": "string", + "format": null, + "specRequired": false, + "synopsis": "The URL of the array.", + "suggestedPowerShellType": "[string]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Admin/New-PfbSaml2Idp.ps1", + "paramBlockLine": 39, + "payloadVariable": "body", + "assignmentStyle": "unknown", + "hasAttributes": true } - ], - "escapeHatchOnly": [ - "Name" - ], - "caveat": "body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability" - }, - "annotations": [] - }, - { + }, + { + "name": "binding", + "type": "string", + "format": null, + "specRequired": false, + "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/Admin/New-PfbSaml2Idp.ps1", + "paramBlockLine": 39, + "payloadVariable": "body", + "assignmentStyle": "unknown", + "hasAttributes": true + } + }, + { + "name": "enabled", + "type": "boolean", + "format": null, + "specRequired": false, + "synopsis": "If set to `true`, the SAML2 SSO configuration is enabled.", + "suggestedPowerShellType": "[bool]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Admin/New-PfbSaml2Idp.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-PfbSaml2Idp.ps1", + "paramBlockLine": 39, + "payloadVariable": "body", + "assignmentStyle": "unknown", + "hasAttributes": true + } + }, + { + "name": "management", + "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 + } + }, + { + "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 + } + }, + { + "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 + } + } + ], + "readOnlyFields": [ + "prn" + ], + "confidence": { + "level": "high", + "unresolvedParameters": [], + "escapeHatchOnly": [], + "caveat": "" + }, + "annotations": [] + }, + { + "endpoint": "POST /storage-class-tiering-policies", + "cmdlets": [ + "New-PfbStorageClassTieringPolicy" + ], + "missingQueryParameters": [], + "missingBodyProperties": [ + { + "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", + "is_local", + "policy_type", + "realms" + ], + "confidence": { + "level": "high", + "unresolvedParameters": [], + "escapeHatchOnly": [], + "caveat": "" + }, + "annotations": [] + }, + { "endpoint": "POST /subnets", "cmdlets": [ "New-PfbSubnet" @@ -11728,113 +13083,283 @@ "cmdlets": [ "New-PfbSyslogServer" ], - "missingQueryParameters": [ - "names" - ], + "missingQueryParameters": [], "missingBodyProperties": [ - "services", - "sources", - "uri" - ], - "readOnlyFields": [], - "confidence": { - "level": "partial", - "unresolvedParameters": [ - { - "parameter": "Name", - "surface": "AttributesOnly", + { + "name": "services", + "type": "array", + "format": null, + "specRequired": false, + "synopsis": "Valid values are `data-audit` and `management`.", + "suggestedPowerShellType": "[string[]]", + "enumValues": [ + "data-audit", + "management" + ], + "enumStatus": "matched", + "target": { "file": "Public/Monitoring/New-PfbSyslogServer.ps1", - "line": 32 + "paramBlockLine": 34, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true } - ], - "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" + }, + { + "name": "sources", + "type": "array", + "format": null, + "specRequired": false, + "synopsis": "The network interfaces used for communication with the syslog server.", + "suggestedPowerShellType": "[object[]]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Monitoring/New-PfbSyslogServer.ps1", + "paramBlockLine": 34, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true + } + }, + { + "name": "uri", + "type": "string", + "format": null, + "specRequired": false, + "synopsis": "The URI of the syslog server in the format PROTOCOL://HOSTNAME:PORT.", + "suggestedPowerShellType": "[string]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Monitoring/New-PfbSyslogServer.ps1", + "paramBlockLine": 34, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true + } + } ], "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" + "level": "high", + "unresolvedParameters": [], + "escapeHatchOnly": [], + "caveat": "" }, "annotations": [] }, { - "endpoint": "POST /tls-policies", + "endpoint": "POST /targets", "cmdlets": [ - "New-PfbTlsPolicy" - ], - "missingQueryParameters": [ - "names" + "New-PfbTarget" ], + "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" - ], - "readOnlyFields": [ - "id", - "is_local", - "policy_type", - "realms" + { + "name": "address", + "type": "string", + "format": null, + "specRequired": false, + "synopsis": "IP address or FQDN of the target system.", + "suggestedPowerShellType": "[string]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "file": "Public/Replication/New-PfbTarget.ps1", + "paramBlockLine": 33, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true + } + } ], + "readOnlyFields": [], "confidence": { - "level": "partial", - "unresolvedParameters": [ - { - "parameter": "Name", - "surface": "AttributesOnly", - "file": "Public/Policy/New-PfbTlsPolicy.ps1", - "line": 29 - } - ], - "escapeHatchOnly": [ - "Name" - ], - "caveat": "body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability" + "level": "high", + "unresolvedParameters": [], + "escapeHatchOnly": [], + "caveat": "" }, "annotations": [] }, { - "endpoint": "POST /user-group-quota-policies", + "endpoint": "POST /tls-policies", "cmdlets": [ - "New-PfbUserGroupQuotaPolicy" + "New-PfbTlsPolicy" ], "missingQueryParameters": [], "missingBodyProperties": [ - "enabled", - "name" + { + "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": "client_certificates_required", + "type": "boolean", + "format": null, + "specRequired": false, + "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/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 + } + }, + { + "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-PfbTlsPolicy.ps1", + "paramBlockLine": 31, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true + } + }, + { + "name": "enabled_tls_ciphers", + "type": "array", + "format": null, + "specRequired": false, + "synopsis": "If specified, enables only the specified 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 + } + }, + { + "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-PfbTlsPolicy.ps1", + "paramBlockLine": 31, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true + } + }, + { + "name": "min_tls_version", + "type": "string", + "format": null, + "specRequired": false, + "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/Policy/New-PfbTlsPolicy.ps1", + "paramBlockLine": 31, + "payloadVariable": "Attributes", + "assignmentStyle": "attributesOnly", + "hasAttributes": true + } + }, + { + "name": "trusted_client_certificate_authority", + "type": null, + "format": null, + "specRequired": false, + "synopsis": "A reference to a certificate or certificate group.", + "suggestedPowerShellType": "[object]", + "enumValues": [], + "enumStatus": "no-spec-enum-found", + "target": { + "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": [ "id", @@ -11843,46 +13368,49 @@ "realms" ], "confidence": { - "level": "partial", - "unresolvedParameters": [ - { - "parameter": "Enabled", - "surface": "AttributesOnly", - "file": "Public/Policy/New-PfbUserGroupQuotaPolicy.ps1", - "line": 49 - } - ], - "escapeHatchOnly": [ - "Enabled" - ], - "caveat": "body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability" + "level": "high", + "unresolvedParameters": [], + "escapeHatchOnly": [], + "caveat": "" }, "annotations": [] }, { - "endpoint": "POST /user-group-quota-policies/rules", + "endpoint": "POST /user-group-quota-policies", "cmdlets": [ - "New-PfbUserGroupQuotaPolicyRule" + "New-PfbUserGroupQuotaPolicy" ], "missingQueryParameters": [], "missingBodyProperties": [ - "enforced" + { + "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" ], - "readOnlyFields": [], "confidence": { - "level": "partial", - "unresolvedParameters": [ - { - "parameter": "Enforced", - "surface": "AttributesOnly", - "file": "Public/Policy/New-PfbUserGroupQuotaPolicyRule.ps1", - "line": 57 - } - ], - "escapeHatchOnly": [ - "Enforced" - ], - "caveat": "body reachable only via -Attributes; lists reflect typed-parameter coverage, not wire reachability" + "level": "high", + "unresolvedParameters": [], + "escapeHatchOnly": [], + "caveat": "" }, "annotations": [] }, @@ -11931,17 +13459,130 @@ "cmdlets": [ "New-PfbWormPolicy" ], - "missingQueryParameters": [ - "names" - ], + "missingQueryParameters": [], "missingBodyProperties": [ - "default_retention", - "enabled", - "location", - "max_retention", - "min_retention", - "mode", - "retention_lock" + { + "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 + } + }, + { + "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 + } + }, + { + "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 + } + } ], "readOnlyFields": [ "context", @@ -11952,19 +13593,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 +13912,8 @@ "systemicGaps": [ { "name": "allow_errors", - "endpointCount": 118, - "queryEndpointCount": 118, + "endpointCount": 119, + "queryEndpointCount": 119, "bodyEndpointCount": 0, "endpoints": [ "GET /active-directory/test", @@ -12394,6 +14026,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 +14048,8 @@ }, { "name": "ids", - "endpointCount": 38, - "queryEndpointCount": 38, + "endpointCount": 39, + "queryEndpointCount": 39, "bodyEndpointCount": 0, "endpoints": [ "DELETE /network-access-policies/rules", @@ -12451,6 +14084,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 +14131,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 +14194,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 +14203,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 +14284,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 +14354,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,19 +14412,20 @@ "annotations": [] }, { - "name": "file_system_ids", - "endpointCount": 8, - "queryEndpointCount": 8, + "name": "versions", + "endpointCount": 9, + "queryEndpointCount": 9, "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" + "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": [] }, @@ -12690,38 +14462,6 @@ ], "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" - ], - "annotations": [] - }, - { - "name": "versions", - "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" - ], - "annotations": [] - }, { "name": "actions", "endpointCount": 6, @@ -12737,21 +14477,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 +14492,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 +14537,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 +14582,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 +14624,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 +14653,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 +14757,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 +14823,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 +14920,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 +14944,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 +15004,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 +15028,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,36 +15065,112 @@ "annotations": [] }, { - "name": "uids", - "endpointCount": 3, - "queryEndpointCount": 3, + "name": "access_based_enumeration_enabled", + "endpointCount": 2, + "queryEndpointCount": 0, + "bodyEndpointCount": 2, + "endpoints": [ + "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": [] + }, + { + "name": "certificate_group_ids", + "endpointCount": 2, + "queryEndpointCount": 2, + "bodyEndpointCount": 0, + "endpoints": [ + "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 /file-systems/users/performance", - "GET /quotas/users", - "GET /usage/users" + "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": "certificate_group_ids", + "name": "default_inbound_tls_policy", "endpointCount": 2, - "queryEndpointCount": 2, - "bodyEndpointCount": 0, + "queryEndpointCount": 0, + "bodyEndpointCount": 2, "endpoints": [ - "DELETE /certificates/certificate-groups", - "GET /certificates/certificate-groups" + "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 +15186,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 +15251,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 +15273,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 +15295,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 +15405,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 +15515,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 +15548,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 +15592,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 +15762,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 +15862,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 +15923,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,42 +16043,42 @@ "annotations": [] }, { - "name": "common_name", + "name": "client_certificates_required", "endpointCount": 1, "queryEndpointCount": 0, "bodyEndpointCount": 1, "endpoints": [ - "POST /certificates" + "POST /tls-policies" ], "annotations": [] }, { - "name": "confirm_date", + "name": "computer_name", "endpointCount": 1, - "queryEndpointCount": 1, - "bodyEndpointCount": 0, + "queryEndpointCount": 0, + "bodyEndpointCount": 1, "endpoints": [ - "POST /lifecycle-rules" + "POST /active-directory" ], "annotations": [] }, { - "name": "contact", + "name": "confirm_date", "endpointCount": 1, - "queryEndpointCount": 0, - "bodyEndpointCount": 1, + "queryEndpointCount": 1, + "bodyEndpointCount": 0, "endpoints": [ - "PATCH /quotas/settings" + "POST /lifecycle-rules" ], "annotations": [] }, { - "name": "country", + "name": "contact", "endpointCount": 1, "queryEndpointCount": 0, "bodyEndpointCount": 1, "endpoints": [ - "POST /certificates" + "PATCH /quotas/settings" ], "annotations": [] }, @@ -14030,12 +16103,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 +16123,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 +16152,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 +16182,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 +16223,12 @@ "annotations": [] }, { - "name": "email", + "name": "enabled_tls_ciphers", "endpointCount": 1, "queryEndpointCount": 0, "bodyEndpointCount": 1, "endpoints": [ - "POST /certificates" + "POST /tls-policies" ], "annotations": [] }, @@ -14139,6 +16242,16 @@ ], "annotations": [] }, + { + "name": "encryption_types", + "endpointCount": 1, + "queryEndpointCount": 0, + "bodyEndpointCount": 1, + "endpoints": [ + "POST /active-directory" + ], + "annotations": [] + }, { "name": "enforce_dictionary_check", "endpointCount": 1, @@ -14219,6 +16332,16 @@ ], "annotations": [] }, + { + "name": "fqdns", + "endpointCount": 1, + "queryEndpointCount": 0, + "bodyEndpointCount": 1, + "endpoints": [ + "POST /active-directory" + ], + "annotations": [] + }, { "name": "fragment_packet", "endpointCount": 1, @@ -14249,6 +16372,16 @@ ], "annotations": [] }, + { + "name": "global_catalog_servers", + "endpointCount": 1, + "queryEndpointCount": 0, + "bodyEndpointCount": 1, + "endpoints": [ + "POST /active-directory" + ], + "annotations": [] + }, { "name": "group", "endpointCount": 1, @@ -14269,6 +16402,16 @@ ], "annotations": [] }, + { + "name": "host", + "endpointCount": 1, + "queryEndpointCount": 0, + "bodyEndpointCount": 1, + "endpoints": [ + "POST /snmp-managers" + ], + "annotations": [] + }, { "name": "idle_timeout", "endpointCount": 1, @@ -14279,6 +16422,16 @@ ], "annotations": [] }, + { + "name": "ignore_usage", + "endpointCount": 1, + "queryEndpointCount": 1, + "bodyEndpointCount": 0, + "endpoints": [ + "PATCH /buckets" + ], + "annotations": [] + }, { "name": "interfaces", "endpointCount": 1, @@ -14309,6 +16462,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 +16532,16 @@ ], "annotations": [] }, + { + "name": "kerberos_servers", + "endpointCount": 1, + "queryEndpointCount": 0, + "bodyEndpointCount": 1, + "endpoints": [ + "POST /active-directory" + ], + "annotations": [] + }, { "name": "key_algorithm", "endpointCount": 1, @@ -14419,6 +16602,16 @@ ], "annotations": [] }, + { + "name": "latest_replica", + "endpointCount": 1, + "queryEndpointCount": 1, + "bodyEndpointCount": 0, + "endpoints": [ + "PATCH /file-system-snapshots" + ], + "annotations": [] + }, { "name": "link_type", "endpointCount": 1, @@ -14479,16 +16672,6 @@ ], "annotations": [] }, - { - "name": "locality", - "endpointCount": 1, - "queryEndpointCount": 0, - "bodyEndpointCount": 1, - "endpoints": [ - "POST /certificates" - ], - "annotations": [] - }, { "name": "log_name_prefix", "endpointCount": 1, @@ -14510,32 +16693,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 +16792,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 +16843,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 +16902,16 @@ ], "annotations": [] }, + { + "name": "password", + "endpointCount": 1, + "queryEndpointCount": 0, + "bodyEndpointCount": 1, + "endpoints": [ + "POST /active-directory" + ], + "annotations": [] + }, { "name": "password_history", "endpointCount": 1, @@ -14719,6 +16952,16 @@ ], "annotations": [] }, + { + "name": "ports", + "endpointCount": 1, + "queryEndpointCount": 0, + "bodyEndpointCount": 1, + "endpoints": [ + "POST /link-aggregation-groups" + ], + "annotations": [] + }, { "name": "prefix", "endpointCount": 1, @@ -14799,6 +17042,16 @@ ], "annotations": [] }, + { + "name": "public_access_config", + "endpointCount": 1, + "queryEndpointCount": 0, + "bodyEndpointCount": 1, + "endpoints": [ + "PATCH /buckets" + ], + "annotations": [] + }, { "name": "public_key", "endpointCount": 1, @@ -14819,6 +17072,16 @@ ], "annotations": [] }, + { + "name": "qos_policy", + "endpointCount": 1, + "queryEndpointCount": 0, + "bodyEndpointCount": 1, + "endpoints": [ + "PATCH /buckets" + ], + "annotations": [] + }, { "name": "quota_limit", "endpointCount": 1, @@ -14939,6 +17202,16 @@ ], "annotations": [] }, + { + "name": "remove_rules", + "endpointCount": 1, + "queryEndpointCount": 0, + "bodyEndpointCount": 1, + "endpoints": [ + "PATCH /policies" + ], + "annotations": [] + }, { "name": "required_transport_security", "endpointCount": 1, @@ -14950,12 +17223,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 +17262,16 @@ ], "annotations": [] }, + { + "name": "service_principal_names", + "endpointCount": 1, + "queryEndpointCount": 0, + "bodyEndpointCount": 1, + "endpoints": [ + "POST /active-directory" + ], + "annotations": [] + }, { "name": "session_names", "endpointCount": 1, @@ -15019,6 +17302,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 +17323,12 @@ "annotations": [] }, { - "name": "source", + "name": "smb", "endpointCount": 1, "queryEndpointCount": 0, "bodyEndpointCount": 1, "endpoints": [ - "POST /keytabs" + "PATCH /directory-services" ], "annotations": [] }, @@ -15050,22 +17343,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 +17382,16 @@ ], "annotations": [] }, + { + "name": "trusted_client_certificate_authority", + "endpointCount": 1, + "queryEndpointCount": 0, + "bodyEndpointCount": 1, + "endpoints": [ + "POST /tls-policies" + ], + "annotations": [] + }, { "name": "type", "endpointCount": 1, @@ -15110,32 +17413,42 @@ "annotations": [] }, { - "name": "v2c", + "name": "uri", "endpointCount": 1, "queryEndpointCount": 0, "bodyEndpointCount": 1, "endpoints": [ - "PATCH /snmp-agents" + "POST /syslog-servers" ], "annotations": [] }, { - "name": "v3", + "name": "uris", "endpointCount": 1, "queryEndpointCount": 0, "bodyEndpointCount": 1, "endpoints": [ - "PATCH /snmp-agents" + "PATCH /directory-services" ], "annotations": [] }, { - "name": "version", + "name": "user", + "endpointCount": 1, + "queryEndpointCount": 0, + "bodyEndpointCount": 1, + "endpoints": [ + "POST /active-directory" + ], + "annotations": [] + }, + { + "name": "verify_client_certificate_trust", "endpointCount": 1, "queryEndpointCount": 0, "bodyEndpointCount": 1, "endpoints": [ - "PATCH /snmp-agents" + "POST /tls-policies" ], "annotations": [] }, @@ -15153,7 +17466,7 @@ "conventionStrength": [ { "name": "names", - "cmdletCount": 301, + "cmdletCount": 316, "cmdlets": [ "Get-PfbActiveDirectory", "Get-PfbAdmin", @@ -15265,10 +17578,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 +17593,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 +17610,7 @@ "New-PfbNetworkInterface", "New-PfbNfsExportPolicy", "New-PfbNlmReclamation", + "New-PfbNodeGroup", "New-PfbObjectStoreAccessPolicy", "New-PfbObjectStoreAccessPolicyRule", "New-PfbObjectStoreAccount", @@ -15310,9 +17630,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 +17730,7 @@ "Update-PfbBucketAuditFilter", "Update-PfbCertificate", "Update-PfbDataEvictionPolicy", + "Update-PfbDirectoryService", "Update-PfbDirectoryServiceRole", "Update-PfbDns", "Update-PfbFileSystem", @@ -15460,7 +17788,7 @@ }, { "name": "ids", - "cmdletCount": 220, + "cmdletCount": 221, "cmdlets": [ "Get-PfbAdmin", "Get-PfbAdminCache", @@ -15549,6 +17877,7 @@ "Get-PfbSyslogServer", "Get-PfbTarget", "Get-PfbTlsPolicy", + "Get-PfbUserGroupQuotaPolicy", "Get-PfbUserGroupQuotaPolicyRule", "Get-PfbWorkload", "Get-PfbWorkloadPlacementRecommendation", @@ -16591,6 +18920,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 +18981,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, @@ -16706,25 +19095,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 +19114,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 +19187,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 +19212,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 +19255,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 +19307,15 @@ "New-PfbNetworkInterface" ] }, + { + "name": "address", + "cmdletCount": 3, + "cmdlets": [ + "New-PfbNetworkInterface", + "Update-PfbNetworkInterface", + "Update-PfbTarget" + ] + }, { "name": "attached_servers", "cmdletCount": 3, @@ -17030,6 +19433,15 @@ "New-PfbFileSystem" ] }, + { + "name": "uids", + "cmdletCount": 3, + "cmdlets": [ + "Get-PfbFileSystemUser", + "Get-PfbFileSystemUserQuota", + "New-PfbQuotaUser" + ] + }, { "name": "actions", "cmdletCount": 2, @@ -17054,6 +19466,14 @@ "New-PfbCertificateCertificateGroup" ] }, + { + "name": "domain", + "cmdletCount": 2, + "cmdlets": [ + "New-PfbLocalDirectoryService", + "Update-PfbDns" + ] + }, { "name": "effect", "cmdletCount": 2, @@ -17070,14 +19490,6 @@ "Update-PfbCertificate" ] }, - { - "name": "gids", - "cmdletCount": 2, - "cmdlets": [ - "Get-PfbFileSystemGroup", - "Get-PfbFileSystemGroupQuota" - ] - }, { "name": "idp", "cmdletCount": 2, @@ -17151,19 +19563,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 +19612,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 +19662,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 +19787,13 @@ "New-PfbFleetMember" ] }, + { + "name": "fqdns", + "cmdletCount": 1, + "cmdlets": [ + "Update-PfbActiveDirectory" + ] + }, { "name": "full_control", "cmdletCount": 1, @@ -17334,6 +19801,13 @@ "New-PfbSmbShareRule" ] }, + { + "name": "global_catalog_servers", + "cmdletCount": 1, + "cmdlets": [ + "Update-PfbActiveDirectory" + ] + }, { "name": "group", "cmdletCount": 1, @@ -17348,6 +19822,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 +19850,13 @@ "Update-PfbCertificate" ] }, + { + "name": "join_ou", + "cmdletCount": 1, + "cmdlets": [ + "Update-PfbActiveDirectory" + ] + }, { "name": "keep_current_version_for", "cmdletCount": 1, @@ -17390,6 +19885,13 @@ "Update-PfbLifecycleRule" ] }, + { + "name": "kerberos_servers", + "cmdletCount": 1, + "cmdlets": [ + "Update-PfbActiveDirectory" + ] + }, { "name": "key_algorithm", "cmdletCount": 1, @@ -17446,6 +19948,13 @@ "Update-PfbAdmin" ] }, + { + "name": "max_retention", + "cmdletCount": 1, + "cmdlets": [ + "Update-PfbWormPolicy" + ] + }, { "name": "max_role", "cmdletCount": 1, @@ -17474,6 +19983,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 +20011,13 @@ "Get-PfbFileSystemReplicaLinkTransfer" ] }, + { + "name": "nameservers", + "cmdletCount": 1, + "cmdlets": [ + "Update-PfbDns" + ] + }, { "name": "node_group_ids", "cmdletCount": 1, @@ -17509,6 +20046,13 @@ "New-PfbNodeGroupNode" ] }, + { + "name": "notification", + "cmdletCount": 1, + "cmdlets": [ + "Update-PfbSnmpManager" + ] + }, { "name": "organization", "cmdletCount": 1, @@ -17537,6 +20081,20 @@ "Update-PfbCertificate" ] }, + { + "name": "password", + "cmdletCount": 1, + "cmdlets": [ + "Update-PfbAdmin" + ] + }, + { + "name": "ports", + "cmdletCount": 1, + "cmdlets": [ + "Update-PfbLag" + ] + }, { "name": "principal", "cmdletCount": 1, @@ -17558,6 +20116,13 @@ "Get-PfbArrayPerformance" ] }, + { + "name": "qos_policy", + "cmdletCount": 1, + "cmdlets": [ + "New-PfbFileSystem" + ] + }, { "name": "rdma_enabled", "cmdletCount": 1, @@ -17600,6 +20165,13 @@ "Update-PfbWormPolicy" ] }, + { + "name": "retrieval_rules", + "cmdletCount": 1, + "cmdlets": [ + "Update-PfbStorageClassTieringPolicy" + ] + }, { "name": "secret_access_key", "cmdletCount": 1, @@ -17621,6 +20193,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 +20221,13 @@ "Update-PfbCertificate" ] }, + { + "name": "static_authorized_principals", + "cmdletCount": 1, + "cmdlets": [ + "Update-PfbSshCaPolicy" + ] + }, { "name": "subject_alternative_names", "cmdletCount": 1, @@ -17649,6 +20242,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 +20284,13 @@ "Update-PfbSnmpManager" ] }, + { + "name": "verify_client_certificate_trust", + "cmdletCount": 1, + "cmdlets": [ + "Update-PfbTlsPolicy" + ] + }, { "name": "version", "cmdletCount": 1, @@ -17670,6 +20298,11 @@ "Update-PfbSnmpManager" ] }, + { + "name": "access_based_enumeration_enabled", + "cmdletCount": 0, + "cmdlets": [] + }, { "name": "access_policies", "cmdletCount": 0, @@ -17685,6 +20318,16 @@ "cmdletCount": 0, "cmdlets": [] }, + { + "name": "add_log_targets", + "cmdletCount": 0, + "cmdlets": [] + }, + { + "name": "add_rules", + "cmdletCount": 0, + "cmdlets": [] + }, { "name": "allow_errors", "cmdletCount": 0, @@ -17720,6 +20363,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 +20388,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 +20408,11 @@ "cmdletCount": 0, "cmdlets": [] }, + { + "name": "computer_name", + "cmdletCount": 0, + "cmdlets": [] + }, { "name": "conditions", "cmdletCount": 0, @@ -17750,6 +20423,11 @@ "cmdletCount": 0, "cmdlets": [] }, + { + "name": "control_type", + "cmdletCount": 0, + "cmdlets": [] + }, { "name": "current_state", "cmdletCount": 0, @@ -17765,6 +20443,11 @@ "cmdletCount": 0, "cmdlets": [] }, + { + "name": "destroy_snapshots", + "cmdletCount": 0, + "cmdlets": [] + }, { "name": "direct_notifications_enabled", "cmdletCount": 0, @@ -17785,6 +20468,11 @@ "cmdletCount": 0, "cmdlets": [] }, + { + "name": "disruptive", + "cmdletCount": 0, + "cmdlets": [] + }, { "name": "edge_agent_update_enabled", "cmdletCount": 0, @@ -17860,11 +20548,6 @@ "cmdletCount": 0, "cmdlets": [] }, - { - "name": "hard_limit_enabled", - "cmdletCount": 0, - "cmdlets": [] - }, { "name": "idle_timeout", "cmdletCount": 0, @@ -17885,6 +20568,11 @@ "cmdletCount": 0, "cmdlets": [] }, + { + "name": "join_existing_account", + "cmdletCount": 0, + "cmdlets": [] + }, { "name": "keytab_file", "cmdletCount": 0, @@ -17905,6 +20593,11 @@ "cmdletCount": 0, "cmdlets": [] }, + { + "name": "latest_replica", + "cmdletCount": 0, + "cmdlets": [] + }, { "name": "link_type", "cmdletCount": 0, @@ -17945,6 +20638,11 @@ "cmdletCount": 0, "cmdlets": [] }, + { + "name": "log_targets", + "cmdletCount": 0, + "cmdlets": [] + }, { "name": "max_login_attempts", "cmdletCount": 0, @@ -17990,6 +20688,11 @@ "cmdletCount": 0, "cmdlets": [] }, + { + "name": "nfs", + "cmdletCount": 0, + "cmdlets": [] + }, { "name": "ntp_servers", "cmdletCount": 0, @@ -18000,6 +20703,11 @@ "cmdletCount": 0, "cmdlets": [] }, + { + "name": "owner", + "cmdletCount": 0, + "cmdlets": [] + }, { "name": "owner_ids", "cmdletCount": 0, @@ -18060,6 +20768,11 @@ "cmdletCount": 0, "cmdlets": [] }, + { + "name": "public_access_config", + "cmdletCount": 0, + "cmdlets": [] + }, { "name": "purity_defined", "cmdletCount": 0, @@ -18110,6 +20823,16 @@ "cmdletCount": 0, "cmdlets": [] }, + { + "name": "remove_log_targets", + "cmdletCount": 0, + "cmdlets": [] + }, + { + "name": "remove_rules", + "cmdletCount": 0, + "cmdlets": [] + }, { "name": "resolve_hostname", "cmdletCount": 0, @@ -18150,6 +20873,11 @@ "cmdletCount": 0, "cmdlets": [] }, + { + "name": "smb", + "cmdletCount": 0, + "cmdlets": [] + }, { "name": "snapshot_configurations", "cmdletCount": 0, @@ -18165,6 +20893,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..46740ce5 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: 354 +- Missing body properties (addable): 395 +- 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): 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): 10 +- 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,24 @@ 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 /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 +309,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 +321,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 +348,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 +364,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 +400,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 +436,18 @@ 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 /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 +462,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..12059790 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": 1780, + "ok": 1695, + "deadKey": 85, "skipReasons": { - "wire name unresolved": 127, - "body property": 280, + "wire name unresolved": 31, + "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..ce3391e8 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", @@ -17487,6 +17917,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 +17967,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 +18107,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 +18127,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 +18867,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 +19027,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 +19447,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 +19589,8 @@ }, { "cmdlet": "Update-PfbQuotaGroup", - "parameter": "Quota", - "wireName": "quota", + "parameter": "FileSystemName", + "wireName": "file_system_names", "status": "no-spec-enum-found", "matchedKey": null, "specValues": null, @@ -19088,9 +19598,9 @@ "recommendation": null }, { - "cmdlet": "Update-PfbQuotaUser", - "parameter": "Quota", - "wireName": "quota", + "cmdlet": "Update-PfbQuotaGroup", + "parameter": "GroupId", + "wireName": "gids", "status": "no-spec-enum-found", "matchedKey": null, "specValues": null, @@ -19098,8 +19608,68 @@ "recommendation": null }, { - "cmdlet": "Update-PfbRealm", - "parameter": "Id", + "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, + "stableSinceOldestVersion": null, + "recommendation": null + }, + { + "cmdlet": "Update-PfbQuotaUser", + "parameter": "Quota", + "wireName": "quota", + "status": "no-spec-enum-found", + "matchedKey": null, + "specValues": null, + "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", "wireName": "ids", "status": "no-spec-enum-found", "matchedKey": null, @@ -19147,6 +19717,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 +19897,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 +19937,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 +20489,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 +20599,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 +20721,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,142 +20785,10 @@ "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-PfbFileSystem", "parameter": "NfsEnabled" @@ -20348,268 +20812,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..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: 1974 +- no-spec-enum-found: 2036 | 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): 23 -- `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,61 @@ 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-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..f0afa76c 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": 1241, + "candidatePairs": 655, + "candidateRate": 0.5278, + "findings": 266, + "findingPairs": 102, + "confirmationRate": 0.4061, "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": 655 }, { "Gate": "Matched", - "Count": 584 + "Count": 586 }, { "Gate": "SelectorUnresolved", - "Count": 34 + "Count": 6 } ], "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.", @@ -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..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` | 1213 | -| `candidatePairs` | 629 | -| `candidateRate` | 0.5185 | -| `findings` | 264 | -| `findingPairs` | 101 | -| `confirmationRate` | 0.4197 | +| `evaluatedPairs` | 1241 | +| `candidatePairs` | 655 | +| `candidateRate` | 0.5278 | +| `findings` | 266 | +| `findingPairs` | 102 | +| `confirmationRate` | 0.4061 | | `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-PfbDeadKeyReport.Tests.ps1 b/Tests/Build-PfbDeadKeyReport.Tests.ps1 index 20000aaf..e07b1292 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 @@ -73,6 +76,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 +155,117 @@ 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 ', ')]" + + # -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 ', ')]" + } + + 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. + # + # 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 + # 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 ', ')]") + } + 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 +301,181 @@ 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' } + } + } + # 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' } + # 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]@{ + 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' } + } + } + } + } + } + # 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 + # measuring the wrong bucket. + "/api/$fixtureVersion/synthetic/allow" = [PSCustomObject]@{ + 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' } + ) + } + } "/api/$fixtureVersion/synthetic/dead" = [PSCustomObject]@{ delete = [PSCustomObject]@{ parameters = @( @@ -215,6 +509,64 @@ 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. + # + # 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 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, 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 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 + # 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 + 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 @@ -273,6 +625,120 @@ 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 +} +'@ + + # 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' @@ -289,6 +755,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 +811,163 @@ 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. + # + # 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 -- 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, 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 ', ')]" + } + + 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. + # 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 ', ')] 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' { + # 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" + # -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') + + # 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' { + # 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/Tests/Build-PfbFieldCmdletMap.Tests.ps1 b/Tests/Build-PfbFieldCmdletMap.Tests.ps1 index e56c6255..ffe9658b 100644 --- a/Tests/Build-PfbFieldCmdletMap.Tests.ps1 +++ b/Tests/Build-PfbFieldCmdletMap.Tests.ps1 @@ -282,6 +282,215 @@ 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 "## 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' + (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/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..d71ae3f3 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') } @@ -286,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) @@ -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,22 @@ 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, 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 # 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..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 @@ -7,11 +8,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 -> 655 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 +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 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 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. @@ -37,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 @@ -70,205 +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.' } - @{ Cmdlet = 'Get-PfbWorkload'; Parameter = 'Name'; Scope = 'Family'; Issue = '#90'; Producers = 1 + # 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 = '#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 = '#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/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/Tests/PfbCmdletParamTools.Tests.ps1 b/Tests/PfbCmdletParamTools.Tests.ps1 index 9785bae1..866f7775 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 } '@ @@ -308,6 +312,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 --------------------------------------- @@ -404,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' { @@ -627,6 +713,154 @@ 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) + $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 + 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 (member name is ToString, not the STATIC guard)'; Expression = '$allNames.ToArray().ToString()' } + @{ Shape = 'a member-access target'; Expression = '$obj.Items.ToArray()' } + # 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 + } + } +} + 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 @@ -821,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' } @@ -843,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' } @@ -867,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 } } @@ -1278,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 } @@ -1315,8 +1571,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' @@ -1336,13 +1594,96 @@ 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 } } +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($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 + } + } + + 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 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 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]$Param) $' + $TargetVariable + ' = @{ ''' + $WireName + ''' = ' + $Value + ' } }' + $funcAst = Get-TestBooleanWireFunctionAst $source + $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 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"' } + ) { + $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' @@ -1372,3 +1713,1762 @@ 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 + } + + 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 + } + + 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)' { + # 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' + } +} + +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 + } +} + +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/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 862fa8a9..04fd96d7 100644 --- a/Tests/coverage-baseline.psd1 +++ b/Tests/coverage-baseline.psd1 @@ -222,6 +222,26 @@ # 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, 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", + # 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).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, # because the generator or spec-walking code under test needs pwsh 7. They RUN on 7 -- @@ -240,10 +260,26 @@ 'Update-PfbTestModuleImport.Tests.ps1' = 34 'PfbPipelineSelectorTools.Tests.ps1' = 33 'PfbSelectorProbeHarness.Tests.ps1' = 16 - 'Build-PfbFieldCmdletMap.Tests.ps1' = 15 + # 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. 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 + # 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/Build-PfbDeadKeyReport.ps1 b/tools/Build-PfbDeadKeyReport.ps1 index ad8e469e..cf56b11e 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()] @@ -163,16 +170,214 @@ 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. 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. 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: + 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 +416,59 @@ 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, 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. 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. + # + # 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) { + 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 +557,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') | diff --git a/tools/Build-PfbFieldCmdletMap.ps1 b/tools/Build-PfbFieldCmdletMap.ps1 index 15ed1d9b..1f5f0eef 100644 --- a/tools/Build-PfbFieldCmdletMap.ps1 +++ b/tools/Build-PfbFieldCmdletMap.ps1 @@ -78,6 +78,42 @@ $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 +# 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 } }) + +# 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 +136,7 @@ $manifest = [ordered]@{ entries = $entries attributesOnly = $attributesOnly typedUnresolved = $typedUnresolved + notApplicable = $notApplicable } $outputDir = Split-Path -Parent $OutputPath @@ -147,6 +184,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("## 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: 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('') Set-Content -Path $ReportPath -Value ($mdLines -join "`n") -Encoding UTF8 Write-Host "Wrote $($entries.Count) entries to $OutputPath and $ReportPath" -ForegroundColor Green diff --git a/tools/Compare-PfbInventoryTuple.ps1 b/tools/Compare-PfbInventoryTuple.ps1 new file mode 100644 index 00000000..eec96d1f --- /dev/null +++ b/tools/Compare-PfbInventoryTuple.ps1 @@ -0,0 +1,224 @@ +#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 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. + + 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 -- 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 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/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 + 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 + 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 + # 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/.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( + [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 + +# 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) + + $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) +} + +# 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 + + $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'." } + & $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 + + $result = Compare-PfbInventoryTupleSet -Baseline $baselineRows -Current $currentRows -DeclaredChange $declarations + + Write-Host "baseline ref : $BaselineRef (from $refSource)" + 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)" } + + # 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 + } + 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..161cc3cd 100644 --- a/tools/README.md +++ b/tools/README.md @@ -307,6 +307,23 @@ 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 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 + 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 @@ -450,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/.json + ``` + ## Response-shape drift (`Build-PfbResponseShapeMap.ps1`) Everything above tracks the **request** side — which endpoints, query parameters, and @@ -674,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/.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 diff --git a/tools/inventory-tuple-baselines/landed/issue-141-task4.json b/tools/inventory-tuple-baselines/landed/issue-141-task4.json new file mode 100644 index 00000000..bcca4fbf --- /dev/null +++ b/tools/inventory-tuple-baselines/landed/issue-141-task4.json @@ -0,0 +1,177 @@ +{ + "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||" + } + ] +} 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) diff --git a/tools/lib/PfbCmdletParamTools.ps1 b/tools/lib/PfbCmdletParamTools.ps1 index 58edc997..b45a4ee4 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 @@ -157,6 +174,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 +198,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 +230,38 @@ 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 (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 (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] + if ($parenthesizedRoot) { + $chainRoot = Resolve-PfbSingleExpression -Ast $parenthesizedRoot.Pipeline + $boolCast = $chainRoot -as [System.Management.Automation.Language.ConvertExpressionAst] + } + } + } + } + + 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 } + } + } + } + if ($IsBooleanLikeParameter -and $expr -is [System.Management.Automation.Language.StringConstantExpressionAst]) { if (Test-PfbAssignmentGuardedBySwitch -Assignment $ValueAst -ParameterName $ParameterName) { return $true } } @@ -344,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 @@ -372,6 +431,53 @@ 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 } + # 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 } + + $target = $invoke.Expression -as [System.Management.Automation.Language.VariableExpressionAst] + if (-not $target) { return $null } + return $target.VariablePath.UserPath +} + function Get-PfbCommonQueryParamHelperWireName { <# .SYNOPSIS @@ -382,8 +488,33 @@ 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. + + 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 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 + 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. @@ -435,7 +566,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 } } } @@ -459,16 +595,295 @@ function Get-PfbCommonQueryParamHelperWireName { return [PSCustomObject]@{ WireName = $parts[0]; TargetVariable = $parts[1] } } -function Get-PfbWireNameForParameter { +function Resolve-PfbWireLandingArbitration { <# .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. + 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 } + + # 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) { + $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 + } + + $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 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 + 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; + 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. + + 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 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. + + 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. 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 } -- 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. + [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( @@ -487,38 +902,76 @@ 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) { + # 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) } } } + # $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) { + # 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 # New-Pfb*/Remove-Pfb*/Update-Pfb* (New-PfbApiClient, New-PfbAlertWatcher, # 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 } + 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. - $nestedMatch = Get-PfbNestedReferenceWireNameForParameter -FunctionAst $FunctionAst -ParameterName $ParameterName -IsBooleanLikeParameter:$IsBooleanLikeParameter - if ($nestedMatch) { return $nestedMatch } + # 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, @@ -526,7 +979,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. - return Get-PfbCommonQueryParamHelperWireName -FunctionAst $FunctionAst -ParameterName $ParameterName + 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) + } +} + +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 { @@ -545,8 +1051,41 @@ 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. 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( @@ -565,9 +1104,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 } @@ -580,15 +1120,13 @@ 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 + return $landings.ToArray() } function Get-PfbNestedReferenceWireNameForParameter { @@ -609,8 +1147,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 @@ -629,8 +1168,39 @@ 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. 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( @@ -664,30 +1234,30 @@ 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. + $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 $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 } @@ -697,15 +1267,13 @@ 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 + return $literalLandings.ToArray() } function Find-PfbAccumulatorVariable { @@ -787,22 +1355,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( @@ -813,56 +1406,148 @@ 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. + # + # 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] + 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. + # + # 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 541 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, $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 { @@ -1001,6 +1686,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 @@ -1010,14 +1808,30 @@ 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. - - 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. + 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 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 @@ -1049,6 +1863,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 @@ -1070,8 +1887,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 @@ -1079,21 +1906,28 @@ function Get-PfbCmdletParameterInventory { } $wireName = if ($wireInfo) { $wireInfo.WireName } else { $null } - $endpointInfo = if ($wireInfo) { Get-PfbEndpointForVariable -FunctionAst $funcAst -TargetVariable $wireInfo.TargetVariable } 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' } - # 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 @@ -1106,8 +1940,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 } }) } } @@ -1129,3 +1963,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) + } +}