@ccheever/exact-ibex-runtime 0.1.0

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 (161) hide show
  1. package/package.json +63 -0
  2. package/src/abort/AbortController.ts +23 -0
  3. package/src/abort/AbortSignal.ts +152 -0
  4. package/src/abort/index.ts +2 -0
  5. package/src/accessibility.ts +12 -0
  6. package/src/arraybuffer-detach.ts +109 -0
  7. package/src/base64/base64.ts +168 -0
  8. package/src/base64/index.ts +1 -0
  9. package/src/blob/Blob.ts +259 -0
  10. package/src/blob/File.ts +59 -0
  11. package/src/blob/FormData.ts +323 -0
  12. package/src/blob/index.ts +3 -0
  13. package/src/bootstrap.ts +1946 -0
  14. package/src/broadcast/BroadcastChannel.ts +280 -0
  15. package/src/broadcast/index.ts +5 -0
  16. package/src/cache/Cache.ts +349 -0
  17. package/src/cache/CacheStorage.ts +89 -0
  18. package/src/cache/index.ts +27 -0
  19. package/src/camera/index.ts +6202 -0
  20. package/src/camera/processor.worker.ts +194 -0
  21. package/src/camera/scene.ts +195 -0
  22. package/src/clipboard/Clipboard.ts +129 -0
  23. package/src/clipboard/ClipboardItem.ts +97 -0
  24. package/src/clipboard/index.ts +6 -0
  25. package/src/clone/index.ts +1 -0
  26. package/src/clone/structuredClone.ts +389 -0
  27. package/src/clone/transferableSymbols.ts +2 -0
  28. package/src/compression/CompressionStream.ts +146 -0
  29. package/src/compression/DecompressionStream.ts +342 -0
  30. package/src/compression/index.ts +4 -0
  31. package/src/console/Console.ts +341 -0
  32. package/src/console/index.ts +2 -0
  33. package/src/core/accessibility-state.ts +263 -0
  34. package/src/core/accessibility.ts +184 -0
  35. package/src/core/agent-state.ts +37 -0
  36. package/src/core/diagnostics-logs.ts +144 -0
  37. package/src/core/host-call-bridge.ts +16 -0
  38. package/src/core/i18n-helpers.ts +189 -0
  39. package/src/core/locale-state.ts +253 -0
  40. package/src/core/locale.ts +95 -0
  41. package/src/crypto/Crypto.ts +2743 -0
  42. package/src/crypto/index.ts +1 -0
  43. package/src/diagnostics/logs.ts +7 -0
  44. package/src/encoding/TextDecoder.ts +1181 -0
  45. package/src/encoding/TextDecoderStream.ts +58 -0
  46. package/src/encoding/TextEncoder.ts +180 -0
  47. package/src/encoding/TextEncoderStream.ts +39 -0
  48. package/src/encoding/index.ts +8 -0
  49. package/src/events/CloseEvent.ts +91 -0
  50. package/src/events/DOMException.ts +409 -0
  51. package/src/events/ErrorEvent.ts +39 -0
  52. package/src/events/Event.ts +151 -0
  53. package/src/events/EventTarget.ts +280 -0
  54. package/src/events/FocusEvent.ts +27 -0
  55. package/src/events/KeyboardEvent.ts +46 -0
  56. package/src/events/MessageEvent.ts +61 -0
  57. package/src/events/ProgressEvent.ts +33 -0
  58. package/src/events/PromiseRejectionEvent.ts +31 -0
  59. package/src/events/index.ts +52 -0
  60. package/src/eventsource/EventSource.ts +371 -0
  61. package/src/eventsource/index.ts +2 -0
  62. package/src/fetch/Headers.ts +642 -0
  63. package/src/fetch/Request.ts +760 -0
  64. package/src/fetch/Response.ts +543 -0
  65. package/src/fetch/body.ts +1256 -0
  66. package/src/fetch/cookie-jar.ts +566 -0
  67. package/src/fetch/demo.ts +207 -0
  68. package/src/fetch/errors.ts +101 -0
  69. package/src/fetch/fetch.ts +2610 -0
  70. package/src/fetch/index.ts +101 -0
  71. package/src/fetch/native-bridge.ts +65 -0
  72. package/src/fetch/types.ts +258 -0
  73. package/src/filereader/FileReader.ts +236 -0
  74. package/src/filereader/index.ts +1 -0
  75. package/src/fs/Dirent.ts +39 -0
  76. package/src/fs/ExactFile.ts +450 -0
  77. package/src/fs/Stats.ts +80 -0
  78. package/src/fs/index.ts +944 -0
  79. package/src/fs/promises.ts +386 -0
  80. package/src/fs/shared.ts +328 -0
  81. package/src/http-server/index.js +697 -0
  82. package/src/http-server/index.ts +27 -0
  83. package/src/identity.generated.ts +14 -0
  84. package/src/index.ts +283 -0
  85. package/src/indexeddb/IDBCursor.ts +188 -0
  86. package/src/indexeddb/IDBDatabase.ts +343 -0
  87. package/src/indexeddb/IDBFactory.ts +269 -0
  88. package/src/indexeddb/IDBIndex.ts +194 -0
  89. package/src/indexeddb/IDBKeyRange.ts +109 -0
  90. package/src/indexeddb/IDBObjectStore.ts +468 -0
  91. package/src/indexeddb/IDBRequest.ts +163 -0
  92. package/src/indexeddb/IDBTransaction.ts +207 -0
  93. package/src/indexeddb/index.ts +34 -0
  94. package/src/indexeddb/utils.ts +52 -0
  95. package/src/inspect/index.ts +1 -0
  96. package/src/inspect/inspect.ts +465 -0
  97. package/src/internal/detect.ts +104 -0
  98. package/src/locale.ts +10 -0
  99. package/src/location/index.ts +1059 -0
  100. package/src/locks/LockManager.ts +460 -0
  101. package/src/locks/index.ts +12 -0
  102. package/src/media/VideoFrame.ts +58 -0
  103. package/src/messaging/MessageChannel.ts +31 -0
  104. package/src/messaging/MessagePort.ts +180 -0
  105. package/src/messaging/index.ts +2 -0
  106. package/src/messaging.ts +247 -0
  107. package/src/native/NativeModules.ts +354 -0
  108. package/src/native/index.ts +1 -0
  109. package/src/navigator/Navigator.ts +351 -0
  110. package/src/navigator/index.ts +1 -0
  111. package/src/node/Buffer.ts +1786 -0
  112. package/src/node/index.ts +4 -0
  113. package/src/node/path.ts +495 -0
  114. package/src/node/process.ts +2528 -0
  115. package/src/performance/Performance.ts +532 -0
  116. package/src/performance/index.ts +21 -0
  117. package/src/polyfills/array.ts +236 -0
  118. package/src/polyfills/arraybuffer.ts +172 -0
  119. package/src/polyfills/groupby.ts +85 -0
  120. package/src/polyfills/index.ts +85 -0
  121. package/src/polyfills/intl.ts +1956 -0
  122. package/src/polyfills/iterator.ts +479 -0
  123. package/src/polyfills/promise.ts +37 -0
  124. package/src/polyfills/set.ts +245 -0
  125. package/src/polyfills/string.ts +85 -0
  126. package/src/polyfills/typedarray.ts +110 -0
  127. package/src/promise-rejection-tracking.ts +464 -0
  128. package/src/react-native/index.ts +388 -0
  129. package/src/runtime-entry.ts +55 -0
  130. package/src/scheduling/AnimationFrame.ts +105 -0
  131. package/src/scheduling/IdleCallback.ts +167 -0
  132. package/src/scheduling/index.ts +13 -0
  133. package/src/security/Capabilities.ts +1146 -0
  134. package/src/security/Permissions.ts +392 -0
  135. package/src/security/capability-bits.generated.ts +63 -0
  136. package/src/security/index.ts +16 -0
  137. package/src/sqlite/Database.ts +456 -0
  138. package/src/sqlite/Statement.ts +206 -0
  139. package/src/sqlite/constants.ts +79 -0
  140. package/src/sqlite/errors.ts +25 -0
  141. package/src/sqlite/index.ts +34 -0
  142. package/src/sqlite/module.js +438 -0
  143. package/src/storage/Storage.ts +291 -0
  144. package/src/storage/StorageManager.ts +91 -0
  145. package/src/storage/index.ts +3 -0
  146. package/src/stream-compat.ts +47 -0
  147. package/src/streams/ReadableStream.ts +4131 -0
  148. package/src/streams/TransformStream.ts +375 -0
  149. package/src/streams/WritableStream.ts +866 -0
  150. package/src/streams/index.ts +41 -0
  151. package/src/timers/Timers.ts +296 -0
  152. package/src/timers/index.ts +11 -0
  153. package/src/url/URL.ts +656 -0
  154. package/src/url/URLPattern.ts +850 -0
  155. package/src/url/URLSearchParams.ts +244 -0
  156. package/src/url/index.ts +9 -0
  157. package/src/websocket/WebSocket.ts +770 -0
  158. package/src/websocket/WebSocketError.ts +52 -0
  159. package/src/websocket/WebSocketStream.ts +628 -0
  160. package/src/websocket/index.ts +7 -0
  161. package/src/window/index.ts +872 -0
