Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ jobs:
if ($resultFiles.Count -gt 1) {
throw "Multiple NUnit result files produced: $($resultFiles.Name -join ', ')"
}
pwsh -File ./tests/QA/Assert-GateResult.ps1 -ResultPath $resultFiles[0].FullName -MinimumTests 1482 -AllowedSkips 0
pwsh -File ./tests/QA/Assert-GateResult.ps1 -ResultPath $resultFiles[0].FullName -MinimumTests 1486 -AllowedSkips 0
if ($LASTEXITCODE -ne 0) {
throw 'The standalone whole-result gate failed.'
}
Expand Down
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ metadata.

Run the suite through `./build.ps1 -Tasks test`, never `Invoke-Pester ./tests` directly: the changelog checks are Sampler-generated and depend on build-injected variables, so a bare Pester run reports two false failures.

**Remote CI contract.** `.github/workflows/ci.yml` runs PowerShell 7.4 and 7.6 across Windows, Ubuntu, and macOS. A source revision is CI-verified only when all six matrix jobs pass for that exact SHA; workflow existence or an older successful run is not evidence. The published `0.3.0` evidence is 772 deterministic tests. The post-release development tree requires 1482 deterministic tests under `./build.ps1 -Tasks test`, with zero failures, errors, skips, or NotRun tests, and `tests/QA/Assert-GateResult.ps1` enforces the same minimum-count floor used by CI and package verification. The synchronization test independently discovers the suite and removes only the repository's explicit platform-only surplus before accepting that shared floor, so agreeing stale copies are not sufficient.
**Remote CI contract.** `.github/workflows/ci.yml` runs PowerShell 7.4 and 7.6 across Windows, Ubuntu, and macOS. A source revision is CI-verified only when all six matrix jobs pass for that exact SHA; workflow existence or an older successful run is not evidence. The published `0.3.0` evidence is 772 deterministic tests. The post-release development tree requires 1486 deterministic tests under `./build.ps1 -Tasks test`, with zero failures, errors, skips, or NotRun tests, and `tests/QA/Assert-GateResult.ps1` enforces the same minimum-count floor used by CI and package verification. The synchronization test independently discovers the suite and removes only the repository's explicit platform-only surplus before accepting that shared floor, so agreeing stale copies are not sufficient.

