@hellolynk-ai/codex-plugin 0.0.0-stage → 0.2.100

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. package/.agents/plugins/marketplace.json +20 -0
  2. package/LICENSE +92 -0
  3. package/README.md +101 -2
  4. package/bin/hellolynk-client-setup.mjs +2748 -0
  5. package/bin/hellolynk-mcp-config-shape.mjs +23 -0
  6. package/hellolynk-client-pack-manifest.json +619 -0
  7. package/hellolynk-client-pack-pin.json +4 -0
  8. package/package.json +58 -4
  9. package/plugins/hellolynk-codex/.app.json +3 -0
  10. package/plugins/hellolynk-codex/.codex-plugin/plugin.json +38 -0
  11. package/plugins/hellolynk-codex/.mcp.json +19 -0
  12. package/plugins/hellolynk-codex/assets/hellolynk-mark.png +0 -0
  13. package/plugins/hellolynk-codex/package.json +11 -0
  14. package/plugins/hellolynk-codex/runtime/bootstrap/core/hellolynk-client-account.mjs +180 -0
  15. package/plugins/hellolynk-codex/runtime/bootstrap/core/hellolynk-client-core.mjs +770 -0
  16. package/plugins/hellolynk-codex/runtime/bootstrap/core/hellolynk-client-profile.mjs +191 -0
  17. package/plugins/hellolynk-codex/runtime/bootstrap/core/hellolynk-host-auth.mjs +347 -0
  18. package/plugins/hellolynk-codex/runtime/bootstrap/core/hellolynk-host-protected-store.mjs +2721 -0
  19. package/plugins/hellolynk-codex/runtime/bootstrap/core/hellolynk-local-process-lock.mjs +157 -0
  20. package/plugins/hellolynk-codex/runtime/bootstrap/core/hellolynk-neutral-host-installation-store.mjs +765 -0
  21. package/plugins/hellolynk-codex/runtime/bootstrap/core/hellolynk-platform-credential-store.mjs +773 -0
  22. package/plugins/hellolynk-codex/runtime/bootstrap/core/hellolynk-runtime-safety.cjs +471 -0
  23. package/plugins/hellolynk-codex/runtime/bootstrap/platform/windows/hellolynk-host-protected-store.ps1 +2866 -0
  24. package/plugins/hellolynk-codex/runtime/connect-activate.mjs +837 -0
  25. package/plugins/hellolynk-codex/runtime/core/hellolynk-agent-configurator.mjs +701 -0
  26. package/plugins/hellolynk-codex/runtime/core/hellolynk-client-account.mjs +180 -0
  27. package/plugins/hellolynk-codex/runtime/core/hellolynk-client-core.mjs +770 -0
  28. package/plugins/hellolynk-codex/runtime/core/hellolynk-client-profile.mjs +191 -0
  29. package/plugins/hellolynk-codex/runtime/core/hellolynk-client-update-entry.mjs +31 -0
  30. package/plugins/hellolynk-codex/runtime/core/hellolynk-client-update-runtime.mjs +600 -0
  31. package/plugins/hellolynk-codex/runtime/core/hellolynk-client-update.mjs +453 -0
  32. package/plugins/hellolynk-codex/runtime/core/hellolynk-contact-request-e2ee.mjs +867 -0
  33. package/plugins/hellolynk-codex/runtime/core/hellolynk-device-enrollment.mjs +435 -0
  34. package/plugins/hellolynk-codex/runtime/core/hellolynk-direct-text-e2ee-client.mjs +1964 -0
  35. package/plugins/hellolynk-codex/runtime/core/hellolynk-direct-text-e2ee.mjs +1207 -0
  36. package/plugins/hellolynk-codex/runtime/core/hellolynk-host-auth.mjs +347 -0
  37. package/plugins/hellolynk-codex/runtime/core/hellolynk-host-protected-store.mjs +2721 -0
  38. package/plugins/hellolynk-codex/runtime/core/hellolynk-host-transport-client.mjs +946 -0
  39. package/plugins/hellolynk-codex/runtime/core/hellolynk-host.mjs +2374 -0
  40. package/plugins/hellolynk-codex/runtime/core/hellolynk-human-confirmation-ledger.mjs +256 -0
  41. package/plugins/hellolynk-codex/runtime/core/hellolynk-human-relationship-controller.mjs +1234 -0
  42. package/plugins/hellolynk-codex/runtime/core/hellolynk-inbox-read-state.mjs +11 -0
  43. package/plugins/hellolynk-codex/runtime/core/hellolynk-indexed-message-store.mjs +1558 -0
  44. package/plugins/hellolynk-codex/runtime/core/hellolynk-instance-conversation.mjs +185 -0
  45. package/plugins/hellolynk-codex/runtime/core/hellolynk-local-contact-requests.mjs +1733 -0
  46. package/plugins/hellolynk-codex/runtime/core/hellolynk-local-contacts.mjs +1228 -0
  47. package/plugins/hellolynk-codex/runtime/core/hellolynk-local-data-migration.mjs +766 -0
  48. package/plugins/hellolynk-codex/runtime/core/hellolynk-local-data-protection.mjs +747 -0
  49. package/plugins/hellolynk-codex/runtime/core/hellolynk-local-identity-trust.mjs +274 -0
  50. package/plugins/hellolynk-codex/runtime/core/hellolynk-local-messaging.mjs +3043 -0
  51. package/plugins/hellolynk-codex/runtime/core/hellolynk-local-peer-transport.mjs +103 -0
  52. package/plugins/hellolynk-codex/runtime/core/hellolynk-local-process-lock.mjs +157 -0
  53. package/plugins/hellolynk-codex/runtime/core/hellolynk-model-inbox.mjs +230 -0
  54. package/plugins/hellolynk-codex/runtime/core/hellolynk-model-notices.cjs +60 -0
  55. package/plugins/hellolynk-codex/runtime/core/hellolynk-native-addressing-tools.cjs +231 -0
  56. package/plugins/hellolynk-codex/runtime/core/hellolynk-native-conversations.mjs +280 -0
  57. package/plugins/hellolynk-codex/runtime/core/hellolynk-native-human-confirmation-broker.mjs +1036 -0
  58. package/plugins/hellolynk-codex/runtime/core/hellolynk-native-inbox-runtime.mjs +553 -0
  59. package/plugins/hellolynk-codex/runtime/core/hellolynk-native-notice.mjs +50 -0
  60. package/plugins/hellolynk-codex/runtime/core/hellolynk-native-reply-routes.mjs +96 -0
  61. package/plugins/hellolynk-codex/runtime/core/hellolynk-native-worker-lease.mjs +84 -0
  62. package/plugins/hellolynk-codex/runtime/core/hellolynk-neutral-host-assembly.mjs +955 -0
  63. package/plugins/hellolynk-codex/runtime/core/hellolynk-neutral-host-installation-store.mjs +765 -0
  64. package/plugins/hellolynk-codex/runtime/core/hellolynk-neutral-host-launcher.mjs +665 -0
  65. package/plugins/hellolynk-codex/runtime/core/hellolynk-neutral-host-process.mjs +4374 -0
  66. package/plugins/hellolynk-codex/runtime/core/hellolynk-neutral-host-store-adapters.mjs +548 -0
  67. package/plugins/hellolynk-codex/runtime/core/hellolynk-neutral-host-workbench-read-model.mjs +1365 -0
  68. package/plugins/hellolynk-codex/runtime/core/hellolynk-platform-credential-store.mjs +773 -0
  69. package/plugins/hellolynk-codex/runtime/core/hellolynk-relay-client-cli.mjs +232 -0
  70. package/plugins/hellolynk-codex/runtime/core/hellolynk-release-retention.mjs +277 -0
  71. package/plugins/hellolynk-codex/runtime/core/hellolynk-runtime-safety.cjs +471 -0
  72. package/plugins/hellolynk-codex/runtime/core/hellolynk-workbench-action-grants.mjs +457 -0
  73. package/plugins/hellolynk-codex/runtime/core/hellolynk-workbench-connection.mjs +234 -0
  74. package/plugins/hellolynk-codex/runtime/neutral-host-bootstrap.mjs +64 -0
  75. package/plugins/hellolynk-codex/runtime/neutral-host-release-entry.mjs +70 -0
  76. package/plugins/hellolynk-codex/runtime/neutral-host-release-loader.mjs +156 -0
  77. package/plugins/hellolynk-codex/runtime/neutral-host-release-manifest.json +358 -0
  78. package/plugins/hellolynk-codex/runtime/neutral-host-release-pin.json +4 -0
  79. package/plugins/hellolynk-codex/runtime/platform/windows/configure-agent.ps1 +281 -0
  80. package/plugins/hellolynk-codex/runtime/platform/windows/credential-store.ps1 +844 -0
  81. package/plugins/hellolynk-codex/runtime/platform/windows/hellolynk-host-protected-store.ps1 +2866 -0
  82. package/plugins/hellolynk-codex/runtime/platform/windows/hellolynk-human-confirmation-ledger.ps1 +155 -0
  83. package/plugins/hellolynk-codex/runtime/platform/windows/hellolynk-local-data-key.ps1 +300 -0
  84. package/plugins/hellolynk-codex/runtime/platform/windows/hellolynk-native-human-confirmation-broker.ps1 +216 -0
  85. package/plugins/hellolynk-codex/runtime/workbench/candidate-bootstrap.mjs +54 -0
  86. package/plugins/hellolynk-codex/runtime/workbench/contract.mjs +1314 -0
  87. package/plugins/hellolynk-codex/runtime/workbench/hellolynk-workbench-build.json +215 -0
  88. package/plugins/hellolynk-codex/runtime/workbench/host-capabilities.mjs +80 -0
  89. package/plugins/hellolynk-codex/runtime/workbench/i18n.mjs +1127 -0
  90. package/plugins/hellolynk-codex/runtime/workbench/loopback-surface.mjs +740 -0
  91. package/plugins/hellolynk-codex/runtime/workbench/mcp-app-bundle.mjs +97 -0
  92. package/plugins/hellolynk-codex/runtime/workbench/service.mjs +485 -0
  93. package/plugins/hellolynk-codex/runtime/workbench/state.mjs +421 -0
  94. package/plugins/hellolynk-codex/runtime/workbench/transports.mjs +171 -0
  95. package/plugins/hellolynk-codex/runtime/workbench/ui/app.mjs +6120 -0
  96. package/plugins/hellolynk-codex/runtime/workbench/ui/connection-form.mjs +155 -0
  97. package/plugins/hellolynk-codex/runtime/workbench/ui/hellolynk-mark.png +0 -0
  98. package/plugins/hellolynk-codex/runtime/workbench/ui/http-transport.mjs +247 -0
  99. package/plugins/hellolynk-codex/runtime/workbench/ui/icons/arrow-left.svg +3 -0
  100. package/plugins/hellolynk-codex/runtime/workbench/ui/icons/close.svg +3 -0
  101. package/plugins/hellolynk-codex/runtime/workbench/ui/icons/contacts.svg +3 -0
  102. package/plugins/hellolynk-codex/runtime/workbench/ui/icons/inbox.svg +3 -0
  103. package/plugins/hellolynk-codex/runtime/workbench/ui/icons/refresh.svg +3 -0
  104. package/plugins/hellolynk-codex/runtime/workbench/ui/icons/search.svg +3 -0
  105. package/plugins/hellolynk-codex/runtime/workbench/ui/icons/security.svg +3 -0
  106. package/plugins/hellolynk-codex/runtime/workbench/ui/icons/settings.svg +3 -0
  107. package/plugins/hellolynk-codex/runtime/workbench/ui/index.html +139 -0
  108. package/plugins/hellolynk-codex/runtime/workbench/ui/loopback-entry.mjs +26 -0
  109. package/plugins/hellolynk-codex/runtime/workbench/ui/mcp-app-client.mjs +151 -0
  110. package/plugins/hellolynk-codex/runtime/workbench/ui/mcp-app-entry.mjs +452 -0
  111. package/plugins/hellolynk-codex/runtime/workbench/ui/styles.css +686 -0
  112. package/plugins/hellolynk-codex/server/codex-neutral-production.mjs +520 -0
  113. package/plugins/hellolynk-codex/server/mcp-proxy.mjs +306 -0
  114. package/plugins/hellolynk-codex/server/mcp-server.mjs +1193 -0
  115. package/plugins/hellolynk-codex/server/native-conversation-directory.mjs +33 -0
  116. package/plugins/hellolynk-codex/server/native-reply-routes.mjs +32 -0
  117. package/plugins/hellolynk-codex/server/native-task-client.mjs +382 -0
  118. package/plugins/hellolynk-codex/server/native-task-runtime.mjs +8 -0
  119. package/plugins/hellolynk-codex/server/neutral-host-adapter.mjs +649 -0
  120. package/plugins/hellolynk-codex/server/workbench-runtime.mjs +180 -0
  121. package/plugins/hellolynk-codex/skills/hellolynk-open/SKILL.md +11 -0
  122. package/plugins/hellolynk-codex/skills/hellolynk-open/agents/openai.yaml +4 -0
  123. package/tools/hellolynk-activate-neutral-host-clients.mjs +837 -0