@@ -0,0 +1,1146 @@
1
+ /**
2
+ * Capability Security System for Ibex Runtime
3
+ *
4
+ * Implements the capability-based security model as defined in JS_RUNTIME_SECURITY.md.
5
+ *
6
+ * The effective permission for any privileged action is the intersection of:
7
+ * 1. OS Permission (root) - enforced at native layer
8
+ * 2. App Root Capabilities - set when runtime is created
9
+ * 3. View Broker Grant - per-view user consent
10
+ * 4. Module Import Capabilities - per-module grants
11
+ *
12
+ * @see JS_RUNTIME_SECURITY.md
13
+ * @see JS_CAPABILITY_SECURITY_MODEL_SPEC_AND_PLAN.md
14
+ */
15
+
16
+ import {
17
+ CapabilityDeniedError,
18
+ createCapabilityDeniedError,
19
+ type CapabilityDenialReason,
20
+ } from '../events/DOMException';
21
+ import { CapabilityBit } from './capability-bits.generated';
22
+
23
+ export { CapabilityBit } from './capability-bits.generated';
24
+
25
+ // ============================================================================
26
+ // Capability Types
27
+ // ============================================================================
28
+
29
+ /**
30
+ * Capability categories aligned with JS_RUNTIME_SECURITY.md Section 5
31
+ */
32
+ export type CapabilityCategory =
33
+ | 'fs'
34
+ | 'network'
35
+ | 'env'
36
+ | 'process'
37
+ | 'ipc'
38
+ | 'crypto'
39
+ | 'time'
40
+ | 'device'
41
+ | 'storage'
42
+ | 'clipboard'
43
+ | 'sqlite';
44
+
45
+ /**
46
+ * Full capability specification with optional parameters.
47
+ * Examples:
48
+ * - "network:fetch" - general fetch capability
49
+ * - "network:fetch:api.example.com" - fetch to specific host
50
+ * - "fs:read:/data" - read from specific path
51
+ * - "device:location" - device location access
52
+ */
53
+ export type Capability = string;
54
+
55
+ /**
56
+ * Denial reasons aligned with JS_CAPABILITY_SECURITY_MODEL_SPEC_AND_PLAN.md
57
+ *
58
+ * Note: This is intentionally a string type (not a union) to allow for
59
+ * forward compatibility with new denial reasons from future OS versions.
60
+ * Use DenialReasonCategory for switch statements to handle unknown reasons.
61
+ */
62
+ export type DenialReason =
63
+ | 'os_denied' // User denied at OS level
64
+ | 'os_restricted' // System policy (parental controls, MDM)
65
+ | 'broker_denied' // View broker denied
66
+ | 'module_not_granted' // Import capability missing
67
+ | 'app_not_granted' // App root capability missing
68
+ | 'requires_settings' // User must enable in Settings
69
+ | 'capability_unknown' // Capability not recognized by runtime
70
+ | 'os_version_too_low' // OS version doesn't support this capability
71
+ | 'temporarily_unavailable' // Capability temporarily unavailable (e.g., airplane mode)
72
+ | (string & {}); // Allow unknown reasons for forward compatibility
73
+
74
+ /**
75
+ * Broad categories for denial reasons.
76
+ * Use this for switch statements to handle unknown specific reasons gracefully.
77
+ *
78
+ * @example
79
+ * ```ts
80
+ * const category = getDenialReasonCategory(error.denialReason);
81
+ * switch (category) {
82
+ * case 'user_action_required':
83
+ * // Show UI to guide user to settings
84
+ * break;
85
+ * case 'permanently_unavailable':
86
+ * // Hide the feature entirely
87
+ * break;
88
+ * case 'temporarily_unavailable':
89
+ * // Show retry option
90
+ * break;
91
+ * default:
92
+ * // Generic "permission denied" message
93
+ * }
94
+ * ```
95
+ */
96
+ export type DenialReasonCategory =
97
+ | 'user_action_required' // User can fix by granting permission
98
+ | 'system_restricted' // System policy prevents access
99
+ | 'not_configured' // App/module hasn't been granted capability
100
+ | 'temporarily_unavailable' // May work later (network issues, etc.)
101
+ | 'permanently_unavailable' // Will never work (OS too old, etc.)
102
+ | 'unknown'; // Unrecognized reason
103
+
104
+ /**
105
+ * Map a denial reason to its broad category for forward-compatible handling.
106
+ */
107
+ export function getDenialReasonCategory(reason: DenialReason): DenialReasonCategory {
108
+ switch (reason) {
109
+ case 'os_denied':
110
+ case 'requires_settings':
111
+ return 'user_action_required';
112
+
113
+ case 'os_restricted':
114
+ return 'system_restricted';
115
+
116
+ case 'broker_denied':
117
+ case 'module_not_granted':
118
+ case 'app_not_granted':
119
+ return 'not_configured';
120
+
121
+ case 'temporarily_unavailable':
122
+ return 'temporarily_unavailable';
123
+
124
+ case 'capability_unknown':
125
+ case 'os_version_too_low':
126
+ return 'permanently_unavailable';
127
+
128
+ default:
129
+ // Forward compatibility: unknown reasons default to 'unknown'
130
+ // This allows new OS-specific reasons to be handled gracefully
131
+ return 'unknown';
132
+ }
133
+ }
134
+
135
+ /**
136
+ * Capability check result with reason for denials
137
+ */
138
+ export interface CapabilityCheckResult {
139
+ allowed: boolean;
140
+ reason?: DenialReason;
141
+ capability: Capability;
142
+ /** Whether the capability can be requested again */
143
+ canRequestAgain?: boolean;
144
+ }
145
+
146
+ /**
147
+ * Audit log event for capability checks
148
+ */
149
+ export interface CapabilityAuditEvent {
150
+ event: 'capability_granted' | 'capability_denied';
151
+ /** The action being performed */
152
+ action: 'check' | 'request' | 'use';
153
+ capability: Capability;
154
+ moduleId?: number;
155
+ source: 'os' | 'broker' | 'module' | 'app';
156
+ reason?: string;
157
+ timestamp: number;
158
+ }
159
+
160
+ // ============================================================================
161
+ // Native Capability Module Interface
162
+ // ============================================================================
163
+
164
+ /**
165
+ * Interface for the native capability checking module.
166
+ * The native layer is the authoritative source for capability enforcement.
167
+ */
168
+ export interface NativeCapabilityModule {
169
+ /**
170
+ * Check if a capability is granted.
171
+ * This should verify all layers: OS, app root, broker, and module.
172
+ */
173
+ checkCapability(capability: Capability, moduleId?: number): boolean;
174
+
175
+ /**
176
+ * Request a capability from the broker (may trigger user prompt).
177
+ * Returns true if granted, false if denied.
178
+ */
179
+ requestCapability(capability: Capability): Promise<boolean>;
180
+
181
+ /**
182
+ * Get the current capability grants for the view.
183
+ */
184
+ getGrantedCapabilities(): Capability[];
185
+
186
+ /**
187
+ * Register an audit callback for capability events.
188
+ */
189
+ onAuditEvent?(callback: (event: CapabilityAuditEvent) => void): void;
190
+ }
191
+
192
+ // ============================================================================
193
+ // Capability Registry
194
+ // ============================================================================
195
+
196
+ let _nativeCapabilityModule: NativeCapabilityModule | null = null;
197
+ let _auditCallbacks: Array<(event: CapabilityAuditEvent) => void> = [];
198
+
199
+ // In-memory fallback for testing (grants everything)
200
+ let _testMode = false;
201
+ let _testGrants = new Set<Capability>();
202
+ let _silentMode = false;
203
+
204
+ // Strict mode - when enabled, denies capabilities without native module
205
+ // Default is lax mode (allows all) to avoid friction during development
206
+ let _strictMode = false;
207
+
208
+ // Track capabilities we've warned about to reduce console noise
209
+ const _warnedCapabilities = new Set<Capability>();
210
+
211
+ /**
212
+ * Check if we should suppress security warnings (for tests).
213
+ */
214
+ export function isSilentMode(): boolean {
215
+ return _silentMode;
216
+ }
217
+
218
+ /**
219
+ * Check if strict capability enforcement is enabled.
220
+ */
221
+ export function isStrictMode(): boolean {
222
+ return _strictMode;
223
+ }
224
+
225
+ /**
226
+ * Enable strict capability enforcement.
227
+ *
228
+ * When strict mode is enabled, capability checks will DENY requests
229
+ * when no native capability module is available. This is the secure
230
+ * behavior for production apps.
231
+ *
232
+ * By default, the capability system is LAX - it allows all capabilities
233
+ * when no native module is present, to reduce friction during development.
234
+ *
235
+ * Call this during app initialization for production builds:
236
+ * ```ts
237
+ * import { enableStrictMode } from '@exact/runtime/security';
238
+ *
239
+ * if (__PROD__) {
240
+ * enableStrictMode();
241
+ * }
242
+ * ```
243
+ *
244
+ * The native layer should call this automatically for release builds.
245
+ */
246
+ export function enableStrictMode(): void {
247
+ _strictMode = true;
248
+ }
249
+
250
+ /**
251
+ * Disable strict capability enforcement (return to lax mode).
252
+ * Primarily useful for testing.
253
+ */
254
+ export function disableStrictMode(): void {
255
+ _strictMode = false;
256
+ }
257
+
258
+ /**
259
+ * Set the native capability module.
260
+ * Called by the native layer during runtime initialization.
261
+ */
262
+ export function setNativeCapabilityModule(module: NativeCapabilityModule): void {
263
+ _nativeCapabilityModule = module;
264
+
265
+ // Register audit callback forwarding
266
+ if (module.onAuditEvent) {
267
+ module.onAuditEvent((event) => {
268
+ _auditCallbacks.forEach(cb => {
269
+ try {
270
+ cb(event);
271
+ } catch (e) {
272
+ console.error('Capability audit callback error:', e);
273
+ }
274
+ });
275
+ });
276
+ }
277
+ }
278
+
279
+ /**
280
+ * Get the native capability module.
281
+ */
282
+ export function getNativeCapabilityModule(): NativeCapabilityModule | null {
283
+ return _nativeCapabilityModule;
284
+ }
285
+
286
+ /**
287
+ * Options for test mode.
288
+ */
289
+ export interface TestModeOptions {
290
+ /** Capability grants to pre-configure */
291
+ grants?: Capability[];
292
+ /** Suppress security warnings in console (default: true) */
293
+ silent?: boolean;
294
+ }
295
+
296
+ /**
297
+ * Enable test mode with specified grants.
298
+ * WARNING: Only use in test environments!
299
+ *
300
+ * @param grantsOrOptions - Either an array of grants (legacy) or options object
301
+ */
302
+ export function enableTestMode(grantsOrOptions: Capability[] | TestModeOptions = {}): void {
303
+ if (_strictMode) {
304
+ throw new Error('Cannot enable capability test mode when strict mode is enabled');
305
+ }
306
+
307
+ // Handle legacy array signature
308
+ if (Array.isArray(grantsOrOptions)) {
309
+ _testMode = true;
310
+ _testGrants = new Set(grantsOrOptions);
311
+ _silentMode = true; // Default to silent for legacy callers (tests)
312
+ return;
313
+ }
314
+
315
+ const options = grantsOrOptions;
316
+ _testMode = true;
317
+ _testGrants = new Set(options.grants ?? []);
318
+ _silentMode = options.silent ?? true;
319
+ }
320
+
321
+ /**
322
+ * Disable test mode.
323
+ */
324
+ export function disableTestMode(): void {
325
+ _testMode = false;
326
+ _testGrants.clear();
327
+ _silentMode = false;
328
+ }
329
+
330
+ /**
331
+ * Add a capability grant in test mode.
332
+ */
333
+ export function addTestGrant(capability: Capability): void {
334
+ if (!_testMode) {
335
+ throw new Error('Test mode not enabled');
336
+ }
337
+ _testGrants.add(capability);
338
+ }
339
+
340
+ // ============================================================================
341
+ // Capability Checking
342
+ // ============================================================================
343
+
344
+ /**
345
+ * Check if a capability is granted.
346
+ *
347
+ * @param capability The capability to check (e.g., "network:fetch", "device:location")
348
+ * @param moduleId Optional module ID for per-module checks
349
+ * @returns CapabilityCheckResult with allowed status and denial reason
350
+ */
351
+ export function checkCapability(capability: Capability, moduleId?: number): CapabilityCheckResult {
352
+ // Fast path: if moduleId provided and we have a cached bitmask
353
+ if (moduleId !== undefined && !_testMode) {
354
+ const bit = getCapabilityBit(capability);
355
+ if (bit !== undefined) {
356
+ const bits = _effectiveBits.get(moduleId);
357
+ if (bits) {
358
+ const gen = _cachedGeneration.get(moduleId);
359
+ if (gen === _permissionGeneration) {
360
+ const allowed = bit < 32
361
+ ? (bits[0] & (1 << bit)) !== 0
362
+ : (bits[1] & (1 << (bit - 32))) !== 0;
363
+ if (allowed) {
364
+ return { allowed: true, capability };
365
+ }
366
+ // Fall through to slow path for detailed denial reason
367
+ }
368
+ }
369
+ }
370
+ }
371
+
372
+ // Test mode - check in-memory grants
373
+ if (_testMode) {
374
+ const allowed = _testGrants.has(capability) ||
375
+ _testGrants.has('*') ||
376
+ _testGrants.has(capability.split(':')[0] + ':*');
377
+
378
+ logAuditEvent({
379
+ event: allowed ? 'capability_granted' : 'capability_denied',
380
+ action: 'check',
381
+ capability,
382
+ moduleId,
383
+ source: 'module',
384
+ reason: allowed ? undefined : 'Test mode: capability not in grants',
385
+ timestamp: Date.now(),
386
+ });
387
+
388
+ return {
389
+ allowed,
390
+ reason: allowed ? undefined : 'module_not_granted',
391
+ capability,
392
+ };
393
+ }
394
+
395
+ // Native module - delegate to native layer
396
+ if (_nativeCapabilityModule) {
397
+ const allowed = _nativeCapabilityModule.checkCapability(capability, moduleId);
398
+ return {
399
+ allowed,
400
+ reason: allowed ? undefined : 'module_not_granted',
401
+ capability,
402
+ };
403
+ }
404
+
405
+ // No native module and not in test mode
406
+ // Behavior depends on strict mode:
407
+ // - Strict mode (opt-in): deny all - secure for production
408
+ // - Lax mode (default): allow all - friendly for development
409
+
410
+ if (_strictMode) {
411
+ if (!_silentMode) {
412
+ console.warn(`[Security] Capability denied (strict mode): ${capability}`);
413
+ }
414
+ return {
415
+ allowed: false,
416
+ reason: 'app_not_granted',
417
+ capability,
418
+ };
419
+ }
420
+
421
+ // Lax mode - allow with warning (only warn once per capability to reduce noise)
422
+ if (!_silentMode && !_warnedCapabilities.has(capability)) {
423
+ _warnedCapabilities.add(capability);
424
+ console.warn(
425
+ `[Security] No capability module available. ` +
426
+ `Allowing "${capability}" in lax mode. ` +
427
+ `Call enableStrictMode() for production security.`
428
+ );
429
+ }
430
+
431
+ return {
432
+ allowed: true,
433
+ capability,
434
+ };
435
+ }
436
+
437
+ /**
438
+ * Require a capability, throwing CapabilityDeniedError if not granted.
439
+ * Use this for synchronous capability checks.
440
+ *
441
+ * @param capability The capability to require
442
+ * @param moduleId Optional module ID
443
+ * @throws CapabilityDeniedError if capability is denied
444
+ */
445
+ export function requireCapability(capability: Capability, moduleId?: number): void {
446
+ const result = checkCapability(capability, moduleId);
447
+ if (!result.allowed) {
448
+ throw new CapabilityDeniedError(
449
+ capability,
450
+ result.reason,
451
+ result.canRequestAgain,
452
+ );
453
+ }
454
+ }
455
+
456
+ /**
457
+ * Request a capability from the broker.
458
+ * This may trigger a user permission prompt.
459
+ *
460
+ * @param capability The capability to request
461
+ * @returns Promise resolving to true if granted, false if denied
462
+ */
463
+ export async function requestCapability(capability: Capability): Promise<boolean> {
464
+ // Test mode
465
+ if (_testMode) {
466
+ return _testGrants.has(capability) ||
467
+ _testGrants.has('*') ||
468
+ _testGrants.has(capability.split(':')[0] + ':*');
469
+ }
470
+
471
+ // Native module
472
+ if (_nativeCapabilityModule) {
473
+ return _nativeCapabilityModule.requestCapability(capability);
474
+ }
475
+
476
+ // No native module - behavior depends on strict mode
477
+ return !_strictMode;
478
+ }
479
+
480
+ /**
481
+ * Get all currently granted capabilities.
482
+ */
483
+ export function getGrantedCapabilities(): Capability[] {
484
+ if (_testMode) {
485
+ return Array.from(_testGrants);
486
+ }
487
+
488
+ if (_nativeCapabilityModule) {
489
+ return _nativeCapabilityModule.getGrantedCapabilities();
490
+ }
491
+
492
+ return [];
493
+ }
494
+
495
+ // ============================================================================
496
+ // Audit Logging
497
+ // ============================================================================
498
+
499
+ /**
500
+ * Register a callback for capability audit events.
501
+ *
502
+ * @param callback Function called for each audit event
503
+ * @returns Unsubscribe function
504
+ */
505
+ export function onCapabilityAudit(
506
+ callback: (event: CapabilityAuditEvent) => void
507
+ ): () => void {
508
+ _auditCallbacks.push(callback);
509
+ return () => {
510
+ const index = _auditCallbacks.indexOf(callback);
511
+ if (index !== -1) {
512
+ _auditCallbacks.splice(index, 1);
513
+ }
514
+ };
515
+ }
516
+
517
+ /**
518
+ * Log an audit event.
519
+ * Called internally and can be called by native layer.
520
+ */
521
+ export function logAuditEvent(event: CapabilityAuditEvent): void {
522
+ _auditCallbacks.forEach(cb => {
523
+ try {
524
+ cb(event);
525
+ } catch (e) {
526
+ console.error('Capability audit callback error:', e);
527
+ }
528
+ });
529
+ }
530
+
531
+ // ============================================================================
532
+ // Capability Introspection (Future-Proofing)
533
+ // ============================================================================
534
+
535
+ /**
536
+ * Permission status for a capability.
537
+ * Mirrors common OS permission states.
538
+ */
539
+ export type CapabilityStatus =
540
+ | 'granted' // Permission is granted
541
+ | 'denied' // Permission was explicitly denied
542
+ | 'not_determined' // User hasn't been asked yet
543
+ | 'restricted' // System policy prevents access
544
+ | 'limited' // Partial access (e.g., limited photo selection)
545
+ | 'unknown'; // Status cannot be determined
546
+
547
+ /**
548
+ * Detailed information about a capability.
549
+ * Use this to understand what a capability is and how to handle it.
550
+ */
551
+ export interface CapabilityInfo {
552
+ /** The capability string */
553
+ capability: Capability;
554
+
555
+ /** Whether this capability is recognized by the runtime */
556
+ exists: boolean;
557
+
558
+ /** Current permission status */
559
+ status: CapabilityStatus;
560
+
561
+ /** Whether the app can request this capability (show permission prompt) */
562
+ canRequest: boolean;
563
+
564
+ /** Whether the capability is currently usable */
565
+ isUsable: boolean;
566
+
567
+ /** Minimum OS version required (e.g., "iOS 14.0", "Android 13") */
568
+ minimumOSVersion?: string;
569
+
570
+ /** Current OS version */
571
+ currentOSVersion?: string;
572
+
573
+ /** If deprecated, what capability replaces this one */
574
+ replacedBy?: Capability[];
575
+
576
+ /** If this is a new capability, what it replaces */
577
+ replaces?: Capability[];
578
+
579
+ /** Human-readable description of what this capability grants */
580
+ description?: string;
581
+
582
+ /** URL to open system settings for this capability */
583
+ settingsUrl?: string;
584
+
585
+ /** Additional platform-specific metadata */
586
+ metadata?: Record<string, unknown>;
587
+ }
588
+
589
+ /**
590
+ * Information about the capability system itself.
591
+ */
592
+ export interface CapabilitySystemInfo {
593
+ /** Version of the capability API */
594
+ apiVersion: string;
595
+
596
+ /** List of all known capabilities */
597
+ knownCapabilities: Capability[];
598
+
599
+ /** Operating system name */
600
+ osName: 'ios' | 'android' | 'web' | 'windows' | 'macos' | 'unknown';
601
+
602
+ /** Operating system version */
603
+ osVersion: string;
604
+
605
+ /** Ibex runtime version */
606
+ runtimeVersion: string;
607
+ }
608
+
609
+ /**
610
+ * Extended native module interface for introspection.
611
+ * Native implementations should implement these for full future-proofing support.
612
+ */
613
+ export interface NativeCapabilityModuleV2 extends NativeCapabilityModule {
614
+ /** Get detailed information about a capability */
615
+ getCapabilityInfo?(capability: Capability): CapabilityInfo;
616
+
617
+ /** Get system information */
618
+ getSystemInfo?(): CapabilitySystemInfo;
619
+
620
+ /** Get capabilities required by an API */
621
+ getRequiredCapabilities?(apiName: string): Capability[];
622
+
623
+ /** Check if a capability exists (is known to the system) */
624
+ capabilityExists?(capability: Capability): boolean;
625
+ }
626
+
627
+ // Current API version - bump when making breaking changes
628
+ const CAPABILITY_API_VERSION = '1.0.0';
629
+
630
+ /**
631
+ * Get detailed information about a capability.
632
+ *
633
+ * Use this to:
634
+ * - Check if a capability exists before using it
635
+ * - Determine if user can be prompted for permission
636
+ * - Get the settings URL to direct users to enable permission
637
+ * - Check OS version requirements
638
+ *
639
+ * @example
640
+ * ```ts
641
+ * const info = getCapabilityInfo('device:location:precise');
642
+ * if (!info.exists) {
643
+ * // Fall back to coarse location
644
+ * const coarseInfo = getCapabilityInfo('device:location');
645
+ * // ...
646
+ * } else if (info.status === 'denied' && !info.canRequest) {
647
+ * // Direct user to settings
648
+ * showSettingsPrompt(info.settingsUrl);
649
+ * }
650
+ * ```
651
+ */
652
+ export function getCapabilityInfo(capability: Capability): CapabilityInfo {
653
+ // Check if native module supports introspection
654
+ const module = _nativeCapabilityModule as NativeCapabilityModuleV2 | null;
655
+ if (module?.getCapabilityInfo) {
656
+ return module.getCapabilityInfo(capability);
657
+ }
658
+
659
+ // Fallback implementation for testing and development
660
+ const isKnown = isKnownCapability(capability);
661
+ const checkResult = checkCapability(capability);
662
+
663
+ return {
664
+ capability,
665
+ exists: isKnown,
666
+ status: checkResult.allowed ? 'granted' : 'not_determined',
667
+ canRequest: isKnown && !checkResult.allowed,
668
+ isUsable: checkResult.allowed,
669
+ description: getCapabilityDescription(capability),
670
+ };
671
+ }
672
+
673
+ /**
674
+ * Get information about the capability system.
675
+ */
676
+ export function getCapabilitySystemInfo(): CapabilitySystemInfo {
677
+ const module = _nativeCapabilityModule as NativeCapabilityModuleV2 | null;
678
+ if (module?.getSystemInfo) {
679
+ return module.getSystemInfo();
680
+ }
681
+
682
+ const fallback = getFallbackCapabilitySystemInfo();
683
+
684
+ // Fallback for testing
685
+ return {
686
+ apiVersion: CAPABILITY_API_VERSION,
687
+ knownCapabilities: Object.values(Capabilities),
688
+ osName: fallback.osName,
689
+ osVersion: fallback.osVersion,
690
+ runtimeVersion: fallback.runtimeVersion,
691
+ };
692
+ }
693
+
694
+ function getFallbackCapabilitySystemInfo(): Pick<
695
+ CapabilitySystemInfo,
696
+ 'osName' | 'osVersion' | 'runtimeVersion'
697
+ > {
698
+ const globalHints = globalThis as {
699
+ __exactPlatform?: unknown;
700
+ __exactPlatformVersion?: unknown;
701
+ process?: {
702
+ platform?: unknown;
703
+ version?: unknown;
704
+ __exactOSRelease?: unknown;
705
+ __exactOSVersion?: unknown;
706
+ };
707
+ };
708
+ const processHints =
709
+ globalHints.process && typeof globalHints.process === 'object'
710
+ ? globalHints.process
711
+ : undefined;
712
+ const platform = readNonEmptyString(globalHints.__exactPlatform) ??
713
+ readNonEmptyString(processHints?.platform);
714
+ const osName = normalizeCapabilityOSName(platform);
715
+
716
+ // @ref LLP 0008#os-info - Android permission diagnostics use the Java host SDK version.
717
+ const osVersion = osName === 'android'
718
+ ? readNonEmptyString(globalHints.__exactPlatformVersion) ??
719
+ readNonEmptyString(processHints?.__exactOSRelease) ??
720
+ stripAndroidVersionPrefix(readNonEmptyString(processHints?.__exactOSVersion)) ??
721
+ 'unknown'
722
+ : 'unknown';
723
+
724
+ return {
725
+ osName,
726
+ osVersion,
727
+ runtimeVersion: readNonEmptyString(processHints?.version) ?? 'development',
728
+ };
729
+ }
730
+
731
+ function readNonEmptyString(value: unknown): string | undefined {
732
+ return typeof value === 'string' && value.length > 0 ? value : undefined;
733
+ }
734
+
735
+ function stripAndroidVersionPrefix(value: string | undefined): string | undefined {
736
+ if (!value) return undefined;
737
+ return value.replace(/^Android\s+/, '');
738
+ }
739
+
740
+ function normalizeCapabilityOSName(platform: string | undefined): CapabilitySystemInfo['osName'] {
741
+ return platform === 'android' ? 'android' : 'unknown';
742
+ }
743
+
744
+ /**
745
+ * Get the capabilities required by a specific API.
746
+ *
747
+ * Use this to understand what permissions an API needs before calling it,
748
+ * allowing you to request permissions proactively or show appropriate UI.
749
+ *
750
+ * @example
751
+ * ```ts
752
+ * const required = getRequiredCapabilities('Geolocation.getCurrentPosition');
753
+ * for (const cap of required) {
754
+ * const info = getCapabilityInfo(cap);
755
+ * if (!info.isUsable && info.canRequest) {
756
+ * await requestCapability(cap);
757
+ * }
758
+ * }
759
+ * ```
760
+ */
761
+ export function getRequiredCapabilities(apiName: string): Capability[] {
762
+ const module = _nativeCapabilityModule as NativeCapabilityModuleV2 | null;
763
+ if (module?.getRequiredCapabilities) {
764
+ return module.getRequiredCapabilities(apiName);
765
+ }
766
+
767
+ // Fallback: built-in API to capability mapping
768
+ const apiCapabilityMap = getApiCapabilityMap();
769
+ return apiCapabilityMap[apiName] ?? [];
770
+ }
771
+
772
+ /**
773
+ * Check if a capability is known to the current runtime.
774
+ *
775
+ * Use this to check for capabilities that may not exist on older OS versions
776
+ * or runtime versions before attempting to use them.
777
+ */
778
+ export function isKnownCapability(capability: Capability): boolean {
779
+ const module = _nativeCapabilityModule as NativeCapabilityModuleV2 | null;
780
+ if (module?.capabilityExists) {
781
+ return module.capabilityExists(capability);
782
+ }
783
+
784
+ // Fallback: check against known capabilities
785
+ const knownValues = Object.values(Capabilities) as string[];
786
+ if (knownValues.includes(capability)) {
787
+ return true;
788
+ }
789
+
790
+ // Check if it's a parameterized version of a known capability
791
+ // e.g., "network:fetch:api.example.com" matches "network:fetch"
792
+ const { category, action } = parseCapability(capability);
793
+ const baseCapability = `${category}:${action}`;
794
+ return knownValues.includes(baseCapability);
795
+ }
796
+
797
+ /**
798
+ * Get the built-in mapping of API names to required capabilities.
799
+ * Uses a getter function to avoid circular dependency issues.
800
+ */
801
+ function getApiCapabilityMap(): Record<string, Capability[]> {
802
+ return {
803
+ // Fetch API
804
+ 'fetch': ['network:fetch'],
805
+ 'Request': ['network:fetch'],
806
+
807
+ // WebSocket
808
+ 'WebSocket': ['network:connect'],
809
+
810
+ // Storage
811
+ 'localStorage': ['storage:local'],
812
+ 'localStorage.getItem': ['storage:local'],
813
+ 'localStorage.setItem': ['storage:local'],
814
+ 'sessionStorage': ['storage:session'],
815
+
816
+ // Crypto
817
+ 'crypto.getRandomValues': ['crypto:random'],
818
+ 'crypto.randomUUID': ['crypto:random'],
819
+ 'crypto.subtle': ['crypto:subtle'],
820
+ 'crypto.subtle.encrypt': ['crypto:subtle'],
821
+ 'crypto.subtle.decrypt': ['crypto:subtle'],
822
+ 'crypto.subtle.sign': ['crypto:subtle'],
823
+ 'crypto.subtle.verify': ['crypto:subtle'],
824
+
825
+ // Geolocation
826
+ 'Geolocation.getCurrentPosition': ['device:location'],
827
+ 'Geolocation.watchPosition': ['device:location'],
828
+
829
+ // Clipboard
830
+ 'navigator.clipboard.read': ['clipboard:read'],
831
+ 'navigator.clipboard.readText': ['clipboard:read'],
832
+ 'navigator.clipboard.write': ['clipboard:write'],
833
+ 'navigator.clipboard.writeText': ['clipboard:write'],
834
+
835
+ // Notifications
836
+ 'Notification': ['device:notifications'],
837
+ 'Notification.requestPermission': ['device:notifications'],
838
+ };
839
+ }
840
+
841
+ /**
842
+ * Get a human-readable description of a capability.
843
+ */
844
+ function getCapabilityDescription(capability: Capability): string {
845
+ const descriptions: Record<string, string> = {
846
+ 'network:fetch': 'Make network requests to remote servers',
847
+ 'network:connect': 'Establish persistent network connections',
848
+ 'network:local': 'Access devices on your local network',
849
+ 'storage:local': 'Store data locally on your device',
850
+ 'storage:session': 'Store temporary session data',
851
+ 'device:location': 'Access your location',
852
+ 'device:location:precise': 'Access your precise location',
853
+ 'device:location:whenInUse': 'Access location while using the app',
854
+ 'device:location:always': 'Access location even in background',
855
+ 'device:camera': 'Access your camera',
856
+ 'device:microphone': 'Access your microphone',
857
+ 'device:photos': 'Access your photos',
858
+ 'device:contacts': 'Access your contacts',
859
+ 'device:bluetooth': 'Use Bluetooth',
860
+ 'device:biometrics': 'Use Face ID or fingerprint',
861
+ 'device:notifications': 'Send you notifications',
862
+ 'crypto:random': 'Generate random numbers',
863
+ 'crypto:subtle': 'Use cryptographic operations',
864
+ 'clipboard:read': 'Read from clipboard',
865
+ 'clipboard:write': 'Write to clipboard',
866
+ };
867
+
868
+ return descriptions[capability] ?? `Access ${capability}`;
869
+ }
870
+
871
+ // ============================================================================
872
+ // Bitset Fast Path
873
+ // ============================================================================
874
+
875
+ /**
876
+ * Get the bit position for a capability.
877
+ * For parameterized caps like "network:fetch:api.example.com",
878
+ * extracts base "network:fetch" and looks up its bit.
879
+ *
880
+ * @returns Bit position (0-55) or undefined if not a well-known capability
881
+ */
882
+ export function getCapabilityBit(capability: Capability): number | undefined {
883
+ // Fast path: direct lookup
884
+ const direct = CapabilityBit[capability];
885
+ if (direct !== undefined) return direct;
886
+
887
+ // Parameterized capability: extract base
888
+ const { category, action } = parseCapability(capability);
889
+ return CapabilityBit[`${category}:${action}`];
890
+ }
891
+
892
+ /**
893
+ * Module effective bitmasks: moduleId -> [lo32, hi32]
894
+ * Precomputed intersection of all 4 security layers for each module.
895
+ */
896
+ const _effectiveBits: Map<number, [number, number]> = new Map();
897
+
898
+ /** Generation counter for permission changes */
899
+ let _permissionGeneration = 0;
900
+
901
+ /** Per-module cached generation */
902
+ const _cachedGeneration: Map<number, number> = new Map();
903
+
904
+ /**
905
+ * Get the current permission generation counter.
906
+ * Increments whenever permissions change, invalidating cached bitmasks.
907
+ */
908
+ export function getPermissionGeneration(): number {
909
+ return _permissionGeneration;
910
+ }
911
+
912
+ /**
913
+ * Increment the permission generation counter.
914
+ * Call this when any permission layer changes (OS, app, broker, or module grants).
915
+ */
916
+ export function invalidatePermissionCache(): void {
917
+ _permissionGeneration++;
918
+ }
919
+
920
+ /**
921
+ * Set the effective capability bitmask for a module.
922
+ * Called at module load time with the precomputed intersection of all 4 layers.
923
+ *
924
+ * @param moduleId The module's numeric ID
925
+ * @param capabilities Array of granted capability strings
926
+ */
927
+ export function setModuleCapabilities(moduleId: number, capabilities: Capability[]): void {
928
+ // Grant capabilities through native layer
929
+ if (typeof (globalThis as any).__exactGrantCapability === 'function') {
930
+ for (const cap of capabilities) {
931
+ (globalThis as any).__exactGrantCapability(moduleId, cap);
932
+ }
933
+ }
934
+
935
+ // Set up fast-path bitmask cache
936
+ let lo = 0;
937
+ let hi = 0;
938
+ for (const cap of capabilities) {
939
+ const bit = getCapabilityBit(cap);
940
+ if (bit !== undefined) {
941
+ if (bit < 32) {
942
+ lo |= (1 << bit);
943
+ } else {
944
+ hi |= (1 << (bit - 32));
945
+ }
946
+ }
947
+ }
948
+ _effectiveBits.set(moduleId, [lo, hi]);
949
+ _cachedGeneration.set(moduleId, _permissionGeneration);
950
+ }
951
+
952
+ /**
953
+ * Clear capability bitmask for a module.
954
+ * Call when a module is unloaded.
955
+ */
956
+ export function clearModuleCapabilities(moduleId: number): void {
957
+ _effectiveBits.delete(moduleId);
958
+ _cachedGeneration.delete(moduleId);
959
+ }
960
+
961
+ /**
962
+ * Fast boolean capability check — no allocation on the success path.
963
+ * Returns true if the module has the capability, false otherwise.
964
+ *
965
+ * This is the hot path: a single Map lookup + bitwise AND.
966
+ *
967
+ * @param moduleId The module's numeric ID
968
+ * @param capability The capability to check
969
+ * @returns true if allowed, false if denied or unknown
970
+ */
971
+ export function checkCapabilityFast(moduleId: number, capability: Capability): boolean {
972
+ const bit = getCapabilityBit(capability);
973
+ if (bit === undefined) return false; // unknown capability
974
+
975
+ const bits = _effectiveBits.get(moduleId);
976
+ if (!bits) return false;
977
+
978
+ // Check generation
979
+ const gen = _cachedGeneration.get(moduleId);
980
+ if (gen !== _permissionGeneration) return false; // stale cache
981
+
982
+ if (bit < 32) {
983
+ return (bits[0] & (1 << bit)) !== 0;
984
+ }
985
+ return (bits[1] & (1 << (bit - 32))) !== 0;
986
+ }
987
+
988
+ // ============================================================================
989
+ // Capability Helpers
990
+ // ============================================================================
991
+
992
+ /**
993
+ * Parse a capability string into category and action.
994
+ *
995
+ * @param capability Full capability string (e.g., "network:fetch:api.example.com")
996
+ * @returns Parsed capability parts
997
+ */
998
+ export function parseCapability(capability: Capability): {
999
+ category: string;
1000
+ action: string;
1001
+ resource?: string;
1002
+ } {
1003
+ const parts = capability.split(':');
1004
+ return {
1005
+ category: parts[0] || '',
1006
+ action: parts[1] || '',
1007
+ resource: parts[2],
1008
+ };
1009
+ }
1010
+
1011
+ /**
1012
+ * Check if a capability matches a grant pattern.
1013
+ * Supports wildcards like "network:*" or "*".
1014
+ *
1015
+ * @param capability The capability to check
1016
+ * @param grant The grant pattern to match against
1017
+ */
1018
+ export function capabilityMatches(capability: Capability, grant: Capability): boolean {
1019
+ if (grant === '*') return true;
1020
+ if (grant === capability) return true;
1021
+
1022
+ const capParts = capability.split(':');
1023
+ const grantParts = grant.split(':');
1024
+
1025
+ // Check each part
1026
+ for (let i = 0; i < grantParts.length; i++) {
1027
+ if (grantParts[i] === '*') return true;
1028
+ if (grantParts[i] !== capParts[i]) return false;
1029
+ }
1030
+
1031
+ // Grant must be at least as specific as capability
1032
+ return grantParts.length <= capParts.length;
1033
+ }
1034
+
1035
+ // ============================================================================
1036
+ // Common Capability Constants
1037
+ // ============================================================================
1038
+
1039
+ /**
1040
+ * Well-known capability names for type safety.
1041
+ * @see OS_CAPABILITY_SECURITY_OVERVIEW.md
1042
+ * @see JS_CAPABILITY_SECURITY_MODEL_SPEC_AND_PLAN.md
1043
+ */
1044
+ export const Capabilities = {
1045
+ // Network
1046
+ NETWORK_FETCH: 'network:fetch',
1047
+ NETWORK_CONNECT: 'network:connect',
1048
+ NETWORK_LISTEN: 'network:listen',
1049
+ NETWORK_LOCAL: 'network:local', // Local network access (mDNS, Bonjour)
1050
+
1051
+ // File System
1052
+ FS_READ: 'fs:read',
1053
+ FS_WRITE: 'fs:write',
1054
+ FS_LIST: 'fs:list',
1055
+ FS_WATCH: 'fs:watch',
1056
+
1057
+ // Environment
1058
+ ENV_READ: 'env:read',
1059
+
1060
+ // Process
1061
+ PROCESS_SPAWN: 'process:spawn',
1062
+ PROCESS_SIGNAL: 'process:signal',
1063
+ PROCESS_CWD: 'process:cwd',
1064
+
1065
+ // Device - Location (granular per OS_CAPABILITY_SECURITY_OVERVIEW.md Section 7.1)
1066
+ DEVICE_LOCATION: 'device:location',
1067
+ DEVICE_LOCATION_WHEN_IN_USE: 'device:location:whenInUse',
1068
+ DEVICE_LOCATION_ALWAYS: 'device:location:always',
1069
+ DEVICE_LOCATION_PRECISE: 'device:location:precise',
1070
+ DEVICE_LOCATION_REDUCED: 'device:location:reduced',
1071
+
1072
+ // Device - Camera/Microphone
1073
+ DEVICE_CAMERA: 'device:camera',
1074
+ DEVICE_MICROPHONE: 'device:microphone',
1075
+
1076
+ // Device - Photos (granular per OS_CAPABILITY_SECURITY_OVERVIEW.md Section 7.3)
1077
+ DEVICE_PHOTOS: 'device:photos',
1078
+ DEVICE_PHOTOS_READ: 'device:photos:read',
1079
+ DEVICE_PHOTOS_WRITE: 'device:photos:write',
1080
+ DEVICE_PHOTOS_LIMITED: 'device:photos:limited',
1081
+
1082
+ // Device - Contacts
1083
+ DEVICE_CONTACTS: 'device:contacts',
1084
+ DEVICE_CONTACTS_READ: 'device:contacts:read',
1085
+ DEVICE_CONTACTS_WRITE: 'device:contacts:write',
1086
+
1087
+ // Device - Sensors
1088
+ DEVICE_SENSORS: 'device:sensors',
1089
+ DEVICE_MOTION: 'device:motion',
1090
+
1091
+ // Device - Bluetooth (granular per OS_CAPABILITY_SECURITY_OVERVIEW.md Section 7.5)
1092
+ DEVICE_BLUETOOTH: 'device:bluetooth',
1093
+ DEVICE_BLUETOOTH_SCAN: 'device:bluetooth:scan',
1094
+ DEVICE_BLUETOOTH_CONNECT: 'device:bluetooth:connect',
1095
+ DEVICE_BLUETOOTH_ADVERTISE: 'device:bluetooth:advertise',
1096
+
1097
+ // Device - Biometrics (Face ID, Touch ID, fingerprint)
1098
+ DEVICE_BIOMETRICS: 'device:biometrics',
1099
+
1100
+ // Device - Credentials (Keychain/Credential Manager)
1101
+ DEVICE_CREDENTIALS: 'device:credentials',
1102
+ DEVICE_CREDENTIALS_READ: 'device:credentials:read',
1103
+ DEVICE_CREDENTIALS_WRITE: 'device:credentials:write',
1104
+
1105
+ // Device - Notifications
1106
+ DEVICE_NOTIFICATIONS: 'device:notifications',
1107
+
1108
+ // Device - Calendar
1109
+ DEVICE_CALENDAR: 'device:calendar',
1110
+ DEVICE_CALENDAR_READ: 'device:calendar:read',
1111
+ DEVICE_CALENDAR_WRITE: 'device:calendar:write',
1112
+
1113
+ // Device - Reminders
1114
+ DEVICE_REMINDERS: 'device:reminders',
1115
+
1116
+ // Device - Health (HealthKit/Health Connect)
1117
+ DEVICE_HEALTH: 'device:health',
1118
+ DEVICE_HEALTH_READ: 'device:health:read',
1119
+ DEVICE_HEALTH_WRITE: 'device:health:write',
1120
+
1121
+ // Storage
1122
+ STORAGE_LOCAL: 'storage:local',
1123
+ STORAGE_SESSION: 'storage:session',
1124
+ STORAGE_PERSIST: 'storage:persist', // Request persistent storage
1125
+
1126
+ // SQLite
1127
+ SQLITE_READ: 'sqlite:read',
1128
+ SQLITE_WRITE: 'sqlite:write',
1129
+
1130
+ // Clipboard
1131
+ CLIPBOARD_READ: 'clipboard:read',
1132
+ CLIPBOARD_WRITE: 'clipboard:write',
1133
+
1134
+ // Crypto
1135
+ CRYPTO_RANDOM: 'crypto:random',
1136
+ CRYPTO_SUBTLE: 'crypto:subtle',
1137
+
1138
+ // Time
1139
+ TIME_NOW: 'time:now',
1140
+ TIME_HIGHRES: 'time:highres',
1141
+
1142
+ // IPC
1143
+ IPC_CHANNEL: 'ipc:channel',
1144
+ } as const;
1145
+
1146
+ export type WellKnownCapability = typeof Capabilities[keyof typeof Capabilities];