ORDERED SETUP — DISPOSABLE AGENT PERMISSION LAB
Use the article's section 4 alongside this checklist. Use fresh lab objects.

1. Confirm operator authorization. Agent ID Administrator covers the selected
   blueprint/certificate portal route. Cloud Application Administrator or
   Application Administrator covers the custom API registration/consent route.
   The observed lab used Global Administrator. The agent receives no such role.
   The article separates directory roles from Graph client delegated scopes.
   Arrange an already authorized cleanup session and capture management-client
   consent before adding the three lab scopes. See CONSENT-CLEANUP.txt first.

2. Entra ID > Agents > Agent blueprints > New agent blueprint: use a unique
   name and your authorized operator as owner and sponsor. This wizard creates
   the blueprint principal too. Agents > Agent identities > New agent identity:
   select that blueprint, name the child, confirm owner/sponsor and create.
   Keep the child without direct grants and the registration setting unchanged.

3. App registrations > New registration: uniquely named API, single tenant,
   no redirect URI. App roles > Create app role:
     Display: Read synthetic lab ticket
     Allowed member types: Applications
     Value: Lab.Read
     Description: Read the fictitious LAB-001 ticket only
     Enabled: Yes
   Record the role GUID. This is the API's only role. Expose an API: set its
   identifier URI to api:// followed by its application GUID. No delegated
   scope is needed. Manifest: set requestedAccessTokenVersion to 2 inside the
   existing api object, preserving other properties. Save/reopen to verify.

4. Copy lab-config.example.json to lab-config.json and fill these fields:
   TenantId: directory GUID.
   BlueprintAppId / BlueprintObjectId: application/client ID and object ID.
   BlueprintPrincipalId: tenant-local blueprint principal's object ID.
   AgentAppId / AgentObjectId: child's application ID and object ID.
   ResourceAppId / ResourceObjectId: API registration's application and object IDs.
   ResourcePrincipalId: API enterprise application's object ID. Follow the
   registration's Managed application in local directory link to find it.
   LabRoleId: Lab.Read role GUID.
   To resolve the blueprint principal, an authorized Graph read can use:
     GET /v1.0/servicePrincipals(appId='BLUEPRINT_APP_ID')?$select=id,appId
   Verify appId before copying id. Some IDs happen to match; do not assume it.

5. Run once in PowerShell 7 in the package folder:
   $c = Get-Content .\lab-config.json -Raw | ConvertFrom-Json
   $subject = 'CN=AgentPermissionLab-' + [guid]::NewGuid().ToString('N')
   $cert = New-SelfSignedCertificate -Type Custom -Subject $subject -KeyAlgorithm RSA -KeyLength 2048 -KeySpec Signature -KeyUsage DigitalSignature -KeyExportPolicy NonExportable -CertStoreLocation 'Cert:\CurrentUser\My' -NotAfter (Get-Date).AddDays(1)
   Export-Certificate -Cert $cert -FilePath .\lab-blueprint-public.cer | Out-Null
   $cert | Select-Object Thumbprint,Subject,NotAfter | ConvertTo-Json | Set-Content .\local-certificate.json
   $c.CertificateSubject = $subject
   $c | ConvertTo-Json -Depth 10 | Set-Content .\lab-config.json
   Only the public .cer is exported; private key remains in CurrentUser/My.