@@ -0,0 +1,2866 @@
1
+ param(
2
+ [Parameter(Mandatory = $true)]
3
+ [ValidateSet(
4
+ "Issue", "Load", "Revoke", "Rotate", "Status", "DisconnectLocalClient",
5
+ "SaveCodexNeutralBinding", "ResolveCodexNeutralBinding", "VerifyCodexNeutralBinding", "RemoveCodexNeutralBinding", "ReplaceCodexNeutralBinding", "MigrateCodexNeutralBinding",
6
+ "SaveOpenClawNeutralBinding", "ResolveOpenClawNeutralBinding", "VerifyOpenClawNeutralBinding", "RemoveOpenClawNeutralBinding", "MigrateOpenClawNeutralBinding",
7
+ "SaveTraeWorkNeutralBinding", "ResolveTraeWorkNeutralBinding", "VerifyTraeWorkNeutralBinding", "RemoveTraeWorkNeutralBinding", "MigrateTraeWorkNeutralBinding",
8
+ "SaveWorkBuddyNeutralBinding", "ResolveWorkBuddyNeutralBinding", "VerifyWorkBuddyNeutralBinding", "RemoveWorkBuddyNeutralBinding", "MigrateWorkBuddyNeutralBinding",
9
+ "SaveClaudeCodeNeutralBinding", "ResolveClaudeCodeNeutralBinding", "VerifyClaudeCodeNeutralBinding", "RemoveClaudeCodeNeutralBinding", "MigrateClaudeCodeNeutralBinding",
10
+ "SaveCursorNeutralBinding", "ResolveCursorNeutralBinding", "VerifyCursorNeutralBinding", "RemoveCursorNeutralBinding", "MigrateCursorNeutralBinding",
11
+ "SaveDshNeutralBinding", "ResolveDshNeutralBinding", "VerifyDshNeutralBinding", "RemoveDshNeutralBinding", "MigrateDshNeutralBinding", "RestoreDshNeutralBinding",
12
+ "SaveTransportCredential", "LoadTransportCredential", "RevokeTransportCredential",
13
+ "VerifyTransportCredentialLifetimeMigration", "MigrateTransportCredentialLifetime",
14
+ "SaveTransportSession", "LoadTransportSession", "RemoveTransportSession",
15
+ "ReplaceTransportSession", "ResolveTransport", "VerifyTransport", "VerifyPath",
16
+ "LoadDeviceEnrollment", "SaveDeviceEnrollment", "SaveEnrollmentTransportCredential"
17
+ )]
18
+ [string]$Command,
19
+
20
+ [Parameter(Mandatory = $true)]
21
+ [string]$StoreRoot,
22
+
23
+ [string]$CapabilityId,
24
+ [string]$CredentialId,
25
+ [string]$SessionId,
26
+ [string]$PrincipalId,
27
+ [string]$HostInstallationId,
28
+ [int64]$Timestamp = -1,
29
+ [string]$PathToVerify,
30
+ [switch]$VerifyDirectory,
31
+ [switch]$VerifyOnly,
32
+ [string]$ResultPipeName
33
+ )
34
+
35
+ $ErrorActionPreference = "Stop"
36
+ $ProgressPreference = "SilentlyContinue"
37
+ # The Node caller writes UTF-8 to a pipe. Decode that protocol explicitly,
38
+ # independent of the inherited Windows console code page (including names).
39
+ [Console]::SetIn([IO.StreamReader]::new([Console]::OpenStandardInput(), [Text.UTF8Encoding]::new($false, $true)))
40
+ $CapabilityIdPattern = '^cap_[A-Za-z0-9_-]{32}$'
41
+ $IdentifierPattern = '^[A-Za-z0-9](?:[A-Za-z0-9._:-]{0,126}[A-Za-z0-9])?$'
42
+ $SecretPattern = '^[A-Za-z0-9_-]{43}$'
43
+ $Contract = 'hellolynk-host-protected-store-v1'
44
+ $RecordContract = 'hellolynk-host-capability-v1'
45
+ $TransportCredentialContract = 'hellolynk-host-transport-credential-v1'
46
+ $TransportSessionContract = 'hellolynk-host-transport-session-v1'
47
+ $AuthorizationHistoryContract = 'hellolynk-host-authorization-history-v1'
48
+ $CodexNeutralBindingContract = 'hellolynk-codex-neutral-binding-v1'
49
+ $OpenClawNeutralBindingContract = 'hellolynk-openclaw-neutral-binding-v1'
50
+ $TraeWorkNeutralBindingContract = 'hellolynk-trae-work-neutral-binding-v1'
51
+ $WorkBuddyNeutralBindingContract = 'hellolynk-workbuddy-neutral-binding-v1'
52
+ $ClaudeCodeNeutralBindingContract = 'hellolynk-claude-code-neutral-binding-v1'
53
+ $CursorNeutralBindingContract = 'hellolynk-cursor-neutral-binding-v1'
54
+ $DshNeutralBindingContract = 'hellolynk-dsh-neutral-binding-v1'
55
+ $CodexNeutralScopes = @(
56
+ 'app-data:authorized-messaging',
57
+ 'app-data:changes-read',
58
+ 'app-data:contact-requests-read',
59
+ 'app-data:contacts-manage',
60
+ 'app-data:contacts-read',
61
+ 'app-data:human-send-request',
62
+ 'app-data:inbox-manage',
63
+ 'app-data:inbox-read',
64
+ 'app-data:outbox-read',
65
+ 'app-data:outbox-retry',
66
+ 'attachments:manage',
67
+ 'surface:human-ui',
68
+ 'ui:launch'
69
+ )
70
+ $OpenClawNeutralScopes = @($CodexNeutralScopes)
71
+ $CodexNeutralPreviousScopes = @($CodexNeutralScopes | Where-Object { $_ -cne 'app-data:authorized-messaging' })
72
+ $OpenClawNeutralPreviousScopes = @($CodexNeutralPreviousScopes)
73
+ $TraeWorkNeutralScopes = @(
74
+ 'app-data:authorized-messaging',
75
+ 'app-data:changes-read',
76
+ 'app-data:contact-requests-read',
77
+ 'app-data:contacts-manage',
78
+ 'app-data:contacts-read',
79
+ 'app-data:human-send-request',
80
+ 'app-data:inbox-manage',
81
+ 'app-data:inbox-read',
82
+ 'app-data:outbox-read',
83
+ 'app-data:outbox-retry',
84
+ 'attachments:manage',
85
+ 'status:read',
86
+ 'surface:human-ui',
87
+ 'ui:launch'
88
+ )
89
+ $WorkBuddyNeutralScopes = @($TraeWorkNeutralScopes)
90
+ $ClaudeCodeNeutralScopes = @($WorkBuddyNeutralScopes)
91
+ $TraeWorkNeutralPreviousScopes = @($TraeWorkNeutralScopes | Where-Object { $_ -cne 'app-data:authorized-messaging' })
92
+ $WorkBuddyNeutralPreviousScopes = @($TraeWorkNeutralPreviousScopes)
93
+ $TraeWorkNeutralLegacyScopes = @(
94
+ 'app-data:human-send-request',
95
+ 'attachments:manage',
96
+ 'status:read',
97
+ 'surface:human-ui'
98
+ )
99
+ $WorkBuddyNeutralLegacyScopes = @($TraeWorkNeutralLegacyScopes)
100
+ $CursorNeutralScopes = @(
101
+ 'app-data:authorized-messaging',
102
+ 'app-data:changes-read',
103
+ 'app-data:contact-requests-read',
104
+ 'app-data:contacts-manage',
105
+ 'app-data:contacts-read',
106
+ 'app-data:human-send-request',
107
+ 'app-data:inbox-manage',
108
+ 'app-data:inbox-read',
109
+ 'app-data:outbox-read',
110
+ 'app-data:outbox-retry',
111
+ 'attachments:manage',
112
+ 'status:read',
113
+ 'surface:human-ui',
114
+ 'ui:launch'
115
+ )
116
+ $CursorNeutralPreviousScopes = @($CursorNeutralScopes | Where-Object { $_ -cne 'app-data:authorized-messaging' })
117
+ $DshNeutralScopes = @($CursorNeutralScopes)
118
+ $DshNeutralPreviousScopes = @($CursorNeutralPreviousScopes)
119
+ $TransportCredentialSecretPattern = '^hellolynk_host_transport_v1_([A-Za-z0-9_-]{3,127})\.([A-Za-z0-9_-]{32,})$'
120
+ $TransportSessionIdPattern = '^hosttransportsession_[A-Za-z0-9_-]{16,}$'
121
+ $TransportSessionSecretPattern = '^hostsession_[a-f0-9]{48}$'
122
+ $CredentialName = 'hellolynk-neutral-host-protected-store-v1'
123
+ $EnvelopeContract = 'hellolynk-host-protected-envelope-v2'
124
+ $RetiredIndexContract = 'hellolynk-retired-transport-index-v1'
125
+
126
+ Add-Type -TypeDefinition @'
127
+ using System;
128
+ using System.Runtime.InteropServices;
129
+ using System.Text;
130
+ using Microsoft.Win32.SafeHandles;
131
+ public static class HelloLynkProtectedStoreNativePath {
132
+ [DllImport("kernel32.dll", CharSet=CharSet.Unicode, SetLastError=true)]
133
+ public static extern SafeFileHandle CreateFile(
134
+ string name, uint access, uint share, IntPtr security, uint creation,
135
+ uint flags, IntPtr template);
136
+ [DllImport("kernel32.dll", CharSet=CharSet.Unicode, SetLastError=true)]
137
+ public static extern uint GetFinalPathNameByHandle(
138
+ SafeFileHandle handle, StringBuilder path, uint length, uint flags);
139
+ }
140
+ '@
141
+
142
+ function Fail([string]$Code) {
143
+ [Console]::Error.WriteLine($Code)
144
+ exit 1
145
+ }
146
+
147
+ function Test-ClaudeCodeStoreRoot([string]$Value) {
148
+ return $Value -match '(?i)[\\/]host$'
149
+ }
150
+
151
+ function Test-ExactScopeSequence($Actual, [string[]]$Expected) {
152
+ if ($Actual -isnot [Array] -or $Actual.Count -ne $Expected.Count) { return $false }
153
+ for ($index = 0; $index -lt $Expected.Count; $index += 1) {
154
+ if ([string]$Actual[$index] -cne [string]$Expected[$index]) { return $false }
155
+ }
156
+ return $true
157
+ }
158
+
159
+ function Test-FullyQualifiedCanonicalPath([string]$Value) {
160
+ try {
161
+ if (-not $Value -or -not [IO.Path]::IsPathFullyQualified($Value)) { return $false }
162
+ $null = [IO.Path]::GetFullPath($Value)
163
+ return $true
164
+ } catch { return $false }
165
+ }
166
+
167
+ function Assert-ExactFields($Value, [string[]]$Expected, [string]$Code) {
168
+ if ($null -eq $Value -or $Value -is [Array]) { Fail $Code }
169
+ $actual = @($Value.PSObject.Properties.Name | Sort-Object)
170
+ $wanted = @($Expected | Sort-Object)
171
+ if ($actual.Count -ne $wanted.Count) { Fail $Code }
172
+ for ($index = 0; $index -lt $wanted.Count; $index += 1) {
173
+ if ($actual[$index] -cne $wanted[$index]) { Fail $Code }
174
+ }
175
+ }
176
+
177
+ function Assert-Record($Record) {
178
+ Assert-ExactFields $Record @(
179
+ 'adapterId', 'capabilityId', 'contract', 'delegateId', 'hostInstallationId',
180
+ 'issuedAt', 'principalId', 'region', 'revokedAt', 'scopes', 'secret',
181
+ 'state', 'version'
182
+ ) 'host_capability_fields_invalid'
183
+ if (
184
+ [string]$Record.contract -cne $RecordContract -or
185
+ [string]$Record.capabilityId -notmatch $CapabilityIdPattern -or
186
+ [string]$Record.region -notmatch $IdentifierPattern -or
187
+ [string]$Record.principalId -notmatch $IdentifierPattern -or
188
+ [string]$Record.delegateId -notmatch $IdentifierPattern -or
189
+ [string]$Record.hostInstallationId -notmatch $IdentifierPattern -or
190
+ [string]$Record.adapterId -notmatch $IdentifierPattern -or
191
+ [int64]$Record.version -lt 1 -or
192
+ @('active', 'revoked') -notcontains [string]$Record.state -or
193
+ [int64]$Record.issuedAt -lt 0 -or
194
+ [string]$Record.secret -notmatch $SecretPattern -or
195
+ $Record.scopes -isnot [Array] -or
196
+ $Record.scopes.Count -lt 1 -or
197
+ $Record.scopes.Count -gt 64
198
+ ) { Fail 'host_capability_invalid' }
199
+ if (
200
+ ([string]$Record.state -eq 'active' -and $null -ne $Record.revokedAt) -or
201
+ ([string]$Record.state -eq 'revoked' -and [int64]$Record.revokedAt -lt [int64]$Record.issuedAt)
202
+ ) { Fail 'host_capability_state_invalid' }
203
+ $previous = $null
204
+ foreach ($scope in $Record.scopes) {
205
+ if ([string]$scope -notmatch $IdentifierPattern) { Fail 'host_capability_scope_invalid' }
206
+ if ($null -ne $previous -and [string]::CompareOrdinal([string]$previous, [string]$scope) -ge 0) {
207
+ Fail 'host_capability_scopes_noncanonical'
208
+ }
209
+ $previous = [string]$scope
210
+ }
211
+ return $Record
212
+ }
213
+
214
+ function Assert-CodexNeutralBinding($Binding, [bool]$ValidatePaths) {
215
+ Assert-ExactFields $Binding @(
216
+ 'adapterId', 'contract', 'delegateId', 'hostInstallationId', 'principalId',
217
+ 'profileId', 'profileManifestPath', 'region', 'releaseManifestSha256', 'releaseRoot', 'runnerRelativePath',
218
+ 'runtimeRoot', 'scopes', 'version'
219
+ ) 'neutral_codex_binding_invalid'
220
+ if (
221
+ [string]$Binding.contract -cne $CodexNeutralBindingContract -or
222
+ [string]$Binding.region -notmatch $IdentifierPattern -or
223
+ [string]$Binding.principalId -notmatch $IdentifierPattern -or
224
+ [string]$Binding.delegateId -notmatch $IdentifierPattern -or
225
+ [string]$Binding.hostInstallationId -notmatch $IdentifierPattern -or
226
+ [string]$Binding.profileId -notmatch $IdentifierPattern -or
227
+ [string]$Binding.adapterId -cne 'codex' -or
228
+ [int64]$Binding.version -ne 2 -or
229
+ $Binding.scopes -isnot [Array] -or
230
+ (-not (Test-ExactScopeSequence $Binding.scopes $CodexNeutralScopes) -and
231
+ -not (Test-ExactScopeSequence $Binding.scopes $CodexNeutralPreviousScopes)) -or
232
+ [string]$Binding.runnerRelativePath -notmatch '^(?:[A-Za-z0-9][A-Za-z0-9._-]*/)*[A-Za-z0-9][A-Za-z0-9._-]*\.mjs$' -or
233
+ [string]$Binding.releaseManifestSha256 -notmatch '^[a-f0-9]{64}$'
234
+ ) { Fail 'neutral_codex_binding_invalid' }
235
+ if ($ValidatePaths) { Assert-CodexNeutralPaths $Binding }
236
+ return $Binding
237
+ }
238
+
239
+ function Assert-CodexNeutralPaths($Binding) {
240
+ $releaseRoot = Assert-AbsoluteNonReparsePath ([string]$Binding.releaseRoot) $true 0
241
+ $runtimeRoot = Assert-AbsoluteNonReparsePath ([string]$Binding.runtimeRoot) $true 0
242
+ $profileManifestPath = Assert-AbsoluteNonReparsePath ([string]$Binding.profileManifestPath) $false 65536
243
+ $runnerPath = [IO.Path]::GetFullPath((Join-Path $releaseRoot ([string]$Binding.runnerRelativePath)))
244
+ $releasePrefix = $releaseRoot.TrimEnd([IO.Path]::DirectorySeparatorChar) + [IO.Path]::DirectorySeparatorChar
245
+ if (-not $runnerPath.StartsWith($releasePrefix, [StringComparison]::OrdinalIgnoreCase)) {
246
+ Fail 'neutral_codex_runner_integrity_invalid'
247
+ }
248
+ $null = Assert-AbsoluteNonReparsePath $runnerPath $false 1048576
249
+ if (
250
+ -not [string]::Equals($runtimeRoot, [IO.Path]::GetFullPath([string]$Binding.runtimeRoot), [StringComparison]::OrdinalIgnoreCase) -or
251
+ -not [string]::Equals($profileManifestPath, [IO.Path]::GetFullPath([string]$Binding.profileManifestPath), [StringComparison]::OrdinalIgnoreCase)
252
+ ) { Fail 'neutral_codex_binding_invalid' }
253
+ }
254
+
255
+ function Assert-AbsoluteNonReparsePath(
256
+ [string]$Value,
257
+ [bool]$Directory,
258
+ [int64]$MaxBytes,
259
+ [string]$BindingInvalidCode = 'neutral_codex_binding_invalid',
260
+ [string]$PathInvalidCode = 'neutral_codex_launch_manifest_invalid'
261
+ ) {
262
+ if (-not $Value -or -not [IO.Path]::IsPathFullyQualified($Value)) { Fail $BindingInvalidCode }
263
+ $full = [IO.Path]::GetFullPath($Value)
264
+ $rootPart = [IO.Path]::GetPathRoot($full)
265
+ $cursor = $rootPart
266
+ $relative = $full.Substring($rootPart.Length)
267
+ foreach ($segment in $relative.Split([IO.Path]::DirectorySeparatorChar, [StringSplitOptions]::RemoveEmptyEntries)) {
268
+ $cursor = Join-Path $cursor $segment
269
+ if (-not (Test-Path -LiteralPath $cursor)) { Fail $PathInvalidCode }
270
+ $item = Get-Item -LiteralPath $cursor -Force
271
+ if (($item.Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0) {
272
+ Fail $PathInvalidCode
273
+ }
274
+ }
275
+ $target = Get-Item -LiteralPath $full -Force
276
+ if ($Directory) {
277
+ if (-not $target.PSIsContainer) { Fail $BindingInvalidCode }
278
+ } else {
279
+ if ($target.PSIsContainer -or $target.Length -gt $MaxBytes) { Fail $PathInvalidCode }
280
+ }
281
+ return $full
282
+ }
283
+
284
+ function Assert-ProtectedStoreNoReparse([string]$Path) {
285
+ $current = [IO.Path]::GetFullPath($Path)
286
+ while ($current) {
287
+ if (Test-Path -LiteralPath $current) {
288
+ $item = Get-Item -LiteralPath $current -Force
289
+ if (($item.Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0) {
290
+ Fail 'host_protected_store_root_invalid'
291
+ }
292
+ }
293
+ $parent = [IO.Directory]::GetParent($current)
294
+ if ($null -eq $parent) { break }
295
+ $current = $parent.FullName
296
+ }
297
+ }
298
+
299
+ function Get-ProtectedStoreCanonicalLocalRoot([string]$Path) {
300
+ $handle = [HelloLynkProtectedStoreNativePath]::CreateFile(
301
+ $Path, 0, 7, [IntPtr]::Zero, 3, 0x02000000, [IntPtr]::Zero
302
+ )
303
+ if ($handle.IsInvalid) { $handle.Dispose(); Fail 'host_protected_store_root_invalid' }
304
+ try {
305
+ $buffer = [Text.StringBuilder]::new(32768)
306
+ $length = [HelloLynkProtectedStoreNativePath]::GetFinalPathNameByHandle(
307
+ $handle, $buffer, $buffer.Capacity, 0
308
+ )
309
+ if ($length -eq 0 -or $length -ge $buffer.Capacity) { Fail 'host_protected_store_root_invalid' }
310
+ $final = $buffer.ToString()
311
+ } finally { $handle.Dispose() }
312
+ if ($final.StartsWith('\\?\UNC\', [StringComparison]::OrdinalIgnoreCase)) {
313
+ Fail 'host_protected_store_root_invalid'
314
+ }
315
+ if ($final.StartsWith('\\?\')) { $final = $final.Substring(4) }
316
+ $final = [IO.Path]::GetFullPath($final)
317
+ if ($final -notmatch '^[A-Za-z]:\\' -or $final.Length -lt 4) {
318
+ Fail 'host_protected_store_root_invalid'
319
+ }
320
+ return $final.TrimEnd([IO.Path]::DirectorySeparatorChar)
321
+ }
322
+
323
+ function Set-ProtectedStoreAcl([string]$Path, [string]$Sid, [bool]$Directory) {
324
+ try {
325
+ $identity = [Security.Principal.SecurityIdentifier]::new($Sid)
326
+ if ($Directory) {
327
+ $security = [Security.AccessControl.DirectorySecurity]::new()
328
+ $security.SetOwner($identity)
329
+ $security.SetAccessRuleProtection($true, $false)
330
+ $inheritance = [Security.AccessControl.InheritanceFlags]::ContainerInherit -bor
331
+ [Security.AccessControl.InheritanceFlags]::ObjectInherit
332
+ $trustedIdentities = @(
333
+ $identity,
334
+ [Security.Principal.SecurityIdentifier]::new(
335
+ [Security.Principal.WellKnownSidType]::LocalSystemSid,
336
+ $null
337
+ ),
338
+ [Security.Principal.SecurityIdentifier]::new(
339
+ [Security.Principal.WellKnownSidType]::BuiltinAdministratorsSid,
340
+ $null
341
+ )
342
+ )
343
+ foreach ($trustedIdentity in $trustedIdentities) {
344
+ $rule = [Security.AccessControl.FileSystemAccessRule]::new(
345
+ $trustedIdentity,
346
+ [Security.AccessControl.FileSystemRights]::FullControl,
347
+ $inheritance,
348
+ [Security.AccessControl.PropagationFlags]::None,
349
+ [Security.AccessControl.AccessControlType]::Allow
350
+ )
351
+ $security.AddAccessRule($rule)
352
+ }
353
+ [IO.FileSystemAclExtensions]::SetAccessControl([IO.DirectoryInfo]::new($Path), $security)
354
+ } else {
355
+ $security = [Security.AccessControl.FileSecurity]::new()
356
+ $security.SetOwner($identity)
357
+ $security.SetAccessRuleProtection($true, $false)
358
+ $rule = [Security.AccessControl.FileSystemAccessRule]::new(
359
+ $identity,
360
+ [Security.AccessControl.FileSystemRights]::FullControl,
361
+ [Security.AccessControl.InheritanceFlags]::None,
362
+ [Security.AccessControl.PropagationFlags]::None,
363
+ [Security.AccessControl.AccessControlType]::Allow
364
+ )
365
+ $security.AddAccessRule($rule)
366
+ [IO.FileSystemAclExtensions]::SetAccessControl([IO.FileInfo]::new($Path), $security)
367
+ }
368
+ } catch { Fail 'host_protected_store_acl_failed' }
369
+ }
370
+
371
+ function Test-ProtectedStoreAcl([string]$Path, [string]$Sid, [bool]$Directory) {
372
+ # Returns instead of calling Fail: Fail exits the process, so callers that
373
+ # repair an invalid ACL must be able to observe the result first.
374
+ try {
375
+ $security = if ($Directory) {
376
+ [IO.FileSystemAclExtensions]::GetAccessControl(
377
+ [IO.DirectoryInfo]::new($Path),
378
+ [Security.AccessControl.AccessControlSections]::Access -bor
379
+ [Security.AccessControl.AccessControlSections]::Owner
380
+ )
381
+ } else {
382
+ [IO.FileSystemAclExtensions]::GetAccessControl(
383
+ [IO.FileInfo]::new($Path),
384
+ [Security.AccessControl.AccessControlSections]::Access -bor
385
+ [Security.AccessControl.AccessControlSections]::Owner
386
+ )
387
+ }
388
+ if (-not $security.AreAccessRulesProtected -or
389
+ $security.GetOwner([Security.Principal.SecurityIdentifier]).Value -cne $Sid) {
390
+ return $false
391
+ }
392
+ $rules = @($security.GetAccessRules(
393
+ $true, $true, [Security.Principal.SecurityIdentifier]
394
+ ))
395
+ $expectedInheritance = if ($Directory) {
396
+ [Security.AccessControl.InheritanceFlags]::ContainerInherit -bor
397
+ [Security.AccessControl.InheritanceFlags]::ObjectInherit
398
+ } else { [Security.AccessControl.InheritanceFlags]::None }
399
+ $expectedSids = if ($Directory) {
400
+ @(
401
+ $Sid,
402
+ [Security.Principal.SecurityIdentifier]::new(
403
+ [Security.Principal.WellKnownSidType]::LocalSystemSid,
404
+ $null
405
+ ).Value,
406
+ [Security.Principal.SecurityIdentifier]::new(
407
+ [Security.Principal.WellKnownSidType]::BuiltinAdministratorsSid,
408
+ $null
409
+ ).Value
410
+ )
411
+ } else { @($Sid) }
412
+ if ($rules.Count -ne $expectedSids.Count) { return $false }
413
+ $observedSids = @()
414
+ foreach ($rule in $rules) {
415
+ $observedSids += $rule.IdentityReference.Value
416
+ if ($expectedSids -notcontains $rule.IdentityReference.Value -or
417
+ $rule.IsInherited -or
418
+ $rule.AccessControlType -ne [Security.AccessControl.AccessControlType]::Allow -or
419
+ $rule.FileSystemRights -ne [Security.AccessControl.FileSystemRights]::FullControl -or
420
+ $rule.InheritanceFlags -ne $expectedInheritance -or
421
+ $rule.PropagationFlags -ne [Security.AccessControl.PropagationFlags]::None) {
422
+ return $false
423
+ }
424
+ }
425
+ foreach ($expectedSid in $expectedSids) {
426
+ if (@($observedSids | Where-Object { $_ -ceq $expectedSid }).Count -ne 1) {
427
+ return $false
428
+ }
429
+ }
430
+ return $true
431
+ } catch { return $false }
432
+ }
433
+
434
+ function Assert-ProtectedStoreAcl([string]$Path, [string]$Sid, [bool]$Directory) {
435
+ if (-not (Test-ProtectedStoreAcl $Path $Sid $Directory)) { Fail 'host_protected_store_acl_invalid' }
436
+ }
437
+
438
+ function Repair-ProtectedStoreAcl([string]$Path, [string]$Sid, [bool]$Directory) {
439
+ # Rewriting an identical ACL still updates the change time, which every
440
+ # Host's path cache treats as a change. Write only an ACL that is not exact.
441
+ if (-not (Test-ProtectedStoreAcl $Path $Sid $Directory)) { Set-ProtectedStoreAcl $Path $Sid $Directory }
442
+ }
443
+
444
+ function Get-PublicCapabilityRecord($Record) {
445
+ return [PSCustomObject]@{
446
+ contract = [string]$Record.contract
447
+ capabilityId = [string]$Record.capabilityId
448
+ region = [string]$Record.region
449
+ principalId = [string]$Record.principalId
450
+ delegateId = [string]$Record.delegateId
451
+ hostInstallationId = [string]$Record.hostInstallationId
452
+ adapterId = [string]$Record.adapterId
453
+ version = [int]$Record.version
454
+ state = [string]$Record.state
455
+ scopes = @($Record.scopes)
456
+ issuedAt = [int64]$Record.issuedAt
457
+ revokedAt = $Record.revokedAt
458
+ }
459
+ }
460
+
461
+ function Test-CodexNeutralBindingEqual($Left, $Right) {
462
+ return (($Left | ConvertTo-Json -Depth 8 -Compress) -ceq ($Right | ConvertTo-Json -Depth 8 -Compress))
463
+ }
464
+
465
+ function Test-CodexCapabilityMatch($Capability, $Binding) {
466
+ if (
467
+ [string]$Capability.contract -cne $RecordContract -or
468
+ [string]$Capability.state -cne 'active' -or
469
+ [string]$Capability.region -cne [string]$Binding.region -or
470
+ [string]$Capability.principalId -cne [string]$Binding.principalId -or
471
+ [string]$Capability.delegateId -cne [string]$Binding.delegateId -or
472
+ [string]$Capability.hostInstallationId -cne [string]$Binding.hostInstallationId -or
473
+ [string]$Capability.adapterId -cne 'codex' -or
474
+ [int64]$Capability.version -ne 2 -or
475
+ $Capability.scopes.Count -ne $Binding.scopes.Count
476
+ ) { return $false }
477
+ for ($index = 0; $index -lt $Binding.scopes.Count; $index += 1) {
478
+ if ([string]$Capability.scopes[$index] -cne [string]$Binding.scopes[$index]) { return $false }
479
+ }
480
+ return $true
481
+ }
482
+
483
+ function Assert-OpenClawNeutralBinding($Binding, [bool]$ValidatePaths, [bool]$RequireLaunch = $false) {
484
+ if ($null -eq $Binding -or $Binding -is [Array]) { Fail 'neutral_openclaw_binding_invalid' }
485
+ $baseFields = @(
486
+ 'adapterId', 'contract', 'delegateId', 'hostInstallationId', 'principalId',
487
+ 'profileId', 'region', 'runtimeRoot', 'scopes', 'version'
488
+ )
489
+ $releaseFields = @(
490
+ 'profileManifestPath', 'releaseManifestSha256', 'releaseRoot', 'runnerRelativePath'
491
+ )
492
+ $releaseCount = 0
493
+ foreach ($field in $releaseFields) {
494
+ if ($null -ne $Binding.PSObject.Properties[$field]) { $releaseCount += 1 }
495
+ }
496
+ $expectedFields = @($baseFields)
497
+ if ($releaseCount -gt 0) { $expectedFields += $releaseFields }
498
+ Assert-ExactFields $Binding $expectedFields 'neutral_openclaw_binding_invalid'
499
+ if (($RequireLaunch -and $releaseCount -ne $releaseFields.Count) -or
500
+ ($releaseCount -ne 0 -and $releaseCount -ne $releaseFields.Count)) {
501
+ Fail 'neutral_openclaw_binding_invalid'
502
+ }
503
+ if (
504
+ [string]$Binding.contract -cne $OpenClawNeutralBindingContract -or
505
+ [string]$Binding.region -notmatch $IdentifierPattern -or
506
+ [string]$Binding.principalId -notmatch $IdentifierPattern -or
507
+ [string]$Binding.delegateId -notmatch $IdentifierPattern -or
508
+ [string]$Binding.hostInstallationId -notmatch $IdentifierPattern -or
509
+ [string]$Binding.profileId -notmatch $IdentifierPattern -or
510
+ [string]$Binding.adapterId -cne 'openclaw' -or
511
+ [int64]$Binding.version -ne 2 -or
512
+ $Binding.scopes -isnot [Array] -or
513
+ (-not (Test-ExactScopeSequence $Binding.scopes $OpenClawNeutralScopes) -and
514
+ -not (Test-ExactScopeSequence $Binding.scopes $OpenClawNeutralPreviousScopes))
515
+ ) { Fail 'neutral_openclaw_binding_invalid' }
516
+ if ($releaseCount -eq $releaseFields.Count -and (
517
+ -not (Test-FullyQualifiedCanonicalPath ([string]$Binding.releaseRoot)) -or
518
+ -not (Test-FullyQualifiedCanonicalPath ([string]$Binding.profileManifestPath)) -or
519
+ [string]$Binding.releaseManifestSha256 -notmatch '^[a-f0-9]{64}$' -or
520
+ [string]$Binding.runnerRelativePath -cne 'runtime/neutral-host-bootstrap.mjs'
521
+ )) { Fail 'neutral_openclaw_launch_configuration_invalid' }
522
+ if ($ValidatePaths) {
523
+ $runtimeRoot = Assert-AbsoluteNonReparsePath ([string]$Binding.runtimeRoot) $true 0
524
+ if (-not [string]::Equals(
525
+ $runtimeRoot,
526
+ [IO.Path]::GetFullPath([string]$Binding.runtimeRoot),
527
+ [StringComparison]::OrdinalIgnoreCase
528
+ )) { Fail 'neutral_openclaw_binding_invalid' }
529
+ }
530
+ return $Binding
531
+ }
532
+
533
+ function Test-OpenClawNeutralBindingEqual($Left, $Right) {
534
+ return (($Left | ConvertTo-Json -Depth 8 -Compress) -ceq ($Right | ConvertTo-Json -Depth 8 -Compress))
535
+ }
536
+
537
+ function Test-OpenClawCapabilityMatch($Capability, $Binding) {
538
+ if (
539
+ [string]$Capability.contract -cne $RecordContract -or
540
+ [string]$Capability.state -cne 'active' -or
541
+ [string]$Capability.region -cne [string]$Binding.region -or
542
+ [string]$Capability.principalId -cne [string]$Binding.principalId -or
543
+ [string]$Capability.delegateId -cne [string]$Binding.delegateId -or
544
+ [string]$Capability.hostInstallationId -cne [string]$Binding.hostInstallationId -or
545
+ [string]$Capability.adapterId -cne 'openclaw' -or
546
+ [int64]$Capability.version -ne 2 -or
547
+ $Capability.scopes.Count -ne $Binding.scopes.Count
548
+ ) { return $false }
549
+ for ($index = 0; $index -lt $Binding.scopes.Count; $index += 1) {
550
+ if ([string]$Capability.scopes[$index] -cne [string]$Binding.scopes[$index]) { return $false }
551
+ }
552
+ return $true
553
+ }
554
+
555
+ function Assert-TraeWorkNeutralBinding($Binding, [bool]$ValidatePaths) {
556
+ if ($null -eq $Binding -or $Binding -is [Array]) { Fail 'neutral_trae_work_binding_invalid' }
557
+ $baseFields = @(
558
+ 'adapterId', 'contract', 'delegateId', 'hostInstallationId', 'principalId',
559
+ 'profileId', 'region', 'runtimeRoot', 'scopes', 'version'
560
+ )
561
+ $releaseFields = @(
562
+ 'profileManifestPath', 'releaseManifestSha256', 'releaseRoot', 'runnerRelativePath'
563
+ )
564
+ $releaseCount = 0
565
+ foreach ($field in $releaseFields) {
566
+ if ($null -ne $Binding.PSObject.Properties[$field]) { $releaseCount += 1 }
567
+ }
568
+ $expectedFields = @($baseFields)
569
+ if ($releaseCount -gt 0) { $expectedFields += $releaseFields }
570
+ Assert-ExactFields $Binding $expectedFields 'neutral_trae_work_binding_invalid'
571
+ if ($releaseCount -ne 0 -and $releaseCount -ne $releaseFields.Count) {
572
+ Fail 'neutral_trae_work_binding_invalid'
573
+ }
574
+ if (
575
+ [string]$Binding.contract -cne $TraeWorkNeutralBindingContract -or
576
+ [string]$Binding.region -notmatch $IdentifierPattern -or
577
+ [string]$Binding.principalId -notmatch $IdentifierPattern -or
578
+ [string]$Binding.delegateId -notmatch $IdentifierPattern -or
579
+ [string]$Binding.hostInstallationId -notmatch $IdentifierPattern -or
580
+ [string]$Binding.profileId -notmatch $IdentifierPattern -or
581
+ [string]$Binding.adapterId -cne 'trae-work' -or
582
+ [int64]$Binding.version -ne 2 -or
583
+ $Binding.scopes -isnot [Array]
584
+ ) { Fail 'neutral_trae_work_binding_invalid' }
585
+ if (-not (Test-ExactScopeSequence $Binding.scopes $TraeWorkNeutralScopes) -and
586
+ -not (Test-ExactScopeSequence $Binding.scopes $TraeWorkNeutralPreviousScopes) -and
587
+ -not (Test-ExactScopeSequence $Binding.scopes $TraeWorkNeutralLegacyScopes)) {
588
+ Fail 'neutral_trae_work_capability_binding_mismatch'
589
+ }
590
+ if ($releaseCount -eq $releaseFields.Count -and (
591
+ -not (Test-FullyQualifiedCanonicalPath ([string]$Binding.releaseRoot)) -or
592
+ -not (Test-FullyQualifiedCanonicalPath ([string]$Binding.profileManifestPath)) -or
593
+ [string]$Binding.releaseManifestSha256 -notmatch '^[a-f0-9]{64}$' -or
594
+ [string]$Binding.runnerRelativePath -cne 'runtime/neutral-host-bootstrap.mjs'
595
+ )) { Fail 'neutral_trae_work_launch_configuration_invalid' }
596
+ if ($ValidatePaths) {
597
+ $runtimeRoot = Assert-AbsoluteNonReparsePath ([string]$Binding.runtimeRoot) $true 0
598
+ if (-not [string]::Equals(
599
+ $runtimeRoot,
600
+ [IO.Path]::GetFullPath([string]$Binding.runtimeRoot),
601
+ [StringComparison]::OrdinalIgnoreCase
602
+ )) { Fail 'neutral_trae_work_binding_invalid' }
603
+ }
604
+ return $Binding
605
+ }
606
+
607
+ function Test-TraeWorkNeutralBindingEqual($Left, $Right) {
608
+ return (($Left | ConvertTo-Json -Depth 8 -Compress) -ceq ($Right | ConvertTo-Json -Depth 8 -Compress))
609
+ }
610
+
611
+ function Test-TraeWorkCapabilityMatch($Capability, $Binding) {
612
+ if (
613
+ [string]$Capability.contract -cne $RecordContract -or
614
+ [string]$Capability.state -cne 'active' -or
615
+ [string]$Capability.region -cne [string]$Binding.region -or
616
+ [string]$Capability.principalId -cne [string]$Binding.principalId -or
617
+ [string]$Capability.delegateId -cne [string]$Binding.delegateId -or
618
+ [string]$Capability.hostInstallationId -cne [string]$Binding.hostInstallationId -or
619
+ [string]$Capability.adapterId -cne 'trae-work' -or
620
+ [int64]$Capability.version -ne 2 -or
621
+ $Capability.scopes.Count -ne $Binding.scopes.Count
622
+ ) { return $false }
623
+ for ($index = 0; $index -lt $Binding.scopes.Count; $index += 1) {
624
+ if ([string]$Capability.scopes[$index] -cne [string]$Binding.scopes[$index]) { return $false }
625
+ }
626
+ return $true
627
+ }
628
+
629
+ function Assert-WorkBuddyNeutralBinding($Binding, [bool]$ValidatePaths) {
630
+ if ($null -eq $Binding -or $Binding -is [Array]) { Fail 'neutral_workbuddy_binding_invalid' }
631
+ $baseFields = @(
632
+ 'adapterId', 'contract', 'delegateId', 'hostInstallationId', 'principalId',
633
+ 'profileId', 'region', 'runtimeRoot', 'scopes', 'version'
634
+ )
635
+ $releaseFields = @(
636
+ 'profileManifestPath', 'releaseManifestSha256', 'releaseRoot', 'runnerRelativePath'
637
+ )
638
+ $releaseCount = 0
639
+ foreach ($field in $releaseFields) {
640
+ if ($null -ne $Binding.PSObject.Properties[$field]) { $releaseCount += 1 }
641
+ }
642
+ $expectedFields = @($baseFields)
643
+ if ($releaseCount -gt 0) { $expectedFields += $releaseFields }
644
+ Assert-ExactFields $Binding $expectedFields 'neutral_workbuddy_binding_invalid'
645
+ if ($releaseCount -ne 0 -and $releaseCount -ne $releaseFields.Count) {
646
+ Fail 'neutral_workbuddy_binding_invalid'
647
+ }
648
+ if (
649
+ [string]$Binding.contract -cne $WorkBuddyNeutralBindingContract -or
650
+ [string]$Binding.region -notmatch $IdentifierPattern -or
651
+ [string]$Binding.principalId -notmatch $IdentifierPattern -or
652
+ [string]$Binding.delegateId -notmatch $IdentifierPattern -or
653
+ [string]$Binding.hostInstallationId -notmatch $IdentifierPattern -or
654
+ [string]$Binding.profileId -notmatch $IdentifierPattern -or
655
+ [string]$Binding.adapterId -cne 'workbuddy' -or
656
+ [int64]$Binding.version -ne 2 -or
657
+ $Binding.scopes -isnot [Array]
658
+ ) { Fail 'neutral_workbuddy_binding_invalid' }
659
+ if (-not (Test-ExactScopeSequence $Binding.scopes $WorkBuddyNeutralScopes) -and
660
+ -not (Test-ExactScopeSequence $Binding.scopes $WorkBuddyNeutralPreviousScopes) -and
661
+ -not (Test-ExactScopeSequence $Binding.scopes $WorkBuddyNeutralLegacyScopes)) {
662
+ Fail 'neutral_workbuddy_capability_binding_mismatch'
663
+ }
664
+ if ($releaseCount -eq $releaseFields.Count -and (
665
+ -not (Test-FullyQualifiedCanonicalPath ([string]$Binding.releaseRoot)) -or
666
+ -not (Test-FullyQualifiedCanonicalPath ([string]$Binding.profileManifestPath)) -or
667
+ [string]$Binding.releaseManifestSha256 -notmatch '^[a-f0-9]{64}$' -or
668
+ [string]$Binding.runnerRelativePath -cne 'runtime/neutral-host-bootstrap.mjs'
669
+ )) { Fail 'neutral_workbuddy_launch_configuration_invalid' }
670
+ if ($ValidatePaths) {
671
+ $runtimeRoot = Assert-AbsoluteNonReparsePath ([string]$Binding.runtimeRoot) $true 0
672
+ if (-not [string]::Equals(
673
+ $runtimeRoot,
674
+ [IO.Path]::GetFullPath([string]$Binding.runtimeRoot),
675
+ [StringComparison]::OrdinalIgnoreCase
676
+ )) { Fail 'neutral_workbuddy_binding_invalid' }
677
+ }
678
+ return $Binding
679
+ }
680
+
681
+ function Test-WorkBuddyNeutralBindingEqual($Left, $Right) {
682
+ return (($Left | ConvertTo-Json -Depth 8 -Compress) -ceq ($Right | ConvertTo-Json -Depth 8 -Compress))
683
+ }
684
+
685
+ function Test-WorkBuddyCapabilityMatch($Capability, $Binding) {
686
+ if (
687
+ [string]$Capability.contract -cne $RecordContract -or
688
+ [string]$Capability.state -cne 'active' -or
689
+ [string]$Capability.region -cne [string]$Binding.region -or
690
+ [string]$Capability.principalId -cne [string]$Binding.principalId -or
691
+ [string]$Capability.delegateId -cne [string]$Binding.delegateId -or
692
+ [string]$Capability.hostInstallationId -cne [string]$Binding.hostInstallationId -or
693
+ [string]$Capability.adapterId -cne 'workbuddy' -or
694
+ [int64]$Capability.version -ne 2 -or
695
+ $Capability.scopes.Count -ne $Binding.scopes.Count
696
+ ) { return $false }
697
+ for ($index = 0; $index -lt $Binding.scopes.Count; $index += 1) {
698
+ if ([string]$Capability.scopes[$index] -cne [string]$Binding.scopes[$index]) { return $false }
699
+ }
700
+ return $true
701
+ }
702
+
703
+ function Assert-ClaudeCodeNeutralBinding($Binding, [bool]$ValidatePaths) {
704
+ if ($null -eq $Binding -or $Binding -is [Array]) { Fail 'neutral_claude_code_binding_invalid' }
705
+ $baseFields = @(
706
+ 'adapterId', 'contract', 'delegateId', 'hostInstallationId', 'principalId',
707
+ 'profileId', 'region', 'runtimeRoot', 'scopes', 'version'
708
+ )
709
+ $releaseFields = @(
710
+ 'profileManifestPath', 'releaseManifestSha256', 'releaseRoot', 'runnerRelativePath'
711
+ )
712
+ $releaseCount = 0
713
+ foreach ($field in $releaseFields) {
714
+ if ($null -ne $Binding.PSObject.Properties[$field]) { $releaseCount += 1 }
715
+ }
716
+ $expectedFields = @($baseFields)
717
+ if ($releaseCount -gt 0) { $expectedFields += $releaseFields }
718
+ Assert-ExactFields $Binding $expectedFields 'neutral_claude_code_binding_invalid'
719
+ if ($releaseCount -ne 0 -and $releaseCount -ne $releaseFields.Count) {
720
+ Fail 'neutral_claude_code_binding_invalid'
721
+ }
722
+ if (
723
+ [string]$Binding.contract -cne $ClaudeCodeNeutralBindingContract -or
724
+ [string]$Binding.region -notmatch $IdentifierPattern -or
725
+ [string]$Binding.principalId -notmatch $IdentifierPattern -or
726
+ [string]$Binding.delegateId -notmatch $IdentifierPattern -or
727
+ [string]$Binding.hostInstallationId -notmatch $IdentifierPattern -or
728
+ [string]$Binding.profileId -notmatch $IdentifierPattern -or
729
+ [string]$Binding.adapterId -cne 'claude-code' -or
730
+ [int64]$Binding.version -ne 2 -or
731
+ $Binding.scopes -isnot [Array]
732
+ ) { Fail 'neutral_claude_code_binding_invalid' }
733
+ if (-not (Test-ExactScopeSequence $Binding.scopes $ClaudeCodeNeutralScopes)) {
734
+ Fail 'neutral_claude_code_capability_binding_mismatch'
735
+ }
736
+ if ($releaseCount -eq $releaseFields.Count -and (
737
+ -not (Test-FullyQualifiedCanonicalPath ([string]$Binding.releaseRoot)) -or
738
+ -not (Test-FullyQualifiedCanonicalPath ([string]$Binding.profileManifestPath)) -or
739
+ [string]$Binding.releaseManifestSha256 -notmatch '^[a-f0-9]{64}$' -or
740
+ [string]$Binding.runnerRelativePath -cne 'runtime/neutral-host-bootstrap.mjs'
741
+ )) { Fail 'neutral_claude_code_launch_configuration_invalid' }
742
+ if ($ValidatePaths) {
743
+ $runtimeRoot = Assert-AbsoluteNonReparsePath ([string]$Binding.runtimeRoot) $true 0
744
+ if (-not [string]::Equals(
745
+ $runtimeRoot,
746
+ [IO.Path]::GetFullPath([string]$Binding.runtimeRoot),
747
+ [StringComparison]::OrdinalIgnoreCase
748
+ )) { Fail 'neutral_claude_code_binding_invalid' }
749
+ }
750
+ return $Binding
751
+ }
752
+
753
+ function Test-ClaudeCodeNeutralBindingEqual($Left, $Right) {
754
+ return (($Left | ConvertTo-Json -Depth 8 -Compress) -ceq ($Right | ConvertTo-Json -Depth 8 -Compress))
755
+ }
756
+
757
+ function Test-ClaudeCodeCapabilityMatch($Capability, $Binding) {
758
+ if (
759
+ [string]$Capability.contract -cne $RecordContract -or
760
+ [string]$Capability.state -cne 'active' -or
761
+ [string]$Capability.region -cne [string]$Binding.region -or
762
+ [string]$Capability.principalId -cne [string]$Binding.principalId -or
763
+ [string]$Capability.delegateId -cne [string]$Binding.delegateId -or
764
+ [string]$Capability.hostInstallationId -cne [string]$Binding.hostInstallationId -or
765
+ [string]$Capability.adapterId -cne 'claude-code' -or
766
+ [int64]$Capability.version -ne 2 -or
767
+ $Capability.scopes.Count -ne $Binding.scopes.Count
768
+ ) { return $false }
769
+ for ($index = 0; $index -lt $Binding.scopes.Count; $index += 1) {
770
+ if ([string]$Capability.scopes[$index] -cne [string]$Binding.scopes[$index]) { return $false }
771
+ }
772
+ return $true
773
+ }
774
+
775
+ function Assert-CursorNeutralBinding($Binding, [bool]$ValidatePaths, [bool]$RequireCurrentScopes = $false) {
776
+ if ($null -eq $Binding -or $Binding -is [Array]) { Fail 'neutral_cursor_binding_invalid' }
777
+ $baseFields = @(
778
+ 'adapterId', 'contract', 'delegateId', 'hostInstallationId', 'principalId',
779
+ 'profileId', 'region', 'runtimeRoot', 'scopes', 'version'
780
+ )
781
+ $releaseFields = @(
782
+ 'profileManifestPath', 'releaseManifestSha256', 'releaseRoot', 'runnerRelativePath'
783
+ )
784
+ $releaseCount = 0
785
+ foreach ($field in $releaseFields) {
786
+ if ($null -ne $Binding.PSObject.Properties[$field]) { $releaseCount += 1 }
787
+ }
788
+ $expectedFields = @($baseFields)
789
+ if ($releaseCount -gt 0) { $expectedFields += $releaseFields }
790
+ Assert-ExactFields $Binding $expectedFields 'neutral_cursor_binding_invalid'
791
+ if ($releaseCount -ne 0 -and $releaseCount -ne $releaseFields.Count) {
792
+ Fail 'neutral_cursor_binding_invalid'
793
+ }
794
+ if (
795
+ [string]$Binding.contract -cne $CursorNeutralBindingContract -or
796
+ [string]$Binding.region -notmatch $IdentifierPattern -or
797
+ [string]$Binding.principalId -notmatch $IdentifierPattern -or
798
+ [string]$Binding.delegateId -notmatch $IdentifierPattern -or
799
+ [string]$Binding.hostInstallationId -notmatch $IdentifierPattern -or
800
+ [string]$Binding.profileId -notmatch $IdentifierPattern -or
801
+ [string]$Binding.adapterId -cne 'cursor' -or
802
+ [int64]$Binding.version -ne 2 -or
803
+ $Binding.scopes -isnot [Array] -or
804
+ (-not (Test-ExactScopeSequence $Binding.scopes $CursorNeutralScopes) -and
805
+ ($RequireCurrentScopes -or -not (Test-ExactScopeSequence $Binding.scopes $CursorNeutralPreviousScopes)))
806
+ ) { Fail 'neutral_cursor_binding_invalid' }
807
+ if ($releaseCount -eq $releaseFields.Count -and (
808
+ -not (Test-FullyQualifiedCanonicalPath ([string]$Binding.releaseRoot)) -or
809
+ -not (Test-FullyQualifiedCanonicalPath ([string]$Binding.profileManifestPath)) -or
810
+ [string]$Binding.releaseManifestSha256 -notmatch '^[a-f0-9]{64}$' -or
811
+ [string]$Binding.runnerRelativePath -cne 'runtime/neutral-host-bootstrap.mjs'
812
+ )) { Fail 'neutral_cursor_launch_configuration_invalid' }
813
+ if ($ValidatePaths) {
814
+ $runtimeRoot = Assert-AbsoluteNonReparsePath (
815
+ [string]$Binding.runtimeRoot
816
+ ) $true 0 'neutral_cursor_binding_invalid' 'neutral_cursor_launch_configuration_invalid'
817
+ if (-not [string]::Equals(
818
+ $runtimeRoot,
819
+ [IO.Path]::GetFullPath([string]$Binding.runtimeRoot),
820
+ [StringComparison]::OrdinalIgnoreCase
821
+ )) { Fail 'neutral_cursor_binding_invalid' }
822
+ }
823
+ return $Binding
824
+ }
825
+
826
+ function Test-CursorNeutralBindingEqual($Left, $Right) {
827
+ return (($Left | ConvertTo-Json -Depth 8 -Compress) -ceq ($Right | ConvertTo-Json -Depth 8 -Compress))
828
+ }
829
+
830
+ function Test-CursorCapabilityMatch($Capability, $Binding) {
831
+ if (
832
+ [string]$Capability.contract -cne $RecordContract -or
833
+ [string]$Capability.state -cne 'active' -or
834
+ [string]$Capability.region -cne [string]$Binding.region -or
835
+ [string]$Capability.principalId -cne [string]$Binding.principalId -or
836
+ [string]$Capability.delegateId -cne [string]$Binding.delegateId -or
837
+ [string]$Capability.hostInstallationId -cne [string]$Binding.hostInstallationId -or
838
+ [string]$Capability.adapterId -cne 'cursor' -or
839
+ [int64]$Capability.version -ne 2 -or
840
+ $Capability.scopes.Count -ne $Binding.scopes.Count
841
+ ) { return $false }
842
+ for ($index = 0; $index -lt $Binding.scopes.Count; $index += 1) {
843
+ if ([string]$Capability.scopes[$index] -cne [string]$Binding.scopes[$index]) { return $false }
844
+ }
845
+ return $true
846
+ }
847
+
848
+ function Assert-DshNeutralBinding($Binding, [bool]$ValidatePaths, [bool]$RequireCurrentScopes = $false) {
849
+ if ($null -eq $Binding -or $Binding -is [Array]) { Fail 'neutral_dsh_binding_invalid' }
850
+ Assert-ExactFields $Binding @(
851
+ 'adapterId', 'contract', 'delegateId', 'hostInstallationId', 'principalId',
852
+ 'profileId', 'profileManifestPath', 'region', 'releaseManifestSha256', 'releaseRoot',
853
+ 'runnerRelativePath', 'runtimeRoot', 'scopes', 'version'
854
+ ) 'neutral_dsh_binding_invalid'
855
+ if ([string]$Binding.contract -cne $DshNeutralBindingContract -or
856
+ [string]$Binding.region -notmatch $IdentifierPattern -or
857
+ [string]$Binding.principalId -notmatch $IdentifierPattern -or
858
+ [string]$Binding.delegateId -notmatch $IdentifierPattern -or
859
+ [string]$Binding.hostInstallationId -notmatch $IdentifierPattern -or
860
+ [string]$Binding.profileId -notmatch $IdentifierPattern -or
861
+ [string]$Binding.adapterId -cne 'dsh' -or [int64]$Binding.version -ne 2 -or
862
+ $Binding.scopes -isnot [Array] -or
863
+ (-not (Test-ExactScopeSequence $Binding.scopes $DshNeutralScopes) -and
864
+ ($RequireCurrentScopes -or -not (Test-ExactScopeSequence $Binding.scopes $DshNeutralPreviousScopes)))) {
865
+ Fail 'neutral_dsh_binding_invalid'
866
+ }
867
+ if (-not (Test-FullyQualifiedCanonicalPath ([string]$Binding.releaseRoot)) -or
868
+ -not (Test-FullyQualifiedCanonicalPath ([string]$Binding.profileManifestPath)) -or
869
+ [string]$Binding.releaseManifestSha256 -notmatch '^[a-f0-9]{64}$' -or
870
+ [string]$Binding.runnerRelativePath -cne 'runtime/neutral-host-bootstrap.mjs') { Fail 'neutral_dsh_launch_configuration_invalid' }
871
+ if ($ValidatePaths) {
872
+ $runtimeRoot = Assert-AbsoluteNonReparsePath ([string]$Binding.runtimeRoot) $true 0 'neutral_dsh_binding_invalid' 'neutral_dsh_launch_configuration_invalid'
873
+ if (-not [string]::Equals($runtimeRoot, [IO.Path]::GetFullPath([string]$Binding.runtimeRoot), [StringComparison]::OrdinalIgnoreCase)) { Fail 'neutral_dsh_binding_invalid' }
874
+ }
875
+ return $Binding
876
+ }
877
+
878
+ function Test-DshNeutralBindingEqual($Left, $Right) { return (($Left | ConvertTo-Json -Depth 8 -Compress) -ceq ($Right | ConvertTo-Json -Depth 8 -Compress)) }
879
+ function Test-DshCapabilityMatch($Capability, $Binding) {
880
+ if ([string]$Capability.contract -cne $RecordContract -or [string]$Capability.state -cne 'active' -or
881
+ [string]$Capability.region -cne [string]$Binding.region -or [string]$Capability.principalId -cne [string]$Binding.principalId -or
882
+ [string]$Capability.delegateId -cne [string]$Binding.delegateId -or [string]$Capability.hostInstallationId -cne [string]$Binding.hostInstallationId -or
883
+ [string]$Capability.adapterId -cne 'dsh' -or [int64]$Capability.version -ne 2 -or $Capability.scopes.Count -ne $Binding.scopes.Count) { return $false }
884
+ for ($index = 0; $index -lt $Binding.scopes.Count; $index += 1) { if ([string]$Capability.scopes[$index] -cne [string]$Binding.scopes[$index]) { return $false } }
885
+ return $true
886
+ }
887
+
888
+ function Test-NeutralBindingOwnerEqual($Left, $Right) {
889
+ foreach ($field in @(
890
+ 'contract', 'region', 'principalId', 'delegateId', 'hostInstallationId',
891
+ 'profileId', 'adapterId', 'version', 'runtimeRoot'
892
+ )) {
893
+ if ([string]$Left.$field -cne [string]$Right.$field) { return $false }
894
+ }
895
+ return $true
896
+ }
897
+
898
+ function Assert-TransportCredential($Record) {
899
+ Assert-ExactFields $Record @(
900
+ 'contract', 'credentialId', 'principalId', 'hostInstallationId', 'state',
901
+ 'secret', 'issuedAt', 'expiresAt', 'revokedAt'
902
+ ) 'host_transport_credential_fields_invalid'
903
+ if (
904
+ [string]$Record.contract -cne $TransportCredentialContract -or
905
+ [string]$Record.credentialId -notmatch $IdentifierPattern -or
906
+ [string]$Record.principalId -notmatch $IdentifierPattern -or
907
+ [string]$Record.hostInstallationId -notmatch $IdentifierPattern -or
908
+ [string]$Record.secret -notmatch $TransportCredentialSecretPattern -or
909
+ [string]$Matches[1] -cne [string]$Record.credentialId -or
910
+ @('active', 'revoked') -notcontains [string]$Record.state -or
911
+ [int64]$Record.issuedAt -lt 0 -or
912
+ [int64]$Record.expiresAt -le [int64]$Record.issuedAt
913
+ ) { Fail 'host_transport_credential_invalid' }
914
+ if (
915
+ ([string]$Record.state -eq 'active' -and $null -ne $Record.revokedAt) -or
916
+ ([string]$Record.state -eq 'revoked' -and (
917
+ $null -eq $Record.revokedAt -or [int64]$Record.revokedAt -lt [int64]$Record.issuedAt
918
+ ))
919
+ ) { Fail 'host_transport_credential_state_invalid' }
920
+ return $Record
921
+ }
922
+
923
+ function Assert-TransportSession($Record) {
924
+ Assert-ExactFields $Record @(
925
+ 'contract', 'sessionId', 'sessionSecret', 'credentialId', 'principalId',
926
+ 'hostInstallationId', 'issuedAt', 'expiresAt'
927
+ ) 'host_transport_session_fields_invalid'
928
+ if (
929
+ [string]$Record.contract -cne $TransportSessionContract -or
930
+ [string]$Record.sessionId -notmatch $TransportSessionIdPattern -or
931
+ [string]$Record.sessionSecret -notmatch $TransportSessionSecretPattern -or
932
+ [string]$Record.credentialId -notmatch $IdentifierPattern -or
933
+ [string]$Record.principalId -notmatch $IdentifierPattern -or
934
+ [string]$Record.hostInstallationId -notmatch $IdentifierPattern -or
935
+ [int64]$Record.issuedAt -lt 0 -or
936
+ [int64]$Record.expiresAt -le [int64]$Record.issuedAt
937
+ ) { Fail 'host_transport_session_invalid' }
938
+ return $Record
939
+ }
940
+
941
+ function Assert-QueryOwner {
942
+ if (
943
+ [string]$PrincipalId -notmatch $IdentifierPattern -or
944
+ [string]$HostInstallationId -notmatch $IdentifierPattern
945
+ ) { Fail 'host_transport_owner_invalid' }
946
+ }
947
+
948
+ function Test-TransportCredentialEqual($Left, $Right) {
949
+ return (
950
+ [string]$Left.contract -ceq [string]$Right.contract -and
951
+ [string]$Left.credentialId -ceq [string]$Right.credentialId -and
952
+ [string]$Left.principalId -ceq [string]$Right.principalId -and
953
+ [string]$Left.hostInstallationId -ceq [string]$Right.hostInstallationId -and
954
+ [string]$Left.state -ceq [string]$Right.state -and
955
+ [string]$Left.secret -ceq [string]$Right.secret -and
956
+ [int64]$Left.issuedAt -eq [int64]$Right.issuedAt -and
957
+ [int64]$Left.expiresAt -eq [int64]$Right.expiresAt -and
958
+ (($null -eq $Left.revokedAt -and $null -eq $Right.revokedAt) -or
959
+ [int64]$Left.revokedAt -eq [int64]$Right.revokedAt)
960
+ )
961
+ }
962
+
963
+ function Read-DeviceEnrollment([string]$EnrollmentPath, [string]$Sid) {
964
+ $enrollment = [PSCustomObject]@{ contract = 'hellolynk-device-enrollment-v1'; revision = 0; value = $null }
965
+ Assert-ProtectedStoreNoReparse $EnrollmentPath
966
+ if (Test-Path -LiteralPath $EnrollmentPath) {
967
+ Assert-ProtectedStoreAcl $EnrollmentPath $Sid $false
968
+ $encrypted = Import-Clixml -LiteralPath $EnrollmentPath
969
+ if ($encrypted -isnot [Management.Automation.PSCredential] -or $encrypted.UserName -cne $CredentialName) {
970
+ Fail 'device_enrollment_store_invalid'
971
+ }
972
+ $plainEnrollment = $encrypted.GetNetworkCredential().Password
973
+ if ([Text.Encoding]::UTF8.GetByteCount($plainEnrollment) -gt 65536) { Fail 'device_enrollment_store_invalid' }
974
+ try { $enrollment = $plainEnrollment | ConvertFrom-Json } catch { Fail 'device_enrollment_store_invalid' }
975
+ $plainEnrollment = $null
976
+ Assert-ExactFields $enrollment @('contract', 'revision', 'value') 'device_enrollment_store_invalid'
977
+ if ($enrollment.contract -cne 'hellolynk-device-enrollment-v1' -or
978
+ $enrollment.revision -isnot [long] -and $enrollment.revision -isnot [int] -or
979
+ $enrollment.revision -lt 0 -or $enrollment.revision -ge 9007199254740991) {
980
+ Fail 'device_enrollment_store_invalid'
981
+ }
982
+ }
983
+ return $enrollment
984
+ }
985
+
986
+ function Test-TransportSessionEqual($Left, $Right) {
987
+ return (
988
+ [string]$Left.contract -ceq [string]$Right.contract -and
989
+ [string]$Left.sessionId -ceq [string]$Right.sessionId -and
990
+ [string]$Left.sessionSecret -ceq [string]$Right.sessionSecret -and
991
+ [string]$Left.credentialId -ceq [string]$Right.credentialId -and
992
+ [string]$Left.principalId -ceq [string]$Right.principalId -and
993
+ [string]$Left.hostInstallationId -ceq [string]$Right.hostInstallationId -and
994
+ [int64]$Left.issuedAt -eq [int64]$Right.issuedAt -and
995
+ [int64]$Left.expiresAt -eq [int64]$Right.expiresAt
996
+ )
997
+ }
998
+
999
+ function Get-ActiveTransportCredential($Store, [int64]$Now) {
1000
+ Assert-QueryOwner
1001
+ $matching = @($Store.transportCredentials | Where-Object {
1002
+ [string]$_.principalId -ceq $PrincipalId -and
1003
+ [string]$_.hostInstallationId -ceq $HostInstallationId -and
1004
+ [string]$_.state -ceq 'active'
1005
+ })
1006
+ if ($matching.Count -eq 0) { Fail 'host_transport_credential_required' }
1007
+ if ($matching.Count -gt 1) { Fail 'host_transport_credential_ambiguous' }
1008
+ if ([int64]$matching[0].expiresAt -le $Now) { Fail 'host_transport_credential_expired' }
1009
+ return $matching[0]
1010
+ }
1011
+
1012
+ # Immutable, authenticated radix pages keep retirement evidence out of the
1013
+ # current-state envelope. Page limits bound individual reads, not lifetime use.
1014
+ function Get-RetiredDigest([byte[]]$Bytes) {
1015
+ return [Convert]::ToHexString([Security.Cryptography.SHA256]::HashData($Bytes)).ToLowerInvariant()
1016
+ }
1017
+
1018
+ function Assert-RetiredReference($Reference) {
1019
+ if ($null -eq $Reference) { return }
1020
+ Assert-ExactFields $Reference @('hash', 'count') 'host_retired_index_invalid'
1021
+ if ([string]$Reference.hash -cnotmatch '^[a-f0-9]{64}$' -or
1022
+ ($Reference.count -isnot [long] -and $Reference.count -isnot [int]) -or $Reference.count -le 0) {
1023
+ Fail 'host_retired_index_invalid'
1024
+ }
1025
+ }
1026
+
1027
+ function Get-RetiredDirectory([string]$Root) {
1028
+ $directory = Join-Path $Root 'transport-retired-v1'
1029
+ Assert-ProtectedStoreNoReparse $directory
1030
+ return $directory
1031
+ }
1032
+
1033
+ function Protect-RetiredJson($Value) {
1034
+ $bytes = [Text.Encoding]::UTF8.GetBytes(($Value | ConvertTo-Json -Depth 12 -Compress))
1035
+ try {
1036
+ return ,([Security.Cryptography.ProtectedData]::Protect($bytes,
1037
+ [Text.Encoding]::UTF8.GetBytes($RetiredIndexContract), [Security.Cryptography.DataProtectionScope]::CurrentUser))
1038
+ } finally { [Array]::Clear($bytes, 0, $bytes.Length) }
1039
+ }
1040
+
1041
+ function Read-RetiredJson([string]$Path, [int64]$MaxBytes, [string]$Hash = '') {
1042
+ $null = Assert-AbsoluteNonReparsePath $Path $false $MaxBytes 'host_retired_index_invalid' 'host_retired_index_unavailable'
1043
+ $cipher = [IO.File]::ReadAllBytes($Path)
1044
+ if ($Hash -and (Get-RetiredDigest $cipher) -cne $Hash) { Fail 'host_retired_index_integrity_failed' }
1045
+ $plain = $null
1046
+ try {
1047
+ $plain = [Security.Cryptography.ProtectedData]::Unprotect($cipher,
1048
+ [Text.Encoding]::UTF8.GetBytes($RetiredIndexContract), [Security.Cryptography.DataProtectionScope]::CurrentUser)
1049
+ return ([Text.UTF8Encoding]::new($false, $true).GetString($plain) | ConvertFrom-Json)
1050
+ } catch { Fail 'host_retired_index_decryption_failed' }
1051
+ finally { if ($null -ne $plain) { [Array]::Clear($plain, 0, $plain.Length) } }
1052
+ }
1053
+
1054
+ function Read-RetiredNode([string]$Root, $Reference, [string]$Prefix, [hashtable]$Nodes = $null) {
1055
+ Assert-RetiredReference $Reference
1056
+ if ($null -eq $Reference) { return $null }
1057
+ # Only Read-Store supplies this map, scoped to its one mutex-protected read.
1058
+ # Content-addressed nodes are immutable; retain every validation context in
1059
+ # the key so a different root, count or prefix cannot reuse another result.
1060
+ $cacheKey = "$Root`0$($Reference.hash)`0$($Reference.count)`0$Prefix"
1061
+ if ($null -ne $Nodes -and $Nodes.ContainsKey($cacheKey)) { return $Nodes[$cacheKey] }
1062
+ $node = Read-RetiredJson (Join-Path (Get-RetiredDirectory $Root) ($Reference.hash + '.dpapi')) 131072 $Reference.hash
1063
+ if ($node.kind -ceq 'leaf') {
1064
+ Assert-ExactFields $node @('contract', 'prefix', 'kind', 'hashes') 'host_retired_index_invalid'
1065
+ if ($node.hashes -isnot [Array] -or $node.hashes.Count -ne $Reference.count -or $node.hashes.Count -gt 512) {
1066
+ Fail 'host_retired_index_invalid'
1067
+ }
1068
+ $seen = @{}
1069
+ foreach ($hash in $node.hashes) {
1070
+ if ($hash -isnot [string] -or $hash -cnotmatch '^[a-f0-9]{64}$' -or -not $hash.StartsWith($Prefix) -or $seen.ContainsKey($hash)) {
1071
+ Fail 'host_retired_index_invalid'
1072
+ }
1073
+ $seen[$hash] = $true
1074
+ }
1075
+ } elseif ($node.kind -ceq 'branch') {
1076
+ Assert-ExactFields $node @('contract', 'prefix', 'kind', 'children') 'host_retired_index_invalid'
1077
+ $children = @($node.children.PSObject.Properties)
1078
+ if ($Prefix.Length -ge 64 -or $children.Count -lt 1 -or $children.Count -gt 16) { Fail 'host_retired_index_invalid' }
1079
+ [long]$count = 0
1080
+ foreach ($child in $children) {
1081
+ if ($child.Name -cnotmatch '^[a-f0-9]$' -or $null -eq $child.Value) { Fail 'host_retired_index_invalid' }
1082
+ Assert-RetiredReference $child.Value
1083
+ if ($count -gt [long]::MaxValue - $child.Value.count) { Fail 'host_retired_index_invalid' }
1084
+ $count += $child.Value.count
1085
+ }
1086
+ if ($count -ne $Reference.count) { Fail 'host_retired_index_invalid' }
1087
+ } else { Fail 'host_retired_index_invalid' }
1088
+ if ($node.contract -cne $RetiredIndexContract -or $node.prefix -cne $Prefix) { Fail 'host_retired_index_invalid' }
1089
+ if ($null -ne $Nodes) { $Nodes[$cacheKey] = $node }
1090
+ return $node
1091
+ }
1092
+
1093
+ function Test-RetiredTransportSession($Store, [string]$Root, [string]$SessionId, [hashtable]$Nodes = $null) {
1094
+ # Preserve the conservative case-insensitive conflict check of Read-Store.
1095
+ if (@($Store.retiredTransportSessionIds | Where-Object { [string]$_ -ieq $SessionId }).Count) { return $true }
1096
+ $hash = Get-RetiredDigest ([Text.Encoding]::UTF8.GetBytes($SessionId.ToLowerInvariant()))
1097
+ return Test-RetiredHash $Store $Root $hash $Nodes
1098
+ }
1099
+
1100
+ function Test-RetiredHash($Store, [string]$Root, [string]$Hash, [hashtable]$Nodes = $null) {
1101
+ if ($Hash -cnotmatch '^[a-f0-9]{64}$') { Fail 'host_retired_index_invalid' }
1102
+ $reference = $Store.retiredTransportSessionIndex
1103
+ $prefix = ''
1104
+ while ($null -ne $reference) {
1105
+ $node = Read-RetiredNode $Root $reference $prefix $Nodes
1106
+ if ($node.kind -ceq 'leaf') { return $node.hashes -ccontains $hash }
1107
+ $digit = $hash.Substring($prefix.Length, 1)
1108
+ $reference = $node.children.$digit
1109
+ $prefix += $digit
1110
+ }
1111
+ return $false
1112
+ }
1113
+
1114
+ # The existing retirement tree and transaction protect both session tombstones
1115
+ # and minimal authorization history. NUL-delimited domains cannot overlap a
1116
+ # valid session ID. Each history ciphertext is bound to the authenticated root.
1117
+ function Get-AuthorizationHistoryKey([string]$Kind, [string]$Id) {
1118
+ if ($Kind -cnotin @('capability','credential') -or
1119
+ ($Kind -ceq 'capability' -and $Id -notmatch $CapabilityIdPattern) -or
1120
+ ($Kind -ceq 'credential' -and $Id -notmatch $IdentifierPattern)) { Fail 'host_authorization_history_invalid' }
1121
+ return Get-RetiredDigest ([Text.Encoding]::UTF8.GetBytes("authorization`0$Kind`0" + $Id.ToLowerInvariant()))
1122
+ }
1123
+
1124
+ function Get-AuthorizationHistoryProof([string]$Key, [string]$CipherHash) {
1125
+ return Get-RetiredDigest ([Text.Encoding]::UTF8.GetBytes("authorization-proof`0$Key`0$CipherHash"))
1126
+ }
1127
+
1128
+ function New-AuthorizationHistoryRecord([string]$Kind, $Record) {
1129
+ if ($Kind -ceq 'capability') { $null = Assert-Record $Record; $idField = 'capabilityId' }
1130
+ elseif ($Kind -ceq 'credential') { $null = Assert-TransportCredential $Record; $idField = 'credentialId' }
1131
+ else { Fail 'host_authorization_history_invalid' }
1132
+ if ($Record.state -cne 'revoked') { Fail 'host_authorization_history_invalid' }
1133
+ $summary = [ordered]@{ state='revoked'; issuedAt=[long]$Record.issuedAt; revokedAt=[long]$Record.revokedAt }
1134
+ $summary[$idField] = [string]$Record.$idField
1135
+ if ($Kind -ceq 'credential') {
1136
+ $summary.principalId = [string]$Record.principalId
1137
+ $summary.hostInstallationId = [string]$Record.hostInstallationId
1138
+ }
1139
+ return [PSCustomObject]@{ contract=$AuthorizationHistoryContract; kind=$Kind; record=[PSCustomObject]$summary }
1140
+ }
1141
+
1142
+ function Read-AuthorizationHistory($Store, [string]$Root, [string]$Kind, [string]$Id) {
1143
+ $key = Get-AuthorizationHistoryKey $Kind $Id
1144
+ if (-not (Test-RetiredHash $Store $Root $key)) { return $null }
1145
+ if ($Store.authorizationHistoryContract -cne $AuthorizationHistoryContract) { Fail 'host_authorization_history_invalid' }
1146
+ $path = Join-Path (Get-RetiredDirectory $Root) ($key + '.dpapi')
1147
+ $null = Assert-AbsoluteNonReparsePath $path $false 8192 'host_authorization_history_invalid' 'host_authorization_history_unavailable'
1148
+ $cipherHash = Get-RetiredDigest ([IO.File]::ReadAllBytes($path))
1149
+ if (-not (Test-RetiredHash $Store $Root (Get-AuthorizationHistoryProof $key $cipherHash))) { Fail 'host_authorization_history_integrity_failed' }
1150
+ $value = Read-RetiredJson $path 8192 $cipherHash
1151
+ Assert-ExactFields $value @('contract','kind','record') 'host_authorization_history_invalid'
1152
+ if ($value.contract -cne $AuthorizationHistoryContract -or $value.kind -cne $Kind) { Fail 'host_authorization_history_invalid' }
1153
+ $record = $value.record
1154
+ $fields = @('issuedAt','revokedAt','state')
1155
+ if ($Kind -ceq 'capability') { $fields += 'capabilityId'; $idField='capabilityId' }
1156
+ else { $fields += @('credentialId','principalId','hostInstallationId'); $idField='credentialId' }
1157
+ Assert-ExactFields $record $fields 'host_authorization_history_invalid'
1158
+ if ($record.$idField -cne $Id -or $record.state -cne 'revoked' -or
1159
+ ($record.issuedAt -isnot [long] -and $record.issuedAt -isnot [int]) -or
1160
+ ($record.revokedAt -isnot [long] -and $record.revokedAt -isnot [int]) -or
1161
+ $record.issuedAt -lt 0 -or $record.revokedAt -lt $record.issuedAt -or
1162
+ ($Kind -ceq 'credential' -and ($record.principalId -notmatch $IdentifierPattern -or $record.hostInstallationId -notmatch $IdentifierPattern))) {
1163
+ Fail 'host_authorization_history_invalid'
1164
+ }
1165
+ return $record
1166
+ }
1167
+
1168
+ function Test-AuthorizationIdUsed($Store, [string]$Root, [string]$Kind, [string]$Id) {
1169
+ $key = Get-AuthorizationHistoryKey $Kind $Id
1170
+ $records = if ($Kind -ceq 'capability') { @($Store.capabilities) } else { @($Store.transportCredentials) }
1171
+ $field = if ($Kind -ceq 'capability') { 'capabilityId' } else { 'credentialId' }
1172
+ if (@($records | Where-Object { [string]$_.$field -ieq $Id }).Count) { return $true }
1173
+ return Test-RetiredHash $Store $Root $key
1174
+ }
1175
+
1176
+ function New-RetiredNode($Context, [string]$Prefix, [string]$Kind, $Content, [long]$Count) {
1177
+ $node = [ordered]@{ contract = $RetiredIndexContract; prefix = $Prefix; kind = $Kind }
1178
+ if ($Kind -ceq 'leaf') { $node.hashes = @($Content) } else { $node.children = $Content }
1179
+ $bytes = Protect-RetiredJson $node
1180
+ $hash = Get-RetiredDigest $bytes
1181
+ $Context.pages[$hash] = $bytes
1182
+ return [PSCustomObject]@{ hash = $hash; count = $Count }
1183
+ }
1184
+
1185
+ function Merge-RetiredNode($Context, $Reference, [string]$Prefix, [string[]]$Hashes) {
1186
+ if (-not $Hashes.Count) { return $Reference }
1187
+ $node = Read-RetiredNode $Context.root $Reference $Prefix
1188
+ if ($null -eq $node -or $node.kind -ceq 'leaf') {
1189
+ $combined = @(@($node.hashes) + $Hashes | Where-Object { $_ } | Sort-Object -Unique)
1190
+ if ($null -ne $Reference -and $combined.Count -eq $Reference.count) { return $Reference }
1191
+ if ($null -ne $Reference) { $Context.obsolete[$Reference.hash] = $true }
1192
+ if ($combined.Count -le 512) { return New-RetiredNode $Context $Prefix 'leaf' $combined $combined.Count }
1193
+ $Hashes = $combined
1194
+ $node = $null
1195
+ }
1196
+ $children = [ordered]@{}
1197
+ [long]$count = 0
1198
+ $changed = $null -eq $node
1199
+ $buckets = @{}
1200
+ foreach ($hash in $Hashes) {
1201
+ $key = $hash.Substring($Prefix.Length, 1)
1202
+ if (-not $buckets.ContainsKey($key)) { $buckets[$key] = [Collections.Generic.List[string]]::new() }
1203
+ $buckets[$key].Add($hash)
1204
+ }
1205
+ foreach ($digit in '0123456789abcdef'.ToCharArray()) {
1206
+ $key = [string]$digit
1207
+ $old = if ($null -ne $node) { $node.children.$key } else { $null }
1208
+ $subset = if ($buckets.ContainsKey($key)) { $buckets[$key].ToArray() } else { @() }
1209
+ $next = Merge-RetiredNode $Context $old ($Prefix + $key) $subset
1210
+ if ($null -ne $next) { $children[$key] = $next; $count += $next.count }
1211
+ if ($next.hash -cne $old.hash) { $changed = $true }
1212
+ }
1213
+ if (-not $changed) { return $Reference }
1214
+ if ($null -ne $node) { $Context.obsolete[$Reference.hash] = $true }
1215
+ return New-RetiredNode $Context $Prefix 'branch' $children $count
1216
+ }
1217
+
1218
+ function Write-RetiredBytes([string]$Path, [byte[]]$Bytes, [string]$Sid) {
1219
+ Assert-ProtectedStoreNoReparse $Path
1220
+ $stream = [IO.File]::Open($Path, [IO.FileMode]::CreateNew, [IO.FileAccess]::Write, [IO.FileShare]::None)
1221
+ try { $stream.Write($Bytes, 0, $Bytes.Length); $stream.Flush($true) } finally { $stream.Dispose() }
1222
+ Set-ProtectedStoreAcl $Path $Sid $false
1223
+ }
1224
+
1225
+ function Test-RetiredReferenceEqual($Left, $Right) {
1226
+ if ($null -eq $Left -or $null -eq $Right) { return $null -eq $Left -and $null -eq $Right }
1227
+ return $Left.hash -ceq $Right.hash -and $Left.count -eq $Right.count
1228
+ }
1229
+
1230
+ function Complete-RetiredTransaction([string]$Root, $Current, $PreparedJournal = $null) {
1231
+ $directory = Get-RetiredDirectory $Root
1232
+ $path = Join-Path $directory 'pending.dpapi'
1233
+ if (-not (Test-Path -LiteralPath $path)) { return $true }
1234
+ $journal = if ($null -ne $PreparedJournal) { $PreparedJournal } else { Read-RetiredJson $path 1048576 }
1235
+ Assert-ExactFields $journal @('contract', 'before', 'after', 'added', 'obsolete') 'host_retired_journal_invalid'
1236
+ if ($journal.contract -cne ($RetiredIndexContract + '-transaction') -or $null -eq $journal.after -or
1237
+ $journal.added -isnot [Array] -or $journal.obsolete -isnot [Array]) { Fail 'host_retired_journal_invalid' }
1238
+ Assert-RetiredReference $journal.before
1239
+ Assert-RetiredReference $journal.after
1240
+ $seen = @{}
1241
+ foreach ($hash in @($journal.added) + @($journal.obsolete)) {
1242
+ if ($hash -isnot [string] -or $hash -cnotmatch '^[a-f0-9]{64}$' -or $seen.ContainsKey($hash)) { Fail 'host_retired_journal_invalid' }
1243
+ $seen[$hash] = $true
1244
+ }
1245
+ if (Test-RetiredReferenceEqual $Current $journal.after) { $remove = @($journal.obsolete) }
1246
+ elseif (Test-RetiredReferenceEqual $Current $journal.before) { $remove = @($journal.added) }
1247
+ else { Fail 'host_retired_journal_conflict' }
1248
+ # Cleanup is retryable after commit. Never turn a committed replacement into
1249
+ # an apparent failed operation, or delete any page from an unknown revision.
1250
+ try {
1251
+ foreach ($hash in $remove) {
1252
+ $target = Join-Path $directory ($hash + '.dpapi')
1253
+ Assert-ProtectedStoreNoReparse $target
1254
+ if (Test-Path -LiteralPath $target) { Remove-Item -LiteralPath $target -Force }
1255
+ }
1256
+ Remove-Item -LiteralPath $path -Force
1257
+ return $true
1258
+ } catch { return $false }
1259
+ }
1260
+
1261
+ function Read-Store([string]$Path) {
1262
+ if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) {
1263
+ return [PSCustomObject]@{
1264
+ contract = $Contract
1265
+ capabilities = @()
1266
+ transportCredentials = @()
1267
+ transportSessions = @()
1268
+ retiredTransportSessionIds = @()
1269
+ codexNeutralBindings = @()
1270
+ openClawNeutralBindings = @()
1271
+ traeWorkNeutralBindings = @()
1272
+ workBuddyNeutralBindings = @()
1273
+ claudeCodeNeutralBindings = @()
1274
+ cursorNeutralBindings = @()
1275
+ dshNeutralBindings = @()
1276
+ }
1277
+ }
1278
+ $item = Get-Item -LiteralPath $Path -Force
1279
+ if (($item.Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0 -or $item.Length -gt 2097152) {
1280
+ Fail 'host_protected_store_path_invalid'
1281
+ }
1282
+ $credential = Import-Clixml -LiteralPath $Path
1283
+ if ($credential -is [Management.Automation.PSCredential]) {
1284
+ if ($credential.UserName -cne $CredentialName) { Fail 'host_protected_store_binding_invalid' }
1285
+ $plain = $credential.GetNetworkCredential().Password
1286
+ } elseif ([string]$credential.contract -ceq $EnvelopeContract) {
1287
+ Assert-ExactFields $credential @('contract', 'ciphertext') 'host_protected_store_binding_invalid'
1288
+ $plainBytes = $null
1289
+ try {
1290
+ $ciphertext = [Convert]::FromBase64String([string]$credential.ciphertext)
1291
+ $plainBytes = [Security.Cryptography.ProtectedData]::Unprotect(
1292
+ $ciphertext, [Text.Encoding]::UTF8.GetBytes($EnvelopeContract),
1293
+ [Security.Cryptography.DataProtectionScope]::CurrentUser)
1294
+ if ($plainBytes.Length -gt 1048576) { Fail 'host_protected_store_too_large' }
1295
+ $plain = [Text.UTF8Encoding]::new($false, $true).GetString($plainBytes)
1296
+ } catch { Fail 'host_protected_store_decryption_failed' }
1297
+ finally { if ($null -ne $plainBytes) { [Array]::Clear($plainBytes, 0, $plainBytes.Length) } }
1298
+ } else {
1299
+ Fail 'host_protected_store_binding_invalid'
1300
+ }
1301
+ if ([Text.Encoding]::UTF8.GetByteCount($plain) -gt 1048576) { Fail 'host_protected_store_too_large' }
1302
+ try { $store = $plain | ConvertFrom-Json } catch { Fail 'host_protected_store_invalid' }
1303
+ $plain = $null
1304
+ $authorizationHistory = $store.authorizationHistoryContract
1305
+ if ($null -ne $store.PSObject.Properties['authorizationHistoryContract']) {
1306
+ if ($authorizationHistory -cne $AuthorizationHistoryContract) { Fail 'host_authorization_history_invalid' }
1307
+ $store.PSObject.Properties.Remove('authorizationHistoryContract')
1308
+ }
1309
+ $retiredIndex = $store.retiredTransportSessionIndex
1310
+ if ($null -ne $store.PSObject.Properties['retiredTransportSessionIndex']) {
1311
+ if ($null -eq $retiredIndex) { Fail 'host_retired_index_invalid' }
1312
+ Assert-RetiredReference $retiredIndex
1313
+ $store.PSObject.Properties.Remove('retiredTransportSessionIndex')
1314
+ }
1315
+ $fields = @($store.PSObject.Properties.Name | Sort-Object)
1316
+ if ($fields.Count -eq 2) {
1317
+ Assert-ExactFields $store @('capabilities', 'contract') 'host_protected_store_fields_invalid'
1318
+ $store | Add-Member -NotePropertyName transportCredentials -NotePropertyValue @()
1319
+ $store | Add-Member -NotePropertyName transportSessions -NotePropertyValue @()
1320
+ $store | Add-Member -NotePropertyName retiredTransportSessionIds -NotePropertyValue @()
1321
+ $store | Add-Member -NotePropertyName codexNeutralBindings -NotePropertyValue @()
1322
+ $store | Add-Member -NotePropertyName openClawNeutralBindings -NotePropertyValue @()
1323
+ $store | Add-Member -NotePropertyName traeWorkNeutralBindings -NotePropertyValue @()
1324
+ $store | Add-Member -NotePropertyName workBuddyNeutralBindings -NotePropertyValue @()
1325
+ $store | Add-Member -NotePropertyName cursorNeutralBindings -NotePropertyValue @()
1326
+ } elseif ($fields.Count -eq 5) {
1327
+ Assert-ExactFields $store @(
1328
+ 'capabilities', 'contract', 'retiredTransportSessionIds',
1329
+ 'transportCredentials', 'transportSessions'
1330
+ ) 'host_protected_store_fields_invalid'
1331
+ $store | Add-Member -NotePropertyName codexNeutralBindings -NotePropertyValue @()
1332
+ $store | Add-Member -NotePropertyName openClawNeutralBindings -NotePropertyValue @()
1333
+ $store | Add-Member -NotePropertyName traeWorkNeutralBindings -NotePropertyValue @()
1334
+ $store | Add-Member -NotePropertyName workBuddyNeutralBindings -NotePropertyValue @()
1335
+ $store | Add-Member -NotePropertyName cursorNeutralBindings -NotePropertyValue @()
1336
+ } elseif ($fields.Count -eq 6) {
1337
+ Assert-ExactFields $store @(
1338
+ 'capabilities', 'codexNeutralBindings', 'contract', 'retiredTransportSessionIds',
1339
+ 'transportCredentials', 'transportSessions'
1340
+ ) 'host_protected_store_fields_invalid'
1341
+ $store | Add-Member -NotePropertyName openClawNeutralBindings -NotePropertyValue @()
1342
+ $store | Add-Member -NotePropertyName traeWorkNeutralBindings -NotePropertyValue @()
1343
+ $store | Add-Member -NotePropertyName workBuddyNeutralBindings -NotePropertyValue @()
1344
+ $store | Add-Member -NotePropertyName cursorNeutralBindings -NotePropertyValue @()
1345
+ } elseif ($fields.Count -eq 7) {
1346
+ Assert-ExactFields $store @(
1347
+ 'capabilities', 'codexNeutralBindings', 'contract', 'openClawNeutralBindings', 'retiredTransportSessionIds',
1348
+ 'transportCredentials', 'transportSessions'
1349
+ ) 'host_protected_store_fields_invalid'
1350
+ $store | Add-Member -NotePropertyName traeWorkNeutralBindings -NotePropertyValue @()
1351
+ $store | Add-Member -NotePropertyName workBuddyNeutralBindings -NotePropertyValue @()
1352
+ $store | Add-Member -NotePropertyName cursorNeutralBindings -NotePropertyValue @()
1353
+ } elseif ($fields.Count -eq 8) {
1354
+ Assert-ExactFields $store @(
1355
+ 'capabilities', 'codexNeutralBindings', 'contract', 'openClawNeutralBindings', 'retiredTransportSessionIds',
1356
+ 'traeWorkNeutralBindings', 'transportCredentials', 'transportSessions'
1357
+ ) 'host_protected_store_fields_invalid'
1358
+ $store | Add-Member -NotePropertyName workBuddyNeutralBindings -NotePropertyValue @()
1359
+ $store | Add-Member -NotePropertyName cursorNeutralBindings -NotePropertyValue @()
1360
+ } elseif ($fields.Count -eq 9) {
1361
+ Assert-ExactFields $store @(
1362
+ 'capabilities', 'codexNeutralBindings', 'contract', 'openClawNeutralBindings', 'retiredTransportSessionIds',
1363
+ 'traeWorkNeutralBindings', 'transportCredentials', 'transportSessions', 'workBuddyNeutralBindings'
1364
+ ) 'host_protected_store_fields_invalid'
1365
+ $store | Add-Member -NotePropertyName cursorNeutralBindings -NotePropertyValue @()
1366
+ } elseif ($fields.Count -eq 10) {
1367
+ if ($null -ne $store.PSObject.Properties['dshNeutralBindings']) {
1368
+ Assert-ExactFields $store @(
1369
+ 'capabilities', 'codexNeutralBindings', 'contract', 'dshNeutralBindings', 'openClawNeutralBindings',
1370
+ 'retiredTransportSessionIds', 'traeWorkNeutralBindings', 'transportCredentials', 'transportSessions',
1371
+ 'workBuddyNeutralBindings'
1372
+ ) 'host_protected_store_fields_invalid'
1373
+ $store | Add-Member -NotePropertyName cursorNeutralBindings -NotePropertyValue @()
1374
+ } else {
1375
+ Assert-ExactFields $store @(
1376
+ 'capabilities', 'codexNeutralBindings', 'contract', 'cursorNeutralBindings', 'openClawNeutralBindings',
1377
+ 'retiredTransportSessionIds', 'traeWorkNeutralBindings', 'transportCredentials', 'transportSessions',
1378
+ 'workBuddyNeutralBindings'
1379
+ ) 'host_protected_store_fields_invalid'
1380
+ }
1381
+ } elseif ($fields.Count -eq 11) {
1382
+ Assert-ExactFields $store @(
1383
+ 'capabilities', 'codexNeutralBindings', 'contract', 'cursorNeutralBindings', 'dshNeutralBindings',
1384
+ 'openClawNeutralBindings', 'retiredTransportSessionIds', 'traeWorkNeutralBindings',
1385
+ 'transportCredentials', 'transportSessions', 'workBuddyNeutralBindings'
1386
+ ) 'host_protected_store_fields_invalid'
1387
+ } else {
1388
+ Assert-ExactFields $store @(
1389
+ 'capabilities', 'claudeCodeNeutralBindings', 'codexNeutralBindings', 'contract', 'cursorNeutralBindings',
1390
+ 'dshNeutralBindings', 'openClawNeutralBindings', 'retiredTransportSessionIds',
1391
+ 'traeWorkNeutralBindings', 'transportCredentials', 'transportSessions', 'workBuddyNeutralBindings'
1392
+ ) 'host_protected_store_fields_invalid'
1393
+ }
1394
+ if ($null -eq $store.PSObject.Properties['dshNeutralBindings']) {
1395
+ $store | Add-Member -NotePropertyName dshNeutralBindings -NotePropertyValue @()
1396
+ }
1397
+ if ($null -eq $store.PSObject.Properties['claudeCodeNeutralBindings']) {
1398
+ $store | Add-Member -NotePropertyName claudeCodeNeutralBindings -NotePropertyValue @()
1399
+ }
1400
+ if (
1401
+ [string]$store.contract -cne $Contract -or
1402
+ $store.capabilities -isnot [Array] -or
1403
+ $store.codexNeutralBindings -isnot [Array] -or $store.codexNeutralBindings.Count -gt 64 -or
1404
+ $store.openClawNeutralBindings -isnot [Array] -or $store.openClawNeutralBindings.Count -gt 64 -or
1405
+ $store.traeWorkNeutralBindings -isnot [Array] -or $store.traeWorkNeutralBindings.Count -gt 64 -or
1406
+ $store.workBuddyNeutralBindings -isnot [Array] -or $store.workBuddyNeutralBindings.Count -gt 64 -or
1407
+ $store.claudeCodeNeutralBindings -isnot [Array] -or $store.claudeCodeNeutralBindings.Count -gt 64 -or
1408
+ $store.cursorNeutralBindings -isnot [Array] -or $store.cursorNeutralBindings.Count -gt 64 -or
1409
+ $store.dshNeutralBindings -isnot [Array] -or $store.dshNeutralBindings.Count -gt 64 -or
1410
+ $store.transportCredentials -isnot [Array] -or
1411
+ $store.transportSessions -isnot [Array] -or $store.transportSessions.Count -gt 1024 -or
1412
+ $store.retiredTransportSessionIds -isnot [Array]
1413
+ ) {
1414
+ Fail 'host_protected_store_invalid'
1415
+ }
1416
+ $seen = @{}
1417
+ foreach ($record in $store.capabilities) {
1418
+ $null = Assert-Record $record
1419
+ if ($seen.ContainsKey([string]$record.capabilityId)) { Fail 'host_capability_duplicate' }
1420
+ $seen[[string]$record.capabilityId] = $true
1421
+ }
1422
+ foreach ($binding in $store.codexNeutralBindings) {
1423
+ $null = Assert-CodexNeutralBinding $binding $false
1424
+ }
1425
+ foreach ($binding in $store.openClawNeutralBindings) {
1426
+ $null = Assert-OpenClawNeutralBinding $binding $false
1427
+ }
1428
+ foreach ($binding in $store.traeWorkNeutralBindings) {
1429
+ $null = Assert-TraeWorkNeutralBinding $binding $false
1430
+ }
1431
+ foreach ($binding in $store.workBuddyNeutralBindings) {
1432
+ $null = Assert-WorkBuddyNeutralBinding $binding $false
1433
+ }
1434
+ foreach ($binding in $store.claudeCodeNeutralBindings) {
1435
+ $null = Assert-ClaudeCodeNeutralBinding $binding $false
1436
+ }
1437
+ foreach ($binding in $store.cursorNeutralBindings) {
1438
+ $null = Assert-CursorNeutralBinding $binding $false
1439
+ }
1440
+ foreach ($binding in $store.dshNeutralBindings) {
1441
+ $null = Assert-DshNeutralBinding $binding $false
1442
+ }
1443
+ $credentialSeen = @{}
1444
+ foreach ($record in $store.transportCredentials) {
1445
+ $null = Assert-TransportCredential $record
1446
+ if ($credentialSeen.ContainsKey([string]$record.credentialId)) { Fail 'host_transport_credential_duplicate' }
1447
+ $credentialSeen[[string]$record.credentialId] = $record
1448
+ }
1449
+ $retiredSeen = @{}
1450
+ foreach ($retiredId in $store.retiredTransportSessionIds) {
1451
+ if ([string]$retiredId -notmatch $TransportSessionIdPattern) { Fail 'host_transport_session_tombstone_invalid' }
1452
+ if ($retiredSeen.ContainsKey([string]$retiredId)) { Fail 'host_transport_session_tombstone_duplicate' }
1453
+ $retiredSeen[[string]$retiredId] = $true
1454
+ }
1455
+ $retiredNodes = @{}
1456
+ if ($null -ne $retiredIndex) {
1457
+ $store | Add-Member -NotePropertyName retiredTransportSessionIndex -NotePropertyValue $retiredIndex
1458
+ $null = Read-RetiredNode ([IO.Path]::GetDirectoryName($Path)) $retiredIndex '' $retiredNodes
1459
+ }
1460
+ if ($null -ne $authorizationHistory) {
1461
+ if ($null -eq $retiredIndex) { Fail 'host_authorization_history_invalid' }
1462
+ $store | Add-Member -NotePropertyName authorizationHistoryContract -NotePropertyValue $authorizationHistory
1463
+ foreach ($pair in @(@('capability','capabilities','capabilityId'),@('credential','transportCredentials','credentialId'))) {
1464
+ foreach ($record in $store.($pair[1])) {
1465
+ if ($record.state -ceq 'active' -and (Test-RetiredHash $store ([IO.Path]::GetDirectoryName($Path)) (Get-AuthorizationHistoryKey $pair[0] $record.($pair[2])) $retiredNodes)) {
1466
+ Fail 'host_authorization_history_active_conflict'
1467
+ }
1468
+ }
1469
+ }
1470
+ }
1471
+ $sessionSeen = @{}
1472
+ foreach ($record in $store.transportSessions) {
1473
+ $null = Assert-TransportSession $record
1474
+ if ($sessionSeen.ContainsKey([string]$record.sessionId)) { Fail 'host_transport_session_duplicate' }
1475
+ if (Test-RetiredTransportSession $store ([IO.Path]::GetDirectoryName($Path)) ([string]$record.sessionId) $retiredNodes) { Fail 'host_transport_session_retired_conflict' }
1476
+ $sessionSeen[[string]$record.sessionId] = $true
1477
+ $credential = $credentialSeen[[string]$record.credentialId]
1478
+ if (
1479
+ $null -eq $credential -or [string]$credential.state -cne 'active' -or
1480
+ [string]$credential.principalId -cne [string]$record.principalId -or
1481
+ [string]$credential.hostInstallationId -cne [string]$record.hostInstallationId -or
1482
+ [int64]$record.expiresAt -gt [int64]$credential.expiresAt
1483
+ ) { Fail 'host_transport_session_credential_mismatch' }
1484
+ }
1485
+ return $store
1486
+ }
1487
+
1488
+ function Write-Store([string]$Path, $Store, [string]$Root, [string]$Sid) {
1489
+ $retired = $null
1490
+ $preparedJournal = $null
1491
+ $nextIndex = $Store.retiredTransportSessionIndex
1492
+ $hasAuthorizationHistory = $Store.authorizationHistoryContract -ceq $AuthorizationHistoryContract
1493
+ if ([string]$Store.contract -ceq $Contract) {
1494
+ if (-not (Complete-RetiredTransaction $Root $nextIndex)) { Fail 'host_retired_cleanup_pending' }
1495
+ if ([Text.Encoding]::UTF8.GetByteCount((ConvertTo-Json -InputObject @($Store.retiredTransportSessionIds) -Compress)) -gt 1048576) {
1496
+ Fail 'host_protected_store_too_large'
1497
+ }
1498
+ $hashes = @($Store.retiredTransportSessionIds | ForEach-Object {
1499
+ if ($_ -isnot [string] -or $_ -notmatch $TransportSessionIdPattern) { Fail 'host_transport_session_tombstone_invalid' }
1500
+ Get-RetiredDigest ([Text.Encoding]::UTF8.GetBytes($_.ToLowerInvariant()))
1501
+ } | Sort-Object -Unique)
1502
+ $retired = @{ root = $Root; pages = @{}; obsolete = @{} }
1503
+ foreach ($pair in @(@('capability','capabilities','capabilityId'),@('credential','transportCredentials','credentialId'))) {
1504
+ foreach ($record in @($Store.($pair[1]) | Where-Object state -CEQ 'revoked')) {
1505
+ $history = New-AuthorizationHistoryRecord $pair[0] $record
1506
+ $key = Get-AuthorizationHistoryKey $pair[0] $record.($pair[2])
1507
+ if (Test-RetiredHash $Store $Root $key) {
1508
+ $saved = Read-AuthorizationHistory $Store $Root $pair[0] $record.($pair[2])
1509
+ if (($saved | ConvertTo-Json -Compress) -cne ($history.record | ConvertTo-Json -Compress)) { Fail 'host_authorization_history_conflict' }
1510
+ } else {
1511
+ if ($retired.pages.ContainsKey($key) -or (Test-Path -LiteralPath (Join-Path (Get-RetiredDirectory $Root) ($key + '.dpapi')))) { Fail 'host_authorization_history_conflict' }
1512
+ $bytes = Protect-RetiredJson $history
1513
+ $retired.pages[$key] = $bytes
1514
+ $hashes += @($key, (Get-AuthorizationHistoryProof $key (Get-RetiredDigest $bytes)))
1515
+ }
1516
+ $hasAuthorizationHistory = $true
1517
+ }
1518
+ }
1519
+ $nextIndex = Merge-RetiredNode $retired $nextIndex '' $hashes
1520
+ }
1521
+ # All six adapters share this store. A write for another client must retain
1522
+ # Claude's binding, just as it retains the other adapters' bindings.
1523
+ $serializedStore = $Store | Select-Object -Property *
1524
+ if ($null -ne $nextIndex) {
1525
+ $serializedStore.retiredTransportSessionIds = @()
1526
+ $serializedStore | Add-Member -NotePropertyName retiredTransportSessionIndex -NotePropertyValue $nextIndex -Force
1527
+ }
1528
+ if ($hasAuthorizationHistory) {
1529
+ $serializedStore.capabilities = @($Store.capabilities | Where-Object state -CNE 'revoked')
1530
+ $serializedStore.transportCredentials = @($Store.transportCredentials | Where-Object state -CNE 'revoked')
1531
+ # Earlier development writers must reject this state, not reuse a revoked
1532
+ # capability ID after silently ignoring its indexed history.
1533
+ $serializedStore | Add-Member -NotePropertyName authorizationHistoryContract -NotePropertyValue $AuthorizationHistoryContract -Force
1534
+ }
1535
+ $plain = $serializedStore | ConvertTo-Json -Depth 8 -Compress
1536
+ if ([Text.Encoding]::UTF8.GetByteCount($plain) -gt 1048576) { Fail 'host_protected_store_too_large' }
1537
+ if ([string]$Store.contract -ceq $Contract) {
1538
+ # Protect bytes directly: SecureString's 65536-character ceiling must not
1539
+ # turn accumulated anti-replay evidence into a connection failure. Keep
1540
+ # every record. This envelope deliberately fails closed in old writers.
1541
+ $plainBytes = [Text.Encoding]::UTF8.GetBytes($plain)
1542
+ try {
1543
+ $ciphertext = [Security.Cryptography.ProtectedData]::Protect(
1544
+ $plainBytes, [Text.Encoding]::UTF8.GetBytes($EnvelopeContract),
1545
+ [Security.Cryptography.DataProtectionScope]::CurrentUser)
1546
+ $credential = [PSCustomObject]@{
1547
+ contract = $EnvelopeContract
1548
+ ciphertext = [Convert]::ToBase64String($ciphertext)
1549
+ }
1550
+ } finally { [Array]::Clear($plainBytes, 0, $plainBytes.Length) }
1551
+ } else {
1552
+ # Device enrollment uses its separate, small PSCredential contract.
1553
+ $secure = ConvertTo-SecureString -String $plain -AsPlainText -Force
1554
+ $credential = [Management.Automation.PSCredential]::new($CredentialName, $secure)
1555
+ }
1556
+ $temp = Join-Path $Root ('.capabilities.{0}.tmp' -f [Guid]::NewGuid().ToString('N'))
1557
+ try {
1558
+ $credential | Export-Clixml -LiteralPath $temp -Force
1559
+ Set-ProtectedStoreAcl $temp $Sid $false
1560
+ $durable = [IO.File]::Open($temp, [IO.FileMode]::Open, [IO.FileAccess]::ReadWrite, [IO.FileShare]::Read)
1561
+ try { $durable.Flush($true) } finally { $durable.Dispose() }
1562
+ if ($null -ne $retired -and $retired.pages.Count) {
1563
+ $directory = Get-RetiredDirectory $Root
1564
+ if (-not (Test-Path -LiteralPath $directory)) { $null = New-Item -ItemType Directory -Path $directory }
1565
+ Set-ProtectedStoreAcl $directory $Sid $true
1566
+ $preparedJournal = [PSCustomObject]@{ contract = $RetiredIndexContract + '-transaction'; before = $Store.retiredTransportSessionIndex;
1567
+ after = $nextIndex; added = @($retired.pages.Keys); obsolete = @($retired.obsolete.Keys) }
1568
+ # Publish the complete write set before creating pages. A terminated write
1569
+ # can then clean either the unpublished pages or the superseded pages.
1570
+ $journalTemp = Join-Path $directory 'pending.tmp'
1571
+ if (Test-Path -LiteralPath $journalTemp) { Remove-Item -LiteralPath $journalTemp -Force }
1572
+ Write-RetiredBytes $journalTemp (Protect-RetiredJson $preparedJournal) $Sid
1573
+ Move-Item -LiteralPath $journalTemp -Destination (Join-Path $directory 'pending.dpapi')
1574
+ foreach ($hash in $retired.pages.Keys) { Write-RetiredBytes (Join-Path $directory ($hash + '.dpapi')) $retired.pages[$hash] $Sid }
1575
+ }
1576
+ for ($attempt = 0; ; $attempt += 1) {
1577
+ try {
1578
+ Move-Item -LiteralPath $temp -Destination $Path -Force
1579
+ break
1580
+ } catch [System.IO.IOException] {
1581
+ if ($attempt -ge 20) { throw }
1582
+ Start-Sleep -Milliseconds (5 * ($attempt + 1))
1583
+ } catch [System.UnauthorizedAccessException] {
1584
+ if ($attempt -ge 20) { throw }
1585
+ Start-Sleep -Milliseconds (5 * ($attempt + 1))
1586
+ }
1587
+ }
1588
+ if ($null -ne $nextIndex) {
1589
+ $Store.retiredTransportSessionIds = @()
1590
+ $Store | Add-Member -NotePropertyName retiredTransportSessionIndex -NotePropertyValue $nextIndex -Force
1591
+ if ($hasAuthorizationHistory) {
1592
+ $Store.capabilities = $serializedStore.capabilities
1593
+ $Store.transportCredentials = $serializedStore.transportCredentials
1594
+ $Store | Add-Member -NotePropertyName authorizationHistoryContract -NotePropertyValue $AuthorizationHistoryContract -Force
1595
+ }
1596
+ # The just-published journal is already in memory. A transient read or
1597
+ # cleanup I/O failure must not misreport the committed state as failed.
1598
+ try { $null = Complete-RetiredTransaction $Root $nextIndex $preparedJournal } catch { }
1599
+ }
1600
+ } finally {
1601
+ if (Test-Path -LiteralPath $temp) { Remove-Item -LiteralPath $temp -Force }
1602
+ $plain = $null
1603
+ $secure = $null
1604
+ $credential = $null
1605
+ }
1606
+ }
1607
+
1608
+ function Write-Result($Value) {
1609
+ if (-not $ResultPipeName -or $ResultPipeName -notmatch '^HelloLynkHostProtected-[A-Za-z0-9_-]{16,96}$') {
1610
+ Fail 'host_protected_result_pipe_invalid'
1611
+ }
1612
+ $json = $Value | ConvertTo-Json -Depth 8 -Compress
1613
+ $pipeOptions = [IO.Pipes.PipeOptions]::Asynchronous -bor [IO.Pipes.PipeOptions]::CurrentUserOnly
1614
+ $pipe = [IO.Pipes.NamedPipeServerStream]::new(
1615
+ $ResultPipeName,
1616
+ [IO.Pipes.PipeDirection]::Out,
1617
+ 1,
1618
+ [IO.Pipes.PipeTransmissionMode]::Byte,
1619
+ $pipeOptions
1620
+ )
1621
+ try {
1622
+ $pipe.WaitForConnection()
1623
+ $writer = [IO.StreamWriter]::new($pipe, [Text.UTF8Encoding]::new($false), 1024, $true)
1624
+ try { $writer.Write($json); $writer.Flush() } finally { $writer.Dispose() }
1625
+ } finally {
1626
+ $pipe.Dispose()
1627
+ $json = $null
1628
+ }
1629
+ }
1630
+
1631
+ try {
1632
+ if (-not $StoreRoot) { Fail 'host_protected_store_root_unavailable' }
1633
+ $verificationCommands = @(
1634
+ 'LoadDeviceEnrollment',
1635
+ 'Status',
1636
+ 'VerifyCodexNeutralBinding', 'VerifyOpenClawNeutralBinding',
1637
+ 'VerifyTraeWorkNeutralBinding', 'VerifyWorkBuddyNeutralBinding', 'VerifyClaudeCodeNeutralBinding',
1638
+ 'VerifyCursorNeutralBinding', 'VerifyDshNeutralBinding',
1639
+ 'VerifyTransport', 'VerifyPath', 'VerifyTransportCredentialLifetimeMigration',
1640
+ 'ResolveCodexNeutralBinding', 'ResolveOpenClawNeutralBinding', 'ResolveTraeWorkNeutralBinding',
1641
+ 'ResolveWorkBuddyNeutralBinding', 'ResolveClaudeCodeNeutralBinding', 'ResolveCursorNeutralBinding', 'ResolveDshNeutralBinding'
1642
+ )
1643
+ $readOnlyBindingResolution = $VerifyOnly -and [string]$Command -match '^Resolve(?:Codex|OpenClaw|TraeWork|WorkBuddy|ClaudeCode|Cursor|Dsh)NeutralBinding$'
1644
+ $verificationMode = [string]$Command -match '^Verify' -or
1645
+ ($VerifyOnly -and [string]$Command -in @('Status', 'LoadDeviceEnrollment')) -or $readOnlyBindingResolution
1646
+ if ($VerifyOnly -and -not ($verificationCommands -contains [string]$Command)) {
1647
+ Fail 'host_protected_store_verify_only_command_invalid'
1648
+ }
1649
+ if (
1650
+ $StoreRoot -notmatch '^[A-Za-z]:[\\/]' -or
1651
+ $StoreRoot.StartsWith('\\') -or
1652
+ $StoreRoot.Contains([char]0)
1653
+ ) { Fail 'host_protected_store_root_invalid' }
1654
+ $root = [IO.Path]::GetFullPath($StoreRoot)
1655
+ if ([string]$Command -match 'ClaudeCodeNeutralBinding$' -and -not (Test-ClaudeCodeStoreRoot $root)) {
1656
+ Fail 'neutral_claude_code_store_root_invalid'
1657
+ }
1658
+ if ($root -notmatch '^[A-Za-z]:\\' -or $root.Length -lt 4) {
1659
+ Fail 'host_protected_store_root_invalid'
1660
+ }
1661
+ try { $drive = [IO.DriveInfo]::new([IO.Path]::GetPathRoot($root)) } catch {
1662
+ Fail 'host_protected_store_root_invalid'
1663
+ }
1664
+ if ($drive.DriveType -ne [IO.DriveType]::Fixed) { Fail 'host_protected_store_root_invalid' }
1665
+ Assert-ProtectedStoreNoReparse $root
1666
+ if ($verificationMode) {
1667
+ if (-not (Test-Path -LiteralPath $root -PathType Container)) {
1668
+ Fail 'host_protected_store_root_invalid'
1669
+ }
1670
+ } else {
1671
+ $null = New-Item -ItemType Directory -Path $root -Force
1672
+ }
1673
+ Assert-ProtectedStoreNoReparse $root
1674
+ $root = Get-ProtectedStoreCanonicalLocalRoot $root
1675
+ Assert-ProtectedStoreNoReparse $root
1676
+ $rootItem = Get-Item -LiteralPath $root -Force
1677
+ if (-not $rootItem.PSIsContainer -or ($rootItem.Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0) {
1678
+ Fail 'host_protected_store_root_invalid'
1679
+ }
1680
+ $sid = [Security.Principal.WindowsIdentity]::GetCurrent().User.Value
1681
+ $path = Join-Path $root 'capabilities-v1.dpapi.clixml'
1682
+ if ($Command -in @('LoadDeviceEnrollment', 'SaveDeviceEnrollment')) {
1683
+ # Separate from the capability format so existing installed readers can
1684
+ # continue opening their store during the explicit enrollment transition.
1685
+ $path = Join-Path $root 'device-enrollment-v1.dpapi.clixml'
1686
+ }
1687
+ $rootPrefix = $root.TrimEnd([IO.Path]::DirectorySeparatorChar) + [IO.Path]::DirectorySeparatorChar
1688
+ if (-not [IO.Path]::GetFullPath($path).StartsWith($rootPrefix, [StringComparison]::OrdinalIgnoreCase)) {
1689
+ Fail 'host_protected_store_path_invalid'
1690
+ }
1691
+
1692
+ # The directory is not atomically replaced, so its idempotent ACL hardening
1693
+ # can happen before the per-root data mutex. The main file is hardened again
1694
+ # under the mutex immediately before it is read.
1695
+ if ($verificationMode) {
1696
+ Assert-ProtectedStoreAcl $root $sid $true
1697
+ } else {
1698
+ Repair-ProtectedStoreAcl $root $sid $true
1699
+ }
1700
+
1701
+ if ([string]$Command -eq 'VerifyPath') {
1702
+ if (-not $PathToVerify -or -not [IO.Path]::IsPathFullyQualified($PathToVerify)) {
1703
+ Fail 'host_protected_store_path_invalid'
1704
+ }
1705
+ $verifiedPath = [IO.Path]::GetFullPath($PathToVerify)
1706
+ if (-not [string]::Equals($verifiedPath, $root, [StringComparison]::OrdinalIgnoreCase) -and
1707
+ -not $verifiedPath.StartsWith($rootPrefix, [StringComparison]::OrdinalIgnoreCase)) {
1708
+ Fail 'host_protected_store_path_invalid'
1709
+ }
1710
+ Assert-ProtectedStoreNoReparse $verifiedPath
1711
+ if ($VerifyDirectory) {
1712
+ if (-not (Test-Path -LiteralPath $verifiedPath -PathType Container)) {
1713
+ Fail 'host_protected_store_path_invalid'
1714
+ }
1715
+ } elseif (-not (Test-Path -LiteralPath $verifiedPath -PathType Leaf)) {
1716
+ Fail 'host_protected_store_path_invalid'
1717
+ }
1718
+ $verifiedItem = Get-Item -LiteralPath $verifiedPath -Force
1719
+ if (($verifiedItem.Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0) {
1720
+ Fail 'host_protected_store_path_invalid'
1721
+ }
1722
+ Assert-ProtectedStoreAcl $verifiedPath $sid ([bool]$VerifyDirectory)
1723
+ Write-Result ([PSCustomObject]@{ verified = $true })
1724
+ exit 0
1725
+ }
1726
+
1727
+ $mutexScope = ('{0}`0{1}' -f $sid, $root.ToUpperInvariant())
1728
+ $scopeHash = [Convert]::ToHexString([Security.Cryptography.SHA256]::HashData(
1729
+ [Text.Encoding]::UTF8.GetBytes($mutexScope)
1730
+ )).Substring(0, 32)
1731
+ $mutex = [Threading.Mutex]::new($false, "Local\HelloLynkNeutralHostProtected-$scopeHash")
1732
+ $acquired = $false
1733
+ try {
1734
+ try { $acquired = $mutex.WaitOne(10000) } catch [Threading.AbandonedMutexException] { $acquired = $true }
1735
+ if (-not $acquired) { Fail 'host_protected_store_lock_timeout' }
1736
+ if (Test-Path -LiteralPath $path -PathType Leaf) {
1737
+ $existingItem = Get-Item -LiteralPath $path -Force
1738
+ if (($existingItem.Attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0) {
1739
+ Fail 'host_protected_store_path_invalid'
1740
+ }
1741
+ if ($verificationMode) { Assert-ProtectedStoreAcl $path $sid $false }
1742
+ else { Repair-ProtectedStoreAcl $path $sid $false }
1743
+ } elseif ($verificationMode) {
1744
+ Fail 'host_protected_store_path_invalid'
1745
+ }
1746
+ if ([string]$Command -eq 'Status') {
1747
+ Write-Result ([PSCustomObject]@{ contract = $Contract; protected = $true })
1748
+ exit 0
1749
+ }
1750
+ if ($Command -in @('LoadDeviceEnrollment', 'SaveDeviceEnrollment')) {
1751
+ $enrollment = Read-DeviceEnrollment $path $sid
1752
+ if ($Command -eq 'SaveDeviceEnrollment') {
1753
+ $plainInput = [Console]::In.ReadToEnd()
1754
+ if ([Text.Encoding]::UTF8.GetByteCount($plainInput) -gt 65536) { Fail 'device_enrollment_store_invalid' }
1755
+ try { $inputRecord = $plainInput | ConvertFrom-Json } catch { Fail 'device_enrollment_store_invalid' }
1756
+ $plainInput = $null
1757
+ Assert-ExactFields $inputRecord @('expectedRevision', 'value') 'device_enrollment_store_invalid'
1758
+ if ($inputRecord.expectedRevision -isnot [long] -and $inputRecord.expectedRevision -isnot [int]) {
1759
+ Fail 'device_enrollment_store_invalid'
1760
+ }
1761
+ if ($inputRecord.expectedRevision -cne $enrollment.revision) { Fail 'device_enrollment_revision_conflict' }
1762
+ if ($null -eq $inputRecord.value -or $inputRecord.value -isnot [PSCustomObject]) { Fail 'device_enrollment_store_invalid' }
1763
+ $enrollment.revision = [long]$enrollment.revision + 1
1764
+ $enrollment.value = $inputRecord.value
1765
+ Write-Store $path $enrollment $root $sid
1766
+ }
1767
+ Write-Result $enrollment
1768
+ exit 0
1769
+ }
1770
+ $store = Read-Store $path
1771
+ $inputRecord = $null
1772
+ if (@(
1773
+ 'DisconnectLocalClient', 'Issue', 'Rotate', 'SaveCodexNeutralBinding', 'RemoveCodexNeutralBinding', 'ReplaceCodexNeutralBinding', 'MigrateCodexNeutralBinding', 'SaveTransportCredential',
1774
+ 'SaveOpenClawNeutralBinding', 'RemoveOpenClawNeutralBinding', 'MigrateOpenClawNeutralBinding',
1775
+ 'SaveTraeWorkNeutralBinding', 'RemoveTraeWorkNeutralBinding', 'MigrateTraeWorkNeutralBinding',
1776
+ 'SaveWorkBuddyNeutralBinding', 'RemoveWorkBuddyNeutralBinding', 'MigrateWorkBuddyNeutralBinding',
1777
+ 'SaveClaudeCodeNeutralBinding', 'RemoveClaudeCodeNeutralBinding', 'MigrateClaudeCodeNeutralBinding',
1778
+ 'SaveCursorNeutralBinding', 'RemoveCursorNeutralBinding', 'MigrateCursorNeutralBinding',
1779
+ 'SaveDshNeutralBinding', 'RemoveDshNeutralBinding', 'MigrateDshNeutralBinding', 'RestoreDshNeutralBinding',
1780
+ 'SaveTransportSession', 'ReplaceTransportSession', 'SaveEnrollmentTransportCredential', 'MigrateTransportCredentialLifetime'
1781
+ ) -contains $Command) {
1782
+ $plainInput = [Console]::In.ReadToEnd()
1783
+ if ([Text.Encoding]::UTF8.GetByteCount($plainInput) -gt 65536) { Fail 'host_capability_input_too_large' }
1784
+ try { $inputRecord = $plainInput | ConvertFrom-Json } catch { Fail 'host_capability_input_invalid' }
1785
+ $plainInput = $null
1786
+ if ($Command -eq 'DisconnectLocalClient') {
1787
+ Assert-ExactFields $inputRecord @('client') 'workbench_disconnect_input_invalid'
1788
+ } elseif (@('Issue', 'Rotate') -contains $Command) {
1789
+ $null = Assert-Record $inputRecord
1790
+ if ([string]$inputRecord.state -ne 'active') { Fail 'host_capability_input_invalid' }
1791
+ } elseif ([string]$Command -eq 'SaveCodexNeutralBinding') {
1792
+ $null = Assert-CodexNeutralBinding $inputRecord $true
1793
+ } elseif ([string]$Command -eq 'RemoveCodexNeutralBinding') {
1794
+ $null = Assert-CodexNeutralBinding $inputRecord $true
1795
+ } elseif ([string]$Command -eq 'ReplaceCodexNeutralBinding') {
1796
+ Assert-ExactFields $inputRecord @('previousBinding', 'replacementBinding') 'neutral_codex_binding_invalid'
1797
+ $null = Assert-CodexNeutralBinding $inputRecord.previousBinding $true
1798
+ $null = Assert-CodexNeutralBinding $inputRecord.replacementBinding $true
1799
+ } elseif ([string]$Command -eq 'MigrateCodexNeutralBinding') {
1800
+ Assert-ExactFields $inputRecord @(
1801
+ 'previousBinding', 'replacementBinding', 'replacementRecord'
1802
+ ) 'neutral_codex_binding_invalid'
1803
+ $null = Assert-CodexNeutralBinding $inputRecord.previousBinding $true
1804
+ $null = Assert-CodexNeutralBinding $inputRecord.replacementBinding $true
1805
+ $null = Assert-Record $inputRecord.replacementRecord
1806
+ if ([string]$inputRecord.replacementRecord.state -ne 'active') { Fail 'host_capability_input_invalid' }
1807
+ } elseif ([string]$Command -eq 'SaveOpenClawNeutralBinding') {
1808
+ $null = Assert-OpenClawNeutralBinding $inputRecord $true $true
1809
+ } elseif ([string]$Command -eq 'RemoveOpenClawNeutralBinding') {
1810
+ $null = Assert-OpenClawNeutralBinding $inputRecord $true
1811
+ } elseif ([string]$Command -eq 'MigrateOpenClawNeutralBinding') {
1812
+ Assert-ExactFields $inputRecord @(
1813
+ 'previousBinding', 'replacementBinding', 'replacementRecord'
1814
+ ) 'neutral_openclaw_binding_invalid'
1815
+ $null = Assert-OpenClawNeutralBinding $inputRecord.previousBinding $true $false
1816
+ $null = Assert-OpenClawNeutralBinding $inputRecord.replacementBinding $true $true
1817
+ $null = Assert-Record $inputRecord.replacementRecord
1818
+ if ([string]$inputRecord.replacementRecord.state -ne 'active') { Fail 'host_capability_input_invalid' }
1819
+ } elseif ([string]$Command -eq 'SaveTraeWorkNeutralBinding') {
1820
+ $null = Assert-TraeWorkNeutralBinding $inputRecord $true
1821
+ } elseif ([string]$Command -eq 'RemoveTraeWorkNeutralBinding') {
1822
+ $null = Assert-TraeWorkNeutralBinding $inputRecord $true
1823
+ } elseif ([string]$Command -eq 'MigrateTraeWorkNeutralBinding') {
1824
+ Assert-ExactFields $inputRecord @(
1825
+ 'previousBinding', 'replacementBinding', 'replacementRecord'
1826
+ ) 'neutral_trae_work_binding_invalid'
1827
+ $null = Assert-TraeWorkNeutralBinding $inputRecord.previousBinding $true
1828
+ $null = Assert-TraeWorkNeutralBinding $inputRecord.replacementBinding $true
1829
+ $null = Assert-Record $inputRecord.replacementRecord
1830
+ if ([string]$inputRecord.replacementRecord.state -ne 'active') { Fail 'host_capability_input_invalid' }
1831
+ } elseif ([string]$Command -eq 'SaveWorkBuddyNeutralBinding') {
1832
+ $null = Assert-WorkBuddyNeutralBinding $inputRecord $true
1833
+ } elseif ([string]$Command -eq 'RemoveWorkBuddyNeutralBinding') {
1834
+ $null = Assert-WorkBuddyNeutralBinding $inputRecord $true
1835
+ } elseif ([string]$Command -eq 'MigrateWorkBuddyNeutralBinding') {
1836
+ Assert-ExactFields $inputRecord @(
1837
+ 'previousBinding', 'replacementBinding', 'replacementRecord'
1838
+ ) 'neutral_workbuddy_binding_invalid'
1839
+ $null = Assert-WorkBuddyNeutralBinding $inputRecord.previousBinding $true
1840
+ $null = Assert-WorkBuddyNeutralBinding $inputRecord.replacementBinding $true
1841
+ $null = Assert-Record $inputRecord.replacementRecord
1842
+ if ([string]$inputRecord.replacementRecord.state -ne 'active') { Fail 'host_capability_input_invalid' }
1843
+ } elseif ([string]$Command -eq 'SaveClaudeCodeNeutralBinding') {
1844
+ $null = Assert-ClaudeCodeNeutralBinding $inputRecord $true
1845
+ } elseif ([string]$Command -eq 'RemoveClaudeCodeNeutralBinding') {
1846
+ $null = Assert-ClaudeCodeNeutralBinding $inputRecord $true
1847
+ } elseif ([string]$Command -eq 'MigrateClaudeCodeNeutralBinding') {
1848
+ Assert-ExactFields $inputRecord @(
1849
+ 'previousBinding', 'replacementBinding', 'replacementRecord'
1850
+ ) 'neutral_claude_code_binding_invalid'
1851
+ $null = Assert-ClaudeCodeNeutralBinding $inputRecord.previousBinding $true
1852
+ $null = Assert-ClaudeCodeNeutralBinding $inputRecord.replacementBinding $true
1853
+ $null = Assert-Record $inputRecord.replacementRecord
1854
+ if ([string]$inputRecord.replacementRecord.state -ne 'active') { Fail 'host_capability_input_invalid' }
1855
+ } elseif ([string]$Command -eq 'SaveCursorNeutralBinding') {
1856
+ $null = Assert-CursorNeutralBinding $inputRecord $true $true
1857
+ } elseif ([string]$Command -eq 'RemoveCursorNeutralBinding') {
1858
+ $null = Assert-CursorNeutralBinding $inputRecord $true
1859
+ } elseif ([string]$Command -eq 'MigrateCursorNeutralBinding') {
1860
+ Assert-ExactFields $inputRecord @(
1861
+ 'previousBinding', 'replacementBinding', 'replacementRecord'
1862
+ ) 'neutral_cursor_binding_invalid'
1863
+ $null = Assert-CursorNeutralBinding $inputRecord.previousBinding $true
1864
+ $null = Assert-CursorNeutralBinding $inputRecord.replacementBinding $true $true
1865
+ $null = Assert-Record $inputRecord.replacementRecord
1866
+ if ([string]$inputRecord.replacementRecord.state -ne 'active') { Fail 'host_capability_input_invalid' }
1867
+ } elseif ([string]$Command -eq 'SaveDshNeutralBinding') {
1868
+ $null = Assert-DshNeutralBinding $inputRecord $true $true
1869
+ } elseif ([string]$Command -eq 'RemoveDshNeutralBinding') {
1870
+ $null = Assert-DshNeutralBinding $inputRecord $true
1871
+ } elseif ([string]$Command -in @('MigrateDshNeutralBinding', 'RestoreDshNeutralBinding')) {
1872
+ Assert-ExactFields $inputRecord @('previousBinding', 'replacementBinding', 'replacementRecord') 'neutral_dsh_binding_invalid'
1873
+ $null = Assert-DshNeutralBinding $inputRecord.previousBinding $true
1874
+ $restoringPrevious = [string]$Command -eq 'RestoreDshNeutralBinding'
1875
+ $null = Assert-DshNeutralBinding $inputRecord.replacementBinding $true (-not $restoringPrevious)
1876
+ if ($restoringPrevious -and -not (Test-ExactScopeSequence $inputRecord.replacementBinding.scopes $DshNeutralPreviousScopes)) {
1877
+ Fail 'neutral_dsh_capability_binding_mismatch'
1878
+ }
1879
+ $null = Assert-Record $inputRecord.replacementRecord
1880
+ if ([string]$inputRecord.replacementRecord.state -ne 'active') { Fail 'host_capability_input_invalid' }
1881
+ } elseif ($Command -eq 'MigrateTransportCredentialLifetime') {
1882
+ Assert-ExactFields $inputRecord @('previous', 'proof') 'host_transport_migration_input_invalid'
1883
+ $null = Assert-TransportCredential $inputRecord.previous
1884
+ Assert-ExactFields $inputRecord.proof @('credentialId', 'principalId', 'hostId', 'credentialCreatedAt', 'credentialExpiresAt') 'host_transport_migration_proof_invalid'
1885
+ } elseif ($Command -in @('SaveTransportCredential', 'SaveEnrollmentTransportCredential')) {
1886
+ $null = Assert-TransportCredential $inputRecord
1887
+ if ([string]$inputRecord.state -ne 'active') { Fail 'host_transport_credential_input_invalid' }
1888
+ } else {
1889
+ $null = Assert-TransportSession $inputRecord
1890
+ }
1891
+ }
1892
+
1893
+ $bindingVerification = [string]$Command -match '^Verify(?:Codex|OpenClaw|TraeWork|WorkBuddy|ClaudeCode|Cursor|Dsh)NeutralBinding$'
1894
+ $operationCommand = switch ([string]$Command) {
1895
+ 'VerifyCodexNeutralBinding' { 'ResolveCodexNeutralBinding' }
1896
+ 'VerifyOpenClawNeutralBinding' { 'ResolveOpenClawNeutralBinding' }
1897
+ 'VerifyTraeWorkNeutralBinding' { 'ResolveTraeWorkNeutralBinding' }
1898
+ 'VerifyWorkBuddyNeutralBinding' { 'ResolveWorkBuddyNeutralBinding' }
1899
+ 'VerifyClaudeCodeNeutralBinding' { 'ResolveClaudeCodeNeutralBinding' }
1900
+ 'VerifyCursorNeutralBinding' { 'ResolveCursorNeutralBinding' }
1901
+ 'VerifyDshNeutralBinding' { 'ResolveDshNeutralBinding' }
1902
+ 'RestoreDshNeutralBinding' { 'MigrateDshNeutralBinding' }
1903
+ default { [string]$Command }
1904
+ }
1905
+ switch ($operationCommand) {
1906
+ 'DisconnectLocalClient' {
1907
+ Assert-ExactFields $inputRecord @('client') 'workbench_disconnect_input_invalid'
1908
+ $fields = @{
1909
+ 'codex' = 'codexNeutralBindings'; 'openclaw' = 'openClawNeutralBindings'
1910
+ 'trae-work' = 'traeWorkNeutralBindings'; 'workbuddy' = 'workBuddyNeutralBindings'
1911
+ 'claude-code' = 'claudeCodeNeutralBindings'
1912
+ 'cursor' = 'cursorNeutralBindings'; 'dsh' = 'dshNeutralBindings'
1913
+ }
1914
+ $client = [string]$inputRecord.client
1915
+ if (-not $fields.ContainsKey($client)) { Fail 'workbench_account_client_invalid' }
1916
+ if ($client -ceq 'claude-code' -and -not (Test-ClaudeCodeStoreRoot $root)) {
1917
+ Fail 'neutral_claude_code_store_root_invalid'
1918
+ }
1919
+ foreach ($record in @($store.capabilities)) {
1920
+ if ([string]$record.adapterId -ceq $client -and [string]$record.state -ceq 'active') {
1921
+ $record.state = 'revoked'
1922
+ $record.revokedAt = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds()
1923
+ }
1924
+ }
1925
+ $field = $fields[$client]
1926
+ $store.$field = @()
1927
+ Write-Store $path $store $root $sid
1928
+ Write-Result ([PSCustomObject]@{ disconnected = $true; client = $client })
1929
+ }
1930
+ 'Issue' {
1931
+ if (Test-AuthorizationIdUsed $store $root 'capability' $inputRecord.capabilityId) {
1932
+ Fail 'host_capability_id_conflict'
1933
+ }
1934
+ $store.capabilities = @($store.capabilities) + @($inputRecord)
1935
+ Write-Store $path $store $root $sid
1936
+ Write-Result ([PSCustomObject]@{ record = $inputRecord })
1937
+ }
1938
+ 'Load' {
1939
+ if ($CapabilityId -notmatch $CapabilityIdPattern) { Fail 'host_capability_id_invalid' }
1940
+ $records = @($store.capabilities | Where-Object capabilityId -CEQ $CapabilityId)
1941
+ if ($records.Count -ne 1 -or [string]$records[0].state -ne 'active') { Fail 'host_capability_inactive' }
1942
+ Write-Result ([PSCustomObject]@{ record = $records[0] })
1943
+ }
1944
+ 'Revoke' {
1945
+ if ($CapabilityId -notmatch $CapabilityIdPattern) { Fail 'host_capability_id_invalid' }
1946
+ $records = @($store.capabilities | Where-Object capabilityId -CEQ $CapabilityId)
1947
+ if ($records.Count -eq 0) {
1948
+ $archived = Read-AuthorizationHistory $store $root 'capability' $CapabilityId
1949
+ if ($null -ne $archived) { $records = @($archived) }
1950
+ }
1951
+ if ($records.Count -ne 1) { Fail 'host_capability_missing' }
1952
+ if ([string]$records[0].state -eq 'active') {
1953
+ $records[0].state = 'revoked'
1954
+ $records[0].revokedAt = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds()
1955
+ Write-Store $path $store $root $sid
1956
+ }
1957
+ Write-Result ([PSCustomObject]@{ capabilityId = $CapabilityId; state = 'revoked'; revokedAt = $records[0].revokedAt })
1958
+ }
1959
+ 'Rotate' {
1960
+ if ($CapabilityId -notmatch $CapabilityIdPattern) { Fail 'host_capability_id_invalid' }
1961
+ $old = @($store.capabilities | Where-Object capabilityId -CEQ $CapabilityId)
1962
+ if ($old.Count -ne 1 -or [string]$old[0].state -ne 'active') { Fail 'host_capability_inactive' }
1963
+ if (Test-AuthorizationIdUsed $store $root 'capability' $inputRecord.capabilityId) {
1964
+ Fail 'host_capability_id_conflict'
1965
+ }
1966
+ $old[0].state = 'revoked'
1967
+ $old[0].revokedAt = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds()
1968
+ $store.capabilities = @($store.capabilities) + @($inputRecord)
1969
+ Write-Store $path $store $root $sid
1970
+ Write-Result ([PSCustomObject]@{ record = $inputRecord; replacedCapabilityId = $CapabilityId })
1971
+ }
1972
+ 'Status' {
1973
+ Fail 'host_protected_store_status_invalid'
1974
+ }
1975
+ 'SaveCodexNeutralBinding' {
1976
+ $existing = @($store.codexNeutralBindings | Where-Object adapterId -CEQ 'codex')
1977
+ if ($existing.Count -gt 1) { Fail 'neutral_codex_binding_invalid' }
1978
+ if ($existing.Count -eq 1) {
1979
+ if (-not (Test-CodexNeutralBindingEqual $existing[0] $inputRecord)) {
1980
+ Fail 'neutral_codex_binding_invalid'
1981
+ }
1982
+ } else {
1983
+ $store.codexNeutralBindings = @($store.codexNeutralBindings) + @($inputRecord)
1984
+ Write-Store $path $store $root $sid
1985
+ }
1986
+ Write-Result ([PSCustomObject]@{ binding = $inputRecord })
1987
+ }
1988
+ 'ResolveCodexNeutralBinding' {
1989
+ $bindings = @($store.codexNeutralBindings | Where-Object adapterId -CEQ 'codex')
1990
+ if ($bindings.Count -eq 0) { Fail 'neutral_codex_binding_configuration_required' }
1991
+ if ($bindings.Count -gt 1) { Fail 'neutral_codex_binding_invalid' }
1992
+ $binding = Assert-CodexNeutralBinding $bindings[0] $true
1993
+ $activeCodex = @($store.capabilities | Where-Object {
1994
+ [string]$_.state -ceq 'active' -and [string]$_.adapterId -ceq 'codex'
1995
+ })
1996
+ $matching = @($activeCodex | Where-Object { Test-CodexCapabilityMatch $_ $binding })
1997
+ if ($matching.Count -eq 0) {
1998
+ if ($activeCodex.Count -eq 0) { Fail 'neutral_codex_capability_missing' }
1999
+ Fail 'neutral_codex_capability_binding_mismatch'
2000
+ }
2001
+ if ($matching.Count -gt 1) { Fail 'neutral_codex_capability_ambiguous' }
2002
+ $record = if ($bindingVerification) { Get-PublicCapabilityRecord $matching[0] } else { $matching[0] }
2003
+ Write-Result ([PSCustomObject]@{ binding = $binding; record = $record })
2004
+ }
2005
+ 'RemoveCodexNeutralBinding' {
2006
+ $matching = @($store.codexNeutralBindings | Where-Object {
2007
+ Test-CodexNeutralBindingEqual $_ $inputRecord
2008
+ })
2009
+ if ($matching.Count -eq 0) { Fail 'neutral_codex_binding_invalid' }
2010
+ if ($matching.Count -gt 1) { Fail 'neutral_codex_binding_invalid' }
2011
+ $store.codexNeutralBindings = @($store.codexNeutralBindings | Where-Object {
2012
+ -not (Test-CodexNeutralBindingEqual $_ $matching[0])
2013
+ })
2014
+ Write-Store $path $store $root $sid
2015
+ Write-Result ([PSCustomObject]@{ removed = $true })
2016
+ }
2017
+ 'ReplaceCodexNeutralBinding' {
2018
+ $previous = $inputRecord.previousBinding
2019
+ $replacement = $inputRecord.replacementBinding
2020
+ if (-not (Test-NeutralBindingOwnerEqual $previous $replacement) -or
2021
+ -not (Test-ExactScopeSequence $previous.scopes $replacement.scopes)) {
2022
+ Fail 'neutral_codex_capability_binding_mismatch'
2023
+ }
2024
+ $bindings = @($store.codexNeutralBindings | Where-Object adapterId -CEQ 'codex')
2025
+ if ($bindings.Count -ne 1 -or
2026
+ -not (Test-CodexNeutralBindingEqual $bindings[0] $previous)) {
2027
+ Fail 'neutral_codex_binding_invalid'
2028
+ }
2029
+ $matching = @($store.capabilities | Where-Object {
2030
+ [string]$_.state -ceq 'active' -and (Test-CodexCapabilityMatch $_ $replacement)
2031
+ })
2032
+ if ($matching.Count -ne 1) { Fail 'neutral_codex_capability_ambiguous' }
2033
+ $store.codexNeutralBindings = @($replacement)
2034
+ Write-Store $path $store $root $sid
2035
+ Write-Result ([PSCustomObject]@{ binding = $replacement })
2036
+ }
2037
+ 'MigrateCodexNeutralBinding' {
2038
+ if ($CapabilityId -notmatch $CapabilityIdPattern) { Fail 'host_capability_id_invalid' }
2039
+ $previous = $inputRecord.previousBinding
2040
+ $replacement = $inputRecord.replacementBinding
2041
+ if (-not (Test-NeutralBindingOwnerEqual $previous $replacement)) {
2042
+ Fail 'neutral_codex_capability_binding_mismatch'
2043
+ }
2044
+ $bindings = @($store.codexNeutralBindings | Where-Object adapterId -CEQ 'codex')
2045
+ if ($bindings.Count -ne 1 -or
2046
+ -not (Test-CodexNeutralBindingEqual $bindings[0] $previous)) {
2047
+ Fail 'neutral_codex_binding_invalid'
2048
+ }
2049
+ $old = @($store.capabilities | Where-Object capabilityId -CEQ $CapabilityId)
2050
+ if ($old.Count -ne 1 -or [string]$old[0].state -cne 'active' -or
2051
+ -not (Test-CodexCapabilityMatch $old[0] $previous)) {
2052
+ Fail 'neutral_codex_capability_binding_mismatch'
2053
+ }
2054
+ if (-not (Test-CodexCapabilityMatch $inputRecord.replacementRecord $replacement)) {
2055
+ Fail 'neutral_codex_capability_binding_mismatch'
2056
+ }
2057
+ $candidates = @($store.capabilities | Where-Object {
2058
+ [string]$_.capabilityId -cne $CapabilityId -and
2059
+ [string]$_.state -ceq 'active' -and
2060
+ (Test-CodexCapabilityMatch $_ $replacement)
2061
+ })
2062
+ if ($candidates.Count -gt 1) { Fail 'neutral_codex_capability_ambiguous' }
2063
+ if ($candidates.Count -eq 1) {
2064
+ $selected = $candidates[0]
2065
+ } else {
2066
+ if (Test-AuthorizationIdUsed $store $root 'capability' $inputRecord.replacementRecord.capabilityId) {
2067
+ Fail 'host_capability_id_conflict'
2068
+ }
2069
+ $selected = $inputRecord.replacementRecord
2070
+ $store.capabilities = @($store.capabilities) + @($selected)
2071
+ }
2072
+ $old[0].state = 'revoked'
2073
+ $old[0].revokedAt = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds()
2074
+ $store.codexNeutralBindings = @($replacement)
2075
+ Write-Store $path $store $root $sid
2076
+ Write-Result ([PSCustomObject]@{
2077
+ binding = $replacement; record = $selected; replacedCapabilityId = $CapabilityId
2078
+ })
2079
+ }
2080
+ 'SaveOpenClawNeutralBinding' {
2081
+ $existing = @($store.openClawNeutralBindings | Where-Object adapterId -CEQ 'openclaw')
2082
+ if ($existing.Count -gt 1) { Fail 'neutral_openclaw_binding_invalid' }
2083
+ if ($existing.Count -eq 1) {
2084
+ if (-not (Test-OpenClawNeutralBindingEqual $existing[0] $inputRecord)) {
2085
+ Fail 'neutral_openclaw_binding_invalid'
2086
+ }
2087
+ } else {
2088
+ $store.openClawNeutralBindings = @($store.openClawNeutralBindings) + @($inputRecord)
2089
+ Write-Store $path $store $root $sid
2090
+ }
2091
+ Write-Result ([PSCustomObject]@{ binding = $inputRecord })
2092
+ }
2093
+ 'ResolveOpenClawNeutralBinding' {
2094
+ $bindings = @($store.openClawNeutralBindings | Where-Object adapterId -CEQ 'openclaw')
2095
+ if ($bindings.Count -eq 0) { Fail 'neutral_openclaw_binding_configuration_required' }
2096
+ if ($bindings.Count -gt 1) { Fail 'neutral_openclaw_binding_invalid' }
2097
+ $binding = Assert-OpenClawNeutralBinding $bindings[0] $true
2098
+ $activeOpenClaw = @($store.capabilities | Where-Object {
2099
+ [string]$_.state -ceq 'active' -and [string]$_.adapterId -ceq 'openclaw'
2100
+ })
2101
+ $matching = @($activeOpenClaw | Where-Object { Test-OpenClawCapabilityMatch $_ $binding })
2102
+ if ($matching.Count -eq 0) {
2103
+ if ($activeOpenClaw.Count -eq 0) { Fail 'neutral_openclaw_capability_missing' }
2104
+ Fail 'neutral_openclaw_capability_binding_mismatch'
2105
+ }
2106
+ if ($matching.Count -gt 1) { Fail 'neutral_openclaw_capability_ambiguous' }
2107
+ $record = if ($bindingVerification) { Get-PublicCapabilityRecord $matching[0] } else { $matching[0] }
2108
+ Write-Result ([PSCustomObject]@{ binding = $binding; record = $record })
2109
+ }
2110
+ 'RemoveOpenClawNeutralBinding' {
2111
+ $matching = @($store.openClawNeutralBindings | Where-Object {
2112
+ Test-OpenClawNeutralBindingEqual $_ $inputRecord
2113
+ })
2114
+ if ($matching.Count -ne 1) { Fail 'neutral_openclaw_binding_invalid' }
2115
+ $store.openClawNeutralBindings = @($store.openClawNeutralBindings | Where-Object {
2116
+ -not (Test-OpenClawNeutralBindingEqual $_ $matching[0])
2117
+ })
2118
+ Write-Store $path $store $root $sid
2119
+ Write-Result ([PSCustomObject]@{ removed = $true })
2120
+ }
2121
+ 'MigrateOpenClawNeutralBinding' {
2122
+ if ($CapabilityId -notmatch $CapabilityIdPattern) { Fail 'host_capability_id_invalid' }
2123
+ $previous = $inputRecord.previousBinding
2124
+ $replacement = $inputRecord.replacementBinding
2125
+ if (-not (Test-NeutralBindingOwnerEqual $previous $replacement)) {
2126
+ Fail 'neutral_openclaw_capability_binding_mismatch'
2127
+ }
2128
+ $bindings = @($store.openClawNeutralBindings | Where-Object adapterId -CEQ 'openclaw')
2129
+ if ($bindings.Count -ne 1 -or
2130
+ -not (Test-OpenClawNeutralBindingEqual $bindings[0] $previous)) {
2131
+ Fail 'neutral_openclaw_binding_invalid'
2132
+ }
2133
+ $old = @($store.capabilities | Where-Object capabilityId -CEQ $CapabilityId)
2134
+ if ($old.Count -ne 1 -or [string]$old[0].state -cne 'active' -or
2135
+ -not (Test-OpenClawCapabilityMatch $old[0] $previous)) {
2136
+ Fail 'neutral_openclaw_capability_binding_mismatch'
2137
+ }
2138
+ if (-not (Test-OpenClawCapabilityMatch $inputRecord.replacementRecord $replacement)) {
2139
+ Fail 'neutral_openclaw_capability_binding_mismatch'
2140
+ }
2141
+ $candidates = @($store.capabilities | Where-Object {
2142
+ [string]$_.capabilityId -cne $CapabilityId -and
2143
+ [string]$_.state -ceq 'active' -and
2144
+ (Test-OpenClawCapabilityMatch $_ $replacement)
2145
+ })
2146
+ if ($candidates.Count -gt 1) { Fail 'neutral_openclaw_capability_ambiguous' }
2147
+ if ($candidates.Count -eq 1) {
2148
+ $selected = $candidates[0]
2149
+ } else {
2150
+ if (Test-AuthorizationIdUsed $store $root 'capability' $inputRecord.replacementRecord.capabilityId) {
2151
+ Fail 'host_capability_id_conflict'
2152
+ }
2153
+ $selected = $inputRecord.replacementRecord
2154
+ $store.capabilities = @($store.capabilities) + @($selected)
2155
+ }
2156
+ $old[0].state = 'revoked'
2157
+ $old[0].revokedAt = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds()
2158
+ $store.openClawNeutralBindings = @($replacement)
2159
+ Write-Store $path $store $root $sid
2160
+ Write-Result ([PSCustomObject]@{
2161
+ binding = $replacement; record = $selected; replacedCapabilityId = $CapabilityId
2162
+ })
2163
+ }
2164
+ 'SaveTraeWorkNeutralBinding' {
2165
+ $existing = @($store.traeWorkNeutralBindings | Where-Object adapterId -CEQ 'trae-work')
2166
+ if ($existing.Count -gt 1) { Fail 'neutral_trae_work_binding_invalid' }
2167
+ if ($existing.Count -eq 1) {
2168
+ if (-not (Test-TraeWorkNeutralBindingEqual $existing[0] $inputRecord)) {
2169
+ Fail 'neutral_trae_work_binding_invalid'
2170
+ }
2171
+ } else {
2172
+ $store.traeWorkNeutralBindings = @($store.traeWorkNeutralBindings) + @($inputRecord)
2173
+ Write-Store $path $store $root $sid
2174
+ }
2175
+ Write-Result ([PSCustomObject]@{ binding = $inputRecord })
2176
+ }
2177
+ 'ResolveTraeWorkNeutralBinding' {
2178
+ $bindings = @($store.traeWorkNeutralBindings | Where-Object adapterId -CEQ 'trae-work')
2179
+ if ($bindings.Count -eq 0) { Fail 'neutral_trae_work_binding_configuration_required' }
2180
+ if ($bindings.Count -gt 1) { Fail 'neutral_trae_work_binding_invalid' }
2181
+ $binding = Assert-TraeWorkNeutralBinding $bindings[0] $true
2182
+ $activeTraeWork = @($store.capabilities | Where-Object {
2183
+ [string]$_.state -ceq 'active' -and [string]$_.adapterId -ceq 'trae-work'
2184
+ })
2185
+ $matching = @($activeTraeWork | Where-Object { Test-TraeWorkCapabilityMatch $_ $binding })
2186
+ if ($matching.Count -eq 0) {
2187
+ if ($activeTraeWork.Count -eq 0) { Fail 'neutral_trae_work_capability_missing' }
2188
+ Fail 'neutral_trae_work_capability_binding_mismatch'
2189
+ }
2190
+ if ($matching.Count -gt 1) { Fail 'neutral_trae_work_capability_ambiguous' }
2191
+ $record = if ($bindingVerification) { Get-PublicCapabilityRecord $matching[0] } else { $matching[0] }
2192
+ Write-Result ([PSCustomObject]@{ binding = $binding; record = $record })
2193
+ }
2194
+ 'RemoveTraeWorkNeutralBinding' {
2195
+ $matching = @($store.traeWorkNeutralBindings | Where-Object {
2196
+ Test-TraeWorkNeutralBindingEqual $_ $inputRecord
2197
+ })
2198
+ if ($matching.Count -ne 1) { Fail 'neutral_trae_work_binding_invalid' }
2199
+ $store.traeWorkNeutralBindings = @($store.traeWorkNeutralBindings | Where-Object {
2200
+ -not (Test-TraeWorkNeutralBindingEqual $_ $matching[0])
2201
+ })
2202
+ Write-Store $path $store $root $sid
2203
+ Write-Result ([PSCustomObject]@{ removed = $true })
2204
+ }
2205
+ 'MigrateTraeWorkNeutralBinding' {
2206
+ if ($CapabilityId -notmatch $CapabilityIdPattern) { Fail 'host_capability_id_invalid' }
2207
+ $previous = $inputRecord.previousBinding
2208
+ $replacement = $inputRecord.replacementBinding
2209
+ if (-not (Test-NeutralBindingOwnerEqual $previous $replacement)) {
2210
+ Fail 'neutral_trae_work_capability_binding_mismatch'
2211
+ }
2212
+ $bindings = @($store.traeWorkNeutralBindings | Where-Object adapterId -CEQ 'trae-work')
2213
+ if ($bindings.Count -ne 1 -or
2214
+ -not (Test-TraeWorkNeutralBindingEqual $bindings[0] $previous)) {
2215
+ Fail 'neutral_trae_work_binding_invalid'
2216
+ }
2217
+ $old = @($store.capabilities | Where-Object capabilityId -CEQ $CapabilityId)
2218
+ if ($old.Count -ne 1 -or [string]$old[0].state -cne 'active' -or
2219
+ -not (Test-TraeWorkCapabilityMatch $old[0] $previous)) {
2220
+ Fail 'neutral_trae_work_capability_binding_mismatch'
2221
+ }
2222
+ if (-not (Test-TraeWorkCapabilityMatch $inputRecord.replacementRecord $replacement)) {
2223
+ Fail 'neutral_trae_work_capability_binding_mismatch'
2224
+ }
2225
+ $candidates = @($store.capabilities | Where-Object {
2226
+ [string]$_.capabilityId -cne $CapabilityId -and
2227
+ [string]$_.state -ceq 'active' -and
2228
+ (Test-TraeWorkCapabilityMatch $_ $replacement)
2229
+ })
2230
+ if ($candidates.Count -gt 1) { Fail 'neutral_trae_work_capability_ambiguous' }
2231
+ if ($candidates.Count -eq 1) {
2232
+ $selected = $candidates[0]
2233
+ } else {
2234
+ if (Test-AuthorizationIdUsed $store $root 'capability' $inputRecord.replacementRecord.capabilityId) {
2235
+ Fail 'host_capability_id_conflict'
2236
+ }
2237
+ $selected = $inputRecord.replacementRecord
2238
+ $store.capabilities = @($store.capabilities) + @($selected)
2239
+ }
2240
+ $old[0].state = 'revoked'
2241
+ $old[0].revokedAt = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds()
2242
+ $store.traeWorkNeutralBindings = @($replacement)
2243
+ Write-Store $path $store $root $sid
2244
+ Write-Result ([PSCustomObject]@{
2245
+ binding = $replacement; record = $selected; replacedCapabilityId = $CapabilityId
2246
+ })
2247
+ }
2248
+ 'SaveWorkBuddyNeutralBinding' {
2249
+ $existing = @($store.workBuddyNeutralBindings | Where-Object adapterId -CEQ 'workbuddy')
2250
+ if ($existing.Count -gt 1) { Fail 'neutral_workbuddy_binding_invalid' }
2251
+ if ($existing.Count -eq 1) {
2252
+ if (-not (Test-WorkBuddyNeutralBindingEqual $existing[0] $inputRecord)) {
2253
+ Fail 'neutral_workbuddy_binding_invalid'
2254
+ }
2255
+ } else {
2256
+ $store.workBuddyNeutralBindings = @($store.workBuddyNeutralBindings) + @($inputRecord)
2257
+ Write-Store $path $store $root $sid
2258
+ }
2259
+ Write-Result ([PSCustomObject]@{ binding = $inputRecord })
2260
+ }
2261
+ 'ResolveWorkBuddyNeutralBinding' {
2262
+ $bindings = @($store.workBuddyNeutralBindings | Where-Object adapterId -CEQ 'workbuddy')
2263
+ if ($bindings.Count -eq 0) { Fail 'neutral_workbuddy_binding_configuration_required' }
2264
+ if ($bindings.Count -gt 1) { Fail 'neutral_workbuddy_binding_invalid' }
2265
+ $binding = Assert-WorkBuddyNeutralBinding $bindings[0] $true
2266
+ $activeWorkBuddy = @($store.capabilities | Where-Object {
2267
+ [string]$_.state -ceq 'active' -and [string]$_.adapterId -ceq 'workbuddy'
2268
+ })
2269
+ $matching = @($activeWorkBuddy | Where-Object { Test-WorkBuddyCapabilityMatch $_ $binding })
2270
+ if ($matching.Count -eq 0) {
2271
+ if ($activeWorkBuddy.Count -eq 0) { Fail 'neutral_workbuddy_capability_missing' }
2272
+ Fail 'neutral_workbuddy_capability_binding_mismatch'
2273
+ }
2274
+ if ($matching.Count -gt 1) { Fail 'neutral_workbuddy_capability_ambiguous' }
2275
+ $record = if ($bindingVerification) { Get-PublicCapabilityRecord $matching[0] } else { $matching[0] }
2276
+ Write-Result ([PSCustomObject]@{ binding = $binding; record = $record })
2277
+ }
2278
+ 'RemoveWorkBuddyNeutralBinding' {
2279
+ $matching = @($store.workBuddyNeutralBindings | Where-Object {
2280
+ Test-WorkBuddyNeutralBindingEqual $_ $inputRecord
2281
+ })
2282
+ if ($matching.Count -ne 1) { Fail 'neutral_workbuddy_binding_invalid' }
2283
+ $store.workBuddyNeutralBindings = @($store.workBuddyNeutralBindings | Where-Object {
2284
+ -not (Test-WorkBuddyNeutralBindingEqual $_ $matching[0])
2285
+ })
2286
+ Write-Store $path $store $root $sid
2287
+ Write-Result ([PSCustomObject]@{ removed = $true })
2288
+ }
2289
+ 'MigrateWorkBuddyNeutralBinding' {
2290
+ if ($CapabilityId -notmatch $CapabilityIdPattern) { Fail 'host_capability_id_invalid' }
2291
+ $previous = $inputRecord.previousBinding
2292
+ $replacement = $inputRecord.replacementBinding
2293
+ if (-not (Test-NeutralBindingOwnerEqual $previous $replacement)) {
2294
+ Fail 'neutral_workbuddy_capability_binding_mismatch'
2295
+ }
2296
+ $bindings = @($store.workBuddyNeutralBindings | Where-Object adapterId -CEQ 'workbuddy')
2297
+ if ($bindings.Count -ne 1 -or
2298
+ -not (Test-WorkBuddyNeutralBindingEqual $bindings[0] $previous)) {
2299
+ Fail 'neutral_workbuddy_binding_invalid'
2300
+ }
2301
+ $old = @($store.capabilities | Where-Object capabilityId -CEQ $CapabilityId)
2302
+ if ($old.Count -ne 1 -or [string]$old[0].state -cne 'active' -or
2303
+ -not (Test-WorkBuddyCapabilityMatch $old[0] $previous)) {
2304
+ Fail 'neutral_workbuddy_capability_binding_mismatch'
2305
+ }
2306
+ if (-not (Test-WorkBuddyCapabilityMatch $inputRecord.replacementRecord $replacement)) {
2307
+ Fail 'neutral_workbuddy_capability_binding_mismatch'
2308
+ }
2309
+ $candidates = @($store.capabilities | Where-Object {
2310
+ [string]$_.capabilityId -cne $CapabilityId -and
2311
+ [string]$_.state -ceq 'active' -and
2312
+ (Test-WorkBuddyCapabilityMatch $_ $replacement)
2313
+ })
2314
+ if ($candidates.Count -gt 1) { Fail 'neutral_workbuddy_capability_ambiguous' }
2315
+ if ($candidates.Count -eq 1) {
2316
+ $selected = $candidates[0]
2317
+ } else {
2318
+ if (Test-AuthorizationIdUsed $store $root 'capability' $inputRecord.replacementRecord.capabilityId) {
2319
+ Fail 'host_capability_id_conflict'
2320
+ }
2321
+ $selected = $inputRecord.replacementRecord
2322
+ $store.capabilities = @($store.capabilities) + @($selected)
2323
+ }
2324
+ $old[0].state = 'revoked'
2325
+ $old[0].revokedAt = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds()
2326
+ $store.workBuddyNeutralBindings = @($replacement)
2327
+ Write-Store $path $store $root $sid
2328
+ Write-Result ([PSCustomObject]@{
2329
+ binding = $replacement; record = $selected; replacedCapabilityId = $CapabilityId
2330
+ })
2331
+ }
2332
+ 'SaveClaudeCodeNeutralBinding' {
2333
+ $existing = @($store.claudeCodeNeutralBindings | Where-Object adapterId -CEQ 'claude-code')
2334
+ if ($existing.Count -gt 1) { Fail 'neutral_claude_code_binding_invalid' }
2335
+ if ($existing.Count -eq 1) {
2336
+ if (-not (Test-ClaudeCodeNeutralBindingEqual $existing[0] $inputRecord)) {
2337
+ Fail 'neutral_claude_code_binding_invalid'
2338
+ }
2339
+ } else {
2340
+ $store.claudeCodeNeutralBindings = @($store.claudeCodeNeutralBindings) + @($inputRecord)
2341
+ Write-Store $path $store $root $sid
2342
+ }
2343
+ Write-Result ([PSCustomObject]@{ binding = $inputRecord })
2344
+ }
2345
+ 'ResolveClaudeCodeNeutralBinding' {
2346
+ $bindings = @($store.claudeCodeNeutralBindings | Where-Object adapterId -CEQ 'claude-code')
2347
+ if ($bindings.Count -eq 0) { Fail 'neutral_claude_code_binding_configuration_required' }
2348
+ if ($bindings.Count -gt 1) { Fail 'neutral_claude_code_binding_invalid' }
2349
+ $binding = Assert-ClaudeCodeNeutralBinding $bindings[0] $true
2350
+ $activeClaudeCode = @($store.capabilities | Where-Object {
2351
+ [string]$_.state -ceq 'active' -and [string]$_.adapterId -ceq 'claude-code'
2352
+ })
2353
+ $matching = @($activeClaudeCode | Where-Object { Test-ClaudeCodeCapabilityMatch $_ $binding })
2354
+ if ($matching.Count -eq 0) {
2355
+ if ($activeClaudeCode.Count -eq 0) { Fail 'neutral_claude_code_capability_missing' }
2356
+ Fail 'neutral_claude_code_capability_binding_mismatch'
2357
+ }
2358
+ if ($matching.Count -gt 1) { Fail 'neutral_claude_code_capability_ambiguous' }
2359
+ $record = if ($bindingVerification) { Get-PublicCapabilityRecord $matching[0] } else { $matching[0] }
2360
+ Write-Result ([PSCustomObject]@{ binding = $binding; record = $record })
2361
+ }
2362
+ 'RemoveClaudeCodeNeutralBinding' {
2363
+ $matching = @($store.claudeCodeNeutralBindings | Where-Object {
2364
+ Test-ClaudeCodeNeutralBindingEqual $_ $inputRecord
2365
+ })
2366
+ if ($matching.Count -ne 1) { Fail 'neutral_claude_code_binding_invalid' }
2367
+ $store.claudeCodeNeutralBindings = @($store.claudeCodeNeutralBindings | Where-Object {
2368
+ -not (Test-ClaudeCodeNeutralBindingEqual $_ $matching[0])
2369
+ })
2370
+ Write-Store $path $store $root $sid
2371
+ Write-Result ([PSCustomObject]@{ removed = $true })
2372
+ }
2373
+ 'MigrateClaudeCodeNeutralBinding' {
2374
+ if ($CapabilityId -notmatch $CapabilityIdPattern) { Fail 'host_capability_id_invalid' }
2375
+ $previous = $inputRecord.previousBinding
2376
+ $replacement = $inputRecord.replacementBinding
2377
+ if (-not (Test-NeutralBindingOwnerEqual $previous $replacement)) {
2378
+ Fail 'neutral_claude_code_capability_binding_mismatch'
2379
+ }
2380
+ $bindings = @($store.claudeCodeNeutralBindings | Where-Object adapterId -CEQ 'claude-code')
2381
+ if ($bindings.Count -ne 1 -or
2382
+ -not (Test-ClaudeCodeNeutralBindingEqual $bindings[0] $previous)) {
2383
+ Fail 'neutral_claude_code_binding_invalid'
2384
+ }
2385
+ $old = @($store.capabilities | Where-Object capabilityId -CEQ $CapabilityId)
2386
+ if ($old.Count -ne 1 -or [string]$old[0].state -cne 'active' -or
2387
+ -not (Test-ClaudeCodeCapabilityMatch $old[0] $previous)) {
2388
+ Fail 'neutral_claude_code_capability_binding_mismatch'
2389
+ }
2390
+ if (-not (Test-ClaudeCodeCapabilityMatch $inputRecord.replacementRecord $replacement)) {
2391
+ Fail 'neutral_claude_code_capability_binding_mismatch'
2392
+ }
2393
+ $candidates = @($store.capabilities | Where-Object {
2394
+ [string]$_.capabilityId -cne $CapabilityId -and
2395
+ [string]$_.state -ceq 'active' -and
2396
+ (Test-ClaudeCodeCapabilityMatch $_ $replacement)
2397
+ })
2398
+ if ($candidates.Count -gt 1) { Fail 'neutral_claude_code_capability_ambiguous' }
2399
+ if ($candidates.Count -eq 1) {
2400
+ $selected = $candidates[0]
2401
+ } else {
2402
+ if (Test-AuthorizationIdUsed $store $root 'capability' $inputRecord.replacementRecord.capabilityId) {
2403
+ Fail 'host_capability_id_conflict'
2404
+ }
2405
+ $selected = $inputRecord.replacementRecord
2406
+ $store.capabilities = @($store.capabilities) + @($selected)
2407
+ }
2408
+ $old[0].state = 'revoked'
2409
+ $old[0].revokedAt = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds()
2410
+ $store.claudeCodeNeutralBindings = @($replacement)
2411
+ Write-Store $path $store $root $sid
2412
+ Write-Result ([PSCustomObject]@{
2413
+ binding = $replacement; record = $selected; replacedCapabilityId = $CapabilityId
2414
+ })
2415
+ }
2416
+ 'SaveCursorNeutralBinding' {
2417
+ $existing = @($store.cursorNeutralBindings | Where-Object adapterId -CEQ 'cursor')
2418
+ if ($existing.Count -gt 1) { Fail 'neutral_cursor_binding_invalid' }
2419
+ if ($existing.Count -eq 1) {
2420
+ if (-not (Test-CursorNeutralBindingEqual $existing[0] $inputRecord)) {
2421
+ Fail 'neutral_cursor_binding_invalid'
2422
+ }
2423
+ } else {
2424
+ $store.cursorNeutralBindings = @($store.cursorNeutralBindings) + @($inputRecord)
2425
+ Write-Store $path $store $root $sid
2426
+ }
2427
+ Write-Result ([PSCustomObject]@{ binding = $inputRecord })
2428
+ }
2429
+ 'ResolveCursorNeutralBinding' {
2430
+ $bindings = @($store.cursorNeutralBindings | Where-Object adapterId -CEQ 'cursor')
2431
+ if ($bindings.Count -eq 0) { Fail 'neutral_cursor_binding_configuration_required' }
2432
+ if ($bindings.Count -gt 1) { Fail 'neutral_cursor_binding_invalid' }
2433
+ $binding = Assert-CursorNeutralBinding $bindings[0] $true
2434
+ $activeCursor = @($store.capabilities | Where-Object {
2435
+ [string]$_.state -ceq 'active' -and [string]$_.adapterId -ceq 'cursor'
2436
+ })
2437
+ $matching = @($activeCursor | Where-Object { Test-CursorCapabilityMatch $_ $binding })
2438
+ if ($matching.Count -eq 0) {
2439
+ if ($activeCursor.Count -eq 0) { Fail 'neutral_cursor_capability_missing' }
2440
+ Fail 'neutral_cursor_capability_binding_mismatch'
2441
+ }
2442
+ if ($matching.Count -gt 1) { Fail 'neutral_cursor_capability_ambiguous' }
2443
+ $record = if ($bindingVerification) { Get-PublicCapabilityRecord $matching[0] } else { $matching[0] }
2444
+ Write-Result ([PSCustomObject]@{ binding = $binding; record = $record })
2445
+ }
2446
+ 'RemoveCursorNeutralBinding' {
2447
+ $matching = @($store.cursorNeutralBindings | Where-Object {
2448
+ Test-CursorNeutralBindingEqual $_ $inputRecord
2449
+ })
2450
+ if ($matching.Count -ne 1) { Fail 'neutral_cursor_binding_invalid' }
2451
+ $store.cursorNeutralBindings = @($store.cursorNeutralBindings | Where-Object {
2452
+ -not (Test-CursorNeutralBindingEqual $_ $matching[0])
2453
+ })
2454
+ Write-Store $path $store $root $sid
2455
+ Write-Result ([PSCustomObject]@{ removed = $true })
2456
+ }
2457
+ 'MigrateCursorNeutralBinding' {
2458
+ if ($CapabilityId -notmatch $CapabilityIdPattern) { Fail 'host_capability_id_invalid' }
2459
+ $previous = $inputRecord.previousBinding
2460
+ $replacement = $inputRecord.replacementBinding
2461
+ if (-not (Test-NeutralBindingOwnerEqual $previous $replacement)) {
2462
+ Fail 'neutral_cursor_capability_binding_mismatch'
2463
+ }
2464
+ $bindings = @($store.cursorNeutralBindings | Where-Object adapterId -CEQ 'cursor')
2465
+ if ($bindings.Count -ne 1 -or
2466
+ -not (Test-CursorNeutralBindingEqual $bindings[0] $previous)) {
2467
+ Fail 'neutral_cursor_binding_invalid'
2468
+ }
2469
+ $old = @($store.capabilities | Where-Object capabilityId -CEQ $CapabilityId)
2470
+ if ($old.Count -ne 1 -or [string]$old[0].state -cne 'active' -or
2471
+ -not (Test-CursorCapabilityMatch $old[0] $previous)) {
2472
+ Fail 'neutral_cursor_capability_binding_mismatch'
2473
+ }
2474
+ if (-not (Test-CursorCapabilityMatch $inputRecord.replacementRecord $replacement)) {
2475
+ Fail 'neutral_cursor_capability_binding_mismatch'
2476
+ }
2477
+ $candidates = @($store.capabilities | Where-Object {
2478
+ [string]$_.capabilityId -cne $CapabilityId -and
2479
+ [string]$_.state -ceq 'active' -and
2480
+ (Test-CursorCapabilityMatch $_ $replacement)
2481
+ })
2482
+ if ($candidates.Count -gt 1) { Fail 'neutral_cursor_capability_ambiguous' }
2483
+ if ($candidates.Count -eq 1) {
2484
+ $selected = $candidates[0]
2485
+ } else {
2486
+ if (Test-AuthorizationIdUsed $store $root 'capability' $inputRecord.replacementRecord.capabilityId) {
2487
+ Fail 'host_capability_id_conflict'
2488
+ }
2489
+ $selected = $inputRecord.replacementRecord
2490
+ $store.capabilities = @($store.capabilities) + @($selected)
2491
+ }
2492
+ $old[0].state = 'revoked'
2493
+ $old[0].revokedAt = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds()
2494
+ $store.cursorNeutralBindings = @($replacement)
2495
+ Write-Store $path $store $root $sid
2496
+ Write-Result ([PSCustomObject]@{
2497
+ binding = $replacement; record = $selected; replacedCapabilityId = $CapabilityId
2498
+ })
2499
+ }
2500
+ 'SaveDshNeutralBinding' {
2501
+ $existing = @($store.dshNeutralBindings | Where-Object adapterId -CEQ 'dsh')
2502
+ if ($existing.Count -gt 1) { Fail 'neutral_dsh_binding_invalid' }
2503
+ if ($existing.Count -eq 1) {
2504
+ if (-not (Test-DshNeutralBindingEqual $existing[0] $inputRecord)) { Fail 'neutral_dsh_binding_invalid' }
2505
+ } else { $store.dshNeutralBindings = @($store.dshNeutralBindings) + @($inputRecord); Write-Store $path $store $root $sid }
2506
+ Write-Result ([PSCustomObject]@{ binding = $inputRecord })
2507
+ }
2508
+ 'ResolveDshNeutralBinding' {
2509
+ $bindings = @($store.dshNeutralBindings | Where-Object adapterId -CEQ 'dsh')
2510
+ if ($bindings.Count -eq 0) { Fail 'neutral_dsh_binding_configuration_required' }
2511
+ if ($bindings.Count -gt 1) { Fail 'neutral_dsh_binding_invalid' }
2512
+ $binding = Assert-DshNeutralBinding $bindings[0] $true
2513
+ $activeDsh = @($store.capabilities | Where-Object { [string]$_.state -ceq 'active' -and [string]$_.adapterId -ceq 'dsh' })
2514
+ $matching = @($activeDsh | Where-Object { Test-DshCapabilityMatch $_ $binding })
2515
+ if ($matching.Count -eq 0) { if ($activeDsh.Count -eq 0) { Fail 'neutral_dsh_capability_missing' }; Fail 'neutral_dsh_capability_binding_mismatch' }
2516
+ if ($matching.Count -gt 1) { Fail 'neutral_dsh_capability_ambiguous' }
2517
+ $record = if ($bindingVerification) { Get-PublicCapabilityRecord $matching[0] } else { $matching[0] }
2518
+ Write-Result ([PSCustomObject]@{ binding = $binding; record = $record })
2519
+ }
2520
+ 'RemoveDshNeutralBinding' {
2521
+ $matching = @($store.dshNeutralBindings | Where-Object { Test-DshNeutralBindingEqual $_ $inputRecord })
2522
+ if ($matching.Count -ne 1) { Fail 'neutral_dsh_binding_invalid' }
2523
+ $store.dshNeutralBindings = @($store.dshNeutralBindings | Where-Object { -not (Test-DshNeutralBindingEqual $_ $matching[0]) })
2524
+ Write-Store $path $store $root $sid; Write-Result ([PSCustomObject]@{ removed = $true })
2525
+ }
2526
+ 'MigrateDshNeutralBinding' {
2527
+ if ($CapabilityId -notmatch $CapabilityIdPattern) { Fail 'host_capability_id_invalid' }
2528
+ $previous = $inputRecord.previousBinding; $replacement = $inputRecord.replacementBinding
2529
+ if (-not (Test-NeutralBindingOwnerEqual $previous $replacement)) { Fail 'neutral_dsh_capability_binding_mismatch' }
2530
+ $bindings = @($store.dshNeutralBindings | Where-Object adapterId -CEQ 'dsh')
2531
+ if ($bindings.Count -ne 1 -or -not (Test-DshNeutralBindingEqual $bindings[0] $previous)) { Fail 'neutral_dsh_binding_invalid' }
2532
+ $old = @($store.capabilities | Where-Object capabilityId -CEQ $CapabilityId)
2533
+ if ($old.Count -ne 1 -or [string]$old[0].state -cne 'active' -or -not (Test-DshCapabilityMatch $old[0] $previous) -or -not (Test-DshCapabilityMatch $inputRecord.replacementRecord $replacement)) { Fail 'neutral_dsh_capability_binding_mismatch' }
2534
+ if (@($store.capabilities | Where-Object { [string]$_.capabilityId -cne $CapabilityId -and [string]$_.state -ceq 'active' -and (Test-DshCapabilityMatch $_ $replacement) }).Count -ne 0) { Fail 'neutral_dsh_capability_ambiguous' }
2535
+ if (Test-AuthorizationIdUsed $store $root 'capability' $inputRecord.replacementRecord.capabilityId) { Fail 'host_capability_id_conflict' }
2536
+ $store.capabilities = @($store.capabilities) + @($inputRecord.replacementRecord); $old[0].state = 'revoked'; $old[0].revokedAt = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds(); $store.dshNeutralBindings = @($replacement)
2537
+ Write-Store $path $store $root $sid; Write-Result ([PSCustomObject]@{ binding = $replacement; record = $inputRecord.replacementRecord; replacedCapabilityId = $CapabilityId })
2538
+ }
2539
+ { $_ -in @('SaveTransportCredential', 'SaveEnrollmentTransportCredential') } {
2540
+ $enrollmentUpgrade = $Command -eq 'SaveEnrollmentTransportCredential'
2541
+ if ($enrollmentUpgrade) {
2542
+ # This is the same per-root mutex used by enrollment CAS. Approval
2543
+ # and replacement cannot race with another local setup operation.
2544
+ $enrollment = Read-DeviceEnrollment (Join-Path $root 'device-enrollment-v1.dpapi.clixml') $sid
2545
+ Assert-ExactFields $enrollment.value @('baseUrl', 'hellolynkId', 'hostId', 'deviceLabel', 'requestId', 'proof', 'approval', 'intents') 'device_enrollment_transport_approval_mismatch'
2546
+ $approval = $enrollment.value.approval
2547
+ Assert-ExactFields $approval @('status', 'principalId', 'hellolynkId', 'region', 'hostId', 'hostTransportCredential') 'device_enrollment_transport_approval_mismatch'
2548
+ Assert-ExactFields $approval.hostTransportCredential @('credentialId', 'credential', 'createdAt', 'expiresAt') 'device_enrollment_transport_approval_mismatch'
2549
+ if ($null -eq $approval -or [string]$approval.status -cne 'approved' -or
2550
+ [string]$approval.principalId -cne [string]$inputRecord.principalId -or
2551
+ [string]$approval.hostId -cne [string]$inputRecord.hostInstallationId -or
2552
+ [string]$enrollment.value.hostId -cne [string]$inputRecord.hostInstallationId -or
2553
+ [string]$approval.hellolynkId -cne [string]$enrollment.value.hellolynkId -or
2554
+ [string]$approval.hostTransportCredential.credentialId -cne [string]$inputRecord.credentialId -or
2555
+ [string]$approval.hostTransportCredential.credential -cne [string]$inputRecord.secret) {
2556
+ Fail 'device_enrollment_transport_approval_mismatch'
2557
+ }
2558
+ try {
2559
+ $approvedCreatedAt = ([DateTimeOffset]$approval.hostTransportCredential.createdAt).ToUnixTimeMilliseconds()
2560
+ $approvedExpiresAt = ([DateTimeOffset]$approval.hostTransportCredential.expiresAt).ToUnixTimeMilliseconds()
2561
+ } catch { Fail 'device_enrollment_transport_approval_mismatch' }
2562
+ if ($approvedCreatedAt -ne [int64]$inputRecord.issuedAt -or $approvedExpiresAt -ne [int64]$inputRecord.expiresAt) {
2563
+ Fail 'device_enrollment_transport_approval_mismatch'
2564
+ }
2565
+ if ([int64]$inputRecord.expiresAt -le [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds()) {
2566
+ Fail 'host_transport_credential_expired'
2567
+ }
2568
+ }
2569
+ $existing = @($store.transportCredentials | Where-Object credentialId -CEQ $inputRecord.credentialId)
2570
+ if ($existing.Count -eq 0 -and (Test-AuthorizationIdUsed $store $root 'credential' $inputRecord.credentialId)) {
2571
+ Fail 'host_transport_credential_immutable_conflict'
2572
+ }
2573
+ if ($existing.Count -gt 1) { Fail 'host_transport_credential_duplicate' }
2574
+ if ($existing.Count -eq 1) {
2575
+ if (-not (Test-TransportCredentialEqual $existing[0] $inputRecord)) {
2576
+ Fail 'host_transport_credential_immutable_conflict'
2577
+ }
2578
+ } else {
2579
+ $sameOwner = @($store.transportCredentials | Where-Object {
2580
+ [string]$_.principalId -ceq [string]$inputRecord.principalId -and
2581
+ [string]$_.hostInstallationId -ceq [string]$inputRecord.hostInstallationId -and
2582
+ [string]$_.state -ceq 'active'
2583
+ })
2584
+ if ($sameOwner.Count -gt 1 -or ($sameOwner.Count -gt 0 -and -not $enrollmentUpgrade)) {
2585
+ Fail 'host_transport_credential_ambiguous'
2586
+ }
2587
+ if ($sameOwner.Count -eq 1) {
2588
+ $old = $sameOwner[0]
2589
+ $replacementTime = [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds()
2590
+ if ($replacementTime -lt [int64]$old.issuedAt) { Fail 'host_transport_credential_revoke_timestamp_invalid' }
2591
+ $sessions = @($store.transportSessions | Where-Object credentialId -CEQ $old.credentialId)
2592
+ $store.retiredTransportSessionIds = @(
2593
+ @($store.retiredTransportSessionIds) + @($sessions | ForEach-Object sessionId) | Sort-Object -Unique
2594
+ )
2595
+ $store.transportSessions = @($store.transportSessions | Where-Object credentialId -CNE $old.credentialId)
2596
+ $old.state = 'revoked'; $old.revokedAt = $replacementTime
2597
+ }
2598
+ $store.transportCredentials = @($store.transportCredentials) + @($inputRecord)
2599
+ Write-Store $path $store $root $sid
2600
+ }
2601
+ Write-Result ([PSCustomObject]@{ record = $inputRecord })
2602
+ }
2603
+ { $_ -in @('VerifyTransportCredentialLifetimeMigration', 'MigrateTransportCredentialLifetime') } {
2604
+ Assert-QueryOwner
2605
+ $records = @($store.transportCredentials | Where-Object {
2606
+ [string]$_.principalId -ceq $PrincipalId -and
2607
+ [string]$_.hostInstallationId -ceq $HostInstallationId -and
2608
+ [string]$_.state -ceq 'active' -and $null -eq $_.revokedAt
2609
+ })
2610
+ if ($records.Count -ne 1) { Fail 'host_transport_migration_credential_inactive_or_ambiguous' }
2611
+ $record = $records[0]
2612
+ $permanent = [int64]253402300799999
2613
+ if ([int64]$record.expiresAt -ne $permanent -and
2614
+ ([int64]$record.expiresAt - [int64]$record.issuedAt) -ne [int64]2592000000) {
2615
+ Fail 'host_transport_migration_lifetime_invalid'
2616
+ }
2617
+ if ($Command -eq 'VerifyTransportCredentialLifetimeMigration') {
2618
+ Write-Result ([PSCustomObject]@{ record = $record })
2619
+ } else {
2620
+ $previous = $inputRecord.previous
2621
+ if ([string]$previous.state -cne 'active' -or $null -ne $previous.revokedAt -or
2622
+ ([int64]$previous.expiresAt - [int64]$previous.issuedAt) -ne [int64]2592000000 -or
2623
+ -not (Test-TransportCredentialEqual $record $previous)) {
2624
+ Fail 'host_transport_migration_compare_failed'
2625
+ }
2626
+ $proof = $inputRecord.proof
2627
+ try {
2628
+ $createdAt = ([DateTimeOffset]$proof.credentialCreatedAt).ToUnixTimeMilliseconds()
2629
+ $expiresAt = ([DateTimeOffset]$proof.credentialExpiresAt).ToUnixTimeMilliseconds()
2630
+ } catch { Fail 'host_transport_migration_proof_invalid' }
2631
+ if ([string]$proof.credentialId -cne [string]$record.credentialId -or
2632
+ [string]$proof.principalId -cne $PrincipalId -or [string]$proof.hostId -cne $HostInstallationId -or
2633
+ $createdAt -ne [int64]$record.issuedAt -or $expiresAt -ne $permanent) {
2634
+ Fail 'host_transport_migration_proof_invalid'
2635
+ }
2636
+ $record.expiresAt = $permanent
2637
+ Write-Store $path $store $root $sid
2638
+ Write-Result ([PSCustomObject]@{ credentialId = $record.credentialId; expiresAt = $permanent; status = 'migrated' })
2639
+ }
2640
+ }
2641
+ 'LoadTransportCredential' {
2642
+ Assert-QueryOwner
2643
+ if ($CredentialId -notmatch $IdentifierPattern) { Fail 'host_transport_credential_id_invalid' }
2644
+ if ($Timestamp -lt 0) { Fail 'host_timestamp_invalid' }
2645
+ $records = @($store.transportCredentials | Where-Object credentialId -CEQ $CredentialId)
2646
+ if ($records.Count -ne 1 -or [string]$records[0].state -ne 'active') {
2647
+ Fail 'host_transport_credential_inactive'
2648
+ }
2649
+ if (
2650
+ [string]$records[0].principalId -cne $PrincipalId -or
2651
+ [string]$records[0].hostInstallationId -cne $HostInstallationId
2652
+ ) { Fail 'host_transport_credential_owner_mismatch' }
2653
+ if ([int64]$records[0].expiresAt -le $Timestamp) { Fail 'host_transport_credential_expired' }
2654
+ Write-Result ([PSCustomObject]@{ record = $records[0] })
2655
+ }
2656
+ 'RevokeTransportCredential' {
2657
+ Assert-QueryOwner
2658
+ if ($CredentialId -notmatch $IdentifierPattern) { Fail 'host_transport_credential_id_invalid' }
2659
+ if ($Timestamp -lt 0) { Fail 'host_timestamp_invalid' }
2660
+ $records = @($store.transportCredentials | Where-Object credentialId -CEQ $CredentialId)
2661
+ if ($records.Count -eq 0) {
2662
+ $archived = Read-AuthorizationHistory $store $root 'credential' $CredentialId
2663
+ if ($null -ne $archived) { $records = @($archived) }
2664
+ }
2665
+ if ($records.Count -ne 1) { Fail 'host_transport_credential_missing' }
2666
+ if (
2667
+ [string]$records[0].principalId -cne $PrincipalId -or
2668
+ [string]$records[0].hostInstallationId -cne $HostInstallationId
2669
+ ) { Fail 'host_transport_credential_owner_mismatch' }
2670
+ if ($Timestamp -lt [int64]$records[0].issuedAt) {
2671
+ Fail 'host_transport_credential_revoke_timestamp_invalid'
2672
+ }
2673
+ $sessions = @($store.transportSessions | Where-Object credentialId -CEQ $CredentialId)
2674
+ if ([string]$records[0].state -eq 'active') {
2675
+ $records[0].state = 'revoked'
2676
+ $records[0].revokedAt = $Timestamp
2677
+ $store.retiredTransportSessionIds = @(
2678
+ @($store.retiredTransportSessionIds) + @($sessions | ForEach-Object sessionId) |
2679
+ Sort-Object -Unique
2680
+ )
2681
+ $store.transportSessions = @($store.transportSessions | Where-Object credentialId -CNE $CredentialId)
2682
+ Write-Store $path $store $root $sid
2683
+ }
2684
+ Write-Result ([PSCustomObject]@{
2685
+ credentialId = $CredentialId
2686
+ state = 'revoked'
2687
+ revokedAt = $records[0].revokedAt
2688
+ cascadedSessionCount = $sessions.Count
2689
+ })
2690
+ }
2691
+ 'SaveTransportSession' {
2692
+ if ($Timestamp -lt 0) { Fail 'host_timestamp_invalid' }
2693
+ $credential = @($store.transportCredentials | Where-Object credentialId -CEQ $inputRecord.credentialId)
2694
+ if ($credential.Count -ne 1 -or [string]$credential[0].state -ne 'active') {
2695
+ Fail 'host_transport_credential_inactive'
2696
+ }
2697
+ if (
2698
+ [string]$credential[0].principalId -cne [string]$inputRecord.principalId -or
2699
+ [string]$credential[0].hostInstallationId -cne [string]$inputRecord.hostInstallationId -or
2700
+ [int64]$credential[0].expiresAt -le $Timestamp -or
2701
+ [int64]$inputRecord.expiresAt -gt [int64]$credential[0].expiresAt
2702
+ ) { Fail 'host_transport_session_credential_mismatch' }
2703
+ if (Test-RetiredTransportSession $store $root ([string]$inputRecord.sessionId)) {
2704
+ Fail 'host_transport_session_retired'
2705
+ }
2706
+ $existing = @($store.transportSessions | Where-Object sessionId -CEQ $inputRecord.sessionId)
2707
+ if ($existing.Count -gt 1) { Fail 'host_transport_session_duplicate' }
2708
+ if ($existing.Count -eq 1) {
2709
+ if (-not (Test-TransportSessionEqual $existing[0] $inputRecord)) {
2710
+ Fail 'host_transport_session_immutable_conflict'
2711
+ }
2712
+ } else {
2713
+ $sameOwner = @($store.transportSessions | Where-Object {
2714
+ [string]$_.principalId -ceq [string]$inputRecord.principalId -and
2715
+ [string]$_.hostInstallationId -ceq [string]$inputRecord.hostInstallationId
2716
+ })
2717
+ if ($sameOwner.Count -gt 0) { Fail 'host_transport_session_ambiguous' }
2718
+ $store.transportSessions = @($store.transportSessions) + @($inputRecord)
2719
+ Write-Store $path $store $root $sid
2720
+ }
2721
+ Write-Result ([PSCustomObject]@{ record = $inputRecord })
2722
+ }
2723
+ 'ReplaceTransportSession' {
2724
+ if ($Timestamp -lt 0) { Fail 'host_timestamp_invalid' }
2725
+ $credential = @($store.transportCredentials | Where-Object credentialId -CEQ $inputRecord.credentialId)
2726
+ if ($credential.Count -ne 1 -or [string]$credential[0].state -ne 'active') {
2727
+ Fail 'host_transport_credential_inactive'
2728
+ }
2729
+ if (
2730
+ [string]$credential[0].principalId -cne [string]$inputRecord.principalId -or
2731
+ [string]$credential[0].hostInstallationId -cne [string]$inputRecord.hostInstallationId -or
2732
+ [int64]$credential[0].expiresAt -le $Timestamp -or
2733
+ [int64]$inputRecord.expiresAt -gt [int64]$credential[0].expiresAt
2734
+ ) { Fail 'host_transport_session_credential_mismatch' }
2735
+ if (Test-RetiredTransportSession $store $root ([string]$inputRecord.sessionId)) {
2736
+ Fail 'host_transport_session_retired'
2737
+ }
2738
+ $sameId = @($store.transportSessions | Where-Object sessionId -CEQ $inputRecord.sessionId)
2739
+ if ($sameId.Count -gt 0 -and -not (Test-TransportSessionEqual $sameId[0] $inputRecord)) {
2740
+ Fail 'host_transport_session_immutable_conflict'
2741
+ }
2742
+ if ($sameId.Count -eq 0) {
2743
+ $old = @($store.transportSessions | Where-Object {
2744
+ [string]$_.principalId -ceq [string]$inputRecord.principalId -and
2745
+ [string]$_.hostInstallationId -ceq [string]$inputRecord.hostInstallationId
2746
+ })
2747
+ $store.retiredTransportSessionIds = @(
2748
+ @($store.retiredTransportSessionIds) + @($old | ForEach-Object sessionId) |
2749
+ Sort-Object -Unique
2750
+ )
2751
+ $store.transportSessions = @($store.transportSessions | Where-Object {
2752
+ -not (
2753
+ [string]$_.principalId -ceq [string]$inputRecord.principalId -and
2754
+ [string]$_.hostInstallationId -ceq [string]$inputRecord.hostInstallationId
2755
+ )
2756
+ }) + @($inputRecord)
2757
+ Write-Store $path $store $root $sid
2758
+ }
2759
+ Write-Result ([PSCustomObject]@{ record = $inputRecord })
2760
+ }
2761
+ 'LoadTransportSession' {
2762
+ Assert-QueryOwner
2763
+ if ($SessionId -notmatch $TransportSessionIdPattern) { Fail 'host_transport_session_id_invalid' }
2764
+ if ($Timestamp -lt 0) { Fail 'host_timestamp_invalid' }
2765
+ $records = @($store.transportSessions | Where-Object sessionId -CEQ $SessionId)
2766
+ if ($records.Count -ne 1) { Fail 'host_transport_session_invalid' }
2767
+ $record = $records[0]
2768
+ if (
2769
+ [string]$record.principalId -cne $PrincipalId -or
2770
+ [string]$record.hostInstallationId -cne $HostInstallationId
2771
+ ) { Fail 'host_transport_session_owner_mismatch' }
2772
+ $credential = @($store.transportCredentials | Where-Object credentialId -CEQ $record.credentialId)
2773
+ if (
2774
+ $credential.Count -ne 1 -or [string]$credential[0].state -ne 'active' -or
2775
+ [int64]$credential[0].expiresAt -le $Timestamp
2776
+ ) { Fail 'host_transport_credential_inactive' }
2777
+ if ([int64]$record.expiresAt -le $Timestamp) { Fail 'host_transport_session_expired' }
2778
+ Write-Result ([PSCustomObject]@{ record = $record })
2779
+ }
2780
+ 'RemoveTransportSession' {
2781
+ Assert-QueryOwner
2782
+ if ($SessionId -notmatch $TransportSessionIdPattern) { Fail 'host_transport_session_id_invalid' }
2783
+ $records = @($store.transportSessions | Where-Object sessionId -CEQ $SessionId)
2784
+ if ($records.Count -gt 0 -and (
2785
+ [string]$records[0].principalId -cne $PrincipalId -or
2786
+ [string]$records[0].hostInstallationId -cne $HostInstallationId
2787
+ )) { Fail 'host_transport_session_owner_mismatch' }
2788
+ $removed = $records.Count -eq 1
2789
+ if ($removed) {
2790
+ $store.transportSessions = @($store.transportSessions | Where-Object sessionId -CNE $SessionId)
2791
+ $store.retiredTransportSessionIds = @(
2792
+ @($store.retiredTransportSessionIds) + @($SessionId) | Sort-Object -Unique
2793
+ )
2794
+ Write-Store $path $store $root $sid
2795
+ }
2796
+ Write-Result ([PSCustomObject]@{ sessionId = $SessionId; removed = $removed })
2797
+ }
2798
+ 'ResolveTransport' {
2799
+ if ($Timestamp -lt 0) { Fail 'host_timestamp_invalid' }
2800
+ $credential = Get-ActiveTransportCredential $store $Timestamp
2801
+ $sessions = @($store.transportSessions | Where-Object {
2802
+ [string]$_.credentialId -ceq [string]$credential.credentialId -and
2803
+ [string]$_.principalId -ceq $PrincipalId -and
2804
+ [string]$_.hostInstallationId -ceq $HostInstallationId
2805
+ })
2806
+ if ($sessions.Count -gt 1) { Fail 'host_transport_session_ambiguous' }
2807
+ $session = $null
2808
+ if ($sessions.Count -eq 1) {
2809
+ if ([int64]$sessions[0].expiresAt -le $Timestamp) {
2810
+ $store.transportSessions = @($store.transportSessions | Where-Object sessionId -CNE $sessions[0].sessionId)
2811
+ $store.retiredTransportSessionIds = @(
2812
+ @($store.retiredTransportSessionIds) + @($sessions[0].sessionId) | Sort-Object -Unique
2813
+ )
2814
+ Write-Store $path $store $root $sid
2815
+ } else {
2816
+ $session = $sessions[0]
2817
+ }
2818
+ }
2819
+ Write-Result ([PSCustomObject]@{ credential = $credential; session = $session })
2820
+ }
2821
+ 'VerifyTransport' {
2822
+ if ($Timestamp -lt 0) { Fail 'host_timestamp_invalid' }
2823
+ $credential = Get-ActiveTransportCredential $store $Timestamp
2824
+ $sessions = @($store.transportSessions | Where-Object {
2825
+ [string]$_.credentialId -ceq [string]$credential.credentialId -and
2826
+ [string]$_.principalId -ceq $PrincipalId -and
2827
+ [string]$_.hostInstallationId -ceq $HostInstallationId
2828
+ })
2829
+ if ($sessions.Count -gt 1) { Fail 'host_transport_session_ambiguous' }
2830
+ $session = $null
2831
+ if ($sessions.Count -eq 1) {
2832
+ if ([int64]$sessions[0].expiresAt -gt $Timestamp) {
2833
+ $session = [PSCustomObject]@{
2834
+ sessionId = [string]$sessions[0].sessionId
2835
+ credentialId = [string]$sessions[0].credentialId
2836
+ principalId = [string]$sessions[0].principalId
2837
+ hostInstallationId = [string]$sessions[0].hostInstallationId
2838
+ expiresAt = [int64]$sessions[0].expiresAt
2839
+ }
2840
+ }
2841
+ }
2842
+ Write-Result ([PSCustomObject]@{
2843
+ credential = [PSCustomObject]@{
2844
+ credentialId = [string]$credential.credentialId
2845
+ principalId = [string]$credential.principalId
2846
+ hostInstallationId = [string]$credential.hostInstallationId
2847
+ expiresAt = [int64]$credential.expiresAt
2848
+ }
2849
+ session = $session
2850
+ })
2851
+ }
2852
+ }
2853
+ $inputRecord = $null
2854
+ $store = $null
2855
+ } finally {
2856
+ if ($acquired) { $mutex.ReleaseMutex() }
2857
+ $mutex.Dispose()
2858
+ }
2859
+ } catch {
2860
+ $candidate = [string]$_.Exception.Message
2861
+ if ($candidate -match '^[a-z][a-z0-9_]{2,127}$') { Fail $candidate }
2862
+ if ($_.Exception -is [System.UnauthorizedAccessException]) { Fail 'host_protected_store_access_failed' }
2863
+ if ($_.Exception -is [System.IO.IOException]) { Fail 'host_protected_store_io_failed' }
2864
+ if ($_.Exception -is [System.Management.Automation.RuntimeException]) { Fail 'host_protected_store_runtime_failed' }
2865
+ Fail 'host_protected_store_operation_failed'
2866
+ }