**Phase 5 (cutover) implementation and Ivy24 verification are complete.** All eight steps ran and were verified against the Ivy24 lab tenant: legacy-caller inventory, `Import-GraphLegacyProfile`, a private versioned package channel with publish/pin/install, a live read through the *installed* package, a GraphKit-backed data plane in IHA behind a default-off flag, reads and a reverted mutating write through it, and a full credential-generation rollover ending in the old generation's revocation. Catalog coverage of IHA's declared surface is 27 of 27 at the API version it actually calls. The 2026-08-15 cutover record preserved two operator actions because active customer repointing would have required the legacy fallback to remain. The owner has since confirmed that there are no installed users, legacy consumers, customer-tenant consumers, or repoint targets. R9 legacy import/migration, customer repointing, rollback-window operation, legacy-layer retirement, and deleted-directory purge are therefore **NotApplicable** and must not be executed merely to manufacture closeout evidence. Reusable `New-GraphAppRegistration` provisioning and actual role-grant verification remain applicable product work. Reopen adopter-specific gates only if an adopter is later identified. Read `docs/cutover/2026-08-15-phase5-cutover.md` before revisiting the historical cutover.

Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- Beta read-only descriptors for per-token Apple enrollment profiles and selected per-device
managed-device hardware/health detail needed by the TenantPulse IHA successor. Both retain
explicit beta metadata and separate live permission/response-shape verification gates.
- The compiled `GraphKit.Auth` authentication boundary. A dependency-free
`GraphKit.Auth.Contracts.dll` owns the GraphKit ABI-v1 DTOs, interfaces, strict loader,
proxies, and host lifetime and loads in the default `AssemblyLoadContext`. `GraphKit.Auth.dll`
Expand Down
2 changes: 1 addition & 1 deletion scripts/New-GraphKitTestedReleaseProof.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ param(
$ErrorActionPreference = 'Stop'
Set-StrictMode -Version 3.0

$minimumTests = 1482
$minimumTests = 1486
$allowedSkips = 0
$allowedNotRun = 0

Expand Down
2 changes: 1 addition & 1 deletion scripts/Test-GraphKitReleaseProof.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ param(
$ErrorActionPreference = 'Stop'
Set-StrictMode -Version 3.0

$minimumTests = 1482
$minimumTests = 1486
$allowedSkips = 0
$allowedNotRun = 0

Expand Down
56 changes: 56 additions & 0 deletions source/Data/Operations/AppleEnrollmentProfile.ListByToken.psd1
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<#
Operation descriptor - data only. Loaded with Import-PowerShellDataFile.

Lists the Apple Automated Device Enrollment profiles attached to one DEP onboarding
token. Microsoft exposes this relationship only in beta. The token id is explicit in
the path so callers cannot accidentally treat profiles from different Apple tokens as
one tenant-wide collection.

The descriptor and deterministic response contract are implemented from Microsoft's
documented path and application permission. Live service permission/shape verification
remains a separate release gate.
#>
@{
SchemaVersion = 1

Type = 'AppleEnrollmentProfile'
Operation = 'ListByToken'
OperationKind = 'Collection'
HandlerStrategyId = 'Collection.Default'

ApiVersion = 'beta'
Stability = 'BetaOnly'
BetaReason = 'Apple DEP enrollment profiles are exposed only beneath the beta depOnboardingSettings relationship.'

Method = 'GET'
PathTemplate = '/deviceManagement/depOnboardingSettings/{depOnboardingSettingId}/enrollmentProfiles'
RequestBodyKind = $null
ResponseKind = 'Json'
PagingStrategy = 'NextLink'
RequiredPagingHeaders = @()
DeduplicationKey = 'id'
SupportsAll = $false
SupportsDelta = $false

ReplayPolicy = 'Safe'
Condition = $null
Reconciliation = $null

AdvancedQuery = @{ Supported = $false }
Concurrency = @{ Mode = 'None'; Header = $null; Required = $false; AllowWildcard = $false }

CredentialPolicy = 'GraphBearer'
AllowedHosts = @()
RedirectPolicy = 'None'
IdentityRequirement = 'Verified'

ResourceFamily = 'Intune.Enrollment'
ThrottleClass = 'Read'

SupportedAuthModes = @('Certificate', 'ClientSecret', 'BearerToken', 'ManagedIdentity')
RequiredPermissions = @(
@{ Type = 'Application'; Value = 'DeviceManagementServiceConfig.Read.All' }
)
RequiredLicense = @('Microsoft Intune')
SupportedClouds = @('Global', 'USGov', 'USGovDoD')
}
56 changes: 56 additions & 0 deletions source/Data/Operations/ManagedDevice.GetBeta.psd1
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<#
Operation descriptor - data only. Loaded with Import-PowerShellDataFile.

Beta singleton sibling used when a caller needs the full per-device hardware and
health-attestation shape. Microsoft documents that many hardwareInformation values are
default/null on collection reads and require a GET for the device id with the property
included in $select. This operation pins the minimum safe detail projection in its path,
along with the service version, permission, and read semantics; callers cannot broaden it
into credential-like properties through arbitrary query input.

Live response/permission verification remains separate from this deterministic contract.
#>
@{
SchemaVersion = 1

Type = 'ManagedDevice'
Operation = 'GetBeta'
OperationKind = 'Singleton'
HandlerStrategyId = 'Singleton.Default'

ApiVersion = 'beta'
Stability = 'DualVersion'
BetaReason = 'The beta singleton carries the detailed hardware and device-health shape required by the IHA successor.'

Method = 'GET'
PathTemplate = '/deviceManagement/managedDevices/{id}?$select=id,hardwareInformation,deviceHealthAttestationState,physicalMemoryInBytes,processorArchitecture,skuFamily,skuNumber,managementFeatures,roleScopeTagIds,ethernetMacAddress,bootstrapTokenEscrowed'
RequestBodyKind = $null
ResponseKind = 'Json'
PagingStrategy = 'None'
RequiredPagingHeaders = @()
DeduplicationKey = $null
SupportsAll = $false
SupportsDelta = $false

ReplayPolicy = 'Safe'
Condition = $null
Reconciliation = $null

AdvancedQuery = @{ Supported = $false }
Concurrency = @{ Mode = 'None'; Header = $null; Required = $false; AllowWildcard = $false }

CredentialPolicy = 'GraphBearer'
AllowedHosts = @()
RedirectPolicy = 'None'
IdentityRequirement = 'Verified'

ResourceFamily = 'Intune.ManagedDevices'
ThrottleClass = 'Read'

SupportedAuthModes = @('Certificate', 'ClientSecret', 'BearerToken', 'ManagedIdentity')
RequiredPermissions = @(
@{ Type = 'Application'; Value = 'DeviceManagementManagedDevices.Read.All' }
)
RequiredLicense = @('Microsoft Intune')
SupportedClouds = @('Global', 'USGov', 'USGovDoD')
}
2 changes: 1 addition & 1 deletion tests/QA/PublishChannel.tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ BeforeAll {
}

function New-PassingResult {
param([string] $Root, [string] $Version = '9.9.9', [int] $Total = 1482)
param([string] $Root, [string] $Version = '9.9.9', [int] $Total = 1486)
$path = Join-Path $Root "NUnitXml_GraphKit_v$Version.Test.xml"
@"
<?xml version="1.0" encoding="utf-8" standalone="no"?>
Expand Down
6 changes: 3 additions & 3 deletions tests/QA/ReleaseProof.tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ BeforeAll {
[switch] $NullRequiredModules,
[string] $BaseVersion = '0.4.0',
[switch] $DirtySource,
[int] $Total = 1482
[int] $Total = 1486
)

$fixtureRoot = Join-Path ([System.IO.Path]::GetTempPath()) ('graphkit-release-proof-' + [guid]::NewGuid().ToString('N'))
Expand Down Expand Up @@ -387,7 +387,7 @@ $requiredAssembliesLine$requiredModulesLine
sha256 = (Get-FileHash -LiteralPath $pesterObjectPath -Algorithm SHA256).Hash.ToLowerInvariant()
}
policy = [pscustomobject] [ordered] @{
minimumTests = 1482
minimumTests = 1486
allowedSkips = 0
allowedNotRun = 0
}
Expand Down Expand Up @@ -1097,7 +1097,7 @@ Describe 'Test workflow release-proof generation' {
$proof.module.baseVersion | Should -Be $script:fixture.BaseVersion
$proof.source.revision | Should -Match '^[0-9a-f]{40}$'
@($proof.module.files).Count | Should -Be 5
$proof.testRun.summary.total | Should -Be 1482
$proof.testRun.summary.total | Should -Be 1486
$proof.testRun.summary.notRun | Should -Be 0
Test-Path -LiteralPath (Join-Path $script:fixture.Root 'output/testResults/candidate-release-input.json') | Should -BeFalse

Expand Down
138 changes: 138 additions & 0 deletions tests/Unit/Operations/TenantPulseDescriptorContracts.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,29 @@ BeforeAll {
Import-Module (Join-Path $built.FullName 'GraphKit.psd1') -Force

$script:catalog = @(Get-GraphOperation -List)

$script:Context = [PSCustomObject]@{
Cloud = 'Global'
GraphBaseUri = [uri] 'https://graph.microsoft.com'
ProfileId = 'contract-test'
TenantId = [guid] '00000000-0000-0000-0000-000000000001'
ClientId = '00000000-0000-0000-0000-000000000010'
IdentityState = 'VerifiedForToken'
TokenSource = [PSCustomObject]@{ AuthMode = 'Certificate' }
}

function New-ContractTestEnvelope {
param([object[]] $Data)

[PSCustomObject]@{
PSTypeName = 'GraphKit.OperationResult'
Data = $Data
Outcome = 'Succeeded'
Certainty = 'Known'
Telemetry = @()
Provenance = $null
}
}
}

Describe 'TenantPulse collection descriptor contracts' {
Expand Down Expand Up @@ -95,4 +118,119 @@ Describe 'TenantPulse collection descriptor contracts' {
$_.PathTemplate -eq '/deviceManagement/managedDeviceCleanupSettings'
}).Count | Should -Be 0
}

It 'ships the beta Apple enrollment-profile child collection needed by the IHA successor' {
$descriptor = $script:catalog | Where-Object {
$_.Type -eq 'AppleEnrollmentProfile' -and $_.Operation -eq 'ListByToken'
}

$descriptor | Should -Not -BeNullOrEmpty
$descriptor.ApiVersion | Should -Be 'beta'
$descriptor.Stability | Should -Be 'BetaOnly'
$descriptor.OperationKind | Should -Be 'Collection'
$descriptor.HandlerStrategyId | Should -Be 'Collection.Default'
$descriptor.PathTemplate | Should -Be '/deviceManagement/depOnboardingSettings/{depOnboardingSettingId}/enrollmentProfiles'
$descriptor.AdvancedQuery.Supported | Should -BeFalse
$descriptor.PagingStrategy | Should -Be 'NextLink'
$descriptor.DeduplicationKey | Should -Be 'id'
$descriptor.ReplayPolicy | Should -Be 'Safe'
$descriptor.ThrottleClass | Should -Be 'Read'
@($descriptor.RequiredPermissions.Value) | Should -Be @('DeviceManagementServiceConfig.Read.All')
}

It 'preserves the documented polymorphic Apple enrollment-profile response fields' {
$script:AppleEnrollmentProfileFixture = [pscustomobject]@{
'@odata.type' = '#microsoft.graph.depIOSEnrollmentProfile'
id = 'profile-1'
displayName = 'Corporate iOS'
description = 'Automated enrollment'
requiresUserAuthentication = $true
configurationEndpointUrl = 'https://example.test/configuration'
enableAuthenticationViaCompanyPortal = $true
requireCompanyPortalOnSetupAssistantEnrolledDevices = $true
isDefault = $true
isMandatory = $false
}

Mock Invoke-GraphPaging -ModuleName GraphKit {
New-ContractTestEnvelope -Data @($script:AppleEnrollmentProfileFixture)
}
Mock Invoke-GraphHandlerStrategy -ModuleName GraphKit { throw 'unexpected singleton strategy call' }

$rows = @(Get-GraphObject `
-Context $script:Context `
-Type AppleEnrollmentProfile `
-Operation ListByToken `
-Parameters @{ depOnboardingSettingId = 'token-1' })

$rows | Should -HaveCount 1
$rows[0].PSObject.TypeNames | Should -Contain 'GraphKit.AppleEnrollmentProfile'
$rows[0].'@odata.type' | Should -BeExactly '#microsoft.graph.depIOSEnrollmentProfile'
$rows[0].id | Should -BeExactly 'profile-1'
$rows[0].displayName | Should -BeExactly 'Corporate iOS'
$rows[0].description | Should -BeExactly 'Automated enrollment'
$rows[0].requiresUserAuthentication | Should -BeTrue
$rows[0].isDefault | Should -BeTrue
$rows[0].isMandatory | Should -BeFalse
Should-Invoke Invoke-GraphPaging -ModuleName GraphKit -Times 1 -Exactly
Should-NotInvoke Invoke-GraphHandlerStrategy -ModuleName GraphKit
}

It 'ships the beta managed-device singleton needed for authoritative hardware detail' {
$descriptor = $script:catalog | Where-Object {
$_.Type -eq 'ManagedDevice' -and $_.Operation -eq 'GetBeta'
}

$descriptor | Should -Not -BeNullOrEmpty
$descriptor.ApiVersion | Should -Be 'beta'
$descriptor.Stability | Should -Be 'DualVersion'
$descriptor.OperationKind | Should -Be 'Singleton'
$descriptor.HandlerStrategyId | Should -Be 'Singleton.Default'
$descriptor.PathTemplate | Should -Be '/deviceManagement/managedDevices/{id}?$select=id,hardwareInformation,deviceHealthAttestationState,physicalMemoryInBytes,processorArchitecture,skuFamily,skuNumber,managementFeatures,roleScopeTagIds,ethernetMacAddress,bootstrapTokenEscrowed'
$descriptor.PagingStrategy | Should -Be 'None'
$descriptor.AdvancedQuery.Supported | Should -BeFalse
$descriptor.ReplayPolicy | Should -Be 'Safe'
$descriptor.ThrottleClass | Should -Be 'Read'
@($descriptor.RequiredPermissions.Value) | Should -Be @('DeviceManagementManagedDevices.Read.All')
}

It 'preserves the documented managed-device hardware and attestation response fields' {
$script:ManagedDeviceDetailFixture = [pscustomobject]@{
id = 'device-1'
operatingSystem = 'Windows'
hardwareInformation = [pscustomobject]@{
serialNumber = 'SERIAL'
totalStorageSpace = 1024
freeStorageSpace = 512
tpmVersion = '2.0'
}
deviceHealthAttestationState = [pscustomobject]@{
secureBoot = 'enabled'
bitLockerStatus = 'secured'
tpmVersion = '2.0'
}
}

Mock Invoke-GraphHandlerStrategy -ModuleName GraphKit {
New-ContractTestEnvelope -Data @($script:ManagedDeviceDetailFixture)
}
Mock Invoke-GraphPaging -ModuleName GraphKit { throw 'unexpected paging call' }

$rows = @(Get-GraphObject `
-Context $script:Context `
-Type ManagedDevice `
-Operation GetBeta `
-Parameters @{ id = 'device-1' })

$rows | Should -HaveCount 1
$rows[0].PSObject.TypeNames | Should -Contain 'GraphKit.ManagedDevice'
$rows[0].id | Should -BeExactly 'device-1'
$rows[0].hardwareInformation.tpmVersion | Should -BeExactly '2.0'
$rows[0].deviceHealthAttestationState.secureBoot | Should -BeExactly 'enabled'
Should-Invoke Invoke-GraphHandlerStrategy -ModuleName GraphKit -Times 1 -Exactly -ParameterFilter {
$Descriptor.OperationKind -eq 'Singleton' -and
$Descriptor.HandlerStrategyId -eq 'Singleton.Default'
}
Should-NotInvoke Invoke-GraphPaging -ModuleName GraphKit
}
}
Loading