6. Install the official module if needed:
   Install-Module Microsoft.Graph.Authentication -Scope CurrentUser
   First capture a baseline through the client's already approved read access:
   Connect-MgGraph -TenantId $c.TenantId -ContextScope Process -Scopes 'Application.Read.All','Directory.Read.All' -NoWelcome
   $managementClientAppId = (Get-MgContext).ClientId
   $managementClientAppId | Set-Content .\management-client-app-id.txt
   .\Manage-LabManagementConsent.ps1 -Mode Capture -ClientAppId $managementClientAppId
   Require exit 0 and inspect the private baseline before proceeding. Capture
   does not overwrite an existing file. Do not recapture after adding scopes.
   Connect with the approved delegated scopes:
   Connect-MgGraph -TenantId $c.TenantId -ContextScope Process -Scopes 'Application.Read.All','Directory.Read.All','AgentIdentityBlueprint.Create','AgentIdentityBlueprint.AddRemoveCreds.All','AgentIdentityBlueprint.UpdateAuthProperties.All' -NoWelcome
   Confirm account/tenant and review any permission request. Run separately:
   .\Configure-LabBlueprint.ps1 -Stage Certificate
   .\Configure-LabBlueprint.ps1 -Stage Callback
   Each stage verifies identifiers and asks before writing. Retain the returned
   certificate key ID. Certificate uses AddRemoveCreds.All; Callback uses
   UpdateAuthProperties.All; Inheritance uses Create. Application.Read.All
   supports object checks. Directory.Read.All supports delegated-grant reads;
   both scopes and each endpoint's operator authorization are required.
   The helper refuses to overwrite credentials, redirects or inheritance.
   Inspect partial failures rather than blindly repeating a stage.

7. Blueprint > Manifest: edit only requiredResourceAccess on this new blueprint:
   "requiredResourceAccess": [
     { "resourceAppId": "RESOURCE_APP_ID",
       "resourceAccess": [{ "id": "LAB_READ_ROLE_ID", "type": "Role" }] }
   ]
   Substitute GUIDs, preserve other fields, save/reopen. Declaration is not
   consent. The original lab used this portal route after a Graph update 403.

8. .\Configure-LabBlueprint.ps1 -Stage Inheritance
   Verify this API only, noScopes/none and allAllowedRoles/allAllowed.
   Newly granted roles for this resource would be inheritable too. Lab.Read
   is the only role the disposable resource exposes; none is granted yet.

9. .\Read-AgentLabInventory.ps1 -Label A
   Expected: complete queries, matching identities/configuration, parentLabRead
   0, childDirectRoles 0, parent/child/resource delegated grant counts 0 each.
   OAuth scopes are Application.Read.All and Directory.Read.All. The typed
   blueprint/inheritance GETs document owner access and Agent ID Administrator
   for nonowners; delegated-grant reads separately require a supported role
   such as Directory Readers. Directory Readers alone does not guarantee the
   complete inventory. Use the already authorized lab operator with read scopes.
   The script does not sign in, request consent or assign roles.
   Stop if queries are incomplete, IDs mismatch or permissions are unexpected.

10. Follow README for build, API guard checks and probe A. Only after A, run
    consent. Verify inventory B, retain the exact assignment ID, run probe B.
    Review all children affected by revocation; remove that parent Lab.Read
    assignment, verify inventory C and run probe C. Preserve intermediate results.

11. Perform the article's exact-object cleanup. For the local certificate:
    $metadata = Get-Content .\local-certificate.json -Raw | ConvertFrom-Json
    $path = 'Cert:\CurrentUser\My\' + $metadata.Thumbprint
    $local = Get-Item -LiteralPath $path
    if ($local.Subject -ne $metadata.Subject) { throw 'Certificate mismatch' }
    Remove-Item -LiteralPath $path -Confirm
    Test-Path -LiteralPath $path
    Expected False. Stop API/consent helpers. Before disconnecting, follow
    CONSENT-CLEANUP.txt: List, Plan, Restore -WhatIf, then confirmed Restore for
    each exact management grant. Principal consent needs its exact user object
    ID and Graph/PowerShell; it cannot be revoked from the portal's User consent
    tab. AllPrincipals uses null principalId. Preserve pre-lab/unrelated scopes;
    verify readback and review any additional preserved scopes separately.
    Disconnect-MgGraph afterwards; disconnecting is not revocation.
    Verify active inventories. Recoverable deletion is not permanent purging.

This portal-first route does not use Graph PowerShell to create the principal
or child. A Graph-only route needs the additional creation permissions in
Microsoft's documentation; the three added configuration scopes do not cover
all provisioning operations.
